《unix环境高级编程》-09、进程关系-读书笔记

时间:2022-06-21 22:13:32

一、基本概念

 终端登录:过去30年,登录过程没有多少改变。

(1)系统管理员创建通常名为/etc/ttys的文件,其中,每个终端设备都有一行,每一行说明设备名和传递给getty程序的参数。

(2)当系统自举时,内核创建进程ID为1的进程,也就是init进程。init进程使系统进入多用户状态。init进程读文件/etc/ttys,每一个允许登录的终端设备,init调用一次fork,它所生成的子进程则执行(exec)getty程序。   如下图:各个进程的实际用户ID和有效用户ID都是0(即它们都具有超级用户特权)。init以空环境执行getty程序。问:什么叫空环境?

             进程ID1

                init

          /        |       \

                fork               每个终端调用一次fork

                  |

                init

                  |

                exec            每个子进程执行(exec)getty

                  |

               getty

(3)getty为终端设备调用open函数,以读、写方式将终端打开。一旦设备被打开,文件描述符0、1、2就被设置到该设备。然后getty输出”login:“之类的信息,并等待用户键入用户名

          进程ID1

             init                                 读取/etc/ttys;

        /      |       \                           meige终端调用一次fork;创建空环境

            fork

              | 

            init

              |    exec

           getty                           打开终端设备(文件描述符0、1、2);读用户名;初始环境设置

             |     exec

          login

注:当用户键入用户名后,getty的工作就完成了,然后就类似于下面的方式调用login程序:

                         execl("/bin/login", "login", "-p", username, (char *)0, envp)

(4)login可以执行多项工作。因为它得到了用户名,所以能调用getpwnam取得相应用户的口令文件登录项。然后调用getpass(3)以显示提示”password:”,接着读用户键入的口令。塔吊用crypt(3)将用户键入的口令加密,并与该用户在阴影口令文件登录项的pw_passwd字段相比较。如果用户几次键入的口令都无效,则login以参数1调用exit表示登录过程失败。父进程(init)连接到子进程的中止情况后,将再次调用fork,其后接着执行getty,对此终端重复上述过程。


(5)如果用户正确登录,login就将执行如下工作:

--将当前工作目录更改为该用户的起始目录(chdir)

--调用chown改变该终端的所有权,使登录用户成为他的所有者

--将对该终端设备的访问权限改变成用户读和写

--调用setgid以及initgroups设置进程的组ID

--用login所得到的所有信息初始化环境:起始目录(HOME)、shell(SHELL)、用户名(USER和LOGNAME)、以及一个系统默认路径(PATH)。

--login进程改变为登录用户的用户ID(setuid)并调用该用户的登录shell:如下:

execl("/bin/sh", "-sh", (char *)0);


(6)到此为止,登录用户的登录shell开始运行。其父进程init进程ID(进程ID为1),所以当此登录shell终止时,init会得到通知(SIGCHLD信号),它会对该终端重复全部上述过程。将登陆shell的文件描述符0、1和2设置为终端设备。


(7)现在,登录shell读取其启动文件:

Bourne shell和Korn shell是:.profile。

GNU Bourne-again shell是: .bash_profile、.bash_login或者.profile

C shell是.cshrc和.login

这些启动文件通常会改变某些环境变量,加上很多环境变量。


二、网络登录

1、通过串行终端登录系统和通过网络登录系统的区别是:通过网络登录时,终端和计算机之间的连接不是点对点连接。


init知道哪些终端设备可用来进行登录,并为每个设备生成一个getty进程。但是,在网络登录情况下,所有登录都经由内核的网络接口驱动程序(如以太网驱动程序),事先并不知道将会有多少这样的登录)。


为了使同一个软件既能处理终端login、又能处理网络login,系统使用了一个称为伪终端的软件驱动程序,它仿真串行终端的运行行为,并将终端操作映射为网络操作。


在BSD中,有一个称为inetd的进程,它等待大多数网络连接。作为系统启动的一部分,init调用一个shell,使其执行shell脚本/etc/rc。由此shell脚本启动一个守护进程inetd,一旦此shell脚本终止,inetd的父进程就变成init。inetd等待TCP/IP连接请求到达主机,而当一个连接请求到达时,它执行一次fork,然后声称的子进程执行适当的程序。


需要重点理解的是:

当通过终端或网络登录时,我们得到一个登录shell,其标准输入、输出和标准出错连接到一个终端设备或者伪终端设备上。




二、进程组

1、每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或多个进程的集合。通常,它们与同一个作业相关联,可以接收来自同一终端的各种信号。每一个进程组有一个唯一的进程组ID。存放于pid_t数据结构中。

