在上一篇中介绍了tty的相关原理,这篇将介绍跟tty密切相关的session和进程组。html
本篇主要目的是澄清一些概念,不涉及细节前端
session就是一组进程的集合,session id就是这个session中leader的进程ID。node
session的主要特色是当session的leader退出后,session中的全部其它进程将会收到SIGHUP信号,其默认行为是终止进程,即session的leader退出后,session中的其它进程也会退出。linux
若是session和tty关联的话,它们之间只能一一对应,一个tty只能属于一个session,一个session只能打开一个tty。固然session也能够不和任何tty关联。shell
session能够在任什么时候候建立,调用setsid函数便可,session中的第一个进程即为这个session的leader,leader是不能变的。常见的建立session的场景是:segmentfault
用户登陆后,启动shell时将会建立新的session,shell会做为session的leader,随后shell里面运行的进程都将属于这个session,当shell退出后,全部该用户运行的进程将退出。这类session通常都会和一个特定的tty关联,session的leader会成为tty的控制进程,当session的前端进程组发生变化时,控制进程负责更新tty上关联的前端进程组,当tty要关闭的时候,控制进程所在session的全部进程都会收到SIGHUP信号。后端
启动deamon进程,这类进程须要和父进程划清界限,因此须要启动一个新的session。这类session通常不会和任何tty关联。bash
进程组(process group)也是一组进程的集合,进程组id就是这个进程组中leader的进程ID。session
进程组的主要特色是能够以进程组为单位经过函数killpg发送信号函数
进程组主要用在shell里面,shell负责进程组的管理,包括建立、销毁等。(这里shell就是session的leader)
对大部分进程来讲,它本身就是进程组的leader,而且进程组里面就只有它本身一个进程
shell里面执行相似ls|more
这样的以管道链接起来的命令时,两个进程就属于同一个进程组,ls是进程组的leader。
shell里面启动一个进程后,通常都会将该进程放到一个单独的进程组,而后该进程fork的全部进程都会属于该进程组,好比多进程的程序,它的全部进程都会属于同一个进程组,当在shell里面按下CTRL+C时,该程序的全部进程都会收到SIGINT而退出。
shell中启动一个进程时,默认状况下,该进程就是一个前端进程组的leader,能够收到用户的输入,而且能够将输出打印到终端,只有当该进程组退出后,shell才能够再响应用户的输入。
但咱们也能够将该进程组运行在后台,这样shell就能够继续相应用户的输入,常见的方法以下:
启动程序时,在后面加&,如sleep 1000 &
,进程将会进入后台继续运行
程序启动后,能够按CTRL+Z让它进入后台,和后面加&不一样的是,进程会被暂停执行
对于后台运行的进程组,在shell里面体现为job的概念,即一个后台进程组就是一个job,job有以下限制:
默认状况下,只要后台进程组的任何一个进程读tty,将会使整个进程组的全部进程暂停
默认状况下,只要后台进程组的任何一个进程写tty,将有可能会使整个进程组的全部进程暂停(依赖于tty的配置,请参考TTY/PTS概述)
全部后台运行的进程组能够经过jobs命令查看到,也能够经过fg命令将后台进程组切换到前端,这样就能够继续接收用户的输入了。这两个命令的具体用法请参考它们的帮助文件,这里只给出一个简单的例子:
#一般状况下,sleep命令会一直等待在那里,直到指定的时间过去后才退出。 #shell启动sleep程序时,就将sleep放到了一个新的进程组, #而且该进程组为前端进程组,虽然sleep不须要输入,也没有输出, #但当前session的标准输入和输出仍是归它,别人用不了, #只有咱们按下CTRL+C使sleep进程退出后,shell本身从新变成了前端进程组, #因而shell从新具有了响应输入以及输出能力 dev@debian:~$ sleep 1000 ^C #咱们能够在命令行的后面加上&符号,shell仍是照样会建立新的进程组, #而且sleep进程就是新进程组的leader, #可是shell会将sleep进程组放到后端,让它成为后台进程组 #这里[1]是job id,1627是进程组的ID,即sleep进程的id dev@debian:~$ sleep 1000 & [1] 1627 #能够经过jobs命令看到当前有哪些后台进程组(job) dev@debian:~$ jobs [1]+ Running sleep 1000 & #使用fg命令带上job id,便可让后端进程组回到前端, #而后咱们使用CTRL+Z命令可让它再次回到后端,并暂停进程的执行 #CTRL+Z和&不同的地方就是CTRL+Z会让进程暂停执行,而&不会 dev@debian:~$ fg 1 sleep 1000 ^Z [1]+ Stopped sleep 1000 #Stopped状态表示进程在后台已经暂停执行了 dev@debian:~$ jobs [1]+ Stopped sleep 1000
deamon程序虽然也是一个session的leader,但通常它不会建立新的进程组,也没有job的管理功能,因此这种状况下一个session就只有一个进程组,全部的进程都属于一样的进程组和session。
咱们这里看一下shell做为session leader的状况,假设咱们在shell里面执行了这些命令:
dev@debian:~$ sleep 1000 & [1] 1646 dev@debian:~$ cat | wc -l & [2] 1648 dev@debian:~$ jobs [1]- Running sleep 1000 & [2]+ Stopped cat | wc -l
下面这张图标明了这种状况下它们之间的关系:
+--------------------------------------------------------------+ | | | pg1 pg2 pg3 pg4 | | +------+ +-------+ +-----+ +------+ | | | bash | | sleep | | cat | | jobs | | | +------+ +-------+ +-----+ +------+ | | session leader | wc | | | +-----+ | | | +--------------------------------------------------------------+ session
pg = process group(进程组)
bash是session的leader,sleep、cat、wc和jobs这四个进程都由bash fork而来,因此他们也属于这个session
bash也是本身所在进程组的leader
bash会为本身启动的每一个进程都建立一个新的进程组,因此这里sleep和jobs进程属于本身单独的进程组
对于用管道符号“|”链接起来的命令,bash会将它们放到一个进程组中
nohup是咋回事呢?nohup干了这么几件事:
将stdin重定向到/dev/null,因而程序读标准输入将会返回EOF
将stdout和stderr重定向到nohup.out或者用户经过参数指定的文件,程序全部输出到stdout和stderr的内容将会写入该文件(有时在文件中看不到输出,有多是程序没有调用flush)
屏蔽掉SIGHUP信号
调用exec启动指定的命令(nohup进程将会被新进程取代,但进程ID不变)
从上面nohup干的事能够看出,经过nohup启动的程序有这些特色:
nohup程序不负责将进程放到后台,这也是为何咱们常常在nohup命令后面要加上符号“&”的缘由
因为stdin、stdout和stderr都被重定向了,nohup启动的程序不会读写tty
因为stdin重定向到了/dev/null,程序读stdin的时候会收到EOF返回值
nohup启动的进程本质上仍是属于当前session的一个进程组,因此在当前shell里面能够经过jobs看到nohup启动的程序
当session leader退出后,该进程会收到SIGHUP信号,但因为nohup帮咱们忽略了该信号,因此该进程不会退出
因为session leader已经退出,而nohup启动的进程属于该session,因而出现了一种状况,那就是经过nohup启动的这个进程组所在的session没有leader,这是一种特殊的状况,内核会帮咱们处理这种特殊状况,这里就再也不深刻介绍
经过nohup,咱们最后达到了就算session leader(通常是shell)退出后,进程还能够照常运行的目的。
经过nohup,就能够实现让进程在后台一直执行的功能,为何咱们还要写deamon进程呢?
从上面的nohup的介绍中能够看出来,虽然进程是在后台执行,但进程跟当前session仍是有着千丝万缕的关系,至少其父进程仍是被session管着的,因此咱们仍是须要一个跟任何session都没有关系的进程来实现deamon的功能。实现deamon进程的大概步骤以下:
调用fork生成一个新进程,而后原来的进程退出,这样新进程就变成了孤儿进程,因而被init进程接收,这样新进程就和调用进程没有父子关系了。
调用setsid,建立新的session,新进程将成为新session的leader,同时该新session不和任何tty关联。
切换当前工做目录到其它地方,通常是切换到根目录,这样就取消了对原工做目录的引用,若是原工做目录是某个挂载点下面的目录,这样就不会影响该挂载点的卸载。
关闭一些从父进程继承过来而本身不须要的fd,避免不当心读写这些fd。
重定向stdin、stdout和stderr,避免读写它们出现错误。