• 使用alarm函数实现sleep,使用alarm函数实现对阻塞操作设置超时


    1 相关函数介绍

    1.1 函数kill和raise

    kill将信号发送给进程。raise函数则允许进程向自身发送信号。

    1. #include
    2. // 成功,返回0;失败,返回-1
    3. int kill(pid_t pid, int signo);
    4. int raise(int signo);

    调用raise(signo)等价于调用kill(getpid(), signo)
    kill函数的pid参数分为以下4种情况:
    (1)pid>0
    将信号发送给进程ID为pid的进程
    (2)pid=0
    将信号发送给与发送进程属于同一进程组的所有进程(发送进程具有权限向这些进程发送信号)
    (3)pid<0
    将信号给其进程组ID等于pid绝对值,且发送进程具有权限向其发送信号的所有进程
    (4)pid==-1
    将信号发送给发送进程有权限向它们发送信号的所有进程
    进程将信号发送给其他进程需要权限。超级用户可以将信号发送给任一进程。对于非超级用户,规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。

    1.2 函数alarm和pause

    alarm函数可以设置一个定时器,在将来的某个时刻该定时器会超时。当定时器超时时,产生SIGALARM信号。如果忽略或不捕捉该信号,则默认动作是终止调用alarm函数的进程。

    1. #include
    2. //返回值:0或之前设置的闹钟时间的余留值
    3. unsigned int alarm(unsigned int seconds);

    每个进程只能有一个闹钟时间。如果在调用alarm时,之前已为该进程注册的闹钟时间还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的返回值。以前注册的闹钟时间则被新值代替。如果有之前注册的尚未超过的闹钟时间,且本次调用的seconds值为0,那么其余留值仍作为alarm函数返回,然后取消以前的闹钟时间。
    pause函数使调用进程挂起直到捕捉到一个信号。

    1. #include
    2. //返回值:-1,errno设置为EINTR
    3. int pause(void);

    只有执行了一个信号处理程序并从其返回时,pause才返回。这时,pause返回-1,errno设置为EINTR。

    2 使用alarm函数实现sleep

    使用alarm和pause,进程可以使自己休眠一段指定的时间。

    1. #include
    2. #include
    3. #include
    4. static void sig_alrm(int signo)
    5. {
    6. /* nothing to do, just return to wake up the pause */
    7. printf("sig_alrm !!! \n");
    8. }
    9. unsigned int sleep1(unsigned int seconds)
    10. {
    11. if (signal(SIGALRM, sig_alrm) == SIG_ERR)
    12. return(seconds);
    13. printf("alarm start !!!\n");
    14. alarm(seconds); /* start the timer */
    15. pause(); /* next caught signal wakes us up */
    16. printf("alarm done !!!\n");
    17. return(alarm(0)); /* turn off timer, return unslept time */
    18. }
    19. int main(void) {
    20. unsigned int unslept;
    21. unslept = sleep1(5);
    22. printf("unslept:%d \n", unslept);
    23. return 0;
    24. }

    测试结果如下:

    1. xxx$ ./sleep1
    2. alarm start !!!
    3. sig_alrm !!!
    4. alarm done !!!
    5. unslept:0

    实际上,sleep1函数有竞争关系:alarm在调用pause之前超时。如果发生这种情况,则在调用pause后将不会再捕捉到信号,调用者将永远被挂起。
    可以使用setjmp和longjmp来避免如上的竞争条件:

    1. #include
    2. #include
    3. #include
    4. #include
    5. static jmp_buf env_alrm;
    6. static void sig_alrm(int signo)
    7. {
    8. printf("sig_alrm!!! \n");
    9. longjmp(env_alrm, 1);
    10. }
    11. unsigned int sleep2(unsigned int seconds)
    12. {
    13. if (signal(SIGALRM, sig_alrm) == SIG_ERR)
    14. return(seconds);
    15. if (setjmp(env_alrm) == 0) {
    16. printf("alarm start!!! \n");
    17. alarm(seconds); /* start the timer */
    18. pause(); /* next caught signal wakes us up */
    19. printf("after pause 1 !!! \n");
    20. }
    21. // longjmp to here!
    22. printf("after pause 2 !!! alarm done!!! \n");
    23. return(alarm(0)); /* turn off timer, return unslept time */
    24. }
    25. int main(void) {
    26. unsigned int unslept;
    27. unslept = sleep2(5);
    28. printf("unslept:%d \n", unslept);
    29. return 0;
    30. }

    测试结果如下:

    1. xxx$ ./sleep2
    2. alarm start!!!
    3. sig_alrm!!!
    4. after pause 2 !!! alarm done!!!
    5. unslept:0

    sleep2函数也有问题,涉及到与其他信号的交互。例如如下伪代码:

    1. unsigned int sleep2(unsigned int);
    2. static void sig_int(int);
    3. int main(void)
    4. {
    5. unsigned int unslept;
    6. if (signal(SIGINT, sig_int) == SIG_ERR)
    7. err_sys("signal(SIGINT) error");
    8. unslept = sleep2(5);
    9. printf("sleep2 returned: %u\n", unslept);
    10. exit(0);
    11. }
    12. static void sig_int(int signo)
    13. {
    14. //耗时操作>5s
    15. }

    如上程序若SIGINT的信号处理程序先触发,且是耗时操作>5s。则等sleep2(5) 5s超时后,sig_int程序会被突然打断,引起异常。

    3 使用alarm函数实现对阻塞操作设置超时

    alarm函数还常用于对可能阻塞的操作设置时间上限值。
    如下程序,从标准输入读一行,然后将其写到标准输出上。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define MAXLINE 4096
    7. static void sig_alrm(int);
    8. static jmp_buf env_alrm;
    9. int main(void)
    10. {
    11. int n;
    12. char line[MAXLINE];
    13. if (signal(SIGALRM, sig_alrm) == SIG_ERR)
    14. printf("signal(SIGALRM) error");
    15. if (setjmp(env_alrm) != 0) {
    16. // longjmp here!!!
    17. printf("read timeout \n");
    18. goto out1;
    19. }
    20. alarm(10);
    21. if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
    22. printf("read error \n");
    23. alarm(0);
    24. write(STDOUT_FILENO, line, n);
    25. exit(0);
    26. out1:
    27. printf("out1 exit(1) >>> \n");
    28. exit(1);
    29. }
    30. static void sig_alrm(int signo)
    31. {
    32. longjmp(env_alrm, 1);
    33. }

    测试结果如下:

    1. xxx$ ./read2
    2. read timeout
    3. out1 exit(1) >>>
    4. xxx$ ./read2
    5. safasdf
    6. safasdf

  • 相关阅读:
    T-Rex2: Towards Generic Object Detection via Text-Visual Prompt Synergy论文解读
    猿创征文|学习记录之 PHP 中的面向对象编程
    MySQL架构 & InnoDB存储引擎
    基本类型转换和引用类型转换
    企业电子招标采购系统源码+项目说明+功能描述+Spring Cloud + Spring Boot 前后端分离 + 二次开发
    零成本搭建个人博客之图床和cdn加速
    【Windows系统5分钟搭建Linux环境】
    MySQL50题
    Vue封装websocket双向通讯
    Python飞机大战小游戏
  • 原文地址:https://blog.csdn.net/u012906122/article/details/126091384