Linux系统编程——进程间的通信(五)信号

对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序 。

信号概述

1、信号的名字和编号
每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数

具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用
在这里插入图片描述
2、信号的处理

信号的处理有忽略,捕捉,和默认三种方式

忽略信号
大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILL和SIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景。
SIG_IGN为系统自带的宏函数。
例:

signal(SIGINT,SIG_IGN);//将SIGINT信号忽略

也就是按下Ctrl + C中断信号时,会忽略它

捕捉信号

告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。

系统默认动作

对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。

具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。

信号的使用

其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 进程,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了进程。如果查看信号编号和名称,可以发现9对应的是 SIGKILL,正是杀死该进程的信号。
而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。

kill -9 进程PID
kill -SIGKILL 进程PID

两者等价

对于信号来说,最大的意义不是为了杀死信号或者进程,而是实现一些异步通讯的手段,那么该如何定义信号的处理函数呢?

创建

常用API

信号处理函数的注册
入门版:函数signal
高级版:函数sigaction

信号处理发送函数
1.入门版:kill
2.高级版:sigqueue
(kill -9 pid)
如果系统通过 ctrl+c 产生了一个 SIGINT(中断信号),显然不是所有程序同时结束,那么,信号一定需要有一个接收者。对于处理信号的程序来说,接收者就是自己。

先是入门版

signal函数

signal 的头文件以及函数原型

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

函数原型由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
注册函数:
sighandler_t signal(int signum, sighandler_t handler);

signum :信号的编号。
handler :中断函数的指针。

handler函数
真实处理信号的函数:
typedef void (*sighandler_t)(int); 中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。

示例代码:捕捉动作

信号处理函数的注册

#include<stdio.h>
#include <signal.h>
//真实处理信号的函数
void handler(int signum)
{
    printf("get signum=%d\n",signum);
    switch(signum){
        case 2:
            printf("SIGINT\n");
            break;
        case 10:
            printf("SIGUSR1\n");
            break;
    }
}
int main()
{
    signal(SIGINT,handler);//信号处理函数的注册
    signal(SIGUSR1,handler);//信号处理函数的注册
    while(1);
    return 0;
}

在这里插入图片描述
当按下Ctrl + C时,会捕捉它,并且将信号的编号打印出来。
当发送信号编号为10的SIGUSR1信号时也是一样的
在这里插入图片描述

在这里插入图片描述

使用信号函数来发送信号(kill函数)

同时也可以使用system来实现kill

signal2.c

#include<stdio.h>
#include <signal.h>
#include <sys/types.h>
int main(int argc,char **argv)
{
    int signum=0;
    int pid=0;
    char cmd[128]={0};//设置system函数处理命令的大小
    signum= atoi(argv[1]);//转为整型
    pid= atoi(argv[2]);//转为整型
    printf("num=%d\n",signum);
    printf("pid=%d\n",pid); 	
    kill(pid,signum);
    // sprintf(cmd,"kill -%d %d",signum,pid);
    // system(cmd);
    printf("send signal ok\n");
    return 0;
}

signal.c代码

#incude <stdio.h>
int main()
{
	while(1);
	return 0;
}

运行signal.c会一直执行死循环
在这里插入图片描述
再运行signal2.c,并向进程pid为33380的进程发送编号为2的信号,也就是 SIGINT(中断信号)
在这里插入图片描述
会发现signal.c进程已经结束
在这里插入图片描述

示例代码:忽略动作

#include<stdio.h>
#include <signal.h>
//真实处理信号的函数
void handler(int signum)
{
    printf("get signum=%d\n",signum);
    printf("SIGINT\n");
}
int main()
{     
    signal(SIGINT,SIG_IGN);//信号处理函数的注册
    while(1);
    return 0; 
}

注:SIG_IGN:为系统自带的宏
在这里插入图片描述
会发现ctrl+c已经对进程不起作用了,这个信号被进程忽略。

高级版

高级版主要是sigaction、sigqueue两个函数,分别用于信号处理函数的注册和信号处理发送。

对于入门版的信号处理API的重点在于动作,但kill 函数发送的信号是无法携带数据。对于高级版的信号处理API的重点信号携带的信息。

sigaction函数

sigaction 可以用来查询或设置信号处理方式。

包含的头文件

#include<signal.h>

函数原型:

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

