linux 信号_Linux信号机制

3673c2ea393721817eeba68ec4ac784c.png

信号就是一条消息,通知进程系统中发生了什么事,每种信号都对应着某种系统事件。一般的底层硬件异常是由内核的异常处理程序处理的,它对用户进程来说是透明的。而信号机制,提供了一种方法通知用户进程发生了这些异常。

例如,一个进程试图除0,会引发内核向他发送SIGFPE信号;执行非法指令会引发SIGILL信号;非法内存访问引发SIGSEGV;当你从键盘上键入Ctrl + C会引发SIGINT;当某个子进程结束会引发内核向其父进程发送SIGCHLD信号,等等。具体请看下图:

4fd29637229f2ea62e15275d505ba9c3.png

1. 信号术语与原则

1.1信号发送

当内核检测到某种系统事件(除零错误或子进程终止等等)或一个进程调用了kill函数显式的要求内核发送一个信号给目的进程时,内核会通过更新目的进程上下文中的某个状态而达到向它发送一个信号的目的。发送信号的方式为:

  • 命令行:kill -signum PID命令,向进程号为PID的进程发送signum信号;
  • 键盘:通过键盘发送特定信号,Ctrl + C 向前台进程组中的每个进程发送SIGINT终止信号;Ctrl + Z 向前台进程组中的每个进程发送SIGTSTP暂停信号;
  • 函数alarm: 使内核在一段时间(secs秒)后,向自己发送SIGALRM信号;

```c #include

unsigned int alarm(unsigned int secs); //返回:待处理的闹钟在被发送前还剩余的秒数,若之前没有待处理的闹钟,则返回0 //若secs = 0,不会调度安排新的闹钟。 ```

函数kill:进程通过调用kill函数发送信号给其它进程(包括自己)。

```c #include #include

int kill(pid_t pid, int sig); //成功返回0,失败返回-1。 ```

  • pid > 0 :发送信号sig给进程pid;
  • pid = 0 :发送信号给自己所在进程组中的每个进程,包括自己。
  • pid < 0 :发送信号sig给进程-pid。

1.2 信号处理

当进程从系统调用返回或是完成了一次上下文切换而重新取得控制权之前,内核会检查该进程的待处理信号集(pengding&(~blocked)),如果为空则完成控制权的交接,如果不为空则会让进程响应该信号集合中信号值最小的那个信号。

目的进程收到信号后有“忽略信号”、“终止进程”和“捕获信号“这3种方式来响应。其中

  • SIGKILL(终止)和SIGSTOP(暂停)这2个信号不可被忽略,也不能像其它信号一样可以通过signal函数改变他们的默认处理函数;

```c #include typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler); ```

如果handler = SIG_IGN,那么就忽略类型为signum的信号;
如果handler = SIG_DFL,那么就恢复类型为signum的信号的默认行为;
否则,handler就是用户自定义的信号处理函数地址。
  • 进程可以有选择的忽略某些信号(通过将blocked位向量中相应的位置1),即该信号虽然被内核或进程发送了过来,但我可以选择视而不见。

```c #include

int sigprocmask(int HOW, const sigset_t set, sigset_t oldset);

int sigemptyset(sigset_t set); //初始化set集为空(set = 0); int sigfillset(sigset_t set); //将所有信号都添加进set集(set = 1); int sigaddset(sigset_t set, int signum); int sigdelset(sigset_t set, int signum); //以上5个函数成功返回0,错误返回-1 int sigismember(const sigset_t *set, int signum); //是成员返回1,不是返回0,错误返回-1 ```

关于sigprocmask函数中的"HOW"有以下几种可能的取值:
  • SIG_BLOCK:把set集中的信号加到进程的blocked中(blocked |= set);
  • SIG_UNBLOCK:从进程的blocked中删除set集中的信号(blocked &= ~set);
  • SIG_SETMASK:忽略set集中的信号(blocked = set);

oldset : 如果他是非空的,则将进程原先blocked的值保存在其中。

一下示例展示了临时忽略SIGINT信号的程序片段:

