文章目录

1 总览信号
- 在信号还没有产生的时候,对于进程来说,应该知道信号产生之后应该有怎样的对应默认行为。
- 信号产生的时候,进程应该知道信号对应的含义,操作系统中已经内置了信号的处理方案
- 进程应该可以识别信号的种类,因为操作系统中已经内置了信号的种类
- 信号随时随地都有可能产生,而信号的产生和进程运行是一种“异步关系”
- 当信号产生后,进程不一定要马上去处理信号,因为可能有优先级更高的事情要做
- 信号已经到来,暂时没有处理,需要在“合适”的时候处理
- 信号已经到来,但是没有立刻处理,在这个“时间窗口”内,进程需要暂时保存信号
- 当准备处理信号的时候,有不同的处理方式
- 默认行为(终止进程,暂停进程,继续运行等操作系统中内置的处理方式)
- 自定义行为(自己定义信号到来的进程需要做哪些操作)
- 忽略行为(不处理这个信号了)
查看信号的种类
在Linux使用kill -l
查看所有的信号
一共有62种信号,其中131是一组信号,称之为“普通信号”,3464是一组信号,称之为“实时信号”,没有32,33两种信号。
更详细的信号内容可以使用man 7 signal
查看
信号是如何记录的?如何发送的?
信号记录在进程的task_struct
进程控制块中。并且使用位图来进程“是否”接收信号。其中位图中比特位的位置表示信号编号,比特位的内容表示是否收到信号。
进程收到信号其实就是进程控制块内的信号位图被修改了。而操作系统是进程的管理者,所以虽然信号发送的方式有很多种(kill
命令或者使用按键),但是信号位图只会被操作系统修改的。因此信号发送的本质就是操作系统直接去修改目标进程task_struct
中的信号位图。
补充:
有两个可以杀死进程的方式
- 使用
kill -9 进程号
的方式(发9号信号) - 使用
killall 进程名称
的方式
2 产生信号的方式
2.1 键盘组合键
ctrl + c
组合:发送2号信号,SIGINT
ctrl + \
组合:发送3号信号,SIGQUIT
ctrl + z
组合:发送20号信号,SIGTSTP
注意:
- shell中可以同时运行一个前台进程和多个后台进程,但是键盘组合键只能发送给前台进程。
- 运行一个进程时后加一个
&
可以将进程放在后台运行。
相关命令:
进程 &
:将进程后台后台运行
jobs
:查看后台任务
fg
:将后台任务放到前台
2.2 程序异常导致硬件问题
核心转储
当代码正常运行完毕,可以通过查看进程对应的退出码判断运行中的情况。
但代码运行中出错而异常退出时,通常都是通过调试来定位错误位置。在Linux中还有另一种方式:核心转储。即将进程在内存中的核心数据转而存储在磁盘上,形成core.pid
文件。
在使用gdb
调试的时候,就可以使用core-file core.pid
命令直接定位跳转到出错的位置。这种调试方式是“事后调试”。
如何打开核心转储?
如果使用的是虚拟机,默认核心转储功能是自动打开的。但是如果使用的线上服务器,默认该功能是关闭的。
可以使用**ulimit -a
命令查看系统资源**。
然后使用ulimit -c xxx
设置core file size
大小,就相当于打开核心转储功能了。
为什么进程会崩溃,程序会异常?
本质是因为收到了信号。当发生除零,野指针,越界访问的时候,都是因为收到了信号。进而进程执行默认行为。
为什么出现错误会收到信号?
当出现错误的时候,一定会在硬件上有所表现,而操作系统是软硬件的管理者,所以操作系统会识别到硬件错误,最终导致操作系统会向进程发送信号。
如:
- 当发生除零错误的时候,cpu中的状态寄存器中的状态会变化,操作系统识别到后将错误包装成信号的形式发送给对应的进程,找到进程pcb修改信号位图的第8号比特位,就相当于给进程发送8号信号了,然后进程就会执行8号信号对应的默认行为。
- 当发生野指针或者越界的时候,会先通过MMU(一种硬件单元)和页表配合使得虚拟地址和物理内存相互映射,但是发生错误时,MMU映射出错就会改变其状态信息,此时会被操作系统识别到,然后操作系统就会修改发生错误的进程中的pcb中的信号位图的第11位,相当于给进程发送了11号信号。
补充:其实语言级别中的try catch
机制底层其实是类似的,也就是当发生错误的时候,操作系统发送给进程的信号被捕捉了,因此进程就没有执行信号的默认行为。
2.3 系统调用函数
kill
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
- 作用
- 给pid号进程发送sig号信号
使用kill()函数,模拟实现kill命令
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
void Usage(const char*proc)
{
printf("Usage: %s pid signo\n");
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
int signo = aoti(argv[2]);
kill(pid, signo);
return 0;
}
raise
#include <signal.h>
int raise(int sig);
- 作用
- 进程给自己发送sig号信号
示例:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
int main()
{
// 一秒钟后会接收到2号信号
while (true)
{
printf("hello process\n");
sleep(1);
raise(2);
}
return 0;
}
abort
#include <stdlib.h>
void abort();
- 作用
- 进程给自己发送6号信号
SIGABRT
,也就是强制终止进程,就算信号被捕捉了也会终止当前进程
- 进程给自己发送6号信号
abort()
函数和exit()
函数类似都会使得进程终止。但是abort
是通过发送信号的方式终止终止进程,并且一定会调用成功。但是exit()
可能会调用失败。
2.4 软件条件
软件条件产生信号就是不满足某种条件而发送的信号。
- SIGPIPE
在使用匿名管道通信的时候,如果文件的读端关闭的话,此时写端再写入也没有意义了,所以此时操作系统就会发送SIGPIPE
信号给写端进程,终止该进程。