信号(或软中断)是在软件层次上对中断的一个模拟,其运行在“用户空间”,一个进程对另外一个或几个进程通过发送信号来实现异步通信。当接收进程接收到信号后,其可以注册一下处理函数来说对这些信号进行处理(也可以选择忽略该信号或者采用系统默认的处理方式)。 

我看可以通过“kill -l”命令来查看系统支持的信号,比如SIGKILL它表示需要终止一个进程,它有一个系统特定的信号值9。这些值都定义在signal.h中 

在signal.h中有个叫做_NSIG(一般为64)的宏其表示该系统支持的最多信号数,而SIGRTMAX (_NSIG-)则表示信号的最大值,而与之相对应的SIGRTMIN却不是表示信号的最小值,其表示可靠信号的最小值。按照信号的可靠性,可将信号分为“可靠信号(实时信号)”和“不可靠信号(非实时信号)”,以SIGRTMIN为界限,值小于SIGRTMIN的信号为不可靠信号,其继承于早期的UNIX系统,SIGRTMIN到SIGRTMAX之间的为可靠信号。 

不可靠信号有两点需要注意:一是其在执行完自定义信号处理函数后会将信号处理方式重置为系统默认方式(如果要多次处理同一个信号,则要多次按照信号处理函数,不过好像后期的Linux对这点做了改进而无需重新安装)。二是不可靠信号不支持排队,其有可能会出现信号丢失的情况。 

假设进程A向进程B发送信号,那么一个很简单的信号流程是:进程A调用某个函数产生某个信号,信号被发送到进程B,然后进程B对该信号进行处理。 

信号的产生和发送: 

我们用结构 

typedef struct siginfo {
//… some info of a signal
} signifo_t;
来表示一个信号的相关信息(比如信号值,由谁发送的等等)
用结构
struct sigqueue {
struct sigqueue *next;
siginfo_t info;
};
来表示有n个siginfo_t结构构成的队列。
再假设有这样一个数据结构:
typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t; 其中_NSIG_WORDS表示的值一般为2。我们知道long为32位,那么sig[_NSIG_WORDS]则为64位,其可以表示64个位的集合,很容易地就可以将集合中的元素其映射到_NSIG个信号上:如果该位为1则表示有着对应值的信号在该集合中。(实际上,因为没有0号信号,所以信号值和位值刚好错开一位) OK,我们如何用这些数据结构来表示哪些信号被发送到了某个进程呢,很简单地,如果进程描述符(PCB)中有sigset_t类型的字段的话,将该字段的对应位置1就可以了,而这些信号的详细信息如果能被保存在sigqueue类型的字段中的话就更完美了。实际上PCB就是这么做的,只不过其将sigset_t和sigqueue合并成了一个称为sigpending的结构: struct sigpending {
struct sigqueue *head, **tail;
sigset_t signal;
}; 所以PCB的sigpending字段表示被发送到了该进程但还没有来得及处理的信号(被挂起的)。 从这里我们可以看出,所谓的“信号的产生”实际上就是某个进程请求内核去将另外一个进程的sigpending字段设置成相应的值罢了,并将相应的其他信息插入到sigqueue队列中。 设置sigpending时有一个比较有意思的地方:我们知道,可以用sigset_t中的某一个位表示对应的信号是否被发送到了该进程,其只能简单地表示“有()”或“无()”,如果某个信号被发送了多次的话,则无法在sigset_t中体现,但可以通过sigqueue队列来体现,实际上,对应不可靠信号(值小于SIGRTMIN的),当设置sigpending时,如果内核发现signal_t相应位已经被置1的话,则内核会丢弃该信号,这也就是为什么说该信号是“不可靠的”;而对应可靠信号,即便signal_t相应位已经被置1,此次信号的相关数据(siginfo_t)仍然会被包装成一个队列节点而被插入到队列中去。 另外从编程角度,要发送一个信号,一般调用下面这些函数: int kill(pid_t pid, int sig);
向进程或进程组发送某个信号
int raise(int sig);
向调用进程自身发信号
int sigqueue(pid_t pid, int signo, const union sigval value);
与kill类似,但多了一些附加信息
unsigned alarm(unsigned seconds);
指定的时间后向调用进程发送一个SIGALRM信号(闹钟信号)
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
与alarm类似同样发送SIGALRM信号,但支持更详细的设定,更多参考这里
void abort(void);
向进程发送SIGABRT信号,让进程中止。 信号的接收和处理 进程从内核态返回用户态时,内核会去检查是否有信号被发送到了该进程,如果有,则让该进程该处理该信号。进程对于一个信号可以有三种处理方式: ,显示地忽略该信号: 其中SIGKILL和SIGSTOP是不能被忽略的,其必须采用下面的第2种方式。 ,采用系统默认的处理方式进行处理: 默认处理可以有这么几种方式:abort; abort并dump; ignore; stop(暂停进程,置为 TASK_STOPPED状态); continue(继续执行,与stop向对应,置为TASK_RUNNING状态) ,调用进程注册的信号处理函数进行自定义处理。 当处理完毕后,内核会改变sigpending中的相关值以表示处理完毕了(比如将sigset_t相应位置0,从sigqueue中删除相关元素等) 至于如何注册信号处理函数,Linux有下面两种方式: typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 调用signal()函数,其表示从现在开始我关心那些信号值为signum的信号,如果该信号发送到笨进程的话,请调用handler去处理它。其主要用于非可靠信号。 比如下面这个DEMO,去自定义处理SIGINT (按ctrl-c终止进程时会发送该信号) #include <stdio.h>
#include <signal.h>
#include <unistd.h> #define SIG2CATCH 2 void handler(int signum)
{
if(signum == SIG2CATCH)
{
printf("you wanna kill me with ctrl-c ?! no way\n");
}
} int main()
{
printf("app start ...\n"); signal(SIG2CATCH, handler); while()
{
sleep();
} printf("app end ...\n"); return ;
} 运行程序,并试图用ctrl-c结束程序后,SIGINT(值为2)信号会发送到该进程,默认情况下其会终止程序,但这里采用了自定义处理函数,其仅仅打印出一段文字(可以用ctrl-z结束) 另一个注册方法是: int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 其主要用于可靠信号,并且可以在sigaction中包含附件信息。详细的参考这里。

