当前位置:首页 » 《随便一记》 » 正文

Linux——信号

16 人参与  2024年10月04日 16:00  分类 : 《随便一记》  评论

点击全文阅读


一.基本概念 

信号是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有这个权利。


三.信号产生

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

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 sigismember(const 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,父进程可以通过对该信号进行处理,从而对子进程进行某些管理。


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/167715.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1