【linux】:模拟实现sleep函数

来源:互联网 发布:mac的鼠标不能按右键 编辑:程序博客网 时间:2024/05/03 02:33

了解linux中的sleep函数

功能:将进程挂起一段时间
函数原型:

#include<unistd.h>unsigned int sleep (unsigned int seconds);//n秒

来举一个很简单的栗子:
这里写图片描述

执行以上代码后,输出结果如下:
这里写图片描述

每隔一秒打印一次,这就是sleep函数的作用。

那么如何自己模拟实现sleep呢?
先介绍一些我们会用到的函数:
(1)sigaction
这里写图片描述
作用:可以读取和修改与指定信号相关联的处理动作,
参数:signum为指定信号的编号,若act指针为NULL,或者传出原来的处理动作,oldact也可以为NULL。act和oldact都指向下面的结构体:
这里写图片描述
sa_handler的原型是一个参数为int,返回类型为void的函数指针。参数即为信号值,所以信号不能传递除信号值之外的任何信息;

  sa_sigaction是实时信号处理函数,它的原型是一个带三个参数,类型分别为int,struct siginfo ,void ,返回类型为void的函数指针。第一个参数为信号值;第二个参数是一个指向struct siginfo结构的指针,此结构中包含信号携带的数据值;第三个参数没有使用。

  sa_mask指定在信号处理程序执行过程中,哪些信号应当被阻塞。默认当前信号本身被阻塞。

  sa_flags包含了许多标志位,比较重要的一个是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以传递到信号处理函数中。即使sa_sigaction指定信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误。

  sa_restorer已过时,POSIX不支持它,不应再使用。

  因此,当你的信号需要接收附加信息的时候,你必须给sa_sigaction赋信号处理函数指针,同时还要给sa_flags赋SA_SIGINFO,

当某个信号的处理函数被调用时,内核自动将当前信号加入进程的信号屏蔽字,当信号处理函数返回时,自动恢复原来的信号屏蔽字。保证在处理某个信号时,如果这个信号再次产生,那么它会被阻塞到当前的处理结束为止。如果在调动信号处理函数时,还需要屏蔽别的信号,则可以通过sa_mask指定。

(2)pause函数
这里写图片描述

作用:挂起调用进程直到有信号递达。
如果信号的处理动作是终止进程,则进程终止。如果信号的处理动作是忽略,则进程继续处于挂起状态。只有当信号的处理动作是捕捉,pause函数才会返回。

接下来我们开始实现mysleep.

这里写图片描述

上述代码中的实现步骤为:
(1)调用sigaction()捕捉信号SIGALRM
(2)调用alarm()设定闹钟
(3)调用pause()挂起等待
(4)取消闹钟
(5)恢复捕捉动作

基于以上代码,提出几个问题。
(1)信号处理函数handler函数什么都不做,为什么还要定义它作为SIGALRM的处理函数?不定义信号处理函数可以吗?

不可以。因为pause()函数使进程挂起等待,直到有信号递达,并且要执行自定义的信号处理函数才会有机会返回。

(2)为什么在mysleep函数返回之前要恢复SIGALAM信号原来的sigaction?

main函数作为调用者只是休息片刻,并不需要改变,因此要恢复原来的sigaction

(3)mysleep的返回值表示什么含义?什么情况下返回非0值?
mysleep返回值表示闹钟剩余时间,在取消闹钟时,上一个闹钟返回0。

我们再看上面的代码,试想,如果有大量优先级较高的进程需要执行时,测试该进程就有可能被切出,即CPU资源分配给了别的进程,那么就有可能等待时间过长,导致该进程再次获得CPU资源时,闹钟时间已过,而pause就永远不会返回。

出现这个问题时由于系统执行代码时的时序不确定。如果在写程序时考虑不周密,可能由于时序问题导致错误,这就叫做竞态条件。

那么如何解决竞态条件的问题呢?
我们可以在设定闹钟之前,屏蔽信号SIGALRM,在让进程挂起时,解除对该进程的屏蔽,然后再让子进程挂起等待信号递达。我们可以用到sigsuspend函数。
这里写图片描述

sigsuspend用于在接收到某个信号之前,临时用mask替换进程的信号掩码,并暂停进程执行,直到收到信号为止。sigsuspend返回后将恢复调用之前的的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR.

代码修改如下:
这里写图片描述