UNIX环境高级编程(3) 第九章

9 进程关系

9.1 引言

9.2 终端登录

1 BSD终端登录

系统管理者创建通常名为/etc/ttys的文件,其中每个终端设备都有一行,每一行说明设备名和传到getty程序的参数。当系统自举时,内核创建进程ID为1的init进程。init进程使系统进入多用户模式。init读取文件etc/ttys,对每一个允许登录的终端设备,init调用一次fork,它所产生的子进程则exec getty程序。

getty对终端设备调用open函数,以读、写方式将终端打开。如果设备是调制解调器,则open可能会在设备驱动程序中滞留,直到用户拨号调制解调器,并且线路被接通。一旦设备被打开,则文件描述符0、1、2就被设置到该设备。然后getty输出login:之类的信息,并等待用户键入用户名。此后getty的使命就完成了,后面交给login程序: execle("/bin/login", "login", "-p", username, (char *)0, envp);


9-1
图9-1 为允许终端登录,init调用的进程

因为最初的init进程具有超级用户特权,所以图上fork出的进程都有超级用户特权。

9-2
图9-2 login调用后进程的状态

上图下边3个进程的进程ID相同,因为进程ID不会因执行exec而改变。并且,除了最初的init进程,所有进程的父进程ID均为1。

login能处理多项工作。首先他得到用户名后可以调用getpwnam取得相应用户的口令文件登录项。然后调用getpass以显示提示Passwd:,接着读用户键入的口令(会禁止回显)。他调用crypt将口令加密,并和shadow文件中的登录项pw_passwd字段相比较。若比较错误,login以参数1调用exit表示登录失败。父进程(init)了解到子进程终止情况后,再次调用fork,执行getty,重复上述过程。

如果用户正确登录,login就将完成如下工作:

  • 将当前工作目录更改为该用户的其实目录(chdir)
  • 调用chown更改该终端的所有权,使登陆者成为它的所有者。
  • 将对该终端设备的访问权限改变成“用户读和写”
  • 调用setgid及initgroups设置进程的组ID。
  • 用login得到的所有信息初始化环境:起始目录(HOME)、shell(SHELL)、用户名(USER和LOGNAME)以及一个系统默认路径(PATH)。
  • login进程更改为登录用户的用户ID(setuid)并调用该用户的登录shell,其方式类似于:

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

    argv[0]的第一个字符‘-’是一个标志,表示该shell被作为登录shell调用。shell可以查看次字符,并相应的修改其启动过程。


9-3
图9-3 终端登录完成各种设置后的进程安排

2 Mac OS X 终端登录

它部分基于FreeBSD,登录进程工作基本相同,但有下列不同之处:

  • init的工作是由launchd完成的。
  • 一开始提供的就是图形终端。
3 Linux 终端登录

它非常类似于BSD,因为Linux login命令是从4.3BSDlogin命令派生出来的。BSD和Linux登录过程主要区别在于说明终端配置的方式。

最近的Ubuntu版本配有称为“Upstart”的init程序。使用存放在/etc/init目录的 *.conf的配置文件。如,运行/dev/tty1上的getty需要的说明可能放在/etc/init/tty1.conf文件中。

4 Solaris 终端登录

支持两种形式的终端登录:

  • getty方式,与前面BSD登录说明一样;
  • ttymon登录,这是SVR4引入的一种新特性。通常,getty用于控制台,ttymon则用于其他终端的登录。

ttymon命令是服务访问设施(Service Access Facility,SAF)的一部分。SAF的目的是用一致的方式对提供系统访问的服务进行管理。

9.3 网络登录

通过串行终端登录至系统和由网路登录到系统两者之间主要(物理上的)区别:网路登录时,在终端和计算机之间的连接不再是点对点,login仅仅是一种可用的服务,这与其他网络服务的性质相同


9-4
图9-4 执行TELNET服务进程时调用的进程序列


9-5
图9-4 网络登录完成各种设置后的进程安排

9.4 进程组

#include <unistd.h>
    pid_t getpgrp(void);
        return: 调用进程的进程组ID

