Linux 开发系列笔记(2.2) - 进程基本控制
建站提交历史文章,原文写作时间 2023 年 2 月前后。
进程基本控制
- 进程创建、进程查看、进程重载、进程调试、进程回收
进程状态命令
查看与设置进程资源限制
cpp
1 | $ ulimit -a // 查看全部进程资源限制 |
查看进程状态快照
cpp
1 | $ ps aux // 或 ps ajx |
-
(默认)
:显示当前终端进程及其子进程的粗略信息。 -
a
:显示终端上的所有进程,包括其他用户进程。 -
x
:显示无终端的所有进程,不包括其他用户进程。 -
u
:显示进程详细信息。多为用户关注信息,如:CPU
占用、内存占用、STAT
状态。 -
j
:显示进程详细信息。多为控制相关信息,如:PPID
、GID
、STAT
状态。
STAT
状态:
txt
1 | 状态: |
监视实时进程状态
cpp
1 | $ top |
按键操作:
-
h
:相关帮助。 -
P
:按CPU
占用排序。 -
M
:按内存占用排序。 -
T
:按运行时长排序。 -
U
:按用户名筛选。 -
k
:杀死进程,输入无效数据退出。 -
q
:退出top
。
杀死进程
cpp
1 | kill <pid> // 杀死进程(默认通过 15 号信号) |
查看与设置进程号
getpid
,getppid
- 关于
进程组
与会话
相关更多内容见2.4.1 进程、进程组、会话
章节。
cpp
1 |
|
创建子进程
创建进程分叉
- 进程分叉将创建当前进程(
父进程
)的拷贝。进程分叉初始,父进程
与子进程
具有完全相同的状态:两者共享相同代码,运行到代码的相同位置,具有完全相同的数据集。当然,两者的进程标识pid
与ppid
是不同的。 返回值
:进程分叉将有两个返回值,一个来自父进程
,一个来自子进程
。- 在
父进程
中,返回子进程
的pid
。 - 在
子进程
中,返回0
。 - 如果进程创建异常,不会产生
子进程
,返回-1
。
- 在
cpp
1 |
|
示例:
cpp
1 |
|
输出:
cpp
1 | Fork test: |
注:由于fork
直译为分叉
,所以该进程创建又称为进程分叉。
进程分叉内存管理原理
fork
函数将进程进行拷贝,生成一个完全相同的进程。- 由于
fork
函数生成一个完全相同的进程,因此子进程
将与父进程
有相同的虚拟地址映射表。换言之,子进程
与父进程
的虚拟地址
映射到相同的物理地址
,但是两者在数据上应该具有独立性,这导致了子进程
与父进程
对当前数据只有读的权限,不具备写的权限。如果进程对物理地址
的值进行修改,将导致两者的数据同时发生修改,这不是期望发生的。 进程分叉
运用了写时拷贝
的技术,具备了写的权限,同时为进程分叉
节约了大量的时间与空间。在进程分叉
的初始状态,子进程
与父进程
的虚拟内存指向相同的真实内存空间,当子进程
与父进程
的任意一方需要修改时,修改部分的虚拟内存会指向一片新的真实内存,并赋予这个空间新的值。这样做的好处是,例如代码和一些无需更改的数据,将不会重复占用真实内存。

