日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux wait函数头文件_手把手教Linux驱动9-等待队列waitq

發(fā)布時間:2023/12/3 linux 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux wait函数头文件_手把手教Linux驱动9-等待队列waitq 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在上一篇《手把手教Linux驅(qū)動8-Linux IO模型》我們已經(jīng)了解了阻塞、非阻塞、同步和異步等相關概念,本文主要講解如何通過等待隊列實現(xiàn)對進程的阻塞。

應用場景:

當進程要獲取某些資源(例如從網(wǎng)卡讀取數(shù)據(jù))的時候,但資源并沒有準備好(例如網(wǎng)卡還沒接收到數(shù)據(jù)),這時候內(nèi)核必須切換到其他進程運行,直到資源準備好再喚醒進程。

waitqueue (等待隊列) 就是內(nèi)核用于管理等待資源的進程,當某個進程獲取的資源沒有準備好的時候,可以通過調(diào)用 add_wait_queue() 函數(shù)把進程添加到 waitqueue 中,然后切換到其他進程繼續(xù)執(zhí)行。當資源準備好,由資源提供方通過調(diào)用 wake_up() 函數(shù)來喚醒等待的進程。

定義頭文件:

#include

定義和初始化等待隊列頭(workqueue):

靜態(tài)的,用宏:

#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

動態(tài)的,也是用宏:

#define init_waitqueue_head(q) do { static struct lock_class_key __key; __init_waitqueue_head((q), #q, &__key); } while (0)

定義實例

wait_queue_head_t wq;init_waitqueue_head(&wq);

阻塞接口:

wait_event(wq, condition)wait_event_timeout(wq, condition, timeout)wait_event_interruptible(wq, condition)wait_event_interruptible_timeout(wq, condition, timeout)wait_event_hrtimeout(wq, condition, timeout)wait_event_interruptible_hrtimeout(wq, condition, timeout)wait_event_interruptible_exclusive(wq, condition)wait_event_interruptible_locked(wq, condition)wait_event_interruptible_locked_irq(wq, condition)wait_event_interruptible_exclusive_locked(wq, condition)wait_event_interruptible_exclusive_locked_irq(wq, condition)wait_event_killable(wq, condition)wait_event_lock_irq_cmd(wq, condition, lock, cmd)wait_event_lock_irq(wq, condition, lock)wait_event_interruptible_lock_irq_cmd(wq, condition, lock, cmd)wait_event_interruptible_lock_irq(wq, condition, lock)wait_event_interruptible_lock_irq_timeout(wq, condition, lock, timeout)

參數(shù)

wq 定義的等待隊列頭,condition 為條件表達式,當wake up后,condition為真時,喚醒阻塞的進程,為假時,繼續(xù)睡眠。

功能說明

接口版本比較多,各自都有自己合適的應用場合,但是常用的是前面四個。

wait_event:不可中斷的睡眠,條件一直不滿足,會一直睡眠。wait_event_timeout:不可中斷睡眠,當超過指定的timeout(單位是jiffies)時間,不管有沒有wake up,還是條件沒滿足,都要喚醒進程,此時返回的是0。在timeout時間內(nèi)條件滿足返回值為timeout或者1;wait_event_interruptible:可被信號中斷的睡眠,被信號打斷喚醒時,返回負值-ERESTARTSYS;wake up時,條件滿足的,返回0。除了wait_event沒有返回值,其它的都有返回,有返回值的一般都要判斷返回值。如下例: int flag = 0; if(wait_event_interruptible(&wq,flag == 1)) return -ERESTARTSYS;wait_event_interruptible_timeout:是wait_event_timeout和wait_event_interruptible_timeout的結合版本,有它們兩個的特點。

其他的接口,用的不多,有興趣可以自己看看。

解除阻塞接口(喚醒)

接口定義:

