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

我看可以通过“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. PropertiesTest

    import java.io.FileInputStream; import java.io.IOException; import java.util.Properties; public clas ...

  2. java位移操作

    http://www.cnblogs.com/kanego/archive/2011/03/21/1990617.html     java位移符号解释和举例

  3. node.js ----NPM使用介绍

    NPM 使用介绍 NPM是随同NodeJS一起安装的包管理工具,能解决NodeJS代码部署上的很多问题,常见的使用场景有以下几种: 允许用户从NPM服务器下载别人编写的第三方包到本地使用. 允许用户从 ...

  4. CSS - 修改input - placeholder 和 readonly 的样式

    placeholder ::-webkit-input-placeholder { /* WebKit browsers */ color: #999999; } :-moz-placeholder ...

  5. 怎么样自己动手写OS

    虽然我现在并不是从事内核方向,却本着探索计算机本质的想法学习的内核,自从写完这个内核以后真的发现对很多东西的理解都更深一层,所以专研内核,对我现在的工作是很有帮助的.我个人强烈建议师弟师妹们尽早地啃一 ...

  6. js将字符串转变成数字

    方法主要有三种 转换函数.强制类型转换.利用js变量弱类型转换. 1. 转换函数: js提供了parseInt()和parseFloat()两个转换函数.前者把值转换成整数,后者把值转换成浮点数.只有 ...

  7. Web网页开发常见经典问题

    1.网络请求参数共享 转发dispatcher和重定向redirect 对于参数共享的区别 Redirect和Dispatcher 区别

  8. nginx教程1:location 匹配规则

    worker_process # 表示工作进程的数量,一般设置为cpu的核数 worker_connections # 表示每个工作进程的最大连接数 server{} # 块定义了虚拟主机 liste ...

  9. Android App 启动页(Splash)黑/白闪屏现象产生原因与解决办法(转)

    转载: Android App 启动页(Splash)黑/白闪屏现象产生原因与解决办法   首先感谢博主分享,本文作为学习记录 惊鸿一瞥 微信的启动页,相信大家都不陌生. 不知道大家有没有发现一个现象 ...

  10. liunx 安装工具总结

    1  下载相关文件,比如hadoop 2  解压文件 tar -zxcf xxx.tar.gz 3  mv xxx 到指定目录:通常安装到/usr/local 或者自己建个目录 /usr/develo ...