美文网首页
Linux进程模型——会话和进程组

Linux进程模型——会话和进程组

作者: 小忍甜甜圈 | 来源:发表于2018-03-09 23:33 被阅读0次

    先放总结给没空看全文的人:

    • session(会话)是用户登录系统以后所需的context(上下文)
    • process group(进程组)是一组相关联的进程,用来方便信号量的分发
    • session退出以后所有隶属于该session的进程组都会收到hup信号而挂起,这样就有了控制进程生命周期的作用
    • tty可以作为输入输出设备被绑定到一个session上,bash就是这么干的
    • 子进程会继承父进程的session和process group,可以通过setsid建立新的session来脱离继承的sid,从而逃离旧session的生命周期
    • 当使用killpg将信号传递给进程组的时候,这个信号会被分发至进程组的每个进程。所以进程组可以方便对进程的控制

    前述模型-父子进程

    在日常的学习生活中,普通人接触较多的主要是parent-children进程模型。这是一个简单明了的编程模型:

    1. 父进程fork()以后产生一个子进程,子进程继承父进程的资源
    2. 子进程exec()点什么
    3. 假如子进程退出,那么父进程就得wait() / signal(SIGCHLD,SIG_IGN),否则子进程会变僵尸进程
    4. 假如父进程退出,那么子进程就会变成孤儿进程,然后被init收养

    一个典型的父子进程模型的代码如下:

    #include  <stdio.h>
    #include  <sys/types.h>
    
    void  ChildProcess(void);                /* child process prototype  */
    void  ParentProcess(void);               /* parent process prototype */
    
    void  main(void)
    {
         pid_t  pid;
         signal(SIGCHLD,SIG_IGN);
         pid = fork();
         if (pid == 0)
              ChildProcess();
         else
              ParentProcess();
    }
    

    但是在多用户多终端下,简单的进程的父子进程模型难以描述复杂的进程关系,于是Linux在原有模型的基础上,引入了session(会话)和process group(进程组)的概念。

    Linux进程模型

    session

    因为Linux是多用户系统,每个用户都需要和系统相对独立的进行交互,于是session的概念就被引入进来。当用户log in,然后打开bash的时候,会通过系统调用setsid建立一个session。session就是用户此次登录的context,可以方便的用来存储信息、控制会话、分配资源。比如,session就是tty(控制终端)绑定的对象。下面是ssh登录远端服务器时,关于session创建的例子:

    1. 首先列出参与的进程信息
    UID        PID  PPID  PGID   SID  C STIME TTY          TIME CMD
    root      1488     1  1488  1488  0  2017 ?        00:02:22 /usr/sbin/sshd -D
    ubuntu   17132 17092 17092 17092  0 14:59 ?        00:00:00 sshd: ubuntu@pts/0
    ubuntu   17135 17132 17135 17135  0 14:59 pts/0    00:00:00 -bash
    ubuntu   17171 17135 17171 17135  0 15:00 pts/0    00:00:00 ps -ejf
    
    1. 服务端上守护进程sshd 1488监听到了client连接请求
    2. 守护进程fork一个子进程sshd: ubuntu 17132,来处理请求。可以看到这个子进程17132的SID与父进程1488不同,这里是通过系统调用setsid()建立了一个新的session
    3. 进程sshd 17132 forkexec了子进程bash作为交互界面,可以看到bash 17135又创建了一个新的session,并且绑定了一个虚拟控制终端(controlling terminal / tty)pts/0。至此我们终于可以通过sshd 17132pts/0输入交互信息了,这些输入最终会作为session 17135的stdin被bash接受并处理。
    4. 在bash中我们执行了ps -ejf查看进程信息,于是bash创建了一个子进程ps -ejf执行请求。可以看到子进程ps 17171拥有和bash相同的sidtty
    5. session 17135session leader也就是bash 17135退出的时候,在此session下的所有进程也会收到hup(挂断信号)而终止退出

    因此,偷懒的守护进程代码是这样:

    #include  <stdio.h>
    #include  <sys/types.h>
    #include <unistd.h>
    
    void  ChildProcess(void);                /* child process prototype  */
    void  ParentProcess(void);               /* parent process prototype */
    
    void  main(void)
    {
         pid_t  pid;
         signal(SIGCHLD,SIG_IGN);
         pid = fork();
         if (pid == 0)
         {
             setsid();  #守护进程建立新的session
             ChildProcess();
         }
         else
              exit();
    }
    

    首先可以发现本段代码相比父子进程模型多了setsid(),它的作用是建立新的session,sid就是当前进程的pid。当守护进程属于一个新的session以后,就会脱离父进程session的生命周期和tty,也就是说,daemon将不会因为父session关闭而被挂断,也不会将父tty作为标准输入输出。
    然后,为什么这是偷懒的代码呢,因为严谨的daemon需要两次fork来产生。下面描述完进程组的以后会给出标准daemon创建例程。

    Process Group

    进程组的存在是为了方便对一组进程进行统一的管理。比如我们可以通过killpg向进程组传递信号量。或者方便利用job controll来进行前后台进程组的切换。
    现在进程组的概念仍然比较抽象,以后有机会学到容器的虚拟化机理,应该能够深刻体会进程组的作用。

    下面是double fork的示例代码:

    fork();
    if (pid == 0) {
      setsid();
      fork();
      if (pid == 0) {
        childProcess();
      }
    }
    exit();
    

    double fork的原因主要有:

    • 如果只有一次fork,假如父进程并没有退出,而是需要继续运行其他代码,那么子进程daemon就不会被过继给init。在两次fork的情况下,第一个child process的作用只是用来产生daemon进程,fork完称以后可以直接exit,这样就能显式保证daemon在创建完成后就会被过继给init,而祖父进程仍然可以继续执行其他逻辑
    • 如果只有一次fork,那么daemon因为setsid,所以会是session leader。这意味着daemon将有权限将这个新创建的session绑定到tty上。而两次fork产生的daemon,因为第二个child process并没有setsid,所以并不是session leader,从而杜绝了这种情况发生 (这种说法来自stackoverflow,我目前并没有验证过)

    相关文章

      网友评论

          本文标题:Linux进程模型——会话和进程组

          本文链接:https://www.haomeiwen.com/subject/qjmpfftx.html