守护进程和僵尸进程

守护进程和僵尸进程

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

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 模块会自动处理大部分回收工作
  • 使用进程池可以避免手动管理进程生命周期
相关推荐
没有梦想的咸鱼185-1037-16631 小时前
AI-Python机器学习与深度学习技术:CNN/Transformer/扩散模型、SHAP可解释及Hermes智能体自动化
人工智能·python·深度学习·机器学习·chatgpt·cnn·transformer
一只小bit6 小时前
LangGraph 子图使用和房源搜索Agent综合案例实现
机器学习·langchain·llm·langgraph
海兰7 小时前
【高速缓存】RedisVL为文本生成嵌入向量实践指南
人工智能·python·机器学习
老刘说AI10 小时前
SGLang 深度优化: Radix 缓存与复杂任务的极致吞吐
人工智能·神经网络·机器学习·缓存·架构·sglang
我登哥MVP11 小时前
Headroom 深度指南:AI Agent 上下文压缩实战
人工智能·python·深度学习·神经网络·机器学习·自然语言处理·pip
再渊13 小时前
基因归因到底怎么计算的?
python·深度学习·机器学习
暴躁的大熊13 小时前
机器学习中的知识图谱:概念、核心技术与应用场景
人工智能·机器学习·知识图谱
ShallWeL14 小时前
【机器学习】(25)—— 反向传播
人工智能·神经网络·机器学习
待磨的钝刨14 小时前
深入理解主成分分析(PCA)
人工智能·线性代数·算法·机器学习
审小匠OpenCPAi14 小时前
审计异常预警阈值怎么设?静态规则、统计阈值(3σ)与机器学习异常的误报率对比
人工智能·机器学习·审计