Linux系统编程学习笔记(6)-信号
来源:互联网 发布:sybase数据库实例教程 编辑:程序博客网 时间:2024/06/05 17:42
什么是信号
信号是由单个词组成的消息。例如,当在终端按Ctrl-C时,内核会向此终端运行的进程发送中段信号,信号的来源有三个地方:
(1) 用户:用户可以输入Ctrl-C或者其他信号来更改进程运行情况。
(2) 内核:当程序执行出错时,内核给进程发送一个信号,如溢出等等。同时内核也利用信号通知进程特定事件的发生。
(3) 进程:一个进程可以通过系统调用kill给另一个进程发送信号。一个进程可以和另一个进程通过信号通信。
由进程的某个操作产生的信号被称为同步信号,由进程外的时间引起的信号被称为异步信号,如用户击键这样的操作。信号列表储存在/usr/include/signal.h文件中。
拿信号SIGINT(中段信号)举例,进程接收到此信号时不一定要消亡,通过调用系统函数signal,可以使用其他的方法处理信号
(1) 接收默认处理:默认处理通常为消亡。进程不一定要使用signal接收默认处理,但是进程能够通过以下调用来恢复默认处理:
signal(SIGINT, SIG_DFL);
(2) 忽略信号:程序可以通过以下调用来告诉内核,它需要忽略SIGINT信号
signal(SIGINT, SIG_IGN);
(3) 调用一个函数:调用此系统函数后,程序能告诉内核,当信号到来时并不是忽略,也不执行默认的操作,而是执行函数(类似回调函数),程序调用如下:
signal(signum, functionname);
系统函数signal的调用规则如下:
#include <signal.h>/***********************param signum 需响应的信号param action 如何响应return -1 遇到错误 prevaction 成功返回 ***********************/result = signal(int signum, void(*action)(int));
其中,action可以是函数名或以下的两个特殊值之一:
- SIG_IGN 忽略信号
- SIG_DFL 将信号恢复为默认处理
信号与时钟编程
目前最常用的延迟函数为sleep,sleep(n)函数将当前程序挂起n秒或者在此期间被一个不能被忽略的信号的到达所唤醒。
sleep函数的调用有三个步骤组成:
- 为信号SIGALARM设置一个处理函数
- 调用alarm(num_seconds)
- 调用pause
值得注意的是,并非仅有信号SIGALARM可以唤醒进程,任何信号都可以,alarm函数的处理过程如下:
#include <unistd.h>/*******************************param seconds 等待的时间(秒)return -1 出错 old 计时器剩余时间*******************************/unsigned old = alarm(unsigned seconds)
如何使用间隔计时器
程序中使用间隔计时器比较麻烦,首先要选择初始间隔和重复间隔。在间隔计时器用的结构体中初始时间是it_value, 重复间隔是it_inteval。如果不想要重复这一特征,将it_inteval设置为0。要把两个时钟都关掉,设置it_value为0.
#include <stdio.h>#include <sys/time.h>#include <signal.h>void countdown(int signum){ static int num = 10; printf("%d..", num--); fflush(stdout); if(num < 0){ printf("DONE! \n"); exit(0); }}int set_ticker(int n_msecs){ struct itimerval new_timeset; long n_sec,n_usecs; n_sec = n_msecs/1000; n_usecs = (n_msecs%1000)*1000L; new_timeset.it_interval.tv_sec = n_sec; new_timeset.it_intercal.tv_usec = n_usec; new_timeset.it_value.tv_sec = n_sec; new_timeset.it_value.tv_usec = n_usec; return setitmer(ITIMER_REAL, &new_timeset, NULL);}int main(){ signal(SIGALARM, countdown); if(set_ticker(500) == -1) perror("set_ticker"); else while(1) return 0;}
代码理解起来很简单,设置定时器,到达固定时间后发出SIGALARM信号,函数countdown随之响应。间隔计时器的设置是通过struct itimerval来完成的。这个结构类型包括初始间隔和重复间隔,两者都储存在struct timecal中:
struct itimerval{ struct timeval it_value; //初始间隔 struct timeval it_interval; //持续间隔}struct timeval{ time_t tv_sec; //秒 suseconds tv_usec; //微秒}
设置定时器和取得定时器的系统函数调用:
#include <sys/time.h>/**********************************参数: which 获取或设置的计时器 val 向当前设置值的指针 newval 指向要被设置值的指针 oldval 指向被替换的设置值的指针返回值: -1 出错 0 成功**********************************/result = getitimer(int which, struct itimerval *val);result = setitimer(int which, const struct itimerval *newval, struct itimerval *oldval);
getitimer将某个特定计时器的当前设置读到val指向的结构中。setitimer将计时器设置为newval指向的结构的值。如果oldval不指向null,之前计时器的设定将被复制到oldval指向的结构中。
信号的处理形式
目前使用的信号处理方式有两种,主要是调用的系统函数不同,分为signal与sigaction。
使用signal处理信号
首先看下面的代码:
#include <stdio.h>#include <signal.h>#include <unistd.h>#define INPUTLEN 100int main(int ac, char *av[]){ void inthandler(int); void quithandler(int); char input[INPUTLEN]; int nchars; signal(SIGINT, inthandler); signal(SIGQUIT, quithandler); do{ printf("\nType a message\n"); nchars = read(0, input, (INPUTLEN-1)); if(nchars == -1) perror("read returned an error"); else{ input[nchars] = '\0'; printf("You typed: %s", input); } } while(strncmp(input, "quit", 4) != 0);}void quithandler(int s){ printf("Received signal %d.. waiting\n", s); sleep(3); printf("Leaving quithandler \n");}void inthandler(int s){ printf("Received signal %d.. waiting\n", s); sleep(2); signal(SIGQUIT, quithandler);}
在程序运行时,若在输入Ctrl-C之后立马输入Ctrl-|,会发现输出如下:
Type a message^CReceived signal 2.. waiting //信号2代表中段信号,Ctrl—C^\Received signal 3.. waiting //信号3代表退出信号,Ctrl-|Leaving quithandler Leaving inthandler
经过上述的实验可以看出,在处理信号INI时,信号QUIT到达,此时,信号INI的处理函数并没有继续执行,而是首先执行的quithandler,等待quithandler执行完成时,再继续执行inthandler.
为了取消这种信号接收的模式,可以修改inthandler函数,使其在处理INT信号时忽略QUIT信号,待INT信号处理完成时恢复的QUIT信号的处理:
void inthandler(int s){ printf("Received signal %d.. waiting\n", s); signal(SIGQUIT, SIG_IGN); //处理信号INT时忽略信号QUIT sleep(2); printf("Leaving inthandler \n"); signal(SIGQUIT, quithandler); //信号INT处理完成时恢复对信号QUIT的处理形式}
使用sigaction处理信号
sigaction系统函数相对于signal的功能更加全面,函数原型如下:
#include <signal.h>/******************************* ********param: signum 要处理的信号 prevaction 指针,指向描述被替换操作的结构 action 指针,指向描述操作的结构return -1 失败 0 成功***************************************/res = sigaction(int signum, const struct sigaction *action, struct sigaction *prevaction);
参数prevaction可以为null。结构体sigation定义了如何处理一个信号,结构体定义如下
struct sigaction{ void(*sa_handler)(); void(*sa_sigaction)(); sigset_t sa_mask; int sa_flags;}
新旧方法的差异在于,使用signal函数处理信号时,无法知晓信号的发出源头,而新方法则可以知悉被调用的原因以及上下 文产生的信息,使用时两者之间的差异如下:
struct sigaction action; action.sa_handler = handler_old;struct sigaction acton; action.sa_handler = handler_old;
同时只需设置sa_flags的SA_SIGINFO位即可使用新的信号处理方式,sa_flags的的部分控制位如下:
- SA_RESTHNAND 当处理函数被调用时重置,此位被设置将导致信号只会被处理一次
- SA_NODEFER 处理信号时关闭信号自动阻塞,允许递归调用信号处理函数
- SA_RESRART
- SA_SIGINFO 如果此位没设置,那么使用sa_handler指向的处理函数的值。如果设置此位,那么传递给处理函数的将不只是信号编号,还将包括指向描述信号产生的原因和条件的结构体。
#include <stdio.h>#include <signal.h>#include <unistd.h>#define INPUTLEN 100void inthandler(int s){ printf("Called with signal %d\n", s); sleep(s); printf("handle done signal %d\n", s);}int main(int argc, char *argv[]){ struct sigaction newhandler; sigset_t blocked; char x[INPUTLEN]; newhandler.sa_handler = inthandler; newhandler.sa_flags = SA_RESTART; //处理阻塞的信号 sigemptyset(&blocked); sigaddset(&blocked, SIGQUIT); newhandler.sa_mask = blocked; if(sigaction(SIGINT, &newhandler, NULL) == -1) perror("sigaction"); else while(1 stdin); printf("input: %s", x ); }}
注意,上述处理阻塞信号的方式,设置sa_mask来设置阻塞的信号类型。当程序执行时连续按下Crtl+C与Ctrl+|程序将执行完INT信号的处理函数之后退出。下面将详细描述如何设置信号为阻塞信号:
使用sigprocmask函数可以设置或取消被阻塞的信号集:
#include <signal.h>/******************************************param how 如何修改阻塞信号集 sigs 指向使用的信号集的指针 prev 指向之前的阻塞信号集的指针return -1 失败 0 成功*******************************************/int res = sigprocmask(int how, const sigset_t *sigs, sigset_t *prev);
how的值可以为SIG_BLOCK, SIG_UNBLOCK, SIG_SET。表示将sigs指定的信号添加,删除或者替换。
信号集可以用sigsetops函数来进行设置:
sigempty(sigset_t *setp) //清除列表中的信号sigfillset(sigset_t *setp) //添加所有的信号到setp中sigaddset(sigset *setp, int signum) //添加信号signum到setp信号列表sigdelset(sigset *setp, int signum) //删除信号
- Linux系统编程学习笔记(6)-信号
- linux 系统编程-学习笔记8--信号/线程
- Linux系统学习笔记:信号
- Linux系统学习笔记:信号
- Linux系统信号学习笔记
- linux应用编程学习(6)信号
- linux系统编程--信号
- Linux系统编程-信号
- linux系统编程之信号(一):信号基本概述
- linux系统编程之信号(一):中断与信号
- linux系统编程之信号(一):中断与信号
- Linux系统编程之信号(一):信号基本概述
- linux系统编程之信号(一):中断与信号
- linux系统编程之信号(一):中断与信号
- Linux系统编程--信号及信号处理(一)
- Linux系统编程--信号及信号处理(二)
- linux系统编程之信号(一):信号基本概述
- linux系统编程之信号(一):中断与信号
- 正则表达式的运用
- POI
- 删除购物车数据
- 如何给boostrap模态框传值
- 解决anaconda pip,parse() got an unexpected keyword argument 'transport_encoding'错误
- Linux系统编程学习笔记(6)-信号
- openlayer4中加载瓦片图层
- 自己编码模拟实现ArrayList底层代码
- 44. Wildcard Matching
- PMBOK笔记 第1章 引论 (1)
- 机器学习(1)--神经网络初探
- 遍历(一)jquery $().each和$.each()
- Centos运行Mysql因为内存不足进程被杀
- 无限极(树状)分类处理