setsid() 函数原型如下:
[C++] 纯文本查看 复制代码 #include <unistd.h>
pid_t setsid(void);
setsid() 是个系统调用,它用来创建一个新的会话。如果调用该函数的不是一个进程组的组长,那么该函数就会创建一个新的会话,结果将发生下面 3 件事情:
1. 该进程将变成新会话首进程 (session leader)。会话首进程就是创建会话的进程。此时,该进程是新会话中的唯一进程。
2. 该进程成为一个新进程组的组长进程。新进程组 ID是该调用进程的进程 ID。
3. 该进程没有控制终端,如果之前有一个控制终端,那么这种联系也会被中断。下面的测试代码中将演示这种情况。
如上所说,当调用进程是非组长进程时会建立一个新的会话;如果是组长进程调用该函数,那么函数会返回出错,其错误号为 EPERM,即不允许着么操作。这是因为,如果这么做了,组长进程就会将自己放置在一个新的会话之中,而组中的其它进程成员仍待在原来的进程组中。事实上,一个新的进程组也不会因此而被创建,原因是显而易见的,因为原来的进程组的组 ID 和 它的组长 ID 相同,假设这个组长新建了一个会话同时又会建立一个进程组,那么这个新建的进程组的组 ID 将和它的 PID 相同,这样就会存在两个一样的组 ID(原来的和新建的),而这样的情况是不允许的,组ID 和 PID 一样,必须保持唯一性。因此,内核不允许组长进程调用该函数。
为了保证不发生上述情况,一般的做法是,先 fork() 出子进程,然后将父进程终止,而子进程继续。因为子进程继承了父进程的进程组ID,而它自己的 PID 则是新分配的,两者不可能相等,所以就保证子进程不会是一个进程组的组长。
测试代码:
[C++] 纯文本查看 复制代码 #define _XOPEN_SOURCE 500
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long)getpid(), (long)getpgrp(), getsid(0));
if (fork() != 0)
_exit( EXIT_SUCCESS ); //错误或父进程都退出
if (setsid() == -1) {
perror("setsid");
exit(EXIT_FAILURE);
}
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long)getpid(), (long)getpgrp(), getsid(0));
if (open("/dev/tty", O_RDWR) == -1) {
perror("open");
exit (EXIT_FAILURE);
}
return 0;
}
运行输出:[beyes@beyes process]$ ./setsid
PID=3182, PGID=3182, SID=1751
[beyes@beyes process]$ PID=3183, PGID=3183, SID=3183
open: No such device or address 上面程序中,一开始打印了出调用进程的 PID ,PGID 以及 SID。注意,接着输出了 shell 的命令行提示符,即 “[beyes@beyes process]$” ,这是因为 shell 注意到父进程的退出,所以它将准备继续接受命令的输入。在子进程被 fork 出来之后,它打印出了自己的 PID ,PGID, SID ,此时这 3 个值都是相同的,也就是说这个子进程自立门户了。因为新建会话的同时,将和原来的控制终端丢失(/dev/tty),因此当试图打开 /dev/tty 时,提示找不到该设备。需要注意的是,和原来的控制终端失去联系,并不意味着子进程不能在你的命令行窗口打印东西(如上面打印子进程的 PID ,PGID 等),而是说它不接受原来终端里发出的控制信号,如 Ctrl + c 或 Ctrl + d 等,你还能在终端界面上看到输出,是因为标准输出是和主从伪终端(/dev/pts/xxx)相关的,而不是和控制终端相关的。
关于主从伪终端的概念可以 man 4 ptmx 进行了解,也可以参考:http://www.groad.net/bbs/read.php?tid-7315.html
下面为了验证上述说法,我们将代码稍微修改:去掉打开 /dev/tty 失败时的 exit() 语句,不让在打开失败时退出程序,如:
[C++] 纯文本查看 复制代码 #define _XOPEN_SOURCE 500
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long)getpid(), (long)getpgrp(), getsid(0));
if (fork() != 0)
_exit( EXIT_SUCCESS ); //错误或父进程都退出
if (setsid() == -1) {
perror("setsid");
exit(EXIT_FAILURE);
}
printf("PID=%ld, PGID=%ld, SID=%ld\n", (long)getpid(), (long)getpgrp(), getsid(0));
if (open("/dev/tty", O_RDWR) == -1) {
perror("open");
// exit (EXIT_FAILURE);
}
sleep(10);
printf ("Not accept your command.. bye\n");
return 0;
}
在上面代码中,屏蔽掉了 exit(0 函数,然后休眠 10 秒,醒来后打印一句话。
在运行上面程序后,在 10 秒手动按下 Ctrl + c ,那么在等大约 10 秒时,你会看到最后的打印,整个过程为:beyes@beyes process]$ ./setsid
PID=3208, PGID=3208, SID=1751
[beyes@beyes process]$ PID=3209, PGID=3209, SID=3209
open: No such device or address
^C # 按下 Ctrl + c
[beyes@beyes process]$ Not accept your command.. bye # 不接受原来终端的控制 |