Bo2SS

Bo2SS

3 多进程

课程内容#

什么是进程#

  • 进程是程序在内存中的镜像,是正在运行的程序,是程序的实例化,是一个复杂的集合体
    • 包含开辟的内存空间、用户信息、组信息、权限、占用的资源、正在跑的代码、打开的文件等等
  • 与之对应
    • ① 什么是程序
      • 程序是编译好的可执行的二进制文件,放在磁盘上
        • 就是一个普通文件,有 x 权限
      • 程序的集合是应用
    • ② 什么是线程
      • 线程代表一系列有顺序的、需要 CPU 执行的指令
      • 一个进程可能由一个或多个线程组成,同时执行指令
    • [PS] 进程是 CPU资源分配的基本单位,线程是 CPU调度的基本单位

fork#

创建一个子进程 [进程接口]

  • man fork
  • 原型 + 描述
    • 图片
    • 返回值 [类型:pid_t]:进程 id
    • 通过复制调用 fork 的进程 [父进程] 创建一个新进程 [子进程],父进程和子进程运行在相互独立的内存空间
      • fork 完成时,两者有一样的内容,之后内存的写、文件映射不会互相影响
      • 当内存发生变化时,才会发生真正的拷贝 [写拷贝的概念]
        • 否则共用的是同一份内存空间
    • image-20210120105027238
    • 父子主要有以下的不同点:
      • 孩子有自己唯一的 PID,并且不和任何已存在的 PID 相同
      • 孩子认为的父亲 PID [getppid] 与真实的父亲 PID 相同
      • 孩子不会继承父亲的内存锁
      • 孩子的资源使用量和 CPU 使用时间都会重置为 0
      • 孩子不会继承待执行的信号、信号量、记录锁、计时器、异步 IO 操作
  • 返回值
    • 图片
    • 成功:在父进程中返回孩子的 PID,子进程中返回 0
      • 父亲无法再通过其他方式获得该孩子的 PID,儿子可通过 getppid 获得父亲的 PID
    • 失败:返回 - 1,并设置 errno [没有创建子进程]

wait#

等待进程状态的改变

  • man wait
  • 原型
    • 图片
    • wstatus [int *]:返回子进程的状态
      • 如子进程中 return、exit 的值
      • 需要使用宏来解析,如 WIFEXITED (wstatus),详见代码演示 —wait— 二
  • 描述
    • 图片
    • 等待对象:调用进程的孩子
    • 状态改变情况:孩子被终止、被信号中断、被信号唤醒
    • 当有被终止的孩子时,
      • wait 命令可以使系统释放孩子相关的资源
      • 否则 [不执行 wait 命令],被终止的子进程就会变成僵尸进程[👇]
        • 死了的孩子没有被父进程察觉,其资源没有被释放
        • 可使用 top 查看
        • 图片
        • zombie 即僵尸进程
    • 只要一个孩子已经改变状态,wait 命令就会被立马返回
      • 否则,会阻塞直到有孩子改变状态或者信号中断
  • 返回值
    • image
    • 返回被终止的孩子 PID 或者 - 1 [出错,并设置 errno]

exec 族#

执行一个文件 [一切皆文件]

  • man exec
  • 原型
    • 图片
    • 有很多兄弟
  • 描述
    • 图片
    • 图片
    • 将用一个全新的进程镜像【替换】当前的进程镜像
      • [让孩子有一个全新的世界]
    • 第一个参数都是要执行文件的名字
      • path:完整路径
      • file:可以是 PATH 环境变量里的命令或完整路径
    • 整个族可概括为:"exec + l/v + p/e/pe"
      • 参数名 arg,表示是前面参数 path 的参数
      • l-list,所有参数放入一整个字符串中 [参数的传递方式]
        • 按惯例,arg0 应与要执行文件的名字有关联
        • 必须以 (char *) NULL 结尾
      • v-vector,所有参数放入一个字符串数组中 [参数的传递方式]
        • 必须以 null 指针结尾
      • p-path,可执行文件的查找范围包括 PATH 环境变量
        • 复制了 Shell 查找命令的过程
      • e-env,允许指定环境变量
        • 变量 - 数值对
  • 返回值
    • img
    • 只在错误发生时返回 - 1

flock#

在打开的文件上操作建议锁

[本质上是为了保护数据]

  • man 2 flock
  • 原型 + 描述
    • 图片
    • 通过文件描述符 fd 操作
    • 主要三种操作
      • LOCK_SH:共享锁
      • LOCK_EX:互斥锁
        • 互斥锁:如果有一个人访问,其他人就不能访问了
        • 举例:很多人上一个卫生间
      • LOCK_UN:解锁
  • 返回值
    • 图片
    • 0,成功;-1,失败

代码演示#

fork#

