曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 4794|回复: 0
打印 上一主题 下一主题

[进程] setsid() -- 设置会话 ID及进程组 ID

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34397
跳转到指定楼层
楼主
发表于 2012-7-26 15:58:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
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          # 不接受原来终端的控制
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2025-6-17 23:01 , Processed in 0.078837 second(s), 23 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表