我是靠谱客的博主 孝顺眼睛,这篇文章主要介绍Linux信号实现精确到微秒的sleep函数:通过sigsuspend函数解决时序竞态问题,现在分享给大家,希望可以做个参考。

原理就是先使用定时器定时,然后再使用pause函数或者sigsuspend函数主动阻塞挂起,最终恢复现场。

如果使用pause函数的话,优点是使用简单,缺点是有可能产生时序竞态,导致进程一直阻塞下去:在定时和挂起之间有一个缝隙,有可能定时后因为其他原因没有直接挂起,而是被动挂起或者处理其他信号,但这段时间时钟还在继续计时,当时间到了以后信号就被发送,等回来主动挂起的时候再也等不到那个信号了,因此进程就会被一直挂起。为了解决这个问题,我们在定时前先将SIGALRM信号屏蔽,然后定时、挂起,在挂起的同时我们解除对SIGALRM的屏蔽,这样就不用担心主动挂起前错过信号了,最后恢复现场。

可以根据代码理解一下,其实是一个很符合直觉的过程。需要注意的是pausesigsuspend只有失败返回值-1,不过这个失败的意思是挂起失败,也就是恢复运行,从这个意义上来讲应该是成功返回值,因此我们不要对-1返回值做处理(我顺手处理了,然后一直出错检查了半天)。

代码如下:

Utils.h:里面是一些我封装的函数,为了简化代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// // Created by edward on 2021/5/7. // #ifndef LINUX_UTILS_H #define LINUX_UTILS_H #include <string> #include <initializer_list> #include <signal.h> /*! * 检查系统调用返回值 * @param x 返回值 * @param msg 错误提示语句 * @param y 错误状态,默认为-1 */ void check_error(int x, const std::string &msg = "error", int y = -1); /*! * 清零mask,并将il中的信号加入到mask中 * @param mask * @param il */ void add2mask(sigset_t *mask, std::initializer_list<int> il); /*! * 将il中的信号从mask中删除 * @param mask * @param il */ void del2mask(sigset_t *mask, std::initializer_list<int> il); #endif //LINUX_UTILS_H

mysleep函数:
2021.05.11更新:修复了传入参数为0或者负数的bug。如果传入参数都是0的话将导致进程进入阻塞状态无法被唤醒

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
struct itimerval my_sleep(int seconds, int microseconds) { if (seconds <= 0 && microseconds <= 0) return {0, 0}; //注册SIGALRM信号捕捉函数 struct sigaction act, oldact; act.sa_handler = alrm_handler; act.sa_flags = 0; sigset_t mask, oldmask,suspendmask; sigemptyset(&mask); //屏蔽键盘信号 add2mask(&mask, {SIGINT, SIGQUIT, SIGTSTP}); act.sa_mask = mask; check_error(sigaction(SIGALRM, &act, &oldact), "sigaction error"); //屏蔽alarm信号 add2mask(&mask, {SIGALRM}); check_error(sigprocmask(SIG_BLOCK, &mask, &oldmask), "sigprocmask error"); //设置定时器 struct itimerval new_value, old_value; new_value.it_value = {seconds, microseconds}; new_value.it_interval = {0, 0}; check_error(setitimer(ITIMER_REAL, &new_value, &old_value), "setitimer error"); //主动阻塞挂起等待被信号唤醒 //pause(); //使用pause会产生竞态,导致信号失效,最终导致进程无限制挂起 //通过首先将信号屏蔽防止信号失效,然后再使用sigpending函数在挂起期间解除对ALRM信号的屏蔽,使得进程最终能够被唤醒 //在挂起时解除屏蔽alarm信号 suspendmask = oldmask; del2mask(&suspendmask, {SIGALRM}); sigsuspend(&suspendmask); //恢复现场 //恢复SIGALRM信号捕获函数 check_error(sigaction(SIGALRM, &oldact, nullptr), "sigaction error"); //重置定时器 check_error(getitimer(ITIMER_REAL, &new_value)); //获取剩余定时时间 old_value.it_interval = {0, 0}; old_value.it_value = {0, 0}; check_error(setitimer(ITIMER_REAL, &old_value, nullptr), "setitimer error"); //解除对ALRM信号的屏蔽 add2mask(&mask, {SIGALRM}); check_error(sigprocmask(SIG_UNBLOCK, &mask, nullptr), "sigprocmask error"); return new_value; //返回剩余定时时间 }

Utils.cpp:工具类实现,非常简单

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// // Created by edward on 2021/5/7. // #include "utils.h" using std::string; void check_error(int x, const string &msg, int y) { if (x == y) { perror(msg.c_str()); exit(1); } } void add2mask(sigset_t *mask, std::initializer_list<int> il) { check_error(sigemptyset(mask), "sigemptyset error"); for (auto signum : il) { check_error(sigaddset(mask, signum), "sigaddset error"); } } void del2mask(sigset_t *mask, std::initializer_list<int> il) { for (auto signum : il) { check_error(sigdelset(mask, signum), "sigdelset error"); } }

最后

以上就是孝顺眼睛最近收集整理的关于Linux信号实现精确到微秒的sleep函数:通过sigsuspend函数解决时序竞态问题的全部内容,更多相关Linux信号实现精确到微秒内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(69)

评论列表共有 0 条评论

立即
投稿
返回
顶部