LinuxC编程实战信号详细总结

本文参考《LinuxC编程实战》

信号的来源

信号来源按产生体哦啊间的不同分为硬件和软件两种方式。
1.硬件方式:
1). 用户按下某些键所产生的信号。如<ctrl + C>组合键产生一个SIGINT信号。
2). 硬件异常产生的一些信号。
2.软件方式:
1). 用户在终端下用kill命令向进程发送任意信号;
2). 进程调用kill 或 sigqueue 函数发送信号;
3). 当检测到某种软件已经具备时发出信号;如alarm和settimer设置的定时超时产生的SIGALRM信号;

信号的种类

在shell下输入命令kill -l 可以显示Linux系统支持的全部信息。信号值的定义在signal.h中:(这里只列举一个)
例如:
SIGINT:用户按下<ctrl+C>组合键产生该信号,终端向正在运行的由终端启动的程序发送此信号。默认动作为终止进程并产生core文件。
该信号在signal.h中的定义为:

#include <signal.h>
#define SIGINT 2

从这里可以看到SIGINT的值为2,经过kill -l 查看有64个信号值,他们的值分别从1到64;而前30个信号以被linux系统定义,31-64为linux的实时信号,他们没有固定的含义(可以由用户自己定义)
注意:linux线程机制使用了前3个实时信号,所有的事实信号默认为终止进程。

1.可靠信号和不可靠信号

类型范围性质
可靠信号1号-31号信号不会丢失,支持排队,递送多次
不可靠信号33号-64号信号会丢失,不支持排队只被递送一次

2.信号的优先级

如果一个进程有多个未决信号,则值(编号)越小越先被递送。linux系统和大多数POSIX标准的操作系统将优先递送不可靠信号。

信号的捕捉和处理

该操作主要有signal和sigaction函数来完成。
signal函数
该函数用来设置进程接收到信号时的动作,函数原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);

参数signum用来设置处理的信号,但接收到该信号便会跳转到handler所指的函数执行; 如果handler不是函数指针,则必须是SIG_IGN(忽略该信号)或SIG_DFL(按默认执行)。handler为函数指针,它所指的函数类型为sighandler_t,即函数有一个int参数且为void型。
例signal1.c:

/*****************************************************
 * 用signal函数忽略<ctrl + C>产生的SIGINT函数,来防止被杀死(可以用<ctrl + \>杀死)
 * ***************************************************/
#include <stdio.h>
#include <signal.h>

int main()
{
    signal(SIGINT, SIG_IGN);           //防止ctrl + C杀死程序

    while(1)                           //制造死循环
    {
        printf("A\n");
    }

    return 0;
}

例signal2.c:

/*****************************************************
 * 用signal函数接收指定信号,并设置函数响应
 * ***************************************************/
#include <stdio.h>
#include <signal.h>

void hand(int num)
{
    printf("\n<ctrl+ C>\n");
}

int main()
{
    signal(SIGINT, hand);           //防止ctrl + C杀死程序

    while(1)                           //制造死循环
    {
        ;
    }

    return 0;
}

例signal3.c

/*****************************************************
 * 用signal函数,理解不可靠信息SIGINT
 * ***************************************************/
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void hand(int num)
{
    sleep(3);
    printf("\n<ctrl+ C>\n");
}

int main()
{
    signal(SIGINT, hand);           //防止ctrl + C杀死程序

    while(1)                           //制造死循环
    {
        ;
    }

    return 0;
}

sigaction函数
该函数可用来检查或设置进程在接收到信号后的动作,执行成功返回0,错误失败返回-1,错误代码存入errno,函数原型为:

#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数signum为信号编码;act若不为空则为signum信号设置新的信号处理函数;oldact若不为空则储存旧的信号处理函数。struct sigaction的定义如下:

struct sigaction{
	void (*sa_handler)(int);        //函数指针,可设置为SIG_DFL或SIG_IGN
	void (*sa_sigaction)(int,siginfo_t *,void *);   //函数指针
	sigset_t sa_mask;               
	int sa_flags;                   
	void (*sa_restorer)(void);      //以作废不使用
}

参数sa_mask声明了一个信号屏蔽集,调用处理函数前将处理的信号加入信号屏蔽集中,新的信号屏蔽码会自动包括正在处理的信号,当从信号捕捉函数返回时,进程的信号屏蔽码会恢复为原来的值。因此,当处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞直到本次信号处理结束为止。若这种信号发生了多次,则对于不可靠信号,它只会被阻塞一次, 即本次信号处理结束以后只会再处理- -次(只有一个在排队,相当于丢失了信号);对于可靠信号(实时信号),则会被阻塞多次,即信号不会丢失,信号发生了多少次就会调用信号处理函数多少次(都在排队)。

