APUE---chap10信号---10.4~10.10(kill/pause/alrm/time/signal)

这篇博客探讨了Linux程序中信号处理的不可靠性,例如SIGINT信号的行为。同时介绍了可重入函数的概念,即在函数调用未结束时再次调用不会出错的函数。文章还详细讲解了kill和raise函数用于发送信号,以及alarm和pause函数在定时任务中的应用。此外,通过示例展示了如何使用alarm和signal实现高精度定时,并讨论了流控策略,如漏桶和令牌桶算法在控制打印速度和资源分配中的作用。
摘要由CSDN通过智能技术生成

10.4 不可靠的信号

  • 信号的不可靠 指的是signal的行为是不可靠。
  • 比如有如下例子:按书上的意思是第二次点(ctrl+c)会不调用int_hanlder(),会调用默认函数。但实测是:每次的(ctrl+c)都会调用int_handler()函数。。
#include <signal.h>

void int_handler(int a) {
    write(1, "7", 1);
}
int main() {
    int i;
    signal(
        SIGINT,
        int_handler);  //函数的调用是内核布置的。
                       //信号的不可靠指的是信号的行为不可靠:当收到SIGINT,然后内核调用int_handler函数,当内核第二次收到同样的信号,第二次的调用会恢复为默认调用

    for (int i = 0; i < 10; i++) {
        write(1, "*", 1);
        sleep(1);
    }
}

10.6 可重入函数:

第一次调用还没结束,又发生了第二次调用,不会出错的函数叫可重入的函数。

系统调用都是可重入的,一部分库函数也是可重入的:比如memcpy.. 尤其要注意返回值为指针的函数,如果是malloc出来的内存,重入可能就会有问题。

  • 信号的响应过程:

10.9 函数kill和raise

  • 函数kill

man 2 kill:  kill是发送信号给一个进程

  • kill的参数

pid > 0 : 杀死pid 进程。

pid = 0 :杀死调用kill所在进程组的所有进程。

pid = -1: 向所有除了init的进程发送信号,比如init()进程杀死所有进程。

pid < -1:会把进程组id为pid的进程组,全部发送这个信号。

  • 函数raise(signo): 给调用raise()的线程发送signo

相当于 kill(getpid(), signo);  

10.10 函数alarm和pause

  • 函数alarm(sec); 在sec秒后会产生SIGALRM信号

 alarm默认杀死进程:

int main() {
    alarm(5);  // 5s后默认杀死进程
    while (1) {
    }
    exit(0);
}

当一个进程又多个alarm,会以最后,最短的时间发出SIGALRM信号

// 5s后杀死进程
int main() {
    alarm(5);  // 默认杀死进程
    // alarm(2);  //超过一个alarm(),程序会在设定的alarm() 最后,时间最早的alarm被杀死

    while (1) {
    }
    exit(0);
}

慎重使用sleep(): 如果sleep通过alarm + pause来封装,如果程序中有alarm信号,就会产生异常。

  • 定时5s的程序:用time()
// 5s的定时程序:用time()函数
int main() {
    time_t end;
    int64_t count = 0;

    end = time(NULL) + 5;

    while (time(NULL) <= end) {
        count++;
    }
    printf("%lld\n", count);
}
// time ./a.out 可以看到time调用的时间是不准的
// lbw@HP-ZHAN-66-Pro-15-G3:~/lbw/gitNote/chap10$ time ./a.out
// 1761106842

// real    0m5.770s
// user    0m5.769s
// sys     0m0.000s
  • 定时5s的程序:用alarm+signal
// 5s的定时程序:用alarm() + signal()

// 同一个程序用gcc main.c -O1优化编译,如果不加volatile,会让程序进入死循环
// 而volatile的用处就体现出来了:让编译器对这个变量不在进行编译优化,这个时候在用gcc main.c -O1优化编译也不会出问题
volatile int loop = 1;

void alarm_handler(int s) {
    loop = 0;
}

int main() {
    alarm(5);
    signal(SIGALRM, alarm_handler);
    int count = 0;
    while (loop) {
        count++;
    }
    printf("%lld\n", count);

    exit(0);
}
//打印结果如下 :可以看到时间精度很高
// lbw@HP-ZHAN-66-Pro-15-G3:~/lbw/gitNote/chap10$ time ./a.out
// 2474397847

// real    0m5.002s
// user    0m5.000s
// sys     0m0.000s
// lbw@HP-ZHAN-66-Pro-15-G3:~/lbw/gitNote/chap10$

注意:如果用优化编译,需要注意变量的定义要加上volatile

  • 流控之漏桶
//漏桶: 流控:控制打印速度 alarm + signal
volatile int loop = 0;

void alarm_handler(int s) {
    alarm(1);
    loop = 1;
}

int main(int argc, char **argv) {
    signal(SIGALRM, alarm_handler);
    alarm(1);

    while (1) {
        while (!loop)
            pause();
        loop = 0;
        printf("hello,hello\n");
    }
}

  • 流控之令牌桶
//令牌桶:有权限一秒打印一次hello,
//如果程序被阻塞了3s,分配给程序的资源:3s打印3次hello,那么解除阻塞之后,程序就会拥有打印3次hello的权限,于时可以打印3次。
// 相比于漏桶,不会白白浪费可使用资源。
volatile int token = 0;
#define MAX 100
void alarm_handler(int s) {
    alarm(1);
    token++;  //增加令牌
    if (token > MAX) {
        token = MAX;
    }
}

int main(int argc, char **argv) {
    signal(SIGALRM, alarm_handler);
    alarm(1);

    while (1) {
        while (token <= 0)
            pause();
        token--;  //消耗令牌
        // 模拟操作
        printf("hello\n");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值