一、信号的基本概述
1.什么是信号?
(1)对于linux来说,信号是软中断。比如在终端输入ctrl+c来中断正在运行的程序,原理是linux通过信号机制来停止一个程序。
(2)每一个信号都有一个名字和编号,名字是以"SIG"开头的,例如SIGIO,SIGCHLD等。信号是从1开始编号的,不存在0号信号。
(3)信号名都被定义为正整数。
(4)信号定义在signal.h头文件中。
(5)使用kill -l查看信号的名字和序列。信号是从1开始编号的,不存在0号信号。
(6)linux下对信号的宏定义
#define SIGHUP 1
进程由於控制终端死去或者控制终端发出起命令
#define SIGINT 2
键盘中断所产生的信号
#define SIGQUIT 3
键盘终止
#define SIGILL 4
非法的指令
#define SIGTRAP 5
进程遇到一个追踪(trace)或者是一个中断嵌套
#define SIGABRT 6
由abort系统调用所产生的中断信号
#define SIGIOT 6
类似於SIGABRT
#define SIGBUS 7
进程试图使用不合理的记忆体
#define SIGFPE 8
浮点异常
#define SIGKILL 9
KILL
#define SIGUSR1 10
用户自定义
#define SIGSEGV 11
段错误
#define SIGUSR2 12
用户自定义
#define SIGPIPE 13
管道操作时没有读只写
#define SIGALRM 14
由alarm系统调用产生的timer时钟信号
#define SIGTERM 15
收到终端信号的进程
#define SIGSTKFLT 16
堆叠错误
#define SIGCHLD 17
子进程向父进程发出的子进程已经stop或者终止的信号
#define SIGCONT 18
继续运行的信号
#define SIGSTOP 19
stop
#define SIGTSTP 20
键盘所产生的stop信号
#define SIGTTIN 21
当运行在後状态时却需要读取stdin的资料
#define SIGTTOU 22
当运行在後状态时却需要写向stdout
#define SIGURG 23
socket的紧急情况
#define SIGXCPU 24
进程超额使用CPU分配的时间
#define SIGXFSZ 25
进程使用了超出系统规定文件长度的文件
#define SIGVTALRM 26
内部的alarm时钟过期
#define SIGPROF 27
在一个程式段中描绘时钟集过期
#define SIGWINCH 28
终端视窗的改变
#define SIGIO 29
非同步IO
#define SIGPOLL SIGIO
pollable事件发生
————————————————
版权声明:本文为CSDN博主「hnzmdzcm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chenming_zhang/article/details/7824502
名称--------默认动作---------说明---------------------
SIGHUP 终止进程 终端线路挂断
SIGINT 终止进程 中断进程
SIGQUIT 建立CORE文件 终止进程,并且生成core文件
SIGILL 建立CORE文件 非法指令
SIGTRAP 建立CORE文件 跟踪自陷
SIGBUS 建立CORE文件 总线错误
SIGSEGV 建立CORE文件 段非法错误
SIGFPE 建立CORE文件 浮点异常
SIGIOT 建立CORE文件 执行I/O自陷
SIGKILL 终止进程 杀死进程
SIGPIPE 终止进程 向一个没有读进程的管道写数据
SIGALARM 终止进程 计时器到时
SIGTERM 终止进程 软件终止信号
SIGSTOP 停止进程 非终端来的停止信号
SIGTSTP 停止进程 终端来的停止信号
SIGCONT 忽略信号 继续执行一个停止的进程
SIGURG 忽略信号 I/O紧急信号
SIGIO 忽略信号 描述符上可以进行I/O
SIGCHLD 忽略信号 当子进程停止或退出时通知父进程
SIGTTOU 停止进程 后台进程写终端
SIGTTIN 停止进程 后台进程读终端
SIGXGPU 终止进程 CPU时限超时
SIGXFSZ 终止进程 文件长度过长
SIGWINCH 忽略信号 窗口大小发生变化
SIGPROF 终止进程 统计分布图用计时器到时
SIGUSR1 终止进程 用户定义信号1
SIGUSR2 终止进程 用户定义信号2
SIGVTALRM 终止进程 虚拟计时器到时
————————————————
版权声明:本文为CSDN博主「hnzmdzcm」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chenming_zhang/article/details/7824502
2.信号处理的方式
(1)信号的处理方法有3种:忽略,捕捉,默认动作。
(2)关于忽略信号:
(忽略信号)有两种信号不能被忽略:SIGKILL,SIGSTOP。因为他们向内核和超级用户提供了进程停止和终止的可靠方法。如果这两个信号可以被忽略,那么可能引起以下问题:假如有恶意进程(病毒)在运行,普通用户或超级用户会发送SIGKILL来杀掉这个进程,如果此时SIGKILL信号被忽略,那么恶意程序将变成没人能管理的程序,因此带来无法估计的后果。内核设计者在设计之初就这么设计的,相当于开了上帝视角给自己留一手。
(3)关于捕捉信号:
(捕捉信号)是一种信号处理函数,和单片机的中断处理函数很像。开发人员写一个信号处理函数,当指定信号出现时,通知内核,然后内核调用开发者自定义的服务函数。
(4)关于默认动作
对于每一个信号来说,系统都有一个默认的处理动作,当该信号发生时系统会自动执行。系统处理大多数信号的动作是杀死该进程。其他的处理动作可以通过指令man 7 signal查看。
3.如何使用信号?
(杀死信号)kill -9 进程id号
(杀死信号的第二种方法)kill -SIGKILL 进程id号
(查看进程id号)ps
或
4.信号除了被杀死,还有哪些意义?
答:信号是实现异步通信的手段(利用捕捉信号实现异步通信)。
5.怎样自定义信号处理函数?
(相关api)
#include <signal.h>
typedef void (*sighandler_t)(int);//这是声明的一种指针类型
sighandler_t signal(int signum, sighandler_t handler);
6.实验
(1)实验一:自定义信号处理函数
#include"stdio.h"
#include "signal.h"
void handler(int signum)
{
printf("get signum =%d\n",signum);
switch(signum)
{
case 1:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
break;
}
}
int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);
return 0;
}
(2)实验二:写一个demo,运行一个可执行文件来杀死指定进程
注意:要使用到int main(int argc,char **argv)来传递参数;
int main(int argc,char* argv[]) 也可以写成 int main(int argc,char** argv)。
argc表示程序运行时发送给main函数的命令行参数的个数(包括可执行程序以及传参)。
argv[]是字符指针数组,它的每个元素都是字符指针,指向命令行中每个参数的第一个字符。
argv[0]指向可执行程序。
argv[1]指向可执行程序后的第一个字符串。
argv[2]指向可执行程序后的第二个字符串 。
argv[3]指向可执行程序后的第三个字符串 。
argv[argc]为NULL。
编写了以下c文件(功能:执行可执行文件,后面跟2个参数,就能杀死指定进程(这两个参数分别是信号值9和进程号))
#include "stdio.h"
#include "signal.h"
#include "sys/types.h"
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
printf("signum = %d,pid = %d\n",signum,pid);
kill(pid,signum);
printf("send signal ok\n");
return 0;
}
如图:先执行第一个demo:./a.out(执行这个demo,过一会用demo2来杀死他);
接着查看该进程号为37754;
然后执行"./killProcess 9 37754"杀死37754进程;
可以看到终端上显示Killed;
atoi();是ascii转整形的函数;
kill:发送信号,指定将信号发送给某个进程,常用来杀掉进程,可以通过ps、top命令来查看进程(这是入门级别的发送信号方式);
kill(pid,signum);也可以写成
char cmd[128]={0};
sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
(3)忽略一个指定信号
使用SIG__IGN
#include"stdio.h"
#include "signal.h"
int main()
{
signal(SIGINT,SIG_IGN);
signal(SIGKILL,SIG_IGN);
signal(SIGUSR1,SIG_IGN);
while(1);
return 0;
}
运行结果 如图:
键盘输入ctrl+c无效,说明忽略“ctrl+c”信号成功(ctrl+c信号对应的是signum =2 );
键盘输入“kill -9 4271”后,程序退出运行;说明 无法忽略SIGKILL信号
二、信号如何携带消息(入门级+高级)
1.概述
入门级:(只有信号,没有消息)
kill(发送信号);
signal(接收信号);
高级:(既有信号,又有消息)
需要探讨以下几个问题:
接收消息:
问题1:来信号时怎么处理(怎么绑定)?----sigaction
问题2:来信号时如何读出消息?
问题3:信号如何携带消息?
发送消息:
问题4:用什么发送?---sigqueue
问题5:怎么放入消息?
2.API的介绍
自己描述的不清晰,可直接参考这篇博客:linux C编程10-信号_siginfo_t_邻居家的小南瓜的博客-CSDN博客信号是事件发生时对进程的通知机制,也可以把它称为软件中断。信号与硬件中断的相似之处在于能够打断程序当前执行的正常流程, 其实是在软件层次上对中断机制的一种模拟。 大多数情况下,是无法预测信号达到的准确时间,所以,信号提供了一种处理异步事件的方法。Linux 内核定义了 31 个不同的实时信号,信号编号范围为 34~64,使用 SIGRTMIN 表示编号最小的实时信号,使用 SIGRTMAX 表示编号最大的实时信号,其它信号编号可使用这两个宏加上一个整数或减去一个整数。实时信号较之于...https://blog.csdn.net/qq_37932504/article/details/118366865
(1)sigaction(来信号时怎么处理)
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
三个参数的作用:信号的值,信号处理函数,备份原有信号(当该指针为空时无数据,指针非空时有数据)
参数 signum: 需要设置的信号,除了 SIGKILL 信号和 SIGSTOP 信号之外的任何信号。
参数 act: act 参数是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构,
该数据结构描述了信号的处理方式;如果参数 act 不为 NULL,则表示需要为信号设置新的处理方式;
如果参数 act 为 NULL,则表示无需改变信号当前的处理方式。
oldact: oldact 参数也是一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构。
如果参数oldact 不为 NULL, 则会将信号之前的处理方式等信息通过参数 oldact 返回出来;
如果无意获取此类信息,那么可将该参数设置为 NULL。
返回值: 成功返回 0;失败将返回-1,并设置 errno。
struct sigaction
{
void (*sa_handler)(int);//函数指针
void (*sa_sigaction)(int signum, siginfo_t *info, void *context);//函数指针(用于绑定某些参数)
sigset_t sa_mask;//结构体
int sa_flags;
void (*sa_restorer)(void);
};
理解:
sigaction可以为某些信号设置回调,这些设置都在struct sigaction中进行
(1)void (*sa_handler)(int)信号处理函数;
其取值如下:
SIG_IGN 忽略该信号;
SIG_DEL 执行系统默认动作;
注意:如果配置这个回调,则效果和signal一致。无法接收消息!!!!!!!!!
(2)void (*sa_sigaction)(int signum,siginfo_t *info,void *context)也是信号处理函数,
当sa_flag为SA_SIGFINFO时将使用该函数。
注意:配置这个回调时,才能接收消息!!!!!!!!!!!!!!!
3个参数:
参数1 :signum是信号的值。
参数2 :一个结构体,后面会描述;
参数3 : void *context;//空或非空用来判断是否有内容
上述两个信号函数是互斥的,一次只能使用两个字段中的一个!!!!!!!!!!!!!!!
因此在一些实现中,使用union来存放这两个函数。
(3)sigset_t sa_mask 是一个sigset,可以在信号处理函数执行的时候起到阻塞的作用。
(sa_mask 信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定信号;);
sigset_t sa_mask可以不配置,不配置时默认为阻塞。
(4)int sa_flags 这是一个选项字段。
比如说:
SA_RESART 使被信号打断的系统调用自动重新发起(己废弃);
SA_NOCLDSTOP 使父进程在它的子进程暂停或继续运行时不会收到SIGCHLD信号;
SA_NOCLDWAIT 使父进程在它的子进程退出时不会收到SIGCHLD信号,
这时子进程如果退出也不会成为僵尸进程;
SA_NODEFER 使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号;
SA_RESETHAND 信号处理之后重新设置为默认处理方式;
SA_SIGINFO 使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数!!!!!!
(5)sa_restorer:该成员已过时,不要再使用了。
siginfo_t结构体的原型如下:
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 */
long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address(since Linux 2.6.32) */
void *si_call_addr; /* Address of system call instruction(since Linux 3.5) */
int si_syscall; /* Number of attempted system call(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call(since Linux 3.5) */
}
(2)如何发送消息?---sigqueue
(sigqueue函数原型)
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
参数 pid(解决了发给谁的问题): 指定接收信号的进程对应的 pid,将信号发送给该进程。
参数 sig(解决了发送什么信号的问题): 指定需要发送的信号。与 kill()函数一样,也可将参数 sig 设置为 0,用于检查参数 pid 所指定的进程是否存在。
参数 value(发送的消息,消息可以是整型或char型): 参数 value 指定了信号的伴随数据, union sigval 数据类型。
3.编程实验
功能:
写一个接收处理的c文件(sigaction_demo.c)和一个发送信号的c文件(sigqueue_demo.c);(sigaction_demo.c可以打印信号值和携带的消息;sigqueue_demo.c可以发送信号)
编译(分别生成可执行文件sigaction和sigqueue);
先执行接收程序sigaction,查看该进程号为xxxxxx(比如:39962);
再执行发送程序./sigqueue 10 xxxxx(比如:39962);
观察打印信息;
思路:
首先 考虑接收端的实现:
接收端利用sigaction();
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
第一个参数配置为:SIGUSR1(用户自定义的)
第二餐参数是1个结构体:
struct sigaction
{
void (*sa_handler)(int);//函数指针
void (*sa_sigaction)(int, siginfo_t *info, void *context);//函数指针(用于绑定某些参数)
sigset_t sa_mask;//结构体
int sa_flags;
void (*sa_restorer)(void);
};
1)不使用第一个回调;(因为使用它无法接收消息)
2)使用第二个回调;(注意sa_flags配置为SA_SIGINFO,该回调才被用来接收消息)
3)sa_mask可以不配置,不配置时默认为阻塞();
4)sa_flags要配置为SA_SIGINFO(因为使用第二个回调接收消息,则sa_flags要配置为SA_SIGINFO)
5)sa_restorer:该成员已过时,不要再使用了。
第三个参数,无意获得此类信息,所以配置为NULL
所以接收端的代码如下:
#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;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO ;
sigaction(SIGUSR1,&act,NULL);
while(1);
}
接下来考虑发送端的实现
发送端的实现依赖sigqueue
设计思路如下:
指定信号值为10;
pid依赖linux指令“ps -aux | grep xxxx”来查看(比如发送端的可执行文件叫“a.out”,则ps -aux | grep a.out来查看pid)
信号携带的值value,可以随便定义(因为是实验)。项目中要看具体的应用了。
代码如下:
#include "stdio.h"
#include "signal.h"
int main(int argc,char **argv)
{
int signum;
int pid;
signum = atoi(argv[1]);
pid = atoi(argv[2]);
union sigval value;
value.sival_int = 100;
sigqueue(pid,signum,value);
printf("done\n");
return 0;
}
~
运行结果如图:
信号值为10;携带的消息是100;(注意可以在sigaction_demo.c的函数中添加printf(“sender_id = %d\n”,info->si_pid);来打印发送者的pid)