早期BSD派生系统中参数是pid,后用下函数模仿此种行为。

#include <unistd.h>
    pid_t getpgid(pid_t pid);
        return: 进程组ID,error:-1
    int setpgid(pid_t pid, pid_t pgid);
        return: 0,error: -1

可加入一个现有的进程组或创建一个新进程组。

  1. 组长进程

    1) 组长进程标识: 其进程组ID == 其进程ID
    2) 组长进程可以创建一个进程组,创建该进程组中的进程,然后终止
    3) 只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关
    4) 进程组生存期: 进程组创建到最后一个进程离开(终止或转移到另一个进程组)

  2. 进程组id == 父进程id,即父进程为组长进程

PID: process ID
PPID: process parent ID
PGID: process group ID
SID: session ID
TGID: thread group ID

9.5 会话

会话(session)是一个或多个进程组的集合。shell中的管道讲几个进程编成一组,如:
proc1 | proc2 &
proc3 | proc4 | proc5


9-6
图9-6 进程组和会话中进程安排

#include <unistd.h>
pid_t setsid(void);
    return: 进程组ID;error:-1

建立一个会话.

#include <unistd.h>
pid_t getsid(pid_t pid);
    return: 会话首进程的进程组ID,error: -1

9.6 控制终端

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

  • 一个会话可以有一个控制终端。
  • 建立和控制终端连接的会话首进程被称为控制进程。
  • 一个会话中的几个进程组可被分为一个前台进程组以及一个或者多个后台进程组
  • 如果一个会话有一个控制终端,则它有一个前台进程组,会话中的其他进程组则为后台进程组
  • 无论何时键入终端的中断键(常常是DELETE或Ctrl+C),就会将中断信号发送给前台进程组的所有进程
  • 无论何时键入终端的退出键(常常是Ctrl+\),就会将退出信号发送给前台进程组中的所有进程
  • 如果终端接口检测到调制解调器已经断开连接,则将挂断信号发送给控制进程(会话首进程)。

这些特性于下图:


9-7
图9-7 进程组、会话和控制终端

9.7 函数tcgetpgrp、tcsetpgrp和tcgetsid

需要一种方法来通知内核哪一个进程组是前台进程组:

#include <unistd.h>
    pid_t tcgetpgrp(int fd);
        return: 前台进程组ID,error: -1
    int tcsetpgrp(int fd, pid_t pgrpid);
        return: 0,error: -1
#include <termios.h>
    pid_t tcgetsid(int fd);
        return: 会话首进程的进程组ID,error: -1

9.8 作业控制

它是BSD在1980左右增加的一个特性。它允许一个终端上启动多个作业(进程组),由它控制哪一个作业可以访问该终端以及那些作业在后台运行。

作业控制要求下面3点形式支持:

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

9.9 shell执行程序

首先使用不支持作业控制的、在Solaris上运行的经典Bourne shell。如果执行:
ps -o pid,ppid,pgid,sid,comm
则其输出:

PID    PPID    PGID   SID  COMMAND
949    947     949    949     sh
1774   949     949    949     ps
//ps的父进程是shell。shell和ps命令两者位于同一会话和前台进程组。

如果在后台执行命令:
ps -o pid,ppid,pgid,sid,comm &
则唯一改变的值是命令的进程ID:

PID    PPID    PGID   SID  COMMAND
949    947     949    949     sh
1812   949     949    949     ps
//因为这种shell不知道作业控制,所以没有将后台作业放入自己的进程组,也没有从后台作业处取走控制终端。

ps -o pid,ppid,pgid,sid,comm | cat1
其输出是:

PID    PPID    PGID   SID  COMMAND
949    947     949    949     sh
1823   949     949    949     cat1
1824   1823    949    949     ps

注:管道中的最后一个进程是shell的子进程,而执行管道中其他命令的进程则是该最后进程的子进程.

如果在后台执行此管道:
ps -o pid,ppid,pgid,sid,comm | cat1 &
则只改变进程ID。