signum:参数指出要捕获的信号类型,也就是信号的编号
act:参数指定新的信号处理方式,struct sigaction类型如果不为空说明需要对该信号有新的配置。
oldact:备份,如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

struct sigaction结构体

struct sigaction {
   void (*sa_handler)(int); //信号处理程序,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
   void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用
   sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。
   int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据
 };
//回调函数句柄sa_handler、sa_sigaction只能任选其一

sa_handler
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
void (*sa_sigaction)(int, siginfo_t *, void *);

参数有信号编号num,结构体siginfo,指针,指针为空表示没有数据,指针非空表示有数据

而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息

 siginfo_t {
       int      si_signo;    /* Signal number */
       int      si_errno;    /* An errno value */
       int      si_code;     /* Signal code */
       int      si_trapno;   /* Trap number that caused
                              hardware-generated signal
                             (unused on most architectures) */
       pid_t    si_pid;      /* Sending process ID */
       uid_t    si_uid;      /* Real user ID of sending process */
       int      si_status;   /* Exit value or signal */
       clock_t  si_utime;    /* User time consumed */
       clock_t  si_stime;    /* System time consumed */
       sigval_t si_value;    /* Signal value */
       int      si_int;      /* POSIX.1b signal */
       void    *si_ptr;      /* POSIX.1b signal */
       int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
       int      si_timerid;  /* Timer ID; POSIX.1b timers */
       void    *si_addr;     /* Memory location which caused fault */
       int      si_band;     /* Band event */
       int      si_fd;       /* File descriptor */
}

成员有很多,si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。
关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。

sa_mask

sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置sa_mask 成员,设置在其的信号集中的信号,==会在捕捉函数调用前设置为阻塞,==并在捕捉函数返回时恢复默认原有设置。这样的目的是,在调用信号处理函数时,就可以阻塞默写信号了。在信号处理函数被调用时,操作系统会建立新的信号阻塞字,包括正在被递送的信号。因此,可以保证在处理一个给定信号时,如果这个种信号再次发生,那么他会被阻塞到对之前一个信号的处理结束为止。

sa_flags

SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL

SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

sigqueue函数

向指定进程发送一个信号和数据

头文件

#include <signal.h>

函数原型

int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
       int   sival_int;
       void *sival_ptr;
};

pid:目标进程的进程号
sig:发的是什么信号,信号编号
value发送的消息(int或者char*),是一个联合体,表示信号附带的数据,附带数据可以是一个整数也可以是一个指针,有如下形式:

union sigval {
	int sival_int;
	void *sival_ptr;//指向要传递的信号参数
};value 

注意:
1、使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。
2、sigaction 结构体中的sa_sigaction成员提供了信号捕捉函数。如果实现的sa_handler成员,那么将无法获取额外携带的数据。
3、sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。
4、、sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。但是,信号不能无限的排队,信号排队的最大值受到SIGQUEUE_MAX的限制,达到最大限制后,sigqueue 会失败,errno 会被设置为 EAGAIN。

示例代码

sigget.c

#include<stdio.h>
#include <signal.h>
/*信号处理函数*/
void handler(int signum, siginfo_t *info, void *context)
{
    printf("get signum %d\n",signum);
    if(context!=NULL){//如果有内容
    printf("get data=%d\n",info->si_int);
    printf("get data=%d\n",info->si_value.sival_int);
}
int main()
{
    struct sigaction act;
    printf("pid = %d\n",getpid());
    act.sa_sigaction = handler;//信号处理程序,能够接受额外数据和sigqueue配合使用
    act.sa_flags = SA_SIGINFO;//影响信号的行为SA_SIGINFO表示能够接受数据
    sigaction(SIGUSR1,&act,NULL);
    //sigaction(SIGINT,&act,NULL);
    while(1);
    return 0;
}

sigsen.c

#include <signal.h>
#include <stdio.h>
int main(int argc,char **argv)
{
     int pid = atoi(argv[2]);
     int signum = atoi(argv[1]);
     union sigval value;
     value.sival_int = 100;
     sigqueue(pid,signum,value);//信号处理发送函数
     return 0;
}

在这里插入图片描述
接收端发送阻塞,再运行send
在这里插入图片描述
在这里插入图片描述
可以看到接收端get已经接收到send发送的数据。

参考

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值