Linux编程---信号处理

信号是一种异步的进程间通信的方式.但是这种通知方式能交换的信息很少.只能说给一个事件的标志.类似单片机中的中断,强迫进程停止做当前应当做的事情,而去执行中断执行程序.

信号的产生有如下几种:

1.用户按下了某个终止键,如ctrl-\或ctrl-c.是由终端程序向当前进程发送一个中断信号.

2.程序异常.比如除零错误.

3.kill函数向其发送了一个终止信号

4.进程向自己发送信号.如进程调用alarm函数.

5.企图读写终端的后台进程会得到作业的控制信号SIGTTIN或SIGTOU.

6.当进程超越了CPU或文件的大小限制时,内核会生成一个信号.

得到信号的进程也有三种方式应对.

1.忽略信号.大部分信号都可以忽略,但SIGSTOP和SIGKILL除外

2.捕获信号.这就要进程对信号进行专门的设置了.

3.执行系统默认动作.这个主要是系统默认对信号规定的默认动作.

---流产: 终止进程并且生成内存转储文件.即产生存储上下文环境,寄存器内容等信息的core文件.(abort函数)

---终止: 这个不产生core文件

---忽略: 忽略信号

---挂起: 暂停进程

---继续: 恢复进程.

对于进程正处在可中断的睡眠状态并且这个信号是非阻塞的,内核便唤醒此进程使它能够接受信号.

对于信号,进程可以立即处理,也可以选择让其悬挂.对于不可靠信号,一个进程对同一种信号只能悬挂一个.也就是信号可以存储,但是只能存一个.对于可靠信号,信号来几个就执行几次信号句柄,信号可以排队,所以可以用来计数.

对于小于SIGRTMIN的信号为不可靠信号,SIGRTMIN-SIGRTMAX为可靠信号.实际上实时对应可靠,非实时对应不可靠.

这里是我抄网上的,测试版本为ubuntu 12.04LTS.我之前也不太明白实时和非实时,可靠和不可靠信号的差别.如果理解有误还请指出.

这里介绍一个NSIG宏.这个宏记录了系统中允许的信号总数+1.

void psignal(int signo,const char *msg);

这个函数我第一次见...网上貌似也没资料.....

若msg是控制正,函数只打印signo对应的描述,并随后带上一个换行符.

若msg是非空指针,平函数用这个字符串作为其输出消息的前缀,并且在此前缀和signo对应的消息之间放置一个冒号和空格将他们分开.

简单来说就是查询信号用途的函数...

不同信号类型中的信号也蛮多的...具体的还是以后编程遇到后慢慢记吧.这里就不写了.

还是写一些函数的说明吧...

一.信号生成

int raise(int sig);

这个函数功能就是发送sig到调用进程...

如果sig信号设置了句柄,那么raise只有在句柄函数返回时才会返回.

int kill(pid_t pid,int sig);

这个函数用的比较多吧.虽说叫kill,但实际上是发送信号的函数.并不一定只能发送kill.

如果sig为0,那么kill只进行正常的错误检查而不实际发送信号,这常常用来检查pid的合法性.如果发送信号的进程可能不在的话,那么就可以这么用.

pid实际上参数用法还蛮多的.

>0  发送信号给pid进程

=0  发送给进程所在的进程组的所有进程.

=-1 广播信号,即发送给能发送的所有进程.

<-1 发送信号给进程组ID等于pid绝对值.并且发送者有权向它发送信号的所有进程.

这个pid的取值和之前的wait很像.

这里还提到了权限的问题.一般进程的实际用户ID或有效用户ID必须与接受信号进程的实际用户ID或有效用户ID相同.对于有效ID是超级用户的进程来说,可以向任何进程发送信号.

二.设置信号动作.

typedef void (*sighandler_t)(int );

sighandler_t signal(int signum,sighandler_t handler);

两个参数:signum设置型号类型.handler设置信号句柄.

handler其实可以取以下三种值之一:

SIG_DFL 默认动作

SIG_IGN 忽略该信号.但SIGKILL和SIGSTOP这两个信号,不能设置为忽略.尽量不要忽略系统相关的信号.

函数指针 就是执行这个函数体的函数.参数一般得到的是信号类型.

这个函数的返回值是前一次有效动作的指针.,因此它的原型与第二个参数相同.当signal调用成功时.返回值要么是SIG_DFL,SIGIGN或函数句柄.

如果调用出错,那么则会返回SIG_ERR并设置errno.唯一的错误吗是EINVAL

对于fork和exec来说,信号相关对于fork来说是继承的.

对exec来说则不继承.所有忽略的信号继续忽略,其他有句柄的则改为默认动作.

由于signal函数使用的时候存在一个时间窗口,信号很可能不可靠.所以有了下面这个信号函数.通常用它的比较多

