一.基础知识信号产生的条件

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博客相关推荐

  1. 竞态条件的赋值_《Java并发编程实战》读书笔记一:基础知识

    一.线程安全性 一个对象是否是需要是线性安全的,取决于它是否需要被多个线程访问 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要额外的同步,这个 ...

  2. java 多线程 临界区_【Java并发性和多线程】竞态条件与临界区

    本文为转载学习 在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源.如,同一内存区(变量,数组,或对象).系统(数据库,web services等)或文件.实际上,这些问题只有 ...

  3. linux操作系统之竞态条件(时序竞态)

    (1)时序竞态:前后两次运行同一个程序,出现的结果不同. (2)pause函数:使用该函数会造成进程主动挂起,并等待信号唤醒,调用该系统调用的进程会处于阻塞状态(主动放弃CPU) 函数原型:int p ...

  4. [Linux]继续探究mysleep函数(竞态条件)

    之前我们探究过mysleep的简单用法,我们实现的代码是这样的: #include<stdio.h> #include<signal.h>void myhandler(int ...

  5. Linux系统编程----8(竞态条件,时序竞态,pause函数,如何解决时序竞态)

    竞态条件(时序竞态): pause 函数 调用该函数可以造成进程主动挂起,等待信号唤醒.调用该系统调用的进程将处于阻塞状态(主动放弃 cpu) 直 到有信号递达将其唤醒,等不到一直等 int paus ...

  6. 计算机系统学习之(1):基础知识概要——进程、中断、线程、竞态条件、关键区域、死锁、进程调度

    文章目录 进程的创建 哪些事件导致进程的创建 fork 和 exec 命令创建和控制进程 fork() 命令 execve() 命令 进程的状态 中断 中断的种类 线程 线程共享内容 线程独有内容 进 ...

  7. Unix/Linux编程:竞态条件与sigsuspend函数

    利用pause和alarm函数实现sleep #include <unistd.h>int pause(void); pause函数使调用进程挂起直到有信号到达.如果信号的处理动作是终止进 ...

  8. 竞态条件与sigsuspend函数

    前言: 实现自己的mysleep函数 中,存在一个问题: 当alarm(sec)执行完,CPU非常忙,还没有执行pause函数,就去执行其他代码段了.等到alarm时间到,调用了信号处理函数后,此时C ...

  9. 线程学习5——竞态条件

    竞态条件 概述:如果两个或两个以上的线程同时访问相同的对象,或者访问不同步的共享状态.就会出现竞态条件. 举例:如果多个线程同时访问类StateThread中的方法,最后结果会如何呢? 定义一个类St ...

最新文章

  1. c语言逐步搜索法求有根区间,[C语言第五章.ppt
  2. Python游戏开发,pygame模块,Python实现过迷宫小游戏
  3. Lintcode 408 解题思路及c++代码
  4. 2015 提高组 跳石头--二分答案
  5. CodeForces - 1551E Fixed Points(dp)
  6. Loading 遮蔽层 简单实现。
  7. LeetCode371——Sum of Two Integers(不用+)
  8. python怎么制作图像_python数字图像处理(5):图像的绘制
  9. sql优化学习(一)
  10. python 自动化框架_学会Python+Selenium,分分钟搭建Web自动化框架!
  11. leetcode 61. Rotate List
  12. 实战干货:基于Redis6.0 部署迷你版本消息队列
  13. 下c语言实现wc_用 Python 实现词云可视化
  14. wifiManager的简单调试
  15. java基于ssm的个人博客系统_一个基于 Spring Boot 的开源免费博客系统
  16. 大文件上传插件webupload插件
  17. 数据库中了勒索病毒,怎么办?
  18. 电脑长时间睡眠会自动关机吗_电脑睡眠久了是不是自动关机
  19. 毕业论文html代码查重吗,毕业论文中的代码内容重复了怎么办? 毕业论文代码重复率高...
  20. 数字 IC 技能拓展(18)如何快速上手 FPGA 开发板呢

热门文章

  1. mysql left join 三表查询_MySql的join(连接)查询 (三表 left join 写法)
  2. PTA L1-057 PTA使我精神焕发 C++实现
  3. CobaltStrike 部署
  4. python入门到放弃恶搞图-学Python方法用错,直接从入门到放弃!
  5. Android 设置壁纸流程
  6. virtualBox新建虚拟电脑
  7. 教你怎么不办会员也可以下载17素材网的源文件
  8. 音频之WAV格式编码解析
  9. JVM内存区域和垃圾收集器
  10. 深入理解 Android 9.0 Crash 机制(二)