Python os.setsid 函数
上次修改时间:2025 年 4 月 11 日
本篇综合指南探讨了 Python 的 os.setsid
函数,该函数用于创建新的进程会话。我们将介绍 Unix 进程组、会话管理和实际的守护进程化示例。
基本定义
os.setsid
函数会创建一个新会话,并将该进程设置为会话的领导者。这是一个 Unix 特定的系统调用,用于与控制终端分离。
主要特性:创建新会话,成为会话领导者,成为进程组领导者,没有控制终端。 返回新的会话 ID。
创建新会话
这个基本示例演示了如何使用 os.setsid 创建一个新会话。 子进程成为独立于父进程的会话领导者。
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 的常见用途是创建守护进程。 此示例显示了完整的守护进程化过程,包括会话创建和文件描述符处理。
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 来解决此问题。
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 进行进程组管理。
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 如何与控制终端分离,以及会话领导者如何与终端信号交互。
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 如何创建新的独立会话。
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 创建一个完全新的独立会话。
安全注意事项
- 权限分离: 新会话可能会影响权限继承
- 信号处理: 会话领导者以不同的方式处理终端信号
- 资源限制: 新会话可能具有不同的资源约束
- 进程隔离: 会话提供基本的进程隔离
- 平台限制: Windows 具有不同的进程分组
最佳实践
- 先 fork: 始终在调用 setsid 之前进行 fork
- 处理 stdio: 在 setsid 之后重定向或关闭文件描述符
- 更改目录: 避免文件系统卸载问题
- 信号处理: 设置适当的信号处理程序
- 错误检查: 验证 setsid 返回值
资料来源
作者
列出所有 Python 教程。