信号与信号量之间没有关系!!!!! 信号的处理⽅法,在信号产⽣之前,已经准备好了。 信号不一定是立即处理,在合适的时候处理。 处理(信号捕捉): 1.默认处理 2.忽略 3.自定义动作
信号:外部 或者其他人 或者硬件 给进程发送的一种异步的事件通知机制。 异步:多种事件,彼此不影响,同时发生。 处理的前提是,得记录下来产生的信号。所以得进行信号保存。
#include <signal.h>
typedef void (*sighandler_t)(int); //函数指针类型
sighandler_t signal(int signum, sighandler_t handler);
参数说明:
signum:信号编号[后⾯解释,只需要知道是数字即可]
handler:函数指针,表⽰更改信号的处理动作,当收到对应的信号,就回调执⾏handler⽅法 
signal的返回值,是之前该信号所对应的调用函数。 1 ~ 31 :普通信号 34 ~ 63 :实时信号 没有0号信号!!! 较大部分信号,是终止进程。
SIG_IGN 将信号的处理忽略掉
SIG_DFL信号产生
前台进程,在状态之后有+号。谁拥有标准输入(谁能从键盘获取数据),谁就是前台进程。 键盘产生的信号,只能 用来控制前台进程,无法控制后台进程,因为只有前台进程才能获取键盘输入。
为什么bash进程自己不对信号响应??? bash进程对所有的信号,进行忽略了。同样,bash无法对9号信号进行忽略。
程序在除0,野指针等情况时,程序会崩溃,为什么?? 本质是程序因为异常,导致收到了信号,然后让进程终止的。除0 :8号信号 野指针:11号信号
进程是如何保存信号的??信号要保存到哪里??? 信号被保存在进程的task_struct结构体中! 用位图表示,收到的信号。用比特位的位置来表示信号编号, 0 1 表示是否收到信号。 向目标进程发送信号,本质是修改信号位图。发信号也是写信号。 信号位图在task_struct中,修改位图,就是修改task_struct内核数据结构。只有OS能修改内核数据结构。 发送信号的方式有很多种,但是最终,只能由OS向目标进程写信号。也就必须要由系统调用 kill 除0,本质是CPU出错了。 野指针本质是MMU+页表上 ,MMU报错了。 两者本质都是硬件报错了。
a.给指定的进程发送指定的信号。 b.raise 函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号)。 c.abort 函数使当前进程接收到信号⽽异常终⽌。给指定进程发送指定信号。6号信号。 类似于exit.abort是默认生成 core dump的。 abort终止进程,即会调用6号信号对应函数(信号捕捉),还会终止进程。 与exit比较,更适合在项目中使用。(core dump便于调试)
在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产⽣机制。这些条 件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据 产⽣的SIGPIPE信号)等。当这些软件条件满⾜时,操作系统会向相关进程发送相应的信号,以通知 进程进⾏相应的处理。简⽽⾔之,软件条件是因操作系统内部或外部软件操作⽽触发的信号产⽣管道损毁(pipe broken):在管道通信部分,读端关闭写端对应的进程也会关闭。其本质上是收到了SIGPIPE 13号信号。 发送信号的方式是围绕用户,硬件,软件各种场景展开的—借助OS之手向目标进程写信号
NAME
alarm - set an alarm clock for delivery of a signal
SYNOPSIS
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
RETURN VALUE
alarm() returns the number of seconds remaining until any previously
scheduled alarm was due to be delivered, or zero if there was no previ‐
ously scheduled alarm.alarm 给调用进程设置闹钟。
调⽤ alarm 函数可以设定⼀个闹钟,也就是告诉内核在 seconds 秒之后给当前进程发 SIGALRM(14号) 信号,该信号的默认处理动作是终⽌当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
(用kill重新发信号,信号捕捉调用alarm打断闹钟。)
在这里使用闹钟循环打印可以看出,有IO时,效率会变低
alarm设置闹钟是一次性的。想要重复使用可以在14号信号对应函数处,在 设置一个闹钟。千万不要死循环设置闹钟!!可能会导致闹钟永远触发不了。 给闹钟参数设置为0,表示取消闹钟。
系统闹钟,其实本质是OS必须⾃⾝具有定时功能,并能让⽤⼾设置这种定时功能,才可能实现闹钟这样的技术。 现代Linux是提供了定时功能的,定时器也要被管理:先描述,在组织。
struct timer
{
int expired; //未来过期的时间
int cnt; // 要触发几次
void (*callback)();
....
}
struct timer_list {
struct list_head entry;
unsigned long expires;
void (*function)(unsigned long);
unsigned long data;
struct tvec_t_base_s *base;
};信号不会立即处理,产生之后,处理之前,就有时间窗口,所以信号必须被保存起来。 • 实际执⾏信号的处理动作称为信号递达(Delivery) • 信号从产⽣到递达之间的状态,称为信号未决(Pending)。 • 进程可以选择阻塞(Block)某个信号。 • 被阻塞的信号产⽣时将保持在未决状态,直到进程解除对此信号的阻塞,才执⾏递达的动作. • 注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,⽽忽略是在递达之后可选的⼀种处理动作。 pending表,位图结构,31个有效位。信号未决。 比特位的位置:信号编号。 比特位的内容:是否收到。1表示信号未抵达。 block表,位图结构。 比特位的位置:信号编号。 比特位的内容:是否阻塞。先看block,再看pending handler表 函数指针数组 signal修改信号对应的函数时,就是在handler中修改。设置捕捉。为0时,默认操作,为1时,忽略。
OS需要让用户控制信号,本质就是访问和操作上面的三张表。 通过系统调用控制,比如:signal操作handler表 sigprocmask sigpending sigset_t 从上图来看,每个信号只有⼀个bit的未决标志,⾮0即1,不记录该信号产⽣了多少次,阻塞标志也是这样 表⽰的。因此,未决和阻塞标志可以⽤相同的数据类型sigset_t来存储,sigset_t称为信号集,这个类型 可以表⽰每个信号的“有效”或“⽆效”状态,在阻塞信号集中“有效”和“⽆效”的含义是该信号 是否被阻塞,⽽在未决信号集中“有效”和“⽆效”的含义是该信号是否处于未决状态。阻塞信号集也叫做当前进程的信号屏蔽字(SignalMask), 这⾥的“屏蔽”应该理解为阻塞⽽不是忽略。 信号集操作函数
#include
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo); • 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表⽰该信号集不包含 任何有效信号。 • 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表⽰该信号集的有效信号 包括系统⽀持的所有信号。 • 注意,在使⽤sigset_t类型的变量之前,⼀定要调⽤sigemptyset或sigfillset做初始化,使信号集处于 确定的状态。 初始化sigset_t变量之后就可以在调⽤sigaddset和sigdelset在该信号集中添加或删 除某种有效信号。 这四个函数都是成功返回0,出错返回-1。 sigismember是⼀个布尔函数,⽤于判断⼀个信号集的有效信 号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
sigprocmask 调⽤函数 sigprocmask 可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
返回值:若成功则为0,若出错则为-1 oset输出型参数,输出上一次block表的数据 Sigpending
#include
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。 调⽤成功则返回0,出错则返回-1 set输出型参数。 kill函数可以用来修改pending表。
信号什么时候被处理??? 当进程调度的时候,从内核态返回用户态的时候,会进行信号的检测和处理。 信号并不会造成中断,但是信号的处理要在内核态中做。这时由于时钟中断!!!
sigaction
#include
int sigaction(int signo, const struct sigaction *act, struct sigaction *oact);
struct sigaction {
void (*sa_handler)(int); //函数指针
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask; //
int sa_flags;
void (*sa_restorer)(void);
};//使用该结构体时,要把成员进行初始化,否则会出现未定义错误。• sigaction函数可以读取和修改与指定信号相关联的处理动作。调⽤成功则返回0,出错则返回-1。 signo是指定信号的编号。若act指针⾮空,则根据act修改该信号的处理动作。若oact指针⾮空,则 通过oact传出该信号原来的处理动作。act和oact指向sigaction结构体 • 将sa_handler赋值为常数SIG_IGN传给sigaction表⽰忽略信号,赋值为常数SIG_DFL表⽰执⾏系统 默认动作,赋值为⼀个函数指针表⽰⽤⾃定义函数捕捉信号,或者说向内核注册了⼀个信号处理函 数,该函数返回值为void,可以带⼀个int参数,通过参数可以得知当前信号的编号,这样就可以⽤同⼀ 个函数处理多种信号。显然,这也是⼀个回调函数,不是被main函数调⽤,⽽是被系统所调⽤。
当某个信号的处理函数被调⽤时,内核⾃动将当前信号加⼊进程的信号屏蔽字,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。(也就是说,当某个信号的调用函数正在被运行时,该信号是被屏蔽的),这样就保证了在处理某个信号时,如果这种信号再次产⽣,那么它会被阻塞到 当前处理结束为⽌。(防止同种信号嵌套问题)
如果在调⽤信号处理函数时,除了当前信号被⾃动屏蔽之外,还希望⾃动屏蔽另外⼀些信号,则⽤sa_mask字段说明这些需要额外屏蔽的信号,当信号处理函数返回时⾃动恢复原来的信号屏蔽字。使用sigaddset函数添加信号屏蔽字。