赞
踩
目录
【分析】
一个操作系统中,存在着多个进程,每个进程有着多种信号;所以操作系统要管理这些的信号--先描述,在组织!
描述:普通型号1~31一共有31种,对于一个进程来说,预先已经知道对于这31种信号的处理方法,所以OS只需要告诉进程某个信号是否存在--只有两种状态--位图结构!
组织:在学习进程时候,我们应该了解了每个进程都对应一个task_struct(PCB),在这个task_struct中,记录着进程的各种信息,各种信息中同样也包括信号的记录。信号在task_struct中是以位图的方式记录的,task_struct有变量signal,可以把它的类型理解成无符号整数。比特位的位置为信号编号,比特位的内容为是否收到信号,假如收到6号信号就会把第六个比特位置。
所谓的发送信号,本质其实写入信号,直接改特定进程的信号位图中的特定的比特位,0->1
task_struct是内核数据结构,只能由OS来修改 -- 无论什么样产生信号的方法,最终都是让OS来完成最后的发送过程!
signal函数
原型:signal(int sig, void (*func)(int))
功能:用来自定义信号处理方式
参数:
- sig:信号编号
- func为函数指针,传入的函数为信号的处理方式
通过终端按键也就是通过键盘产生信号,比如我们常用的ctrl+c。ctrl+\。
注意:
问题:我们平时在输入的时候,计算机怎么知道我从键盘输入了数据呢?
答:键盘是通过硬件中断的方式,通知系统我们的按键已经按下了。
【按键信号分析】
CPU硬件有着多个针脚,键盘的按键按下的时候,通过转化对应的针脚会产生中断号,OS此时就会感知到,向中断向量表中发送信号,从该表中,OS就可以分析出按下的对应的是哪一个按键,并将按键解析成对应的信号(整形),这时候OS找到对应的前台的进程并发送相应的信号。
首先在后台执行死循环程序,然后用kill命令给它发SIGSEGV信号。
kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
signo为信号编号。成功返回0,失败返回-1。
【模拟实现kill命令】
- #include <iostream>
-
- using namespace std;
-
- #include <cstring>
-
- #include <sys/types.h>
- #include <signal.h>
-
- void Usage(char *str)
- {
- printf("%s 信号编码 进程pid\n", str);
- }
-
- int main(int argc, char *argv[])
- {
- if (argc != 3)
- {
- Usage(argv[0]);
- }
- else
- {
- int pid = atoi(argv[2]);
- int sig = atoi(argv[1] + 1);
- cout << "pid:" << pid << "; sig:" << sig << endl;
- int ret = kill(pid, sig);
- if (ret != 0)
- {
- perror("kill:");
- }
- }
- return 0;
- }
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
int raise(int signo);
成功返回0,失败返回-1。
void abort(void);
功能:向自己发送6号信号SIGABRT
注意:SIGABRT可以被捕捉,但是捕捉之后依然会让进程终止,这就是SIGABRT的特点
就像exit函数一样,abort函数总是会成功的,所以没有返回值。
SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。(当读端关闭的时候,OS会向写端发送SIGPIPE信号从而终止写端的进程)
本节主要介绍alarm函数 和SIGALRM信号。
alarm函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。
alarm的返回值是0或者是剩余秒数。如果闹钟被提前唤醒,返回值为剩余秒数,否则是0。
【使用】写一个计数程序,看看cou一秒钟可以计多少次数。
- int count=0;
- void myhandler(int signo)
- {
- cout<<"count:"<<count<<endl;
- cout << "get a signo:" << signo << endl;
- exit(0);
- }
-
- int main()
- {
-
- signal(SIGALRM, myhandler);
- alarm(1);
- while (true)
- {
- count++;
- }
- }
这个程序的作用是1秒钟之内不停地数数,1秒钟到了就被SIGALRM信号终止。
在代码中有野指针,除0等操作时程序会出现异常,这时候会产生信号,然后程序崩溃。程序崩溃的本质就是收到了信号。
下面对这个过程进行具体解释:以除0操作为例,我们知道计算都是在cpu中的,cpu中有一个状态寄存器,当进行除0操作后,状态寄存器会异常。os是软硬件的管理者,当检测到cpu状态异常后,会定位到相应的进程,更改进程的信号位图,进程通过位图识别到信号发生崩溃。信号宏名字:SIGFPE
再比如当前进程访问了非法内存地址,负责虚拟地址与物理内存的一个硬件MMU会产生异常,之后和上述过程一样。信号:SIGSEGV
空指针和野指针问题分析:
问题:普通信号有有31种,而默认行为只有终止进程,停止进程,继续进程等较少的功能,而为什么操作系统会提供这么多信号机制呢?
答:因为我们并不关心进程的终止,而是要知道进程为什么会终止,多种信号,可以提供给我们更加丰富的信息,从而让我们方便的解决问题!
问题:终止进程的信号有两种action:Term&Core,这有什么用?
再验证之前我们先来学习一个概念:Core Dump
在程序正常结束后,我们可以通过错误码判断程序是否运行正确。但代码在运行过程中出错,我们也要有办法判断,其中一个方法就是调试,除此之外,linux为我们提供了核心转储功能。当一个进程要异常终止时,可以选择把进程的核心内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump
核心转储功能一般在云服务器,线上生产时是关闭的。
ulimit -a 查看系统资源
可以发现core文件大小为0。想要使用此功能,我们首先要改一下core文件大小。
可以发现在发送信号后进行了(core dumped),并且多出了一个core.6229文件。我们使用gdb调试myproc,再查看core文件,可以得到进程异常终止信息。这种方法也适用于代码中内存越界,除0等错误,可以快速定位到第几行。
问题:核心转储功能调试的时候这么好用,为什么云服务器默认会关闭它?
答:写代码一共有三种环境:开发环境、测试环境、生产环境;云服务器是生产环境。在生产环境里面,为了维护软件的稳定,当某个进程出了问题,会被其他进程立马重启,从而保证目前的软件能跑!而核心转储功能会在每次程序异常退出的时候生产一个core.pid二进制文件,而且该文件通常都比较大,当一个程序出了问题且在没人维护的时候被重启多次,就会产生多个core文件,没人管,就会占满磁盘空间,导致整个服务崩溃。所以核心转储功能只适合用来事后处理,当需要的时候在开启这个功能!
core dump表示是否有核心转储功能!
获取进程退出的所有信息:
- int status=0;
- waitpid(id,&status,0);
- cout<<"exit code: "<<((status>>8)&0xFF)<<endl;
- cout<<"eixt signal: "<<(status&0x7F)<<endl;
- cout<<"exit dump flag: "<<((status>>7)&0x1)<<endl;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。