前言
?? 本文介紹進程的休眠與喚醒。
?? 嵌入式驅動學習專欄將詳細記錄博主學習驅動的詳細過程,未來預計四個月將高強度更新本專欄,喜歡的可以關注本博主并訂閱本專欄,一起討論一起學習。現在關注就是老粉啦!
行文目錄
- 前言
- 1. 阻塞和非阻塞
- 2. 進程的幾種狀態
- 3. 等待隊列
- 3.1 等待隊列頭
- 3.2 等待隊列項
- 參考資料
1. 阻塞和非阻塞
?? 當應用程序對設備驅動進行操作時,如果不能獲取到設備資源,那么阻塞IO就會將應用程序掛起,直到設備資源可以獲取為止。其模式如下所示:
?? 應用程序調用read
函數從設備中讀取數據,當設備不可用或數據未準備好的時候進入到休眠態,等設備可用就會從休眠態喚醒,然后從設備中讀取數據返回到應用程序。
?? 阻塞訪問最大的好處就是當設備文件不可操作時進程進入休眠態,可以把CPU資源讓出來。
?? 應用程序用阻塞IO訪問的代碼如下。默認讀取方式就是阻塞式訪問。
int fd;
int data = 0;fd = open("dev/xxx_dev", O_RDWR); // 阻塞方式打開
ret = read(fd, &data, sizeof(data)); // 讀取數據
?? 非阻塞式IO中,應用程序對應的線程不會掛起,要么一直輪詢等待,直到設備資源可用,要么直接放棄,其模式如下:
?? 應用程序調用read
函數從設備中獲取數據,當設備不可用或數據未準備好時會立即向內核返回一個錯誤碼,表示數據讀取失敗,應用程序會再次重新讀取數據,這樣循環往復,直到數據讀取成功。
?? 應用程序用阻塞IO訪問的代碼如下,主要在open
函數的第二個參數上有變化:
int fd;
int data = 0;fd = open("dev/xxx_dev", O_RDWR | O_NONBLOCK); // 阻塞方式打開
ret = read(fd, &data, sizeof(data)); // 讀取數據
2. 進程的幾種狀態
TASK_RUNNING: 正在運行或處于就緒狀態:就緒狀態是指進程申請到了CPU以外的其他所有資源,提醒:一般的操作系統教科書將正在CPU上執 行的進程定義為RUNNING狀態、而將可執行但是尚未被調度執行的進程定義為READY狀態,這兩種狀態在Linux下統一為 TASK_RUNNING狀態.
??
TASK_INTERRUPTIBLE: 處于等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接、信號等等),但可以被中斷喚醒.一般情況下,進程列表中的絕大多數進程都處于 TASK_INTERRUPTIBLE狀態.畢竟皇帝只有一個(單個CPU時),后宮佳麗幾千;如果不是絕大多數進程都在睡眠,CPU又怎么響應得過來.
??
TASK_UNINTERRUPTIBLE:處于等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接、信號等等),但不可以被中斷喚醒.
??
TASK_ZOMBIE:僵死狀態,進程資源用戶空間被釋放,但內核中的進程PCB并沒有釋放,等待父進程回收.
??
TASK_STOPPED:進程被外部程序暫停(如收到SIGSTOP信號,進程會進入到TASK_STOPPED狀態),當再次允許時繼續執行(進程收到SIGCONT信號,進入TASK_RUNNING狀態),因此處于這一狀態的進程可以被喚醒.
3. 等待隊列
?? 阻塞訪問中,如果設備不可操作的時候,進程進入休眠態,但當設備文件可以操作時就必須喚醒進程,一般在終端中完成喚醒工作。Linux內核提供等待隊列來實現阻塞進程的喚醒工作。
3.1 等待隊列頭
?? 如果要使用等待隊列,必須先新建并初始化一個等待隊列頭,等待隊列頭使用結構體wait_queue_head_t
:
struct __wait_queue_head {spinlock_t lock;struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
?? 定義好等待隊列頭后就使用函數init_waitqueue_head
進行初始化。
/** @description: 初始化等待隊列頭* @param-q : 要初始化的等待隊列頭* @return : 無*/
void init_waitqueue_head(wait_queue_head_t *q)
3.2 等待隊列項
?? 等待隊列頭是一個等待隊列的頭部,每個訪問設備的進程為一個隊列項,當設備不可用時將這些進程對應的等待隊列項添加到等待隊列中,用結構體wait_queue_t
表示等待隊列項
struct __wait_queue {unsigned int flags;void *private;wait_queue_func_t func;struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
?? DECLARE_WAITQUEUE
定義并初始化一個等待隊列項
// 定義并初始化一個等待隊列,name是等待隊列項的名字,tsk是該等待隊列項屬于哪個任務(進程),一般設置為current
DECLARE_WAITQUEUE(name, tsk)
?? 以下是添加等待隊列項的函數:
/** @description: 添加隊列項* @param-q : 等待隊列項要加入的等待對獵頭* @param-wait : 要加入的等待隊列項* @return : 無*/
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
?? 以下是刪除等待隊列項的函數:
/** @description: 刪除隊列項* @param-q : 要刪除等待隊列項的等待對獵頭* @param-wait : 要刪除的等待隊列項* @return : 無*/
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)
?? 喚醒休眠態進程有兩個函數wake_up()
和wake_up_interruptible()
。
wake_up 可以喚醒處于
TASK_INTERRUPTIBLE
和TASK_UNINTERRUPTIBLE
狀態的進程
wake_up_interruptible只可以喚醒處于TASK_INTERRUPTIBLE
的進程
/** @description: 環境休眠態的進程* @param-q : 要喚醒的等待隊列頭,其中的所有線程都會喚醒* @return : 無*/
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
?? 除了主動喚醒,也可以設置等待隊列等待某個事件來喚醒等待隊列的線程。
/** @description : 當condition為真時,喚醒以wq為等待對獵頭的等待隊列,condition為假時,會一直阻塞。* 此函數會將進程設置為TASK_UNINTERRUPTIBLE 狀態* @param-wq : 要喚醒的等待隊列頭,其中的所有線程都會喚醒* @param-condition: 喚醒條件* @return : 無*/
wait_event(wq, condition)
/** @description : 功能和 wait_event 類似,但是此函數可以添加超時時間,以 jiffies 為單位* @param-wq : 要喚醒的等待隊列頭,其中的所有線程都會喚醒* @param-condition: 喚醒條件* @param-timeout : 超時時間* @return : 0,表示超時時間到,而且 condition為假。1,表示 condition 為真,也就是條件滿足了*/
wait_event_timeout(wq, condition, timeout)
/** @description : 與 wait_event 函數類似,但是此函數將進程設置為 TASK_INTERRUPTIBLE,就是可以被信號打斷*/
wait_event_interruptible(wq, condition)
/** @description : 與 wait_event_timeout 函數類似,但是此函數將進程設置為 TASK_INTERRUPTIBLE,就是可以被信號打斷*/
wait_event_interruptible_timeout(wq, condition, timeout)
總結:使用等待隊列實現阻塞訪問重點注意兩點:
①、將任務或者進程加入到等待隊列頭
②、在合適的點喚醒等待隊列,一般都是中斷處理函數里面。
參考資料
[1] 【正點原子】I.MX6U嵌入式Linux驅區動開發指南 第五十二章
[2] linux內核任務調度-- wait_event