```c sigset_t mask, oldmask;

sigemptyset(&mask); sigaddset(&mask, SIGINT);

sigprocmask(SIG_BLOCK, &mask, &oldmask); . . //此处的所有语句将不会响应SIGINT信号 . sigprocmask(SIG_SETMASK, &oldmask, NULL); //之后的语句将会正常响应SIGINT信号 ```

  • 任何信号只能被记录阻塞一次;即如果进程正在执行某类型信号的处理函数,那么在此进程返回主程序前,不管又收到了多少个该类型的信号,它只会被记录一次(即等到该进程从上次处理函数返回后,它只会再响应一次该类型的信号)。因为内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护着被阻塞的信号集。每次,收到一个信号,就在blocked相应的位置1,响应一个信号,就在pengding中相应的清零。

2. 安全的信号处理函数

由于信号处理函数和主程序是并发运行的,他们享有相同的全局变量,他们的运行顺序是不可预测的,这就导致何时接收到信号的规则往往有违人们的直觉,或者说主程序和子程序间不一定会按照你预想的顺序去执行。所以为了防止竞争冒险,在编写信号处理函数时有几个保守的原则需要遵守:

  • 处理程序尽可能简单
  • 在处理程序中仅使用异步安全的函数,也就是说该函数是可重入的(只访问局部变量)且不能中断;下图列出了所有Linux保证安全的系统函数,可以发现许多常见的库函数(printf、sprintf、malloc、exit等)都不是安全函数,在编写信号处理函数时要尽量避免使用。

312f22952819291c7bca7a862094eec7.png
为了在信号处理程序中能够打印一些简单的消息,我们可以使用一些异步信号安全的系统函数来构建自己的特有包装函数。作为例子,下面的程序展示了利用异步信号安全的系统函数write编写自己的SIO(safe I/O)函数。c ssize_t sio_puts(char s[]) { int count = 0; char *str = s; if(!str) _exit(1); while(*str++) count++; return write(STDOUT, s, count); }
  • 保存和恢复error;为了避免处理程序中某些语句的出错导致error被设置,进而影响主程序中的判断,在信号处理程序的第一条语句保存原error,在它返回前恢复error。
  • 不管是主程序还是子程序,在访问全局变量时,都要阻塞所有的信号,以防相互干扰
  • 用volatile声明全局变量。 volatile要求编译器每次都是从内存中读取全局变量的值,而非从缓存中。
  • 使用sig_atomic_t声明标志。 此处的标志代表在主程序和子程序间传递信号的全局变量,因为sig_atomic_t要求编译器对它的操作是原子的,所以即使没有阻塞所有信号,它也不会被任何信号打断。
  • 使用sigaction函数重新包装signal函数,使得系统自动重启被中断的系统调用。 由于一些系统函数(例如read、write、accept等)需要执行较长时间,所以可能会被信号中断。而在许多较早以前版本的Unix系统中,被中断的系统调用并不会在信号处理返回后重启,而是直接返回错误并将error设置为EINTR。而sigaction函数可以设置信号处理时的语义。
以下代码用sigaction函数编写了signal函数的包装函数[Signal][1],并且具有如下语义:
  • 只有当前处理的该类型信号被阻塞;
  • 其它信号也不会排队等待;
  • 只要可能,被中断的系统调用会自动重启;
  • 一旦为某信号设置了信号处理程序,它会一直保持到Signal重新为该信号设置SIG_IGN或SIG_DFL的信号处理程序。

``` handler_t Signal(int signum, handler_t handler) { struct sigaction action,oldaction;

action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;if(sigaction(signum, &action, &oldaction) < 0)unix_error("Signal error");
return(oldaction.sa_handler)

} ```

3.信号的同步

当需要编写读写相同内存位置的并发进程,我们不得不考虑进程间的(既包括进程与进程之间,也包括主进程与子进程之间)竞争关系。这是一个很大的命题,在此限于文章主题,只讨论信号之间的竞争关系如何处理。主要分两个方面,一是隐式竞争,二是显式竞争。

3.1 避免隐式竞争

考虑一个类似shell的函数功能,父进程在一个全局作业列表中记录着它的当前子进程,每个作业一个条目。addjob和deletejob函数分别向这个作业列表中添加和删除作业。父进程每创建一个子进程就把它添加在作业列表中,每当在SIGCHLD信号处理程序中回收一个僵死的子进程时,就在job列表中删除这个子进程。