sa flags成员用来说明信号处理的一一些其他相关操作,它可以取以下值或它们的组合。
在这里插入图片描述
当使用sa_aigaction来指定处理函数时,第二个参数定义如下:
在这里插入图片描述
pause函数
该函数使调用进程挂起直到捕捉到一个信号,函数原型为

#include <unistd.h>
int pause(void);

pause函数会令目前进程暂停(进入睡眠状态),知道被信号(signal)所中断。该函数只返回-1,并将errno设置为EINTR。

信号处理函数的返回

信号处理函数可以正常返回,也可以调用其他函数返回到程序的主函数中,而不是从该处理程序返回。信号处理程序可以返回或者调用abort. exit或longjmo(goto不支持跳出它所在的函数,因此不能用来从信号处理程序返回到主函数中)
setjmp/longjmp函数
使用longjmp函数可以跳转到setjmp设置的位置,函数原型为:

#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

参数env 是一个特殊类型jmp_ buf的变量。这一数据类型是某种形式的数组,其中存放的是在调用longimp时能用来恢复栈状态的所有信息。一般来说 env是个全局变量,因为需从另一个函数中引用它。我们可以在希望返回的位置使用setimp,直接调用setimp时返回0;当从longjmp返回时,setimp 的返回值是longimp的第2个参数的值,可以利用这一点使多个longjmp返回到一个 setjimp处,下面例题演示了setjmp 和longjmp丽数的用法。

#include <stdio.h>
#include <setjmp.h>
#include <unistd.h>
#include <signal.h>

jmp_buf env;       //保存跳转位置的栈信息

void handler_sigrtmin15(int signo)
{
    printf("recv SIGRTMIN+15\n");
    longjmp(env,1);
}

void handler_sigrtmax15(int signo)
{
    printf("recv SIGRTMAX-15\n");
    longjmp(env,2);
}

int main()
{
    int pid;
    printf("pid = %d\n",getpid());
    switch(setjmp(env))
    {
        case 0:
        {
            break;
        }
        case 1:
        {
            printf("return from SIGRTMIN+15\n");
            break;
        }
        case 2:
        {
            printf("return from SIGRTMAX-15\n");
            break;
        }
        default:
        {
            break;
        }
    }
    pid = getpid();
    signal(SIGRTMIN+15,handler_sigrtmin15);
    signal(SIGRTMIN-15,handler_sigrtmax15);

    while(1)
        ;
    
    return 0;
}

注意:应为信号处理函数在处理信号好时会屏蔽该信号,直到信号处理函数返回时结束,但使用longjmp时信号处理函数不是正常返回,所以longjmp使用一次后,该信号会被永久屏蔽,除非手动处理。 下面两个函数可以解决这个问题。
sigsetjmp/siglongjmp函数
函数原型为:

#include <stdio.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);

这两个函数与上面的区别在与sigsetjmp函数多了一个参数savesigs,如果它非0,则sigsetjmp在env中保存当前信号屏蔽字,在调用siglongjmp时会以env恢复原来的信号屏蔽字(savesigs只要非0及可)。

信号的发送

kill函数
该函数用来发送信号给指定进程,函数原型为:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

函数的行为与pid有关,第二个参数sig表示信号编码。

  • pid为正数,将信号发送给ID为pid的进程
  • pid为0, 将信号发送给当前进程所属进程组里的所有进程
  • pid为-1,将信号广播至系统除1号进程(init进程)和自身以外的所有进程
  • pid为比-1还小的数,则发送信号给属于进程组-pid的所有进程
  • pid为0,kill()仍执行正常的错误检查,不发送信号。

函数执行成功返回0,失败错误返回-1。
注意:只有root权限才能向任意进程发送信号,非root权限的进程只能向属于同一进程组或同一用户的进程发送信号。
sigqueue函数
该函数支持信号带有参数,从而可以配合sigaction使用。函数原型为:

#include <signal.h>
int sigquwuw(pid_t pid, int sig, const union sigval value);

sigqueue与kill不同的是他不能给一组进程发信号。参数value是一个共用体。

unionsignal{
	int sival_int;
	void *sival_ptr;
};

所以信号所携带的参数要么是整型,要么是void型指针。
alarm函数
该函数可以用来设定秒数,定时器超过将产生SIGALRM信号给调用进程。函数原型为:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

