上篇博客我们说了闹钟函数alarm和pause函数,我们将他们结合起来写一个mysleep
#include<stdio.h>
#include<signal.h>
#include<unistd.h>
void sig_alrm(int signo)
{
}
unsigned int mysleep(unsigned int nsecs)
{
struct sigaction new,old;
unsigned int unslept = 0;
new.sa_handler = sig_alrm;//处理函数
sigemptyset(&new.sa_mask);
new.sa_flags = 0;
sigaction(SIGALRM,&new,&old);//注册信号处理函数
alarm(nsecs);//设置闹钟
pause();//挂起等待信号抵达
unslept = alarm(0);//清空闹钟
sigaction(SIGALRM,&old,NULL);//恢复默认信号处理
return unslept;
}
int main()
{
while(1)
{
mysleep(5);
printf("5 seconds passed\n");
}
return 0;
}
main 函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
调用alarm(nsecs)设定闹钟
调用pause等待,内核切换到别的进程运行
倒计时完成后,闹钟超时,内核发SIGALRM给这个进程
从内核态返回到这个进程的用户态之前会处理未决信号,发现有SIGALRM信号,其处理函数是sig_alrm
切换到用户态执行sig_alrm函数,进入sig_alrm函数是SIGALRM信号被自动屏蔽,从sig_alrm函数返回是SIGALRM信号就会自动解除屏蔽,然后自动执行系统调用sigreturn再次进入内核,再返回用户态继续执行进程的主控制流
在上面我们注册SIGALRM信号的处理函数
调用alarm函数设定闹钟
内核调度优先级更高的进程在pause函数之前调度,并且时间很长的话
这个时候闹钟超时了,内核发送SIGALRM信号给这个进程,处于未决状态
优先级更高的进程执行完了,内核要调回这个进程执行。SIGALRM信号抵达,执行处理函数sig_alrm之后再次进入黑河
返回这个进程的主控制流程,alarm返回,调用pause挂起等待
但是这个时候SIGALRM信号已经处理完了,但是pause还在等待却等不到信号了
出现这个问题的根本原因是系统运行的时序并不像我们写程序时所想的那样,虽然alarm闹钟函数之后紧接着就是pause,但是由于进程优先级调度的存在,无法保证pause函数一定会在alarm函数倒计时后被调用,由于一步时间在任何时候后都可能发生(这里的一步时间指出现优先级更高的进程)如果我们写程序时不考虑周全就会出现因为时序问题而出现的错误,这叫做竞态条件
要解决这个问题就要用一个函数可以将解除信号屏蔽和挂起等待信号这两步合并成一个原子操作就好了
#include<signal.h>
int sigsuspend(const sigset_t *sigmask)
和pause一样,sigsuspend没有成功返回值,只有执行了一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置位EINTR。调用sigsuspend是,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除对某个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,那么返回后仍然是屏蔽的
可重入函数
不可重入函数
在进程调用中,经常会出现多个任务调用一个函数的情况。如果一个函数被设置成这样,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果,这样的函数是不安全的,也称为不可重入函数
可重入函数
是可以被多个任务调度但是却不会影响安全的函数,这样的函数有可能是纯代码,可以允许该函数在多个副本运行,由于他们使用的是分离的栈,所以不会互相干扰。或者说如果一个函数只访问自己的局部变量或者参数,则成为可重入函数