现在让我们用一个运行在Linux上的作业控制shell来检验同一个例子(使用Bourne-again shell):
ps -o pid,ppid,pgid,sid,tpgid,comm
其输出为:

PID    PPID    PGID   SID   TPGID  COMMAND
2837   2818    2837   2837   5796   bash
5796   2837    5796   2837   5796   ps

可看到与Bourne shell例子的区别:Bourne-again shell将前台作业(ps)放入了它自己的进程组(5796)。ps命令是进程组组长进程,也是该进程组的唯一进程。

在后台执行此进程:
ps -o pid,ppid,pgid,sid,tpgid,comm &
其输出为:

PID    PPID    PGID   SID   TPGID  COMMAND
2837   2818    2837   2837   2837   bash
5797   2837    5797   2837   2837   ps

再一次,ps命令被放入它自己的进程组,但是此时进程组(5797)不再是前台进程组,而是一个后台进程组。TPGID 2837指示前台进程组是登陆shell。

按谢列方式在一个管道中执行两个进程:
ps -o pid,ppid,pgid,sid,tpgid,comm | cat1
其输出为:

PID    PPID    PGID   SID   TPGID  COMMAND
2837   2818    2837   2837   5799   bash
5799   2837    5799   2837   5799   ps
5800   2837    5799   2837   5799   cat1

两个进程ps和cat1都在一个新进程组(5799)中,这是一个前台进程组。Bourne-again shell是两个进程的父进程。

在后台执行此管道:
ps -o pid,ppid,pgid,sid,tpgid,comm | cat1 &
结果类似:

PID    PPID    PGID   SID   TPGID  COMMAND
2837   2818    2837   2837   2837   bash
5801  2837     5801   2837   2837   ps
5802   2837    5801   2837   2837   cat1
//ps和cat1都处于同一后台进程组。

注意,使用的shell不同,创建各个进程的顺序也可能不同。

9.10 孤儿进程组

一个其父进程已终止的进程称为孤儿进程,这种进程由init进程“收养”。

一个进程组不是孤儿进程组的条件是,该进程组中有一个进程,其父进程属于同一会话的另一个组中。如果进程组不是孤儿进程组,那么在属于同一会话的另一个组中的父进程就有机会重新启动该组中停止的进程。在这里,进程组中每一个进程的父进程都属于另一个会话。所以此进程组是孤儿进程组。

9.11 FreeBSD实现

前面说明了进程、进程组、会话和控制终端的各种属性,值得观察一下所有这些是如何实现的。
9-13

图9-13 会话和进程组的FreeBSD实现

从session结构(对话期结构)说明图中标出的各个字段。

  • s_count 对话期中的进程组数,当为0时,则可释放此结构
  • s_leader 对话期的首进程proc结构指针。
  • s_ttyvp 指向控制终端vnode结构的指针
  • s_ttyp 指向控制终端tty结构的指针

接着说明tty 结构(终端设备结构),每个终端和伪终端设备均在内核中分配这样一种结构。

  • t_sesion 指向以此终端作为控制终端的session结构
  • t_pgrp 前台进程组的pgrp结构
  • t_termios 包含此终端的有关信息(波特率、回送打开或关闭等,见11章)
  • t_winsize 此终端的窗口大小的winsize结构

再是pgrp结构(进程组结构),它包含一个特定进程组的信息。

  • pg_id 进程组ID
  • pg_seeeion 指向此进程组所属的session结构
  • pg_mem 指向进程组的第一个进程proc结构的指针

再是proc结构,它包含一个进程的所有信息。

  • p_pgrpnxt 指向下一个进程的指针
  • p_pid 进程ID
  • p_pptr 指向父进程的proc结构的指针
  • p_pgrp 指向所属的进程组的pgrp结构的指针

最后还有一个vnode结构。在打开控制终端设备分配此结构。进程对/dev/tty的所有访问都通过vnode结构。

