APUE_第九章 进程关系_学习笔记

9.1 引言

1) 父进程使用wait/waitpid取得子进程的退出状态,存放在wait的参数或是waitpid的第二个参数上;
2) 每一个进程都有父进程, 初始内核并无父进程;

9.2 终端登录

1) 终端有两种:本地终端, 远程终端(通过调制解调器连接);
两种情况下,登录都是通过终端驱动程序访问终端的;
2) BSD终端登录过程介绍, 其他系统如linux的登录过程类似BSD;

BSD 登录过程:

  • init: 读取/etc/ttys; 为每一个终端调用fork; 创建空环境;
  • getty: 打开终端设备(文件描述符0, 1, 2); 设置初始化环境;
  • login: 输出"login:", 得到用户名, 调用getpwnam()取得口令文件登录; 输出“Password:",读取用户输入的口令,调用crypt将用户的口令加密,并且与该用户在阴影口令文件中登录项的pw_passwd字段比较; 如果几次输入密码失败, login调用exit(1), 父进程init取得子进程的终止状态,调用fork,重复上面循环;

如果login登录正确,将执行下面操作:

  1. 将当前目录改为该用户的起始目录;
  2. 使用chown改变终端的所有权, 使登录客户成为终端的所有者;
  3. 将对终端的权限改为读、写;
  4. 调用setgid, initgroups设置进程的组ID(进程的权限);
  5. 使用login得到的所有信息初始化环境:起始目录、shell、用户名、PATH(系统默认路径);
  6. login进程将其用户ID改为登录用户ID(之前login进程的所有ID都是0);

此后, 登录shell启动, 登录shell读取其启动文件。启动文件会改变某些环境变量,加上很多环境变量;启动文件执行文笔, 用户得到shell提示符, 并且能够键入命令;

linux登录与BSD登录的区别主要在于:说明终端配置的方式不同;linux中, /etc/inittab 包含了配置信息,内含各个终端设备的信息,参数;

9.2 网络登录

1) 网络登录终端的特点:

  1. 网络登录下, 所有登录都是经由内核的网络接口驱动程序(如以太网驱动程序);
  2. 内核实现并不知道有网络登录, 进程并不是等待一个登录,而是等待一个网络连接请求的到达;
  3. 为了使得一个软件既能处理本地login, 又能处理网络login, 系统使用伪终端的软件驱动程序,仿真串行终端的运行行为,然后将终端操作映射为网络操作;
    2) 伪终端软件驱动程序存在的意义,网络请求看到的就是网络操作;login进程看到的就是终端操作;这就

3) BSD网络登录

  1. 一个inetd进程,等待大多数网络连接(不是等待用户登录);
  2. init 调用一个shell脚本(/etc/rc), 此脚本执行一个守护进程inetd,然后此脚本终止, inetd进程的父进程变为init; inetd进程等待TCP/IP网络连接请求; 一旦请求到达, inetd执行一次fork,生成一个子进程;
  3. inetd收到网络请求之后, exec(telnetd), 然后telnetd fork出子进程,子进程调用login,执行登录指令; 父进程继续处理网络连接;
    TELNET 远程登录主机的过程: TELNET是使用TCP/IP的远程用户登录应用程序。主机上有对应的TELNET服务进程; 服务进程与客户进程通过TCP连接交换数据; 主机上的telnetd进程打开一个伪终端设备,并使用fork产生两个进程; 父进程处理网络连接的通信, 子进程执行login程序;父子进程通过伪终端连接; 如果登录正确,子进程login就执行与本地登录时相同的操作(更改当前工作目录,设置为登录用户ID和组ID,初始化登录用户的环境, login使用exec将当前进程替换为登录shell;

9.4 进程组

1) 返回进程组ID的函数是什么?有两个函数,其区别是什么? getpgrp() == getpgid(0);
每个进程组都有一个组长进程;
2) int setpgid(pid_t pid, pid_t pgid);

  1. pid == pgid,pid 进程变为进程组组长;
  2. pid == 0,调用者进程变为进程组组长;
  3. pgid == 0, pid指定的进程将作为进程组ID;
  4. pid进程加入进程组pgid;

3) 一个进程只能为他自己或是子进程设置进程组ID。并且在子进程调用exec之后, 就不能再改变子进程的进程组ID了;
4) 实际情况中: fork之后, 父进程设置其子进程的进程组ID, 同时还要使得子进程设置自己的进程组ID(这里重复设置子进程的进程组ID是为了避免竞争,因为你不知道父子进程谁先执行);

9.5 会话

1) 通常可以使用管道线将几个进程编成一组;
pid_t setsid(void);
2) 调用该函数的进程不是一个进程组组长, 此函数就会创建一个新会话,并发生下面三件事情:

  1. 该进程变成一个会话首进程(会话首进程是创建会话的进程), 该进程是新会话中唯一的进程;
  2. 该进程之前一定不是一个进程组组长,此时变为一个进程组组长;
  3. 该进程绝对没有控制终端,如果该进程在调用setsid之前有终端,这个联系也会被中断;
    如果调用setsid的进程已经是一个组长了, 此函数会出错;因此,实际中, 通常先调用fork,然后使得父进程终止,子进程继续(子进程并不调用exec,所以此时子进程完成与父进程相同的工作,但是子进程此时的pid是新的哦pid,绝对不会是进程组组长);此时调用setsid函数绝对不会出错;
    将会话首进程的ID视为会话ID;
    pid_t getsid(pid_t pid); 返回pid所在会话的会话ID; pid == 0, 返回调用者进程所在会话的会话ID;

