首先說說什么是阻塞和非阻塞的概念:阻塞操作就是指進程在操作設備時,由于不能獲取資源或者暫時不能操作設備時,系統就會把進程掛起,被掛起的進程會進入休眠狀態并且會從調度器的運行隊列移走,放到等待隊列中,然后一直休眠,直到該進程滿足可操作的條件,再被喚醒,繼續執行之前的操作。非阻塞操作的進程在不能進行設備操作時,并不會掛起,要么放棄,要么不停地執行,直到可以進行操作為止。
我們都知道,在應用中,打開一個設備文件時,指定了是以阻塞還是非阻塞打開(缺省是阻塞方式),然后后面的讀寫一切都是交由驅動來實現,那么驅動是如何實現read()和write()的阻塞呢!下面以讀寫一個內存塊為例子,當該內存寫滿了,不能寫的時候,調用write()函數該怎么處理,當該內存已經讀取完了,空了的時候,調用read()函數,又改如何處理(該代碼簡化了,只為說明問題,不能正常編譯使用):
wait_queue_head_t read_queue; ? ? ?//定義讀等待隊列頭部
? wait_queue_head_t write_queue; ? ??//定義寫等待隊列頭部
? struct semaphore sem; ? ? ? ? ? ? ? ? ? ??//定義信號量,用于互斥訪問公共資源
static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
if(down_interruptible(&sem))
? return -ERESTARTSYS;? ? ??//使用?down_interruptible,給公共資源上鎖,以防出現并發引起的競態問題
while (!have_data)? ? ?//have_data用來判斷緩沖區中是否有數據,如果有數據,直接跳過該while語句,執行下面的 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // copy_to_user
? {
up(&sem);?? ? //由于沒有數據,不能進行讀取數據操作,要釋放鎖,解鎖,這里的解鎖很重要,要是沒有解鎖,很容 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//易進入死鎖,具體怎樣,下面再分析? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
if(filp->f_flags & O_NONBLOCK)? ?//判斷該文件時以阻塞方式還是非阻塞方式打開
return -EAGAIN; ?? ? ? ? ? ? ? ? ? //由于是非阻塞打開,直接返回
wait_event_interruptible(read_queue,have_date);//阻塞方式代開,該語句會讓進程進入休眠狀態,然后等待其他進程 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//的喚醒并且have_data=true時,才會被完全喚醒,執行下面的語句
if(down_interruptible(&sem))? ? ? ? ? ?//由于可以進行讀取了,所以在此給公共資源上鎖
return -ERESTARTSYS;
if (copy_to_user(buf, (void*)(dev->data + p), count))? { ??? ?//實現數據從內核空間讀取到用戶空間,完成讀取操作
..................
}
have_data = false; ??//標記該數據已經讀取完畢
up(&sem);? ? ?//釋放鎖
wake_up(&write_queue);?//讀取完畢,緩沖區有空間可以寫入了,就喚醒寫進程,讓寫進程把數據寫入
return ;
}
下面分析write函數,其原理和實現也是和read函數一樣,都是先給公共資源上鎖,再判斷是阻塞訪問還是非阻塞訪問,如果是非阻塞訪問,且資源不能獲取時,直接返回,若果時阻塞且不能獲取資源時,就進入休眠,等待其他進程的喚醒。
static ssize_t mem_write(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
if(down_interruptible(&sem))
? return -ERESTARTSYS;? ? ??//使用?down_interruptible,給公共資源上鎖,以防出現并發引起的競態問題
while (have_data)? ? ?//have_data用來判斷緩沖區中是否有數據,如果有數據,表示緩沖區已經滿了,不能寫入,
//如果have_data是false,即沒有數據,緩沖區是空的,可以寫入數據,就執行下面的copy_from_user
? {
up(&sem);??? ? //由于有數據,不能進行寫入數據操作,要釋放鎖,解鎖 ? ? ? ? ?? ? ? ? ? ? ? ? ? ? ? ? ??if(filp->f_flags & O_NONBLOCK)?? ?//判斷該文件時以阻塞方式還是非阻塞方式打開
return -EAGAIN; ??? ? ? ? ? ? ? ? ? //由于是非阻塞打開,直接返回
wait_event_interruptible(write_queue,!have_date);//阻塞方式代開,該語句會讓進程進入休眠狀態,然后等待其他進程 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//的喚醒并且have_data=false時,才會被完全喚醒,執行下面的語句
if(down_interruptible(&sem))?? ? ? ? ? ?//由于可以進行寫入操作了,所以在此給公共資源上鎖
return -ERESTARTSYS;
if (copy_from_user((dev->data + p), buf,count))? { ??? ?//實現數據從內核空間讀取到用戶空間,完成讀取操作
..................
}
have_data = true; ??//標記該數據已經讀取完畢
up(&sem);?? ? ?//釋放鎖
wake_up(&read_queue);?//寫入數據完畢,緩沖區有數據可以讀取了,就喚醒讀進程,讓讀進程開始讀取數據
return ;
}
以上是驅動中的讀取和寫入操作,當寫進程發現數據已滿,不能寫入時,且上層應用是以阻塞的方式打開設備文件時,所以必須要寫入數據才能返回,否則不能返回,那么就有兩種實現機制,要不就是不停地忙等待,等待設備可以寫入時,便寫入,然后返回,可是這樣做的話,非常影響CPU的執行效率,大大降低了CPU的性能,所以linux內核中采取了等待隊列的實現方式,就是當一個阻塞進程寫入數據時,發現不能寫入時,會把這個進程掛起,放到等待隊列中休眠,然后一直在休眠,直到有個讀進程,把緩沖區的數據讀取完畢后,然后讀進程會把寫進程喚醒,告訴寫進程緩沖區可以寫入數據了,于是寫進程繼續寫入操作,并且返回。舉個例子,小明餓了,要吃飯,于是跑去媽媽那里,說要吃飯,媽媽說放沒有做好,你說小明是繼續在這里一直等著媽媽把飯做好,還是先去睡一覺好呢,如果我是小明,我就先去睡一覺,然后媽媽把飯做好了,就把小明叫醒,小明,可以吃飯了,于是小明起來,跑去吃飯。當讀進程阻塞時,也是這樣,就不分析了。
現在說說為什么每次進去阻塞前都要把鎖釋放掉,然后喚醒時再次上鎖,我們試想一下,假如讀進程發現緩沖區為空,不能讀取時,準備進入休眠了,沒有把鎖釋放,效果會怎樣,就相當于讀進程帶著鎖睡著了,一旦讀進程帶著鎖睡著了,寫進程來了,可是寫進程因為不能獲取鎖,就不能訪問臨界區的資源,更不能往緩沖區里面寫入數據,所以緩沖區會一直為空,且寫進程也會不停地在那里休眠,等到讀進程釋放鎖,可是讀進程睡著了,不能釋放鎖,寫進程也休眠了,不能喚醒讀進程,于是就發生了死鎖了。這就好比小明他爸爸藏了一個還魂丹在保險箱里,有一天,他爸爸暈倒了,可是沒有告訴小明鎖放在那里,于是小明只能在保險箱外面,看著他爸爸暈過去,卻無能為力了.....
?
評論
查看更多