当我们在shell上写出一个死循环退不出来的时候,只需要一个组合键,ctrl+c,就可以解决了,这就是一个信号。
用户按下Ctrl+C ,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程。前台进程因为收到信号,进而引起进程退出。
信号是进程之间事件异步通知的一种方式,属于软中断。
用kill -l命令可以察看系统定义的信号列表
一个62个信号。其中前31个时普通信号,后31个时实时信号。
处理动作有以下三种:
- 忽略此信号。
- 执行该信号的默认处理动作。
- 提供一个信号处理函数,用户自定义函数,这种方式称为捕捉。
1.kill函数:
可以给一个指定的进程发送指定的信号。
参数:
返回值:成功返回0失败返回-1
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
3.abort函数
使当前进程接收到信号而异常终止。给自己发送异常终止信号6(SIGABRT)
SIGPIPE是一种由软件条件产生的信号。
#include
unsigned int alarm(unsigned int seconds);
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发送14号信号(SIGALRM信号),该信号的默认处理动作是终止该进程。
硬件异常被硬件以某种方式被硬件检测到并通知内核,然后内核向当前进程发送适当的信号。例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为8号信号(SIGFPE)发送给进程。再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为11信号(SIGSEGV)发送给进程。
注意:阻塞和忽略时不同的,信号阻塞状态是信号未决,而信号忽略状态是信号递达。
如果在进程解除对某信号的阻塞之前这种信号产生过多次,将如何处理?
sigset_t
来存储,sigset_t称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。#include
int sigemptyset(sigset_t *set);
//sigemptyset初始化set所指向的信号集,使其中所有bit位清零
int sigfillset(sigset_t *set);
//sigfillset初始化set所指向的信号集,使其中所有bit位设为有效
int sigaddset (sigset_t *set, int signo);
//sigaddset将信号集中的某个信号bit位设为有效
int sigdelset(sigset_t *set, int signo);
sigdelset将信号集中的某个信号bit位设为无效
对于上面4个函数的返回值:成功返回0,出错则为-1
int sigismember(const sigset_t *set, int signum);
sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号
若包含则返回1,不包含则返回0,出错返回-1
注意: 在使用sigset_ t类型的变量之前,一定要调 用sigemptyset或sigfillset做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
调用sigprocmask函数可以读取或更改进程的阻塞信号集(Block位图表)
#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
参数:
如果set是非空指针,则 更改进程的信号屏蔽字,参数how指示如何更改
oset若是非空指针,读取当前进程的阻塞信号集通过oset传出,输出型参数。
返回值:若成功则为0,若出错则为-1
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1
如果一个函数只访问自己的局部变量或参数,则称为可重入函数。这种函数不会影响运行结果。
常见不可重入函数:
1.调用malloc/free的函数,因为malloc也是用全局链表来管理堆的
2.调用了I/O函数库,标准I/O库中很多实现都以不可重入的方式使用全局数据结构
volatile修饰的变量,是不可被"覆盖"的,读取变量必须读取变量真实的存储位置(不可读取缓存、寄存器等位置的值)。
volatile 作用:保持内存的可见性,告知编译器,被volatile关键字修饰的变量,不允许被优化,对该变量的任何操作,都必须在真实的内存中进行操作
我们知道清除僵尸进程的方法就是使用wait和waitpid函数父进程可以阻塞等待子进程结束,也可以非阻塞的查询是否有子进程需要被清理(轮询),第一种方式父进程阻塞就不能做其他事情了,第二种,父进程不断去询问,代码实现比较复杂。
其实子进程在终止时会给父进程发一个SIGCHILD信号,默认处理动作是忽略,用户可以自定义SIGCHILD的处理函数,这样父进程就可以专心处理自己的事情,不用关心子进程了,子进程退出时会通知父进程,父进程在信号处理函数中调用wait来处理子进程就可以了
想不产生僵尸进程还有另外一种方法:父进程调用sigaction将SIGCHILD处理动作置为SIG_IGN这样fork出的子进程在终止时会自动清理掉也不会通知父进程系统默认的忽略和用户自定义的忽略一般是没有区别的,但这是一个特例,对于Linux可以用,在其他unix系统上不一定能用。