守护进程和僵尸进程

守护进程和僵尸进程

这是操作系统中两个完全不同的概念,一个是有意设计的特殊进程类型 ,另一个是进程结束后的临时状态

1. 核心区别速览

特征 守护进程 僵尸进程
定义 后台运行的进程,脱离终端 已结束但未被父进程回收的进程
状态 正在运行 已死亡(只保留进程描述符)
目的 提供服务(如 httpd、sshd) 是错误状态,不是设计目标
资源 占用内存、CPU 只占用进程表条目
危害 正常服务 过多会耗尽进程ID
如何产生 主动设计(fork、setsid等) 父进程未调用 wait()
如何解决 正常服务 父进程调用 wait() 或杀死父进程

2. 守护进程(Daemon)

什么是守护进程

守护进程是运行在后台、不与终端交互、持续提供服务的进程

python 复制代码
# 生活中的类比
守护进程 = 物业公司
- 一直在后台运行
- 不直接与住户交互
- 提供电力、清洁、安保等服务

守护进程的特征

  • 没有控制终端(TTY)
  • 通常以 d 结尾(如 sshdhttpdmysqld
  • 父进程通常是 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 模块会自动处理大部分回收工作
  • 使用进程池可以避免手动管理进程生命周期
相关推荐
Frank学习路上3 小时前
【AI技能】跟着费曼学自动驾驶
人工智能·机器学习·自动驾驶
克里斯蒂亚诺·罗纳尔达4 小时前
智能体学习16——学习与适应(Learning-and-Adaptation)-深入解读
深度学习·学习·机器学习
call me by ur name4 小时前
ERNIE 5.0 Technical Report论文解读
android·开发语言·人工智能·机器学习·ai·kotlin
lisw055 小时前
《计算机辅助设计与图形学学报》分析评介!
人工智能·机器学习
xiaoyaohou116 小时前
003、轻量化改进(一):网络剪枝原理与实战
算法·机器学习·剪枝
极光代码工作室6 小时前
基于NLP的智能客服系统设计与实现
python·深度学习·机器学习·ai·自然语言处理
云程笔记6 小时前
021.损失函数深度解读:YOLO的定位、置信度、分类损失计算
人工智能·yolo·机器学习·计算机视觉·分类·数据挖掘
weixin_513449966 小时前
walk_these_ways项目学习记录第九篇(通过行为多样性 (MoB) 实现地形泛化)--学习算法
学习·算法·机器学习
龙腾AI白云7 小时前
多模大模型应用实战:智能问答系统开发
python·机器学习·数据分析·django·tornado