生活角度的信号
a.信号在生活中,随时可以产生(信号的产生和我是异步的)
b.你能认识这个信号
c.我们知道信号产生了,我能识别这个信号,信号该怎么处理
d.我们可能正在做着更重要的事情,把到来的信号暂不处理(1.我记得这个事 2.合适的时候处理)
信号介绍
在bash上执行命令kill -l便可看到系统定义的所有信号
我们只研究前31个信号,后面31个是实时信号这里不做研究
每个信号都有一个编号和一个宏定义名称,这些宏定义都可以在signal.h中找到,在man手册中还可以找到各种信号的详细信息
man 7 signal
这里具体介绍了信号在什么时候产生,处理的动作是什么
信号概念的基本储备
信号:Linux系统提供的一种,向指定进程发送特定事件的方式,做识别和处理。
信号产生是异步的。
信号处理常见方式
1、忽略该信号
2、执行信号的默认处理动作(终止自己、暂停、忽略.....)
3、提供一个信号处理函数,要求内核在处理信号时切换到用户态执行这个处理函数,这种方式称为捕捉一个异常
如何理解信号的发送和保存?
进程---task_struct---struct---成员变量---用位图来保存收到的信号
uint32_t signals;
0000 0000 0000 0000 0000 0000 0000 0000
发送信号:修改指定进程pcb中的信号指定位图,0->1,写信号
pcb:内核数据结构对象,只有OS有资格修改内核结构对象中的值
信号产生具体过程
kill命令
通过kill命令向指定进程发送指定信号
kill -数字 进程号
通过终端按键来产生信号
ctrl+c 2)SIGINT 向当前进程发送2号信号
ctrl+\ 3)SIGQUIT向当前进程发送3号信号
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并Core Dump,我们在Linux环境下来验证一下,先来了解一下什么是Core Dump
当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存在磁盘上,文件名通常是core,这叫做Core Dump。进程异常终止通常是因为有BUG,比如非法访问内存导致段错误,事后可以用调试器检查core文件以查清楚错误原因,这叫做事后调试,一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中),默认是不允许改变这个限制,允许产生core文件。首先用ulimit命令来改变shell进程的Resource Limit,允许core文件最大为1024k
通过信号返回值我们发现有一个core dump位。
云服务器默认关闭这个core文件功能
ulimit -c 1024 //打开这个文件功能
写一个死循环程序
编译并执行程序:
看到的现象是先打印出pid然后一直在死循环,按下组合键ctrl+\后退出并提示core dumped
test程序也会core dump的原因是我们先修改了shell的Resource Limit值,而test进程是由shell产生的所以test进程的PCB也是由shell复制而来,所以test进程和shell就具有相同的Resource Limit值,所以就会产生core dump了。
如图所示就是产生的core文件
最新的Linux版本所生成的core文件没有后缀,同一个文件如果多次执行多次异常终止,那么生成的core文件还是一份。这样就避免了一份文件生成多份core文件占内存。
调用系统函数来向进程发信号
kill函数
参数解释:
第一个参数进程id
第二个参数信号标号
返回值:成功返回0失败返回-1
./mykill 2 1234int main(int argc, char *argv[]){if(argc != 3){std::cerr << "Usage: " << argv[0] << " signum pid" << std::endl;return 1;}pid_t pid = std::stoi(argv[2]);int signum = std::stoi(argv[1]);kill(pid, signum);}
raise函数
发信号给自己 == kill(getpid(), sig)
只能向当前进程发送信号
参数解释:
信号标号
返回值:成功返回0失败返回-1
#include <signal.h>
#include <stdio.h>int main()
{printf("raise befor.");raise(9);//结束自己。相当于_exitprintf("raise after.\n");return 0;
}
CLC@Embed_Learn:~/linux_io/02/02/seven$ ./a.out
Killed //并未打印出raise befor.,所以相当于_exit
abort函数
函数功能:使当前进程接收到信号而异常终止
参数:无参数
返回值:无返回值
void handler(int sig){std::cout << "get a sig: " << sig << std::endl;}int main(){int cnt = 0;signal(SIGABRT, handler);while (true){sleep(1);std::cout << "hello bit, pid: " << getpid() << std::endl;abort();}}
信号虽被捕捉,但abort正常接收终止进程
signal函数
void (*signal(int signum, void (*handler)(int)))(int);
令A= void (*handler)(int) = 函数指针变量。
void (*signal(int signum, A))(int);
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函数;
这个函数的返回值是一个函数指针。
void handler(int sig){std::cout << "get a sig: " << sig << std::endl;}int main()
{signal(SIGABRT, handler);return 0;
}
捕捉到SIGABRT信号后,可以执行我们所定义的函数,并将signum传入函数sig
注意:
该函数设置一次,进程不结束可以无限捕捉
并不是所有的信号都可以捕捉,9号信号不允许自定义捕捉
软件条件产生
#include <unistd.h> unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动 作是终止当前进程。
函数功能:
设定一个闹钟,告诉内核在seconds秒后给当前进程发送一个SIGALRM信号,该信号的默认处理动作是终止当前进程
函数参数解释:闹钟的时间是多少秒
函数返回值:这个函数的返回值是0或者闹钟剩下的秒数,
当你一直不修改闹钟,直到闹钟响这时的返回值是0
当在设定的秒数之内修改了闹钟的秒数就会返回上个闹钟剩下的时间,将seconds值设为零表示取消闹钟
void handler(int sig)
{std::cout << "get a sig: " << sig << std::endl;
}int main(){signal(SIGALRM, handler);alarm(3); // 设定1S后的闹钟 -- 1S --- SIGALRMint n = alarm(0): 取消闹钟, 上一个闹钟的剩余时间std::cout << "n : " << n << std::endl;//3sleep(10);}
OS对闹钟如何做管理?先描述再组织
struct alarm
{
time_t expired;//未来的超时时间=seconds+Now();时间戳
pid_t pid;
fun_t f;
.....
}
这里是用大堆或者小堆将结构体链式存储起来的
硬件异常产生信号
程序为什么会崩溃???非法访问和操作,导致OS给进程发信号
非法访问:SIGSEGV信号
非法操作:SIGFPE信号
崩溃了为什么会退出?可以不退出吗,可以。
默认是终止进程,捕捉信号即可不退出,但最好终止退出进程释放进程的上下文数据,包括溢出标志数据或者其他异常数据。
int main()
{// 程序为什么会崩溃???非法访问、操作(?), 导致OS给进程发送信号啦!! --- 为什么// signal(SIGSEGV, handler);// signal(SIGFPE, handler);// 崩溃了为什么会退出?默认是终止进程// 可以不退出吗?可以,捕捉了异常, 推荐终止进程(为什么?) --- 为什么?// int *p = nullptr;// *p = 100; // SIGSEGVint a = 10;a /= 0; // 8) SIGFPEwhile (true){std::cout << "hello bit, pid: " << getpid() << std::endl;sleep(1);}
}