转自:http://blog.csdn.net/scenlyf/article/details/52068522

版权声明:本文为博主原创文章,未经博主允许不得转载。

首先说一下信号相关的内容。

********信号

我们在运行程序时,经常会碰到报错,这是由于操作系统触发异常,于是系统抛出某一信号产生的。例如,一个死循环的程序,处理方式如下:

1)用户输入命令,在Shell下启动这个前台进程;

2)用户按下Ctrl-C,这个键盘输入产生一个硬件中断,该进程的用户空间代码暂停执行,CPU从用户态切换到内核态处理硬件中断;

3)终端驱动程序将Ctrl-C解释成一个SIGINT信号,记在该进程PCB中(也可以说发送了一 个SIGINT信号给该进程);

4)当某个时刻要从内核返回到该进程的用户空间代码继续执行之前,首先处理PCB中记录的信号,发现有一个SIGINT信号待处理,而这个信号的默认处理动作是终止进程,所以直接终止进程而不再返回它的用户空间代码执行。

一个命令后面加个&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。

每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h中找到。

编号前31为普通信号,编号34以上的是实时信号。这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:man 7 signal

********信号的产生方式

1、kill命令。例如:kill -SIGSEGV 4568 或kill -11 4568(kill命令是调用kill函数实现的)
2、键盘输入以下指令产生信号:
     ctrl+c(产生SIGINT信号);
     ctrl+z(产生SIGTSTP信号:前台进程停止);
     ctrl+/(产生SIGQUIT信号:终止进程并且 Core Dump(事后调试))。
3、软硬件异常。硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
      例如:alarm函数:调用该函数可以设定一个闹钟,内核在seconds秒之后(闹钟超时)产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
       #include<unistd.h>  unsigned int alarm(unsignal int seconds); //seconds为0时,取消设定的闹钟,返回以前设定的闹钟时间还余下的秒数
            
4、kill,raise、abort等函数:
      kill:一个进程调用kill(2)函数可以发送信号给另一个进程;如果不明确指定信号则发送SIGTERM信号,该信号的默认处理动作是终止进程。

#include<signal.h>  int kill(pid_t pid, int sig); //成功返回0,错误返回-1。

raise:该函数可以给当前进程发送指定的信号(自己给自己发信号)。

#include<signal.h>  int raise(int sigo); //成功返回0,错误返回-1。

abort: abort函数使当前进程接收到SIGABRT信号而异常终止。

#include<stdlib.h>  void abort(void); //像exit函数一样,该函数总会成功的,所以没有返回值。

********阻塞信号

      信号的处理动作,有三种方式:
       1)忽略此信号;
       2)默认/缺省(大多数默认处理动作是终止信号);
       3)用户自定义(信号捕捉---提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数)

实际信号的处理动作称为信号递达(Delivery);信号从产生到递达之间的状态称为信号未决(Pending);进程可以选择阻塞(Block )某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。信号的产生、未决和是否阻塞无关。

注意:阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

Linux中:常规信号在递达之前产生多次只计一次,而实时信号在递达之前生多次可以依次放在一个队列里。这里不讨论实时信号。从上图来看,每个信号只有一 个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。因此,未决和阻塞标志可以用相同的据类型sigset_t来存储,sigset_t称为信号集, 阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。

信号集不能进行位操作,必须使用相关函数;信号集是0,1序列。信号集操作函数如下:

      #include<signal.h> 
      int sigemptyset(sigset_t *set);    // 初始化set所指的信号集,其中所有信号的对应bit清空,全部设为无效
      int sigfillset(sigset_t *set);         //初始化set,全部设为有效
      int sigaddset(sigset_t *set, int signo);  //信号集中添加某种有效信号
      int sigdelset(sigset_t *set, int signo);   //信号集中删除某种有效信号
      //这四个函数都是成功返回0,出错返回-1
      int sigismember(const sigset_t *set, int signo);    
      //判断一个信号集的有效信号中是否包含某种信号。包含则返回1,不包含则返回0,出错返回-1。
进行读取或更改进程的信号屏蔽字(阻塞信号集):调用函数sigprocmask。
      #include<signal.h> 
      int sigprocmask(int how, const sigset_t *set, sigset_t *oset);   //修改block表,返回值:若成功则为0,若出错则为-1
      how:修改方式;oset:备份原来的信号屏蔽字;set: 更改进程的信号屏蔽字。
      如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

进行读取进程的未决信号集:调用函数sigpending。

      #include<signal.h> 
      int sigpending(sigset_t *set); //成功返回0,出错返回-1
如下例子,进行2号信号的阻塞处理。
运行结果如下:
********捕捉信号

进程在收到信号不是立即去处理,保存在自己的PCD中,在合适的时候处理。什么时候处理呢?当进程由于陷阱、异常等,陷入内核态,从内核态到用户态时,进行信号检查和处理。进程的4种情况:如果block,立即返回;如果pending,先递达,处理后返回;stop信号等,直接终止;忽略,直接返回。

如下图所示:

函数sigaction:读取和修改与指定信号相关联的处理动作

#include<signal.h>

