Python跨平台获取服务状态:Linux下如何区分进程与服务?
Great question! The distinction between services and processes on Linux is definitely trickier than on Windows, where services are a clearly defined, managed system entity. Let's break down how to tackle this, and how you can adjust your code to get accurate service statuses on Linux.
First, a quick context check:
On Linux, "services" are typically processes managed by an init system (like systemd, the default on most modern distros, or the older sysvinit). Unlike Windows, there's no dedicated "service" object—services are just regular processes monitored, started, and stopped by the init system. That's why your current psutil code grabs all processes, not just managed services.
Option 1: Query the init system directly (most reliable)
The most accurate way to get service statuses is to ask the init system itself. For modern Linux (systemd-dominated), we can use systemctl with JSON output for easy parsing. For older sysvinit systems, we fall back to the service command.
Here's how to integrate this into your code:
import os import psutil import subprocess import shlex import json def getServiceDict(): service_dict = {} if os.name == "nt": for service in psutil.win_service_iter(): service_dict[service.name()] = service.status() elif os.name == "posix": # Try systemd first (modern Linux distros) try: cmd = "systemctl list-units --type=service --all --no-pager --output=json" result = subprocess.run( shlex.split(cmd), capture_output=True, text=True, check=True ) services = json.loads(result.stdout) for service in services: # Strip the .service suffix from the unit name service_name = service["unit"].replace(".service", "") service_state = service["active"] service_dict[service_name] = service_state except (subprocess.CalledProcessError, FileNotFoundError): # Fallback to sysvinit for older systems try: cmd = "service --status-all" result = subprocess.run( shlex.split(cmd), capture_output=True, text=True ) lines = result.stdout.splitlines() for line in lines: if line.startswith("["): # Parse sysvinit's status format status_char = line[1] service_name = line[4:].strip() if status_char == "+": service_dict[service_name] = "running" elif status_char == "-": service_dict[service_name] = "stopped" else: service_dict[service_name] = "unknown" except Exception as e: # Last resort: fall back to psutil processes (mark as non-service) for pid in psutil.pids(): try: proc = psutil.Process(pid) service_dict[proc.name()] = f"process ({proc.status()})" except (psutil.NoSuchProcess, psutil.AccessDenied): continue return service_dict
Option 2: Use psutil to detect managed processes
If you prefer not to rely on external commands, you can use psutil to check clues that a process is a managed service:
- Most systemd services have a parent PID of 1 (the systemd process itself)
- Their control group (cgroup) path will include references to
systemd
Here's a helper function to identify systemd-managed processes:
def is_systemd_service(proc): try: # Check if parent process is systemd (PID 1) if proc.ppid() != 1: return False # Verify cgroup includes systemd reference cgroups = proc.cgroup() return any("systemd" in part for part in cgroups) except (psutil.NoSuchProcess, psutil.AccessDenied): return False
You can then modify your Linux code to only include these processes:
elif os.name == "posix": for pid in psutil.pids(): try: proc = psutil.Process(pid) if is_systemd_service(proc): service_dict[proc.name()] = proc.status() except (psutil.NoSuchProcess, psutil.AccessDenied): continue
Key Notes
- Permissions: Some service details (like cgroup info or full systemctl output) may require root privileges. Test with
sudoif you're missing expected services. - Edge Cases: Not all processes with PID 1 as parent are services (some user processes reparent to init). That's why the
systemctlmethod is more reliable. - Legacy Systems: The sysvinit fallback works but has inconsistent output formats across distros, so it's less ideal than systemd.
内容的提问来源于stack exchange,提问作者Dean