void handler(int sig)
{int olderrno = errno;       //保存进程的原error值sigset_t mask_all,prev_all;pid_t pid;sigfillset(&mask_all);      //将所有信号添加到信号集mask_all中while((pid = waitpid(-1, NULL, 0)) > 0){    //回收僵死子进程sigprocmask(SIG_BLOCK, &mask_all, prev_all);    //阻塞(屏蔽)所有信号deletejob(pid);         //从job列表中删除僵死的子进程条目sigprocmask(SIG_SETMASK, &prev_all, NULL);}if(errno != ECHILD)         //如果父进程的所有子进程都已经回收,则内核发送ECHILD错误Unix_error("waitpid error");errno = olderrno;           //恢复进程的原error值
}int main(int argc, char **argv)
{int pid;sigset_t mask_all,mask_one,prev_one;sigfillset(mask_all);sigemptyset(mask_one);sigaddset(&mask_one, SIGCHLD);  Signal(SIGCHLD, handler);       //使用安全的Signal函数设置处理函数initjobs();                     //初始化工作列表while(1){/*在产生子进程前屏蔽SIGCHLD,以防止主进程还没执行到addjob就已经收到了因子进程终止而发来的SIGCHLD信号,进而进入handler导致在jobs中找不到要删除的子进程条目*/sigprocmask(SIG_BLOCK, &mask_one, &prev_one);   //频闭SIGCHLD信号if((pid = fork()) == 0){sigprocmask(SIG_SETMASK, &prev_one, NULL);  //子进程解除频闭SIGCHLDexecve("/bin/date", argv, NULL);}sigprocmask(SIG_BLOCK, &mask_all, NULL);    //父进程屏蔽所有信号addjob(pid);    sigprocmask(SIG_SETMASK, &prev_one, NULL);  //父进程解除屏蔽}exit(0);
}

3.2 避免显式竞争

有时候主程序需要显式地等待某个信号处理运行。例如shell程序,它必须等待当前的前台进程结束,被SIGCHLD处理程序回收之后,才能继续创建另一个进程。主进程在等待的这段时间应该干些什么才最好呢?我们可以用一个无限循环语句,让主进程就在那执行。但这样也太浪费CPU的资源了;我们也可以用一个sleep或者nanosleep函数让主进程休眠,但到底休眠多长时间不好把握,间隔太小同样会造成多次循环,间隔太大,程序又会太慢。

合适的解决办法是,引入sigsuspend函数:

#include <signal.h>int sigsuspend(const sigset_t *mask);           //返回-1

它暂时挂起调用它的进程,利用参数mask替换当前的信号阻塞集,直到收到一个信号并进入处理程序(如果是终止信号,就直接返回),处理完之后返回主进程,并恢复原来的阻塞集。

下面例子展示了主进程在创建完子进程后,如何利用该函数显式的等待SIGCHLD的到来,以达到同步的效果。

