当前课程知识点:基于Linux的C++ > 第十三讲 进程编程 > 13.06 进程管理(三) > LinuxCPP1306
我们刚才讲了 你fork()一个子进程
然后子进程就结束了
你没等它清除 没等它
那么那个子进程
不就变成了僵尸进程了吗
可是你能在父进程里边调用wait()等它吗
当然可以这么做 但是你要注意
如果你把wait()写在那个父进程里
那么在子进程没有结束的时候
父进程不就被阻塞了嘛
它自己的事情不就做不了了吗
所以 一个更好的策略
是子进程结束的时候
触发一个终止信号
然后父进程接收到这个信号
处理这个子进程的清除工作
所以最合适的方案
就是子进程的异步清除
有一个信号叫SIGCHLD
当子进程终止的时候
它会向父进程自动地发送这个信号
你不用管 它自动就会发
那么我们的父进程就可以
针对这个子进程写一个信号处理例程
完成子进程的最后清除工作
这个清除是属于异步清除
我们并不需要等待它
你父进程自己做自己的事
子进程结束了 我就响应这个事
去清除它 就完了
就不需要等待 等待那就是同步了
我们不需要等待 这就叫异步清除
看实际的程序代码
我们会定义一个信号处理例程函数CleanUp()
带着信号数
那个信号那个值作为它的参数
返回值是void
CleanUp()
当然依然是C函数
封装在extern “C”里
它要做的事情很简单
核心代码就一个 wait()
等待子进程结束 负责清除它
然后把子进程的状态返回出去
保存起来 就OK了
父进程要处理的就是SIGCHLD这个信号
我们要定义
struct sigaction结构体的一个对象sa
这样的一个变量sa被我们定义出来
然后我们memset() 全清零
然后设sa_handler这个字段
为CleanUp()这个函数的入口地址
我就把子进程发送SIGCHLD这个信号之后
我们父进程要做的事
它的信号活动给设置好了
接下来一步就是sigaction()函数调用
把sa这个信号活动和SIGCHLD这个信号
给挂接在一起
我就把它注册进去了
现在一旦父进程接受到子进程
发送过来的SIGCHLD的信号
它就会自动地调用CleanUp()这个函数
去做清除工作
你在主程序里就不用调用wait()去等待它了
你的主程序的正常代码
你就可以朝下写了
父进程也就可以正常往下执行了
什么时候那个信号来
什么时候去处理那个信号嘛
不就完了吗
这就叫子进程的异步清除
最后一类进程叫守护进程
我们前面其实谈到过
daemon它是非常特殊的一个进程
它是在后台默默地做事情
它没有输入 也没有输出
所以创建守护进程的方式
它是非常非常特殊的
它需要几个明确的步骤
而且理论上
大部分步骤实际上应该是顺序的
其中有些步骤可以颠倒 没关系
但是总体上必须按照这样的顺序来
首先你要创建一个新的进程
新的进程将会成为未来的守护进程
那么守护进程的父进程要退出
这样的话 我们能够保证
那个守护进程的父进程的父进程
也就是它的祖父进程
明确地能够确认到父进程已经结束了
且知道守护进程本身
不是那个进程组的组长
只有非组长
它才能够创建新的会话呀 对吧
这个地方也是需要注意的
守护进程这个时候
就会创建一个新的进程组和新的会话
它自己就升格了
就变成了新的这个进程组的组长
和会话的首领 创建的新会话
因为还没有来得及
和我们控制终端相关联
所以它是没有输入输出的
然后我们要改变工作目录
因为守护进程将来在后台做事情的时候
往往它可能会随系统一样启动
那么在这种情况下
工作目录本身不应该继续使用
创建这个守护进程的那个进程的工作目录
那个进程可能是另外一个目录
那个目录在文件系统中
甚至有可能被管理员给卸载掉
所以你的守护进程随系统启动的时候
可能没用它
用它就可能会导致问题
所以往往我们会改变它的工作目录
一般就改变到根目录下
或者在根目录的
其它的某个固定的子目录下
保证它不会被卸载的地方
然后我们要重设文件的权限掩码
这也一样的
守护进程很多时候我们不能继承
或者说不需要继承
父进程的文件访问权限可能不够
所以我们可能不使用它的掩码
我们可能创建自己的文件权限掩码
这是一个
还有的时候 权限多了我也不要
所以它是有一个
自己的文件权限掩码要重设
关闭所有的文件描述符
它不需要继承
父进程打开的那些文件描述符
它都不需要
标准流 它都不需要
它没有输入 没有输出
也没有错误流 不要
这三个错误流全都给我定向到哑终端
做完这些事情
就创建了一个守护进程
创建完了 你可以根用户权限去运行它
它就能够在后台默默地替你做事情了
你还可以设定它随系统一起启动
那么每次系统启动
这个守护进程就能够启动了
我们看实际的代码
我们在这里fork()一个新的子进程
看错误处理
if( pid == -1 ) 说明创建子进程失败
我们return -1
否则if( pid != 0 )
这个时候它是在父进程里
我们父进程直接退出
接下来就是pid为0的时候
那就全是子进程代码
首先创建一个新的会话 setsid()
自动地会创建一个新的进程组
子进程就是会话的首领和进程组的组长
成功了就向下去做
等于等于-1 说明不成功 我们返回
设置工作目录 chdir()
改变它的工作目录
重设它的文件权限掩码
umask()调用
关闭它的文件描述符
这个进程里面
可能会打开好多个文件描述符
理论上都应该一一把它关闭掉
但对我们这个程序来讲呢
因为我们前面没有打开任何文件描述符
它实际上只打开了三个标准流
所以我一个简单for循环
把它前三个文件描述符给它关掉就完了
标准输入流、标准输出流、标准错误流
文件描述符0、1、2
for循环从0到3
i小于3 从0、1、2
三个文件描述符全部关闭 over
关闭标准的文件描述符
三个标准流全关掉
子进程的三个标准流重设到哑终端
open( "/dev/null", O_RDONLY )
把这个哑终端这个设备打开
以只读模式打开
打开它就会创建一个新的文件描述符
注意这一点
它优先选择最小的文件描述符
最小的是哪一个
0号文件描述符
因为我们把三个标准流一关
这个子进程里
事实上什么文件描述符都没用
所以当它打开哑终端的时候
它自动地将使用0号文件描述符
0号文件描述符对应什么
标准输入流
这就意味着我们的标准输入流
被挂到了哑终端上
所有的数据输入都从哑终端来
能来吗 什么都来不了
就关掉了标准输入流
然后调用dup( 0 )
dup()这是一个文件描述符的复制操作
我们后面会讲到这个函数
现在同学们就记得
它就把0号描述符复制一个副本
这个函数很特殊
它这个副本
产生一个新的文件描述放哪呢
它优先选择最小的那个文件描述符
最小的文件描述符是谁
1号文件描述符
也就是把0号文件
描述符复制到1号文件描述符那里面去
1号描述符对应着什么
标准输出流
现在它就把标准输出流
也挂到了哑终端上
也就是所有的输出信息
全都仍到哑终端
那么什么内容都不再显示
再来一遍
它又把标准错误流 2号文件描述符
也挂到哑终端上去
这就意味着3个标准流
都被我们关闭了
全都定向到了哑终端
守护进程都不需要它们
全部都不需要 把它关掉
全都定位到哑终端 over
接下来你的守护进程的实际工作代码
就可以朝下去做了
做完了 当然得return 0
好复杂啊 刚才那几个步骤 对吧
所以Linux操作系统下边
提供了一个函数daemon()
帮助你减轻创建守护进程的负担
调用这一个函数就能搞定
daemon()这个函数超简单
两个参数 都是整型
nochdir(no change directory)、noclose
如果nochdir非0
实际上就是true
就是不改变工作目录
如果noclose非0 就是true
不关闭所有打开的文件描述符
你传进去
它就给你构造一个daemon
一个守护进程出来
一般情况下 我们调用daemon()
创建一个守护进程的时候
这俩参数传的都是0
成功的时候这个函数返回0
失败的时候返回-1
并设置errno的值
你就可以去查那个errno的值到底是什么
就知道它发生了什么错误
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口