1.sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
参数分析:
1.signo:指定信号的编号
2.act输入型参数,根据act修改信号的处理动作
3.oact输出型参数,通过oact传出该信号原来的处理动作
返回值:成功返回0,失败返回-1
act和oact指向sigaction结构体
测试:对函数指针结构体的存入
1 #include<iostream>
2 #include<cstring>
3 #include<unistd.h>
4 #include<signal.h>
5 using namespace std;
6 void handler(int signo)
7 {
8 cout<<"catch a signal,signal number:"<<signo<<endl;
9 }
10 int main()
11 {
12 struct sigaction act,oact;
13 memset(&act,0,sizeof(act));//初始化
14 memset(&oact,0,sizeof(oact));
15 act.sa_handler=handler;
16 sigaction(2,&act,&oact);
17 int sign=20;
18 while (sign>0)
19 {
20 cout<<"i am a process:"<<getpid()<<endl;
21 sleep(1);
22 sign--;
23 /* code */
24 }
25 return 0;
26 }
memset
常用于初始化变量、清空缓冲区或者设置特定模式的字节序列
问题1:pending位图,什么时候从1->0
执行信号捕捉方法之前,先清0,在调用,来使信号再次产生时,当前信号完成了再执行下一个,禁止不断嵌套式的捕捉
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,提交到block表中
//循环模拟实现
void handler(int signo)
{
cout << "catch a signal, signal number : " << signo << endl;
while (true)
{
PrintPending();
sleep(1);
}
}
问题2:信号被处理的时候,对应的信号也会被添加到block表中,防止信号捕捉被嵌套调用
正在处理某条信号时,会屏蔽该信号(以2举例),那么如何在同时屏蔽其他信号呢
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, 1);
sigaddset(&act.sa_mask, 3);
sigaddset(&act.sa_mask, 4);
act.sa_handler = handler; // SIG_IGN SIG_DFL
sigaction(2, &act, &oact);
如果 在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需 要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字
2.可重入函数:可以被多个线程同时使用而不会导致崩溃,因此具有以下特性:
1.不修改静态数据
2.不调用不可调用的函数
3.不返回指向静态数据的指针
为什么链表头插是不可重入的函数?举例分析
首先调用进程1,在哨兵位head后插入一个节点1,此时在另一个窗口调用进程2,往head后面插入一个节点2,此时head->next会在两个进程中指向不同的节点,但是head->next只能指向一个节点,所以进程2创建的节点会覆盖进程1创建的节点,导致程序出现问题,出现节点丢失
目前我们接触的函数基本上都是不可重入的函数,接下来我们看一下这类函数的标志位
1.调用了malloc和free,因为malloc也是调用全局链表管理堆,而STL也不可以,涉及了很多指针的变换和扩容(但凡涉及到指针就要思考多进程的情况)
2.调用了标准I/O库函数,很多实现都使用了不可重入的方式使用了全局数据结构
3.volatile
int flag = 0;
//volatile int flag=0;
void handler(int signo)
{
cout << "catch a signal: " << signo << endl;
flag = 1;
}
int main()
{
signal(2, handler);
// 在优化条件下, flag变量只在读取,可能被直接优化到CPU内的寄存器中
while(!flag); // flag 0, !falg 真 逻辑运算
cout << "process quit normal" << endl;
return 0;
}
ctrl c使程序捕捉到信号2,再ctrl z结束程序 ,$^后面跟上-O(这是哦)和0,1,2,3表示优化等级
在优化条件下, flag变量只在读取,可能被直接优化到CPU内的寄存器中
while(!flag);
// flag 0, !falg 真 逻辑运算
看看豆包的分析(谁言豆族无大帝):
这个程序的主要目的是在一个无限循环中等待一个信号(在这个例子中是信号2,即SIGINT
的别名,但通常用于SIGINT
的是信号编号1,这里可能是一个示例的简化或错误)。当接收到信号时,信号处理函数handler
会被调用,它将全局变量flag
设置为1,从而理论上应该使主循环结束,并输出“process quit normal”。
然而,这个程序在优化级别较高(如-O2
或-O3
)的编译器设置下可能会遇到问题。问题的根源在于flag
变量的访问。在高度优化的编译模式下,编译器可能会认为flag
变量在循环体内没有发生变化(因为它只在信号处理函数中修改,而信号处理函数是异步的),因此可能会将flag
的值优化到寄存器中,或者直接认为循环条件始终为真,从而生成一个无限循环的代码
因为优化,导致我们的内存不可见了!
这时添加 volatile 关键字:防止编译器过度优化,保持内存的可见性!
volatile int flag=0;
4.SIGCHLD信号
子进程退出后会向父进程发送SIGCHLD信号(17)号信号
基于信号捕捉,对信号进行回收,将17号信号的捕捉放入header中
#include<iostream>
2 #include<unistd.h>
3 #include<signal.h>
4 #include <sys/wait.h>
5 using namespace std;
6 void handler(int signo)
7 {
8 pid_t rid;
9 rid = waitpid(-1, nullptr, WNOHANG);//实现非阻塞调用
10 cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
11 }
12
13 int main()
14 {
15 signal(17,handler);
16 for (int i = 0; i < 10; i++)
17 {
18 pid_t id = fork();
19 if (id == 0)
20 {
21 while (true)
22 {
23 cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
24 sleep(5);
25 break;
26 }
cout << "child quit!!!" << endl;
28 exit(0);
29 }
30 sleep(1);
31 }
32 // father
33 while (true)
34 {
35 cout << "I am father process: " << getpid() << endl;
36 sleep(1);
37 }
38
39 return 0;
40 }
~
子进程进行等待的时候,可以基于信号进行等待
等待的好处:
1.获取子进程的退出状态,释放僵尸进程
2.在一般情况下,父进程一般比子进程后退出
还是要调用 wait 接口,father 必须保证自己是一致在运行的-->把子进程等待写入信号捕捉函数中
多个子进程如何正确的回收:while循环加非阻塞方案
void handler(int signo)
{
sleep(5);
pid_t rid;
while ((rid = waitpid(-1, nullptr, WNOHANG)) > 0)//非阻塞方案
{
cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
}
}
随机休眠时长进行测试:
#include <cstdlib> // 用于rand()和srand()
#include <ctime> // 用于time()
srand(time(nullptr)); // 初始化随机数生成器
if (id == 0)
{
int random_sleep_time = rand() % 10 + 1; // 生成1到10之间的随机数
cout << "Child process " << getpid() << " will sleep for " << random_sleep_time << " seconds." << endl;
sleep(random_sleep_time);
cout << "Child process " << getpid() << " is quitting." << endl;
exit(0);
}
子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略( SIG_DFL -> action -> IGN)事实上,由于UNIX 的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作 置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉 signal(17,SIG_IGN);