一、 用alarm和pause实现sleep(3)函数,称为mysleep。
1. main函数调用mysleep函数,后者调用sigaction注册了SIGALRM信号的处理函数sig_alrm。
2. 调用alarm(seconds)设定闹钟。
3. 调用pause等待,内核切换到别的进程运行。
4. seconds秒之后,闹钟超时,内核发SIGALRM给这个进程。
5. 从内核态返回这个进程的用户态之前处理未决信号,发现有SIGALRM信号,其处理函数 是sig_alrm。
6. 切换到用户态执行sig_alrm函数,进入sig_alrm函数时SIGALRM信号被自动屏蔽, 从sig_alrm函数返回时SIGALRM信号自动解除屏蔽。然后自动执行系统调用sigreturn再次进入 内核,再返回用户态继续执行进程的主控制流程(main函数调用的mysleep函数)。
7. pause函数返回-1,然后调用alarm(0)取消闹钟,调用sigaction恢复SIGALRM信号以前的处理 动作。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void sig_alarm(int signo)//信号处理函数
{}
int mysleep(int seconds)
{
structsigaction act, oact;
act.sa_handler= sig_alarm;
sigemptyset(&act.sa_mask);
act.sa_flags= 0;
sigaction(SIGALRM,&act, &oact);//注册信号处理函数
alarm(seconds);//设置闹钟
pause();
int_time = alarm(0);//清空闹钟
sigaction(SIGALRM,&oact,NULL);//恢复默认信号处理动作
return_time;
}
int main()
{
while(1)
{
printf("iam sleeping !\n");
mysleep(3);
}
return0;
}
运行结果:每3秒打印一条语句
缺陷:在闹钟设置之后,响应之前被切出去,再过3秒再切回来,就将永远收不到信号,进程将被永远挂起。
思考问题:
1、 信号处理函数sig_alrm什么都没⼲干,为什么还要注册它作为SIGALRM的处理函数?不注册信号处 理函数可以吗?
信号处理机制:
用函数signal注册一个信号捕捉函数。原型为:
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal 的第1个参数signum表示要捕捉的信号,第2个参数是个函数指针,表示要对该信号进行捕捉的函数,该参数也可以是SIG_DEF(表示交由系统缺省处理,相当于白注册了)或SIG_IGN(表示忽略掉该信号而不做任何处理)。signal如果调用成功,返回以前该信号的处理函数的地址,否则返回 SIG_ERR。
sighandler_t是信号捕捉函数,由signal函数注册,注册以后,在整个进程运行过程中均有效,并且对不同的信号可以注册同一个信号捕捉函数。该函数只有一个参数,表示信号值。
示例:
1)、 捕捉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
void SignHandler(int iSignNo)
{
printf("Capture sign no:%d/n",iSignNo);
}
int main()
{
signal(SIGINT,SignHandler);
while(true)
sleep(1);
return 0;
}
该程序运行起来以后,通过按CTRL+c将不再终止程序的运行。应为CTRL+c产生的SIGINT信号已经由进程中注册的SignHandler函数捕捉了。该程序可以通过Ctrl+/终止,因为组合键Ctrl+/能够产生SIGQUIT信号,而该信号的捕捉函数尚未在程序中注册。
2)、 忽略掉终端CTRL+c产生的SIGINT信号:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
signal(SIGINT,SIG_IGN);
while(true)
sleep(1);
return 0;
}
该程序运行起来以后,将CTRL+C产生的SIGINT信号忽略掉了,所以CTRL+C将不再能是该进程终止,要终止该进程,可以向进程发送SIGQUIT信号,即组合键CTRL+/
3)、 接受信号的默认处理,接受默认处理就相当于没有写信号处理程序:
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{
signal(SIGINT,DEF);
while(true)
sleep(1);
return 0;
}
2、为什么在mysleep函数返回前要恢复SIGALRM信号原来的sigaction?
当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时自动恢复原来的信号屏蔽字,这样就保证了在处理某个信号时,如果这种信号再次产生,那么它会被阻塞到当前处理结束为止。
二、 sigsuspend实现mysleep 函数:
对于第一个版本的mysleep , 出现这个问题的根本原因是系统运行的时序(Timing)并不像我写程序时所设想的那样。虽然alarm(nsecs)紧接着的下⼀行就是pause(),但是⽆无法保证pause()⼀一定会在调用alarm(nsecs)之后的nsecs秒之内被调⽤用。由于异步事件在任何时候都有可能发⽣生(这⾥的异步事件指出现更高优 先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误,这叫做竞态条件 (Race Condition)。
sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对时序要求严格的场合下都应该调⽤用sigsuspend而不是pause。
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
和pause⼀一样,sigsuspend没有成功返回值,只有执⾏行了⼀一个信号处理函数之后sigsuspend才返回,返回值为-1,errno设置为EINTR。
调⽤用sigsuspend时,进程的信号屏蔽字由sigmask参数指定,可以通过指定sigmask来临时解除 对某 个信号的屏蔽,然后挂起等待,当sigsuspend返回时,进程的信号屏蔽字恢复为原来的值,如果原来对该信号是屏蔽的,从sigsuspend返回后仍然是屏蔽的。
以下⽤用sigsuspend重新实现mysleep函数:
1、 屏蔽SIGALARM信号
2、 alarm(seconds);
3、 解除对SIGALARM信号的屏蔽。
4、 挂起等待pause();
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
void sig_alarm(int signo)
{}
int mysleep(int seconds)
{
structsigaction act, oact;
sigset_tnewmask, oldmask, suspmask;
//设置信号处理函数,保存以前的信息
unsignedint unslept;
act.sa_handler= sig_alarm;
sigemptyset(&act.sa_mask);
act.sa_flags= 0;
sigaction(SIGALRM,&act, &oact);
//阻塞信号,保存当前的信号屏蔽字
sigemptyset(&newmask);
sigaddset(&newmask,SIGALRM);
sigprocmask(SIG_BLOCK,&newmask, &oldmask);
//屏蔽SIGALRM
alarm(seconds);
suspmask= oldmask;
sigdelset(&suspmask,SIGALRM);
sigsuspend(&suspmask);
//解除屏蔽,挂起等待//SIGALRM信号递达后,sigsuspend返回,自动恢复原来的屏蔽字,自动恢复原来的屏蔽字,即再次屏蔽SIGMASK
int _time =alarm(0);
sigaction(SIGALRM,&act, NULL);
//恢复默认的信号处理动作
sigprocmask(SIG_SETMASK,&oldmask, NULL);
//重置信号屏蔽字,再次解除对SIGALRM的屏蔽。
return_time;
}
int main()
{
while(1)
{
mysleep(5);
printf("iam sleeping !\n");
}
return0;
}
运行结果:每隔五秒响应动作,打印语句。