kill -9 PID杀死进程使用到的系统调用

kill命令介绍

kill 命令会向操作系统内核发送一个信号(多是终止信号)和目标进程的 PID,然后系统内核根据收到的信号类型,对指定进程进行处理。

kill(选项)(参数)
-a:当处理当前进程时,不限制命令名和进程号的对应关系;
-l <信息编号>:若不加<信息编号>选项,则-l参数会列出全部的信息名称;
-p:指定kill 命令只打印相关进程的进程号,而不发送任何信号;
-s <信息名称或编号>:指定要送出的信息;
-u:指定用户。
 
只有第9种信号(SIGKILL)才可以无条件终止进程,其他信号进程都有权利忽略,下面是常用的信号
HUP     1    终端断线(平滑重启进程)
INT     2    中断(同 Ctrl + C)
QUIT    3    退出(同 Ctrl + \)
TERM   15    终止
KILL    9    强制终止
CONT   18    继续(与STOP相反, fg/bg命令)
STOP   19    暂停(同 Ctrl + Z)

示例:

[root@itbkz.com ~]#ps -ef |grep chronyd
chrony   30365     1  0 19:58 ?        00:00:00 /usr/sbin/chronyd
root     30385 29218  0 19:58 pts/1    00:00:00 grep --color=auto chronyd
[root@itbkz.com ~]#kill -9 30483
[root@itbkz.com ~]#ps -ef |grep chronyd
root     30508 29218  0 20:00 pts/1    00:00:00 grep --color=auto chronyd

kill命令的原理

执行kill -9 <PID>,首先要产生信号。执行kill程序需要一个pid,根据这个pid找到这个进程的task_struct(这个是Linux下表示进程/线程的结构),然后在这个结构体的特定的成员变量里记下这个信号。 这时候信号产生了但还没有被特定的进程处理,叫做Pending signal。

等到下一次CPU调度到这个进程的时候,内核会保证先执行do_signal这个函数看看有没有需要被处理的信号,若有,则处理;若没有,那么就直接继续执行该进程。所以我们看到,在Linux下,信号并不像中断那样有异步行为,而是每次调度到这个进程都是检查一下有没有未处理的信号。

信号传递

调用kill命令之后,信号和目标进程的PID被用户发送到内核。

kill本身就是个程序,是有源代码的,它的代码可以在Linux的coreutils里找到。代码很长,我就不全复制过来了,有兴趣的可以去仔细看看。kill命令的核心代码是长这样的:

static int send_signals (int signum, char *const *argv) {kill (pid, signum);} 
int main (int argc, char **argv) {send_signals (signum, argv + optind);}

kill命令调用了send_signals (signum, argv + optind)函数,send_signals (signum, argv + optind)又调用了系统调用kill (pid, signum),该系统调用在Linux内核linux-3.16.3/kernel/signal.c中实现,其通过对描述进程的结构体task_struct进行操作,在task_struct中特定的成员变量里记下需要传递的信号。task_struct结构体中与信号相关的部分如下:

struct task_struct {/* signal handlers */ 
	struct signal_struct *signal; /* 一个进程所有线程共享一个signal */ 
	struct sighand_struct *sighand; sigset_t blocked,real_blocked; /* 哪些信号被阻塞了 */ 
	sigset_t saved_sigmask; /* restored if set_restore_sigmask() was used */ 
	struct sigpending pending; /* 进程中的多个线程有各自的pending */}

kill (pid, signum)系统调用的核心代码如下,为了方便理解,我给核心逻辑增加了注释:

SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) {return kill_something_info(sig, &info, pid); 
} 

static int kill_something_info(int sig, struct siginfo * info, pid_t pid) {
    int ret;
    // 如果pid大于0,就把信号发送给指定的进程 
    if (pid > 0) {
        ret = kill_pid_info(sig, info, find_vpid(pid));
        return ret;
    }
    // 如果pid <=0 并且不等于-1,发送信号给-pid指定的进程组 
    if (pid != -1) {
        ret = __kill_pgrp_info(sig, info, pid ? find_vpid( - pid) : task_pgrp(current));
    } 
    else { //否则发信号给除自己所属进程之外的其它所有进程 
        int retval = 0,
        count = 0;
        struct task_struct * p;
        for_each_process(p) {
            if (task_pid_vnr(p) > 1 && !same_thread_group(p, current)) {
                int err = group_send_sig_info(sig, info, p); ++count;
                if (err != -EPERM) retval = err;
            }
        }
        ret = count ? retval: -ESRCH;
    }
    return ret;
}