- 以上过程均由内核自动完成。
退出进程
- 进程退出的方法有三种:
_exit(0)
、exit(0)
(推荐)、main
函数中的return 0
。 main
函数中的return 0
实际上是调用了exit(0)
。exit
函数是标准C库
函数,是对Linux
函数_exit
的封装。
cpp
1 |
|
GDB 多进程调试
- 关闭
detach-on-fork
才能在调试中切换进程。
cpp
1 | (gdb) show detach-on-fork // 显示 detach-on-fork 选择 |
- 设置
follow-fork-mode
跟踪起始进程。
cpp
1 | (gdb) show follow-fork-mode // 显示 follow-fork-mode 选择 |
- 切换跟踪进程。
cpp
1 | // 请先确保 detach-on-fork 选项关闭 |
exec 函数簇重载进程
execl
、execlp
、execle
、execv
、execvp
、execvpe
exec
函数簇将重载进程,直接覆盖当前进程代码,因此exec
函数簇通常需要在fork
进程中执行。l
:参数使用列表传递,传递一系列参数,以NULL
参数(哨兵
)结尾。p
:在环境变量
和当前路径中查找file
,其他函数仅在当前路径查找。e
:传递envp
数组指定路径搜索目录,此时哨兵
显得尤为重要。v
:参数使用argv
数组传递,而不是参数列表。
cpp
1 |
|
- 注:根据习惯,第一个参数为文件名,即本身。如
ps aux
参数为ps
、aux
。 - 注:如果执行成功没有
返回值
,因为当前进程代码已覆盖,无法继续执行。 - 注:system 函数实际是对 fork 函数与 exec 函数簇的封装,通常使用 system 函数可以更方便的满足需求。
回收子进程
孤儿进程与僵尸进程
孤儿进程
:父进程
结束、子进程
尚未结束,此时子进程
为孤儿进程
,init进程
(PID
为1
)代替父进程
托管子进程
。孤儿进程
虽然脱离父进程
控制,但是会自然结束,通常不具有危害。僵尸进程
:子进程
结束、父进程
尚未结束,如果父进程
未处理子进程
的结束信息,未回收子进程
的内核区数据,此时子进程
为僵尸进程
。在进程概述中讲到,子进程
结束不会自动释放内核区数据,等待父进程
接收并处理其结束信息。如果处于将死状态的子进程
一直存在,将不断占用内核区内存和PID
,大量积累具有危害。父进程
不处理僵尸进程
,在其结束后,init
进程会为其处理僵尸进程
。但是如果父进程
是长期运行的进程(如服务器进程),危害将持续积累。wait
函数与waitpid
函数用于处理和回收子进程
内核区数据。
wait 函数回收子进程
-
wait
函数与waitpid
函数用于接收进程状态变化,默认阻塞
进程等待任意子进程
状态变化。 -
返回值
:当任意进程状态变化,函数返回子进程PID
,设置wstatus
值表示该子进程
状态变化编码(后述情况不设置wstatus
);当WNOHANG
选项打开,并且存在未结束子进程
,返回0
(当WNOHANG
选项关闭,并且存在未结束子进程
,阻塞);当所有子进程
已回收,抛出No child process
错误,返回-1
;当出现其他错误,返回-1
。 -
pid
:分为下述情况:< -1
:等待任意组ID
为-pid
(pid
的绝对值)的子进程
状态变化。-1
:等待任意子进程
状态变化。0
:等待任意组ID
与函数启动时父进程
的组ID
相同的子进程
状态变化。> 0
:等待进程ID
为pid
的子进程
状态变化。- 如果对应进程符号
ID
要求但不是子进程
(子孙进程
不属于子进程
)的,不属于控制范围。
-
wstatus
:整型指针,用于接收状态变化编码,可为NULL
表示不接收信息。解码宏如下:WIFEXITED(wstatus)
:返回01整型。是否正常退出(exit
为正常退出)。WEXITSTATUS(wstatus)
:返回整型。如果上宏为真,返回退出状态(exit
参数,0~255
)。WIFSIGNALED(wstatus)
:返回01整型。是否异常退出(信号退出为异常退出)。WTERMSIG(wstatus)
:返回整型。如果上宏为真,返回引发退出的信号。WIFSTOPPED(wstatus)
:返回01整型。是否进入暂停。WSTOPSIG(wstatus)
:返回整型。如果上宏为真,返回引发暂停的信号。WIFCONTINUED(wstatus)
:返回01整型。是否进程继续。
-
options
:宏如下:(使用|
连接)(默认)
:等待进程正常退出、进程异常退出(以及被追踪进程暂停)。WNOHANG
:不阻塞,立即返回。WUNTRACED
:也会等待进程(未被追踪进程)被暂停。WCONTINUED
:也会等待进程被继续。
-
wait(wstatus)
等价于waitpid(-1, wstatus, 0)
。 -
关于更多:
$ man 2 wait
。
cpp
1 |
|
信号回收子进程
- 我们注意到
wait
函数回收子进程时,需要主动检测是否有子进程状态变化;如果我们希望即使处理子进程,必须阻塞的回收子进程,或者使用独立线程处理子进程。在2.3.4 信号
章节中将提到,子进程状态变化时将触发SIGCHLD
信号,届时可以考虑使用信号即时回收子进程。
cpp
1 | // 请先了解 2.3.4 信号 章节后阅读此代码 |
评论
Gitalking ...