参数seconds表示设定的秒数,经过seconds后,内核给调用该函数的进程发送SIGALRM信号。如果seconds为0,则不要再发送SIGALRM信号。最新一次调用alarm将取消之前的设定。如果之前以经调用过alarm,则返回剩余的时间;如果之前没有设置过计时器则返回0。
注意:alarm只发送一次信号,要多次发送需要多次调用alarm函数
getitimer/setitimer函数
该函数与alarm函数一样,都是用来设置定时器,他们使用同一个定时器,所以会相互影响。该函数据有更多的功能,函数原型为:

#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);


参数value指定时器的时间,结构体的定义如下:

struct itimerval{
	struct timeval it_interval;
	struct timeval it_value;
}

struct timeval{
	lonf tv_sec;     //秒速
	long tv_usec;    //微秒
}

对于函数getitimer,如果存在由which指定的定时器,则将剩余时间保存在it _value中,该定时器的初始值保存在it _interval 中:如果不存在指定类型的定时器,则将value置为0返回。执行成功返回0,当有错误发生时则返回-1,错误代码存入ermo中,详细的错误代码说明请参考man手册。对于函数setitimer,参数ovalue如果不是空指针,则将在其中保存上次设定的定时器的值。定时器从value递减为0时,产生一个信号,并将it _value 的值设为it inteval,然后重新开始计时,如此周而复始。仅当it value 的值为0或者计时到达而it interval 的值为0时,停止计时。执行成功返回0,当有错误发生时则返回-1.
abort函数
该函数用来想进程发送SIGABRT信号,函数原型为:

#include <stdlib.h>
void abort(void);

如果进程设置信号处理函数以捕捉SIGABRT信号,且信号处理函数不返回(如使用longjmp),则abort()无法关闭进程。该函数没有返回值。

信号屏蔽

信号集

信号总数目达64个,超过了一个整型数能表示的位数(一个整型变量通常为32位),因此不能用整型量中的一位代表一种信号。POSIX 标准定义了数据类型sigset t来表示信号集,并且定义了一系列函数来操作信号集。它们的函数原型如下:

#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);

这些函数的含义如下:

  • 函数 sigemptyset用来初始化一个信号集, 使其不包括任何信号。
  • 函数sifillet用来初始化一个信号集,使其包括所有信号。
  • 函数sigaddset用来向set指定的信号集中添加由signum指定的信号。
  • 函数 sigdelset用来从set指定的信号集中删除由signum指定的信号。
  • 函数 sigismember用来测试信号signum是否包括在set指定的信号集中。

函数sigemptyset. sille sigaddset 以及sigdelset 在执行成功时返回0,失败返回-1。函数sigismember返回I表示测试的信号在信号集中,返回0表示测试的信号不在信号集中,出错返回-1。
注意:所有应用程序在使用信号集前,要对该信号集调用一次sigemptyset或silletl以初始化信号集。这是因为C语言编译器将不赋初值的外部和静态度量都初始化为0.

信号屏蔽

信号屏蔽又称信号阻塞,该函数的原型为:

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t * oldset);
int sigpending(sigset_t * set);
int sigsuspend(const sigset_t *mask);

这些函数的含义如下:
sigprocmask函数
每个进程都有一个信号屏蔽码,它规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽码。如果参数oldset是非空指针,则该进程之前的信号屏蔽码通过oldset返回:如果参数set是非空指针,则该函数将根据参数how来修改信号当前屏蔽码,how的取值如下。
在这里插入图片描述
该函数执行成功返回0,错误失败返回-1。
how中前两种参数较难理解,可以配合下面代码理解:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void my_err(const char * err_string, int line)
{
    fprintf(stderr, "line:%d ", line);
    perror(err_string);
    exit(1);
}

void handler_sigint(int signo)        //SIGINT信号处理函数
{
    printf("\nrecv SIGINT\n");
}

int main()
{
    sigset_t newmask, oldmask, zeromask;

    if(signal(SIGINT, handler_sigint) == SIG_ERR)
    {
        my_err("signal",__LINE__);
    }

    sigemptyset(&newmask);        //初始化一个空的信号集newmask
    sigemptyset(&zeromask);       //初始化一个空的信号集
    sigaddset(&newmask, SIGINT);  //向newmask信号集加入了SIGINT信号

    if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)   //将newmask加入信号屏蔽集
    //if(sigprocmask(SIG_UNBLOCK,&newmask,&oldmask) < 0)   //将删除信号品比集中newmask中的内容
    {
        my_err("sigprocmask",__LINE__);
    }
    else
    {
        printf("SIGINT blocked\n");
    }

    sleep(10);

    sigpending(&zeromask);             //将为决信号放如zeromask
    if(sigismember(&zeromask, SIGINT) == 1)
    {
        printf("SIGINT is in pending queue\n");
    }
    else
    {
        printf("SIGINT is not in pending queue\n");
    }
    
    if(sigprocmask(SIG_UNBLOCK,&newmask,&oldmask) < 0)   //将删除信号品比集中newmask中的内容
    {
        my_err("sigprocmask",__LINE__);
    }
    else
    {
        printf("SIGINT unblocked\n");
    }

    while(1)
        ;

    return 0;
}

