4、信号并发(李慧琴课程笔记)

本文详细介绍了Linux中的信号处理,包括信号的概念、查询和通知方法、signal函数、可重入函数特性、信号响应过程、常用信号处理函数(如kill、raise、pause等)、高级信号管理(如setitimer和sigaction),以及实时信号的区别。重点讲解了信号的并发处理和控制策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4、信号并发(李慧琴课程笔记)

4.1信号

异步事件的处理:查询法,通知法

信号的是软件中断,信号的响应依赖于中断。

4.1.1 signal函数

一般用下面这种形式

void (*signal(int signum, void (*func)(int)))(int);
/*signal返回值是在给一个信号定义新的行为时,顺便来保存旧的行为。*/
static void int_handler(int s)
{
    write(1, "!", 1);
}
int main()
{
    int i;
    //signal(SIGINT,SIG_IGN);/*忽略掉SIGINT这个中断信号,其实也就是ctl+c*/
    signal(SIGINT,int_handler);/*在程序没结束的时候,信号来了才会去执行int_handler函数里面的内容,信号会打断阻塞的系统调用*/
    for(i=0; i < 10; i++)
    {
        write(1, "*", 1);
        sleep(1);/*一秒钟打印一次*号*/
    }
}

补充:信号不可靠

信号会打断阻塞的系统调用。

当阻塞等待打开或者完成一个设备的过程中,有可能会被signal handler打断,就会报错。

如下图所示,if(errno!=EINTR)会判断是真错还是假错,假错可能就是被信号打断了,那么重新执行一次OPEN函数。

在这里插入图片描述

4.1.2 可重入函数

**概念:**第一次调用还未结束,第二次调用已经开始,而且不会出错

所有的系统调用都是可重入的,一部分库函数也是可重入的,比如说memcpy。

还有就是rand和rand_r函数,rand_r函数就是可重入的,但是rand不行。

在看函数手册过程中,如果函数有_r的选项,那么原函数一定不可重入。

4.1.3 信号的相应过程

信号从收到到响应有一个不可避免的延迟。是因为有中断把我当前执行过程打断,我内核态往用户态走的时候才能响应这个信号

思考:1.如何忽略掉一个信号?

内核中有一个全为1的mask列表和一个全为0的pending列表,当当前进程被信号打断时,相应的pending位会被置1,这时候与上mask就知道是哪个中断类型,然后mask和pending位置0,从而进入内核态。这时候不是发生了中断么,那么肯定会保存当前进程的执行代码的地址,在执行中断完毕后回到这个位置,于此同时我们还要执行信号处理函数,因此这个地址会被替换为信号处理函数的地址,然后回到用户态,在执行完以后再替换回最开始的地址回到用户态继续执行,同时mask和pending位也要回复到原来的状态。

在这里插入图片描述

我们不能阻止信号的到来,但是可以决定信号是否被响应,也可以决定什么时候被响应。比如signal函数有一个选项ignore,就可以将mask永远置0,则永远匹配不了这个pending信号。

2.标准信号为什么要丢失?

是用位图来保存的,因此来了一万个可能也就一共置1次1。

3.信号是从kernel态回到user态的路上响应的。

4.标准信号的相应没有严格的顺序:

在有中断优先级的情况下还是有顺序的,但是如果是平级的话就没有严格的顺序了。

4.1.4 常用函数(kill、raise、pause)

sleep函数尽量不要使用,在有的环境下sleep是由alarm和pause封装的,而我们的一般是nanosleep封装的,所以考虑到移植一般不用sleep函数,因为如果是用alarm和pause封装的,那么alarm时间有可能会不准确,可能会出现另外一个alarm函数替换掉时间

alarm函数以秒为单位将SIGALRM信号传递给调用进程,SIGALRM的默认机制是杀死当前进程

Linux使用单一计时器,构造一组函数,实现任意数量的计时器_仅使用一个定时器,构造一组函数-优快云博客-----------------alarm相关

令牌桶实现-优快云博客----------------------------------alarm和signal相关

signal返回值是在给一个信号定义新的行为时,顺便来保存旧的行为。

1int kill(pid_t pid, int sig);/*pid>0,给指定进程发送信号;给进程发送信号,pid为0,则给本组所有进程发送信号;pid为-1则给所有进程发一次信号除了一号初始进程;如果pid<-1则发送给进程组为-pid(也就是绝对值)的所有进程信号。sig为0则用于检查进程或者进程组是否存在,若返回值为0则存在,返回值为-1则需要判断错误类型,若为EINVAL,指定了一个无效信号,若为EPERM则为这个进程存在但不允许被发送信号,ESRCH则为这个信号不存在*/

2raise(int sig);/*给当前进程或者线程发送信号,相当于kill(getpid(), sig);;在多线程情况下相当于pthread_kill(pthread_self(),sig);*/

3alarm(unsigned int seconds);/*alram无法实现多函数的计时器,比如说我alarm(10)然后紧跟一个alarm(1)只会计数一秒然后退出程序,*/