int sigaction (int signum,const struct sigaction *act,struct sigaction *oact);

第二个参数是一个结构体,其中包含了信号句柄.第三个参数则是原来的信息.

struct sigaciton{

void  (*sa_handler)(int);

void  (*sa_sigaction)(int ,siginfo_t *,void *);

sigset_t sa_mask;

int    sa_flags;

}

sa_handler就是句柄.

sa_sigaction也是个句柄地址,当sa_flags设置了SA_SIGINFO的时候才起作用,否则用handler.

sa_mask指明信号句柄执行期间要阻塞的一组信号,在sigaction执行过程中会屏蔽这些信号.

sa_flags可取的值就多了,有以下这些:

SA_NOCLDSTOP

此标志只对SIGCHLD信号有效,正常情况下,子进程被停止时,总是向父进程发送信号,有时候被停止的子进程恢复运行时,可能向父进程发送信号.当signum参数传值是SIGCHLD时,在这两种情况下都不再发送信号.

SA_RESTART

如果设置该标志并且捕获信号,系统将在信号句柄返回时自动恢复被该信号中断了的系统调用.否则被中断的系统调用将返回-1并置errno为EINTR.多数情况下sa_flags的值为SA_RESTART.

SA_ONSTACK

如果设置此标志,系统将在用sigaltstack指定的替代信号栈上运行的信号句柄.否则使用用户栈来交付信号.

SA_NODEFER

如果设置此标志并且捕获信号,在信号句柄执行期间系统不自动阻塞该信号.这对应于不可靠信号signal的情形.(估计没什么人会用这个吧...)

SA_RESETHAND

如果设置此标志并且捕获信号,在信号句柄的入口,系统将重置信号动作为SIG_DFL并清楚SA_SIGINFO标志,并且sigaction的行为就和signal差不多了.

SA_NOCLDWAIT

只对SIGCHLD起作用.如果设置此标志,并且sig是SIGCHLD,调用进程的所有子进程在终止时不转变成僵死进程.这样父进程就不必调用wait函数了.如果调用了wait则进程会一直阻塞直到所有子进程都结束才返回,返回值为-1和设置errno为ECHILD.

SA_SIGINFO

这个就是上面的情况了.未设置用sa_handler并且禁止修改sa_sigaction.

句柄原型必须是void f(int signo);

如果设置了这个标志,则必须修改sa_sigaction设置信号句柄并禁止修改sa_handler.

句柄原型为void f(int signo,siginfo *info,void *context);

这个有些复杂,不过还是可以说清楚的.

实际上在siginfo_t中包含了更多的信号产生的信息.context也指向了一个进程上下文环境.Linux下默认类型为sigcontext结构体.

这个内容好多...我就不展开了,我看APUE上有相关信息,用到再查吧.

三.阻塞信号

阻塞信号意味着告诉操作系统保持该信号并推迟它的发送.被阻塞的信号不会丢失,他们只是暂时被悬挂,直到阻塞解除.

1.sigset_t类型的信号操作

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set,int signo);

int sigdelset(sigset_t *set,int signo);

int sigismember(const sigset_t *set,int signo);

主要是针对信号集合的操作.

第一个是清空集合,第二个则是填满所有类型信号.返回值总是0.

第三个则是增加一个信号到集合中.第四个是从集合中删除一个信号,成功返回0,

第五个则是检测一个信号是否为集合中的信号,在里面则返回1,否则返回0.

2.设置信号屏蔽

int sigprocmask(int how,const sigset_t *set,sigset_t *oset);

how的值有三种:

SIG_BLOCK      把set信号集中的信号加入到阻塞信号集中

SIG_UNBLOCK   解除set中的全部阻塞信号

SIG_SETMASK   使用set集合为屏蔽信号集合.

如果第二个参数为NULL,那么则什么都不做,how也没有用,只是得到原有屏蔽信号集合.

注意不能阻塞SIGKILL和SIGSTOP.如果set集合中有这两个信号,那么sigprocmask只是忽略他们但并不返回错误.

3.检查悬挂信号

int sigpending(sigset_t *set);

通过set返回已经被悬挂的信号.

四.等待信号

int pause();

这个就是阻塞进程直到有信号来.并且直到这个信号的句柄执行完成之后才返回.返回值为-1,并设置errno为EINTR.

pause在很多时候并不太可靠,所以最好不要用pause来等待新号到达,之后再开始做正真的工作.这样做不安全.即使通过设置标志来安排新号句柄合作也仍然不能可靠地使用pause.

如果在

static void sig_usr(int signo)

{   flag=1;

}

if(!flag)

pause();

