how linux kill/killall works

The kill and killall commands in Linux are used to send signals to processes, primarily to terminate them. Here’s an in-depth explanation of their functionality:


1. The kill Command

Purpose: Send signals to processes by PID.

Syntax: kill [OPTION] [SIGNAL] PID...
Default Signal: SIGTERM (15), which requests graceful termination.
Common Signals:
SIGTERM (15): Ask the process to terminate (allows cleanup).
SIGKILL (9): Force immediate termination (cannot be ignored).
SIGHUP (1): Often used to reload configurations (e.g., daemons).

Mechanism:
  1. System Call: Uses the kill() system call to send a signal to a process.
  2. Signal Delivery:
    • The kernel checks if the user has permission to send the signal (matching UID or superuser).
    • If the process has a signal handler for the signal (except SIGKILL/SIGSTOP), it executes the handler.
    • For SIGKILL or unhandled signals, the kernel enforces the default action (termination).
  3. Permissions: Users can only signal their own processes unless they have elevated privileges.
Example:
kill -9 1234  # Force-kills PID 1234 with SIGKILL.

2. The killall Command

Purpose: Send signals to processes by name.

Syntax: killall [OPTION] [SIGNAL] PROCESS_NAME...
Process Lookup: Matches processes by name (from /proc/[PID]/comm, typically the executable name).
Variants: Behavior may differ between implementations (e.g., killall in SysV vs. BSD).

Mechanism:
  1. Process Enumeration:
    • Scans the /proc filesystem, checking the comm field of each process.
    • Sends the specified signal to all matching PIDs.
  2. Wildcards: Some implementations support pattern matching (e.g., killall -r "nginx.*").
Example:
killall -HUP nginx  # Reloads all nginx processes via SIGHUP.

3. Under the Hood: The kill() System Call

Function: int kill(pid_t pid, int sig);
PID Rules:
pid > 0: Signal sent to the specific process.
pid = 0: Signal sent to all processes in the sender’s process group.
pid = -1: Signal sent to all processes the user has permission to signal (except system processes).
pid < -1: Signal sent to the process group with ID |pid|.


4. Signal Handling in the Kernel

Signal Queue: Signals are queued if the process is not running. The kernel delivers them when the process resumes.
Uninterruptible States: Processes in D state (uninterruptible sleep, e.g., waiting for I/O) may delay signal handling until they resume.
Termination:
SIGTERM: Process can clean up (close files, release resources).
SIGKILL: Kernel immediately terminates the process without cleanup, bypassing user-space handlers.


5. Permission Checks

UID/GID Match: The sender’s UID must match the target process’s UID, or the sender must have CAP_KILL capability (typically root).


6. Common Pitfalls

Zombie Processes: Cannot be killed (already dead); their parent must reap them via wait().
Name Mismatches: killall relies on the comm field, which may differ from the executable name (e.g., truncated to 15 characters or modified by the process).
Overreach: killall may terminate unintended processes if names overlap.


7. Alternatives and Related Tools

pkill: Send signals using pattern matching (e.g., pkill -f "python script.py").
pgrep: List PIDs by name or pattern.


Summary

kill targets processes by PID, offering precise control.
killall targets processes by name, simplifying bulk operations.
• Both rely on the kill() system call and kernel-enforced permissions.
Signal Choice:
• Use SIGTERM for graceful shutdown.
• Use SIGKILL as a last resort for unresponsive processes.

Here’s an implementation of simplified versions of kill and killall in C, with detailed comments explaining how they work:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <dirent.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>

/* Simple kill implementation */
void my_kill(int pid, int sig) {
    /* Use kill() system call to send signal */
    if (kill(pid, sig) == -1) {
        perror("kill failed");
    } else {
        printf("Sent signal %d to PID %d\n", sig, pid);
    }
}

/* Process information structure */
struct proc_info {
    int pid;
    char comm[16];  /* comm field is 15 chars + null terminator */
};