9.6 控制终端

1) 会话和进程组的一些特性:

  1. 一个会话可以有一个控制终端, 也可以没有控制终端;
  2. 建立与控制终端连接的会话首进程,被称为控制进程;
  3. 只有一个前台进程组,可以有多个后台进程组;
  4. 如果终端接口检测到调制解调器或是网络已经断开, 就会将挂断信号发送给控制进程(会话首进程);

2) 有时候不管标准输入、标准输出是否被重定向,程序都需要与控制终端交互。 保证程序能够读写控制终端的方法是打开文件/dev/tty; 在内核中, 此文件等同与控制终端;如果程序没有控制终端, 打开此文件将会失败;

9.7 tcgetpgrp, tcsetpgrp, tcgetsid 函数

1) 需要通知内核哪一个进程组是前台进程组, 这样终端设备驱动程序就能将信号发送给特定对象;
pid_t tcgetpgrp(int filedes); //返回前台进程组ID, 此前台进程组与在filedes上打开的终端关联
int tcsetpgrp(int filedes, pid_t pgrpid); //此进程有一个终端,将该终端的前台进程组设置为pgrpid, pgrpid 是同一个会话中的一个进程组ID, filedes 必须引用该会话的控制终端;
这两个函数通常由作业控制shell调用;

2) pid_t tcgetsid(int filedes); //识别控制终端的会话首进程;

9.8 作业控制

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

  1. shell必须支持作业控制;
  2. 内核中的终端驱动程序必须支持作业控制;
  3. 内核必须提供对某些作业控制信号的支持;

2) 一个作业只是几个进程的集合,通常是一个进程的管道线;
当启动一个后台作业, shell赋予它一个作业标识,并打印一个或几个进程ID;

3) 在键盘上与终端驱动程序进行交互, 三个字符:

  1. 中断字符,产生SIGINT
  2. 退出字符, 产生SIGQUIT
  3. 挂起字符, 产生SIGTSTP

4) 后台作业不能读、写终端:

  1. 后台作业读取终端, 终端驱动程序检测到这种情况,会向后台作业发送一个SIGTTIN信号,该信号会暂停此后台作业;然后shell会向有关用户发出通知,有关用户可以将后台作业置入前台进程组(tcsetpgrp),然后发送信号(SIGCONT)重新执行该作业,并将完成相应的读取终端;
  2. 如果后台作业输出到终端, 有两种选择;
    stty tostop //禁止后台作业输出到控制终端;
    stty -tostop //允许后台作业输出至控制终端;
    终端驱动程序检测到后台作业输出到终端,会发生SIGTTOU信号;

9.9 shell执行程序

1) linux中:
ps -o pid,ppid,pgid,sid,comm
管道线中最后的一个进程是shell的子进程。管道线之前的进程是最后一个进程的子进程;

2) 如果一个后台进程试图读取控制终端,会发生什么?(后台作业通过标准输入访问控制终端

  1. 有作业控制时候, 后台作业如果读终端,会产生SIGTTIN信号,暂停后台作业;
  2. 没有作业控制的时候, 如果进程自己没有重定向标准输入,shell自动将后台进程的标准输入重定向到/dev/null. 读取/dev/null 会产生一个文件结束, 意味着后台作业读取到一个文件的结束符, 后台作业结束;
    后台作业通过/dev/tty 访问并读取控制终端 —— 看情况

9.10 孤儿进程

1) 挂断信号: SIGHUP, 对挂断信号的系统默认动作是终止该进程;
POSIX 要求向新的孤儿进程组中所有处于停止状态的每一个进程发送挂断信号(SIGHUP), 接着再发送继续信号(SIGCONT);

2) 如果后台进程组是孤儿进程组,此时读取终端, 终端驱动程序让内核发送SIGTTIN信号, 使得孤儿进程组停止,由于POSIX的规定, 这些停止的进程会受到SIGHUP信号, SIGHUP的系统默认处理是终止这些进程,这些进程于是被终止掉(作为孤儿进程的后台进程读取终端的时候, 这些进程默认被终止);

9.11 FreeBSD

1) 每个会话都会分配一个session结构;调用setsid的时候,在内核中会分配一个新的会话结构;
每一个终端设备或是伪终端设备都会在内核中分配tty结构;
为么找到特定会话的前台进程组,sessiion的s_ttyp —— tty的t_pgrp , 其中t_pgrp中就是前台进程组的ID;
2) 在打开终端设备时候分配此结构, 进程对/dev/tty 的所有引用都通过vnode结构; 实际的i节点是v节点的一部分;
在这里插入图片描述

需要练习的:

1 setgid 函数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值