kill (pid, signum)会调用kill_something_info(sig, &info, pid)kill_something_info(sig, &info, pid)会根据pid的正负来决定是发给特定的进程还是一个进程组,我们下面主要来看发给一个特定进程的情况,即调用kill_pid_info(sig, info, find_vpid(pid)),该函数的代码如下:

int kill_pid_info(int sig, struct siginfo * info, struct pid * pid) {
    int error = -ESRCH;
    struct task_struct * p;
    p = pid_task(pid, PIDTYPE_PID);
    if (p) {
        error = group_send_sig_info(sig, info, p);
    }
    return error;
}```

kill_pid_info(sig, info, find_vpid(pid))出现了我们上文提到的task_strcut,这个是Linux下表示每个进程/线程的结构体,根据struct pid找到这个结构后,就调用了group_send_sig_info(sig, info, p),这个函数的代码如下:

int group_send_sig_info(int sig, struct siginfo * info, struct task_struct * p) {
    int ret;
    ret = do_send_sig_info(sig, info, p, true);
    return ret;
}

int do_send_sig_info(int sig, struct siginfo * info, struct task_struct * p, bool group) {
    unsigned long flags;
    int ret = -ESRCH;
    if (lock_task_sighand(p, &flags)) {
        ret = send_signal(sig, info, p, group);
        unlock_task_sighand(p, &flags);
    }
    return ret;
}

static int send_signal(int sig, struct siginfo * info, struct task_struct * t, int group) {
    int from_ancestor_ns = 0;
    #ifdef CONFIG_PID_NS from_ancestor_ns = si_fromuser(info) && !task_pid_nr_ns(current, task_active_pid_ns(t));
    #endif
    return __send_signal(sig, info, t, group, from_ancestor_ns);
}

static int __send_signal(int sig, struct siginfo * info, struct task_struct * t, int group, int from_ancestor_ns) {
    struct sigpending * pending;
    struct sigqueue * q;
    int override_rlimit;
    int ret = 0,result; // 发送给进程和线程的区别在这里,如果是进程,则&t->signal->shared_pending,否则&t->pending pending = group ? &t->signal->shared_pending : &t->pending; 
    /* * fast-pathed signals for kernel-internal things like SIGSTOP * or SIGKILL. */ 
    if (info == SEND_SIG_FORCED) goto out_set; 
    … 
    out_set: // 把信号通知listening 
    	signalfd. signalfd_notify(t, sig); // 将sig加入目标进程的信号位图中,待下一次CPU调度的时候读取 
    	sigaddset(&pending->signal, sig); // 用于决定由哪个进程/线程处理该信号,然后wake_up这个进程/线程 
    	complete_signal(sig, t, group); 
    ret: 
    	trace_signal_generate(sig, info, t, group, result); 
    return ret; 
}

可以看到,最终调用到__send_signal,设置信号的数据结构,唤醒需要处理信号的进程,整个信号传递的过程就结束了。这时候信号还没有被进程处理,还是一个pending signal。

信号处理(内核)

内核调度到该进程时,会调用do_notify_resume()来处理信号队列中的信号,之后这个函数又会调用do_signal(),再调用handle_signal(),具体过程就不用代码说明了,最后会找到每一个信号的处理函数,问题是这个信号的处理函数怎么找到?

还记得在上文提到的task_struct吗,里面有一个成员变量sighand_struct就是用来存储每个信号的处理函数的。

struct sighand_struct { 
	atomic_t count; /* 引用计数 */ 
	struct k_sigaction action[_NSIG]; /* 存储处理函数的结构 */ 
	spinlock_t siglock; /* 自旋锁 */ 
	wait_queue_head_t signalfd_wqh; /* 等待队列 */ 
}; 

struct k_sigaction { 
	struct sigaction sa; 
} 
struct sigaction { 
	__sighandler_t sa_handler;
}

其中sa_handler就指向了信号的处理程序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值