http://www.cnblogs.com/charlesblc/p/6277848.html
注意,sleep是會被信號喚醒的。
sleep函數:
#include <unistd.h> unsigned int sleep(unsigned int seconds); 此函數使調用進程被掛起,直到滿足以下條件之一: 1)已經過了seconds所指定的牆上時鍾時間 2)調用進程捕捉到一個信號並從信號處理程序返回 注:由於其他系統活動,實際返回時間比所要求的會遲一些,像alarm一樣。 sleep的返回值: 1)在上述第一種情形中,返回值是0 2)當由於捕捉到某個信號sleep提前返回時,返回值是未睡夠的時間(所要求的時間減去實際休眠時間) |
內核對信號的處理方式
參考 http://blog.csdn.net/lina_acm/article/details/51510783
內核給一個進程發送軟中斷信號的方法,是在進程所在的進程表項的信號域設置對應於該信號的位。這里要補充的是,如果信號發送給一個正在睡眠的進程,那么要看該進程進入睡眠的優先級,如果進程睡眠在可被中斷的優先級上,則喚醒進程;否則僅設置進程表中信號域相應的位,而不喚醒進程。這一點比較重要,因為進程檢查是否收到信號的時機是:一個進程在即將從內核態返回到用戶態時;或者,在一個進程要進入或離開一個適當的低調度優先級睡眠狀態時。
感覺,sleep函數,都是可被中斷的吧。不是很確定。
內核處理一個進程收到的信號的時機是在一個進程從內核態返回用戶態時。所以,當一個進程在內核態下運行時,軟中斷信號並不立即起作用,要等到將返回用戶態時才處理。進程只有處理完信號才會返回用戶態,進程在用戶態下不會有未處理完的信號。
處理信號有三種類型:進程接收到信號后退出;進程忽略該信號;進程收到信號后執行用戶設定用系統調用signal的函數。當進程接收到一個它忽略的信號時,進程丟棄該信號,就象沒有收到該信號似的繼續運行。如果進程收到一個要捕捉的信號,那么進程從內核態返回用戶態時執行用戶定義的函數。
而且執行用戶定義的函數的方法很巧妙,內核是在用戶棧上創建一個新的層,該層中將返回地址的值設置成用戶定義的處理函數的地址,這樣進程從內核返回彈出棧頂時就返回到用戶定義的函數處,從函數返回再彈出棧頂時,才返回原先進入內核的地方。這樣做的原因是用戶定義的處理函數不能且不允許在內核態下執行(如果用戶定義的函數在內核態下運行的話,用戶就可以獲得任何權限)。
在BSD系統中,內核模擬了對硬件中斷的處理方法,即在處理某個中斷時,阻止接收新的該類中斷。
第二個要引起注意的是,如果要捕捉的信號發生於進程正在一個系統調用中時,並且該進程睡眠在可中斷的優先級上,這時該信號引起進程作一次longjmp,跳出睡眠狀態,返回用戶態並執行信號處理例程。當從信號處理例程返回時,進程就象從系統調用返回一樣,但返回了一個錯誤代碼,指出該次系統調用曾經被中斷。這要注意的是,BSD系統中內核可以自動地重新開始系統調用。
具體可以參考下一篇文章:http://www.cnblogs.com/charlesblc/p/6277921.html
第三個要注意的地方:若進程睡眠在可中斷的優先級上,則當它收到一個要忽略的信號時,該進程被喚醒,但不做longjmp,一般是繼續睡眠。但用戶感覺不到進程曾經被喚醒,而是象沒有發生過該信號一樣。
第四個要注意的地方:內核對子進程終止(SIGCLD)信號的處理方法與其他信號有所區別。當進程檢查出收到了一個子進程終止的信號時,缺省情況下,該進程就象沒有收到該信號似的,如果父進程執行了系統調用wait,進程將從系統調用wait中醒來並返回wait調用,執行一系列wait調用的后續操作(找出僵死的子進程,釋放子進程的進程表項),然后從wait中返回。SIGCLD信號的作用是喚醒一個睡眠在可被中斷優先級上的進程。如果該進程捕捉了這個信號,就象普通信號處理一樣轉到處理例程。如果進程忽略該信號,那么系統調用wait的動作就有所不同,因為SIGCLD的作用僅僅是喚醒一個睡眠在可被中斷優先級上的進程,那么執行wait調用的父進程被喚醒繼續執行wait調用的后續操作,然后等待其他的子進程。
2、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>
1、unsigned int sleep(unsigned int seconds); 秒級
2、int usleep(useconds_t usec); 微秒級:1/10^-6
#define _POSIX_C_SOURCE 199309
#include <time.h>
3、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>
1、void ndelay(unsigned long nsecs); 納秒級:1/10^-10
2、void udelay(unsigned long usecs); 微秒級: 1/10^-6
3、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延時,睡眠時候不可喚醒;