int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);  //成功返回0,失败返回-1

signo是指定信号的编号。若act指针非空,则根据act修改该信号的处理动作。若oact指针非空,则通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体:

-----sa_handler:赋值为常数SIG_IGN传给sigaction表示忽略信号;赋值为常数SIG_DFL表示执行系统默认动作,赋值为一个函数指针表示用自定义函数捕捉信号。 向内核注册了一个信号处理函数,该函数返回值为void,可以带一个int参数,通过参数可以得知当前信号的编号,这样就可以用同一个函数处理多种信号。显然,这也是一个回调函数,不是被main函数调用,而是被系统所调用。

-----sa_mask:进程的信号屏蔽字。如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。

-----sa_flags:sa_flags字段包含一些选项,本文代码把sa_flags设为0。

-----sa_sigaction是实时信号的处理函数。

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。

pause函数:使调用进程挂起直到有信号递达

#include <unistd.h>
         int pause(void);  // pause只有出错的返回值(exec也是),错误码EINTR表示“被信号中断”。

pause函数使调用进程挂起直到有信号递达

-----如果信号的处理动作是终止进程,则进程终止,pause函数没有机会返回;

-----如果信号的处理动作是忽略,则进程继续处于挂起状态,pause不返回;

-----如果信号的处理动作是捕捉,则调用了信号处理函数之后pause返回-1,errno设置为EINTR。
下面用alarm和pause实现sleep(3)函数,my_sleep。

1、main函数调用my_sleep函数,后者调用sigaction注册了SIGALRM信号的处理函数handler。

2、调用alarm(times)设定闹钟。

3、调用pause等待,内核切换到别的进程运行。

4、times秒之后,闹钟超时,内核发SIGALRM给这个进程。

5、从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数是handler。

6、切换到用户态执行handler函数,进入handler函数时SIGALRM信号被自动屏蔽, 从handler函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流程。

7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理动作。

函数被不同的控制流程调用,有可能在第一次调用还没返回时就再次进入该函数,这称为重入。例如链表的插入,insert函数访问一个全局链表,有可能因为重入而造成错乱,像这样的函数称为不可重入函数,反之,如果一个函数只访问自己的局部变量或参数,则称为可重入Reentrant 函数。

运行结果如下:

运行后,发现可以成功实现3秒打印一句语句,但上述程序存在问题。

1.、注册SIGALRM信号的处理函数。

2、调用alarm(times)设定闹钟。

3、内核调度优先级更高的进程取代当前进程执行,并且优先级更高的进程有很多个,每个都要执行很长时间

4、times秒钟之后闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态。

5、优先级更高的进程执行完了,内核要调度回这个进程执行。SIGALRM信号递达,执行处理函数sig_alrm之后再次进入内核

6、返回这个进程的主控制流程,alarm(times)返回,调用pause()挂起等待。

7、可是SIGALRM信号已经处理完了,还等待什么呢?

出现这个问题的根本原因是系统运行的时序(Timing)并不像我们写程序时所设想的那样。虽然alarm(times)紧接着的下一行就是pause(),但是无法保证pause()一定会在调用alarm(times)之后的times秒之内被调用。由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。

sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调用sigsuspend而不是pause。

#include <signal.h>
      int sigsuspend(const sigset_t *sigmask);
      和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。调用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。

改后的my_sleep如下:

说明:
如果在调用my_sleep函数时SIGALRM信号没有屏蔽:
1、调用sigprocmask(SIG_BLOCK,&newmask, &oldmask)时,屏蔽SIGALRM。
2、调用sigsuspend(&suspmask)时,解除对SIGALRM的屏蔽,然后挂起等待。
3、SIGALRM递达后suspend返回,自动恢复原来的屏蔽字,也就是再次屏蔽SIGALRM。
4、调用sigprocmask(SIG_SETMASK, &oldmask, NULL)时,再次解除对SIGALRM的屏蔽。

【作者】张昺华
【出处】http://www.cnblogs.com/sky-heaven/
【博客园】 http://www.cnblogs.com/sky-heaven/
【新浪博客】 http://blog.sina.com.cn/u/2049150530
【知乎】 http://www.zhihu.com/people/zhang-bing-hua
【我的作品---旋转倒立摆】 http://v.youku.com/v_show/id_XODM5NDAzNjQw.html?spm=a2hzp.8253869.0.0&from=y1.7-2
【我的作品---自平衡自动循迹车】 http://v.youku.com/v_show/id_XODM5MzYyNTIw.html?spm=a2hzp.8253869.0.0&from=y1.7-2
【新浪微博】 张昺华--sky
【twitter】 @sky2030_
【facebook】 张昺华 zhangbinghua
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.

