ZetCode

Python os.setsid 函数

上次修改时间:2025 年 4 月 11 日

本篇综合指南探讨了 Python 的 os.setsid 函数,该函数用于创建新的进程会话。我们将介绍 Unix 进程组、会话管理和实际的守护进程化示例。

基本定义

os.setsid 函数会创建一个新会话,并将该进程设置为会话的领导者。这是一个 Unix 特定的系统调用,用于与控制终端分离。

主要特性:创建新会话,成为会话领导者,成为进程组领导者,没有控制终端。 返回新的会话 ID。

创建新会话

这个基本示例演示了如何使用 os.setsid 创建一个新会话。 子进程成为独立于父进程的会话领导者。

basic_setsid.py
import os
import time

pid = os.fork()

if pid == 0:  # Child process
    print(f"Child PID: {os.getpid()}")
    print(f"Child PGID before setsid: {os.getpgid(0)}")
    
    # Create new session
    sid = os.setsid()
    print(f"New SID: {sid}")
    print(f"Child PGID after setsid: {os.getpgid(0)}")
    
    time.sleep(10)  # Keep process alive to observe
else:  # Parent process
    print(f"Parent PID: {os.getpid()}")
    print(f"Parent PGID: {os.getpgid(0)}")
    os.waitpid(pid, 0)  # Wait for child

子进程使用 os.setsid 创建一个新会话,从而变得独立。 父进程的进程组保持不变,而子进程获得一个新的进程组。

请注意,如果调用进程已经是进程组领导者,os.setsid 将会失败,这就是我们首先进行 fork 的原因。

创建守护进程

os.setsid 的常见用途是创建守护进程。 此示例显示了完整的守护进程化过程,包括会话创建和文件描述符处理。

daemon_process.py
import os
import sys
import time

def daemonize():
    # Fork first time to background
    pid = os.fork()
    if pid > 0:
        sys.exit(0)  # Exit parent
    
    # Create new session
    os.setsid()
    
    # Fork second time to ensure not session leader
    pid = os.fork()
    if pid > 0:
        sys.exit(0)
    
    # Change working directory
    os.chdir('/')
    
    # Close file descriptors
    for fd in range(3, 1024):
        try:
            os.close(fd)
        except OSError:
            pass
    
    # Redirect stdio to /dev/null
    os.open('/dev/null', os.O_RDWR)  # stdin
    os.dup2(0, 1)  # stdout
    os.dup2(0, 2)  # stderr

if __name__ == '__main__':
    daemonize()
    while True:
        with open('/tmp/daemon.log', 'a') as f:
            f.write(f"Daemon running at {time.ctime()}\n")
        time.sleep(5)

这通过两次 fork、创建新会话和处理文件描述符来创建一个合适的守护进程。 守护进程会定期写入日志文件。

双重 fork 确保守护进程无法重新获得控制终端。 工作目录已更改,以防止文件系统卸载问题。

处理会话领导者限制

此示例演示了进程组领导者无法调用 setsid 的限制,以及如何通过正确的 fork 来解决此问题。

session_leader.py
import os

try:
    # This will fail because we're already a process group leader
    sid = os.setsid()
    print(f"Created session {sid}")
except OSError as e:
    print(f"Error calling setsid: {e}")

# Proper approach: fork first
pid = os.fork()
if pid == 0:  # Child
    try:
        sid = os.setsid()
        print(f"Child created session {sid}")
        print(f"New PGID: {os.getpgid(0)}")
    except OSError as e:
        print(f"Child error: {e}")
    os._exit(0)
else:  # Parent
    os.waitpid(pid, 0)
    print("Parent exiting")

第一次尝试失败,因为主进程是进程组领导者。 第二种方法首先进行 fork,允许子进程(不是组领导者)成功创建一个新会话。

这说明了为什么守护进程化通常涉及在调用 setsid 之前进行 fork,以避免进程组领导者限制。

进程组管理

此示例显示了 setsid 如何影响进程组,并演示了使用 os.setpgid 和 os.getpgid 进行进程组管理。

process_groups.py
import os
import time

def show_ids(label):
    print(f"{label}: PID={os.getpid()}, PGID={os.getpgid(0)}, SID={os.getsid(0)}")

show_ids("Parent before fork")

pid = os.fork()
if pid == 0:  # Child
    show_ids("Child before setsid")
    
    # Create new session
    sid = os.setsid()
    show_ids("Child after setsid")
    
    # Fork again to create a process group
    pid2 = os.fork()
    if pid2 == 0:  # Grandchild
        show_ids("Grandchild before setpgid")
        os.setpgid(0, 0)  # Create new process group
        show_ids("Grandchild after setpgid")
        time.sleep(10)
    else:
        time.sleep(10)
else:  # Parent
    show_ids("Parent after fork")
    os.waitpid(pid, 0)

这演示了创建新会话和进程组时进程层次结构的变化。 孙子进程在子进程的会话中创建自己的进程组。

输出显示了 PID、PGID 和 SID 值如何在每个步骤中变化,从而说明了进程之间的关系。

终端控制和会话领导

此示例演示了 setsid 如何与控制终端分离,以及会话领导者如何与终端信号交互。

terminal_control.py
import os
import signal
import time

def handler(signum, frame):
    print(f"Received signal {signum}")

# Set up signal handler
signal.signal(signal.SIGHUP, handler)

pid = os.fork()
if pid == 0:  # Child
    print(f"Child PID: {os.getpid()}")
    
    # Create new session (detaches from terminal)
    os.setsid()
    
    print("Child in new session, ignoring SIGHUP")
    signal.signal(signal.SIGHUP, signal.SIG_IGN)
    
    time.sleep(30)  # Keep process alive
else:  # Parent
    print(f"Parent PID: {os.getpid()}")
    time.sleep(1)  # Let child set up
    
    # Send SIGHUP to child's process group
    os.kill(-pid, signal.SIGHUP)
    os.waitpid(pid, 0)

子进程创建一个新会话并忽略 SIGHUP 信号。 父进程尝试将 SIGHUP 发送到子进程的原始进程组。

这演示了会话领导者如何以不同的方式处理终端信号,以及进程组如何影响信号传递。

会话 ID 继承

此示例显示了会话 ID 如何跨 fork 和 exec 调用继承,以及 setsid 如何创建新的独立会话。

session_inheritance.py
import os
import sys

def show_session():
    print(f"PID: {os.getpid()}, SID: {os.getsid(0)}")

show_session()  # Original process

pid = os.fork()
if pid == 0:  # Child
    show_session()  # Inherited session
    
    # Create new session
    os.setsid()
    show_session()
    
    # Execute new program
    os.execvp("python3", ["python3", "-c", 
             "import os; print(f'Exec PID: {os.getpid()}, SID: {os.getsid(0)}')"])
else:  # Parent
    os.waitpid(pid, 0)
    show_session()  # Still original session

子进程首先继承父进程的会话,然后使用 setsid 创建一个新会话,最后执行一个新程序来显示其会话。

这说明了 exec 保留会话 ID,而 setsid 创建一个完全新的独立会话。

安全注意事项

最佳实践

资料来源

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我自 2007 年以来一直在撰写编程文章。 迄今为止,我已经撰写了 1,400 多篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 Python 教程