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就指向了信号的处理程序。