4pause();/*等待一个信号打断他,类似于阻塞*/
pause()函数:调用该函数(系统调用)的进程将处于阻塞状态(主动放弃cpu),直到有信号递达将其唤醒。
/*比如说*/
int main()
{
    alarm(10);
    alarm(1);
    alarm(5);
    
    while(1)
        pause();
}
/*时间函数time与信号alarm的区别在与alarm更加精确,尤其是在变量优化之后,volatile优化要判断的变量*/
/*在alarm与signal信号之间一定要有顺序要求,即设置时钟信号的行为一定会在发出第一个alarm信号之前*/
/*必须如下所示
signal(SIGALRM,alarm_handler);
alarm(5);
*/

5setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
/*
which有三个选项:1、ITIMER_REAL--------实时时间
			   2、ITIMER_VIRTUAL------虚拟时间,仅在进程进行时递减,然后到期时返回SIGVTALRM
			   3、ITIMER_PROF-------通常用于应用程序在用户和内核空间中花费的时间
setitimer可以实现周期时钟(因为itimerval结构体)

new_value:参数是一个指向itimerval结构的指针,用于设置新的定时器值;
old_value:参数则是一个指向itimerval结构的指针,用于获取旧的定时器值;

当it_value使用完以后,it_interval会自动填充到it_value,因此可以构成周期性时钟
struct itimerval{
	struct	timeval it_interval; -----next value
	struct	timeval it_value;	------current value
}

	struct timeval{
		long tv_sec;------seconds
		long tv_usec;------microseconds
	}


*/

6abort();
/*调用abort函数会异常的终止进程,并且,会刷新缓冲区,因为abort函数内部调用了fflush(NULL),刷新缓冲区不代表调用了fclose。*/

7system();
/*在有信号参与的程序当中,要阻塞住一个信号,然后要忽略掉SIGINT、SIGQUIT两个信号*/

8sleep();
/*	尽量不要使用
	可以用nanosleep来替代
	可以用usleep来替代
*/

4.1.5 信号集

#include <signal.h>

int sigemptyset(sigset_t *set);

int sigfillset(sigset_t *set);

int sigaddset(sigset_t *set, int signum);

int sigdelset(sigset_t *set, int signum);

int sigismember(const sigset_t *set, int signum);

4.1.6 信号屏蔽字

信号中,以下两个函数不能轻易的从信号中跳出去

#include <setjmp.h>
int setjmp(jmp_buf env);/*有歧义,因此不能轻易跳出去*/
int sigsetjmp(sigjmp_buf env, int savesigs);/*savesigs为真保存掩码集,为假不保存掩码集,同样不能轻易跳出*/
1sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
/*
how---三种方式:1、SIG_BLOCK 阻塞,相当于屏蔽这个信号
			  2、SIG_UNBLOCK 非阻塞,解除对这个信号的屏蔽
			  3、SIG_SETMASK 如果这项被选定 则是恢复set的内容并且保存恢复之前oldset的信号情况
			  	例子:sigprocmask(SIG_BLOCK, &set, &oldset);
			  		 sigprocmask(SIG_SETMASK,&oldset,NULL);

例子:一开始设置为SIG_BLOCK 之后连续发送很多次信号过来,在SIG_UNBLOCK之后,该信号只会响应一次

set------针对信号集的所有信号,比如我set放入三个信号,然后选择how里面三种方式中的一种

oldset------保留对mask执行动作之前mask的状态,有的情况需要用到oldset这个变量来保存之前的状态,在一系列操作以后返回之前的这个状态

*/

2int sigpending(sigset_t *set);
/*
涉及到pending集的处理
*/






4.1.7 扩展

#include <signal.h>
int sigsuspend(const sigset_t *mask);/*
			 sigsuspend的整个操作都是原子的,也就不允许被打断的。sigsuspend的整个原子操作过程为:

              (1) 设置新的mask阻塞当前进程;

              (2) 收到信号,恢复原先mask;

              (3) 调用该进程设置的信号处理函数;

              (4) 待信号处理函数返回后,sigsuspend返回。
              类似于原子化的pause()函数
              						*/
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    /*
    struct sigaction{
    	void (*sa_handler)(int);				左侧两个void函数选其一用
    	void (*sa_sigaction)(int ,siginfo_t *, void *);  较为重要,参数int可以是多个信号集合,第二个参数可以决定响应的信号源,具体可以查看man手册,其中si_code用的比较多
    	sigset_t sa_mask;
    	int sa_flags;
    	void (*sa_restorer)(void);
    };
    sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
    sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
    sa_flags 用来设置信号处理的其他相关操作,下列的数值可用。 
    如果sa_flags为SA_SIGINFO则表示三参的信号处理函数。
    SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
    SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
    SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
    
    */

4.1.8 实时信号

实时信号不会像标准信号一样丢失。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值