sigpending函数
该函数用来获取调用进程因被阻塞而不能递送和当前为决的信号集,该信号集通过参数set返回。执行成功返回0,错误失败返回-1。
例:sig_mask.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void my_err(const char *err_string, int line)
{
    fprintf(stderr, "line:%d ",line);
    perror(err_string);
    exit(1);
}

void hander_sigint(int signo)
{
    printf("\nrecv SIGINT\n");
}

int main()
{
    sigset_t newmask, oldmask, pendmask;    //定义信号集

    if(signal(SIGINT,hander_sigint) ==  SIG_ERR)
    {
        my_err("signal",__LINE__);
    }

    sleep(10);                                       //留出按<ctrl + C>的时间

    sigemptyset(&newmask);                           //初始化一个信号集,不包括任何信号
    sigaddset(&oldmask,SIGINT);                      //向oldmask信号集中添加SIGINT信号

    if(sigprocmask(SIG_UNBLOCK, &newmask,&oldmask) < 0)
    {
        my_err("sigprocmask",__LINE__);
    }
    else
    {
        printf("SIGINT blocked\n");                //SIGINT阻塞
    }
    
    sleep(10);                                     //留出按<ctrl + C>的时间

   if(sigpending(&pendmask) < 0)                   //将未决信号集返回给pendmask信号集
    {
        my_err("sigpending",__LINE__);
    }

    switch(sigismember(&pendmask, SIGINT))         //检测信号是否在pendmask信号集中
    {
        case 0:
        {
            printf("SIGINT is not in pending queue\n");
            break;
        }
        case 1:
        {
            printf("SIGINT is in pending queue\n");
            break;
        }
        case -1:
        {
            my_err("sigismember",__LINE__);
            break;
        }
        default:
            break;
    }

    if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)                  //将信号屏蔽码设置为oldmask信号集
    {
        my_err("sigprocmask",__LINE__);
    }
    else
    {
        printf("SIGINT unblocked\n");                  //SIGINT没有阻塞
    }

    while(1)
        ;
    
    return 0;
}

sigsuspend函数
函数sigsuspend将进程的信号屏蔽码设置为mask,然后与pause函数-样等待信号的发生并执行完信号处理函数。信号处理函数执行完后再把进程的信号屏蔽码设置为原来的屏蔽字,然后sigsuspend函数才返回。sigsuspend 函数保证改变进程的屏蔽码和将进程挂起等待信号是原子操作。sigsuspend函数总是返回-1,并将errno置为EINTR。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void my_err(const char * err_string, int line)
{
    fprintf(stderr, "line:%d ", line);
    perror(err_string);
    exit(1);
}

void handler_sigint(int signo)        //SIGINT信号处理函数
{
    printf("\nrecv SIGINT\n");
}

int main()
{
    sigset_t newmask, oldmask, zeromask;                    //定义三个信号集

    if(signal(SIGINT, handler_sigint) == SIG_ERR)           //接收信号
    {
        my_err("signal",__LINE__);
    }

    sigemptyset(&newmask);        //初始化一个空的信号集newmask
    sigemptyset(&zeromask);       //初始化一个空的信号集
    sigaddset(&newmask, SIGINT);  //向newmask信号集加入了SIGINT信号

    if(sigprocmask(SIG_BLOCK,&newmask,&oldmask) < 0)       //将newmask中的信号加入信号屏蔽集
    {
        my_err("sigprocmask",__LINE__);
    }
    else
    {
        printf("SIGINT blocked\n");           //加入成功,SIGINT被阻塞
    }


    if(sigsuspend(&zeromask) != -1)           //将信号屏蔽集设置为zeromask(为空),即相当于取消所有的信号屏蔽,并等待信号
    {
        my_err("sigsuspend",__LINE__);
    }
    else
    {
        printf("recv a signo, return from sigsuspend\n");      //取消成功,当前信号屏蔽集为空
    }

    if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)            //将oldmask设置为信号屏蔽集
    {
        my_err("sigprocmask",__LINE__);
    }
    pause();                                                  //等待信号 

    if(sigprocmask(SIG_SETMASK,&oldmask,NULL) < 0)              //将oldmask设置为信号屏蔽集

    {
        my_err("sigprocmask",__LINE__);
    }
    
    while(1)
        ;

    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值