目錄
前言
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>
或
定義&初始化等待隊列頭部:使用宏 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 參數。
建議:找個例程看看就明白了。