linux 进程学习笔记-进程信号sigal的更多相关文章

  1. linux 进程学习笔记-进程跟踪

    进程跟踪 long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data); Linux用ptrace来进行进 ...

  2. linux 进程学习笔记-进程ID,PID

    PID,进程号 , 范围在2~(??为什么需要这么多),而一个名为idle (或swapper)的进程占据的编号0,init进程占据了编号1. 进程0和进程1 : 系统启动时会从无到有地创建进程0,它 ...

  3. linux c学习笔记----进程创建(fork,wait,waitpid)

    1.pid_t fork(); (1)当一个进程调用了fork 以后,系统会创建一个子进程.这个子进程和父进程不同的地方只有他的进程ID 和父进程ID,其他的都是一样.就象符进程克隆(clone)自己 ...

  4. linux 进程学习笔记-进程退出/终止进程

    <!--[if !supportLists]-->Ÿ <!--[endif]-->退出/终止进程 void _exit(int status) 与 void exit(int ...

  5. linux 进程学习笔记-进程pipe管道

    所谓“进程间通信(IPC,inter-process communication)”,按照其目的讲就是让进程之间能够“共享数据”,“传输数据”,“事件通知”等,我所知道的一共有“管道” “信号” “消 ...

  6. Linux内核学习笔记-2.进程管理

    原创文章,转载请注明:Linux内核学习笔记-2.进程管理) By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...

  7. Linux内核学习笔记二——进程

    Linux内核学习笔记二——进程   一 进程与线程 进程就是处于执行期的程序,包含了独立地址空间,多个执行线程等资源. 线程是进程中活动的对象,每个线程都拥有独立的程序计数器.进程栈和一组进程寄存器 ...

  8. 操作系统学习笔记----进程/线程模型----Coursera课程笔记

    操作系统学习笔记----进程/线程模型----Coursera课程笔记 进程/线程模型 0. 概述 0.1 进程模型 多道程序设计 进程的概念.进程控制块 进程状态及转换.进程队列 进程控制----进 ...

  9. JUC学习笔记——进程与线程

    JUC学习笔记--进程与线程 在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的进程与线程部分 我们会分为以下几部分进行介绍: 进程与线程 并发与并行 同步与异步 线程详解 进程与线程 ...

随机推荐

  1. Skia构建系统与编译脚本分析

    分析下Skia的构建系统,详细编译过程參看Windows下从源代码编译Skia.这里以ninja为例来分析.运行以下三条命令就能够完毕编译: SET "GYP_GENERATORS=ninj ...

  2. StringUtils方法

    org.apache.commons.lang.StringUtils中方法的操作对象是java.lang.String类型的对象,是JDK提供的String类型操作方法的补充,并且是null安全的( ...

  3. MySQL 优化、设计规则浅谈

    当数据量大,数据库相应慢时都会针对数据库进行优化.这时都是要针对具体情况,具体业务需求进行优化的. 但是有些步骤和规则应该适合各种情况的.这里综合网上找的资料简单分析一下. 第一优化你的sql和索引: ...

  4. Django--网页管理实例解析

    此篇为代码流程的注释以及自己写的小项目的思路: 首先是项目的路由配置: urlpatterns = [ # url(r'^admin/', admin.site.urls), url(r'^yemia ...

  5. Redis专题(2):Redis数据结构底层探秘

    前言 上篇文章Redis闲谈(1):构建知识图谱介绍了redis的基本概念.优缺点以及它的内存淘汰机制,相信大家对redis有了初步的认识.互联网的很多应用场景都有着Redis的身影,它能做的事情远远 ...

  6. jQuery Validate(一)

    jQuery Validate 插件为表单提供了强大的验证功能,让客户端表单验证变得更简单. 但是在学习的过程中,我也遇到了疑惑,网上的很多例子貌似都是依赖jquery.metadata.js这个库, ...

  7. JavaScript读书笔记(6)-Function

    Function类型 ECMAScript中函数是对象,每个函数都是Function类型的实例,也有属性和方法,函数是对象,函数名实际上市一个指向函数对象的指针,不会与某个函数绑定: function ...

  8. PHP中输出文件,怎么区别什么时候该用readfile() , fread(), file_get_contents(), fgets()

    我在服务器端(Apache环境)上放了一个安卓apk安装包的下载链接,使用readfile()读取apk文件输出下载后,手机安装apk显示解析包错误.但apk本身没问题,下载后文件的大小也是完整的.服 ...

  9. 概率图模型(PGM)学习笔记(二)贝叶斯网络-语义学与因子分解

    概率分布(Distributions) 如图1所看到的,这是最简单的联合分布案例,姑且称之为学生模型. 图1 当中包括3个变量.各自是:I(学生智力,有0和1两个状态).D(试卷难度,有0和1两个状态 ...

  10. TP 框架 如果去掉表前缀

    #jd_admin_abc 去掉前缀 C('DB_PREFIX')=获取前缀 结果为admin_abc $table_Name=str_replace(C('DB_PREFIX'), '', $tab ...