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延時,睡眠時候不可喚醒;

相關文章
相關標籤/搜索