天天看點

【linux】驅動-13-阻塞與非阻塞

目錄

前言

13. 阻塞與非阻塞

13.1 阻塞與非阻塞

13.2 休眠與喚醒

13.2.1 核心休眠函數

13.2.2 核心喚醒函數

13.3 等待隊列(阻塞)

13.3.1 定義等待隊列頭部

13.3.2 初始化等待隊列頭部

13.3.3 定義等待隊列元素

13.3.4 添加/移除等待隊列元素

13.3.5 等待事件

13.3.6 喚醒隊列

13.3.7 在等待隊列上睡眠

13.4 輪詢

13.4.1 select 函數

13.4.2 poll 函數

13.4.3 epoll 函數

13.5 驅動中的 poll 函數

本章内容為驅動基石之一。

驅動隻提供功能,不提供政策。

阻塞與非阻塞 都是應用程式主動通路的。從應用角度去解讀阻塞與非阻塞。

原文:https://www.cnblogs.com/lizhuming/p/14912496.html

阻塞:

指在執行裝置操作時,若不能獲得資源,則挂起程序,直至滿足操作的條件後再繼續執行。

非阻塞:

指在執行裝置操作時,若不能獲得資源,則不挂起,要麼放棄,要麼不停查詢,直至裝置可操作。

實作阻塞的常用技能包括:(目的其實就是阻塞)

休眠與喚醒機制(和等待隊列相輔相成)。

等待隊列(和休眠與喚醒機制相輔相成)。

poll機制。

若需要實作阻塞式通路,可以使用休眠與喚醒機制。

相關函數其實在 等待隊列 小節有說明了,現在隻是函數彙總。

核心源碼路徑:include\linux\wait.h。

函數名

描述

wait_event(wq, condition)

休眠,直至 condition 為真;休眠期間不能被打斷。

wait_event_interruptible(wq, condition)

休眠,直至 condition 為真;休眠期間可被打斷,包括信号。

wait_event_timeout(wq, condition, timeout)

休眠,直至 condition 為真或逾時;休眠期間不能被打斷。

wait_event_interruptible_timeout(wq, condition, timeout)

休眠,直至 condition 為真或逾時;休眠期間可被打斷,包括信号。

wake_up_interruptible(x)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”的線程,隻喚醒其中的一個線程

wake_up_interruptible_nr(x, nr)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”的線程,隻喚醒其中的 nr 個線程

wake_up_interruptible_all(x)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”的線程,喚醒其中的所有線程

wake_up(x)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的線程,隻喚醒其中的一個線程

wake_up_nr(x, nr)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的線程,隻喚醒其中 nr 個線程

wake_up_all(x)

喚醒 x 隊列中狀态為“TASK_INTERRUPTIBLE”或“TASK_UNINTERRUPTIBLE”的線程,喚醒其中的所有線程

等待隊列:

其實就是核心的一個隊列功能機關&API。

在驅動中,可以使用等待隊列來實作阻塞程序的喚醒。

使用方法:

定義等待隊列頭部。

初始化等待隊列頭部。

定義等待隊列元素。

添加/移除等待隊列。

等待事件。

喚醒隊列。

另外一種使用方法就是 在等待隊列上睡眠。

等待隊列頭部結構體:

等待隊列元素結構體:

定義等待隊列頭部方法:<code>wait_queue_head_t my_queue;</code>

初始化等待隊列頭部源碼:<code>void init_waitqueue_head(wait_queue_head_t *q);</code>

定義&amp;初始化等待隊列頭部:使用宏 DECLARE_WAIT_QUEUE_HEAD。

定義等待隊列元素源碼:<code>#define DECLARE_WAITQUEUE(name, tsk);</code>

name:該等待隊列元素的名字。

tsk:該等待隊列元素歸屬于哪個任務程序。

添加等待隊列元素源碼:<code>void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);</code>

wq_head:等待隊列頭部。

wq_entry:等待隊列。

移除等待隊列元素源碼:<code>void add_wait_queue(struct wait_queue_head *wq_head, struct wait_queue_entry *wq_entry);</code>

睡眠,直至事件發生:<code>wait_event(wq_head, condition)</code>

wq_head:等待隊列頭。

condition:事件。當其為真時,跳出。

TASK_INTERRUPTIBLE:處于等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接配接等等),可被信号中斷喚醒。可被 信号 和 wake_up() 喚醒。

TASK_UNINTERRUPTIBLE:處于等待隊伍中,等待資源有效時喚醒(比如等待鍵盤輸入、socket連接配接等等),但會忽略信号、不可以被中斷喚醒。即是隻能由 wake_up() 喚醒。

睡眠,直至事件發生或逾時:<code>wait_event_timeout(wq_head, condition, timeout)</code>

等待事件發生,且可被信号中斷喚醒:<code>wait_event_interruptible(wq_head, condition)</code>

等待事件發生或逾時,且可被信号中斷喚醒:<code>wait_event_interruptible_timeout(wq_head, condition, timeout)</code>

io_wait_event():

以下兩個函數對應等待事件使用:

喚醒隊列:<code>void wake_up(wait_queue_head_t *queue);</code>

喚醒隊列,信号中斷可喚醒:<code>void wake_up_interruptible(wait_queue_head_t *queue);</code>

函數源碼:

<code>sleep_on(wait_queue_head_t *q)</code>