【Linux】自主实现my_sleep【转】相关推荐

  1. linux源码acl,Linux自主访问控制机制模块详细分析之posix_acl.c核心代码注释与acl.c文件介绍...

    原标题:Linux自主访问控制机制模块详细分析之posix_acl.c核心代码注释与acl.c文件介绍 2.4.4.6 核心代码注释 1 posix_acl_permission() int(stru ...

  2. linux 模拟实现my_sleep的两个版本

    1.既然要讲sleep,那么大家就有必要了解一下什么是信号. 信号:是UNIX进程间通信的一种标准方式. 信号的产生:1.键盘快捷键 2.异常 3.操作系统命令和函数 4.闹钟 信号的数量:64种,1 ...

  3. linux 对象管理器,Linux多安全策略和动态安全策略框架模块详细分析之函数实现机制中文件对象管理器分析(3)...

    3.决策的实施 当主体对客体进行访问时,客体管理器会收集主体和客体的SID,并根据此SID对在AVC中进行查找:如果找到,则根据相应的安全决策进行处理:反之客体管理器会将主体的SID.客体的SID以及 ...

  4. selinux-setroubleshoot安装及详解

      查看文章     selinux 2011-05-18 11:50 对超级管理员有效 安全加强型linux 自主访问体制 :又大变小 user         group           ot ...

  5. 计算机安全技术(第2版)

    计算机安全技术(第2版) 主编:张同光 清华大学出版社 ISBN:9787302429654 出版日期:2016.03.01 印刷日期:2016.03.16 http://www.tup.com.cn ...

  6. Android智能手机安全解决方案

    1. 终端安全解决方案全景 1.1. Android系统框架预览 此外,鉴于许多硬件厂商不希望公开其设备驱动程序,GOOGLE在运行时库层对下屏蔽实现细节,对上提供统一接口增加了硬件抽象层. 1.2. ...

  7. 移动互联网白皮书(2014)

    移动互联网白皮书 (2014年) 工业和信息化部电信研究院 2014年5月 版权声明 本白皮书版权属于工业和信息化部电信研究院,并受法律 保护.转载.摘编或利用其它方式使用本白皮书文字或者观点 的,应 ...

  8. 当下常见的十大(现在补充了,是十一大手机操作系统)手机(平板)操作系统...

    写在前面:此文是我原创.我个人是手机发烧友,算是MOTO的fans.我喜欢捣鼓各种各样的手机系统.手机系统已经从原先的三足鼎立(塞班.WM.Linux)变成现在的百家争鸣.我特意花精力整理这篇手机系统 ...

  9. 华中科技大学网安学院信息系统安全实验 系统安全 APPARMOR

    2.1 APPARMOR实验 2.1.1 实验目的 AppArmor是linux系统中提供的一种强制访问控制方法,与SELinux类似,AppArmor 通过提供强制访问控制(MAC) 来补充传统的L ...

  10. CoreOS 和 Kubernetes 1.5 自主运行 Kubernetes、Container Linux

    CoreOS 的成立初衷是网络安全保护.显然,这是一个比较大的目标,但是毫无疑问对于隐私策略.商务和政府来说是十分必要的.而且这个目标也是可以完成的. 为了达到这个目标,我们在 Tectonic 大会 ...

最新文章

  1. 网络推广软文浅谈网站优化过程中图片的优化技巧和注意事项!
  2. mongodb消息服务器,win10 MongoDB 3.6 服务端配置
  3. [BZOJ1072][SCOI2007]排列perm
  4. nginx大量TIME_WAIT的解决办法--转
  5. Caffe2:python -m caffe2.python.operator_test.relu_op_test
  6. 【图像分类】如何使用 mmclassification 训练自己的分类模型
  7. maven使用出现的错误
  8. sqlserver字符串分割
  9. swing 绝对布局 窗口事件 鼠标事件 键盘事件 上传头像流程
  10. long类型保留两位小数_Java保留两位小数的几种写法总结
  11. 华为鸿蒙智慧屏和手机,【荣耀智慧屏评测】鸿蒙初体验:荣耀智慧屏跨系统交互构建新生态(全文)_荣耀 智慧屏_手机评测-中关村在线...
  12. Linux工控主板的多通道高速并行采集方案(基于libiio)
  13. geany怎么创建文件夹_安装 Geany
  14. 微信解除自定义表情150个上限?
  15. Portillo’s上市首日涨幅45.5%,资本爱上吃“热狗”?
  16. 写在冬日的第一天--一个女程序员第十六年工作总结
  17. 0 公式 0 基础学习电磁兼容 — 1. EMC 测试类型简介
  18. 数学建模理论自制笔记1:微分方程及其模型
  19. 三七互娱-游戏运维岗笔试题(无答案)
  20. JS函数curry(柯里化)

热门文章

  1. Atitit.获取swing ui 按钮控件的id 与名字 与JPDA 调试体系
  2. Atitit.播放系统规划新版本 and 最近版本回顾 v3  pbf.doc
  3. atitit.js 与c# java交互html5化的原理与总结.doc
  4. atitit。企业组织与软件工程的策略 战略 趋势 原则 attilax 大总结
  5. paip. C#.NET循环获取不同随机数的方法根据时间
  6. C#.NET com组件的编写
  7. 机构、基民双输,基金销售的利益困局如何破?
  8. Julia : csv =hdf5
  9. Julia:关于push 与 Array 和Dict 数据结构
  10. 新年礼物第二弹,热升华机械键盘免费抽