#define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)#define wake_up_nr(x, nr) __wake_up(x, TASK_NORMAL, nr, NULL)#define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, NULL)#define wake_up_locked(x) __wake_up_locked((x), TASK_NORMAL, 1)#define wake_up_all_locked(x) __wake_up_locked((x), TASK_NORMAL, 0)#define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)#define wake_up_interruptible_all(x) __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)#define wake_up_interruptible_sync(x) __wake_up_sync((x), TASK_INTERRUPTIBLE, 1)

功能說明

wake_up:一次只能喚醒掛在這個等待隊列頭上的一個進程wake_up_nr:一次喚起nr個進程(等待在同一個wait_queue_head_t有很多個)wake_up_all:一次喚起所有等待在同一個wait_queue_head_t上所有進程wake_up_interruptible:對應wait_event_interruptible版本的wake upwake_up_interruptible_sync:保證wake up的動作原子性,wake_up這個函數(shù),很有可能函數(shù)還沒執(zhí)行完,就被喚起來進程給搶占了,這個函數(shù)能夠保證wak up動作完整的執(zhí)行完成。

其他的也是與對應阻塞接口對應的。

使用實例

以字符設備為例,在沒有數(shù)據(jù)的時候,在read函數(shù)中實現(xiàn)讀阻塞,當向內(nèi)核寫入數(shù)據(jù)時,則喚醒阻塞在該等待隊列的所有任務。

讀操作

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss) { wait_event_interruptible(rwq,flage!=0); …………… flage=0; wake_up_interruptible(&wwq); return size; }

寫操作

static ssize_t hello_write(struct file *filp,const char __user *buf,size_t size,loff_t *poss) { ????wait_event_interruptible(wwq,flage!=1); ……………????flage=1;????wake_up_interruptible(&rwq); return size; }

如何同步支持非阻塞?

上述操作雖然實現(xiàn)了阻塞功能,但是我們在應用程序打開一個字符設備的時候,有時候我們希望操作是非阻塞的,比如:

fd=open("/dev/hello",O_RDONLY|O_NONBLOCK);

那么驅(qū)動如何得到這個標記呢?

參考《手把手教Linux驅(qū)動6-inode,file,file_operations關系》,該標記會存儲在結構體struct file的f_flags成員中。

所以程序可以修改如下:

static ssize_t hello_read(struct file *filp,char __user *buf,size_t size,loff_t *poss) { int ret = 0;if(flage==0){if(filp->f_flags & O_NONBLOCK){ return -EAGAIN;}wait_event_interruptible(rwq,flage!=0);} ……………flage=0;wake_up_interruptible(&wwq); return size; }

一種靈活的添加刪除等待隊列頭中的等待隊列:

(1)定義:
靜態(tài):

#define DECLARE_WAITQUEUE(name, tsk) wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

(2)動態(tài):

wait_queue_t wa;init_waitqueue_entry(&wa,&tsk);

tsk是進程結構體,一般是current(linux當前進程就是用這個獲取)。還可以用下面的,設置自定義的等待隊列回調(diào)函數(shù),上面的是linux默認的一個回調(diào)函數(shù)default_wake_function(),不過默認的用得最多:

wait_queue_t wa;wa->private = &tsk;int func(wait_queue_t *wait, unsigned mode, int flags, void *key){ //}init_waitqueue_func_entry(&wa,func);

(回調(diào)有什么作用?)
用下面函數(shù)將等待隊列,加入到等待隊列頭(帶remove的是從工作隊列頭中刪除工作隊列):

extern void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);extern void add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait);extern void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

上面的阻塞和解除阻塞接口,只能是對當前進程阻塞/解除阻塞,有了這幾個靈活的接口,我們可以單獨定義一個等待隊列,只要獲取進程task_struct指針,我們可以將任何進程加入到這個等待隊列,然后加入到等待隊列頭,我們能將其它任何進程(不僅僅是當前進程),掛起睡眠,當然喚醒時,如果用wake_up_all版本的話,也會一同喚起。這種情況,阻塞不能用上面的接口了,我們需要用下一節(jié)講述的接口(schedule()),解除阻塞可以用wake_up,wake_up_interruptible等。

