[单刷APUE系列]第一章——Unix基础知识[1]
[单刷APUE系列]第一章——Unix基础知识[2]
[单刷APUE系列]第二章——Unix标准及实现
[单刷APUE系列]第三章——文件I/O
[单刷APUE系列]第四章——文件和目录[1]
[单刷APUE系列]第四章——文件和目录[2]
[单刷APUE系列]第五章——标准I/O库
[单刷APUE系列]第六章——系统数据文件和信息
[单刷APUE系列]第七章——进程环境
[单刷APUE系列]第八章——进程控制[1]
[单刷APUE系列]第八章——进程控制[2]
[单刷APUE系列]第九章——进程关系
原著这里前面实际上还有两节,但是笔者感觉并不是特别重要,只是Unix各个实现的登录,所以就直接从进程组开始讲。
在使用 man 2 intro
的时候,Unix系统手册上已经写了很多关于系统的概念,其中就有进程组概念
Each active process is a member of a process group that is identified by a non-negative integer called the process group ID. This is the process ID of the group leader. This grouping permits the signaling of related processes (see termios(4)) and the job control mechanisms of csh(1).
激活的进程都是进程组的医院,进程组ID由进程组顶部进程的pid作为标识,进程组主要用于相关进程之间的信号传递和作业控制。由于进程组ID实际上是一个pid,所以Unix系统给出了以下函数
pid_t getpgrp(void); pid_t getpgid(pid_t pid);
getpgrp函数是返回当前进程的进程组ID,SUS标准还规定了getpgid用于返回指定pid的进程组ID,如果pid为0,则返回当前进程的进程组ID。
进程组实际上是一个虚概念,并不是物理划分的组,任何一个进程都可以认为是一个进程组,进程自身就带有一个属性用于标识自身的进程组,当进程组ID和自身进程ID相等时,就是一个只有一个进程的进程组,当然,进程也可以调用 setpgid
函数加入一个进程组或者创建一个新进程组。
int setpgid(pid_t pid, pid_t pgid);
setpgid函数将pid进程的进程组ID设置为pgid,如果pid为0,则使用调用者的进程ID,如果pgid为0,则由pid指定的进程ID作为进程组ID。
A session is a set of one or more process groups. A session is created by a successful call to setsid(2), which causes the caller to become the only member of the only process group in the new session.
会话是一系列进程组的集合,使用setsid函数来创建一个会话,并且当前的进程会变成新会话中的唯一进程组的唯一成员。既然前面提到了setsid函数,我们就来看看手册说明
pid_t setsid(void); The setsid function creates a new session. The calling process is the session leader of the new session, is the process group leader of a new process group and has no controlling terminal. The calling process is the only process in either the session or the process group. Upon successful completion, the setsid function returns the value of the process group ID of the new process group, which is the same as the process ID of the calling process.
和前面会话的讲解基本差不多,就多了一点——没有控制终端。在说明页(man 2 intro)中,笔者并没有找到有关会话ID的概念,但是确实存在会话唯一标示符,可以类比前面的内容,大胆猜测一下,会话ID实际上就等于会话首进程的进程ID,实际上,在BSD系统历史上,也确实引入了这个概念
pid_t getsid(pid_t pid); The session ID of the process identified by pid is returned by getsid(). If pid is zero, getsid() returns the session ID of the current process.
这个基本不用多说了。
控制终端是什么,手册页上是这么描述的, A terminal that is associated with a session is known as the controlling terminal for that session and its members.
,除了控制终端以外,会话和进程组还有其他特点
会话有控制终端,只要是能实现终端功能的设备都行
和控制终端建立链接的会话首进程是控制进程
会话中的进程组可以分为前台进程组、后台进程组
存在控制终端则必定存在前台进程组
从上面可以看到几个新名词,控制进程、前台进程组和后台进程组,控制进程这里也直接拿手册页描述, A session leader with a controlling terminal is a controlling process.
,前台后台进程组可能经常使用Unix系统的人比较熟悉,前台进程组在SSH断线后就会自动终止,而后台进程组则不会终止。
pid_t tcgetpgrp(int fildes); int tcsetpgrp(int fildes, pid_t pgid_id); pid_t tcgetsid(int fildes);
tcgetpgrp函数看函数名好像是获得进程组ID,但是实际上是根据终端设备的文件描述符返回前台进程组ID,tcsetpgrp则是当进程有一个控制终端的时候,将前台进程组ID设置为pgid_id,这个值应当是同一会话的一个进程组ID,filedes则是该会话的控制终端。实际上非常好理解,进程组ID的控制应当是归属会话的启动进程的,也就是第一个进程,而更改前台进程组ID则应当限定为同一会话,将后台进程组更改为前台,这就是这两个函数的作用。最后一个tcgetsid函数则是获得会话ID,实际上前面也讲过了,这个就是第一个进程组的第一个进程的进程ID。
shell环境下的作业控制各位应当是很熟悉了,作业控制就是在一个终端下进行的多任务处理,所有的作业实际上都是shell的子进程,这很容易理解。任何情况下,终端都只有一个前台任务,其他都是后台任务,对于后台任务的处理,基本上都是使用 &
让其在后台运行,或者说是更加普遍的 screen
命令,当然,这个不在讨论范围内,在前面的讲解中,有一些关于终端产生信号的部分,其实有三个特殊字符用于产生信号
Ctrl+C => SIGINT
Ctrl+/ => SIGQUIT
Ctrl+Z => SIGTSTP
其中前两个特殊字符已经讲过,最后一个Ctrl+Z就是挂起信号。
在前面讲过,shell执行程序是通过exec函数族来进行的,也就是说,所有的程序都是shell的子进程,经过前面的讲解,应该知道进程至少有pid、ppid、pgid和sid四种属性,对于经典的sh终端来说,由于sh不支持作业控制,也就是说,只有一个前台进程组存在,在实际使用中经常使用到管道,管道实际上是一系列串行的任务集合,shell非常巧妙的逆序将其fork自身,然后反向执行,也就是说,由于第一个任务的结果是第二个任务的输入,所以实际上fork的过程是shell fork最后一个任务,逆序的向下派生的。笔者在这里可能介绍的不够全面,可以看原著学习。
在前面介绍过一个进程如果没有了父进程,则父进程变为init进程,这种叫做孤儿进程,那么也可以类比到进程组,是否存在孤儿进程组。这节也是可看可不看。
实际上在写这章的时候笔者是很头疼的,因为基本没什么干货,都是些概念性的东西,很多都是 man 2 intro
上面看看就完事了,特别是前面的终端登录,实际上由于原著已经很老了,很多实现基本都变了,所以很难写出实践和感想,笔者建议就只看看原著就行了。