sleep与信号唤醒的问题 & 内核对信号的处理方式 & udelay

http://www.cnblogs.com/charlesblc/p/6277848.htmlhtml

 

注意,sleep是会被信号唤醒的。linux

 

 
 

 

内核对信号的处理方式

参考 http://blog.csdn.net/lina_acm/article/details/51510783编程

 

内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。这里要补充的是,若是信号发送给一个正在睡眠的进程,那么要看该进程进入睡眠的优先级,若是进程睡眠在可被中断的优先级上,则唤醒进程;不然仅设置进程表中信号域相应的位,而不唤醒进程。这一点比较重要,由于进程检查是否收到信号的时机是:一个进程在即将从内核态返回到用户态时;或者,在一个进程要进入或离开一个适当的低调度优先级睡眠状态时。函数

感受,sleep函数,都是可被中断的吧。不是很肯定。post

 

内核处理一个进程收到的信号的时机是在一个进程从内核态返回用户态时。因此,当一个进程在内核态下运行时,软中断信号并不当即起做用,要等到将返回用户态时才处理。进程只有处理完信号才会返回用户态,进程在用户态下不会有未处理完的信号。学习

 

处理信号有三种类型:进程接收到信号后退出;进程忽略该信号;进程收到信号后执行用户设定用系统调用signal的函数。当进程接收到一个它忽略的信号时,进程丢弃该信号,就象没有收到该信号似的继续运行。若是进程收到一个要捕捉的信号,那么进程从内核态返回用户态时执行用户定义的函数。spa

并且执行用户定义的函数的方法很巧妙,内核是在用户栈上建立一个新的层,该层中将返回地址的值设置成用户定义的处理函数的地址,这样进程从内核返回弹出栈顶时就返回到用户定义的函数处,从函数返回再弹出栈顶时,才返回原先进入内核的地方。这样作的缘由是用户定义的处理函数不能且不容许在内核态下执行(若是用户定义的函数在内核态下运行的话,用户就能够得到任何权限)。.net

 

在BSD系统中,内核模拟了对硬件中断的处理方法,即在处理某个中断时,阻止接收新的该类中断。code

 

第二个要引发注意的是,若是要捕捉的信号发生于进程正在一个系统调用中时,而且该进程睡眠在可中断的优先级上,这时该信号引发进程做一次longjmp,跳出睡眠状态,返回用户态并执行信号处理例程。当从信号处理例程返回时,进程就象从系统调用返回同样,但返回了一个错误代码,指出该次系统调用曾经被中断。这要注意的是,BSD系统中内核能够自动地从新开始系统调用。htm

具体能够参考下一篇文章:http://www.cnblogs.com/charlesblc/p/6277921.html

 

第三个要注意的地方:若进程睡眠在可中断的优先级上,则当它收到一个要忽略的信号时,该进程被唤醒,但不作longjmp,通常是继续睡眠。但用户感受不到进程曾经被唤醒,而是象没有发生过该信号同样。

 

第四个要注意的地方:内核对子进程终止(SIGCLD)信号的处理方法与其余信号有所区别。当进程检查出收到了一个子进程终止的信号时,缺省状况下,该进程就象没有收到该信号似的,若是父进程执行了系统调用wait,进程将从系统调用wait中醒来并返回wait调用,执行一系列wait调用的后续操做(找出僵死的子进程,释放子进程的进程表项),而后从wait中返回。SIGCLD信号的做用是唤醒一个睡眠在可被中断优先级上的进程。若是该进程捕捉了这个信号,就象普通讯号处理同样转处处理例程。若是进程忽略该信号,那么系统调用wait的动做就有所不一样,由于SIGCLD的做用仅仅是唤醒一个睡眠在可被中断优先级上的进程,那么执行wait调用的父进程被唤醒继续执行wait调用的后续操做,而后等待其余的子进程。

 

二、setjmp和longjmp的做用

前面在介绍信号处理机制时,屡次提到了setjmp和longjmp,但没有仔细说明它们的做用和实现方法。这里就此做一个简单的介绍。

在介绍信号的时候,咱们看到多个地方要求进程在检查收到信号后,从原来的系统调用中直接返回,而不是等到该调用完成。这种进程忽然改变其上下文的状况,就是使用setjmp和longjmp的结果。setjmp将保存的上下文存入用户区,并继续在旧的上下文中执行。这就是说,进程执行一个系统调用,当由于资源或其余缘由要去睡眠时,内核为进程做了一次setjmp,若是在睡眠中被信号唤醒,进程不能再进入睡眠时,内核为进程调用longjmp,该操做是内核为进程将原先setjmp调用保存在进程用户区的上下文恢复成如今的上下文,这样就使得进程能够恢复等待资源前的状态,并且内核为setjmp返回1,使得进程知道(注:知道是从longjmp调用的)。这就是它们的做用。

 

时间单位:  
  毫秒(ms)、微秒 (μs)、纳秒(ns)、皮秒(ps)、飞秒(fs)、阿秒、渺秒  
  1 s = 10^3 ms = 10^6 us = 10^9 ns = 10^12 ps = 10^15 fs=10^18阿秒=10^21渺秒=10^43普朗克常数

在Linux Driver开发中,常常要用到延迟函数:msleep,mdelay/udelay.

虽然msleep和mdelay都有延迟的做用,但他们是有区别的.