这种情况下,在flag判断完到执行pause中间出现一个信号并且之后一直没有信号,那么就会造成进程永久阻塞.所以为这种情况封装了另外一个函数.

int siguspend(const sigset_t *sigmask);

这个函数把sigmask中的信号集合临时代替调用进程的信号屏蔽.然后挂起调用进程直到有不属于sigmask的信号到达为止.注意!是不属于sigmask集合的信号来才恢复阻塞!

五.使用分开的信号栈

对于大程序,调用层次比较深的可能需要用这个吧.为了避免爆栈,只好从堆中拿出一部分当作栈了.特别是对于SIGSEGV这种内存相关的信号了,可以通过中断来让程序更加强壮.

int sigaltstack(const stack_t *ss,stack_t *oss);

stack_t结构体包含

int ss_flags   栈状态标志

void *ss_sp  栈地址

size_t ss_size  栈大小

通过malloc一个空间.放入stack_t结构体中.

其中ss_flags这个有两种宏标志.SS_ONSTACK和SS_DISABLE.前者表示用新的地址.后者表示不用这个栈.感觉DISABLE好鸡肋...估计是用在调用完之后,用来查看状态的吧.

同时可以用宏参数SIGSTKSZ和MINSTKSZ来确定系统允许的信号栈空间大小.通常ss_size用SIGSTKSZ即可.

执行成功返回0,失败返回-1并设置errno为错误原因.

sigaltstack只是准备好一个栈,具体那个句柄使用这个栈还不知道.在调用sigaction的时候使用了宏SA_ONSTACK才可以来指定用哪个栈.

六.信号句柄编程技巧

1.句柄内非局部控制转移

主要是用过setjmp和longjmp或sigsetjmp和siglongjmp,来实现句柄中实现非局部控制转移.

int sigsetjmp(sigjmp_buf env,int savemask);

void siglongjmp(sigjmp_buf env,int val);

在UNIX标准中没有规定setjmp,longjmp是否保存屏蔽信息...

但在Linux下sigsetjmp,siglongjmp与setjmp,longjmp都保存屏蔽信息.

这个非局部控制转移估计多用于错误恢复吧.

2.可重入函数与异步信号安全函数

可重入函数需要满足三个条件:

--不修改自身代码

--只修改自己的局部数据

--不调用其他任何非可重入函数

这里为什么要提到可重入函数呢..因为很多时候信号会中断程序,并且信号句柄也可能调用一些函数造成破坏.

malloc就是不可重入的.如果在malloc执行的时候被信号中断,并且句柄中调用了malloc.那么很可能造成malloc所维护内存分配表就会写乱了.

所以在句柄中用不可重入函数要小心.我一开始觉得可以通过锁来解决,但是仔细一想,锁可能还不能完全的解决问题,还应有一个延时一段时间,重新发送信号的过程.

可重入函数可以在信号句柄内安全地调用,这种函数也称为异步信号安全函数.

任何函数,除非特殊声明,否则不是异步信号安全函数.

不可重入函数一般具有以下特点:

1) 使用了静态数据结构

2) 调用了malloc或free.

3) 是标准IO库中的函数.因为标准库中通常使用全局数据结构.

七.实时信号

Linux一共支持30个实时信号从34到63.和一般的信号相比,主要有如下差别:

1) 实时信号可以排队

2) 如果信号用sigqueue发送的,那么随信号可以附带一个额外的值.如果接收进程设置SA_SIGINFO标志为这个信号安装了句柄,它可以从信号句柄类型为siginfo_t的第二个参数成员si_value中得到这个值.

3) 当信号阻塞时,受阻的多个实时信号是按确定的顺序交付的.同类型信号是顺序交付,不同类型按信号值从小到大依次交付.

这里提到了一个函数sigqueue.

int sigqueue(pid_t pid,int signo,const union sigval value);

第三个参数就是上面说的额外的值.

联合体如下union sigval{

int sival_int;

void *sival_ptr;

}

也就是说这是个整数或是指针.

如果实时信号还用sigsuspend就不合适了.因为其延长了信号回应的时间.这里专门提供了两门等待实时信号的函数

int sigwaitinfo(const sigset_t *set,siginfo_t *info);

int sigtimedwait(const sigset_t *set,siginfo_t *info,const struct timesoec * timeout);

第一个参数是等待信号的集合,第二个参数是用来得到信号信息的参数.timedwait的最后一个参数则是用来限制等待时间的,如果为NULL,那么就和waitinfo一样了.

信号处理就写到这里了.基本该写的都写了.

欢迎指出错误~

Linux编程---信号处理,布布扣,bubuko.com

时间: 2024-10-05 15:07:03

Linux编程---信号处理的相关文章

Linux编程---线程

