信号:(signal)
中断分为软件中断和硬件中断,软件中断就是用软件的方式中断代码。信号是Unix/Linux系统下最常见的一种软件中断方式,提供了一种处理异步事件的方法。
信号都有一个宏名称,以SIG开头。本质就是一个非负整数,查看信号的命令:
kill -l
编程时,信号用宏名称,因为有些系统信号数字不同,但宏名称是一样的。
信号导致代码中断的条件很多,比如:
中断(Ctrl+C,SIGINT)、退出(Ctrl+\,SIGQUIT)、(挂起(Ctrl+Z,SIGSTP);段错误(SIGSEGV)、总线错误、整数除以0(SIGFPE)等
**浮点数可以 除以0,结果是 INF(无限大)。**
Unix系统信号 从 1到48,Linux系统从 1 -64 ,但不确保连续,而且 规范中没有规定信号的数量。在Linux系统中,1-31是 不可靠信号,是早期的信号,不支持排队,有可能丢失;34-64是可靠信号,支持排队,不可能丢失。信号分为可靠信号和不可靠信号。
信号处理方式有三种:
1. 默认处理,系统提供的,多半是终止进程。
2. 忽略信号。**SIGKILL和SIGSTOP不能被忽略**
3. 捕捉信号,自定义处理函数。
当前用户只能给自己的进程发信号,root可以给所有进程发信号。
父子进程之间的信号处理:
如果父进程用fork()创建的子进程,子进程与父进程的信号处理方式完全一致。如果父进程用vfork()+execl()方式创建的子进程,父进程自定义处理函数的子进程改为默认,其它信号的状态则不变。因为信号捕捉函数的地址很可能在新程序中已无意义。
信号的发送方式:
1. 键盘发送(部分信号)
Ctrl+C -> 信号2
Ctrl+\ -> 信号3
2. 程序错误(部分信号)
段错误、总线错误、整数被0除
3. kill命令 (所有信号)
kill -信号 进程PID
4. 系统函数 (所有信号)
raise() kill() alarm() sigqueue() ...
int kill(pid_t pid,int signo) kill函数将信号发送给进程或进出组
返回:成功返回0,失败返回-1.
pid的值:最常用的是 正数,其次是 -1
正数:就是给指定的一个进程发信号(pid)
-1 :就是给所有进程发信号(有权限的)
0 : 本组所有的进程发信号
<-1 : 指定进程组(组ID=-pid)的所有进程
int raise (int signo) 只允许进程向自身发送信号。
unsigned int alarm(unsigned int seconds) 设置一个计时器,产生SIGALRM信号,返回0或以前设置闹钟时间的剩余秒数。
int pause(void) 使调用者挂起直至捕捉到一个信号,只有执行了一个信号处理程序并从其返回,pause才返回,并将errno设置为EINTR。
中断的系统调用
低速系统调用是可能会使进程永远阻塞的一类系统调用,包括
1. 读写、打开某些类型的文件(管道,终端设备,网络设备等)
2. pause 函数和wait 函数
3. 某些ioctl 操作
4. 某些进程间通信函数
当阻塞与某个低速系统调用的一个进程捕获某个信号(信号在阻塞期间发生)且从相应处理函数返回时,该系统调用可能返回一个EINTR错误。根据具体情况处理是否需要重启被中断的系统调用。(即判断errno是否为EINTR,是则再次调用系统函数)
可重入函数
在进行信号处理(即信号处理程序中),应当调用可重入函数,否则其结果不可预见。不可重入函数的主要原因是:
1. 使用静态数据结构
2. 调用malloc 和free
3. 是标准I/O函数,标C函数很多都已不可重入方式使用全局数据结构。
当信号处理函数中调用可能修改errno值的函数时,应当先保存先前值,在其后恢复errno。
函数signal() / sigaction() 可以设置信号的处理方式。
typedef voidSigfunc (int)
Sigfunc * signal (int signo , Sigfunc * func)
即signal是一个指向Sigfunc类型函数的指针,其参数为signo和它自身一样的函数指针。**函数指针 signal(int 信号值 , 函数指针)**
func值: SIG_IGN - 忽略该信号
SIG_DFL - 恢复默认处理
自定义处理函数
返回:成功返回之前的处理方式,失败返回SIG_ERR。
sleep()和usleep()
sleep() 函数会让程序休眠,直到休眠的时间到,或者有未忽略的信号到来。会返回剩余的秒数。
usleep()函数 更精确,休眠是以 微秒为单位。
信号集和信号屏蔽
系统定义了存放多个信号的数据结构,就是信号集 (sigset_t数据类型),本质是一个超大的整数,每个二进制位 代表一个信号。
信号集的运算结构(提供函数):
sigaddset()- 增加一个信号 (某个二进制置1)
sigfillset() - 所有信号全部增加(所有位全置1)
sigdelset() - 删除一个信号
sigemptyset() - 删除所有信号(所有位置0)
sigismember() - 查询一个信号是否在
函数 sigprocmask() 可以屏蔽/解除屏蔽。
int sigprocmask(int how,sigset_t*new,sigset_t* old)
参数how是信号屏蔽的方式。
new 是新的权限屏蔽字(屏蔽哪些权限)
old是传出参数,返回以前的信号屏蔽字,便于后面恢复
how的三种方式:
SIG_BLOCK: 之前的屏蔽+new的屏蔽
SIG_UNBLOCK: 之前的屏蔽 - new的屏蔽
SIG_SETMASK:直接用新的屏蔽,与之前的无关
**SIG_SETMASK最常用。
sigsuspend
如果要在一个原子操作中先设置信号屏蔽字,然后使进程休眠,可以使用sigsuspend函数。(不可靠的实现sigpromask和pause之间的窗口期产生的信号会丢失)
int sigsuspend(const sigset_t *sigmask)
它将进程的信号屏蔽字设置为sigmask指向的值,在捕捉到一个信号或发生了一个会终止该进程的信号前,该进程被挂起。捕捉到信号后返回之前的信号屏蔽字。
sigaction() 的绝大多数的功能都可由signal()实现,一些特殊的功能sigaction()可以实现。sigaction()可以让信号有更多的信息,甚至可以在发信号的时候附带额外的数据。signal不同的实现提供不同的信号语义以达成向后兼容,可以通过sigaction函数实现自己的signal函数。
intsigaction (int signo,const struct sigaction * act , struct sigaction *oact)
若act非空,则要修改其动作;若oact非空则经该指针返回该信号的上一个动作。sigaction结构如下
struct sigaction {
void (*sa_handler) (int)
sigset_t sa_mask ;
int sa_flags;
void (*sa_sigaction )(int ,siginfo_t *, void *)
}
计时器(了解)
Linux计时器就是 N秒以后,每隔M秒产生一个信号。计时器有三种:真实、虚拟和实用,常用的是真实计时器,真实计时器产生SIGALRM信号。
函数setitimer()可以设置计时器,getitimer()可以获取计时器。