一、讨论范围
- 编号34以上的信号叫做实时信号,不在讨论范围内
- 上图中信号的产生方式和默认处理动作可以通过
man 7 signal指令来查看
二、信号的结构
1.信号的本质
task_struct内部存在一个位图结构,用int表示:uint32_t signals;
0000 0000 0000 0000 0000 0000 0000 0000
所谓的发送信号其实就是修改特定进程信号位图的特定比特位
- 比特位的位置就是信号的编号
- 比特位的内容表示1是否收到信号
2.预备知识
2.1 如何发送信号
kill options PID
其中,PID是进程的ID,可以是单个进程的ID,也可以是多个进程的ID,用空格分隔
eg:
给ID为1234的进程发送2号信号
kill -2 1234
该处使用的url网络请求的数据。
2.2 如何查看后台程序
ps aux | grep 名称关键词
eg:
查找名称与“chrome”相关的后台程序
ps aux | grep chrome
三、改变信号默认触发方式的两个函数
举个例子,在我们不小心写出死循环,或者故意写出死循环以便于使程序持续运行的时候,通常会在命令行输入“Ctrl+C”来停止命令行的无限刷屏,而我们输入的“Ctrl+C”,实际上就是给我们提到的死循环程序发送了2号信号;
下面是两个函数,它们的功能可以让一个程序在接收到2号信号时继续运行,而不是就此退出
1.signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能为“在收到signum号信号的时候执行handler函数”
eg:signal(2,handler) signal(SIGQUIT,handler)
两种写法都可以,signum可以是除了SIGKILL和SIGSTOP以外的任何有效信号
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigcb()
{
printf("SIGINT\n");
}
int main()
{
signal(SIGINT,(sighandler_t)sigcb);
while(1)
{
printf("m alive\n");
sleep(2);
}
return 0;
}
运行结果:
按下“Ctrl+C”也会有类似的结果
自己想试一下的话后面可以用“Ctrl+Z”结束这个程序,向它发送9号信号也可以,因为SIGKILL和SIGSTOP这个两个信号不能被系统忽略,也不能被改变默认处理动作
man一下之后可以发现使用文档表示这个函数已经过时了,而且不同系统版本下signal函数的功能也可能不一样
2.sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
也是用来改变进程对于已接收信号做出的行动;
signum可以是任何除了SIGKILL和SIGSTOP以外的有效信号;
如果act不是NULL,则从act安装信号signum的新动作。如果oldact不是NULL,先前的操作保存在oldact中。
所以为了使用这个函数,struct sigaction的结构也必须了解
The sigaction structure is defined as something like:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
- sa_handler就是接收到signum信号以后被调用的新方法,使用的时候先声明一个这样的结构体,然后再把函数指针指向你想要的函数就好了
- 不要同时给sa_handler和sa_sigaction同时赋值
- sa_restorer这个成员已经过时了,别用
- 如果sa_flags被指定为SA_SIGINFO,收到signum对应的信号以后就执行sa_sigaction指向的函数(sa_handler这时候就没用了),这个函数把int作为其第一个参数,一个指向siginfo_t的指针作为其第二个参数,并将指向ucontext_t(强制转换为void *)的指针作为其第三个参数。(通常,handler函数不使用第三个参数)
- sa.sa_flags = 0; 表示将 sa_flags 字段设置为零,表示使用默认的标志位,即默认的信号处理行为。
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sigcb(int m)//为了不报错所以弄成了和结构体中指针相匹配的类型
{
printf("SIGINT\n");
}
int main()
{
struct sigaction m;
m.sa_handler=sigcb;
sigaction(SIGINT,&m,NULL);
while(1)
{
printf("m alive\n");
sleep(1);
}
return 0;
}
运行结果:
四、信号集操作函数和相关指令
这里的“信号集操作函数”指的就是用来操作signal mask的函数;
在 Unix 系统中,每个进程都有一个信号掩码signamask,用于管理它希望阻塞或允许传递的信号。sigprocmask 函数允许程序员操作这个信号掩码,以便选择性地阻塞或解除阻塞特定的信号。
#include <signal.h>
用起来也是事先声明好一个sigset_t类型的信号集``
0.sigprocmask
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
oldset用来存储set的前一个值,也就是说如果oldset为非null,则信号掩码的前一个值存储在oldset中。
如果set为NULL,则信号掩码不变(忽略how),信号掩码的当前值仍然在oldset中返回(如果oldset不是的话)
参数how的参数:
1.sigemptyset
int sigemptyset(sigset_t *set);
函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含 任何有效信号。
2.sigaddset
int sigaddset (sigset_t *set, int signo);
添加一个signo信号到set这个信号集里面,使signo这个信号变成可以接收到的有效信号
3.sigdelset
int sigdelset(sigset_t *set, int signo);
把set信号集里面的signo信号删了
4.sigfillset
int sigfillset(sigset_t *set);
函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示 该信号集的有效信号包括系
统支持的所有信号。
示例代码:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
sigset_t tmp;
sigemptyset(&tmp);
sigaddset(&tmp,2);
sigaddset(&tmp,40);
sigprocmask(SIG_BLOCK,&tmp,NULL);
while(1)
{
printf("m alive\n");
sleep(1);
}
return 0;
}
把2号信号和40号信号都屏蔽了,然后再向test3发送这两个信号,发现什么事也没有发生:
文章来源:https://www.toymoban.com/news/detail-798839.html
总结
之前那篇讲共享内存的博客相对来说数据比较好,原因可能是我是读了man的使用手册以后按我理解的逻辑顺序讲的,而且我个人认为使用文档里对函数的说明比博客和百度要全面一些
有一些函数因为使用文档太长了所以我只挑了比较重要的部分来讲,如有遗漏欢迎指出
感谢支持文章来源地址https://www.toymoban.com/news/detail-798839.html
到了这里,关于【Linux】进程信号,相关函数的简单实用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!