首先说一下线程的概念.其实就是运行在进程的上下文环境中的一个执行流.普通进程只有一条执行流,但是线程提供了多种执行的路径并行的局面. 同时,线程还分为核心级线程和用户级线程.主要区别在属于核内还是核外. 核心级线程,地位基本和进程相当,由内核调度.也就是说这种系统时间片是按线程来分配的.这种线程的好处就是可以适当的运用SMP,即针对多核CPU进行调度. 用户级线程,在用户态来调度.所以相对来说,切换的调度时间相对核心级线程来说要快不少.但是不能针对SMP进行调度. 对于现在的系统来说,纯粹的用户

linux编程下signal()函数

linux编程下signal()函数 当服务器close一个连接时,若client端接着发数据.根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了.根据信号的默认处理规则SIGPIPE信号的默认执行动作是 terminate(终止.退出), 所以client会退出. 若不想客户端退出可以把 SIGPIPE设为SIG_IGN 如: signal(SIGPIPE,SIG_IGN); 这时SI

【转】牛人整理分享的面试知识:操作系统、计算机网络、设计模式、Linux编程,数据结构总结

基础篇:操作系统.计算机网络.设计模式 一:操作系统 1. 进程的有哪几种状态,状态转换图,及导致转换的事件. 2. 进程与线程的区别. 3. 进程通信的几种方式. 4. 线程同步几种方式.(一定要会写生产者.消费者问题,完全消化理解) 5. 线程的实现方式. (也就是用户线程与内核线程的区别) 6. 用户态和核心态的区别. 7. 用户栈和内核栈的区别. 8. 内存池.进程池.线程池.(c++程序员必须掌握) 9. 死锁的概念,导致死锁的原因. 10. 导致死锁的四个必要条件. 11. 处理死锁

事件驱动编程——《Unix/Linux编程实践教程》读书笔记(第7章)

1.curses库 /* 基本curses函数 */ initscr(); // 初始化curses库和tty endwin(); // 关闭curses并重置tty refresh(); // 使屏幕按照你的意图显示 move(r, c); // 移动光标到屏幕的(r, c)位置 addstr(s); // 在当前位置画字符串s addch(c); // 在当前位置画字符c clear(); // 清屏 standout(); // 启动standout模式(一般使屏幕反色) standend

Linux编程基础

一.Linux编程环境 1. 程序编辑器: 命令模式: i(insert):插入 a(after):之后插入 x():删除字符 dd:删除一行 :行号进行指定 :/字符串:搜索字符串 :q,退去 :wq ndd:删除n行 yy:复制当前行 p:粘贴 ny:从当前航开始复制 2. gcc gcc -s  text.c gcc  -c text.c gcc -o text  text.c gcc -o text test1.o text2.o 二.调试工具 1. gdb 执行调试命令:gdb tes

Linux编程---I/O部分

非常多函数都能够在网上找到,也比較基础,所以原型仅仅给出了函数名.详细用到再man吧. 输入输出是个非常重要的一块内容.差点儿网络相关的东西基本都是靠底层IO调用来实现的. 好吧.还是先踏踏实实的介绍一下C标准库中的IO函数吧.个别函数我也是第一次见.对于不太常见的我就多解释一下,反正通常这些函数百度一下就清楚了,我就不多解释了~ 1.C标准库IO函数 1.1流的关闭开启与重定向 fopen:打开一个流 fclose:关闭一个流 freopen:又一次打开一个流 1.2 读与写 读: fgetc

【Linux编程】存储映射I/O

存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射,对缓冲区的读.写操作就是对文件的读.写操作,从而可以不再使用read.write系统调用. 将文件映射到存储区的函数由mmap完成,函数原型如下: #include <sys/mman.h> /* 成功返回映射区起始地址,出错返回MAP_FAILED */ void *mmap(void *addr, size_t len, int prot, int flag, int filedes, off_t off); 参数说明: addr

每天进步一点点——Linux编程中的文件锁之flock

转载请说明出处:http://blog.csdn.net/cywosp/article/details/30083015 1. 场景概述 在多线程开发中,互斥锁可以用于对临界资源的保护,防止数据的不一致,这是最为普遍的使用方法.那在多进程中如何处理文件之间的同步呢?我们看看下面的图: 图中所示的是两个进程在无同步的情况下同时更新同一个文件的过程,其主要的操作是: 1. 从文件中读取序号. 2. 使用这个序号完成应用程序定义的任务. 3. 递增这个序号并将其写回文件中. 从图中可得知两个进程读取分

linux编程中接收主函数返回值以及错误码提示

程序A创建子进程,并调用进程B,根据不调用的不同情况,最后显示结果不同. #include <stdio.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <errno.h> int main() { pid_t pid, rpid; int stat; if ((pid = fork()) < 0) { perror("for