Linux下的终端和作业控制

来源:互联网 发布:淘宝客为什么要用软件 编辑:程序博客网 时间:2024/06/17 16:45

一、进程间的关系

进程间的三种关系:进程组、作业和会话。
1、进程组
每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一作业相关联,可以接收来自同一终端的各种信号。每个进程组有一个唯一的进程组ID。每个进程组都可以有一个组长进程(该组进程的第一个,组成员ID等于组长ID)。组长进程的标识是,其进程组ID等于其进程ID。

组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。

2、作业
1>Shell分前后台来控制的不是进程而是作业(Job)或者进程组(Process Group)。一个前台作业可以由多个进程组成,一个后台也可以由多个进程组成,Shell可以运行一个前台作业和任意多个后台作业,这称为作业控制

2>作业与进程组的区别:
如果作业中的某个进程又创建了子进程,则子进程不属于作业,但是却属于进程组。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在(如果这个子进程还没终止),它自动变为后台进程组。(一般情况进程组合作业不做区别)

查看后台作业:jobs
将前台作业提至后台:bg 1(作业号) 先要contrl C终止后台作业
将后台作业提至前台:fg 1(作业号)

3>实例:fork一个子进程,如果父进程先退出,作业结束,进程组还在,shell成为前台作业,子进程成为后台进程组,ctrl C终止不了。

 8 #include<stdio.h>  9 #include<unistd.h> 10 #include<stdlib.h> 11  12 int main() 13 { 14     pid_t pid = fork(); 15     while(1) 16     { 17         if(pid == -1) 18         { 19             perror("fork error"); 20             return -1; 21         } 22         else if(pid > 0)//father 23         { 24             printf("i am father\n"); 25             exit(1);//父进程退出 26         } 27         else//child 28         { 29             printf("i am child\n"); 30             sleep(1); 31         } 32     } 33     return 0; 34 }

这里写图片描述

如果作业中的某个进程又创建了子进程,则子进程属于进程组不属于作业,如果父子进程同时在前台运行,ctrl C可同时终止父子进程,ctrl C向前台进程组发信号而不是向前台作业发信号。

 8 #include<stdio.h>  9 #include<unistd.h> 10 #include<stdlib.h> 11  12 int main() 13 { 14     pid_t pid = fork(); 15     while(1) 16     { 17         if(pid == -1) 18         { 19             perror("fork error"); 20             return -1; 21         } 22         else if(pid > 0)//father 23         { 24             printf("i am father\n"); 25             sleep(1); 26         } 27         else//child 28         { 29             printf("i am child\n"); 30             sleep(1); 31         } 32     } 33     return 0; 34 }

这里写图片描述

3、会话
会话(Session)是一个或多个进程组的集合。一个会话可以有一个控制终端。这通常是登陆到其上的终端设备(在终端登陆情况下)或伪终端设备(在网络登陆情况下)。建立与控制终端连接的会话首进程被称为控制进程。
一个会话中的几个进程组可被分为一个前台进程组以及一个或多个后台进程组。所以一个会话中,应该包括控制进程(会话首进程),一个前台进程组和任意后台进程组。

新打开一个终端即新建立一个会话的过程。关闭一个终端,即关闭一次会话 ,其中所有的作业,进程组都不存在了。

1 $ proc1 | proc2 &2 $ proc3 | proc4 | proc5

其中proc1与proc2属于同一个后台进程组,proc3,proc4和proc5属于同一个前台进程组,Shell本身属于一个单独的进程组。这些进程组的控制终端相同,它们同属于一个会话,当用户在控制终端输入特殊的控制键(如Ctrl+C,产生SIGINT,Ctrk+,产生SIGQUIT,Ctrl+Z,
产生SIGTSTP),内核发送相应的信号给前台进程组中的所有进程

二、终端

1、终端的基本概念
在UNIX系统中,用户通过终端登录系统后得到一个Shell进程,这个终端成为Shell进程的控制终端 (Controlling Terminal),控制终端是保存在PCB中的信息,而我们知道fork会复制PCB中的信息,因此由Shell进程启动的其它进程的控制终端也是这个终端。默认情况 下(没有重定向),
每个进程的标准输入、标准输出和标准错误输出都指向控制终端,进程从标准输入读也就是读用户的键盘输入,进程往标准输出或标准错误输出写也就是输出到显示器上。此外在控制终端输入一些特殊的控制键可以给前台进程发信号,例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。

2、每个进程都可以通过一个特殊的设备文件/dev/tty访问它的控制终端。
ttyname函数可以由文件描述符查出对应的文件名,该文件描述符必须指向一个终端设备而不 能是任意文件。

查看终端对应的设备 
这里写图片描述

终端一:
这里写图片描述

终端二:
这里写图片描述

再重新打开一个终端:
这里写图片描述

3、终端登录过程(可选)
一台PC通常只有一套键盘和显示器,也就是只有一套终端设备,但是可以通过Ctrl-Alt-F1~Ctrl-Alt- F6切换到6个字符终端,相当于有6套虚拟的终端设备,它们共用同一套物理终端设备,对应的设备文件分别是/dev/tty1~/dev/tty6,所以称为虚拟终端(Virtual Terminal)。

