竞态条件的赋值_信号-sunshine225-51CTO博客
一.基础知识信号产生的条件
a. 终端按键产生。如:ctrl+c(SIGINT信号),ctrl+\(SIGQUIT信号),ctrl+z(SIGTSTP信号)......
b. 系统命令和函数。如:kill(2)函数,raise函数,abort函数(SIGABRT信号)(就像exit函数一样,abort函数总是会成功的,所以没有返回值)。
c.软硬件产生。如:当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释 为SIGFPE信号发送给进程;闹钟超时产生SIGALRM信号;向读端已关闭的管道写数据时产生SIGPIPE信号; 若不想按默认动作处理信号,可调用sigaction(2)函数告诉内核如何处理某种信号......
信号处理动作
a.忽略
b.执行默认动作
c.自定义动作(捕捉信号)
阻塞信号
a.信号递达(Delivery):实际执行信号的处理动作。
b.信号未决(Pending):信号从产生到递达之间的状态。
c.信号阻塞: 进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略表示信号已经递达,因为忽略是在递达之后可选的一种处理动作。
信号在内核中的表示可以看作是这样的:
每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达才清除该标志。
(1). SIGHUP信号未阻塞也未产生过,当它递达时执行默认处理动作。
(2). SIGINT信号产生过,但正在被阻塞,所以暂时不能递达。虽然它的处理动作是忽略,但在没有解除 阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
(3). SIGQUIT信号未产生过,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
在进程解决对某信号的阻塞状态前,信号可能过多次,Linux是这样实现的:常规信号是采用只记录一次,而实时信号将这些信号保存在一个队列中。
问:如果在进程解除对某信号的阻塞之前这种信号产生过多次,如何处理?
(1)POSIX.1允许系统递送该信号一次或多次。
(2)Linux是这样实现的:常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。从上图来看,每个信号只有一 个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。未决和阻塞标志可以用sigset_t(信号集)数据类型来存储。
5.函数
1)sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何 有效信号。
2)sigfillset初始化set所指向的信号集。
在使用sigset_t类型的变量之前,一定要调用sigemptyset或sigfillset做初始化,使信号集处于确定 的状态。
3)sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
以上4个函数成功返回0,出错返回-1。
4)sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返 回1,不包含则返回0,出错返回-1。
5)sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。成功返回0,出错回-1。
6)sigpending读取当前进程的未决信号集。调用成功则返回0,出错则返回-1。
6.代码验证1 #include
2 #include
3 void printsigset(sigset_t* set)
4 {
5 int i=1;
6 for(;i<32;i++)
7 {
8 if(sigismember(set,i))
9 {
10 putchar('1');
11 }
12 else
13 {
14 putchar('0');
15 }
16 }
17 printf("\n"); //效果等价于puts(""); puts()函数用来向标准输出设备(屏幕)写字符串并换行。
18 }
19 int main()
20 {
21 sigset_t s,p;
22 sigemptyset(&s);
23 sigaddset(&s,SIGINT);
24 sigprocmask(SIG_BLOCK,&s,NULL);
25 while(1)
26 {
27 sigpending(&p);
28 printsigset(&p);
29 sleep(1);
30 }
31 return 0;
32 }
输出结果:
结果分析:
每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决状态,按Ctrl-\,因为SIGQUIT信号没有阻塞,仍然可以终止程序。
二.捕捉
1.内核如何实现信号的捕捉
如果信号的处理动作是用户自定义函数,在信号递达时就调用这个函数,这称为捕捉信号。由于信号处理函数的代码是在用户空间的,处理过程比较复杂,举例如下:
(1). 用户程序注册了SIGQUIT信号的处理函数sighandler。
(2). 当前正在执行main函数,这时发生中断或异常切换到内核态。
(3). 在中断处理完毕后要返回用户态的main函数之前检查到有信号SIGQUIT递达。
(4). 内核决定返回用户态后不是恢复main函数的上下文继续执行,而是执行sighandler数,sighandler和main函数使用不同的堆栈空间,它们之间不存在调用和被调用的关系,是两个独立的控制流程。
(5). sighandler函数返回后自动执行特殊的系统调用sigreturn再次进入内核态。
(6). 如果没有新的信号要递达,这次再返回用户态就是恢复main函数的上下文继续执行了。
就像是倒写的8,如图
2.函数
(1)a.sigaction函数可以读取和修改与指定信号相关联的处理动作;
b.成功返回0,出错返回- 1。
(2)a.signum是指定信号的编号;
b.若act指针非空,则根据act修改该信号的处理动作;
c.若oldact指针非空,则通过oldact传出该信号原来的处理动作;
d.act和oldact指向sigaction结构体。
(3)a.将sa_handler赋值为常数SIG_IGN传给sigaction表示忽略信号;
b.赋值为常数SIG_DFL表示执行系统默认动作;
c.赋值为一个函数指针表示用自定义函数捕捉信号,或者说向内核注册了一个信号处理函数。(该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。)
(1)pause函数使调用进程挂起直到有信号递达。
(2)如果信号的处理动作是终止进程,则进程止,pause函数没有机会返回;
(3)如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;
(4)如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR, 所以pause只有出错的返回值。(错误码EINTR表示“被信号中断”)。
3.代码实现1 #include
2 #include
3 #include
4
5 void sig_alrm(int signum)
6 {}
7
8 unsigned int mysleep(unsigned int nsecs)
9 {
10 struct sigaction new,old;
11 unsigned int unslept=0;
12 new.sa_handler=sig_alrm;
13 sigemptyset(&new.sa_mask);
14 new.sa_flags=0;
15
16 sigaction(SIGALRM,&new,&old);
17 alarm(nsecs);
18 pause();
19 unslept=alarm(0);
20 sigaction(SIGALRM,&old,NULL);
21 return unslept;
22 }
23 int main()
24 {
25 while(1)
26 {
27 mysleep(3);
28 printf("3 seconds passed!\n");
29 }
30 return 0;
31 }
输出结果:
结果分析:
(1). main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
(2). 调用alarm(nsecs)设定闹钟。
(3). 调用pause等待,内核切换到别的进程运行。
(4). nsecs秒之后,闹钟超时,内核发SIGALRM给这个进程。
(5). 从内核态返回此进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm。
(6). 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽, 从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
(7). pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前处理动作。
三.补充
1.sig_atomic_t类型与volatile限定符
(1)sig_atomic_t类型
例:对全局数据的访问只有一行代码,是不是原子操作呢?查看汇编
虽然C代码只有一行,但是在32位机上对一个64位的long long变量赋值需要两条指令完成,因此不是原子操作。同样地,读取这个变量到寄存器需要两个32位寄存器才放得下,也需要两条指令,不是原子操作。若main和sighandler都对这个变量a赋值,最后变量a的值可能会发生错乱。
如果在程序中需要使用一个变量,要保证对它的读写都是原子操作,C标准定义了一个类型sig_atomic_t,在不同平台的C语言库中取不同的类型,例如在32位机 上定义sig_atomic_t为int类型。
(2)sig_atomic_t类型 -> volatile限定符
在main函数中首先要注册某个信号的处理函 数sighandler,然后在一个while死循环中等待信号发生,如果有信号递达则执行sighandler, 在sighandler中将a改为1,这样再次回到main函数时就可以退出while循环,执行后续处理。用上面的方法编译和反汇编这个程序,在main函数的指令中有:
将全局变量a从内存读到eax寄存器,对eax和eax做AND运算,若结果为0则跳回循环开头,再次从内存读变量a的值,可见这三条指令等价于C代码的while(!a);循环。但编译器会优化,如下:
优化之后省去了每次循环读内存的操作。第一条指令将全局变量a的内存单元直接和0比较,如果相等,则第二条指令成了一个死循环,注意,这是一个真正的死循环:即使sighandler将a改为1,只要没有影响Zero标志位,回到main函数后仍然死在第二条指令上,因为不会再次从内存读取变量a的值。
编译器无法识别程序中存在多个执行流程。之所以程序中存在多个执行流程,是因为调用了特定平台上的特定库函数,比如sigaction、pthread_create,这些不是C语言本 身的规范,不归编译器管,程序员应该自己处理这些问题。C语言提供了volatile限定符,如果将 上述变量定义为volatile sig_atomic_t a=0;那么即使指定了优化选项,编译器也不会优化掉对变 量a内存单元的读写。
(3)必须使用volatile限定符
a.程序中存在多个执行流程访问同一全局变量
b.变量的内存单元中的数据不需要写操作就可以自己发生变化,每次读上来的值都可能不一样
c.即使多次向变量的内存单元中写数据,只写不读,也并不是在做无用功,而是有特殊意义的什么样的内存单元会具有这样的特性呢?肯定不是普通的内存,而是映射到内存地址空间的硬件寄存器,例如串口的接收寄存器属于上述第一种情况,而发送寄存器属于上述第二种情况。sig_atomic_t类型的变量应该总是加上volatile限定符,因为要使用sig_atomic_t类型的理由也正 是要加volatile限定符的理由。
2.竞态条件
(1)竞态条件(Race Condition): 如果我们写程序时考虑不周密,就可能由于时序问题而导致错误。
(2)sigsuspend:包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下,应调用sigsuspend而不是pause。
a.sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
b.调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除 对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
3.代码实现(用sigsuspend重新实现my_sleep函数)1 #include
2 #include
3 #include
4 void sig_alrm(int signum)
5 {}
6 unsigned int my_sleep(unsigned int nsecs)
7 {
8 struct sigaction new,old;
9 sigset_t newmask,oldmask,suspmask;
10 unsigned int unslept;
11
12 new.sa_handler=sig_alrm;
13 sigemptyset(&new.sa_mask);
14 new.sa_flags=0;
15 sigaction(SIGALRM,&new,&old);
16
17 sigemptyset(&newmask);
18 sigaddset(&newmask,SIGALRM);
19 sigprocmask(SIG_BLOCK,&newmask,&oldmask);
20
21 alarm(nsecs);
22
23 suspmask=oldmask;
24 sigsuspend(&suspmask);
25
26 unslept=alarm(0);
27 sigaction(SIGALRM,&old,NULL);
28
29 sigprocmask(SIG_SETMASK,&oldmask,NULL);
30 return unslept;
31 }
32 int main()
33 {
34 my_sleep(3);
35 printf("3 seconds passed!\n");
36 return 0;
37 }
输出结果:
(3)SIGCHLD信号
子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程 终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。
3.可重入函数(Reentrant)
(1)重入:函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。
(2)定义:函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入函数。
(3)如果一个函数符合以下条件之一则是不可重入的:调用了malloc或free,因为malloc也是用全局链表来管理堆的。
调用了标准I/O库函数。标准I/O库的很多实现都以不可重入的方式使用全局数据结构。
SUS规定有些系统函数必须以线程安全的方式实现
竞态条件的赋值_信号-sunshine225-51CTO博客相关推荐
- 竞态条件的赋值_《Java并发编程实战》读书笔记一:基础知识
一.线程安全性 一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个 ...
- java 多线程 临界区_【Java并发性和多线程】竞态条件与临界区
本文为转载学习 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源.如,同一内存区(变量,数组,或对象).系统(数据库,web services等)或文件.实际上,这些问题只有 ...
- linux操作系统之竞态条件(时序竞态)
(1)时序竞态:前后两次运行同一个程序,出现的结果不同. (2)pause函数:使用该函数会造成进程主动挂起,并等待信号唤醒,调用该系统调用的进程会处于阻塞状态(主动放弃CPU) 函数原型:int p ...
- [Linux]继续探究mysleep函数(竞态条件)
之前我们探究过mysleep的简单用法,我们实现的代码是这样的: #include<stdio.h> #include<signal.h>void myhandler(int ...
- Linux系统编程----8(竞态条件,时序竞态,pause函数,如何解决时序竞态)
竞态条件(时序竞态): pause 函数 调用该函数可以造成进程主动挂起,等待信号唤醒.调用该系统调用的进程将处于阻塞状态(主动放弃 cpu) 直 到有信号递达将其唤醒,等不到一直等 int paus ...
- 计算机系统学习之(1):基础知识概要——进程、中断、线程、竞态条件、关键区域、死锁、进程调度
文章目录 进程的创建 哪些事件导致进程的创建 fork 和 exec 命令创建和控制进程 fork() 命令 execve() 命令 进程的状态 中断 中断的种类 线程 线程共享内容 线程独有内容 进 ...
- Unix/Linux编程:竞态条件与sigsuspend函数
利用pause和alarm函数实现sleep #include <unistd.h>int pause(void); pause函数使调用进程挂起直到有信号到达.如果信号的处理动作是终止进 ...
- 竞态条件与sigsuspend函数
前言: 实现自己的mysleep函数 中,存在一个问题: 当alarm(sec)执行完,CPU非常忙,还没有执行pause函数,就去执行其他代码段了.等到alarm时间到,调用了信号处理函数后,此时C ...
- 线程学习5——竞态条件
竞态条件 概述:如果两个或两个以上的线程同时访问相同的对象,或者访问不同步的共享状态.就会出现竞态条件. 举例:如果多个线程同时访问类StateThread中的方法,最后结果会如何呢? 定义一个类St ...
最新文章
- c语言逐步搜索法求有根区间,[C语言第五章.ppt
- Python游戏开发,pygame模块,Python实现过迷宫小游戏
- Lintcode 408 解题思路及c++代码
- 2015 提高组 跳石头--二分答案
- CodeForces - 1551E Fixed Points(dp)
- Loading 遮蔽层 简单实现。
- LeetCode371——Sum of Two Integers(不用+)
- python怎么制作图像_python数字图像处理(5):图像的绘制
- sql优化学习(一)
- python 自动化框架_学会Python+Selenium,分分钟搭建Web自动化框架!
- leetcode 61. Rotate List
- 实战干货:基于Redis6.0 部署迷你版本消息队列
- 下c语言实现wc_用 Python 实现词云可视化
- wifiManager的简单调试
- java基于ssm的个人博客系统_一个基于 Spring Boot 的开源免费博客系统
- 大文件上传插件webupload插件
- 数据库中了勒索病毒,怎么办?
- 电脑长时间睡眠会自动关机吗_电脑睡眠久了是不是自动关机
- 毕业论文html代码查重吗,毕业论文中的代码内容重复了怎么办? 毕业论文代码重复率高...
- 数字 IC 技能拓展(18)如何快速上手 FPGA 开发板呢