守护进程和僵尸进程
这是操作系统中两个完全不同的概念,一个是有意设计的特殊进程类型 ,另一个是进程结束后的临时状态。
1. 核心区别速览
| 特征 | 守护进程 | 僵尸进程 |
|---|---|---|
| 定义 | 后台运行的进程,脱离终端 | 已结束但未被父进程回收的进程 |
| 状态 | 正在运行 | 已死亡(只保留进程描述符) |
| 目的 | 提供服务(如 httpd、sshd) | 是错误状态,不是设计目标 |
| 资源 | 占用内存、CPU | 只占用进程表条目 |
| 危害 | 正常服务 | 过多会耗尽进程ID |
| 如何产生 | 主动设计(fork、setsid等) | 父进程未调用 wait() |
| 如何解决 | 正常服务 | 父进程调用 wait() 或杀死父进程 |
2. 守护进程(Daemon)
什么是守护进程
守护进程是运行在后台、不与终端交互、持续提供服务的进程。
python
# 生活中的类比
守护进程 = 物业公司
- 一直在后台运行
- 不直接与住户交互
- 提供电力、清洁、安保等服务
守护进程的特征
- 没有控制终端(TTY)
- 通常以
d结尾(如sshd、httpd、mysqld) - 父进程通常是 init(PID=1)
- 独立于用户登录/注销
Python 创建守护进程
python
import os
import sys
import time
import signal
def create_daemon():
"""创建守护进程的标准步骤"""
# 1. 第一次 fork(脱离父进程)
try:
pid = os.fork()
if pid > 0:
sys.exit(0) # 父进程退出
except OSError as e:
print(f"第一次 fork 失败: {e}")
sys.exit(1)
# 2. 修改工作目录为根目录
os.chdir("/")
# 3. 创建新的会话,成为会话组长
os.setsid()
# 4. 第二次 fork(确保不是会话组长,防止获取终端)
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError as e:
print(f"第二次 fork 失败: {e}")
sys.exit(1)
# 5. 重设文件权限掩码
os.umask(0)
# 6. 关闭所有文件描述符
for fd in range(3, 1024):
try:
os.close(fd)
except OSError:
pass
# 7. 重定向标准输入、输出、错误到 /dev/null
sys.stdin = open('/dev/null', 'r')
sys.stdout = open('/dev/null', 'w')
sys.stderr = open('/dev/null', 'w')
# 8. 设置信号处理
signal.signal(signal.SIGTERM, lambda sig, frame: sys.exit(0))
# 使用 Python 的 python-daemon 库(推荐)
try:
import daemon
import daemon.pidfile
def my_service():
while True:
# 守护进程的工作
with open('/tmp/daemon.log', 'a') as f:
f.write(f"守护进程运行中... {time.time()}\n")
time.sleep(5)
# 创建守护进程
with daemon.DaemonContext(
pidfile=daemon.pidfile.PIDLockFile('/tmp/mydaemon.pid'),
working_directory='/',
umask=0o077,
):
my_service()
except ImportError:
print("请安装 python-daemon: pip install python-daemon")
简单示例:自定义守护进程
python
import os
import sys
import time
def simple_daemon():
"""简化的守护进程示例"""
# fork 子进程
pid = os.fork()
if pid > 0:
print(f"主进程退出,PID={pid}")
sys.exit(0)
# 子进程成为守护进程
os.setsid()
os.chdir('/')
# 记录日志
with open('/tmp/mydaemon.log', 'w') as log:
while True:
log.write(f"守护进程 PID={os.getpid()} 运行中...\n")
log.flush()
time.sleep(10)
if __name__ == "__main__":
simple_daemon()
常见的守护进程示例
python
import multiprocessing
import os
import time
import signal
def daemon_service():
"""模拟一个守护服务"""
pid_file = '/tmp/myservice.pid'
# 检查是否已有实例在运行
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
old_pid = int(f.read())
try:
os.kill(old_pid, 0)
print(f"服务已在运行,PID={old_pid}")
return
except OSError:
pass
# 写入 PID 文件
with open(pid_file, 'w') as f:
f.write(str(os.getpid()))
try:
while True:
# 守护进程的主要工作
with open('/tmp/service.log', 'a') as log:
log.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] 服务运行中\n")
time.sleep(5)
except KeyboardInterrupt:
os.unlink(pid_file)
print("守护进程停止")
if __name__ == "__main__":
# 设置为守护进程
p = multiprocessing.Process(target=daemon_service)
p.daemon = True # 守护进程标志
p.start()
print(f"守护进程已启动,PID={p.pid}")
# 主进程继续其他工作
time.sleep(30)
3. 僵尸进程(Zombie Process)
什么是僵尸进程
僵尸进程是已经结束执行,但其进程描述符仍然保留在进程表中的进程。
python
# 生活中的类比
僵尸进程 = 已死亡但未办理销户的人
- 人已经死了(不再工作)
- 但户口本上还有记录(占用名额)
僵尸进程的产生原因
python
import multiprocessing
import os
import time
def child_process():
"""子进程会正常结束"""
print(f"子进程 {os.getpid()} 开始工作")
time.sleep(2)
print(f"子进程 {os.getpid()} 结束")
# 进程结束,但父进程没有调用 wait()
if __name__ == "__main__":
print(f"父进程 PID: {os.getpid()}")
# 创建子进程
p = multiprocessing.Process(target=child_process)
p.start()
# 父进程不调用 p.join() 或 os.wait()
# 子进程结束后会变成僵尸进程
print("父进程继续运行...")
time.sleep(10) # 父进程没有回收子进程
# 查看僵尸进程: ps aux | grep defunct
查看僵尸进程
bash
# Linux/Unix 命令
ps aux | grep defunct
ps -e -o pid,ppid,stat,cmd | grep Z
# 输出示例
# 12345 12344 Z+ [python] <defunct>
避免僵尸进程的方法
方法1:使用 join() 回收
python
import multiprocessing
import time
def worker():
time.sleep(1)
if __name__ == "__main__":
p = multiprocessing.Process(target=worker)
p.start()
p.join() # 等待子进程结束并回收
方法2:使用信号处理自动回收
python
import signal
import os
import time
# 设置 SIGCHLD 信号处理函数
def handle_sigchld(signum, frame):
"""当子进程结束时自动回收"""
try:
while True:
# 非阻塞方式回收所有子进程
pid, status = os.waitpid(-1, os.WNOHANG)
if pid == 0:
break
print(f"回收子进程 {pid}")
except ChildProcessError:
pass
signal.signal(signal.SIGCHLD, handle_sigchld)
def worker():
print(f"子进程 {os.getpid()} 工作")
time.sleep(1)
if __name__ == "__main__":
for i in range(5):
pid = os.fork()
if pid == 0:
worker()
exit(0)
time.sleep(2) # 等待子进程结束并自动回收
方法3:使用进程池自动管理
python
from multiprocessing import Pool
import time
def worker(x):
time.sleep(1)
return x * 2
if __name__ == "__main__":
# 进程池会自动回收子进程
with Pool(processes=4) as pool:
results = pool.map(worker, range(10))
# 退出 with 块时自动清理
方法4:双 fork 技术(隔离僵尸)
python
import os
import time
import sys
def create_orphan():
"""创建孤儿进程,由 init 收养"""
pid = os.fork()
if pid > 0:
# 父进程立即退出,子进程成为孤儿
sys.exit(0)
# 子进程成为孤儿,被 init(PID=1) 收养
os.setsid()
pid = os.fork()
if pid > 0:
sys.exit(0)
# 这个进程会被 init 自动回收
while True:
time.sleep(1)
print(f"孤儿进程运行中,PID={os.getpid()}")
if __name__ == "__main__":
create_orphan()
模拟僵尸进程的完整示例
python
import os
import time
def create_zombie():
"""创建僵尸进程(演示用)"""
pid = os.fork()
if pid == 0:
# 子进程
print(f"子进程 {os.getpid()} 即将结束")
exit(0) # 子进程结束
else:
# 父进程
print(f"父进程 {os.getpid()} 创建了子进程 {pid}")
print("父进程不回收子进程,子进程将成为僵尸")
print(f"运行 'ps aux | grep {pid}' 查看僵尸进程")
# 保持父进程运行
time.sleep(60)
# 最后回收
os.waitpid(pid, 0)
print("回收僵尸进程")
if __name__ == "__main__":
create_zombie()
4. 守护进程 vs 僵尸进程对比图
守护进程(正常运行的进程)
┌─────────────────────────────────────┐
│ 状态: 运行中 (Running/Sleeping) │
│ 内存: 占用正常内存 │
│ CPU: 占用CPU资源 │
│ 文件: 打开文件描述符 │
│ 目的: 提供服务 │
└─────────────────────────────────────┘
↓ 正常服务
持续运行直到停止
僵尸进程(已死但未清理)
┌─────────────────────────────────────┐
│ 状态: 僵尸 (Zombie/Defunct) │
│ 内存: 只保留进程表条目 │
│ CPU: 不占用CPU │
│ 文件: 所有资源已释放 │
│ 原因: 父进程未调用 wait() │
└─────────────────────────────────────┘
↓ 解决方法
父进程 wait() 或 杀死父进程
5. 实用命令和调试
bash
# 查看守护进程
ps -e -o pid,ppid,stat,cmd | grep -E '^ *[0-9]+.*[d]$'
ps aux | grep -E '/(usr|bin)/.*d$'
# 查看僵尸进程
ps aux | grep defunct
ps -e -o pid,ppid,stat,cmd | grep Z
# 查看进程树(显示父子关系)
pstree -p
# 查看所有进程状态
ps -e -o pid,ppid,stat,comm
# 统计僵尸进程数量
ps -e -o stat | grep Z | wc -l
6. Python 中管理守护和僵尸的完整示例
python
import os
import signal
import time
import multiprocessing
class ProcessManager:
"""进程管理器"""
def __init__(self):
self.processes = []
# 设置信号处理,自动回收僵尸进程
signal.signal(signal.SIGCHLD, self._handle_sigchld)
def _handle_sigchld(self, signum, frame):
"""处理子进程结束信号"""
try:
while True:
pid, status = os.waitpid(-1, os.WNOHANG)
if pid == 0:
break
print(f"自动回收子进程 {pid}")
except ChildProcessError:
pass
def create_daemon(self, target, *args, **kwargs):
"""创建守护进程"""
p = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
p.daemon = True
p.start()
self.processes.append(p)
return p
def create_normal(self, target, *args, **kwargs):
"""创建普通进程(会被回收)"""
p = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
p.start()
self.processes.append(p)
return p
def wait_all(self):
"""等待所有进程结束"""
for p in self.processes:
p.join()
def cleanup(self):
"""清理所有进程"""
for p in self.processes:
if p.is_alive():
p.terminate()
p.join()
def worker(name, duration):
"""工作进程"""
print(f"进程 {name} (PID={os.getpid()}) 开始工作 {duration}秒")
time.sleep(duration)
print(f"进程 {name} 结束")
if __name__ == "__main__":
pm = ProcessManager()
# 创建守护进程
daemon = pm.create_daemon(worker, "守护进程", 30)
print(f"守护进程 PID: {daemon.pid}")
# 创建普通进程
normal = pm.create_normal(worker, "普通进程", 3)
print(f"普通进程 PID: {normal.pid}")
# 等待普通进程结束
normal.join()
# 清理守护进程
time.sleep(2)
pm.cleanup()
print("主进程结束")
总结
- 守护进程:有意设计的后台服务进程,持续运行提供功能
- 僵尸进程:已结束但未被回收的错误状态,应避免
- 守护进程通过特殊技术(fork、setsid)创建,脱离终端
- 僵尸进程通过正确调用 wait()/join() 来避免
- 在 Python 中,multiprocessing 模块会自动处理大部分回收工作
- 使用进程池可以避免手动管理进程生命周期