mdeday还忙等待函数(至关于for循环)在延迟过程当中没法运行其余任务.这个延迟的时间是准确的.是须要等待多少时间就会真正等待多少时间.而msleep是休眠函数,它不涉及忙等待.你若是是msleep(10),那实际上延迟的时间,大部分时候是要多于10ms的,是个不定的时间值.

他们的差别,平时我也讲的出来,但是真正用起来的时候,就忘记了.曾在两个driver的i2c的code中,须要用到delay函数,而我用了msleep函数,一直I2C速度超慢.而我又不知道哪里出了问题,我潜意识中,认为我只delay了1ms,但是,其实是十几毫秒.

 

这几个函数都是内核的延时函数:

1.

udelay(); mdelay(); ndelay();实现的原理本质上都是忙等待,ndelay和mdelay都是经过udelay衍生出来的,咱们使用这些函数的实现每每会碰到编译器的警告implicit declaration of function'udelay',这每每是因为头文件的使用不当形成的。在include/asm-???/delay.h中定义了udelay(),而在include/linux/delay.h中定义了mdelay和ndelay.(这点弄错了吧,应该是ndelay最小吧)

 

udelay通常适用于一个比较小的delay,若是你填的数大于2000,系统会认为你这个是一个错误的delay函数,所以若是须要2ms以上的delay须要使用mdelay函数。

 

2.因为这些delay函数本质上都是忙等待,对于长时间的忙等待意味这无谓的耗费着cpu的资源,所以对于毫秒级的延时,内核提供了msleep,ssleep等函数,这些函数将使得调用它的进程睡眠参数指定的时间

 

应用层:
   #include <unistd.h>
   一、unsigned int sleep(unsigned int seconds); 秒级
   二、int usleep(useconds_t usec);              微秒级:1/10^-6
   #define _POSIX_C_SOURCE 199309
   #include <time.h>
   三、int nanosleep(const struct timespec *req, struct timespec *rem);
       struct timespec {
                  time_t tv_sec;        /* seconds */
                  long   tv_nsec;       /* nanoseconds */
              };
       // The value of the nanoseconds field must be in the range 0 to 999999999.
 
 内核层:
   include <linux/delay.h>
   一、void ndelay(unsigned long nsecs);         纳秒级:1/10^-10
   二、void udelay(unsigned long usecs);         微秒级: 1/10^-6
   三、void mdelay(unsigned long msecs);         毫秒级:1/10^-3

 

sleep_on(), interruptible_sleep_on(); 
sleep_on_timeout(), interruptible_sleep_on_timeout(); 
根据你的状况选用这些函数,注意: sleep操做在kernel必须当心、当心。。。 
udelay()等函数是cpu忙等,没有传统意义上的sleep。这些函数至关于咱们平时的阻塞读、写之类的语义,主要用于等外设完成某些操做

 

------

nanosleep:

 struct timespec
              {
                      time_t  tv_sec;         /* seconds */
                      long    tv_nsec;        /* nanoseconds */
              };

这个函数功能是暂停某个进程直到你规定的时间后恢复,参数req就是你要暂停的时间,其中req->tv_sec是以秒为单位,而tv_nsec以毫微秒为单位(10的-9次方秒)。因为调用nanosleep是是进程进入TASK_INTERRUPTIBLE,这种状态是会相应信号而进入TASK_RUNNING状态的,这就意味着有可能会没有等到你规定的时间就由于其它信号而唤醒,此时函数返回-1,切还剩余的时间会被记录在rem中。

 

看到这里刚刚看到他的实现是:将其状态设置成TASK_INTERRUPTIBLE,脱离就绪队列,而后进行一次进程调度再由内核在规定的时间后发送信号来唤醒这个进程。

 

在我刚开始学习编程时候,那时候我也曾试图使上下2条指令相隔必定时间来运行,那时个人作法是在这2条指令之间加上了一个400次的循环。这也算一种实现方式,我管它叫做延迟,但没有利用进程休眠来实现的好。但有一种特殊状况,使用休眠就没法实现了。

 

咱们知道这里确定脱离不了时钟中断,没有时钟中断的计时咱们是没法实现这一功能的。那么假设时钟种中断是10毫秒一次(这种CPU仍是有的),那么咱们能够看到在函数调用的时候咱们能够以毫微秒来暂停,若是我tv_sec = 0, tv_nsec = 2,那么时钟中断必定是在10微秒后来唤醒这个进程的,若是非实时性任务差个8微秒估计没什么大不了,不幸的是LINUX支持实时性任务SCHED_FIFO和SCHED_RR.(咱们之前谈到过)。

 

这时8微秒的差距就是不能容忍了,这是就不能靠休眠和时钟中断来实现了,这是linux采用就是延迟办法,执行一个循环来达到暂停的目的。

 

这2种实现的差异就是休眠实现的话,进程会进入休眠状态,而延迟实现的话,CPU是在执行循环不会进入休眠态。因此能够说虽然名为nanosleep,但它不必定会使进程进入sleep状态,固然不进入sleep 态的条件太苛刻(没多少人会写实时任务,且仍是暂停要小于CPU时钟频率,加上如今CPU的频率是如此之高,这种状况通常发生在要求外设中断不小于某个特定值,并且应该是采用比较老的CPU或者嵌入式中)。

唤醒问题:

msleep:睡眠以后不可唤醒;

msleep_interuptible:睡眠以后可唤醒;

ssleep:s延时,睡眠时候不可唤醒;

相关文章
相关标签/搜索