《APUE》笔记-第九章-进程关系

时间:2022-02-02 04:21:25

重点:终端登录、网络登录、进程组、会话、控制终端、孤儿进程组

1.终端登录

终端或者是本地的(直接连接)或者是远程的(通过调制解调器连接)。在这两种情况下,登录都经由内核中的终端设备驱动程序。

BSD终端登录:

《APUE》笔记-第九章-进程关系

大概过程如下:

1.系统自举,内核创建进程ID为1的init进程

2.init进程读取文件/etc/ttys,对每一个允许登录的终端设备,fork一次,子进程则exec getty

3.getty调用open打开终端,输出login:,等待用户输入用户名

4.getty程序调用login程序:execle("/bin/login", "login", "-p", username, (char *)0, envp)

5.login等待用户输入密码,若错误则返回第2步;若正确登录,login接着完成一系列工作

6.login调用该用户的登录shell:excl("/bin/sh", "-sh", (char *)0)

7.登录shell读取其启动文件,配置环境变量,最后输出shell提示符,等待用户输入命令

2.网络登录

所有登录都经由内核的网络接口驱动程序,必须等待一个网络连接的到达,为使同一个软件既能处理终端登录,又能处理网络登录,系统使用了一种称为伪终端的软件驱动程序。

《APUE》笔记-第九章-进程关系

详细讲解见:网络登录过程

大概过程如下:

1.用户通过telnet客户端连接服务器

2.服务器上由inet进程监听连接请求。当一个连接到达时,inet进程fork一个子进程,inet进程继续监听请求,而子进程则exec telneted,成为telneted进程。

3.telneted进程fork一次,一分为二,父进程操作伪终端主设备,子进程将伪终端从设备作为它的控制终端,二者通过伪终端通信,父进程还负责和telnet客户端通信,而子进程负责用户的登录过程,提示输入帐号,然后调用exec变成login进程,提示输入密码,然后调用exec变成Shell进程。这个Shell进程认为自己的控制终端是伪终端从设备,伪终端主设备可以看作键盘显示器等硬件,而操作这个伪终端的“用户”就是父进程telnetd

4.当用户输入命令时,telnet客户端将用户输入的字符通过网络发给telnetd服务器,由telnetd服务器代表用户将这些字符输入伪终端。Shell进程并不知道自己连接的是伪终端而不是真正的键盘显示器,也不知道操作终端的“用户”其实是telnetd服务器而不是真正的用户。Shell仍然解释执行命令,将标准输出和标准错误输出写到终端设备,这些数据最终由telnetd服务器发回给telnet客户端,然后显示给用户看。

3.进程组、 会话、 控制终端

《APUE》笔记-第九章-进程关系

3者关系如上图所示:

每个进程组有一个组长进程,组长进程的进程ID等于进程组ID,进程组的生命周期与组长进程是否终止无关,一个进程只能为它自己或它的子进程设置进程组ID;

会话是一个或多个进程组的集合。创建会话的进程成为新会话的会话首进程,该进程本身之前不能是一个进程组的组长,该进程成为一个新进程组的组长进程,该进程没有控制终端。

一个会话可以有一个控制终端,会话首进程为控制进程,一个会话中可以有一个前台进程组,一个或多个后台进程组。

键入中断、退出键会将信号发送至前台进程组的所有进程;调制解调器(或网络)已经断开连接,则将挂断信号发送给控制进程

保证程序能与控制终端对话的方法是open文件/dev/tty,在内核中,此特殊文件是控制终端的同义语

《APUE》笔记-第九章-进程关系

4.孤儿进程组

孤儿进程组定义:该组中每个成员的父进程要么是该组的一个成员,要么不是该组所属会话的成员。

另一种描述:一个进程组不是孤儿进程组的条件是-----该组中有一个进程,其父进程在属于同一会话的另一个组中

产生一个孤儿进程组,程序如下:

#include <stdio.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <errno.h>

static void sig_hup(int signo)
{
printf("SIGHUP received, pid = %ld\n", getpid());
}

static void pr_ids(char *name)
{
printf("%s: pid=%ld, ppid=%ld, pgrp=%ld, sid=%ld, tpgrp=%ld\n",
name, getpid(), getppid(), getpgrp(), getsid(0), tcgetpgrp(STDIN_FILENO));
}

int main()
{
char c;
pid_t pid;
pr_ids("parent");
pid = fork();
if (pid > 0)
sleep(5);//父进程睡眠5秒后终止
else
{
pr_ids("child");
signal(SIGHUP, sig_hup);
kill(getpid(), SIGTSTP);//向自身发送停止信号
pr_ids("child");//发送SIGHUP信号,接着发送SIGCONT信号,得以继续执行
if (read(STDIN_FILENO, &c, 1) != 1)//读控制终端
printf("read error %d on controlling TTY\n", errno);
}
exit(0);
}
结果:

《APUE》笔记-第九章-进程关系
分析:

1.父进程睡眠5秒,让子进程先执行,5秒后终止

2.因为系统对SIGHUP信号的默认动作是终止该进程,为了让子进程不被终止,所以需要信号处理程序sig_hup

3.子进程向自身发送SIGTSTP信号,子进程被停止

4.5秒后,父进程终止,发送SIGHUP信号,子进程捕获该信号,并进行处理

5.在父进程终止之后,进程组就只包含了一个停止的子进程,从而进程组成为孤儿进程组。POSIX.1要求向孤儿进程组中处于停止状态的每一个进程发送挂断信号(SIGHUP),接着又向其发送继续信号(SIGCONT),所以,子进程继续向下执行

6.父进程终止时,子进程变成后台进程组。当后台进程组识图读控制终端时,对该后台进程组产生SIGTTIN信号。但这是一个孤儿进程组,POSIX.1规定read返回出错。


pid:进程ID、ppid:父进程ID、pgrp:进程组ID、sid:会话首进程ID、tpgrp:前台进程组ID

进程属于一个进程组,进程组属于一个会话,会话可能有也可能没有控制终端