学无止境……
分类: LINUX
2014-07-04 14:21:06
概念:
信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断(在软件层次上对中断机制的一种模拟)。从它的命名可以看出,它的实质和使用很象中断。
原理上,一个进程收到一个信号与处理器收到一个中断基本上是一样的。“信号"也是异步的,一个进程不必通过轮询查找等待信号的来到,也不无法预知信号何时会来。
信号的类型:
信号被定义在>中,都以"SIG"为前缀。 此外在系统终端用命令 ” $kill -l “ 也可以查看系统中定义的 signal 。
”信号“定义的种类比较多,但是可以按照一定规律分类。根据网友的分类如下。
(1) 与进程终止相关的信号。当进程退出,或者子进程终止时,发出这类信号。
(2) 与进程例外事件相关的信号。如进程越界,或企图写一个只读的内存区域(如程序正文区),或执行一个特权指令及其他各种硬件错误。
(3) 与在系统调用期间遇到不可恢复条件相关的信号。如执行系统调用exec时,原有资源已经释放,而目前系统资源又已经耗尽。
(4) 与执行系统调用时遇到非预测错误条件相关的信号。如执行一个并不存在的系统调用。
(5) 在用户态下的进程发出的信号。如进程调用系统调用kill向其他进程发送信号。
(6) 与终端交互相关的信号。如用户关闭一个终端,或按下break键等情况。
(7) 跟踪进程执行的信号。
此外根据信号的可靠性又可以分成:可靠信号、不可靠信号;根据时间关系分为:实时信号,非实时信号。
"不可靠信号":那些建立在早期机制上的信号,信号值小于SIGRTMIN(可用 kill -l 命令查看,SIGRTMIN=34,SIGRTMAX=64)的信号都是不可靠信号。
“实时信号”:非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
信号的产生:
信号的产生主要有两个来源:硬件来源,软件来源。
硬件来源: 终端中用户输入 “Ctrl + C” 将对当前程序产生 “SIGKILL”信号 、硬件异常(除数为0,无效的存储访问)等
软件来源:程序调用 kill 等产生信号的函数或用户使用kill命令产生信号、某种软件条件达到产生信号(SIGPIPE:管道读进程终止 ,SIGALRM:进程设定的闹钟时间到达)
“Ctrl + C”---->SIGINT “Ctrl + /” ---->SIGQUIT
产生信号相关函数介绍:
kill() & raise()
raise()函数的实质是调用了kill()
- #includ<signal.h> // 定义函数的头文件
- int kill(pid_t pid, int signo); //将信号发送给一个进程或进程组
- int raise(int signo); // 把信号发送给进程自身
因此重点介绍kill()函数。kill()的 signo参数为欲发送的信号值,pid 参数取值如下表:
- #include<signal.h> // kill() 头文件
- #include<unistd.h> // getpid()头文件
- kill(getpid(), signo); // getpid() 取得目前进程的进程识别码
pid值 信号对象 pid > 0 进程ID为pid的进程 pid < 0 ( pid != -1) 进程组ID为 |pid| 的所有进程 pid = -1 将信号广播给除自身外的所有进程 pid = 0 同一个进程组的进程
alarm() & pause()
alarm():顾名思义闹钟函数,其功能也表现无遗。用来设置信号SIGALRM,在经过参数 seconds 指定的秒数后传送给目前的进程(每个进程只能有一个闹钟,调用alarm()后之前设置的闹钟将失效)。如果参数 seconds 为 0,则之前设置的闹钟会被取消,并将剩下的时间返回。
- #include<unistd.h> // 函数定义头文件
- unsigned int alarm(unsigned int seconds);
- int pause(void);
pause():令当前的进程暂停(进入睡眠状态),直到被信号(signal)所中断
abort()
此函数向进程自身发送SIGABORT信号,默认操作会使进程异常退出。
- #include<stdlib.h>
- void abort(void);
sigqueue()
在队列中向指定进程发送一个信号和数据,是比较新的发送信号系统调用,发送信号时支持带参数,一般与sigaction()配合使用。注:相比kill()函数,sigqueue()功能更强大,但是sigqueue()只能向一个进程发送信号,而不能发送信号给进程组
- #include<signal.h>
- int sigqueue(pid_t pid,int sig,const union sigval value);
- *************************************************************
- parm:
- pid:目标进程的进程号
- sig:信号代号
- value:是一个联合体,表示信号附带的数据(传递给sigaction(),它也有此类型参数,),附带数据可以是一个整数也可以是一个指针,定义的原型如下:
- union sigval {
- int sival_int;
- void *sival_ptr;//指向要传递的信号参数
- }sigval_t;
信号的处理
程序收到信号可以根据信号做出相应的响应,一般处理方式有如下三种。
1> 捕捉信号,类似中断机制,对需要处理的信号,进程执行指定函数。
2> 忽略信号,忽略接收到的信号,不做任何处理。充耳不闻,就像没有发生过一样。(但是SIGKILL和SIGSTOP不能忽略)
3> 默认操作,每个信号系统都定义了默认的操作。
此外进程需要处理某个信号就必须在进程中注册该信号,让信号值和进程针对该信号的操作对应起来。注册信号有两个函数signal()和sigaction(),如下:
- 1 #include<signal.h>
- 2
- 3 void (*signal(int signum, void (*handler)(int))) (int);
- 4 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
signal()
原函数定义的有点夸张,其实也可以这样定义,也方便理解
- typedef void sign(int);
- sign *sinal(int, handler *)
参数signum指定信号值;
参数handler指定该信号对应的处理函数地址(设为SIG_IGN忽略,SIG_DEF默认操作)。signal()调用成功返回最后一次注册信号signum而调用的handler值,失败返回SIG_ERR。
signal()函数使用比较简单,一般只用于非实时性信号的注册(SIGRTMIN之前的信号)。
点击(此处)折叠或打开
- /* signal()函数使用示例 */
- #include<signal.h>
- #include<stdio.h>
- #include<unistd.h>
- void sig_int(int sig) // sig 参数为 signal()注册信号时传递的 信号值参数
- {
- printf("Get signal: %d \n",sig);
- signal(SIGINT, sig_int); //重新注册信号,否则该程序收到此信号将执行默认操作
- }
- void main()
- {
- signal(SIGINT, sig_int); //注册 SIGNIT 信号,以及对应的操作函数
- while(1)
- {
- printf("LuoYe !\n");
- sleep(1);
- }
- }
运行结果如下:
(重难点)
? sigaction() ?
用于改变进程接收到特定信号后的行为,函数原型:
- #include <signal.h>
- int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
- /*******************************************/
- parm:
- signum:信号值,可以为除SIGKILL及SIGSTOP外的任何一个特定有效的信号(因为这两个信号定义自己的处理函数,将导致信号安装错误)
- act:指定了对特定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等,当设为NULL时将以默认方式处理信号
- oldact:用来保存原来对相应信号的处理,也可设为NULL
- 注:如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。
参数 act 是函数中最重要的参数,它被定义为一个sigaction的结构。那么里面究竟包含些什么信息呢?
1)void (*sa_handler)(int)和void (*sa_sigaction)(int, siginfo_t *, void *)都是用于指定信号关联函数,到底采用哪个要看sa_flags中的设置。
- struct sigaction {
- void (*sa_handler)(int);
- void (*sa_sigaction)(int, siginfo_t *, void *); //siginfo_t 中带有信号相关的参数
- sigset_t sa_mask;
- int sa_flags;
- void (*sa_restorer)(void); // 已过时,POSIX不支持它,不应再被使用
- }
当使用sa_handler时指定的处理函数只有一个参数(信号值),跟注册信号就跟调用signal()类似。
使用sa_sigaction指定处理函数时指定的信号处理函数带有3个参数:
第一个参数为信号值,
第三个参数没有使用,
第二个参数是指向 siginfo_t 结构的指针,结构中包含信号携带的数据值
sigaction()通过参数siginfo_t.si_valu获得sigqueue(pid_t pid, int sig, const union sigval val) 传递过来的val参数。其实siginfo_t.si_int直接与sigval.sival_int关联siginfo_t.si_ptr直接与sigval.sival_ptr关联,通过这两个参数也同样可以获得sigqueue()传递的第三个参数。
- typedef struct siginfo_t{
- int si_signo;//信号编号
- int si_errno;//如果为非零值则错误代码与之关联
- int si_code;//说明进程如何接收信号以及从何处收到
- pid_t si_pid;//适用于SIGCHLD,代表被终止进程的PID
- pid_t si_uid;//适用于SIGCHLD,代表被终止进程所拥有进程的UID
- int si_status;//适用于SIGCHLD,代表被终止进程的状态
- clock_t si_utime;//适用于SIGCHLD,代表被终止进程所消耗的用户时间
- clock_t si_stime;//适用于SIGCHLD,代表被终止进程所消耗系统的时间
- sigval_t si_value;
- int si_int;
- void * si_ptr;
- void* si_addr;
- int si_band;
- int si_fd;
- };
- 其中第10行中sigval_t为联合数据类型,其原型为:
- typedef union sigval {
- int sival_int;
- void *sival_ptr;
- }sigval_t
2)sa_mask:用来设置信号屏蔽字,该屏蔽字由一个信号集定义。
3)sa_flags:指定了对信号处理的选项。下表为常用字段:
选项 定义 SA_NODEFER 不把信号添加到信号屏蔽字中 SA_SIGIFO 指定信号处理函数需要三个参数,所以指定处理信号操作时应选择sa_sigaction而不是sa_handler SA_RESETHAND 调用信号处理函数后,把信号处理函数重置为默认(SIG_DFL).此时效果相当于signal(),它默认会重置。 SA_RESTART 由此信号中断的系统调用会自动启动 SA_NOMASK 就是SA_NODEFER的别名,它不支持POSIX,所以为了软件的可移植性一般不使用此标志
点击(此处)折叠或打开
- /** sigaction() 函数简单例程 ***/
- #include<signal.h>
- #include<stdio.h>
- #include<unistd.h>
- void sig_int(int sig) // 信号的响应函数
- {
- printf("Get signal: %d\n",sig);
- }
- void main()
- {
- /** 初始化 sigaction 结构 **/
- struct sigaction act;
- act.sa_handler = sig_int;
- sigemptyset(&act.sa_mask); //将参数set信号集初始化并清空
- act.sa_flags = 0;
- sigaction(SIGINT, &act, 0); // 注册信号。只需注册一次,区别与使用signal()函数时每次都需要重新注册
- while(1)
- {
- printf("LuoYe !\n");
- sleep(1);
- }
- }
执行的结果跟 signal()例程一样。
还有一些与信号机制相关的要点(信号集)及函数,在另外的blog在介绍。http://blog.chinaunix.net/uid-29145190-id-4339938.html
本人在阅读 ser2net 源码时发现自己对“信号机制”的理解有所欠缺。所以重新捡起“信号机制”,本文就是在学习过程中的一些记录,顺便分享下。本人菜鸟一个,主要参考了网上大神们的技术帖,稍稍加入了一些个人的理解。可能会有些错误,仅作参考。