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

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

  • 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  
#include  

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  
#include  
#include 

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进程模型——会话和进程组)