Linux 作业控制

Linux 作业控制

作业控制指的是,在一个登录会话里,允许用户在不通进程组(或者jobs)之间的切换。

作业控制的概念

交互式shell最基础的功能是从用户终端读取命令,然后生成进程来执行这些命令。主要是通过fork和exec来结合实现。

进程组

一个单独的命令就能运行一个进程,但是往往是一条命令存在多个进程,比如管道的使用,往往就会引入多个进程。

这些来自于同一条命令的进程们被称作进程组,所谓进程组,有一个特性就是我们能够一次性对它们操作。比如,终端输入Ctrl+C,这时候会产生SIGINT信号,从而终止前端进程组内的所有进程。看下面例子

  • PID: 当前进程ID
  • PPID: 父进程ID
  • SID: 当前会话ID
  • PGID: 进程组ID
$ ps -o pid,ppid,sid,pgid,command
  PID  PPID   SID  PGID COMMAND
17509 17505 17509 17509 -bash
17539 17509 17509 17539 ps -o pid,ppid,sid,pgid,command

ps 和 -bash 是父子进程关系,但是不属于同一个进程组,再来看下面的例子。

$ ps -o pid,ppid,sid,pgid,command | cat
  PID  PPID   SID  PGID COMMAND
17509 17505 17509 17509 -bash
17558 17509 17509 17558 ps -o pid,ppid,sid,pgid,command
17559 17509 17509 17558 cat

ps和cat这两个进程的父进程都是-bash,且属于同一个进程组。再来看下面的例子。

$ cat > /tmp/1.txt &
[1] 17589
$ -->>回车
[1]+  Stopped                 cat > /tmp/1.txt
$ ps -o pid,ppid,sid,pgid,command 
  PID  PPID   SID  PGID COMMAND
17509 17505 17509 17509 -bash
17589 17509 17509 17589 cat
17593 17509 17509 17593 ps -o pid,ppid,sid,pgid,command

ps和cat有共同的ppid,sid,但是却是不同的PGID,这说明这两个进程不是同一个进程组。怎么解释上面的过程?看下面

$ cat > /tmp/1.txt &
& 是后台执行,这里是变成了一个作业,bash fork调用setpgid(),设置子进程PID成为单独的进程组,

会话

会话是一些列进程组的集合,正常情况下,所有从同一个登录shell出来的进程属于一个会话。

每个进程都属于一个进程组,当一个进程被创建,默认是和父进程属于同一个进程组,同一个会话。但是可以通过setpgid来设置其到另一个进程组,前提是这个进程组也属于同一个会话。

要把进程放到一个不同的会话的唯一方法就是,使它成为一个会话的第一个初始化进程,也就是session leader,可以通过setsid来实现,同时也会把这个session leader设置成一个新的进程组,之后再不能把它从该进程组移除。

一个新的会话都是被系统的login程序创建的,而默认的session leader就是用户登录所执行的shell程序

shell程序要想支持作业控制,必须要能控制哪个job在何时能占用终端,否则多个jobs同时向终端请求读入,会导致用户的输入不知道往哪个jobs写入。所以,要实现这个功能,shell就必须要和终端驱动程序打交道。

Shell在某一时刻让单独一个进程组占用终端,这个进程组就叫做前端作业,而其余的那些被shell控制,不能访问终端的作业都是后台作业。

如果后台作业尝试去读终端,会被终端驱动程序stopped。用户可以通过(Ctrl+z)挂起一个前台运行的作业,shell的一个功能就是通知用户什么时候job停止,并且提供一种机制能让用户来交互式地继续运行stopped的job,并且对前台作业和后台作业切换。

终端控制

进程的一个属性就是控制终端,子进程被fork后继承了父进程对终端的控制。从这点看,一个会话中的所有进程从session leader继承了控制终端的属性。一个有控制终端的session leader叫做“终端的控制进程”。

当一个进程调用setsid的时候成为一个会话的leader,并且不能在控制终端。

访问控制终端

一个控制终端的前台进程可以随意访问终端,但是后台进程则不行。当一个后台的作业尝试去从控制终端读取,设备驱动程序会给这个进程组发送SIGTTIN信号,这个信号会导致这个进程组内所有进程都stop(除非它们捕捉信号并且处理),然后,如果reading进程忽略或者block这个信号,read就会失败,返回EIO错误。

同样的,当一个后台job尝试写终端,进程组就会收到一个SIGTTOU的信号,但是可以通过设置TOSTOP来修改,若果不设置这个标志(默认情况),那么后台job尝试写终端都会允许的,因为不会接收到SIGTTOU信号。写终端也会被允许,若果写进程把SIGTTOU信号被忽略或者block。

孤儿进程组

当一个终端的控制进程终止,它的终端就变成可用,可能会有新的会话在这个终端上建立(比如新的用户登录),这个可能会引发问题,如果任意一个“老会话”的进程尝试读取终端。

为了阻止这种情况发送,进程组会继续运行即使session leader已经停止,这些进程组就叫做孤儿进程组

当一个进程组变成孤儿进程组,会收到一个SIGHUP的信号,从而导致这些进程组都终止。然后,这些进程也可以忽略SIGHUP信号或者处理它,那么它就可以作为孤儿进程组继续运行,但是无法再使用终端。

装载请注明 转自firefoxbug Linux作业控制

标签:none