一、复制缓冲区、行缓冲

  • 图片
  • 输出结果
    • 图片
    • ❗ fork 后面已经没有输出函数了,为什么输入 suyelu,会输出两个suyelu?
    • 【事实】虽然 fork 后代码复制了一份给子进程,但是子进程只会执行 fork 后的代码
    • 【关键】缓冲区被复制了,里面还存有 suyelu
      • printf 中没有换行符,而标准 I/O 是行缓冲I/O,第 13 行执行完并不会刷新缓冲区
      • 当程序结束时,才触发刷新缓冲区的条件
    • [PS] zsh 下可能只会输出一次 suyelu,可能是 zsh 的优化?bash 下有俩

二、父子进程相互独立

  • 图片
  • 输出结果
    • 图片
    • ❗ 父进程一定先执行吗?
      • 不一定,父子进程完全独立,不相干,本质上谁先执行是由内核调度决定
      • 但父进程极大概率先执行,因为内核调度的每个进程有一个运行时间,父进程生了孩子后,它的运行时长还没到
  • [PS]
    • 1 号进程 <pid 为 1 的进程> 是 init 进程,其它进程都是由它生出来的
    • 与人类世界相反,计算机世界的第一个进程一直活着,等着给子进程收尸

三、创建 10 个子进程,并打印自己的序号

10 个子进程是亲兄弟

  • 图片
  • 如果不加 18 行的 break
    • 将产生 2^10 个进程:1 -> 2 -> 4 -> 8 -> 16 -> 32 -> ... -> 2^10
    • 统计运行的父、子进程数量:ps -ef | grep -v grep | grep Ten | wc -l
      • [Ten 为可执行程序名]
  • sleep 时长不会叠加
    • 进程遇到 sleep,系统调度换到其它进程运行,最后等待时间只体现约 10s
  • i 变量被子进程带走后就独立了,不会因父进程中 i 变量的改变而改变

wait#

一、制造僵尸进程

  • 图片
  • 不用 wait 感知子进程的终止,即会产生僵尸进程
  • 用户有多种查看僵尸进程的方式 [让程序在后台运行:./a,out &]
    • 基于 ps,查看有 defunct 或 Z 标记的进程
    • 图片
    • 基于 top
    • 图片
    • 利用 pstree 可以看到僵尸进程的血缘关系
    • 图片
  • [PS] 杀僵尸进程需要杀其父进程;该父进程的父进程是 zsh,程序结束后,zsh 会告知系统对父子进程收尸

二、感知子进程返回状态

  • 图片
  • 程序在运行约 2s 后,输出如下:
  • 图片
  • ❗ 为什么子进程 return 1,父进程 wait 得到的 status 是 256
    • 16 位 int 型变量值为 256 👉 其二进制对应第 8 位为 1、其余位均为 0
    • 再参考下图 [Linux-UNIX 系统编程手册 (上册)—26.1.3 节],问题有了答案
    • 图片
    • 其实,在 man 手册中提到了可使用宏来检查状态
    • 图片
    • WEXITSTATUS (wstatus) 则可以解析退出状态
    • 在源码中,每个宏对应了下面的位操作
    • 图片
    • 所以在 printf 状态时,根据需求,通过宏处理以下即可

exec 族#

【替换为全新的进程】

  • 图片
  • 子进程在第 17 行被替换为全新的进程 [vim],之后的代码永远不会被执行
    • fork 后直接 exec:不会在 fork 时复制父进程的内存空间又在 exec 时马上启用 [写拷贝概念:当内存发生变化时,才会发生真正的拷贝]
  • wait (NULL) 负责收尸
  • execlp 的第二个参数可以任意,但和第一个参数有关联更有意义
    • 在下面可以体现该参数的某方面意义
    • 如果将 exec 代码换成第 17 行,第二个参数随意取名
    • 图片
    • 生成可执行文件 Test 的源文件 test.c 如下:
      • 图片
      • 输出 argv [0] 的值
    • 执行上下两份代码的结果如下:
      • 图片
      • 可见,第二个参数体现在了 argv [0] 变量里

附加知识点#

  • 使用 while (1){} 时在循环体里加 sleep,对 CPU 更友好
    • 否则可能导致 CPU 利用率蹿升、空转、过热
  • pstree 可以方便地看到进程的继承关系,-p 可以显示 pid
  • 查看僵尸进程:ps、ps -aux、ps -ef、top 均可
  • 死锁:两个以上的运算单元,双方都在等待对方停止运行,以获取系统资源,但是没有一方提前退出
  • 计算机中的同步与生活中的不太一样
    • 不是做同样的操作
    • 而是事件发生的顺序是确定的,是有因果关系的

思考点#

Tips#

  • du [-h]:查看当前目录以及所有子目录的大小 [human-readable]
  • 对于多进程的输出,使用 more 会将不同进程的输出独立显示
  • 推荐电影:《她》2013
    • 图片
    • 一个硅基生命与许多碳基生命的爱情故事,涉及高并发概念
    • 豆瓣百度云,提取码:8pic

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。