更高級靈活的阻塞:

阻塞當前進程的原理:用函數(shù)set_current_state()修改當前進程為TASK_INTERRUPTIBLE(不可中斷睡眠)或TASK_UNINTERRUPTIBLE(可中斷睡眠)狀態(tài),然后調(diào)用schedule()告訴內(nèi)核重新調(diào)度,由于當前進程狀態(tài)已經(jīng)為睡眠狀態(tài),自然就不會被調(diào)度。schedule()簡單說就是告訴內(nèi)核當前進程主動放棄CPU控制權。這樣來,就可以說當前進程在此處睡眠,即阻塞在這里。

在上一小節(jié)“靈活的添加刪等待隊列頭中的等待隊列”,將任意進程加入到waitqueue,然后類似用:

task_struct *tsk;wait_queue_t wa;//假設tsk已經(jīng)指向某進程控制塊p->state = TASK_INTERRUPTIBLE;//or TASK_UNINTERRUPTIBLEinit_waitqueue_entry(&wa,&tsk);

就能將任意進程掛起,當然,還需要將wa,掛到等待隊列頭,然后用wait_event(&wa),進程就會從就緒隊列中退出,進入到睡眠隊列,直到wake up時,被掛起的進程狀態(tài)被修改為TASK_RUNNING,才會被再次調(diào)度。(主要是schedule()下面會說到)。


wait_event實現(xiàn)原理:

先看下wait_event實現(xiàn):

#define __wait_event(wq, condition) do { DEFINE_WAIT(__wait); for (;;) { prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); if (condition) break; schedule(); } finish_wait(&wq, &__wait); } while (0)#define wait_event(wq, condition) do { if (condition) break; __wait_event(wq, condition); } while (0)

DEFINE_WAIT:

定義一個工作隊列。

prepare_to_wait:

定義:void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)功能:將工作隊列wait加入到工作隊列頭q,并將當前進程設置為state指定的狀態(tài),一般是TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE狀態(tài)(在這函數(shù)里有調(diào)用set_current_state)。 第一個參數(shù):工作隊列頭 第二個參數(shù):工作隊列 第三個參數(shù):當前進程要設置的狀態(tài)

finish_wait

用了prepare_to_wait之后,當退出時,一定要用這個函數(shù)清空等待隊列。

