回顾:[Linux][OS][信号的保存和处理]
信号捕捉
1.sigaction
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
参数:
-
- signo:指定信号的编号
- act:输入型参数,根据act修改该信号的处理动作
- oact:输出型参数,通过oact传出该信号原来的处理动作
返回值:成功返回0,出错则返回- 1
act和oact指向sigaction结构体:
测试:
对函数指针结构体的存入
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<signal.h>
using namespace std;
void handler(int signo)
{
cout<<"catch a signal,signal number:"<<signo<<endl;
}
int main()
{
struct sigaction act,oact;
memset(&act,0,sizeof(act));//初始化
memset(&oact,0,sizeof(oact));
act.sa_handler=handler;
sigaction(2,&act,&oact);
while (true)
{
cout<<"i am a process:"<<getpid()<<endl;
sleep(1);
/* code */
}
return 0;
}
小 tip:memset
常用于初始化变量、清空缓冲区或者设置特定模式的字节序列。
- 问题一: 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.可重入函数
验证:链表的插入是不可重入的
- insert 函数被
- 节点丢失,内存泄露
如果一个函数,被重复进入的情况下,出错了或者可能出错,就是不可重入函数,否则就叫可重入函数
目前我们学到的大部分函数都是多执行流下的不可重入的:
- 调用了malloc或free,因为malloc也是用全局链表来管理堆的。(例如 STL 不可以,涉及了很多指针的变换和扩容)
- 调用了标准I/O库函数。标准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;
}
编译优化的设置:g++ -o $@$^ -O1
(0 1 2 3 对优化等级的选择)
- 在优化条件下, flag变量只在读取,可能被直接优化到CPU内的寄存器中
while(!flag);
// flag 0, !falg 真 逻辑运算
改 1 优化后为什么就无法退出循环了?
因为优化,导致我们的内存不可见了!
这时添加 volatile 关键字:防止编译器过度优化,保持内存的可见性!
volatile int flag=0;
4.SIGCHLD 信号
子进程退出的时候,不是静悄悄的退出
子进程在退出的时候,会主动的向父进程发送 SIGCHLD(17)信号
怎么证明?基于信号捕捉,来对信号进行回收,将对 17 的回收加入到 handler 方法中
void handler(int signo)
{
pid_t rid;
rid = waitpid(-1, nullptr, WNOHANG);//实现非阻塞调用
cout << "I am proccess: " << getpid() << " catch a signo: " << signo << "child process quit: " << rid << endl;
}
int main()
{
signal(17,handler);
for (int i = 0; i < 10; i++)
{
pid_t id = fork();
if (id == 0)
{
while (true)
{
cout << "I am child process: " << getpid() << ", ppid: " << getppid() << endl;
sleep(5);
break;
}
cout << "child quit!!!" << endl;
exit(0);
}
sleep(1);
}
// father
while (true)
{
cout << "I am father process: " << getpid() << endl;
sleep(1);
}
return 0;
}
来进行调用测试:
得出子进程在进行等待的时候,我们可以基于信号的方式进行等待
等待的好处:
- 获取子进程的退出状态,释放子进程的僵尸
- 虽然不知道父子谁先运行,但是我们清楚,一定是 father 最后退出
还是要调用 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);