内核从session结构开始,然后用s_ttyp得到控制终端的tty结构。然后用 t_pgrp得到前台进程组的pgrp结构

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
本书全面介绍了UNIX系统的程序设计界面—系统调用界面和标准C库提供的许多函数。 本书的前15章着重于理论知识的阐述,主要内容包括UNIX文件和目录、进程环境进程控制、 进程间通信以及各种I/O。在此基础上,分别按章介绍了多个应用实例,包括如何创建数据库函数库, PostScript 打印机驱动程序,调制解调器拨号器及在伪终端上运行其他程序的程序等。 本书内容丰富权威, 概念清晰精辟,一直以来被誉为UNIX编程的“圣经”,对于所有UNIX程序员—无论是初学者还是专家级人士 —都是一本无价的参考书籍。 目 录 译者序 译者简介 前言 第1章 UNIX基础知识 1 1.1 引言 1 1.2 登录 1 1.2.1 登录名 1 1.2.2 shell 1 1.3 文件和目录 2 1.3.1 文件系统 2 1.3.2 文件名 2 1.3.3 路径名 2 1.3.4 工作目录 4 1.3.5 起始目录 4 1.4 输入和输出 5 1.4.1 文件描述符 5 1.4.2 标准输入、标准输出和标准 出错 5 1.4.3 不用缓存的I/O 5 1.4.4 标准I/O 6 1.5 程序和进程 7 1.5.1 程序 7 1.5.2 进程进程ID 7 1.5.3 进程控制 7 1.6 ANSI C 9 1.6.1 函数原型 9 1.6.2 类属指针 9 1.6.3 原始系统数据类型 10 1.7 出错处理 10 1.8 用户标识 11 1.8.1 用户ID 11 1.8.2 组ID 12 1.8.3 添加组ID 12 1.9 信号 12 1.10 UNIX时间值 14 1.11 系统调用和库函数 14 1.12 小结 16 习题 16 第2章 UNIX标准化及实现 17 2.1 引言 17 2.2 UNIX标准化 17 2.2.1 ANSI C 17 2.2.2 IEEE POSIX 18 2.2.3 X/Open XPG3 19 2.2.4 FIPS 19 2.3 UNIX实现 19 2.3.1 SVR4 20 2.3.2 4.3+BSD 20 2.4 标准和实现的关系 21 2.5 限制 21 2.5.1 ANSI C限制 22 2.5.2 POSIX限制 22 2.5.3 XPG3限制 24 2.5.4 sysconf、pathconf 和fpathconf 函数 24 2.5.5 FIPS 151-1要求 28 2.5.6 限制总结 28 2.5.7 未确定的运行时间限制 29 2.6 功能测试宏 32 2.7 基本系统数据类型 32 2.8 标准之间的冲突 33 2.9 小结 34 习题 34 第3章 文件I/O 35 3.1 引言 35 3.2 文件描述符 35 3.3 open函数 35 3.4 creat函数 37 3.5 close函数 37 3.6 lseek函数 38 3.7 read函数 40 3.8 write函数 41 3.9 I/O的效率 41 3.10 文件共享 42 3.11 原子操作 45 3.11.1 添加至一个文件 45 3.11.2 创建一个文件 45 3.12 dup和dup2函数 46 3.13 fcntl函数 47 3.14 ioctl函数 50 3.15 /dev/fd 51 3.16 小结 52 习题 52 第4章 文件和目录 54 4.1 引言 54 4.2 stat, fstat和lstat函数 54 4.3 文件类型 55 4.4 设置-用户-ID和设置-组-ID 57 4.5 文件存取许可权 58 4.6 新文件和目录的所有权 60 4.7 access函数 60 4.8 umask函数 62 4.9 chmod和fchmod函数 63 4.10 粘住位 65 4.11 chown, fchown和 lchown函数 66 4.12 文件长度 67 4.13 文件截短 68 4.14 文件系统 69 4.15 link, unlink, remove和rename 函数 71 4.16 符号连接 73 4.17 symlink 和readlink函数 76 4.18 文件的时间 76 4.19 utime函数 78 4.20 mkdir和rmdir函数

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值