网站开发可行性研究报告,湖州网站开发区火炬手,韩国最新新闻事件,程序员接外包平台目录
一、信号的阻塞
1.1 信号在内核中的保存方式
1.2 sigset_t信号集
#xff08;1#xff09;信号集操作
#xff08;2#xff09;sigprocmask函数
#xff08;3#xff09;sigpending函数
二、信号的处理
2.1 用户态和内核态
2.2 重谈进程地址空间
三、信号…目录
一、信号的阻塞
1.1 信号在内核中的保存方式
1.2 sigset_t信号集
1信号集操作
2sigprocmask函数
3sigpending函数
二、信号的处理
2.1 用户态和内核态
2.2 重谈进程地址空间
三、信号的捕捉
sigaction函数
前文
【Linux】进程信号上-CSDN博客文章浏览阅读729次点赞29次收藏27次。本篇文章围绕Linux中的进程信号展开讲解包含信号的概念、信号的产生等内容https://blog.csdn.net/Eristic0618/article/details/142900955 一、信号的阻塞
1.1 信号在内核中的保存方式
前面提到进程在接收到普通信号时并不是立即进行处理的
信号产生但未被处理的状态称为信号未决Pending执行信号的处理动作称为信号递达Delivery信号产生但可以被阻塞Block
假设一个信号被阻塞则这个信号将一直保持未决的状态直到进程解除对该信号的阻塞才能执行递达动作
进程的阻塞和信号的阻塞是两回事信号阻塞类似于屏蔽了某个信号
信号的阻塞与忽略也是不同的信号被阻塞后会一直存在而忽略是对信号的一种处理动作
进程的PCB中有三个表分别是block表、pending表和handler表其中
block表是一个位图又叫做阻塞信号集或信号屏蔽字记录了某个信号是否被屏蔽如果对应位置为0表示该信号未被屏蔽为1则被屏蔽pending表也是位图用于记录进程收到了哪些信号handler表是一个函数指针数组用于记录每种信号的处理方法 三个表一起看就能知道一个信号是否被屏蔽是否被收到处理方法是什么
我们通过signal函数捕捉某个信号实际上就是把用户提供的函数指针覆盖到handler表的对应位置 信号的处理方式类型为__sighandler_t实际定义为
typedef void(*__sighandler_t)(int);
系统中还定义了一些信号的默认处理方法
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)
其中SIG_DFL表示收到信号后终止进程SIG_IGN表示忽略某个信号 1.2 sigset_t信号集
1信号集操作
sigset_t是一个数据类型称为信号集本质上是一个位图
我们的block表和pending表的类型都是sigset_t其每一位对应一个信号值为1或0则表示是否未决或被阻塞
要对一个信号集进行修改只能通过特定函数
#include signal.hint sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
其中
sigemptyset函数会将set指向的信号集所有位清零表示该信号集不包含任何有效信号sigfillset函数则会将set指向的信号集每一位置为1sigaddset函数会将set指向的信号集中signum号信号对应的位修改为1表示添加某个信号sigdelset函数会将set指向的信号集中signum号信号对应的位修改为0表示删除某个信号sigismember函数会判断signum号信号在set指向的信号集中是否有效有效返回1否则返回0出错返回-1
通过这几个函数我们就能对一个信号集进行初始化或修改等操作了 2sigprocmask函数
#include signal.hint sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
sigprocmask函数可以读取或修改进程的信号屏蔽字阻塞信号集成功返回0出错返回-1
其中
how更改信号屏蔽字的方式SIG_BLOCK表示将set中的有效信号添加到当前信号屏蔽字SIG_UNBLOCK表示将set中的有效信号从当前信号屏蔽字中删除SIG_SETMASK表示将当前信号屏蔽字设置为setset若为非空则按照set的内容和how指定的方式对当前信号屏蔽字进行修改oset若为非空则将当前信号屏蔽字通过该参数读出
若set和oset都为非空则先读取再修改 3sigpending函数
#include signal.hint sigpending(sigset_t *set);
sigpending函数可以通过set参数读取当前进程的未决信号集成功返回0出错返回-1
例如
#include iostream
#include signal.h
#include stdlib.h
#include unistd.husing namespace std;void PrintSigset(sigset_t *set) //打印信号集
{for (int i 1;i 32; i) //遍历所有普通信号{if(sigismember(set, i)) //判断当前信号在信号集中是否有效cout 1;elsecout 0;}cout endl;
}int main()
{sigset_t set, p;sigemptyset(set);sigaddset(set, 1); //向信号集中添加1号信号sigprocmask(SIG_BLOCK, set, NULL); //将set中的有效信号添加到信号屏蔽字中while(true){cout process running...,pid: getpid() , pending: ;sigpending(p); //取出未决信号集PrintSigset(p); //打印未决信号集sleep(1);}return 0;
}
运行结果 在代码中我们将1号信号添加到了进程的信号屏蔽字中所以如果进程接收到1号信号后不会对其进行处理
通过运行结果可以发现在我们向进程发送1号信号后未决信号集的第一位由0变为1说明此时1号信号处于未决状态没有被处理。如果过段时间后再对1号信号解除阻塞那么进程就会在合适的时候处理信号
当然和信号的捕捉一样9和19号信号也是无法被阻塞的。试想一下如果一个进程将所有的信号都阻塞了那是不是变得刀枪不入了 二、信号的处理
2.1 用户态和内核态
我们一直说进程会在合适的时候处理信号具体是什么时候呢
进程要知道自己是否收到了信号或是否需要对信号进行处理就需要访问自己的pending表、block表和handler表。但这三张表都是内核中的数据结构说明进程一定要处在一种内核状态才能对信号进行处理
首先搞清楚什么是内核态和与其对应的用户态
用户态User Mode是进程运行用户程序的状态权限较低只能通过系统调用来请求操作系统提供的服务内核态Kernel Mode是操作系统内核的运行状态。进程在调用了系统调用、触发时钟中断或异常后会将CPU的控制权交给操作系统内核并陷入内核态处于内核态的CPU拥有最高的权限可以访问操作系统的所有资源为了操作系统的安全操作系统不允许用户直接对其内部进行操作。但有时用户需要操作系统帮助完成需要更高权限的工作因此区分了内核态与用户态 这里直接给出结论我们的进程在从内核态返回到用户态的时候进行信号的检测与处理 关于进程在用户态和内核态之间的切换还有一些内容需要我们了解
例如进程在执行一些封装了系统调用的库函数时就需要从用户态陷入到内核态其中是通过一条汇编指令 int 0x80 实现的 2.2 重谈进程地址空间
Linux的进程地址空间大小为4GB其中位于较高地址的1GB为内核空间通过内核级页表映射了操作系统的代码和数据较低地址的3GB为用户空间供进程使用通过用户级页表映射到物理内存中进程自己的代码和数据。 操作系统中有几个进程就有几个用户级页表但内核级页表在操作系统中只有一份可以被所有进程看到所以每个进程的内核空间映射的内容都是一样的 既然内核空间是进程地址空间的一部分而内核空间又映射了操作系统的代码和数据那么我们在调用系统调用接口的时候难道不就是在进程自己的地址空间中调用吗 站在进程的视角调用系统调用接口就相当于我们从用户空间的正文代码中跳转到内核空间执行完毕后再返回到用户空间
但是操作系统肯定不会允许我们进程随心所欲的访问内核空间的内容只有进程处于内核态时才拥有访问内核空间的权限。如何判断一个进程是处于用户态还是内核态呢
CPU当中存在一个ecs寄存器若其最低两位比特位为00则代表进程处于内核态为11则代表进程处于用户态当执行了int 0x80时就会将这两位从11用户态变为00内核态。通过检测这两位就可以判断进程处于什么态是否有权限访问内核空间
既然内核态的权限那么大那处于内核态的进程能否执行用户空间的代码呢不行
依旧是为了操作系统的安全OS不会执行任何用户所写的代码避免用户的代码中包含非法操作等情况因此进程需要从内核态再重新返回用户态执行用户的代码
我们回到信号的话题看看进程遇到异常收到信号时是如何进行用户态和内核态的切换的
当进程发生异常从用户态陷入到内核态进行异常的处理处理完毕后在返回用户态之前就会检查是否有信号需要处理如果此时对应信号的处理方法是用户自定义的则需要从内核态返回用户态执行对应函数如果处理方法是系统默认则处理完毕后会直接返回代码原来中断的位置继续执行执行完用户自定义的信号处理方法后处于用户态的进程无法直接跳转回原来的位置需要再次陷入内核态处于内核态的进程能够跳转回主控制流程中上次被中断的地方继续向下执行 三、信号的捕捉
sigaction函数
除了signal函数我们还可以通过sigaction函数捕捉一个信号包括实时信号
#include signal.hint sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
sigaction函数用于修改特定信号的处理动作成功返回0失败返回-1并设置错误码
其中
act和oact是指向sigaction结构体的指针这个结构体的名字和函数名一样其结构为
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就行这是我们捕捉信号的自定义处理方法。sa_sigaction是实时信号的处理方法我们不深入了解
关于sa_mask字段 在捕捉某个信号时内核会暂时性的将该信号屏蔽直到将当前信号处理完毕后才会解除屏蔽避免同样的信号被重复捕捉 如果在处理信号时除了屏蔽当前信号还希望额外暂时性屏蔽一些其他信号则通过sa_mask字段进行说明 我们自定义的信号处理方法通过act参数传入旧的信号处理方法则可以通过oact参数输出
完.