信号的概念:信号是一个软件中断(信号灯)
只是告诉有这样一个信号,但是具体信号怎样处理,啥时候处理由进程决定,所以是软中断
信号的产生
ctrl+c :
2号信号 SIGINT
ctrl+z :
20号信号 SIGTSTP
ctrl+ | :
3号信号 SIGQUIT
kill -l //看一下操作系统 的 信号定义
kill -[信号值] pid //可以让该进程执行该信号
kill 函数
raise函数 //谁调用给谁发送信号
信号的种类
非可靠信号(非实时信号)
1~31 //可能会丢失信号
可靠信号 (实时信号)
34~64 //一定不会丢失信号
总共有 62个信号
信号的处理方式
操作系统对信号的处理方式(man 7 signal)
term core cont ign stop
默认处理方式:SIG_DFL//操作系统已经定义好信号的处理方式
忽略处理方式:SIG IGN//该信号为忽略处理
//面试官问我们僵尸进程如何产生
//我们的回答:子进程先与父进程退出,并告知父进程退出状态信息,发送了SIGCHLD信号,父进程对号
//忽略处理
信号的注册
一个进程收到一个信号,这个过程称之为注册(内核给我们进程的信号)
信号的注册和注销并不是一个过程,是两个独立的过程
信号的注册:信号对应的比特位修改成为 1 ,添加sigqueue节点到sigqueue队列当中
区分:
//修改位图就是将sig数组的比特位进行修改(比如发送2号信号,那就修改2号信号比特位)
非实时信号
第一次注册
修改sig位图 (0-1) , 添加signqueue节点
eg :17号信号的比特位从0改1 , 并添加signqueue节点
第二次注册
前提:同一个非实时信号,在前一个信号未被处理时再次注册会修改位图(0-1),但是不会添加sigqueue节点
//那末后面这个信号就会被注释掉//这也就是为啥非实时信号会丢失的缘故
-------------------------------------------------------------------------------------------
实时信号注册
第一次注册修改位图(0-1),添加signqueue节点
第二次注册修改位图(1-1),添加signqueue节点
信号注销
信号的自定义处理方式(让收到的信号变为我们自己程序员自己定义的函数以达到我们期望实现的功能)
9号信号不能被自定义处理
//如果操作系统所有的信号都被我们程序员自定义处理了
//我们自定义的函数又啥都不干,操作系统一点办法都没有
//操作系统规定9号进程不能被自定义处理
kill -9 pid 依然可以强制杀你这个进程
sched.h // 操作系统内核代码
小技巧
处理进程信号的时机
我们写的代码让它开始跑,创建进程(并且操作系统给它一个信号)开始它是在用户态的等到我们需要内核为我们执行某些功能时我们进入到内核中,内核中执行完功能,我们返回用户态,这时调用do_signal函数
看一下有别的信号没
(1.默认处理2.忽略处理)
(均在操作系统中),执行信号,再do_signal若无信号回到用户态继续执行代码
(3.用户自定义处理方式在用户态下完成)执行完处理方式代码
回到操作系统内核 do_signal...
信号阻塞
int sigemptyset(sigset_t *set); // 清空位图, 将比特位全部置为0
int sigfillset(sigset_t *set); // 将比特位全部置为1
int sigaddset(sigset_t *set,int signum): //将某个信号对应的比特位置为1
int sigdelset(sigset_t *set,int signum); //将某个信号对应的比特位置为0
int sigismember(const sigset_t *set, int signum); // 判断某个信号是否在set当中,其实就是判断某个信号的比特位是否为1
对位图的修改的函数
int sigemptyset(sigset_t *set); // 清空位图, 将比特位全部置为0
int sigfillset(sigset_t *set); // 将比特位全部置为1
int sigaddset(sigset_t *set,int signum); //将某个信号对应的比特位置为1
int sigdelset(sigset_t *set,int signum); //将某个信号对应的比特位置为0
int sigismember(const sigset_t *set, int signum); // 判断某个信号是否在set当中,其实就是判断某个信号的比特位是否为1
接口
int sigprocmask(int how, const sigset t *set, sigset t *oldset)
how: 想让sigprocmask做什么事情
SIG_BLOCK: 设置某个信号为阻塞状态
SIG_UNBLOCK : 设置某个信号为非阻寒状态
SIG_SETMASK : 用第二个参数“set”, 替换原来的阻塞位图。 (替换的意思)
set : 新设置的阻塞位图
oldset : 原来老的阻塞位图
原理解析:
当how为SIG_BLOCK时, 函数会根据set, 计算新的阻塞位图, 方式为:block(new)=block (old) | set:
当how为为SIG_UNBLOCK时,函数会根据set,计算新的阻塞位图,方式为:block(new) = block(old)|(~set):
当how为为SIG_SETMASK时,函数会根据set,计算新的阻塞位图, 方式为:block(new) = set:
//可以看得出非实时信号2号信号,只处理了1次发生了信号丢失
父进程如何避免子进程成为僵尸进程
处理方法:
子进程退出时给父进程发送SIG_CHLD信号,对该信号我们自己定义处理方式(使用wait函数)
回收子进程的退出状态信息
//避免父进程啥都不干只为了等待子进程的退出(解放父进程避免父进程ri han gan)
volatile关键字
作用:
保持内存可见性
计算机cpu在执行代码时,从寄存器中读取数据,而非内存(缓和速度矛盾)
volatile 确保它一定读的是我们内存中的数据//内存--》寄存器--》cpu
插入gcc/g++优化编译 “-O0” "-O1" "-O2" "-O3"数字越高级别越高,优化越高,代码执行速度越快
//优化等级高的话我们一些数据是不会从内存中读取而是直接读寄存器的值
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
volatile int val=1;
void callback(int sig)
{
printf("signal=%d had been revised\n",sig);
val=0;
}
void main()
{
signal(2,callback);
while(val)
{
printf("i am sleep\n");
sleep(1);
}
printf("hhhh jump down\n");
}