进程组、作业、会话

进程组、作业、会话

一. 进程组

        我们知道,一个程序运行起来会变成一个进程,每个进程都有一个进程ID,但是除了这个ID,每个进程还属于一个进程组。进程组,顾名思义,是一个或多个进程的集合。每个进程组有一个唯一的进程组ID,且每个进程组都可以有一个组长ID。组长ID的标识是:进程组ID等于其进程ID,组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要进程组中有一个进程存在,该进程组就存在,与进程组的组长进程是否存在无关。我们可以看下面一个例子:


        首先,我们先让一个进程组在后台运行起来,“&”就是表示将进程组放在后台运行。然后用ps axj查看了进程组信息,其中选项的具体含义是:

a:列出所有用户,带控制终端的进程

x:列出当前用户带或不带控制终端的进程

j:表示列出与作业控制相关的信息

        我们可以看到,该进程组的组ID是PGID=6634,进程有三个,ID分别为6634、6635、6636,其中进程ID为6634的进程为该进程组的组长进程。我们首先杀死了组长进程,但该进程组依旧运行不受影响;再杀死进程ID为6635的进程,进程组依旧不受影响,直至整个进程组中没有进程了。

二. 作业

        当一个进程运行起来形成一个进程组之后,我们认为该进程组是为了完成某一任务的,可以将该任务称为一项作业。所以通常情况下,一个进程组是与一项作业相对应的,该进程组中的所有进程对应同一个作业。所以说,shell分前后台控制的是一项作业或一个进程组而不是一个进程。一个进程组由多个进程组成,所以一项作业也由多个进程组成。

        shell可以运行一个前台作业和任意多个后台作业,这叫做作业控制。

        作业与进程组的区别在于,如果作业中的某个进程又创建了子进程,该子进程不属于作业,但是依旧属于进程组。

        当前台没有作业运行时,shell在前台运行。当我们在前台运行我们的作业时,shell就被挤到了后台,此时shell接收不到用户输入的命令,即我们在前台输入命令时得不到反应。而一旦作业运行结束,shell就把自己提到前台。要注意的时,在作业运行时,若作业中的进程创建了子进程,该子进程在作业运行结束后还存在,它自动变为后台进程。

实例:

#include <stdio.h>                                                                                                                                    
#include <unistd.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {   
        perror("fork");
        return 1;
    }   
    else if(id == 0)//child
    {   
        while(1)
        {   
            printf("child[%d] I am child\n", getpid());
            sleep(1);
        }   
    }   
    else//parent
    {   
        int i = 5;
        while(i--)
        {   
            printf("parent[%d] I am goning to dead after %d\n", getpid(), i); 
            sleep(1);
        }   
    }   
    return 0;
}

        我们分析上述代码,在运行起来形成一个作业。在作业运行过程中,创建了一个子进程,它不属于该作业。所以在5s之后,父进程退出,作业结束,只剩子进程被提回后台运行,shell提回前台。具体运行结果如下:


三. 会话

        会话是一个或多个进程组的结合。一个会话可以有一个控制终端。通常是在登录在其上的终端设备(在终端登录情况下)或伪终端设备(在网络登录情况下:xshell等)。建立与控制终端连接的会话首进程被称为控制进程

        一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程)、一个前台进程组、任意个后台进程组。

       当新打开一个终端,会创建一个会话。同时会有一个会话首进程(控制进程)将该会话与控制终端相连接。此后,在该会话中创建的进程在向终端打印内容时,都会打印到与控制进程相连接的终端下。具体演示如下:

(下图借鉴于博客点击打开链接


        首先,打开一个终端,在该终端下运行2个进程组:1000 | 2000 和3000 | 4000。

        上图中SID表示会话ID,TTY表示终端号。我们发现两个进程组的会话ID均为3406,即这两个进程组同属于一个会话,与该会话相连接的控制终端是:pts/0,且这两个进程组中的进程的父进程ID均为3406。

        查看后,我们发现3406其实是bash(简单介绍一下bash表示终端是本地登录的,-bash表示终端是网络登陆的)在这里,bash充当3个角色:(1)各进程的父进程;(2)会话ID与bash相同;(3)会话首进程即控制进程(默认为bash)

        接着我们使用kill命令将bash杀死后,即杀死控制进程后,该会话与控制终端pts/0失去连接,也就是该会话没有控制终端了。上图显示,杀死bash之后,又进入了一个新的会话或新的控制终端。

        在新的控制终端上创建一个进程5000,查看后,发现该进程所属的会话ID为3450,与该会话连接的控制终端为pts/1。且上面两个进程组中进程因为父进程bsah被杀死,所以都变成了孤儿进程被1号进程收养。它们所属的会话失去了控制终端pts/0,变成了?。但是该会话还存在,因为会话ID仍为3406。

注意:

        (1)杀死话首进程只是与终端失去连接,并不等于杀死会话,要杀死一个会话,要通过注销的方式进程。

        (2)话首进程自成一个作业或进程组。

        所以,一个进程的PCB中还包括:


四. 作业控制

1. 作业控制相关命令

        在前面介绍作业时,提到过“shell可以同时运行一个前台作业和一个或多个后台作业”,这就称为作业控制。下面介绍几个作业控制的相关命令:

jobs:显示当前终端下的进程组或作业

fg+作业号:将后台作业提到前台

bg+作业号:使处于stopped状态的进程变为Running状态

这里要注意的是,ctrl+c杀掉的不是进程,而是整个作业。

2. 作业控制相关信号

分析上图过程如下:

(1)首先我们在后台运行cat命令。因为cat命令需要读取标准输入,但后台作业又不能使用标准输入,所以cat被提到后台后,为解决二者之间的矛盾,内核会自动向cat进程发送SIGTTIN信号,使进程处于Stopped状态。所以我们jobs查看时,cat处于stopped状态;

(2)然后我们利用fg命令将cat作业提到前台(如果该作业处于停止状态,就发送SIGCOUNT信号来唤醒它,使其处于Running状态),此时cat便可以正常的执行了;

(3)键盘输入ctrl+z向cat作业发送SIGTSTP信号,使该作业处于Stopped状态,因此cat作业又被提到后台运行;

(4)再利用bg命令向作业发送SIGCOUNT信号,将处于Stopped状态的作业唤醒使其处于Running状态。但是,因为cat作业主要读取标准输入,而后台作业又不能读取标准输入。所以,此时,在bg发送SIGCOUNT命令之后,内核又会发送SIGTTIN信号给cat作业,使其继续处于Stopped状态;

(5)最后用kill命令向cat作业发送15号信号时,因为cat处于后台,所以接收不到该信号。但是只要当cat作业回到前台,就会处理该信号,使cat作业终止。




猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/80420652