- 作者: 雪山肥鱼
- 时间:20210718S 20:37
- 目的:信号集、未决信号集,信号屏蔽字等
# 信号机制
# 信号集
## 相关系统调用
## 读取未决信号机
# 捕捉信号
## sigact.sa_mask 的作用
# 系统调用函数被中断
# raise() 自己给自己发信号
# alarm
信号机制
Linux的信号也成为软中断,是软件层次上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理起收到一个中断请求来说是一样的。
信号是异步的,一个进程不必通过任何操作来等待信号的到达。 信号是进程间通信异步通信机制。
- 硬件信号
除0, 段错误等 - 软件来源
kill rais alarm cll + c 等
kill -l 可以查看所有信号,只了解了 前32个 从unix继承来的经典信号。
信号集
信号集故名思意是 信号的集合, 信号集的类型为sigset_t, 每一种信号用1bit 来表示。
sizeof(sigset_t) = 8 ,即8个字节,64bit. 表示了 64种信号.足够表示所有信号
-
pending 未决信号集 -- sigset_t
信号进来,在未决信号集中,将自己对应的信号bit置为1
但实际上并没有对信号进行处理。 -
信号屏蔽字 -- sigset_t
是否让信号进来
0 : 允许
1 : 不允许此信号进来,忽略 此信号 -
信号的抵达
- 默认处理
- TERM
- CORE
- IGNORE
- STOP 挂起
- CONTINUE 继续S
- 忽略
- 捕捉
前32位经典信号:未决信号集并不支持排队。
如果同时来了很多个 相同的型好,Ctrl+C, 未决信号集只会把对应的bit置为1, 只会把所有的ctrl+c 当作一次处理
后32位经典信号:支持排队
用户无法修改 未决信号集,只有内核可以修改
用户可以修改信号屏蔽字
相关系统调用
信号机制的流程.png信号处理函数
int sigemtyset(sigset_t * set); //将set 每一位置为0
int sigfillset(sigset_t * set) ; //将set 每一位置为1
int sigaddset(sigset_t *set, int signo); signo信号对应的位置1
int sigdelset(sigset_t *set, int signo); signo信号对于国内位置0
int sigismember(const sigset_t *set, int signo); //怕段set中signo信号对应的bit是否位1,返回 1 or 0
int sigprocmask(int how, const sigset_t * set, sigset *oset);
/* how:
SIG_BLOCK set 包含了我们希望添加到当前信号屏蔽字的信号, 相当于 mast = mask|set
SIG_UNBLOCK set 包含了我们希望从当前信号屏蔽字中接触阻塞的信号, 相当于 mast = mask&~set,取反求与操作
SIG_SETMASK 相当于当前信号屏蔽字位set 所指向的值,相当于 mask=set
set: 传入参数
oset: 传出参数,原来的信号屏蔽字,一般可以填0
return: 成功返回0 , 否则出错则位-1
*/
通过上述系统调用可以看出来,默认的信号屏蔽字全位0, 通过我们手动的创建的set 去修改原来默认的信号屏蔽字。
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
int main(int argc, char **argv) {
sigset_t mask_set;
sigemptyset(&mask_set);
sigaddset(&mask_set, SIGINT);
sigaddset(&mask_set, SIGQUIT);
sigprockask(SIGBLOCK, &mask_set, NULL);
while(1)
sleep(1);
return 0;
}
直接在信号屏蔽字阻塞掉,不会走到后续的信号处理函数。
kill -9 与 kill -7 不能被阻塞 不能被捕捉,不能被忽略略
挂起,以免某些进程一直在竞争CPU
读取未决信号集
读取当前进程的未决信号集
#include <signal.h>
int sigpending(sigset_t *set);
/*
通过set 参数传出,调用成功则返回0 否则返回
*/
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
void print_set(sigset_t set) {
int i = 0;
for(int i = 1; i<32; i++)
if(sigismember(&set, i))
putchar('1');
else
putchar('0');
putchar('\n');
}
int main(int argc, char **argv) {
sigset_t mask_set, pending_set;
sigemptyset(&mask_set);
sigaddset(&mask_set, SIGINT);
sigaddset(&mask_set, SIGQUIT);
sigprockmask(SIGBLOCK, &mask_set, NULL);
while(1) {
sigpending(&pending_set);
print_set(pending_set);
sleep(1);
}
return 0;
}
有意思的测试:
int count =0;
while(1) {
sigpending(&pending_set);
print_set(pending_set);
if(count ++ = 10)
sigprocmask(SIG_UNBLOCK, &mark_set, NULL);
sleep(1);
}
在10s之前 我连续输入多次 ctrl +c,此时ctrl+c 一直在未决信号集中,此时信号屏蔽字一直将信号 SIGINT 阻塞。
当过了10s后 清除信号屏蔽字,则开始响应 ctrl+c。仅仅响应一次S
捕捉信号
#include <signal.h>
int sigaction(int signum, const struct sigaction * act, struct sigaction * oldact);
struct sigaction {
void (*sa_handler) (int);
void (*sa_sigaction) (int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
/*
sa_handler : 早期的捕捉函数
sa_sigaction: 新增加的捕捉函数,可以传参,和 sa_handler 互斥,两者通过sa_flags选择哪种捕捉函数
sa_mask:执行捕捉函数时, 设置阻塞其他信号,sa_mask|进程阻塞信号集,退出捕捉函数后,还原回原有的阻塞信号集
sa_flags: 值为SA_SIGINFO时,用sa_sigacton, 当未指定值时用sa_handler, SA_RESTART 让被打断的系统调用重新开始
sa_restorer: 保留,已过时
*/
#Include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void sig_handler(int sig) {
printf("sig_handler %d\n", sig);
}
int main(int argc, char **argv) {
struct sigaction sigact;
sigact.sa_flags = 0;
sigact.sa_handler = sig_handler;
sigemptyset(&sigact.sa_mask);
sigaction(SIGINT, &sigact, NULL);
while(1)
sleep(1);
return 0;
}
sigact.sa_maks 的作用
- 中断信号来了, 进入中断函数1
- 此时如果相同的中断信号进入,则一直在未决信号集排队,等到信号处理函数结束后,响应
- 如果来的是不同的中断信号,则在中断函数1中,会被打断,响应新的信号。
如果在中断函数1中, 不想响应指定的新的信号。 则可以用到,sa_mask
#Include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void sig_handler(int sig) {
printf("sig_handler %d\n", sig);
sleep(5);
pirntf("sig_handler end\n");
}
int main(int argc, char **argv) {
struct sigaction sigact;
sigact.sa_flags = 0;
sigact.sa_handler = sig_handler;
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask, SIGQUIT);
sigaction(SIGINT, &sigact, NULL);
while(1)
sleep(1);
return 0;
}
只是在中断函数执行的过程中忽略响应信号,中断函数1结束后,会继续响应信号.
sigaddset(&sigact.sa_mask, SIGQUIT);
sigaction(SIGINT, &sigact,NULL) //相当于增加信号的一种属性啦
vs.
sigaddset(&mask_set, SIGQUIT);
sigprockmask(SIGBLOCK, &mask_set, NULL);
- 信号屏蔽字在进程中永远有效
- sigaction.sa_mask 只在中断函数中有效,中断函数结束后就没用啦。
系统调用函数被中断
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
void sig_handler(int sig) {
printf("sig_handler %d\n", sig);
sleep(5);
printf("sig_handler end\n");
}
int main(int argc, char ** argv) {
char buf[32];
int ret;
struct sigaction sigact;
sigact.sa_flags = SA_RESTART;
sigact.sa_hanlder = sig_handler;
sigemptyset(&sigact.sa_mask);
sigaddset(&sigact.sa_mask, SIGQUIT);
sigaction(SIGINT, &sigact, NULL);
ret = read(STDIN_FILENO, buf, sizeof(buf));
if(ret == -1) {
if(errno = EINTR)
perror("read");
else
exit(1);
}
}
read 系统调用被信号打断,可以设置sigaction 的 sa_flags, 就可以重新执行系统调用了。
另外的处理方式:系统调用被打断,重新读buffer
size_t readn(int fd, void * usrbuf, size_t n) {
size_t nleft = n;
ssize_t nread;
char * bufp = usrbuf;
while(nleft > 0) {
if(nread = read(fd, bufp, nleft)) == -1)
{
if(errno == EINTER) { /*中断继续*/ contnue; }
else {return -1} /*error*/
}
else if( nread == 0 ) {break; /*督导文件末尾EOF*/
else
{
nleft -= nread;
bufp += nread;
}
}
return (n-nleft);
}
raise() 自己给自己发信号
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void handle_sig(int sig) {
if(sig == SIGBUS)
{
printf("get SIGBUS\n");
}
}
int main(int argc, char ** argv) {
signal(SIGBUS, handle_sig);
raise(SIGBUS);
return 0;
}
alarm() && setitimer()
定时器函数,alarm() 专门为SIGALRM 信号设计
alarm 的默认动作是 TERM
#include <signal.h>
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#Include <fcntl.h>
void sig_handler(int sig) {
printf("signal%d\n", sig);
alarm(5);
}
int main(int argc, char ** argv) {
signal(SIGALRM, sig_handler);
alarm(5);
while(1) {
puts("helloworld\n");
sleep(1);
}
}
现象: 每间隔5s 打印一次 signal 14
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
void sig_handler(int sig) {
printf("signal%d\n", sig);
alarm(5);
}
int main(int argc, char ** argv) {
signal(SIGALARM, handle_sig);
struct itimerval timval;
timval.it_interval.tv_sec = 1;
timval.it_interval.tv_usec = 0;
timval.it_value.tv_sec = 5; //第一次触发时间
timval.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &timval, NULL);
while(1)
{
sleep(1);
}
return 0;
}
网友评论