Linux进程控制
提示:文章内容较长,请参考目录阅读
这里写目录标题
- 一、进程创建
-
- 1.1 了解fork函数
- 1.2 fork函数内部完成的工作
- 1.3 用户空间与内核空间
- 1.4 写时拷贝
- 1.5 fork的用法——守护进程
- 二、进程终止
-
- 2.1 进程终止的场景
- 2.2 进程常见退出方法
- 2.3 _exit与exit函数
- 2.4 关于缓冲区
- 2.5 atexit函数
- 三、进程等待
-
- 3.1 进程等待的意义
- 3.2 wait
-
- 基本功能
- 僵尸进程
- wait程序
- 3.3 waitpid
-
- 基本功能
- 程序
- 3.4 参数status
- 四、进程程序替换
-
- 4.1 基础知识
- 4.2 exec函数族
-
- exec函数族简介
- execl
- execlp
- execle
- execv
- execvp
- execve
一、进程创建
1.1 了解fork函数
Linux中通过调用fork函数来创建一个新进程,创建出来的新进程为子进程,而原先的进程为父进程。
通过man指令查看fork函数:
fork函数返回值有两种情况:
- 创建失败,返回-1
- 创建成功,给父进程返回一个大于0的数,即子进程的进程号;给子进程返回0
1.2 fork函数内部完成的工作
- 分配新的内存块和内核数据结构给子进程
- 将父进程的部分数据结构拷贝至子进程,即拷贝父进程PCB,修改其中的为子进程PID
- 将子进程添加到系统进程列表(双向链表)
- fork返回,开始调度器调度(操作系统开始调度
1.3 用户空间与内核空间
- 内核空间:Linux操作系统和驱动程序都运行在内核空间,所以包括fork在内的系统调用都在内核空间运行
- 用户空间:一般应用程序都运行在用户空间,包括我们写的程序
程序在运行中如果调用了系统函数,就会切换到内核空间,执行完系统函数的程序后,再返回用户空间。
1.4 写时拷贝
使用fork创建子进程后,父子进程代码共享,子进程的PCB和页表都是拷贝父进程的,同一变量的物理地址和虚拟地址的映射关系时一样的,操作系统并没有给子进程单独开辟物理空间。当父子进程任意一方要进行写入时,便会拷贝数据,此时父子进程通过页表映射到不同的物理地址。
如图所示:
1.5 fork的用法——守护进程
- 守护进程,守护进程是运行在后台的一种特殊进程,脱离于终端,可以避免被任何终端所产生的信息所打断。父进程创建子进程,通过进程程序替换让子进程执行真正的业务。
通过ps axj查看当前运行着的守护进程:
二、进程终止
2.1 进程终止的场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
2.2 进程常见退出方法
正常终止(可以通过echo $?)查看退出码
- main函数返回
- 调用exit
- 调用_exit
异常终止
- 程序崩溃(解引用空指针,内存越界等)
- Ctrl c,终止程序
2.3 _exit与exit函数
使用方法
_exit终止当前进程
exit函数
exit函数的作用也是终止当前进程,并且函数最终会调用_exit函数,但是在调用_exit之前,还做了其他工作:
- 执行用户通过atexit或者on_exit定义的清理函数
- 关闭所有打开的流,冲刷缓冲区
区别
exit与_exit的区别如图所示:
程序演示
用两个程序展示exit与_exit函数的区别
在这个程序中,由于调用了_exit函数,缓冲区的数据没有被冲刷就退出了进程(注意"hello world"后面没有换行符,因为换行符也会冲刷缓冲区),所以运行该程序后没有打印结果:
将程序稍作修改,调用exit函数:
结果正常打印:
因为exit函数在终止进程之前会冲刷缓冲区,所以打印输出了缓冲区中的字符串"hello world!"
2.4 关于缓冲区
- 缓冲区的作用:
缓冲区由C标准库建立,目的是减少IO次数,因为IO操作比较耗费时间,所以定义了缓冲区,出发刷新缓冲区的条件之后,缓冲区的内容才会继续IO操作(打印输出、将内容写入文件、从文件中读取等)。 - 刷新缓冲区的方式:exit、main函数中的return、fflush、"\n"等
- 缓冲方式:
全缓冲:缓冲区写满才进行IO
行缓冲:在输入和输出中遇到"\n"时进行IO操作
不缓冲:无缓冲区,标准IO库不对字符进行缓冲存储
2.5 atexit函数
atexit函数,注册一个回调函数,在进程终止的时候进行调用(注册回调函数的函数)
程序验证:
三、进程等待
3.1 进程等待的意义
僵尸进程
由于子进程退出时,父进程没有回收子进程的退出信息,可能会造成“僵尸进程”的问题,造成内存泄漏。而进程等待时处理僵尸进程最合理的解决方案。
3.2 wait
基本功能
wait时操作系统提供的函数,包含在头文件中
- 作用:等待退出的子进程,回收子进程退出状态信息
- 函数特性:谁调用谁等待,直到等到子进程退出,发起阻塞之后,需等待函数完成功能才返回。
- 返回值:调用成功返回被等待进程pid,失败则返回-1
- 参数:status是一个输出型参数,在父进程中定义,获取子进程的退出状态
僵尸进程
在学习使用wait前,先回顾僵尸进程,如果子进程先于父进程退出,父进程没有回收子进程的退出状态信息,子进程便成为了僵尸进程,如图:
运行结果:
通过ps aux查看进程状态信息:
wait程序
程序1
在这个程序中,子进程仍然先于父进程退出,但是由于父进程中调用了wait函数进行进程等待,回收了子进程的退出状态信息,所以子进程没有成为僵尸进程,如图:
子进程已经正常退出:
程序2
运行结果:
在子进程运行的30秒中,通过pstack指令查看其父进程正在执行的代码:
通过上面的程序我们为你可以看出,当父进程调用wait函数时,如果子进程没有退出,则父进程会一直等待,直至等到子进程退出为止,这种属性称为阻塞。
3.3 waitpid
基本功能
- 函数功能:进行进程等待
- 返回值:正常调用时返回收集到的子进程的pid;
设置options为"WNOHANG"时,调用中若没有已退出的子进程可收集,则返回0;
调用出错,则返回-1; - 参数:
pid:-1,表示等待任一子进程;>0,等待进程号与pid值相等j的进程,如:设置pid为10000,则表示只等待进程号为10000的进程。
status:子进程的退出信息,与wait相同。
options:可设置为"WNOHANG",表示若子进程未结束,则不再等待,函数返回0,若子进程正常结束,返回子进程的pid。
程序
程序运行后,通过ps aux查看进程状态:
3.4 参数status
含义
wait和waitpid函数都有参数status,该参数是一个整型,输出型参数。在父进程中定义,将地址传递给子进程,在子进程中可以更改其值,设置退出状态。
status是一个整型,内部不同的位具有不同的含义:
正常终止:退出状态会被设置位退出码
异常终止:终止信号和core dump标志位被设置
根据不同不同比特位所表示的含义,可以通过按位与的方式获得各个信号的值(与’1’进行按位与)。
终止信号:status&0x7F
core dump:(status>>7)&0x1
退出状态:(status>>9)&0xFF
程序
根据(status&0x7f)的值是否为0判断子进程是否为正常终止,为0则表明终止信号未被设置,正常终止,否则便是异常终止
- 正常终止
程序
运行结果
- 异常终止
程序
运行结果
四、进程程序替换
4.1 基础知识
进程替换的原因
父进程创建出来的子进程和父进程拥有相同的代码,因此想要执行不同的程序,就需要让子进程调用程序替换的接口,执行其他程序。
替换原理
调用exec函数进行进程替换并没有创建新的进程,所以进程号没有改变,替换以后,从新程序最开始处执行。并完成两项工作:替换进程的代码和数据段,更新堆栈。
4.2 exec函数族
exec函数族简介
库函数
系统函数
exec函数族中,execve是系统提供的函数,其余均为库函数。
命名规则
- l(list) :表示参数采用列表,第一个参数是可执行程序本身,各个参数之间用","隔开,并以NULL结尾
- v(vector):参数采用数组(字符指针数组)
- p(path):代表自动搜索环境变量PATH
- e(env):表示自己维护环境变量
execl
execl参数传路径和命令行参数
execlp
execlp会自动搜索PATH环境变量,第一个参数传文件名即可
execle
参数采用列表,需自己维护环境变量
execv
参数采用字符指针数组,需定义指针数组保存命令行参数
execvp
execvp函数参数用数组保存,且会自动搜索PATH环境变量,所以可以不用传递路径
execve
execve是操作系统提供的函数,其他exec函数都是由C标准库提供,通过调用execve实现,execve参数保存在数组,且需要维护环境变量