写这篇博客只是在梳理这几天我看到知识。
LinuxC编程比较难,以前没有接触过操作系统理论,不是正统的计算机专业出身。看书看着看着就忘了,这是我第2次看《LinuxC编程一站式学习》。看第一遍的时候,思维混乱,过目就忘。
所以这回,我借写博客,边看边整理知识体系,以便能真正的记住。
信号中的两个概念:
1、信号递达(Delivery):执行信号的处理叫信号递达。
2、信号未决(Pending):信号从产生到递达之间的状态。
信号的产生,大致分为4类:
1、用户终端按某些按钮之后会产生某些信号。如按下ctrl+c后会产生SIGINT信号。
2、硬件异常产生某些信号。由于某些条件使得硬件检测时通知内核,然后内核向当前进程发送适当的信号。如当前进程访问非法地址时,MMU会发生异常,内核会将这个异常
解释为SIGSEGV信号发送给当前进程。
3、进程调用kill函数将信号发送个另一个进程。
4、当内核检测到某软件条件发生时通过信号通知进程,如闹钟超时产生SIGALRM信号,向读端已关闭的管道写数据时产生SIGPIPE信号。
信号的处理(不按系统默认的动作处理信号,调用sigaction函数告诉内核处理信号),可选的动作有3种:
1、忽略此信号。
2、执行该信号的默认动作。
3、提供一个信号处理函数,内核在处理该信号时切换到用户执行这个处理函数。
--------------------------------------------------------------------------------------------------------------------
调用系统函数向进程发送信号
kill函数可以给一个指定的进程发送制定的信号。
raise函数可以给当前进程发送制定的信号(自己给自己发送信号)。
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
成功返回0,错误返回-1.
abort 函数使当前进程接收到 SIGABRT 信号而异常终止。
#include <stdlib.h>
void abort(void);
abort 函数总是会成功的,所以没有返回值。
-------------------------------------------------------------------------------------------------------------------
由软件条件产生信号
alarm函数设定一个闹钟,告诉内核在指定的秒数后给当前进程发送一个SIGALRM信号。该信号的默认处理动作是终止当前进程 。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
函数的返回值是0或者以前设定的闹钟时间剩下的秒数。
如果seconds值为0,则表示取消闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
-------------------------------------------------------------------------------------------------------------------
信号的阻塞(Block)
进程可以选择阻塞某种信号。
被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。
阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种动作。
每个信号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示动作。信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号递达菜清除该标志。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
常规信号在递达之前产生多次只计一次,而实时信号在递达之前产生多次可以依次放在一个队列里。
每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
----------------------------------------------------------------------------------------------------------------
信号集:信号集时用来描述信号的集合,linux所支持的所有信号可以全部或部分的出现在信号集中,主要与信号阻塞相关函数配合使用。
用结构体sigset_t表示,sigset_t为
typedef struct{
unsigned long sig[_NSOG_WORDS];
}sigset_t;
在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞。
未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
信号集操作函数
sigset_t 类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖
于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作 sigset_t 变量,而不应该对它
的内部数据做任何解释。
#include <signal.h>
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。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
函数sigprocmask可以读取或更改进程的信号屏蔽字。
返回值:若成功则为0,若出错则为-1.
如果oset为非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
如果 oset 和 set 都是非空指针,则先将原来的信号屏蔽字备份到 oset 里,然后根据 set 和 how 参数更改信号屏蔽字。
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。