APUE 学习笔记——进程关系

1、unix 系统的终端登录流程是什么?

  • 当系统自举时,内核创建进程ID为 1 的进程,即init进程
  • init进程读取文件/etc/ttys,对每个允许登录的终端设备,init调用一次fork,其所生成的子进程则exec getty程序(以一个空的环境)
  • getty对终端设备调用open函数,以读、写方式将终端打开。

    • 一旦设备被打开则文件描述符0、1、2被设置到该设备
    • 然后getty输出login:之类的信息,并等待用户键入用户名
    • 当用户键入了用户名后,getty的工作就完成了,它以类似下列的方式调用login程序:

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

      其中envpgetty以终端名和在gettytab中说明的环境字符串为login创建的环境。-p标志通知login保留传递给它的环境,也可以将其他环境字符串添加到该环境中,但是不能替换它

  • login能处理多项工作
    • login得到了用户名,所以能够调用getpwnam获取相应用户的口令文件登录项,然后调用getpass以显示Password:
    • 接着login读取用户键入的口令,它调用crypt将用户键入的口令加密,并且与该用户在阴影口令文件中登录的pw_passwd字段比较
    • 如果用户自己键入的口令都无效,则login以参数1调用exit表示登录过程失败
      • 父进程init了解了子进程的终止情况后,再次调用fork其后又调用了getty,对此终端重复上述过程
    • 如果用户键入的口令正确,则login完成下列工作:
      • 将当前工作目录更改为用户的起始目录
      • 调用chown更改该终端的所有权,使得登录用户成为它的所有者
      • 将对该终端设备的访问权限变成”用户读和写“
      • 调用setgidinitgroups设置进程组ID
      • login得到的所有信息初始化环境:起始目录(HOME)、shell(SHELL)、用户名(USERLOGNAME)以及一个系统默认路径(PATH)
      • login进程调用setuid,将进程的用户ID更改登录用户的用户ID,并调用该用户的登录shell,其方式类似于:execl("/bin/sh","-sh",(char*)0);
      • 至此,登录用户的登录shell开始运行。登录shell读取其启动文件(如.profile)。这些启动文件通常是更改某些环境变量并增加很多环境变量。当执行完启动文件后,用户最后得到shell提示符,并能键入命令

2、进程组是什么?

进程组:每个进程除了有一个进程ID之外,还属于一个进程组。进程组是一个或者多个进程的集合。

  • 通常进程组是在同一个作业中结合起来的
  • 同一个进程组中的各进程接收来自同一个终端的信号
  • 每个进程组都有一个组长进程。进程组ID就等于组长进程的进程ID
    • 进程组的组长进程可以创建该组中的进程
    • 进程组的组长进程也可以终止。只要进程组中至少有一个进程存在,则该进程中就存在,这与组长进程是否终止无关
  • 进程组的生命周期:从组长进程创建进程中开始,到组内最后一个进程离开为止的时间

2、setpgid 设置进程组函数的规则?

一个进程只能为它自己或者他的子进程设置进程组ID,且进程组ID只能为父进程进程组ID、父进程的进程ID或者子进程的进程ID。

  • 如果pid等于pgid,则由pid指定的进程变成进程组组长
  • 如果pid等于0,则使用调用者的进程ID
  • 如果pgid等于0,则使用pid指定的进程ID用作进程组ID

3、setsid 函数新建一个会话的规则?

进程调用setsid建立一个新会话。如果调用此函数的进程不是一个进程组的组长进程,则此函数创建一个新会话并且发生下面三件事:

  • 该进程成为一个新进程组的组长进程。新进程组ID就是该调用进程的进程ID
  • 该进程会变成新会话的会话首进程session leader。此时该进程是新会话中的唯一进程

    会话首进程是创建该会话的进程

  • 该进程没有控制终端。即使调用setsid之前该进程有一个控制终端,该联系也被切断

如果调用此函数的进程是个进程组的组长,则此函数返回出错。

通常是进程首先fork,然后父进程终止,子进程调用setsid继续执行。这确保了子进程不是一个进程组的组长

4、会话与进程组的关系是什么?

  • 一个会话内的所有进程都必须是该会话首长进程的后代,这样就保证了这些进程都是由该会话首长进程直接或者间接开启的,只有这样的才能保证这些进程确实是在会话首长进程耳目视线之内的,同时,孤儿进程组不再受到会话首长进程的控制。
  • 控制终端只能由会话头进程创建,首长申请的终端就是给这些孩子们用的,首长将这些孩子们分成了若干的进程组,指定一个组为前台进程组,只有这个前台进程组的进程才能使用控制终端。
  • 一个会话中的进程组可以分成一个前台进程组,以及一个或者多个后台进程组
  • 一个会话可以有一个控制终端controlling terminal
    • 这可以是终端设备(在终端登录的情况下)
    • 也可以是伪终端设备(在网络登录的情况下)
  • 建立与控制终端连接的会话首进程称作控制进程controlling process
  • 无论何时键入终端的中断键(通常是Ctrl+C),都会将中断信号发送至前台进程组的所有进程
  • 无论何时键入终端的退出键(通常是Ctrl+\),都会将退出信号发送至前台进程组的所有进程
  • 会话ID不一定等于前台进程组的组ID。对于一个会话,会话ID通常不变(前提是没有函数主动设置它);但是前台进程组进程由于作业调度会经常发生变化