注:例如当再终端使用命令行格式启动命令时,当终端退出时,这些进程 都会退出,因为这些进程与此控制终端相连,则终端退出时,都可以接受来自此终端的退出信息,也就 共同退出了。

使用getpgrp可以返回调用进程的进程组ID。

#include  <unistd.h>

pid_t getpgrp(void);


2、每个进程组都可以有一个组长进程,组长进程的标识是,其进程组ID等于其进程组长ID

    组长进程可以创建一个进程组,创建该组中的进程,然后终止。只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生存期。进程组中的最后一个进程可以终止,或者转移到另一个进程组

进程可以通过setpgid来加入一个现有的组或者创建一个新进程组。

#include <unistd.h>

int  setpgid(pid_t pid,pid_t pgid)


注意:  

(1)一个进程只能为它自己或者它的子进程设置进程组ID。在它的子进程调用了exec函数之一,它就不再能改变该子进程的进程组ID。


3、会话

 会话(session)是一个或多个进程组的集合。

一个进程组有一个唯一会话期首进程,会话期ID为首进程ID。

会话期可以有一个单独的控制终端,与控制终端连接的会话期首进程叫控制进程。


注1:

用户用ssh登录,则用户的登录shell就是该会话的会话期首进程,其PID就是会话PID。则在该shell上执行的任何前台命令或者是后台命令(&)都属于该会话组。

当因网络故障,终端接口都会检测到ssh网络连接断开,向当前会话组包括前、后台的所有进程发送SIGHUP信号导致这些进程都被终止。(即在终端上,那么它们同属于一个会话组,会话组ID就是登陆shell的进程ID)



进程调用  setsid  函数创建一个新会话。

#include <unistd.h>

pid_t  setsid(void);

如果调用此函数的进程不是一个进程组的组长,则此函数就会创建一个新会话,结果将发生以下三件事:

(1)该进程会变成新会话首进程(session leader)。会话首进程就是创建该会话的进程。此时,该进程是新会话中唯一的进程。

(2)该进程称为一个新进程组的组长进程。新进程组ID是该调用进程的进程ID。

(3)该进程没有控制终端。如果在调用setsid之前该进程有一个控制终端,则这种联系也会被切断。这段话是说,因为启动进程总是在某个终端通过命令行形式启动,因此进程会具有控制终端,当使用fork之后,父进程退出,子进程调用setsid之后,就切断了与控制终端之间的联系,当控制终端终止时,也就不会影响到子进程的运行。

注1:

当一个新用户登录到某台机器时,登录进程会创建一个新的会话,会话包含一个单独的进程:用户的登录shell。登录shell的功能是作为会话的领导者(leader)。会话领导者的pid被用作会话ID。会话通常是一个或者多个进程组的集合。会话安排已登录用户的动作,并关联用户到一个控制终端上,控制终端是一个特定的tty设备,处理用户的终端I/O。因此,会话主要是shell相关的。实际上没有其它东西关心会话。


注2:

如果该调用进程已经是一个进程组的组长,则此函数就返回出错。为了保证不发生这种事情,通常先调用fork,然后使其父进程终止,而子进程继续。因为子进程继承了父进程组ID,而其进程ID是新分配的,两者不可能相等,所以就保证了子进程不会是一个进程组的组长。


4、会话首进程是具有唯一进程ID的单个进程,所以可以将会话首进程的进程ID视为会话ID。

会话首进程总是一个进程组的组长进程,所以两者是等价的。


5、控制终端

会话和进程组有一些其他特性:

(1)一个会话可以有一个控制终端。

(2)建立和控制终端连接的会话首进程被称为控制进程。

(3)一个会话中的几个进程组可被分为一个前台进程组以及一个或者多个后台进程组

(4)如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组

(5)无论何时键入终端的中断键(常常是DELETE或Ctrl+C),就会将中断信号发送给前台进程组的所有进程

(6)无论何时键入终端的退出键(常常是Ctrl+\),就会将退出信号发送给前台进程组中的所有进程

(7)如果终端接口检测到调制解调器已经断开连接,则将挂断信号发送给控制进程(会话首进程)。



三、作业控制

    作业控制: 它允许一个在终端上启动多个作业(进程组),它控制哪一个作业可以访问该终端,以及哪些作业在后台运行。作业控制要求下面三种形式的支持:

(1)支持作业的shell

(2)内核中的终端驱动程序必须支持作业控制

(3)内核必须提供对某些作业控制信号的支持