Linux——信号

一.基本概念 

信号是Linux系统提供的一种,向指定进程发送特定事件的方式。

通过kill -l指令。即可查看系统中所有的信号,其中我们只关注1-31号,其他信号为实时信号不作考虑。

信号产生是异步的。 


二.信号处理

  • 默认动作
  • 忽略动作
  • 自定义处理-信号捕捉

进程处理信号,都是默认的,默认动作通常是:终止、暂停、忽略等。 

自定义捕捉指定信号函数: 

#include<signal.h>

sighandler_t signal(int signum,sighandler_t handler);

参数:

signum:信号值

handler:类型为void (*sighandler_t)(int)的函数指针。

该函数通过自定义一个函数操作,将其传入signal函数,随后通过信号signum执行该函数操作。

其中signum会作为handler函数的参数传入。

 通过该函数修改指定信号的捕捉方式,再次调用该信号时,就会执行对应的handler函数。

同时可以对不同的信号传递相同的handler函数。 

理解进程的发送与保存(浅度)

 在进程的task_struct内部,存在能够保存信号的位图成员变量。

向进程发送信号,就是修改其PCB中的信号的指定位图,只有OS有这个权利。


三.信号产生

  1. 通过kill命令,向指定进程发送指定的信号。
  2. 键盘可以产生信号,如ctrl + c,即终止进程。
  3. 系统调用。

1.系统调用函数

#include<sys/types.h>

#include<signal.h>

int kill(pid_t pid,int sig);

向指定进程,发送指定信号。

#include<signal.h>

int raise(int sig); 

向调用该函数的进程发送指定信号。

#include<stdlib.h>

void abort(void);

向调用该函数的进程发送6号信号,即异常终止。

6号信号可以被自定义捕捉,但是仍然会执行其异常终止的功能

9号信号不允许被自定义捕捉,其默认为终止进程的信号

#include<unistd.h>
unsigned int alarm(unsigned int seconds);

闹钟函数,可以定时终止进程。参数为闹钟时间返回值为上一个闹钟的剩余时间

在OS中会存在很多个闹钟,这就要求OS要把所有的闹钟组织其中共同管理

闹钟的执行顺序采用最小堆的方式,闹钟的剩余时间越短,就越靠上


2.异常信号

当我们的进程执行时发生了非法访问等异常操作时,OS就会向进程发送异常信号从而终止进程,这就是通常所谓的程序崩溃。

常见的异常信号有:

8号SIGFPE:除0错误

11号SIGSEGV:野指针访问

如果我们把异常信号捕捉了,也是可以不让进程终止的,但是程序也不会继续往下执行。

当我们的程序出现异常时,OS作为软硬件资源的管理者,要随时处理这些问题,尤其是除0错误而引发的硬件错误,即向目标进程发送信号。

此时,寄存器开始发挥作用,寄存器只有一套,但是寄存器里的数据属于每一个进程,我们需要通过寄存器来完成硬件上下文的保存和恢复。 

进程的终止状态有两种:

  • term:异常终止
  • core:异常终止,但是会帮我们形成一个debug文件,保存进程异常时的核心数据,协助我们找到错误。

四.阻塞信号

实际执行信号的处理动作称为信号递达。包括默认,忽略和自定义捕捉。

信号从产生到递达之间的状态,称为信号未决

进程可以选择阻塞某个信号,阻塞一个信号,对应的信号一旦产生,永不递达,一直未决,直到主动解除阻塞。

一个信号如果阻塞,和它有没有未决无关。

被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。

阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。

在进程的task_struct内部,存在三张表

  • handler表:函数指针数组,其下标就是信号的编号,由此来索引信号处理方法。
  • pending表:位图,未决信号集,比特位的位置代表信号编号,内容代表信号是否收到。
  • block表:位图,阻塞信号集,比特位的位置代表信号编号,内容代表信号是否阻塞。

 通过这三张表,就可以让进程完成对信号的识别。


五.信号保存

信号集操作函数

#include <signal.h>
int sigemptyset(sigset_t *set);//初始化set指向的信号集,全部比特位置0
int sigfillset(sigset_t *set);//初始化set指向的信号集,全部比特位置1
int sigaddset (sigset_t *set, int signo);//在set指向的信号集中添加信号
int sigdelset(sigset_t *set, int signo);//在set指向的信号集中删除信号
int sigismemberconst sigset_t *set, int signo);
这四个函数都是成功返回0,出错返回-1
sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1

读取或更改进程的信号屏蔽字(阻塞信号集)

#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

参数:

how:有三个选项:

set: 输入型参数,即要设置的信号屏蔽字的值。

oldset:输出型参数,保存原始的信号屏蔽字,返回给用户。

成功返回0,失败返回-1。


获取当前进程的pending位图

#include <signal.h>

int sigpending(sigset_t *set);//输出型参数

成功返回0,失败返回-1。


六.信号处理

信号捕捉

进程的地址空间大小为4G,其中3G归用户所用,称为用户空间,而剩余的1G大小称为内核空间,归OS所有。实际上,在地址空间和OS之间,还可以存在一张内核级页表,可以将OS映射到内核空间,从而使OS存在于进程的地址空间中。

系统同时运行多个进程时,所有的进程共同维护一份内核级页表,这就是为什么不管进程如何切换,我们都能够找到OS的原因

OS如何从键盘读取数据

当键盘向OS写入数据时,会发生硬件中断,此时数据会被写入CPU的寄存器中进行保存, 在内存中,存在专门存放外设管理的函数指针数组,其中数组的下标,对应每一种外设的中断号,数据在写入寄存器时,寄存器也会保存中断号,此时内存就可以通过中断号,将寄存器里的数据读入内存

信号捕捉函数:

#include <signal.h>

int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);

参数:

signum:信号编号。

act:输入型参数,结构体类型,其内部包含一个函数指针对象,指向信号要执行的handler方法。

oldact:输出型参数,保存信号原本的结构体信息。

struct sigaction

{
    void(*sa_handler)(int);//handler方法
    void(*sa_sigaction)(int, siginfo_t *, void *);//不关心
    sigset_t sa_mask;//进程正在处理某信号,同时我们还想屏蔽其他信号,则通过sigaddset函数将其他信号填入sa_mask中。
    int sa_flags;//不关心,置0。
    void(*sa_restorer)(void);//不关心
};

当进程正在对某信号进行处理时,默认该信号会被自动屏蔽,直至该信号被处理完成时,会自动解除对该信号的屏蔽。 


七.其他信号知识

子进程在退出时,会向父进程发送退出信号——SIGCHLD,父进程可以通过对该信号进行处理,从而对子进程进行某些管理。

评论 46
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

很楠不爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值