信号及信号处理
一、信号概念
1.信号编号
kill -l
ps aux
ps ajx
linux中提供了64种信号编号,前32为经典信号(软件/操作系统),后32为实时信号(硬件/驱动)。
2.信号机制
man 7 signal
3.信号产生种类
1.特殊终端按键
ctl+c - SIGINT
ctl+z - SIGTSTP(暂停)
ctl+\ - SIGQUIT
2.硬件异常
除以0操作
访问非法内存
3.kill函数或kill命令
int kill(pid_t pid,int sig)
pid > 0
pid是信号欲送往的进程的标识。
pid ==0
信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
pid <- 1
信号将送往以-pid为组标识的进程。
pid == -1
信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4.int raise(int sig) (自己向自己发信号)
5.void abort(void) (自己向自己发SIGABRT终止)
6.某种软件条件已发生
定时器alarm到时,每个进程只有一个定时器。
unsigned int alarm(unsigned int seconds)
到时向进程发SIGALRM信号(运行结束)
返回未睡够秒数
4.信号产生原因
···
二、信号集和信号屏蔽字
1.进程处理信号行为
SIG_IGN
SIG_DFL
a signal handling function
1.默认处理动作(每个信号都有)
2.忽略
3.捕捉(用户自定义信号处理函数)
2.信号集处理函数
构造一个信号集,再注册到当前进程
3.PCB的信号集
4.sigprocmask(读取或更改进程的信号屏蔽字)
#include <signal.h>
int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );
返回值:若成功则返回0,若出错则返回-1
how参数
5.sigpending(读取当前的未决信号集)
#include <signal.h>
int sigpending( sigset_t * set)
通过set传出
例:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void printsigset(sigset_t *set)
{
int i = 0;
for(;i<32;i++){
if(sigismember(set,i))
putchar('1');
else
putchar('0');
}
puts(""); //打印换行
}
int main()
{
sigset_t s,p;
sigemptyset(&s);
sigaddset(&s,SIGINT);
sigprocmask(SIG_BLOCK,&s,NULL);
while(1)
{
sigpending(&p);
printsigset(&p);
sleep(1);
}
return 0;
}
SIGKILL和SIGSTOP不能被阻塞、捕捉和忽略!
三、信号捕捉设定(sigaction)
#include<signal.h>
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //捕捉函数(早期)
void (*sa_sigaction)(int, siginfo_t *, void *); //与上面的互斥,只能执行一个
sigset_t sa_mask; //设置阻塞信号集
int sa_flags; //SA_SIGINFO 或者 0,设置是用第一个还是第二个
void (*sa_restorer)(void);
}
例:
//unix/linux提供的信号捕捉函数
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
int temp = 0;
void handler_sigint(int signo)
{
printf("I an do_sig\n");
printf("signo = %d\n",signo);
}
int main()
{
struct sigaction act;
act.sa_handler = handler_sigint;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGINT,&act,NULL);
while(1){
printf("***********\n");
sleep(1);
}
}
捕捉函数执行过程
信号并不一定是产生后第一时间被响应(中断、异常、系统调用进入内核)
四、SIGUSR1 SIGUSR2实现父子进程同步输出
子进程继承父进程的信号屏蔽字和信号处理动作
#include<stdio.h>
#include<signal.h>
#include <unistd.h>
void pdosig(int num ,struct __siginfo * _siginfo, void * ptr)
{
static int count = 0;
printf("I am parent ,pid is %d,the proc : %d send signal: %d to me count is: %d\n",getpid(),_siginfo->si_pid,num,count);
count+=2;
sleep(1);
kill(_siginfo->si_pid,SIGUSR2);
}
void cdosig(int num,struct __siginfo * _siginfo, void * ptr)
{
static int count = 1;
printf("I am child ,pid is %d,the proc : %d send signal: %d to me count is: %d\n",getpid(),_siginfo->si_pid,num,count);
count+=2;
sleep(1);
kill(_siginfo->si_pid,SIGUSR1);
}
int main()
{
struct sigaction sig;
int pid = fork();
if(pid >0)
{
printf("i am father %d\n",getpid());
sig.sa_sigaction = pdosig;
sigemptyset(&sig.sa_mask);
sig.sa_flags=0;
sigaction(SIGUSR1,&sig,NULL);
}
else
{
printf("i am child %d\n",getpid());
sig.sa_sigaction = cdosig;
sigemptyset(&sig.sa_mask);
sig.sa_flags=0;
sigaction(SIGUSR2,&sig,NULL);
kill(getppid(),SIGUSR1);
}
while(1)
{
sleep(1);
}
return 0;
}
五、C标准库信号处理函数
例:
//C标准库提供的信号捕捉函数
//在调用捕捉函数时,禁止调用不可重入函数,c库函数一般为不可重入函数
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void do_sig(int n)
{
printf("hello\n");
}
int main()
{
signal(SIGINT,do_sig);
while(1){
printf("*********\n");
sleep(1);
}
return 0;
}
//可重入函数不能含全局变量和静态本地变量
//集合fork,exec,wait于一体
#include<stdio.h>
#include<stdlib.h>
int main()
{
system("要执行的东西*******");
return 0;
}
可重入函数
不能含全局变量和静态本地变量
信号捕捉函数里应使用可重入函数,禁止调用不可重入函数
六、信号引起的竞态和异步I/O
1.时序竞态
int pause(void)
//是调用的进程挂起,直到有信号递达,如果递达信号是忽略,则继续挂起
int sigsuspend(const sigset_t *mask)
//以通过指定mask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程信号屏蔽字恢复原来的值
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void do_sit(int n)
{
}
int main(void)
{
struct sigaction act;
act.sa_handler=do_sit;
//act.sa_handler=SIG_IGN; 不行
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGUSR1,&act,NULL);
pause();
printf("hello");
return 0;
}
2.mysleep函数实现:
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alrm(int signo)
{
}
//返回的是未睡够的秒数
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction newact,oldact;
sigset_t newmask,oldmask,suspmask;
unsigned int unslept;
newact.sa_handler=sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags=0;
sigaction(SIGALRM,&newact,&oldact);
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask,&oldmask);
alarm(nsecs);
suspmask=oldmask;
sigdelset(&suspmask,SIGALRM);
sigsuspend(&suspmask);
unslept=alarm(0);
sigaction(SIGALRM,&oldact,NULL);
sigprocmask(SIG_SETMASK,&oldmask,NULL);
return(unslept);
}
int main(void)
{
while(1){
mysleep(2);
printf("Two seconds passed\n");
}
return 0;
}
3.避免异步I/O的类型
sig_atomic_t
//平台下的原子类型(取决于平台)
volatile
//防止编译器开启优化选项时,优化对内存的读取
七、SIGCHLD信号处理
1.产生条件
子进程终止时
子进程接收到SIGCTOP信号停止时
子进程处在停止态,接受到SIGCONT后唤醒时
2.status处理方式
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
void sys_err(char *str)
{
perror(str);
exit(1);
}
void do_sig_child(int n)
{
/*while(!(waitpid(0,NULL,WNOHANG)==-1)
;
*/
int status;
pid_t pid;
while(waitpid(0,&status,WNOHANG)>0){
if(WIFEXITED(status)) //是否为正常退出
printf("child %d exit %d\n",pid,WEXITSTATUS(status)); //测试退出值
else if(WIFSIGNALED(status)) //是否被信号终止
printf("child %d cancel signal %d\n",pid,WTERMSIG(status)); //是被第几号信号终止
}
}
int main()
{
pid_t pid;
int i;
//阻塞SIGCHLD
for(i=0;i<10;i++){
if((pid=fork())==0)
break;
else if(pid<0)
sys_err("fork");
}
if(pid==0){
int n =8;
while(n--){
printf("child ID %d\n",getpid());
sleep(1);
}
return i;
}
else if(pid>0){
//先设置捕捉
//再解除SIGCHLD
struct sigaction act;
act.sa_handler=do_sig_child;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD,&act,NULL);
while(1){
printf("parent ID %d\n",getpid());
sleep(1);
}
}
return 0;
}
八、向信号捕捉函数传参
1.sigqueue
int sigqueue(pid_t pid,int sig,const union sigval value)
union sigval{
int sival_int;
void *sival_ptr; //有血缘关系
};
2.sigaction
九、信号中断系统调用
read阻塞时,信号中断系统调用:
1.返回部分读到的数据
2.read调用失败,erron设成EINER