#include <signal.h>volatile sig_atomic_t pid;void sigchld_handler(int signum)
{int olderror = errno;pid = waitpid(-1, NULL, 0);int errno = olderrno;    
}void sigint_handler(int signum)
{}int main(int argc, char **argv)
{sigset_t mask,prev;Signal(SIGCHLD, sigchld_handler);Signal(SIGINT, sigint_handler);sigemptyset(&mask);sigaddset(&mask, SIGCHLD);while(1){sigprocmask(SIG_BLOCK, &mask, &prev);   //屏蔽SIGCHLD信号if(fork() == 0) //子进程exit(0);pid = 0;while(!pid){    sigsuspend(&prev);  //挂起并等待SIGCHLD信号的到来,其处理函数会使得pid大于0}sigprocmask(SIG_SETMASK, &prev, NULL);printf("...");}exit(0);
}

[1]: 引用:Unix Network Programming: The Sockets Networking API,第三版,第一卷

************************************************

嵌入式 Linux C ARM - 专题 - 简书​www.jianshu.com
98d1adb13f37f8da0f808767fbeea014.png
CSDN-专业IT技术社区-登录​blog.csdn.netLeon_Geo - 简书​www.jianshu.com
b7a2bc151ffbe2ff893cba1ad92859cc.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/259091.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DOxygen for C++使用说明——添加数学公式

公式 Doxygen允许你把 公式显示在最终的输出中&#xff08;这个功能仅限于HTML和输出&#xff09;.为了可以在HTML documentation显示公式&#xff08;转化为图片&#xff09;&#xff0c;你必须安装以下软件&#xff1a; latex: 编译器, 被用来解析公式, 首先提取公式写到一…

VC2010下Qt5的中文乱码问题

要搞清楚这个问题&#xff0c;先要弄明白编码。但是编码问题实在太复杂&#xff0c;这里肯定讲不开。 我先找一个例子&#xff0c;比如&#xff1a;“中文” 的 Unicode 码点/UTF8编码/GBK 分别是多少。 先去这个网站&#xff0c;输入 “中文” 查询对应的 Unicode 码点/UTF8编…

Tomcat 的 DefaultServlet

问题描述&#xff1a; 群里有人测试 Spring MVC&#xff0c;没有配置任何Controller&#xff0c;只配置了一个view resolver&#xff0c;指定了前缀后缀。 然后&#xff0c;他问的是 当访问 localhost:8080/test 的时候&#xff0c;为什么会被重定向到 localhost:8080/test/ &a…

Python学习(七)面向对象 ——封装

Python 类的封装 承接上一节&#xff0c;学了Student类的定义及实例化&#xff0c;每个实例都拥有各自的name和score。现在若需要打印一个学生的成绩&#xff0c;可定义函数 print_score() 该函数为类外的函数&#xff0c;如下&#xff1a; 1 class Student(object):2 def …

spss练习数据_SPSS篇——如何在成千上百万个数据中标识重复个案

本文就带大家来学习一个小技巧&#xff0c;如何运用SPSS标识重复个案。我们都知道在Excel中&#xff0c;通常会用到“筛选”功能来选出指定条件相同的单元格。那么在SPSS中&#xff0c;如何在成千上百万个数据中筛选出重复的个案呢&#xff1f; 小编就是要告诉你&#xff0c;几…

DOxygen for C++使用说明——Markdown支持

自Doxygen 版本1.8.0&#xff0c;Markdown被引进。 接下来&#xff0c;我们将先简单介绍标准的Markdown语法&#xff0c;读者可以进入Markdown官网查询更详细的细节。然后讨论一下Doxygen支持的Markdown扩展&#xff0c;最后讨论一下Doxygen对Markdown标准的实现细节。 Stand…

方程式漏洞之复现window2008/win7 远程命令执行漏洞

前几天就想写的&#xff0c;因为一些缘故就没写。此次是在外网环境下进行的。大家在内网中也一个样。 方法&#xff1a; 使用Eternalblue模块&#xff0c;剑测是否有漏洞然后msf生成一个dll直接反弹shell. PS&#xff1a;win版本的不知道缘何生成出来的dll是0kb 我就在自己本地…

C++空类和string类

1. 空类 1.1 空类默认哪六个成员函数。 1 class Empty2 {3 public:4 Empty(); //缺省构造函数 Empty e;5 Empty( const Empty& ); //拷贝构造函数 Empty e2(e1);6 ~Empty(); //析构函数7 Empty& operator( const Empty& ); //赋值运算符…

客服会话 小程序 如何发起_小程序、公众号、App三者如何融合布局?这里有一份避坑指南...

对产品经理来说&#xff0c;小程序无疑是2020年最火爆的词之一了。我们能看到&#xff0c;就在今年疫情期间&#xff0c;小程序DAU达到4.5亿&#xff0c;而超市、生鲜果蔬、社区购物等都同比增长100个点左右&#xff0c;小程序的商业价值很明显地在快速释放。小程序如此火爆&am…

DOxygen for C++使用说明——注释代码二

这一次我在谷歌搜索中检索到了Doxygen在github的仓库&#xff0c;进去一看&#xff0c;令人大喜&#xff0c;github仓库里含有了一个Doxygen的官方配置文件Doxyfile,于是下载下来&#xff0c;发现Doxyfile已经配置了将仓库中的\src文件编译成Documentation,并且将结果放在了dox…

python super()(转载)

一、问题的发现与提出 在Python类的方法&#xff08;method&#xff09;中&#xff0c;要调用父类的某个方法&#xff0c;在Python 2.2以前&#xff0c;通常的写法如代码段1&#xff1a; 代码段1&#xff1a; class A:def __init__(self):print "enter A"print "…

Swagger+Spring mvc生成Restful接口文档

2019独角兽企业重金招聘Python工程师标准>>> Swagger 是一个规范和完整的框架&#xff0c;用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。总体目标是使客户端和文件系统作为服务器以同样的速度来更新。文件的方法&#xff0c;参数和模型紧密集成到服务器端…

JavaScript——变量与基本数据类型

前言 JavaScript中的变量为松散类型&#xff0c;所谓松散类型就是指当一个变量被申明出来就可以保存任意类型的值&#xff0c;就是不像SQL一样申明某个键值为int就只能保存整型数值&#xff0c;申明varchar只能保存字符串。一个变量所保存值的类型也可以改变&#xff0c;这在Ja…

vscode可以打开jupyternotebook吗_刚刚,官方宣布 VS Code 支持 Python 全开发了!

关注Python高校每天早上23:10准时推送北京时间 2019 年 9 月 21 日&#xff0c;PyCon China 2019 在上海举行。在下午的演讲中&#xff0c;来自微软开发工具事业部的资深研发工程师韩骏做了主题为《Python 与 Visual Studio Code 在人工智能应用中的最佳 Azure 实践》的演讲。在…

C++类的内联成员函数应放在哪

今天复习C Primer的时候&#xff0c;看到了关于C类的内联成员函数的放置&#xff0c;应该放在头文件中。那么这到底是为什么 呢&#xff1f;仅仅是一种代码规范问题还是必须这样做呢&#xff1f; 下面我就来讲讲我自己的理解吧。要彻底理解这个问题&#xff0c;首先就要了解下函…

python selenium自动化(三)Chrome Webdriver的兼容

当一个自动化测试被实现在一个浏览器之后&#xff0c;我们会希望我们的测试能够覆盖到尽量多的别的浏览器。通过跨平台的测试来保证我们的程序在多个浏览器下都能正常工作。 在安装了selenium之后&#xff0c;firefox webdriver和IE webdriver就已经是ready to use的了&#xf…

NDK 编译armebai-v7a的非4字节对齐crash Fatal signal 7 (SIGSEGV) 错误解决

一直都是编译armabi的。没有不论什么问题&#xff0c;这个架构是软件模拟浮点运算的。后来看到NDK文档上说armabi-v7a是针对有硬件处理浮点计算的arm cpu的。 于是就改动配置编译armebai-v7a的so文件。 结果是编译没问题。一执行就是crash掉&#xff0c;Fatal signal 7 (SIGSEG…

作业三

作业三 第一章问题&#xff1a;书上写的“Bug的多少可以直接衡量一个软件的开发效率、用户满意度、可靠性和可维护性”&#xff0c;那么一个比较完好的软件中一般大概会出现多少Bug? 第二章问题&#xff1a;现在开始训练写更多的程序能否更早地达到软件工程师的标准&#xff1…

springboot默认数据源如何设置连接数_Spring Boot系列之配置数据库连接池

在实际的应用开发中&#xff0c;与数据库交互通常使用数据库连接池来重用Connection对象&#xff0c;减少资源消耗。Spring Boot 的数据源是自动配置的。在 Spring Boot 2.2.1 版本中&#xff0c;有几种数据源配置可选&#xff0c;它们按照 HikariCP -> Tomcat -> DBCP2 …

使用Qt正则表达式提取全路径的文件名

问题描述&#xff1a; 给定三个全路径&#xff0c;例如 path1"C:/Users/asus/Desktop/nefertiti_4465.obj"; path2"C:/Users/asus/Desktop/nefertiti_4465_k1.txt"; path3"C:/Users/asus/Desktop/nefertiti_4465_k2.txt"; 我希望说明path2和pa…