Linux信号(二)——编写自己的mysleep函数

1.alarm函数和pause函数

使用alarm函数可以设置一个时间值(闹钟时间),在将来的某个时刻这个值会被超过。当所设置的时间超过后,产生SIGALRM信号。如果不忽略或不捕捉此信号,则其默认动作是终止该进程。参数seconds的值是秒数,经历了指定的seconds秒后会产生信号SIGALRM。
#include 
unsigned  int alarm(unsigned  int seconds);
                   //返回:0或者以前设置的闹钟的余留秒数       
 
使用pause函数使调用进程挂起直至捕捉到一个信号。只有执行一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,errno设置为ENTER。
#include 
int  pause(void);
                   //返回:-1,error设置为EINTR

2.sigaction函数

sigaction函数的功能是检查或者修改(或两者)与指定信号相关联的处理动作。signo是要检测或修改的具体动作的信号的编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统返回该信号的原先动作。
参数结构sigaction结构如下:

struct sigaction
	{

		void (*sa_handler) (int);//sa_handler指向一个信号捕捉函数(不是SIG_IGN(忽视)或SIG_DFL(默认处理))

		sigset_t sa_mask;//在调用该信号处理函数时,除了当前信号被自动屏蔽之外,还希望屏蔽一些其他信号,则用sa_mask字段说明这些需要额外屏蔽的信号
				//当信号处理函数返回自动恢复原来的信号屏蔽字
		int sa_flags;//sa_flags字段包含对信息进行处理的各个选项

	}

#include 
int sigaction(int sig, const struct sigaction* act ,struct sigaction*ocat );
                  //成功返回0,失败返回-1并设置error


3.编写普通版本的sleep函数

#include 
#include 
#include 
#include 
#include 
static void sigAlrm(int signo)
{
    return ;
}
unsigned int  mysleep(unsigned int nsecs)
{
    struct sigaction act,oact;
    act.sa_handler=sigAlrm;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    if(sigaction(SIGALRM,&act,&oact)==0){
        alarm(nsecs);
        pause();
        return ( alarm(0));
    }   
    else{
        perror("sigacion error\n");
    }   
}
int main()
{
    while(1){
        mysleep(4);
        printf("test mysleep\n");
    }   
    return 0;
}



普通版本的sleep函数存在如下缺点
(1)如果调用者已经设置了闹钟,则它被mysleep函数中的第一次alarm调用擦去。

(2)该程序修改了对SIGALRM的配置。如果编写一个函数供其他函数调用,则在该函数被调用时要先保存原配置,在该函数返回之前再恢复原配置。

(3)因为pause函数使调用进程挂起直到捕捉到一个信号,在调用alarm和pause之间有个竞态条件,在一个繁忙的系统,alarm调用pause之前可能超时,并调用了信号处理程序。如果发生这种情况,则在调用pause之后,如果没有捕捉到其它信号,该调用者会被永远挂起。

注: 需要注意的是虽然sig_alrm函数什么都没干,但还是得注册作为SIGALRM的处理函数,因为SIGALRM信号的默认处理是终止进程,这也是在mysleep函数返回时要恢复SIGALRM信号原来的sigaction的原因。

竞态条件:由于异步事件在任何时候都有可能发生(这里的异步事件指出现更高优先级的进程),如果我们写程序时考虑不周密,就可能由于时序问题而导致错误。

4.sigsuspend函数

该函数提供一种功能,需要在一个原子操作中实现恢复信号屏蔽字,然后使进程睡眠。能将  “解除信号屏蔽”和“挂起等待信号”这两步能合并成一个原子操作,这正是sigsuspend函数的功能。sigsuspend包含了pause的挂起等待功能,同时解决了竞态条件的问题,在对  时序要求严格的场合下都应该调用sigsuspend而不是pause。
进程的信号屏蔽字设置为sigmask指向的值。在捕捉到一个信号或发生了一个终止信号之前,该信号也被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,且将该进程的信号屏蔽字设置为调用sigsuspend之前。

#include 
int sigsuspend(const sigset_t * sigmask);
                        //成功没有返回值,返回到调用者,总是返回-1,并且errno设置为EINTR(表示一个被中断的系统调用)



5.用sigsuspend重新实现mysleep函数


#include 
#include 
#include 
static void sigAlrm(int signo)
{
    return ;
}
unsigned int  mysleep(unsigned int nsecs)
{
    struct sigaction newact,oldact;
    sigset_t newmask,oldmask,suspmask; 
    unsigned int unslept=0;
    newact.sa_handler=sigAlrm;
    sigemptyset(&newact.sa_mask);
    newact.sa_flags=0;
    if(sigaction(SIGALRM,&newact,&oldact)==0){
         sigemptyset(&newmask);
         sigaddset(&newmask,SIGALRM);
         sigprocmask(SIG_BLOCK,&newmask,&oldmask);
         alarm(nsecs);
         suspmask=oldmask;
         sigdelset(&suspmask,SIGALRM);
         sigsuspend(&suspmask);
         unslept=alarm(0);
         sigaction(SIGALRM,&oldact,NULL);
         sigprocmask(SIG_SETMASK,&oldmask,NULL);
         return unslept;
    }   
    else{
        perror("sigacion error\n");
    }   
}
int main()
{
    while(1){
        mysleep(2);
        printf("test mysleep\n");
    }
    return 0;
}


你可能感兴趣的:(Linux)