(。・?・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~https://blog.csdn.net/ky233?type=blog
点个关注不迷路?'?'?
目录
linux信号本身是一种通知机制,用户和操作系统(本质上都是操作系统)通过发送一定的信号,来通知进程,某些事件已经发生,要来进程后续进行处理
#include<iostream>
#include<unistd.h>
using namespace std;
int main()
{
while(1)
{
cout<<"hello Linux"<<endl;
sleep(3);
}
return 0;
}
平常我们随便写一个进程,可以用ctrl+c来终止进程,但本质上这是像这个进程发送了2号(SIGINT)信号
我们通过kill -l,就可以插件Linux里常见的信号!
man 7 signal,这个代码可以查看信号常用的描述
键盘的工作方式是通过:中断方式进行的,当然也可以识别组合键ctrl+c!
OS解释组合键->查找进程列表->前台运行的进程->OS写入对应的信号到进程内部的位图结构中!
首先我们要知道是什么信号,是否产生呢?
进程必须具有保存信号的相关数据结构那么用什么呢?
很显然是位图,我们创建一个unisgned int,用比特位对应的位数,以及代表的1和0就可以解决!
进程的PCB内部保存了信号位图字段?
信号位图是在task_struct中保存的,而task_struct内核数据结构是由操作系统来管理的,所以只有OS有资格来更改!所以,所有信号都是由操作系统来发送的!!!
所以信号发送的本质就是:OS向目标进程写信号!修改PCB中的指定位图结构,完成“发送”信号的过程!
?首先我们知道信号常见处理动作有“自定义捕捉”那么如何捕捉呢?
这就要用到signal函数了
typedef void(*sighandler_t)(int)
sighandler_t signal(int signum,sighandler_t handler)
那么如何使用呢
#include<iostream>
#include<unistd.h>
#include<signal.h>
using namespace std;
void catchsig(int signum)
{
cout<<"捕捉到了一个信号,正在处理中:"<<signum<<" pid:"<<getpid()<<endl;
}
int main()
{
signal(SIGINT,catchsig);
while(1)
{
cout<<"我是一个进程,我正在运行... pid:"<<getpid()<<endl;
sleep(1);
}
return 0;
}
那么我们可以发现两个现象
这是因为特定信号的处理动作,一般只有一个,比如捕捉了就不能忽略了!
signal函数,仅仅是修改进程对信号的后续处理动作,不是直接调用对应的处理动作
比如,我们这次直接就是更改了进程的处理方法将之变成打印,而并没有将之关闭
而signal函数仅仅是注册,而后续没有收到2号信号的话则不进行调用!
同时我们也证明了,ctrl+c就是我们的2号命令
我们可以发现3号命令后面加了个Core这个是什么呢?这个叫做核心转储。
一般而言,云服务器(生产环境)的核心转储功能是被关闭的
我们可以用这个命令打开我们的核心转储功能
ulimit -c 10240
此时我们在此kill -3就会发现,多了一个core的文件。那么这个文件是什么呢?
所谓核心转储就是当进程出现某种异常情况的时候,是否由OS将当前进程在内存中的相关核心数据,转存到磁盘中,这样做主要是为了调试。而core文件就是核心转储的数据!
int kill(pid_t pid int sig)
mykill:
#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<string>
#include<stdlib.h>
using namespace std;
static void Usage(string proc)
{
cout<<"Usage:\r\n\t"<<proc<<"signumber processid"<<endl;
}
int main(int argc,char *argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(0);
}
int signumber=atoi(argv[1]);
int procid=atoi(argv[2]);
kill(procid,signumber);
return 0;
}
int raise(int sig)
int main(int argc,char *argv[])
{
cout<<"开始运行"<<endl;
raise(8);
return 0;
}
void abort(void)
int main(int argc,char *argv[])
{
cout<<"开始运行"<<endl;
abort();
return 0;
}
用户调用系统接口,进程执行OS对应的系统调用代码,OS提取接口的参数,然后向目标写入信号,修改对应的进程信号标记位,等待进程独立并执行对应的动作!
管道,读端不光不读,而且还关闭了,写端一直写,这个时候写是没有意义的,OS会自动终止对应的写的进程,是通过发送信号的方式,发送SIGPIPE信号
也就是说当这个进程并不能够满足我们的某种行为的时候,OS就会发送信号终止这个进程
unsigned int alarm(unsigned int seconds)
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动 作是终止当前进程
int main(int argc,char *argv[])
{
alarm(1);
int count = 0;
while(1)
{
cout<<count++<<endl;
}
return 0;
}
我们发现才两万多,为什么这计算这么点呢?
因为这是我们的cout+云服务器网络发送过来+显示到外设=IO所以才会这么慢!
如果我们想要单纯的计算算力,则可以用信号捕捉的方式来计算
int count = 0;
void catchsig(int signum)
{
cout<<count<<endl;
}
int main(int argc,char *argv[])
{
alarm(1);
signal(SIGALRM,catchsig);
while(1)
{
count++;
}
return 0;
}
? ?
但是我们设定了一个闹钟,一旦触发一次就会自动移除,所以我们可以捕捉的时候再加上一次就可以了?
int count = 0;
void catchsig(int signum)
{
cout<<count<<endl;
alarm(1);
}
int main(int argc,char *argv[])
{
alarm(1);
signal(SIGALRM,catchsig);
while(1)
{
count++;
}
return 0;
}
这样我们就最基本的完成了一个定时器功能
如果我们有一个除0错误的代码,OS会给进程发送8号信号
void catchsig(int signum)
{
sleep(1);
cout<<"捕获了一个信号: "<<signum<<endl;
}
int main(int argc,char *argv[])
{
int a=100;
a/=0;
signal(SIGFPE,catchsig);
while(1)
{
sleep(1);
}
return 0;
}
我们会发现一直在死循环的捕捉,但是为什么会一直死循环呢?
首先我们要理解除0:
首先计算的是cpu,是一个硬件,CPU内部有一个寄存器,叫做状态寄存器(位图),是有对应的状态标记位进行溢出标记的,OS会自动进行计算完毕之后的检测!如果溢出标记位为1,则OS里面会识别到有溢出问题,立即只要找到当前谁在运行,提取出PID,os完成发送信号,进程就会再合适的时候处理
但硬件出现异常,进程一定会退出吗?不一定,只是默认是退出,但是即便不退出,我们也不能做什么。
那么为什么会死循环呢?这是因为寄存器中的异常一直没有被解决!
就比如我们的野指针问题也是一样的,会出现段错误,11号信号(SIGSEGV)
那么如何理解野指针或者越界问题呢?
首先我们的指针找到对应的地址,都是通过地址才找到目标位置的,我们语言上面的地址,全部都是虚拟地址,是由虚拟地址转换成物理地址的,是经过页表+MMU(硬件!)找到对应的物理地址的!所以,野指针或者越界,是非法地址,所以MMU转换的时候,是一定会报错的!
所有的信号,有他的来源,但最经全部都是被OS识别,解释,并发送的!