Why doesn't CTRL-C stop NET USE - [2004-03-19]
十四年来,John Vert一在向我抱怨这个问题,为什么Ctrl-C无法打断cmd中的"net use"命令。这到底是为什么,为什么我不能用Ctrl-C打断它?事实是,一系列复杂因素凑到一起造成这种用户体验欠佳的局面。
首先是Ctrl-C在控制台程序中的处理方式。若程序调用过SetConsoleCtrlHandler,控制台子系统(csrss)记录回调函数地址,将Ctrl-C被按下时,csrss向目标进程插入一个新线程调用前述回调函数。如果控制台窗口有多个进程(考虑cmd中执行net.exe之类的情形),csrss按照注册顺序依次调用前台进程的回调函数,直至某个回调函数返回TRUE,表明Ctrl-C得到处理。若程序未调用SetConsoleCtrlHandler,缺省的回调函数是ExitProcess。
现在,cmd.exe有注册Ctrl-C的回调函数,但net.exe没有,所以按下Ctrl-C时,系统会安排net.exe调用ExitProcess。到目前为止,一切正常。
但是有个问题,net.exe阻塞在WNetAddConnection2,其内部阻塞在一个同步ioctl,而ExitProcess的语义要求其等待这个同步ioctl结束。难道没有一种机制来取消这种未完成的I/O吗?确实有一个,CancelIo就是干这事的。但你仔细看CancelIo的文档,它只能取消由本线程发起的I/O操作。注意,csrss创建了一个全新的线程来执行Ctrl-C的回调函数,该线程并未发起任何I/O操作,所有的I/O都由net.exe的主线程发起。
cmd.exe可以响应Ctrl-C,但此时cmd.exe不是前台进程,net.exe是前台进程,后者优先处理Ctrl-C。于是,你只能等啊等啊等啊。
每次John在大厅碰上我,都会问我什么时候修复Ctrl-C的问题。
scz: 前些日子我写《从僵死的调试器中抢救被调试进程》时,用kd调试Ctrl-C的响应过程,看到csrss向目标进程插入新线程以调用回调函数,当时的前台进程是cdb.exe,回调函数是KERNELBASE!CtrlRoutine。
scz: LO这篇是2004年写的,现在是2022年,Ctrl-C仍然打不断net.exe,估计微软不会考虑在net.exe中使用异步I/O,三十多年了,爱咋的咋的?LO应该已不在微软,John Vert呢?