终端会话和孤儿进程组(POSIX-2.2.2.52)--引出问题

希望shell和一个程序都同时接收一个ctrl-c,最好的办法就是将它们设置到一个进程组当中,并且把这个进程组设置为终端上的前台进程组,如下所示,其中2774是该终端上bash进程的pid:
void handler(int n)
{
        printf("GOT SIGINT/n");
}
int main()
{
        setpgid(0, 2774);  //将test和bash设置成同一个进程组
        signal(SIGINT, handler);
        while (1) {
                sleep(1);
                //kill(0, SIGINT);    //先注释掉
        }
}
编译为test,在运行test之前先设置一下bash的trap,使得bash也能捕捉到SIGINT信号:
trap 'echo got sigint' INT
希望的情况是,运行test后按下ctrl-c,屏幕上显示GOT SIGINT和got sigint,可是一个也没有显示,为何?
这就涉及到了控制终端,进程组等概念了。bash没有打印got sigint是因为当bash调用fork-exec启动test的时候,已经调用了ioctl(fd, TIOCSPGRP, &子进程pid);而ioctl会将子进程即test的pid设置成tty的pgrp字段,这个事实通过两种方式可以了解到,第一可以通过strace命令,其次可以通过看一下bash和linux内核的源码,内核在接收到ctrl-c的时候会将此键码作为普通字符放入终端的接收缓冲中去,就是调用终端行规程的receive_buf。然后receive_buf最终发现ctrl-c控制字符后会调用isig函数,后者继续调用kill_pg(tty->pgrp, sig, 1);最终tty->pgrp决定了要发送信号给谁。bash既然已经将tty的pgrp改成了test的pid,而test又把自己交给了bash,在调用setpgid的时候,test会将自己从pgid表上detach,因此for_each_task_pid(tty->pgrp, PIDTYPE_PGID, p, l, pid)将一个进程也找不到,所以就谁也收不到了。
     现在放开kill代码行的注释,再次编译运行,自己发送信号,GOT SIGINT成功显示了,可是bash的got sigint却没有显示,这是因为bash在wait子进程的结束,没有打印got sigint是因为此时终端被前台的test独占了,SIGINT其实已经pendding到bash了,不信的话将test结束掉,看看got sigint是不是打印出来了。既然是bash调用ioctl将tty的pgrp给改了,那么改回来是不是就可以了呢?倒是可以试一下,重新注释掉kill行并且添加下面的代码:
tcsetpgrp(fd, 2774);
或者下面的:
ioctl(fd, TIOCSPGRP, &id);
结果连调用都不成功...为何bash就可以将终端的的pgrp设置成别人的而test就不行呢?难道bash是什么特权进程吗?unix/linux中是没有特权进程的,所以还要从test本身来找原因,毕竟它非常短小。我们看到程序一开始有个setpgid的调用,这里面有点说法。内核是不负责设置pgid或者session的,这些都是bash的事情,内核函数copy_process中可以看出,只要不是创建线程,内核会将所有的进程都设置到一个进程组中,然而事实上系统的众多进程却不是属于同一个进程组,这就是bash在中间做了点事情,bash在启动新进程时会调用setpgid将新进程的pgid设置成其pid,因此所有的子进程都单独占有一个进程组,关键问题就是为何调用了setpgid之后就不能能再设置终端的pgrp了呢?这里面的原因在于POSIX的一个规定,那就是不能将孤儿进程组设置成tty的前台进程组,所谓的孤儿进程组就是组内的所有成员要么自己和父亲在同一个组,这样的话受影响是一致的,要么就是自己和父亲属于不同的session,如此一来终端信号将在父子之间隔离,而我们的test中经过第一行那么一设置之后,pgrp组就成了孤儿进程组,里面一共就两个进程,一个bash,一个test,因此后面的TIOCSPGRP调用将不会成功。
     最后,如果非要实现ctrl-c影响两个进程的话,办法是有的,那就是执行test &,后面要加一个&号,bash在执行这一类进程的时候将不会调用TIOCSPGRP的ioctl让子进程独占终端,相反bash会让自己占据终端,这样执行后按下ctrl-c,将同时输出got sigint和GOT SIGINT

你可能感兴趣的:(session,kill,bash,终端,Signal,linux内核)