设备文件/dev/tty0表示当前虚拟终端,比如切换到Ctrl-Alt-F1的字符终端时/dev/tty0就表示/dev/tty1,切换到Ctrl-Alt- F2的字符终端时/dev/tty0就表示/dev/tty2,就像/dev/tty一样也是一个通用的接口,但它不能表示图形终端窗口所对应的终端。

内核中处理终端设备的模块包括硬件驱动程序线路规程(Line Discipline)。
这里写图片描述

硬件驱动程序负责读写实际的硬件设备,比如从键盘读入字符和把字符输出到显示器,线路规程像一个过滤器,对于某些特殊字符并不是让它直接通过,而是做特殊处理,比如在键盘上按下Ctrl- Z,对应的字符并不会被用户程序的read读到,而是被线路规程截获,解释成SIGTSTP信号发给前台进程,通常会使该进程停止。线路规程应该过滤哪些字符和做哪些特殊处理是可以配置的。

终端设备有输入和输出队列缓冲区,如下图所示。
这里写图片描述
以输入队列为例,从键盘输入的字符经线路规程过滤后进入输入队列,用户程序以先进先出的顺序从队列中读取字符,一般情况下,当输入队列满的时候再输入字符会丢失,同时系统会响铃警报。终端可以配置成回显(Echo)模式,在这种模式下,输入队列中的每个字符既送给用户程序也送给输出队列,因此我们在命令行键入字符时,该字符不仅可以被程序读取,我们也可以同时在屏幕上看到该字符的回显。

终端的登录过程:
这里写图片描述
终端登录步骤:
1>系统启动时,init进程根据配置文件/etc/inittab确定需要打开哪些终端;
2>getty根据命令行参数打开终端设备作为它的控制终端,把文件描述符0、1、2都指向控制终端,然后提示用户输入帐号。用户输入帐号之后,getty的任务就完成了,它再执行login程序:

execle("/bin/login", "login", "-p", username, NULL, envp);

3>login程序提示用户输入密码(输入密码期间关闭终端的回显),然后验证帐号密码的正确性。
如果密码不正确,login进程终止,init会重新fork/exec一个getty进程。如果密码正确,login程序设置一些环境变量,设置当前工作目录为该用户的主目录,然后执行Shell:

execl("/bin/bash", "-bash", NULL);

注意argv[0]参数的程序名前面加了一个-,这样bash就知道自己是作为登录Shell启动的,执行登录Shell的启动脚本。从getty开始exec到login,再exec到bash,其实都是同一个进程,因此控制终端没变,文件描述符0、1、2也仍然指向控制终端。由于fork会复制PCB信息,所以由Shell启动的 其它进程也都是如此。

三、作业控制

1、作业控制的基本概念
一个前台作业可以由多个进程组成,一个后台作业也可以由多个进程组成,Shell可以同时运行一个前台作业和任意多个后台作业,这称为作业控制(Job Control)。

例如用以下命令启动5个进程:

$ proc1 | proc2 &$ proc3 | proc4 | proc5

其中proc1和proc2属于同一个后台进程组,proc3、proc4、proc5属于同一个前台进程组,Shell进程本身属于一个单独的进程组。这些进程组的控制终端相同,它们属于同一个Session。当用户在控制终端输入特殊的控制键(例如Ctrl-C)时,内核会发送相应的信号(例如SIGINT)给前台进程组的所有进程。各进程、进程组、Session的关系如下图所示:
这里写图片描述

2、与作业控制有关的信号
下面通过实验来理解与作业控制有关的信号。
这里写图片描述
将cat放到后台运行,由于cat需要读标准输入(也就是终端输入),而后台进程是不能读终端输入的,因此内核发SIGTTIN信号给进程,该信号的默认处理动作是使进程停止。

这里写图片描述
jobs命令可以查看当前有哪些作业。fg命令可以将某个作业提至前台运行,如果该作业的进程组正在后台运行则提至前台运行,如果该作业处于停止状态,则给进程组的每个进程发SIGCONT信号使它继续运行。参数%1表示将第1个作业提至前台运行。cat提到前台运行后,挂起等待终端输入,当 输入hello world并回车后,cat打印出同样的一行,然后继续挂起等待输入。如果输入Ctrl-Z则向所有前台进程发SIGTSTP信号,该信号的默认动作是使进程停止,cat继续以后台作业的形式存在。

这里写图片描述

这里写图片描述
bg命令可以让某个停止的作业在后台继续运行,也需要给该作业的进程组的每个进程发SIGCONT信号。cat进程继续运行,又要读终端输入,然而它在后台不能读终端输入,所以又收到SIGTTIN信号而停止。

用kill命令给一个停止的进程发SIGTERM信号,这个信号并不会立刻处理,而要等进程准备继续运行之前处理,默认动作是终止进程。但如果给一个停止的进程发SIGKILL信号就不同了。
这里写图片描述

防止后台程序想显示其上打印消息:stty tostop
这里写图片描述
首先用stty命令设置终端选项,禁止后台进程写,然后启动一个后台进程准备往终端写,这时进程 收到一个SIGTTOU信号,默认处理动作也是停止进程。紧接着放置前台,cat进程运行完成,退出。