讨论几种父、子进程退出时相互产生的影响,同时也整理一下进程与子进程之间的关系。
孤儿进程
(没爹)
孤儿进程,顾名思义,子进程还在世的时候父进程却结束了。那么孤儿进程没了父进程,是不是就被孤立了呢?不会的,我们还需要了解到1号进程——init进程,在初始化unix系统的时候,会创建一个init进程。然后由init进程创建终端,而终端进程随着用户的接入,会启动更多的进程,以此类推。在这整个系统中,所有的进程都属于以init为根的一棵树。当某个父进程终止,子进程就会被init进程收养。在这些孤儿进程结束时,init进程会回收他们的退出信息,保证他们不一直成为僵尸进程。
以下是如何创建孤儿进程的例子
def create_orphan():
"""
:return:
"""
cur_pid = os.getpid()
pgid = os.getpgrp()
print('parent group id', pgid)
print('-----fork before-----')
# 在父进程的堆栈中,c_id 为父进程的编号,在子进程的堆栈中,值为0。
# fork将进程信息写入进程信息表,写时复制父进程的各种数据,同时设置好新进程的各种数据
# 其中子进程的执行内容设置为fork的后一句,并将进程推送到就绪态的队列中,等待调度器调度执行。
c_id = os.fork()
print('-----fork after-----')
if c_id == 0: # 子进程
ppid = os.getppid()
print(os.getpid(), 'kill', ppid)
os.kill(ppid, 9)
os.system('ps aux|grep {}'.format(ppid))
print(os.getpid(), '[killed p] ', ppid) # 杀死父进程以后被init进程接管。
print('[killed p] now ppid is ', os.getppid())
now_ppid = os.getpgrp()
print('child group id {}'.format(now_ppid))
os.killpg(now_ppid, 9)
print(os.getpid(), '[kill group]', now_ppid) # 整个组死掉了
print('end...')
else: # 父进程
print('i am ', os.getpid())
流程输出
parent group id 45381
-----fork before-----
-----fork after-----
i am 45381
-----fork after-----
45382 kill 45381
[1] 45381 killed python process.py
(dl) (dl) async ⍉ ➜ echoocking 45387 0.0 0.0 4268036 808 s003 S 1:20PM 0:00.00 grep 45381
echoocking 45383 0.0 0.0 4268616 1112 s003 S 1:20PM 0:00.00 sh -c ps aux|grep 45381
45382 [killed p] 45381
[killed p] now ppid is 1
child group id 45381
流程解释
获取group id
打印fork前的提示语
执行fork
由于当前是父进程在执行,所以进入c_id != 0 的流程。执行完后父进程等待子进程,父进程被挂起,
子进程被调度执行,子进程执行fork后的语句
进入c_id == 0 的流程
执行kill 父进程,此时子进程的父进程id已经变为1,表示该进程已由init进程进行接管
查看orphan进程的父进程,其父进程号依旧可以查询到,并且与进程组号相同
使用进程组号 执行killpg,向整组发送kill信号
可以看到整个进程组全部退出,后续的打印也就没有输出。
multiprocessing Process popen_fork
以下是multiprocessing的popen_fork的实现,和上述例子非常相似。
def _launch(self, process_obj):
code = 1
parent_r, child_w = os.pipe() # 不过为什么要创建pipe呢? pipe获得了两个文件描述符。
self.pid = os.fork()
if self.pid == 0:
try:
os.close(parent_r)
if 'random' in sys.modules:
import random
random.seed()
code = process_obj._bootstrap()
finally:
os._exit(code)
else:
os.close(child_w)
util.Finalize(self, os.close, (parent_r,))
self.sentinel = parent_r
进程创建过程
在Unix,子进程是父进程的拷贝,其地址空间是父进程地址空间的副本,不可写的内存部分是共享的,例如程序代码。被修改的变量等通过写时复制进行修改。父子进程拥有相同的内存映像、打开的文件描述符,环境变量等。
子进程执行fork后的程序代码。所以multiprocessing里的进程创建后设置执行的内容,就是在fork之后,程序计数器值为fork 后一句的语句编号。
孤儿进程总结
父进程被终止,子进程转为孤儿进程, 结束整组进程可以杀死孤儿进程。或者等待子进程自己结束,结束后的数据回收由init进程接管,所以孤儿进程不会对系统造成过多问题。
ps:此时如果你用ctrl+c,是无法结束子进程的,因为他的终端已经成了1号进程,必须找到其进程号,kill 进程号,来结束。
僵尸进程🧟♂️
(有爹,爹不管)
父进程创建,由父进程创建子进程,当子进程退出以后,大部分的资源被释放,但是还是会有例如pid, 存在时间的记录等资源没有被释放。所以当子进程退出后,子进程会先变成僵尸进程,然后由父进程进行剩余的清理工作。当父进程没有对子进程进行清理工作的话,子进程就会维持僵尸进程的状态。
过多僵尸进程会 pid 不够用。。以及系统资源会一直被占用。
僵尸进程可以用 ps aux 这个命令来观察。
[root@linux ~]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 1740 540 ? S Jul25 0:01 init [3]
root 2 0.0 0.0 0 0 ? SN Jul25 0:00 [ksoftirqd/0]
root 3 0.0 0.0 0 0 ? S< Jul25 0:00 [events/0]
.....中間省略.....
root 5881 0.0 0.3 5212 1204 pts/0 S 10:22 0:00 su
root 5882 0.0 0.3 5396 1524 pts/0 S 10:22 0:00 bash
root 6142 0.0 0.2 4488 916 pts/0 R+ 11:45 0:00 ps aux
STAT:该程序目前的状态,主要的状态有:
R :该程序目前正在运作,或者是可被运作;
S :该程序目前正在睡眠当中(可说是idle 状态啦!),但可被某些讯号(signal) 唤醒。
T :该程序目前正在侦测或者是停止了;
Z :该程序应该已经终止,但是其父程序却无法正常的终止他,造成zombie (疆尸) 程序的状态
守护进程 (自动孤儿)
在linux里,守护进程其实就是服务对应的 默默的在后台跑着的程序。
一般来说 守护进程没有任何存在的父进程(即PPID=1),成为守护进程的方式是父进程创建完子进程以后,立即退出,由init接管子进程(碰瓷init, init内心也是崩溃的)。上述也叫脱壳。
这里是python 的守护进程的实现:Sander Marechal's code sample is superior to the original
python multiprocessing daemon vs linux daemon
父进程退出,kill所有daemon进程。和linux的守护进程是俩概念。这里的daemon模式 只是普通的子进程,当非守护进程(父进程)退出的时候,daemon进程也会被退出的。在python里,daemonic processes are not allowed to have children。
以下是python设置子进程daemon的例子;
flush_key_process = Process(target=self.flush_redis_key_gap_time, name='{}_monitor'.format(self.key_name))
flush_key_process.daemon = True
flush_key_process.start()
总结: 子进程 vs 父进程
- 当父进程意外退出时,子进程会如何
子进程会变成孤儿进程,被init接管,子进程退出后的clean工作也由init进程完成。 - 当唯一的子进程退出时,父进程会如何
父进程清理子进程退出后的资源。如果父进程是阻塞等待的话,那么父进程会解除阻塞,继续执行。 - 当python multiprocessing 里以守护进程运行的时候,父进程退出,子进程会如何
会被kill。
其他
kill -9 能否在程序中被捕捉,然后执行一些清理操作?
The SIGKILL signal is sent to a process to cause it to terminate immediately (kill). In contrast to SIGTERM and SIGINT, this signal cannot be caught or ignored, and the receiving process cannot perform any clean-up upon receiving this signal.
答案是并不能,原因👆。
程序与资源管理
什麼是 daemon 與服務 (service)
僵尸进程与孤儿进程
multiprocessing.Process.daemon
python sigkill catching strategies
现代操作系统
linux系统编程之进程:父进程查询子进程的退出,wait,waitpid
网友评论