长春企业网站排名优化,山东青岛最新消息,外贸英才网,短期网站建设培训一、信号的捕捉处理
信号保存后会在合适的时间进行处理#xff1b;
1.1信号处理时间
进程会在操作系统的调度下处理信号#xff0c;操作系统只管发信号#xff0c;即信号处理是由进程完成的#xff1b;
1.信号处理首先进程得检查是否有信号#xff1b;2.进程…一、信号的捕捉处理
信号保存后会在合适的时间进行处理
1.1信号处理时间
进程会在操作系统的调度下处理信号操作系统只管发信号即信号处理是由进程完成的
1.信号处理首先进程得检查是否有信号2.进程要处于内核状态才能处理信号
即进程会在内核态返回用户态的时候检查并处理信号 对于程序的执行有一部分是自己写的有一部分是库提供的还有一部分是操作系统提供的执行操作系统提供的代码需要进行身份的切换执行自己写的和库提供的一般是以用户态的身份执行执行系统调用或者是进入操作系统内部(如硬件中断根据中断号执行内核的终端表方法)需要以内核态的身份进行
操作系统可以响应外部硬件的中断也可以响应内部软件产生的中断int 80(是一条汇编也是CPU可以认识的指令)就是一种软件中断功能是让进程从用户态陷入内核态
总结程序运行时会从代码区开始执行执行到函数调用时触发int 80将ecs后两位由11变成00进入内核态根据用户级页表映射进入内核空间执行代码根据内核级页表映射到内存空间执行代码返回时要先执行一次do signal(当前进程对信号做一次检测对pending表block表的检测)如果不需要处理直接变成用户态将ecs寄存器的后两位由00变成11并且返回到原先用户空间执行代码处否则先将pending表对应信号位置零对于忽略方式直接返回默认方式直接执行都是在内核空间处理而对于自定义处理需要先将身份转换为用户态(因为操作系统不信任用户的代码有风险)然后执行完使用sigreturn(使用函数压栈的方式传入的此函数所以可以执行)返回内核态跳转之前的位置然后在跳转回用户态执行的地方
要注意main和sighandler是两个不同的执行流不是调用和被调用的关系 对于自定义信号处理进行了4次身份切换两次信号的检测而默认和忽略方式处理信号只是进行了2次身份切换1次信号检测
由于进程是会被调度的进程上下文的恢复和进程调度的实现都是在内核空间的所以一定会由频繁的身份切换所以一定会对信号进行检测
1.2信号处理接口
#include signal.h
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);#include signal.h
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//既可以处理普通信号也可以处理实时信号
struct sigaction {void (*sa_handler)(int);void (*sa_sigaction)(int, siginfo_t *, void *);//不关心处理实时信号sigset_t sa_mask;//除了当前信号自动屏蔽还可以选择将多个信号屏蔽int sa_flags;//不关心默认设为0void (*sa_restorer)(void);//不关心
};
//与signal使用类似只不过需要传入结构体对象 信号处理前会先将pending位图对应位置的1置为0
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么 它会被阻塞到当前处理结束为止。 如果在调用信号处理函数时,除了当前信号被自动屏蔽之外,还希望自动屏蔽另外一些信号,则用sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时自动恢复原来的信号屏蔽字。
上述方式可以防止信号捕捉函数被重复调用
二、补充话题
2.1可重入函数
当头插节点时有三个位置newnode1prevcur三个节点指针刚完成刚完成newnode1和cur节点的链接还没有执行prev和newnode1的链接因为信号的处理跳转到新的执行流刚好信号处理也是头插使用的是newnode2这次执行是完整的返回用户态是继续执行会将指向newnode2的prev指向了newnode1这样会导致newnode2丢失导致内存泄漏
如上现象就是函数被重入了对于函数如果重入了出错则该函数就是不可重入函数反之就是可重入函数
信号处理和main执行流并不是和多线程一样一旦创建线程执行流就开始运行并且和main执行流是并行的而是取决于信号并且执行信号流时会使得main执行流被暂停是一个进程的不同执行流
2.2volatile
volatile作用是保持内存可见性进行运算(算术运算或者逻辑运算)都会进入CPU的运算器进行
在优化条件下如果只是对变量读取不进行修改可能变量会被优化到寄存器里面而不是内存中这样优化是的不需要进行访存提高了效率
如设置全局变量main执行流只是进行了读取而信号执行流进行了写入这时候编译器优化main执行流不从内存中读取而是从寄存器读取但是寄存器中的内容并不会被修改信号执行流进行了写入内存中实实在在的被修改了这时候就会导致全局变量即使被修改了但是main在执行流不可见所以需要在全局变量前加volatile关键字修饰保持内存的可见性使得main执行流从内存中进行读取
register是一个建议性关键字用来进行优化还是会创建变量但是可能会将变量内容放到寄存器
2.2.1Linux gcc/g优化
gcc -O .c文件
#选项包括-O0到-O30表示没有优化1-3从低到高进行优化2.3SIGCHID
子进程退出时父进程必须进行等待否则子进程就会变成僵尸状态
等待的目的1.使得子进程可以被回收2.获得子进程的退出信息3.由于子进程的退出是未知的所以父进程需要阻塞或者是非阻塞的方式进行等待要保证父进程是最后一个退出的
子进程并不是直接就退出了而是会向父进程发送信号的此信号就叫做SIGCHID(17号)信号
进程等待可以采用基于信号的方式实现异步等待要保证父进程在这期间是一直运行的
使用waitpid非阻塞的方式或者是基于信号的等待都可以使得父进程继续运行不用阻塞
关于多个子进程等待使用-1来接受任意多个子进程使用WNOHANG防止等待时有进程不退出导致阻塞
对于信号实现子进程的等待也可以不调用waitpid事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程调用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户用sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用
17号信号的默认处理方式是忽略而忽略方式是自动清理子进程