功能:

  • 該函數(shù)首先調(diào)用prepare_to_wait,修改進程到睡眠狀態(tài),
  • 條件不滿足,schedule()就放棄CPU控制權,睡眠,
  • 當wake up的時候,阻塞在wq(也可以說阻塞在wait_event處)等待隊列頭上的進程,再次得到運行,接著執(zhí)行schedule()后面的代碼,這里,顯然是個循環(huán),prepare_to_wait再次設置當前進程為睡眠狀態(tài),然后判斷條件是否滿足,
  • 滿足就退出循環(huán),finish_wait將當前進程恢復到TASK_RUNNING狀態(tài),也就意味著阻塞解除。不滿足,繼續(xù)睡下去。如此反復等待條件成立。
  • 明白這個過程,用prepare_to_wait和schedule()來實現(xiàn)更為靈活的阻塞,就很簡單了,解除阻塞和前面的一樣用wake_up,wake_up_interruptible等。

    下面是wake_up和wait_event流程圖:

    wait_event和wake_up流程

    獨占等待


    當調(diào)用wake_up時,所有等待在該隊列上的進程都被喚醒,并進入可運行狀態(tài)如果只有一個進程可獲得資源,此時,其他的進程又將再次進入休眠,如果數(shù)量很大,被稱為”瘋狂售群”。這樣會非常占用系統(tǒng)資源。

    解決方法:

    wait_queue_t成員flage有個重要的標志W(wǎng)Q_FLAG_EXCLUSIVE,表示:

    當一個等待隊列入口有 WQ_FLAG_EXCLUSEVE 標志置位, 它被添加到等待隊列的尾部. 沒有這個標志的入口項, 添加到開始.當 wake_up 被在一個等待隊列上調(diào)用, 它在喚醒第一個有WQ_FLAG_EXCLUSIVE 標志的進程后停止.

    wait_event默認總是將waitqueue加入開始,而wake_up時總是一個一個的從開始處喚醒,如果不斷有waitqueue加入,那么最開始加入的,就一直得不到喚醒,有這個標志,就避免了這種情況。

    prepare_to_wait_exclusive()就是加入了這個標志的。

    補充

    Linux將進程狀態(tài)描述為如下五種:1. TASK_RUNNING:可運行狀態(tài)。處于該狀態(tài)的進程可以被調(diào)度執(zhí)行而成為當前進程。2. TASK_INTERRUPTIBLE:可中斷的睡眠狀態(tài)。處于該狀態(tài)的進程在所需資源有效時被喚醒,也可以通過信號或定時中斷喚醒(因為有signal_pending()函數(shù))。3. TASK_UNINTERRUPTIBLE:不可中斷的睡眠狀態(tài)。處于該狀態(tài)的進程僅當所需資源有效時被喚醒。4. TASK_ZOMBIE:僵尸狀態(tài)。表示進程結束且已釋放資源,但其task_struct仍未釋放。5. TASK_STOPPED:暫停狀態(tài)。處于該狀態(tài)的進程通過其他進程的信號才能被喚醒

    Linux 通過結構體task_struct維護所有運行的線程、進程,不同狀態(tài)的任務,會由不同的隊列進行維護,schedule()函數(shù)就負責根據(jù)這些狀態(tài)的變化調(diào)度這些任務。關于進程的調(diào)度,后續(xù)會新開文章詳細介紹。

    實例


    下面實例主要功能是基于我們之前課程《手把手教Linux驅(qū)動3-之字符設備架構詳解,有這篇就夠了》最后的代碼實例,在此基礎上增加寫阻塞的功能。

  • 內(nèi)核中有緩沖內(nèi)存,以及是否可以訪問的標記;
  • int flage=0; //1:有數(shù)據(jù)可讀 0:無數(shù)據(jù),不可讀char kbuf[128];
  • 初始狀態(tài)下flage為0,沒有數(shù)據(jù);
  • 應用進程讀取數(shù)據(jù)會調(diào)用到內(nèi)核函數(shù)hello_read(),如果flage為1,則直接讀走數(shù)據(jù),并將改flage置1,如果flage為0,則進程阻塞,直到有進程寫入數(shù)據(jù)將該flage置1;
  • 應用進程每次寫入數(shù)據(jù)會調(diào)用到內(nèi)核函數(shù)hello_write(),如果flage為0,則直接寫入數(shù)據(jù),并設置flage為1,如果為1,則阻塞,直到有其他進程調(diào)用到讀函數(shù)hello_read()將flage置0。
  • 驅(qū)動

    ule_exit(hellodev_exit);

    測試程序

    read.c

    #include #include #include #include #include #include int main(){ int fd= 0; char buf[128]; int num;// fd=open("/dev/hello",O_RDONLY); 阻塞方式讀取 fd=open("/dev/hello",O_RDONLY|O_NONBLOCK); //非阻塞 if(fd<0) { printf("open memdev failed!"); return -1; } read(fd,buf,sizeof(buf)); printf("num:%s",buf); close(fd); return 0; }

    write.c

    #include #include #include #include #include #include int main(){ int fd =0; char buf[128]="hello yikouLlinux"; int num;? fd=open("/dev/hello",O_RDWR); if(fd <0) { printf("open device failed!"); return -1; } write(fd,buf,sizeof(buf)); close(fd); return 0; }

    掌握了等待隊列的用法,后面我們就可以進行中斷的講解了。

    總結

    以上是生活随笔為你收集整理的linux wait函数头文件_手把手教Linux驱动9-等待队列waitq的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。