当前位置: 首页 > Linux

Linux会话和进程组概述

时间:2023-04-06 23:06:48 Linux

在上一篇文章中介绍了tty的相关原理,本篇将介绍与tty息息相关的会话和进程组。这篇文章的主要目的是澄清一些概念,不细说session一个session是一个进程的集合,一个sessionid就是这个session中leader的进程ID。session的特点session的主要特点是当session的leader退出时,session中的所有其他进程都会收到一个SIGHUP信号,它的默认行为是终止进程,即leader退出后会话退出,会话中的其他进程也会退出。如果session和tty有关联,那么它们之间只能是一一对应的。一个tty只能属于一个session,一个session也只能开启一个tty。当然,会话可能不与任何tty相关联。会话创建可以通过调用setsid函数随时创建会话。会话中的第一个进程是会话的领导者,领导者不能改变。创建session的一个常见场景是:用户登录后,启动shell时会创建一个新的session,shell作为sessionleader,然后shell中运行的所有进程都属于这个session.当shell退出时,所有运行该进程的用户都会退出。这种类型的会话通常与特定的tty相关联。sessionleader将成为tty的控制进程。当session的前端进程组发生变化时,控制进程负责在tty上更新关联的前端进程组。当tty即将关闭时,控制进程所在会话中的所有进程都会收到SIGHUP信号。启动守护进程。这类进程需要和父进程划清界限,所以需要启动一个新的会话。这样的会话通常不与任何tty关联。进程组一个进程组(processgroup)也是进程的集合,进程组id就是这个进程组中leader的进程ID。进程组的特点进程组的主要特点是可以以进程组为单位通过killpg函数发送信号。进程组创建主要用在shell中。shell负责进程组的管理,包括创建和销毁。(这里的shell就是sessionleader)对于大多数进程来说,它本身就是进程组的leader,进程组中只有一个进程。在shell中执行ls|more等管道连接的命令时,两个进程属于同一个进程组,ls是进程组的组长。一个进程在shell中启动后,一般会将该进程放入一个单独的进程组中,然后该进程fork的所有进程都会属于该进程组,比如一个多进程程序,它的所有进程都会属于同一个进程组,当在shell中按下CTRL+C时,程序的所有进程都会收到SIGINT并退出。当一个进程在后台进程组shell中启动时,默认情况下,该进程是前端进程组的领导者,可以接收用户输入并将输出打印到终端。只有当进程组退出时,shell才能响应用户的输入。但是我们也可以在后台运行进程组,让shell继续对应用户的输入。常用方法如下:启动程序时,在其后加&,如sleep1000&,进程会进入后台继续运行程序启动后,可以按CTRL+Z让它进入背景。和在最后加&的区别在于进程会被挂起。对于后台运行的进程组,体现在shell中job的概念,即一个后台进程组就是一个job。该作业有以下限制:默认情况下,只要后台进程组中的任何进程读取tty,整个进程组中的所有进程都会被挂起。默认情况下,只要后台进程组中的任何进程对tty进行写操作,就可能导致整个进程组的所有进程挂起(取决于tty的配置,请参考TTY/PTS概述)。可以通过jobs命令查看所有后台运行的进程组,也可以通过fg命令将后台进程组切换到前端,以便继续接收用户输入。这两个命令的具体使用方法可以参考它们的帮助文件。这里只是一个简单的例子:#通常,sleep命令会在那里等待,直到指定的时间过去了才退出。#shell启动sleep程序时,将sleep放入一个新的进程组,#而这个进程组是前端进程组,虽然sleep不需要输入输出,#但是当前的标准输入输出session仍然属于它,其他人不能使用它,#只有当我们按下CTRL+C让休眠进程退出时,shell本身又重新成为一个前端进程组,#所以shell才有能力响应再次输入输出dev@debian:~$sleep1000^C#我们可以在命令行后面加上&符号,shell还是会创建一个新的进程组,#sleep进程是新进程组的leader,#但是shell会把sleep进程组放到后台,让它成为后台进程组#这里[1]是jobid,1627是进程组的ID,也就是iddev@debian:~$sleep1000&[1]1627#可以使用jobs命令查看哪些后台进程组(job)dev@debian:~$jobs[1]+runningsleep1000#使用fg命令带上jobid带上后端进程组回到前端,#然后我们可以使用CTRL+Z命令让它再次回到Backend,并暂停进程的执行#CTRL+Z和&不同的是CTRL+Z会暂停进程的执行进程,但&不会dev@debian:~$fg1sleep1000^Z[1]+Stoppedsleep1000#Stopped状态表示进程已经在后台挂起。dev@debian:~$jobs[1]+Stoppedsleep1000session与进程组的关系deamon程序虽然也是sessionleader,但一般不会新建进程组。也没有作业管理功能,所以在这种情况下,一个会话只有一个进程组,所有进程都属于同一个进程组和会话。这里我们就以会话领导者的身份来看一下shell。假设我们在shell中执行这些命令:dev@debian:~$sleep1000&[1]1646dev@debian:~$cat|wc-l&[2]1648dev@debian:~$jobs[1]-Runningsleep1000&[2]+Stoppedcat|wc-l下图显示了这种情况下它们之间的关系:+-------------------------------------------------------------+|||pg1pg2pg3pg4||+-----++------++-----++-----+|||狂欢||睡觉||猫||职位|||+-----++------++-----++-----+||会议负责人|厕所|||+-----+|||+--------------------------------------------------------------+sessionpg=进程组(processgroup)bash是sesessionleader、sleep、cat、wc、jobs这四个进程都是bashfork出来的,所以也属于这个session。Bash也是它自己的进程组的领导者。bash会为它启动的每一个进程创建一个新的进程组,所以这里的sleep和jobs进程属于自己独立的进程组。对于用管道符号“|”连接的命令,bash会将它们放在一个进程组中。nohupnohup是怎么回事?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启动的程序。当sessionleader退出时,进程会收到SIGHUP信号,但是因为nohup帮我们忽略了这个信号,所以进程不会退出,因为sessionleader已经退出了,而nohup启动的进程属于session,所以有一种情况是nohup启动的进程组所在的session没有leader。这是一种特殊情况,内核会帮助我们处理这种特殊情况,这里不做深入介绍。通过nohup,我们终于达到了即使sessionleader(通常是shell)退出后进程仍然可以正常运行的目的。通过nohup,deamon可以实现让进程一直在后台执行的功能。为什么要写守护进程?从上面nohup的介绍可以看出,虽然进程是在后台执行的,但是进程还是和当前session有着千丝万缕的联系,至少它的父进程还是由session管理的,所以我们还是需要一个有没有session相关的进程来实现deamon的功能。实现deamon进程的一般步骤如下:调用fork生成新进程,然后原进程退出,使新进程成为孤儿进程,然后被init进程接收,使新进程进程与调用进程没有父子关系。调用setsid创建一个新的session,新进程将成为新session的leader,新session不关联任何tty。将当前工作目录切换到其他地方,一般是到根目录,这样就取消了对原工作目录的引用。如果原工作目录是某个挂载点下的目录,则不会影响挂载点。卸载。关闭一些继承自父进程但自己不需要的fd,以免不小心读写这些fd。重定向stdin、stdout和stderr以避免在读取和写入它们时出错。有关永不消亡的进程,请参阅ProcessesJobControl