Linux 信号概念

程序在执行的时候,几乎任何时刻都会反生事件。
信号通常用来向一个进程通知事件。
信号是不可提前预知的,所以信号是异步的。
信号随时都可能发生,接收信号的进程也可以没有控制权。
每个信号名都以SIG开头,信号名的定义在<signal.h>中。
信号名一般都是宏,内部通常是一个正整数。
许多情况下都会出现信号,如硬件异常,非法的内存引用,软件异常等等。
kill函数就是向目标进程发一个SIGKILL信号,就和kill命令一样。
当进程收到一个信号,他可以对信号采取如下三种措施之一
--忽略这个信号;
--捕获这个信号,这需要执行一段称为信号处理器的特殊代码;
--允许执行信号的默认操作。
当信号发生的事件出现时,比如硬件异常,就会产生一个针对具体进程的信号。
当进程对发送给它的信号采取措施的时候,就叫该信号被传送。
产生信号和传递信号之间的时间间隔称为信号未决。
信号的部署是指进程如何响应信号。
进程可以忽略信号、用默认操作处理信号、响应这个信号。
进程不能简单的通过判断一个变量,例如errno来判断是否出现了一个信号。

关于信号中断逻辑的理解

如果抛开线程本身通过精心设计的 checkpoint 被『杀死』逻辑(例如每次循环检查 flag ,
主线程只需要设置一下 flag 就可以达到『杀死』子线程),那么事实上没有手段可以『杀死』一个正在『用户态』的线程,
至少在linux平台,是没有办法强制停止一个正在进行cpu密集型计算作业线程的,除了直接 kill 进程,别无他法。

不信你可以写写代码试试,比如用pthread_create一个计算斐波那契数列第100位的程序,
假设计算过程在你的机器上持续10秒,然后主线程sleep 1秒之后去尝试杀掉它,
哪怕你用pthread_kill,pthread_cancel这些函数依旧是无法杀掉它的,它依然会跑10秒。
linux就没有提供『真正意义上的强制杀死线程的api』(windows倒是提供了这么一个危险的api,但也不鼓励使用),
而这些所谓的『杀死』线程的api,都是一种信号量响应,它需要条件才能生效——你的线程在『内核态』。
只有你的线程在内核状态,才能响应同样是内核范畴的signal。

『内核态』可以是sleep状态,sync状态,io-blocking状态等等,
所以假设你在你刚刚的计算斐波那契数列的函数中,每次递归或者循环的时候都sleep(0),userland交还给操作系统,
你的pthread_cancel将会生效。

很多高级语言,脚本语言通过对sleep(0)封装来实现的userland context的交还,
从而实现信号检测,像is_interrupt()一类,简单来说就是人为制造内核态的checkpoint。

所以『杀掉』一个线程的精髓在于『适时地让你的程序陷入内核态』(最常见的手段就是sleep)并处理可能的故障以及资源的回收。
现在就需要搞清楚,线程的『卡住』是卡在什么地方?有可能是内核态,比如阻塞在socket的read上,
那么就是io-blocking状态,此时你用pthread_cancel或者pthread_kill都是可以『杀死』线程的。
如果线程卡在一个算法上面,这个算法本身过于复杂,要跑很久,你想在跑完之前就『杀死』它,那就没办法直接杀掉这个线程。

 经过测试,以下程序无法正常结束。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

void* thread_func(void* arg) {
    int n = 1000000;
    long long fib1 = 0, fib2 = 1, fib;
    int i = 0;

    while (1)
    {
        for (i = 2; i <= n; i++) {
            fib = fib1 + fib2;
            fib1 = fib2;
            fib2 = fib;
        }
    }

    return 0;
}

int main() {
    pthread_t thread1;
    int num1 = 1;
    int ret;

    pthread_create(&thread1, NULL, thread_func, (void*)&num1);

    sleep(3);
    printf("begin pthread_kill\n");
    signal(SIGUSR2, SIG_IGN);
    ret = pthread_kill(thread1, SIGUSR2);
    if (ret != 0) {
        printf("发送信号失败\n");
        return 1;
    }

    printf("end pthread_kill\n");

    pthread_join(thread1, NULL);

    return 0;
}

在Linux中,主线程执行CPU密集型计算时,可能会占用大量的CPU资源,导致其他线程无法获得足够的CPU时间片,从而影响系统的响应性能。

为了避免这种情况,Linux内核会定期发送信号给进程,例如SIGALRM信号,以便让进程中的线程有机会释放CPU资源,从而保证系统的响应性能。

当主线程执行CPU密集型计算时,如果收到了信号,它会暂停当前的计算,执行信号处理函数,然后继续执行之前的计算。

这种方式可以让主线程在保证计算进度的同时,响应信号,从而保证系统的响应性能。因此,即使主线程执行CPU密集型计算,也可以被信号打断。

 

posted on 2016-09-05 17:26  寒魔影  阅读(495)  评论(0编辑  收藏  举报

导航