ZetCode

Python os.kill Function

Last modified April 11, 2025

This comprehensive guide explores Python's os.kill function, which sends signals to processes. We'll cover signal types, process IDs, and practical process management examples.

Basic Definitions

The os.kill function sends a specified signal to a process identified by its process ID (PID). It's a wrapper around the Unix kill system call.

Key parameters: pid (process ID to signal), sig (signal number to send). Common signals include SIGTERM (15), SIGKILL (9), and SIGHUP (1).

Sending SIGTERM to a Process

SIGTERM (signal 15) is the default termination signal. It allows the process to perform cleanup before exiting. This is the polite way to request termination.

terminate_process.py
import os
import signal
import time
import subprocess

# Start a long-running process
process = subprocess.Popen(["python", "-c", "while True: pass"])

print(f"Process started with PID: {process.pid}")

# Wait a bit then send SIGTERM
time.sleep(2)
os.kill(process.pid, signal.SIGTERM)

# Wait for process to terminate
process.wait()
print(f"Process terminated with return code: {process.returncode}")

This example starts a Python process that runs indefinitely, then sends SIGTERM to request termination. The process can catch and handle SIGTERM.

The subprocess.Popen.wait() ensures we wait for the process to actually terminate after sending the signal.

Forcefully Killing a Process with SIGKILL

SIGKILL (signal 9) immediately terminates a process without cleanup. The process cannot catch or ignore this signal. Use as a last resort.

kill_process.py
import os
import signal
import subprocess
import time

# Start a process that ignores SIGTERM
cmd = '''
import signal, time
signal.signal(signal.SIGTERM, signal.SIG_IGN)
while True: time.sleep(1)
'''
process = subprocess.Popen(["python", "-c", cmd])

print(f"Process started with PID: {process.pid}")

# First try SIGTERM (will be ignored)
os.kill(process.pid, signal.SIGTERM)
time.sleep(1)

# Process still running, use SIGKILL
if process.poll() is None:
    print("SIGTERM failed, sending SIGKILL")
    os.kill(process.pid, signal.SIGKILL)
    process.wait()

print(f"Process terminated with return code: {process.returncode}")

This shows how SIGKILL can terminate a process that ignores SIGTERM. The process.poll() checks if the process is still running after SIGTERM.

SIGKILL should be used sparingly as it doesn't allow for proper cleanup of resources like open files or network connections.

Sending SIGHUP to Reload Configuration

SIGHUP (signal 1) traditionally indicates a terminal disconnect, but is often used to reload configuration. Many daemons reload config on SIGHUP.

reload_config.py
import os
import signal
import subprocess
import time

# Start a process that handles SIGHUP
cmd = '''
import signal, time

def handle_hup(signum, frame):
    print("Received SIGHUP, reloading config")
    
signal.signal(signal.SIGHUP, handle_hup)
while True: time.sleep(1)
'''
process = subprocess.Popen(["python", "-c", cmd], 
                          stdout=subprocess.PIPE,
                          universal_newlines=True)

print(f"Process started with PID: {process.pid}")

# Send SIGHUP to trigger config reload
time.sleep(1)
os.kill(process.pid, signal.SIGHUP)

# Read process output
print(process.stdout.readline())

# Clean up
os.kill(process.pid, signal.SIGTERM)
process.wait()

This demonstrates a process that catches SIGHUP to reload configuration. The subprocess captures stdout so we can see the reload message.

Many server applications like nginx and Apache use SIGHUP for graceful configuration reloading without restarting.

Sending Signals to Process Groups

Negative PIDs send signals to entire process groups. This is useful for terminating all child processes together with their parent.

process_group.py
import os
import signal
import subprocess
import time

# Start a process that creates children
cmd = '''
import os, time, subprocess
print(f"Parent PID: {os.getpid()}")
children = [subprocess.Popen(["sleep", "60"]) for _ in range(3)]
[print(f"Child PID: {c.pid}") for c in children]
time.sleep(60)
'''
process = subprocess.Popen(["python", "-c", cmd], 
                         stdout=subprocess.PIPE,
                         universal_newlines=True)

# Read PIDs from output
parent_pid = int(process.stdout.readline().split(":")[1])
child_pids = [int(process.stdout.readline().split(":")[1]) for _ in range(3)]

print(f"Parent PID: {parent_pid}")
print(f"Child PIDs: {child_pids}")

# Send SIGTERM to entire process group
time.sleep(1)
os.kill(-parent_pid, signal.SIGTERM)

# Verify all processes terminated
for pid in [parent_pid] + child_pids:
    try:
        os.kill(pid, 0)  # Check if process exists
        print(f"Process {pid} still running!")
    except ProcessLookupError:
        print(f"Process {pid} terminated")

This creates a parent process with three child processes. Sending SIGTERM to the negative parent PID terminates the entire process group.

Process groups ensure all related processes can be managed together, which is particularly useful for shell job control and service management.

Checking Process Existence with Signal 0

Signal 0 doesn't actually send a signal but checks if sending is possible. This can verify if a process exists and if you have permission to signal it.

check_process.py
import os
import subprocess
import time

# Start a temporary process
process = subprocess.Popen(["sleep", "10"])
pid = process.pid

print(f"Checking process with PID: {pid}")

# Check if process exists
try:
    os.kill(pid, 0)
    print(f"Process {pid} exists and can be signaled")
except ProcessLookupError:
    print(f"Process {pid} does not exist")
except PermissionError:
    print(f"No permission to signal process {pid}")

# Wait for process to finish and check again
process.wait()
try:
    os.kill(pid, 0)
except ProcessLookupError:
    print(f"Process {pid} no longer exists after completion")

This demonstrates using signal 0 to check process existence. The first check succeeds while the process runs, the second fails after completion.

This technique is useful for process monitoring and health checks without actually interrupting the target process.

Handling Signals in Python

Python processes can catch and handle signals using the signal module. This example shows a Python process that gracefully handles termination.

signal_handler.py
import os
import signal
import sys
import time

def handle_term(signum, frame):
    print(f"Received signal {signum}, cleaning up...")
    # Perform cleanup here
    time.sleep(1)  # Simulate cleanup
    print("Cleanup complete, exiting")
    sys.exit(0)

# Register signal handlers
signal.signal(signal.SIGTERM, handle_term)
signal.signal(signal.SIGHUP, handle_term)

print(f"Running with PID: {os.getpid()}")
print("Send SIGTERM or SIGHUP to trigger handler")

# Main loop
while True:
    time.sleep(1)
    print("Working...")

This script registers handlers for SIGTERM and SIGHUP. When either signal is received, the cleanup function runs before exiting.

Signal handlers allow processes to shutdown gracefully, saving state and releasing resources properly when terminated.

Windows Considerations

On Windows, os.kill has limited functionality compared to Unix:

Security Considerations

Best Practices

Source References

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Python tutorials.