目录
一,信号
信号的实质是软中断,它被发送给一个正在执行的进程以通知该进程有某一事件发生了。
进程之间可以互相通过系统调用kill()发送软中断信号,内核也可以因为内部事件而给进程发送信号。收到信号的进程对各种信号有不同的处理方法,处理方法分为3类:第一种是类似于中断处理程序,对需要的信号,进程可以指定处理函数;第二种是忽略该信号,不进行任何处理;第三种是对该信号的处理保留系统的默认值,这种默认操作大多数使得信进程终止。进程通过系统调用sigal()来指定进程对某个信号的处理行为。
每个信号都有一个名字,以SIG开头,在头文件<signal.h>中,这些信号被定义为正整数,称为信号编号,没有编号为0的信号。
在进程表的表项中有一个软中断信号域,该域每一位对应一个信号,当有信号发送给进程时,对应位置位。
信号列表:
kill -l
man 7 signal //查看信号列表
二,信号操作
1,信号的处理
内核给进程发送信号的方法是在进程所在的进程表项中的信号域设置对应的位,内核处理一个进程收到的信号的时机是在一个进程从内核态返回到用户态时,处理信号有三种类型:进程接收到信号后退出,忽略信号,捕捉信号(调用用户自定义函数进行处理,类似于中断服务函数,该函数由signal进行注册)。
#include <signal>
typedef void (*sighander_t) (int) //函数指针
sighander_t signal(int signum,sighander_t hander)
signum表示所注册函数针对的信号,hander取值为SIG_IGN表示忽略信号,SIG_DFL表示系统默认处理,或者是传入自定义处理函数地址。
例如:SIGINT信号由程序终止时发出,ctrl+c:
#include <sys/types.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
void SignHander(int num) //自定义函数
{
printf("Capture signal num %d\n",num);
exit(0);
}
int main()
{
signal(SIGINT,SignHander);
while(1)
sleep(1);
return 0;
}
当键入ctrl+c程序终止,收到信号后执行自定义函数打印输出如下:
另一种更为强大的信号处理机制: sigaction系统调用
#include <signal.h>
int sigaction (int signum,const struct sigactions *act,struct sigaction *oldact);
成功返回0,出错返回-1,signum表示要捕捉的信号,act是一个结构体,其包含了信号处理的地址和方式,oldact是一个传出函数,sigaction函数调用成功后,oldaction包含以前对信号处理的信息。
sigaction结构体原型如下:
struct sigaction {
void (*sa_handler)(int); //老类型信号处理指针,此参数和signal()的参数handler相同
void (*sa_sigaction)(int, siginfo_t *, void *); //新类型信号处理指针
sigset_t sa_mask; //将要被阻塞信号集合
int sa_flags; //信号处理方式掩码 取值为SA_SIGNFO表示采用新的sa_sigaction处理
void (*sa_restorer)(void); //保留
}sa_sigactions指向函数原型为:
void handler(int isugnum, siginfo_t * psigninfo, void * preserved);
isugnum为传入的信号
psigninfo与该信号有关的信息,是一个结构体
preserved保留
sa_handler,sa_sigaction应只有一个生效,sa_mask是一个包含信号集合的结构体,该结构体信号表示在进行信号处理时,将要被阻塞的信号,sa_flags表示信号处理时的不同选项。
例程如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int g_iSeq=0;
void SignHandlerNew(int iSignNo,siginfo_t * pInfo,void* pReserved)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandlerNew,signo %d\n",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandlerNew,signo:%d\n",iSeq,iSignNo);
}
int main()
{
char szBuf[20];
int iRet;
struct sigaction act;
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
sigemptyset(&act.sa_mask);
//注册函数
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
do
{
//从标准输入读入
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0)
{
perror("read fail\n");
break;
}
szBuf[iRet]=0;
printf("Get:%s",szBuf);
}
while(strcmp(szBuf,"quit\n")!=0);
return 0;
}
在实际应用当中,一个用户常常对多个信号进行处理,为对多个信号进行处理,引入信号集的概念,信号集定义为sigset_t类型的变量。POSIX.1定义了以下处理信号集的函数:
#include <signal.h>
int sigemptyset(sigset_t *set); //信号集设置为空
int sigfillset(sigset_t *set); //信号集设置为满,即包含所有信号
int sigaddset(sigset_t *set,int signum); //signum的信号添加进信号集
int sigdelset(sigset_t *set,int signum); //删除信号集中信号signum
成功返回0,失败返回-1;
#include <signal.h>
int sigismember(sigset_t *set,int signum); //判断signum是否存在信号集中
是真返回1,假返回0;
set为指向信号集的指针,signum表示一个信号。
2,信号的发送
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int signum);
成功返回0,出错返回-1,pid表示kill函数发送对象的进程或进程组号,具体取值规则不赘述;
signum为发送的信号值,取0时,不发送信号,但可用于检查进程是否存在,以及当前进程是否有向目标发送信号的权限,非root权限的进程只能向同一session或者同一用户的进程发送信号。
kill.c如下:
父进程发送信号SIGABRT,使得子进程非正常结束:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int status;
if(!(pid=fork()))
{
printf("I am child process\n");
sleep(10); //睡眠
printf("I am child process again\n");
return 1; //非正常终止
}
else
{
printf("sent signal to child process(%d)\n",pid);
sleep(1);
//发送信号
if(kill(pid,SIGABRT)==-1)
{
printf("kill fail\n");
}
wait(&status);
/*
WIFSIGNALED(status)为非0 表明进程异常终止。
若上宏为真,此时可通过WTERMSIG(status)获取使得进程退出的信号编号
*/
if(WIFSIGNALED(status))
{
printf("child process receive signal %d\n",WTERMSIG(status));
}
}
return 0;
}
如下,并没有打印“I am child process again”,表示子进程收到信号并终止进程。
#include <sys/types.h>
#include <signal.h>
int raise(int signum);
该函数是进程向自己本身发送一个信号; 成功返回0,失败返回-1。
raise.c如下:
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
int main()
{
printf("Linux C_1\n");
if(raise(SIGABRT)==-1)
{
printf("raise fail\n");
exit(1);
}
printf("Linux C_2\n");
return 0;
}
该进程向自身发送了异常终止信号,就终止进程,故 "Linux C_2\n" 不会打印。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
alarm函数专门为SIGALARM信号设定,使系统在一定时间之后发送信号,返回值:如果调用alarm之前,进程已经设定闹钟,则返回上一个闹钟剩余时间,否则返回0,参数seconds指定下一次发送信号的时间,调用alarm,任何之前的alarm调用都将无效,如果seconds为0,那么进程不在包含任何闹钟时间。
alarm.c:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void Handler()
{
printf("Alarm even\n");
}
int main()
{
signal(SIGALRM,Handler);
alarm(5);
for(int i=0;i<8;i++)
{
sleep(1);
printf("sleep_%d \n",i);
}
return 0;
}
如下,设置alarm信号5s后响应:
3,信号的阻塞
#include <signal.h>
int sigprocmask(int how,sigset *set,sigset_t * oldset);
成功返回0,出错返回-1;set用于表示指向的信号集,oldset不为NULL,函数将当前信号掩码返回到oldset。how表示对如何对信号集及信号掩码进行操作,其取值有:
SIG_BLOCK: 将set信号集包含信号加到当前信号掩码中;
SIG_UNBLOCK: 将set信号集包含信号从当前信号掩码中移除;
SIG_SETMASK:设定当前信号掩码为set指向信号集包含的信号;
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
int g_iSeq=0;
void SignHandlerNew(int iSignNo,siginfo_t * pInfo,void* pReserved)
{
int iSeq=g_iSeq++;
printf("%d Enter SignHandlerNew,signo %d\n",iSeq,iSignNo);
sleep(3);
printf("%d Leave SignHandlerNew,signo:%d\n",iSeq,iSignNo);
}
int main()
{
char szBuf[20];
int iRet;
struct sigaction act;
act.sa_sigaction=SignHandlerNew;
act.sa_flags=SA_SIGINFO;
//阻塞 SIGINT 信号
sigset_t sigSet;
//清空sigSet信号集
sigemptyset(&sigSet);
//该信号集添加SIGINT信号
sigaddset(&sigSet,SIGINT);
//阻塞掉sigSet中的信号
sigprocmask(SIG_BLOCK,&sigSet,NULL);
sigemptyset(&act.sa_mask);
//注册函数
sigaction(SIGINT,&act,NULL);
sigaction(SIGQUIT,&act,NULL);
do
{
iRet=read(STDIN_FILENO,szBuf,sizeof(szBuf)-1);
if(iRet<0)
{
perror("read fail\n");
break;
}
szBuf[iRet]=0;
printf("Get:%s",szBuf);
}
while(strcmp(szBuf,"quit\n")!=0);
return 0;
}
结果如下: ctrl+\发送了SIGQUIT信号没有被阻塞,而ctrl+c 发送的SIGINT信号被阻塞掉: