浅谈Linux中的信号处理机制(一)
有好些日子没有写博客了,自己想想还是不要荒废了时间,写点儿东西记录自己的成长还是百利无一害的。今天是9月17号,暑假在某家游戏公司实习了一段时间,做的事情是在Windows上用c++写一些游戏英雄技能的逻辑实现。虽然时间不算长,但是也算学了一点东西,对团队项目开发流程也有了一个直观的感受,项目里c++11新特性也有用到不少,特别是lambda表达式,STL的一些容器和算法也终于有了可以实践的地方。由于自己比较喜欢Linux C,也就没有做留下的打算,现在回到了学校,好好复习一段时间,准备一下校招吧。如果有朋友有工作机会的话,不妨可以推荐一下我O(∩_∩)O,我的EMAIL:baixiangcpp@gamil.com。貌似有点扯远了,好久不写东西了。
谈起Linux编程signal是非常重要的一块内容。关于signal,Linux 的 manual有很详细的介绍。具体:man 7 signal .我就不浪费篇幅贴出来了。
信号被认为是一种软件中断(区别于硬件中断)。信号机制提供了一种在单进程/线程 下处理异步事件的方法。具体过程是当进程运行到某处,接受到一个信号,保留“现场”,响应信号(注意这里的响应是一种宏观意义上的响应,对信号的忽略(SIG_IGN)也被以为是一种响应,后面会详细谈到信号响应的方式。),在返回到刚刚保存的地方继续运行。我制作了一张GIF或许可以清晰的体现这样的处理方式:
产生信号的条件有很多,某些组合键(CTRL+C、CTRL+\,CTRL+Z等),kill命令,kill系统调用以及由内核产生的某些信号(如内核检测到段错误、管道破裂等)。值得注意的是当我们发送信号时受到权限的限制,发送一个信号到另一个没有权限的进程是不合法的(关于权限的规则会在之后的博客总结)。信号的种类非常多,都以SIG+名字的形式命名的宏,通常都有实际意义和用法具体可查阅manual。有些常见的信号是需要熟记的如SIGINT,SIGCHLD,SIGIO等等。在编写程序的时候,我们最好用信号的宏的形式,这样可读性更好。那么如何“响应”信号呢?
对于大部分的信号,Linux系统都有默认的处理方式。而大部分默认的处理方式是终止程序并转储core文件。要处理信号,Linux系统处理信号的接口有两个sigaction(),signal(),较简单的是signal()函数,其形式如下:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
siganl()函数有两个参数其中有一个int的参数便是要处理的信号,诸如SIGINT的宏。另一个参数类型为sighandler_t的函数指针,handler指针对应的函数我们称之为:信号处理函数(signal-handler function)。可见signal()的第二个参数是一个信号处理函数,返回值也是一个信号处理函数,失败返回宏SIG_ERR(SIGKILL和SIGSTOP的默认行为分别是杀死和停止一个进程,任何试图改变这两个信号的处理方式的行为都将返回错误)。这样经典形式的函数在Linux上我们经常会经常碰到。signal()函数的作用就是建立一个signum信号的处理函数(establish a signal handler function)。通俗一点来说就是当signum信号到来时,进程会保存当前的进程栈,转去执行siganl()中指定的handler函数。之前提到过,信号的响应方式有多种,因此handler不仅可以是一个函数指针也可以是ISO C为我们定义的宏:SIG_IGN,SIG_DEL,和他们的名字一样SIG_IGN是忽略这个信号,SIG_DEL是保持这个信号的默认处理方式(默认处理方式也可以可以是SIG_IGN ,比较绕,但是合理)。前文提到的三个宏定义分别如下(/usr/include/bits/signum.h):
#define SIG_ERR ((__sighandler_t) -1)
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)
下面我写一个小的DEMO演示一下如何写一个信号处理函数:
#include <signal.h>
#include <stdio.h> void sigdemo(int sig)
{
printf("Receive a signal:%s\n",strsignal(sig));
} int main()
{
if(signal(SIGINT,sigdemo) == SIG_ERR)
{
perror("signal()");
return ;
}
printf("Main started.\n");
pause();//wait a signal.
}
可以看到,我在main函数中并没有主动调用sigdemo函数,可是运行程序后,当我们在中断按下CTRL+C时(发送SIGINT信号,宏对应的值是2),出现了这样的结果:
[baixiang ~$]a.out
Main started.
^CReceive a signal:2
可见sigdemo函数得到了执行,其参数sig便是接受到的信号的值。要将信号的值,转换为其意义string.h中提供了一个函数char* strsignal(int sig), 基本上看到该函数原型就知道这个函数怎么用了,在此我就不再浪费篇幅赘述了。
上文我们通过使用CTRL+C组合键发送信号SIGINT给当前的进程。但是这种方法只能发送少部分信号且并不适用所有的进程比如后台进程和守护进程。守护进程不必说,连终端都没有。交互shell (interactive shell)在启动一个后台进程的时候,会自动把中断和退出信号设置为忽略,关于这点我在网上看到一篇不错的博客:http://hongjiang.info/shell-script-background-process-ignore-sigint/ 。这样的情况下就无法使用快捷键的方式了。这里我介绍几种其他的发送信号的方式。
首先是shell命令kill其用法如下:
kill [-s signal|-p] [-q sigval] [-a] [--] pid...
-s signal signal可以是诸如SIGINT,SIGQUIT之类的宏,亦可以是1,2,3...这样的值,可以随意使用,你开心就好。
-q queue sigval是值,可以伴随信号传递,但是这里只可以是一个integer,在进程中可以使用sigaction()接收到这个值,与之对应的是另一个函数sigqueue()。这里先不详细介绍,下文会谈到。
pid就是目标进程的进程id,可以是一个或者多个。但是发送信号时,要确保你所使用的用户是具有发送信号到目标进程的权限的。
kill的选项远不止这些,但是通常这些已经够用了。如有兴趣请自行 “man 1 kill”查看。
和shell命令kill有一个同名的系统调用kill(),其原型是这样的:
int kill(pid_t pid, int sig);
相信看了上边的shell指令,这个函数的用法就一目了然了吧。pid是目标进程的pid,sig是要发送的信号。和其他函数一样它也是成功返回0,失败-1。然而真的这么简单吗?事实上不是。pid这个参数在这里大有学问。它的取值不仅仅可以是进程id,它甚至可以是负的。如果你对linux下编程熟悉的话,这样的用法肯定接触过,获取消息队列时使用的msgrcv()函数,其中的msgtype参数也具有类似的用法。当然扯远了。
pid>0 此时正式最普通的一种情况,pid是要目标进程的pid。
pid=0 那么kill()会将信号发送给调用进程同组的所有进程,也包括他自己。
pid=-1 那么信号将被发送至所有它具有权限发送信号的每一个进程(init进程和调用进程除外)。
pid<-1 信号会发送sig信号到组id等于该pid绝对值的进程组中的每一个进程。
如果pid在以上四种情况之外,无法匹配到目标进程,那么就会返回-1,errno被设置为ESRCH。当没有权限发送时kill()也将失败返回-1,errno会被设置为EPERM。关于linux上权限是如何作用的细节,我争取再后面的博客总结一下。
与kill()类似的还有一个函数killpg(),用法简单多了,也不浪费篇幅了,查看manual就能搞定。
最后一个发送信号的函数是raise(),它只接受一个参数signal,然后把该信号传递给调用进程:
int raise(int sig);//成功返回0,失败返回-1
由于这个函数不需要引用进程ID,它是被纳入C99标准的函数。
除了这几种产生信号的shell命令和函数之外还有一些情况下可以产生信号,比如alarm(),settimer()之类的一些与时间相关的函数,以及一些常见的软硬件错误都会产生信号。详细谈这些貌似就有点淡化主题了,扯远了。
信号的可靠与不可靠主要体现在两个方面:
- 对于不可靠信号,进程每次处理信号后,都会将信号的处理方式设置为默认动作。而对于可靠信号,它的处理函数执行以后,对该信号的处理方式不会发生变化。
- 信号可能会丢失。
由于Linux信号机制基本上从早期的UNIX系统上的信号机制移植过来的,所以Linux仍旧支持这些早期的不可靠信号。但是Linux也对不可靠信号做了(上面两点区别的第一小点)改进,即不可靠信号处理方式,不会在处理函数执行后变成默认方式。所以,在Linux上对于不可靠信号与可靠信号的区别就在于是否支持排队。
关于信号是否会丢失,我们看这样两段代码,首先是rcv.c:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> void fun(int sig)
{
printf("Recvive a signal:%s\n",strsignal(sig));
} int main()
{
if(signal(SIGINT,fun) == SIG_ERR)
perror("signal");
printf("%d\n",getpid());
while(1)
pause();
}
这段程序先安装SIGINT的信号处理函数fun,fun函数只是打印信号信息。之后打印出进程id同时死循环等待信号。
另一段程序是send.c:
#include <signal.h>
#include <stdlib.h>
#include <stdio.h> int main(int argc,char** argv)
{
pid_t pid = (pid_t)atol(argv[1]);
printf("%d\n",pid);
int i = 0;
for(;i<500;++i)
kill(pid,SIGINT);
}
send程序从终端接收一个参数即目标进程的PID,然后向其发送500次SIGINT信号。下面我们分别把rcv.c,send.c编译成rcv和send。首先运行rcv,打印出了进程pid 20273然后,我们在开一个终端运行send 20273,观察到这样的结果:
send明明发送了500个SIGINT信号,而rcv中只接受处理了13个SIGINT信号,这是怎么回事儿呢?明天再接着写下去,今天太晚了,战斗力不足有点犯困了。
浅谈Linux中的信号处理机制(一)的更多相关文章
- 浅谈Linux中的信号处理机制(二)
首先谢谢 @小尧弟 这位朋友对我昨天夜里写的一篇<浅谈Linux中的信号处理机制(一)>的指正,之前的题目我用的“浅析”一词,给人一种要剖析内核的感觉.本人自知功力不够,尚且不能对着Lin ...
- 浅谈Linux中的信号处理机制(三)
一晃眼,已经到9月底了,都来不及去感慨时间匆匆.最近常常会想明年的今天我将会在那里干着什么样的工作?对未来又是憧憬又是担忧,压力山大.无论如何现在还是踏踏实实的学习吧,能这样安安静静学习的日子也不多了 ...
- Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理
Java网络编程和NIO详解7:浅谈 Linux 中NIO Selector 的实现原理 转自:https://www.jianshu.com/p/2b71ea919d49 本系列文章首发于我的个人博 ...
- 浅谈linux中shell变量$#,$@,$0,$1,$2,$?的含义解释
浅谈linux中shell变量$#,$@,$0,$1,$2,$?的含义解释 下面小编就为大家带来一篇浅谈linux中shell变量$#,$@,$0,$1,$2的含义解释.小编觉得挺不错的,现在就分享给 ...
- 浅谈Linux中的各种锁及其基本原理
本文首发于:https://mp.weixin.qq.com/s/Ahb4QOnxvb2RpCJ3o7RNwg 微信公众号:后端技术指南针 0.概述 通过本文将了解到如下内容: Linux系统的并行性 ...
- 浅谈Linux的内存管理机制
转至:http://ixdba.blog.51cto.com/2895551/541355 一 物理内存和虚拟内存 我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此, ...
- 转:浅谈Linux的内存管理机制
一 物理内存和虚拟内存 我们知道,直接从物理内存读写数据要比从硬盘读写数据要快的多,因此,我们希望所有数据的读取和写入都在内存完成,而内存是有限的,这样就引出了物理内存与虚拟内存的概 ...
- 浅谈 Linux 内核无线子系统
浅谈 Linux 内核无线子系统 本文目录 1. 全局概览 2. 模块间接口 3. 数据路径与管理路径 4. 数据包是如何被发送? 5. 谈谈管理路径 6. 数据包又是如何被接收? 7. 总结一下 L ...
- 【VS开发】【DSP开发】浅谈Linux PCI设备驱动(二)
我们在 浅谈Linux PCI设备驱动(一)中(以下简称 浅谈(一) )介绍了PCI的配置寄存器组,而Linux PCI初始化就是使用了这些寄存器来进行的.后面我们会举个例子来说明Linux PCI设 ...
随机推荐
- 【WCF】WCF中的InstanceContext与ConcurrencyMode【转】
一.前言 最近忙于公司的在线升级项目,一个人要负责公司四大产品的在线升级,这四个产品是在Revit中以插件形式存在的,目前基于WCF来实现.等客户总量突破5万了,再重新用socket实现. 由于有服务 ...
- 什么是java 序列化,如何实现java 序列化?
我们有时候将一个 java 对象变成字节流的形式传出去或者从一个字节流中恢复成一个java 对象,例如,要将java 对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个ja ...
- easyui datagrid 动态操作editor 的方法
easyui本身是不提供这么细节的功能的,需要我们自己拓展下: 在easyui.min.js中扩展: $.extend($.fn.datagrid.methods, { addEditor : fun ...
- 【精粹系列】Mysql精粹
关于Mysql整理的需要记忆和熟练掌握的内容 1.查询数据表的信息(比如有多少行数据): show table status like 'tab_User' -- 数据表中的数量 2. 使用 ex ...
- Lind.DDD.Manage项目核心技术分享
回到目录 关于Lind.DDD.Manager的培训与学习 讲解:张占岭 花名:仓储大叔 主要框架:Lind.DDD,Lind.DDD.Manager 关于Lind.DDD.Manager 由于数据模 ...
- Bootstrap之栅格系统
bootstrap 移动优先 中文官网 http://www.bootcss.com/ 1.基本模板 <!DOCTYPE html> <html lang="en&quo ...
- [python]沪深龙虎榜数据进一步处理,计算日后5日的涨跌幅
沪深龙虎榜数据进一步处理,计算日后5日的涨跌幅 事前数据: 前面处理得到的csv文件 文件名前加入“[wait]”等待程序处理 python代码从雅虎股票历史数据api获取数据,计算后面5日的涨跌幅 ...
- Linux安全基础:grep命令的使用
grep (缩写来自Globally search a Regular Expression and Print)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来.Unix的 ...
- SharePoint 2013 工作流设计之Designer 使用“可视化视图”
SharePoint 2013增强了工作流功能,而Designer里面也添加了可视化设计视图,也就是类似Visio的设计视图(需要Visio 2013支持),下面我们简单介绍下,在可视化视图下,使用工 ...
- SharePoint 2013 User Profile Services之跨场发布
在之前博客中已经描述了User Profile的两种配置场景,这篇博客将详细介绍微软官方推荐的配置方法. 测试环境的架构可以参考之前的博客内容,这里就不做介绍了,直接切入主题. 1. 在sp-farm ...