进入官方网站浏览器,锦州网站建设新闻,越南网站怎么做,简单网站建设设计个人主页~ 进程信号的捕捉处理 一、信号捕捉处理的概述1、信号捕捉处理全过程2、用户态和内核态的区别#xff08;一#xff09;用户态#xff08;二#xff09;内核态#xff08;三#xff09;用户态与内核态的切换#xff08;四#xff09;硬件条件 二、再谈进程地址…
个人主页~ 进程信号的捕捉处理 一、信号捕捉处理的概述1、信号捕捉处理全过程2、用户态和内核态的区别一用户态二内核态三用户态与内核态的切换四硬件条件 二、再谈进程地址空间操作系统本质 三、系统调用函数四、其他补充内容1、可重入函数2、volatile关键字 一、信号捕捉处理的概述
1、信号捕捉处理全过程 如果信号的处理动作是用户自定义函数在信号递达时就调用这个函数这称为捕捉信号这个我们前面说过但是我们的过程是比较复杂的首先我们在执行主控制流程的某条指令时因为系统调用等原因会进入内核然后内核处理完成后发送信号如果信号的处理动作是自定义的信号处理函数就回到用户区执行信号处理函数执行完之后因为信号处理函数的特殊性它要再次进入内核区然后回到用户模式继续执行
我们在用户区和内核区来回切换的时候操作系统负责做我们的身份用户身份和内核身份切换工作用户态陷入内核态是通过汇编指令int 80完成的
在进程从内核态返回用户态时进行信号的检测和处理
并且main函数和自定义信号捕捉处理函数使用不同的堆栈空间它们之间不存在调用和被调用的关系
2、用户态和内核态的区别
用户态和内核态是操作系统中CPU的两种运行状态它们在访问权限、资源使用等方面存在显著差异以下是对这两种状态的标准解释
一用户态
用户态是操作系统为普通用户程序提供的一种运行模式在用户态下运行的程序拥有较低的特权级别只能访问受限的系统资源和执行特定的操作大部分用户编写的应用程序如文本编辑器、浏览器等都是在用户态下运行的
用户态程序只能访问自己的内存空间不能直接访问系统的核心资源如硬件设备、操作系统内核的数据结构等并且只能执行一部分指令一些具有高风险或对系统影响较大的指令如修改系统时钟、控制硬件中断等是被禁止执行的 由于用户态程序的权限受到限制即使程序出现错误如内存越界、死循环等也不会对整个操作系统造成严重影响只会影响到该程序自身
二内核态
内核态是操作系统内核运行的模式具有最高的特权级别操作系统内核负责管理系统的核心资源如内存、进程、文件系统、设备驱动等因此需要在高特权的内核态下运行
内核态程序可以访问系统的所有资源包括硬件设备、内核数据结构、所有进程的内存空间等并且可以执行所有的 CPU 指令包括那些在用户态下被禁止执行的特权指令这些特权指令可以用于实现系统的关键功能如进程调度、内存管理、中断处理等 由于内核态程序具有最高的权限一旦内核态程序出现错误可能会导致整个操作系统崩溃或出现严重的系统故障
三用户态与内核态的切换
在操作系统的运行过程中程序需要在用户态和内核态之间进行切换以完成不同的任务常见的切换场景包括
系统调用当用户态程序需要访问系统资源或执行特权操作时会通过系统调用如 open、read、write 等请求操作系统内核的服务在执行系统调用时程序会从用户态切换到内核态由操作系统内核来处理请求处理完成后再切换回用户态中断处理当系统发生硬件中断如时钟中断、键盘中断等或软件中断如异常、陷阱等时CPU 会自动从用户态切换到内核态由操作系统内核来处理中断事件处理完成后根据情况决定是否切换回用户态
但是操作系统是不相信用户的所以用户态和内核态进行切换的时候是操作系统完成的
四硬件条件
在 CPU 中有一个ecs寄存器它的后两个bit位就标记了当前是处于用户态还是内核态其中 00 表示处于内核态11 表示处于用户态int 80 指令本质上就是将 11 修改成 00
二、再谈进程地址空间 我们再来拿出我们的老图我们知道每个进程都有一个PCB然后PCB中有一个结构体叫做mm_struct又被称为进程地址空间就是我们常说的虚拟地址虚拟地址通过页表和MMU映射到物理内存上可以说页表就是虚拟内存和物理内存之间的桥梁每个进程都会有一个页表但是这是对于用户区空间来说的我们的内核空间实际上也是有一个内核页表这个内核页表是唯一的内核空间中的数据也是唯一的我们不同的进程它们用户区空间的代码和数据不同但是内核空间的数据一定相同所有进程共享一份内核空间以及一份内核页表
对于进程来说去调用系统调用接口就是在我自己的地址空间中执行的对于操作系统来说任何一个时刻都有进程执行我们想执行操作系统的代码就可以随时执行
操作系统本质
操作系统的本质就是一个基于时钟中断的一个死循环这里的时钟中断与STM32中的《TIM定时器》学习STM32的可以点开看一下没有学习就算了不影响理解类似都是每隔很短的时间向计算机发送时钟中断操作系统在收到时钟中断后就去中断向量表中执行相应的进程调度之类的方法
三、系统调用函数
sigaction用于设置或检查信号处理行为的系统调用函数
#include signal.h
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact); 返回值成功返回0失败返回-1 signo需要处理的信号编号 act输入型参数指向struct sigaction结构体用于指定新的信号处理动作 oact输出型参数指向struct sigaction结构体用于保存原来的信号处理动作
其中结构体sigaction是这样的
struct sigaction {void (*sa_handler)(int); // 信号处理函数指针或SIG_IGN忽略信号、SIG_DFL使用默认处理void (*sa_sigaction)(int, siginfo_t *, void *); //另一种信号处理函数指针用于处理带附加信息的信号sigset_t sa_mask; //在信号处理函数执行期间需要阻塞的信号集int sa_flags; //控制信号处理行为的标志位void (*sa_restorer)(void); //已弃用通常设为 NULL
};其中我们主要研究两个成员sa_handlersa_mask
这里我们说一个结论就是信号处理函数在处理信号时是不再接受新的信号的在该信号处理函数被调用时在刚要调用的某个时间内核会自动将当前信号加入到进程的信号屏蔽字中这就可以实现在处理某个信号的过程中不会被这个信号反复打扰如果这个信号再次产生它会被阻塞到当前函数处理结束如果在当前信号被阻塞以外还想要屏蔽其他的一些信号则动用sa_mask说明要额外屏蔽的信号
#include iostream
#include signal.h
#include cstring
#include unistd.husing namespace std;void PrintPending()
{sigset_t set;//将当前被阻塞且处于未决状态的信号集存储到set信号集中输出型参数sigpending(set);//打印set位图即pending位图for (int signo 31; signo 1; signo--){if (sigismember(set, signo))cout 1;elsecout 0;}cout endl;
}void handler(int signo)
{//收到2号信号打印一句收到了然后循环调用PrintPendingcout catch a signal, signal number : signo endl;while (true){PrintPending();sleep(1);}
}int main()
{//创建并初始化act和oactstruct sigaction act, oact;memset(act, 0, sizeof(act));memset(oact, 0, sizeof(oact));//设置sa_mask为全0sigemptyset(act.sa_mask);//设置信号屏蔽sigaddset(act.sa_mask, 1);sigaddset(act.sa_mask, 3);sigaddset(act.sa_mask, 4);//设置信号处理函数为handleract.sa_handler handler; // SIG_IGN SIG_DFL//需要处理的就是2号信号sigaction(2, act, oact);while (true){cout I am a process: getpid() endl;sleep(1);}return 0;
}正常情况下我们没有发送任何信号1号信号会将进程终止当我们发送2号信号sigaction函数将信号捕捉后我们进入到handler函数开始打印全0的pending位图此时属于信号处理函数处理信号的过程然后我们发送1234号信号因为134号信号我们提前设置进sa_mask当中了所以我们在发送信号的时候对应的比特位就会由0置1表示阻塞通过
四、其他补充内容
1、可重入函数 这是一个正常的单链表node1和node2是两个待插入节点其中我们要头插node1其方法就是将next指针指向现在的头节点再将自己赋值给头节点
insert(struct Node* node)
{node-next head;head node;
}但是在node1-next head;执行完毕后还没来得及执行head p;突然来了一个信号这个信号刚好被捕捉了执行自定义动作刚好自定义动作是将node2头插
void handler(int signo)
{insert(node2);
}
insert(struct Node* node)
{node-next head;head node;
}执行完这个信号处理函数后再返回执行main函数中的代码 这样一来我们实际上的代码就变成了
node1-next head;
node2-next head;
head node2;
head node1;这样上面node1是头插进去了但是node2是不可能通过单链表的接口访问到了这个节点就丢失了这样就会导致内存泄漏
如果一个函数像上面一样被重复进入的情况下出错或者可能出错就叫做不可重入函数否则就叫做可重入函数我们学习到的大部分函数都是不可重入函数因为只要是涉及指针改指向的问题基本上都是不可重入的
2、volatile关键字
volatile是一个类型修饰符用于告知编译器它修饰的内容拒绝优化
该程序在收到2号信号之前一直在while循环中啥也不干收到2号信号执行handler打印设置flag为1然后继续while判断从这里开始就变得不一样了 我们可以看到我们编译的方法加了-O1选项这个叫做优化一共有-O0 -O1 -O2 -O3四种种优化等级其中O0是无优化
这是加了volatile的关键词产生的结果按下ctrlc打印执行后flag置1while判断继续向后执行打印程序结束 这是不加volatile的关键词产生的结果按下ctrlc打印执行后flag置1怎么不往下执行了呢我再次按下ctrlc打印再次执行 这里就是优化的问题优化其实是CPU管理资源的一种方式优化后CPU在第一次读取flag的时候将其加载到CPU寄存器中handler结束后进行while的判断时直接判断CPU上的这个flag不再去物理内存当中寻找因为这样的寻找是耗费资源的加了volatile关键字后不再产生优化在判断时判断的就是物理内存中的flag值
所以涉及到要随时了解某些变量的状态的时候这些变量在优化的时候最好用volatile修饰一下 今日分享就到这了~