5、什么是进程控制?

运行在一个终端上启动多个作业,它控制哪个作业可以访问终端以及那些作业在后台运行。一个会话终止于会话首长的 death(注意:进程组的终止与组长进程的终止无关)

  • 一个作业是一个进程组。该进程组的进程协同完成一个任务
  • Linux中,当执行ls > a.out &等命令以&时,就启动了一个后台作业
    • shell会赋予它一个作业标识符,并打印作业标识符以及一个或者多个进程ID
  • Linux中,当执行ls > a.out等命令时,就启动了一个前台作业
  • 当我们键入以下三个字符之一时,会使终端产生信号,并将它们发送到所有的前台进程组(后台进程组不受影响):
    • Ctrl+C中断字符:产生SIGINT信号
    • Ctrl+\退出字符:产生SIGQUIT信号
    • Ctrl+Z挂起字符:产生SIGTSTP信号
  • 只有前台作业能够接收来自终端的输入。如果后台作业试图读取终端,则这并不是个错误。终端驱动程序会检测到这种情况,并向后台作业发送一个特定的信号SIGTTIN,该信号通常会停止此后台作业,而shell会向用户发出这种情况的通知
    • 用户此时可以通过shell命令将该后台作业转换为前台作业运行
  • 通过stty命令可以禁止或者允许后台作业输出到终端。
    • 用户禁止后台作业向控制终端写入,则当后台作业试图写到标准输出时,终端驱动程序识别出该写操作来自于后台进程,于是向该作业发送SGITTOU信号,该信号通常会停止此后台作业,而shell会向用户发出这种情况的通知
    • 用户此时可以通过shell命令将该后台作业转换为前台作业运行
  • 作业控制是在窗口终端得到广泛应用之前设计和实现的

6、什么是孤儿进程组?

所谓的孤儿进程组简单点说就是脱离了创造它的 session 控制的,离开其 session 眼线的进程组,unix 中怎样控制进程,怎样证明是否在自己的眼线内,那就是树形结构了,只要处于以自己为根的子树的进程就是自己眼线内的进程,这个进程就是受到保护的,有权操作的,而在别的树枝上的进程原则上是触动不得的.

一个进程组不是孤儿进程组的条件是:该进程组中一个会话内的所有进程都必须是该会话首长进程的后代,这样就保证了这些进程都是由该会话首长进程直接或者间接开启的,只有这样的才能保证这些进程确实是在会话首长进程耳目视线之内的,同时,孤儿进程组不再受到会话首长进程的控制。存在一个进程,其父进程在属于同一个会话的另一个组中。

  • 如果进程组不是孤儿进程组,则属于同一个会话的另一个组中的父进程就有机会重启该组中停止的进程

如果一个进程组中的所有进程:

  • 要么其父进程不再同一个会话中
  • 要么其父进程就在同一个组中

则该进程组是个孤儿进程组

7、为什么要对孤儿进程组发送信号?

当一个终端控制进程(即会话首进程)终止后,那么这个终端可以用来建立一个新的会话。这可能会产生一个问题,原来旧的会话(一个或者多个进程组的集合)中的任一进程可再次访问这个的终端。为了防止这类问题的产生,于是就有了孤儿进程组的概念。当一个进程组成为孤儿进程组时,posix.1要求向孤儿进程组中处于停止状态的进程发送 SIGHUP(挂起)信号,系统对于这种信号的默认处理是终止进程,然而如果无视这个信号或者另行处理的话那么这个挂起进程仍可以继续执行。

当孤儿进程组产生的时候,如果孤儿进程组中有TASK_STOP的进程,那么就发送SIGHUPSIGCONT信号给这个进程组

  • 这个顺序是不能变的。我们知道进程在进程在TASK_STOP的时候是不能响应信号的,只有当进程继续运行的时候,才能响应之前的信号。

    • 如果先发送SIGCONT信号再发送SIGHUP信号,那么SIGCONT信号后,进程就开始重新进入运行态,这个和马上响应SIGHUP信号的用意相悖
    • 所以这个时候需要在进程TASK_STOP的过程中首先发送SIGHUP信号,为的是让进程运行之后马上执行SIGHUP信号。
  • 这两个信号是发送给有处于TASK_STOP状态的进程的进程组的所有进程的。所以进程组中正在运行的进程,如果没有建立SIGHUP信号处理函数,那么运行的进程就会因为SIGHUP退出。

API

pid_t getpgrp(void);
pid_t getpgid(pid_t pid);

int setpgid(pid_t pid,pid_t pgid);

pid_t setsid(void);
pid_t getsid(pid_t pid);

pid_t tcgetpgrp(int fd);
int tcsetpgrp(int fd,pid_t pgrpid);//获取/设置当前进程所在会话的前台进程组ID

pid_t tcgetsid(int fd);//获取会话首进程的进程组ID(也就是会话ID)
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!