<code>interruptible_sleep_on(wait_queue_head_t *q)</code>

sleep_on():

把目前程序狀态設定為 TASK_INTERRUPTIBLE,并定義一個等待隊列元素,并添加到 q 中。

直到資源可用或 q 隊列指向連結的程序被喚醒。

與 wake_up() 配套使用。interruptible_sleep_on() 與 wake_up_interruptible() 配套使用。

當使用者應用程式以非阻塞的方式通路裝置,裝置驅動程式就要提供非阻塞的處理方式。

poll、epoll 和 select 可以用于處理輪詢。這三個 API 均在 應用層 使用。

注意,輪詢也是在APP實作輪詢的。

select():

函數原型:<code>int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);</code>

numfds:需要檢查的 fd 中最大的 fd + 1。

readfds:讀 檔案描述符集合。NULL 不關心這個。

writefds:寫 檔案描述符集合。NULL 不關心這個。

exceptfds:異常 檔案描述符集合。NULL 不關心這個。

timeout:逾時時間。NULL 時為無限等待。

時間結構體:

傳回:

0:逾時。

-1:錯誤。

其他值:可進行操作的檔案描述符個數。

原理:fd_set 為一個 N 位元組類型,需要操作的 fd 值在對應比特上置為 1 即可。若 fd 的值為 6,需要檢查讀操作,則把 readfds 第 6 個 bit 置 1。調用該函數後,先把對應 fd_set 清空,再檢查、标記可操作情況。Linux 提供以下接口操作:

fd_set 是有限制的,可以檢視源碼,修改也可。但是改大會影響系統效率。

由于 fd_set 是有限制的,是以當需要監測大量檔案時,便不可用。

這時候,poll() 函數就應運而生。

poll() 和 select() 沒什麼差別,隻是前者沒有最大檔案描述符限制。

函數原型:<code>int poll(struct pollfd *fds, nfds_t nfds, int timeout)</code>

fds:要監視的檔案描述符集合。

nfds:要監視的檔案描述符數量。

timeout:逾時時間。機關 ms。

-1:發生錯誤,并設定 error 為錯誤類型。

其它:傳回 revent 域值不為 0 的 pollfd 個數。即是發生事件或錯誤的檔案描述符數量。

被監視的檔案描述符格式:

可請求的事件 events:

說明

POLLIN

有資料可讀

POLLPRI

有緊急的資料需要讀取

POLLOUT

可以寫資料

POLLERR

指定的檔案描述符發生錯誤

POLLHUP

指定的檔案描述符被挂起

POLLNVAL

無效的請求

POLLRDNORM

等同于 POLLIN

select() 和 poll() 會随着監測的 fd 數量增加,而出現效率低下的問題。

poll() 每次監測都需要曆遍所有被監測的描述符。

epoll() 函數就是為大量并大而生的。在網絡程式設計中比較常見。

epoll() 使用方法:

建立一個 epoll 句柄:

函數原型:<code>int epoll_creat(int size);</code>

size:随便大于 0 即可。 Linux2.6.8 後便不再維護了。

epoll 句柄。

-1:建立失敗。

向 epoll 添加要監視的檔案及監測的事件。

函數原型:<code>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);</code>。

epfd:epoll 句柄。

op:操作辨別。

EPOLL_CTL_ADD:向 epfd 添加 fd 表示的描述符。

EPOLL_CTL_MOD:修改 fd 的 event 時間。

EPOLL_CTL_DEL:從 epfd 中删除 fd 描述符。

fd:要監測的檔案。

event:要監測的事件類型。

等待事件發生。

函數原型:<code>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);</code>。

events:指向 epoll_event 結構體數組。

maxevents:events 數組大小,必須大于 0。

timeout:逾時時間。

epoll_event 結構體:

EPOLLIN

EPOLLPRI

EPOLLOUT

EPOLLERR

EPOLLHUP

EPOLLET

設定 epoll 為邊沿觸發,預設觸發模式為水準觸發

EPOLLONESHOT

一次性的監視,當監視完成後,還需要監視某個 fd,那就需要把 fd 重新添加到 epoll 中

當應用程式調用 select() 函數和 poll() 函數時,驅動程式會調用 file_operations 中的 poll。

函數原型:<code>unsigned int(*poll)(struct file *filp, struct poll_table_struct *wait)</code>

file:file 結構體。

wait:輪詢表指針。主要傳給 poll_wait 函數。

該函數主要工作:

對可能引起裝置檔案狀态變化的等待隊列調用 poll_wait() 函數,将對應的等待隊列頭部添加到 poll_table 中。

傳回表示是否能對裝置進行無阻塞讀、寫通路的掩碼。可以傳回以下值:

POLLIN:有資料可讀。

POLLPRI:有緊急的資料需要讀取。

POLLOUT:可以寫資料。

POLLERR:指定的檔案描述符發生錯誤。

POLLHUP:指定的檔案描述符挂起。

POLLNVAL:無效的請求。

POLLRDNORM:等同于 POLLIN,普通資料可讀。

poll_wait()

函數原型:<code>void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p)</code>

該函數不會阻塞程序,隻是将目前程序添加到 wait 參數指定的等待清單中。

filp:要操作的裝置檔案描述符。

wait_address:要添加到 wait 輪詢表中的等待隊列頭。

p:file_operations 中 poll 的 wait 參數。

建議:找個例程看看就明白了。

繼續閱讀