/* Get process name from /proc/[pid]/comm */
int get_process_comm(int pid, struct proc_info *info) {
    char path[256];
    FILE *fp;
    
    /* Build path to comm file */
    snprintf(path, sizeof(path), "/proc/%d/comm", pid);
    
    if ((fp = fopen(path, "r")) == NULL) {
        return -1;  /* Process may have exited */
    }
    
    /* Read first line (process name) */
    if (fgets(info->comm, sizeof(info->comm), fp) == NULL) {
        fclose(fp);
        return -1;
    }
    
    fclose(fp);
    
    /* Remove trailing newline */
    info->comm[strcspn(info->comm, "\n")] = '\0';
    info->pid = pid;
    return 0;
}

/* killall implementation */
void my_killall(const char *name, int sig) {
    DIR *proc_dir;
    struct dirent *entry;
    struct proc_info info;
    
    /* Open /proc directory */
    if ((proc_dir = opendir("/proc")) == NULL) {
        perror("Can't open /proc");
        return;
    }
    
    /* Iterate through all /proc entries */
    while ((entry = readdir(proc_dir)) != NULL) {
        /* Skip non-PID entries (directories with numbers only) */
        if (!isdigit(entry->d_name[0])) continue;
        
        int pid = atoi(entry->d_name);
        
        /* Get process name */
        if (get_process_comm(pid, &info) == -1) continue;
        
        /* Compare process names */
        if (strcmp(info.comm, name) == 0) {
            my_kill(pid, sig);
        }
    }
    
    closedir(proc_dir);
}

int main(int argc, char *argv[]) {
    /* Simple command line parsing */
    if (argc < 3) {
        fprintf(stderr, "Usage: %s [-SIGNAL] PID/NAME\n", argv[0]);
        return 1;
    }
    
    int sig = SIGTERM;  /* Default signal */
    int opt = 1;
    
    /* Check for signal specification */
    if (argv[1][0] == '-') {
        sig = atoi(argv[1] + 1);  /* Convert "-15" to 15 */
        opt++;
    }
    
    /* Determine if we're using kill or killall */
    if (isdigit(argv[opt][0])) {
        /* Kill by PID */
        my_kill(atoi(argv[opt]), sig);
    } else {
        /* Killall by name */
        my_killall(argv[opt], sig);
    }
    
    return 0;
}

Key Implementation Details:

1. kill Implementation (my_kill):
• Uses the kill() system call
• Handles error reporting through perror()
• Supports any valid signal number

2. killall Implementation (my_killall):
• Scans /proc directory to find processes
• Reads process names from /proc/[PID]/comm
• Compares names using exact match
• Handles transient processes (that might exit during scanning)

3. Process Information Retrieval:
/proc/[PID]/comm contains the process name (limited to 15 chars)
• Directories in /proc with numeric names represent processes
• Handles processes that disappear between discovery and signaling

4. Signal Handling:
• Defaults to SIGTERM (15)
• Supports numeric signal specification (e.g., -9 for SIGKILL)

Limitations vs. Real Commands:

  1. No pattern matching (real killall has -r for regex)
  2. Doesn’t handle process name truncation (real comm fields are 15 chars)
  3. No user/group filtering (real commands have -u and -g options)
  4. No confirmation prompts (real killall has -i option)

Python Alternative (killall):

import os
import sys
import signal

def py_killall(name, sig=signal.SIGTERM):
    """Python implementation of killall"""
    for entry in os.listdir('/proc'):
        if not entry.isdigit():
            continue
        
        try:
            with open(f"/proc/{entry}/comm", "r") as f:
                proc_name = f.read().strip()
        except IOError:
            continue
        
        if proc_name == name:
            try:
                os.kill(int(entry), sig)
                print(f"Sent signal {sig} to PID {entry}")
            except ProcessLookupError:
                print(f"Process {entry} disappeared")
            except PermissionError:
                print(f"Permission denied for PID {entry}")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} NAME [SIGNAL]")
        sys.exit(1)
    
    sig = signal.SIGTERM
    if len(sys.argv) > 2:
        sig = int(sys.argv[2])
    
    py_killall(sys.argv[1], sig)

Key Differences from Native Commands:

  1. Real implementations handle zombie processes differently
  2. Production-grade tools use more efficient process enumeration
  3. Actual commands have better error recovery and edge-case handling
  4. Native implementations support more signal types and process attributes

These implementations demonstrate the core functionality, but real-world process management requires additional error checking and features found in mature system tools.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值