Linux 阻塞和非阻塞IO 实验
目錄
- 阻塞和非阻塞IO
- 阻塞和非阻塞簡介
- 等待隊列
- 輪詢
- Linux 驅(qū)動下的poll 操作函數(shù)
- 阻塞IO 實驗
- 硬件原理圖分析
- 實驗程序編寫
- 運行測試
- 非阻塞IO 實驗
- 硬件原理圖分析
- 實驗程序編寫
- 運行測試
阻塞和非阻塞IO 是Linux 驅(qū)動開發(fā)里面很常見的兩種設(shè)備訪問模式,在編寫驅(qū)動的時候一定要考慮到阻塞和非阻塞。本章我們就來學習一下阻塞和非阻塞IO,以及如何在驅(qū)動程序中處理阻塞與非阻塞,如何在驅(qū)動程序使用等待隊列和poll 機制。
阻塞和非阻塞IO
阻塞和非阻塞簡介
這里的“IO”并不是我們學習STM32 或者其他單片機的時候所說的“GPIO”(也就是引腳)。這里的IO 指的是Input/Output,也就是輸入/輸出,是應(yīng)用程序?qū)︱?qū)動設(shè)備的輸入/輸出操作。
阻塞:當應(yīng)用程序?qū)υO(shè)備驅(qū)動進行操作的時候,如果不能獲取到設(shè)備資源,那么阻塞式IO 就會將應(yīng)用程序?qū)?yīng)的線程掛起,直到設(shè)備資源可以獲取為止。
線程的掛起操作實質(zhì)上就是線程進入"非可執(zhí)行"狀態(tài)下,在這個狀態(tài)下CPU不會分給線程時間片,進入這個狀態(tài)可以用來暫停一個線程的運行。線程掛起后,可以通過重新喚醒線程來使之恢復(fù)運行。cpu分配的線程片非常的短、同時也非常珍貴。避免資源的浪費。
非阻塞:對于非阻塞IO,應(yīng)用程序?qū)?yīng)的線程不會掛起,它要么一直輪詢等待,直到設(shè)備資源可以使用,要么就直接放棄。阻塞式IO 如圖52.1.1.1所示:
圖52.1.1.1 中應(yīng)用程序調(diào)用read 函數(shù)從設(shè)備中讀取數(shù)據(jù),當設(shè)備不可用或數(shù)據(jù)未準備好的時候就會進入到休眠態(tài)。等設(shè)備可用的時候就會從休眠態(tài)喚醒,然后從設(shè)備中讀取數(shù)據(jù)返回給應(yīng)用程序。非阻塞IO 如圖52.1.2 所示:
從圖52.1.1.2 可以看出,應(yīng)用程序使用非阻塞訪問方式從設(shè)備讀取數(shù)據(jù),當設(shè)備不可用或數(shù)據(jù)未準備好的時候會立即向內(nèi)核返回一個錯誤碼,表示數(shù)據(jù)讀取失敗。應(yīng)用程序會再次重新讀取數(shù)據(jù),這樣一直往復(fù)循環(huán),直到數(shù)據(jù)讀取成功。
應(yīng)用程序可以使用如下所示示例代碼來實現(xiàn)阻塞訪問:
1 int fd; 2 int data = 0; 3 4 fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打開*/ 5 ret = read(fd, &data, sizeof(data)); /* 讀取數(shù)據(jù)*/從示例代碼52.1.1.1 可以看出,對于設(shè)備驅(qū)動文件的默認讀取方式就是阻塞式的,所以我們前面所有的例程測試APP 都是采用阻塞IO。
如果應(yīng)用程序要采用非阻塞的方式來訪問驅(qū)動設(shè)備文件,可以使用如下所示代碼:
第4 行使用open 函數(shù)打開“/dev/xxx_dev”設(shè)備文件的時候添加了參數(shù)“O_NONBLOCK”,表示以非阻塞方式打開設(shè)備,這樣從設(shè)備中讀取數(shù)據(jù)的時候就是非阻塞方式的了。
等待隊列
1、等待隊列頭
阻塞訪問最大的好處就是當設(shè)備文件不可操作的時候進程可以進入休眠態(tài),這樣可以將CPU 資源讓出來。但是,當設(shè)備文件可以操作的時候就必須喚醒進程,一般在中斷函數(shù)里面完成喚醒工作。Linux 內(nèi)核提供了等待隊列(wait queue)來實現(xiàn)阻塞進程的喚醒工作,如果我們要在驅(qū)動中使用等待隊列,必須創(chuàng)建并初始化一個等待隊列頭,等待隊列頭使用結(jié)構(gòu)體
wait_queue_head_t 表示,wait_queue_head_t 結(jié)構(gòu)體定義在文件include/linux/wait.h 中,結(jié)構(gòu)體內(nèi)容如下所示:
定義好等待隊列頭以后需要初始化,使用init_waitqueue_head 函數(shù)初始化等待隊列頭,函數(shù)原型如下:
void init_waitqueue_head(wait_queue_head_t *q)參數(shù)q 就是要初始化的等待隊列頭。
也可以使用宏DECLARE_WAIT_QUEUE_HEAD 來一次性完成等待隊列頭的定義的初始化。
2、等待隊列項
等待隊列頭就是一個等待隊列的頭部,每個訪問設(shè)備的進程都是一個隊列項,當設(shè)備不可用的時候就要將這些進程對應(yīng)的等待隊列項添加到等待隊列里面。結(jié)構(gòu)體wait_queue_t 表示等待隊列項,結(jié)構(gòu)體內(nèi)容如下:
使用宏DECLARE_WAITQUEUE 定義并初始化一個等待隊列項,宏的內(nèi)容如下:
DECLARE_WAITQUEUE(name, tsk)name 就是等待隊列項的名字,tsk 表示這個等待隊列項屬于哪個任務(wù)(進程),一般設(shè)置為current ,在Linux 內(nèi)核中current 相當于一個全局變量,表示當前進程。因此宏DECLARE_WAITQUEUE 就是給當前正在運行的進程創(chuàng)建并初始化了一個等待隊列項。
3、將隊列項添加/移除等待隊列頭
當設(shè)備不可訪問的時候就需要將進程對應(yīng)的等待隊列項添加到前面創(chuàng)建的等待隊列頭中,只有添加到等待隊列頭中以后進程才能進入休眠態(tài)。當設(shè)備可以訪問以后再將進程對應(yīng)的等待隊列項從等待隊列頭中移除即可,等待隊列項添加API 函數(shù)如下:
函數(shù)參數(shù)和返回值含義如下:
q:等待隊列項要加入的等待隊列頭。
wait:要加入的等待隊列項。
返回值:無。
等待隊列項移除API 函數(shù)如下:
函數(shù)參數(shù)和返回值含義如下:
q:要刪除的等待隊列項所處的等待隊列頭。
wait:要刪除的等待隊列項。
返回值:無。
4、等待喚醒
當設(shè)備可以使用的時候就要喚醒進入休眠態(tài)的進程,喚醒可以使用如下兩個函數(shù):
參數(shù)q 就是要喚醒的等待隊列頭,這兩個函數(shù)會將這個等待隊列頭中的所有進程都喚醒。
wake_up 函數(shù)可以喚醒處于TASK_INTERRUPTIBLE 和TASK_UNINTERRUPTIBLE 狀態(tài)的進程,而wake_up_interruptible 函數(shù)只能喚醒處于TASK_INTERRUPTIBLE 狀態(tài)的進程。
5、等待事件
除了主動喚醒以外,也可以設(shè)置等待隊列等待某個事件,當這個事件滿足以后就自動喚醒等待隊列中的進程,和等待事件有關(guān)的API 函數(shù)如表52.1.2.1 所示:
| wait_event(wq, condition) | 等待以wq 為等待隊列頭的等待隊列被喚醒,前提是condition 條件必須滿足(為真),否則一直阻塞。此函數(shù)會將進程設(shè)置為TASK_UNINTERRUPTIBLE 狀態(tài) |
| wait_event_timeout(wq, condition, timeout) | 功能和wait_event 類似,但是此函數(shù)可以添加超時時間,以jiffies 為單位。此函數(shù)有返回值,如果返回0 的話表示超時時間到,而且condition為假。為1 的話表示condition 為真,也就是條件滿足了。 |
| wait_event_interruptible(wq, condition) | 與wait_event 函數(shù)類似,但是此函數(shù)將進程設(shè)置為TASK_INTERRUPTIBLE,就是可以被信號打斷。 |
| wait_event_interruptible_timeout(wq, condition, timeout) | 與wait_event_timeout 函數(shù)類似,此函數(shù)也將進程設(shè)置為TASK_INTERRUPTIBLE,可以被信號打斷。 |
輪詢
如果用戶應(yīng)用程序以非阻塞的方式訪問設(shè)備,設(shè)備驅(qū)動程序就要提供非阻塞的處理方式,也就是輪詢。poll、epoll 和select 可以用于處理輪詢,應(yīng)用程序通過select、epoll 或poll 函數(shù)來查詢設(shè)備是否可以操作,如果可以操作的話就從設(shè)備讀取或者向設(shè)備寫入數(shù)據(jù)。當應(yīng)用程序調(diào)用select、epoll 或poll 函數(shù)的時候設(shè)備驅(qū)動程序中的poll 函數(shù)就會執(zhí)行,因此需要在設(shè)備驅(qū)動程序中編寫poll 函數(shù)。我們先來看一下應(yīng)用程序中使用的select、poll 和epoll 這三個函數(shù)。
1、select 函數(shù)
select 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
nfds:所要監(jiān)視的這三類文件描述集合中,最大文件描述符加1。
readfds、writefds 和exceptfds:這三個指針指向描述符集合,這三個參數(shù)指明了關(guān)心哪些描述符、需要滿足哪些條件等等,這三個參數(shù)都是fd_set 類型的,fd_set 類型變量的每一個位都代表了一個文件描述符。readfds 用于監(jiān)視指定描述符集的讀變化,也就是監(jiān)視這些文件是否可以讀取,只要這些集合里面有一個文件可以讀取那么seclect 就會返回一個大于0 的值表示文件可以讀取。如果沒有文件可以讀取,那么就會根據(jù)timeout 參數(shù)來判斷是否超時。可以將readfs設(shè)置為NULL,表示不關(guān)心任何文件的讀變化。writefds 和readfs 類似,只是writefs 用于監(jiān)視這些文件是否可以進行寫操作。exceptfds 用于監(jiān)視這些文件的異常。
比如我們現(xiàn)在要從一個設(shè)備文件中讀取數(shù)據(jù),那么就可以定義一個fd_set 變量,這個變量要傳遞給參數(shù)readfds。當我們定義好一個fd_set 變量以后可以使用如下所示幾個宏進行操作:
void FD_ZERO(fd_set *set) void FD_SET(int fd, fd_set *set) void FD_CLR(int fd, fd_set *set) int FD_ISSET(int fd, fd_set *set)FD_ZERO 用于將fd_set 變量的所有位都清零,FD_SET 用于將fd_set 變量的某個位置1,也就是向fd_set 添加一個文件描述符,參數(shù)fd 就是要加入的文件描述符。FD_CLR 用于將fd_set變量的某個位清零,也就是將一個文件描述符從fd_set 中刪除,參數(shù)fd 就是要刪除的文件描述
符。FD_ISSET 用于測試一個文件是否屬于某個集合,參數(shù)fd 就是要判斷的文件描述符。
timeout:超時時間,當我們調(diào)用select 函數(shù)等待某些文件描述符可以設(shè)置超時時間,超時時間使用結(jié)構(gòu)體timeval 表示,結(jié)構(gòu)體定義如下所示:
當timeout 為NULL 的時候就表示無限期的等待。
返回值:0,表示的話就表示超時發(fā)生,但是沒有任何文件描述符可以進行操作;-1,發(fā)生錯誤;其他值,可以進行操作的文件描述符個數(shù)。
使用select 函數(shù)對某個設(shè)備驅(qū)動文件進行讀非阻塞訪問的操作示例如下所示:
2、poll 函數(shù)
在單個線程中,select 函數(shù)能夠監(jiān)視的文件描述符數(shù)量有最大的限制,一般為1024,可以修改內(nèi)核將監(jiān)視的文件描述符數(shù)量改大,但是這樣會降低效率!這個時候就可以使用poll 函數(shù),poll 函數(shù)本質(zhì)上和select 沒有太大的差別,但是poll 函數(shù)沒有最大文件描述符限制,Linux 應(yīng)用程序中poll 函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
fds:要監(jiān)視的文件描述符集合以及要監(jiān)視的事件,為一個數(shù)組,數(shù)組元素都是結(jié)構(gòu)體pollfd類型的,pollfd 結(jié)構(gòu)體如下所示:
fd 是要監(jiān)視的文件描述符,如果fd 無效的話那么events 監(jiān)視事件也就無效,并且revents返回0。events 是要監(jiān)視的事件,可監(jiān)視的事件類型如下所示:
POLLIN 有數(shù)據(jù)可以讀取。 POLLPRI 有緊急的數(shù)據(jù)需要讀取。 POLLOUT 可以寫數(shù)據(jù)。 POLLERR 指定的文件描述符發(fā)生錯誤。 POLLHUP 指定的文件描述符掛起。 POLLNVAL 無效的請求。 POLLRDNORM 等同于POLLINrevents 是返回參數(shù),也就是返回的事件,由Linux 內(nèi)核設(shè)置具體的返回事件。
nfds:poll 函數(shù)要監(jiān)視的文件描述符數(shù)量。
timeout:超時時間,單位為ms。
返回值:返回revents 域中不為0 的pollfd 結(jié)構(gòu)體個數(shù),也就是發(fā)生事件或錯誤的文件描述符數(shù)量;0,超時;-1,發(fā)生錯誤,并且設(shè)置errno 為錯誤類型。
使用poll 函數(shù)對某個設(shè)備驅(qū)動文件進行讀非阻塞訪問的操作示例如下所示:
1 void main(void) 2 { 3 int ret; 4 int fd; /* 要監(jiān)視的文件描述符*/ 5 struct pollfd fds; 6 7 fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式訪問*/ 8 9 /* 構(gòu)造結(jié)構(gòu)體*/ 10 fds.fd = fd; 11 fds.events = POLLIN; /* 監(jiān)視數(shù)據(jù)是否可以讀取*/ 12 13 ret = poll(&fds, 1, 500); /* 輪詢文件是否可操作,超時500ms */ 14 if (ret) { /* 數(shù)據(jù)有效*/ 15 ...... 16 /* 讀取數(shù)據(jù)*/ 17 ...... 18 } else if (ret == 0) { /* 超時*/ 19 ...... 20 } else if (ret < 0) { /* 錯誤*/ 21 ...... 22 } 23 }3、epoll 函數(shù)
傳統(tǒng)的selcet 和poll 函數(shù)都會隨著所監(jiān)聽的fd 數(shù)量的增加,出現(xiàn)效率低下的問題,而且poll 函數(shù)每次必須遍歷所有的描述符來檢查就緒的描述符,這個過程很浪費時間。為此,epoll應(yīng)運而生,epoll 就是為處理大并發(fā)而準備的,一般常常在網(wǎng)絡(luò)編程中使用epoll 函數(shù)。應(yīng)用程序需要先使用epoll_create 函數(shù)創(chuàng)建一個epoll 句柄,epoll_create 函數(shù)原型如下:
函數(shù)參數(shù)和返回值含義如下:
size:從Linux2.6.8 開始此參數(shù)已經(jīng)沒有意義了,隨便填寫一個大于0 的值就可以。
返回值:epoll 句柄,如果為-1 的話表示創(chuàng)建失敗。
epoll 句柄創(chuàng)建成功以后使用epoll_ctl 函數(shù)向其中添加要監(jiān)視的文件描述符以及監(jiān)視的事件,epoll_ctl 函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
epfd:要操作的epoll 句柄,也就是使用epoll_create 函數(shù)創(chuàng)建的epoll 句柄。
op:表示要對epfd(epoll 句柄)進行的操作,可以設(shè)置為:
fd:要監(jiān)視的文件描述符。
event:要監(jiān)視的事件類型,為epoll_event 結(jié)構(gòu)體類型指針,epoll_event 結(jié)構(gòu)體類型如下所示:
結(jié)構(gòu)體epoll_event 的events 成員變量表示要監(jiān)視的事件,可選的事件如下所示:
EPOLLIN 有數(shù)據(jù)可以讀取。 EPOLLOUT 可以寫數(shù)據(jù)。 EPOLLPRI 有緊急的數(shù)據(jù)需要讀取。 EPOLLERR 指定的文件描述符發(fā)生錯誤。 EPOLLHUP 指定的文件描述符掛起。 EPOLLET 設(shè)置epoll 為邊沿觸發(fā),默認觸發(fā)模式為水平觸發(fā)。 EPOLLONESHOT 一次性的監(jiān)視,當監(jiān)視完成以后還需要再次監(jiān)視某個fd,那么就需要將 fd 重新添加到epoll 里面。上面這些事件可以進行“或”操作,也就是說可以設(shè)置監(jiān)視多個事件。
返回值:0,成功;-1,失敗,并且設(shè)置errno 的值為相應(yīng)的錯誤碼。
一切都設(shè)置好以后應(yīng)用程序就可以通過epoll_wait 函數(shù)來等待事件的發(fā)生,類似select 函數(shù)。epoll_wait 函數(shù)原型如下所示:
函數(shù)參數(shù)和返回值含義如下:
epfd:要等待的epoll。
events:指向epoll_event 結(jié)構(gòu)體的數(shù)組,當有事件發(fā)生的時候Linux 內(nèi)核會填寫events,調(diào)用者可以根據(jù)events 判斷發(fā)生了哪些事件。
maxevents:events 數(shù)組大小,必須大于0。
timeout:超時時間,單位為ms。
返回值:0,超時;-1,錯誤;其他值,準備就緒的文件描述符數(shù)量。
epoll 更多的是用在大規(guī)模的并發(fā)服務(wù)器上,因為在這種場合下select 和poll 并不適合。當設(shè)計到的文件描述符(fd)比較少的時候就適合用selcet 和poll,本章我們就使用sellect 和poll 這兩個函數(shù)。
Linux 驅(qū)動下的poll 操作函數(shù)
當應(yīng)用程序調(diào)用select 或poll 函數(shù)來對驅(qū)動程序進行非阻塞訪問的時候,驅(qū)動程序file_operations 操作集中的poll 函數(shù)就會執(zhí)行。所以驅(qū)動程序的編寫者需要提供對應(yīng)的poll 函數(shù),poll 函數(shù)原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)函數(shù)參數(shù)和返回值含義如下:
filp:要打開的設(shè)備文件(文件描述符)。
wait:結(jié)構(gòu)體poll_table_struct 類型指針,由應(yīng)用程序傳遞進來的。一般將此參數(shù)傳遞給poll_wait 函數(shù)。
返回值:向應(yīng)用程序返回設(shè)備或者資源狀態(tài),可以返回的資源狀態(tài)如下:
我們需要在驅(qū)動程序的poll 函數(shù)中調(diào)用poll_wait 函數(shù),poll_wait 函數(shù)不會引起阻塞,只是將應(yīng)用程序添加到poll_table 中,poll_wait 函數(shù)原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)參數(shù)wait_address 是要添加到poll_table 中的等待隊列頭,參數(shù)p 就是poll_table,就是file_operations 中poll 函數(shù)的wait 參數(shù)。
阻塞IO 實驗
在上一章Linux 中斷實驗中,我們直接在應(yīng)用程序中通過read 函數(shù)不斷的讀取按鍵狀態(tài),當按鍵有效的時候就打印出按鍵值。這種方法有個缺點,那就是imx6uirqApp 這個測試應(yīng)用程序擁有很高的CPU 占用率,大家可以在開發(fā)板中加載上一章的驅(qū)動程序模塊imx6uirq.ko,然后以后臺運行模式打開imx6uirqApp 這個測試軟件,命令如下:
./imx6uirqApp /dev/imx6uirq &測試驅(qū)動是否正常工作,如果驅(qū)動工作正常的話輸入“top”命令查看imx6uirqApp 這個應(yīng)用程序的CPU 使用率,結(jié)果如圖52.2.1 所示:
從圖52.2.1 可以看出,imx6uirqApp 這個應(yīng)用程序的CPU 使用率竟然高達99.6%,這僅僅是一個讀取按鍵值的應(yīng)用程序,這么高的CPU 使用率顯然是有問題的!原因就在于我們是直接在while 循環(huán)中通過read 函數(shù)讀取按鍵值,因此imx6uirqApp 這個軟件會一直運行,一直讀取按鍵值,CPU 使用率肯定就會很高。最好的方法就是在沒有有效的按鍵事件發(fā)生的時候,
imx6uirqApp 這個應(yīng)用程序應(yīng)該處于休眠狀態(tài),當有按鍵事件發(fā)生以后imx6uirqApp 這個應(yīng)用程序才運行,打印出按鍵值,這樣就會降低CPU 使用率,本小節(jié)我們就使用阻塞IO 來實現(xiàn)此功能。
硬件原理圖分析
本章實驗硬件原理圖參考15.2 小節(jié)即可。
實驗程序編寫
1、驅(qū)動程序編寫
本實驗對應(yīng)的例程路徑為:開發(fā)板光盤-> 2、Linux 驅(qū)動例程-> 14_blockio。
本章實驗我們在上一章的“13_irq”實驗的基礎(chǔ)上完成,主要是對其添加阻塞訪問相關(guān)的代碼。新建名為“14_blockio”的文件夾,然后在14_blockio 文件夾里面創(chuàng)建vscode 工程,工作區(qū)命名為“blockio”。將“13_irq”實驗中的imx6uirq.c 復(fù)制到14_blockio 文件夾中,并重命名為blockio.c。接下來我們就修改blockio.c 這個文件,在其中添加阻塞相關(guān)的代碼,完成以后的
blockio.c 內(nèi)容如下所示(因為是在上一章實驗的imx6uirq.c 文件的基礎(chǔ)上修改的,為了減少篇幅,下面的代碼有省略):
第32 行,修改設(shè)備文件名字為“blockio”,當驅(qū)動程序加載成功以后就會在根文件系統(tǒng)中出現(xiàn)一個名為“/dev/blockio”的文件。
第61 行,在設(shè)備結(jié)構(gòu)體中添加一個等待隊列頭r_wait,因為在Linux 驅(qū)動中處理阻塞IO需要用到等待隊列。
第107~110 行,定時器中斷處理函數(shù)執(zhí)行,表示有按鍵按下,先在107 行判斷一下是否是一次有效的按鍵,如果是的話就通過wake_up 或者wake_up_interruptible 函數(shù)來喚醒等待隊列r_wait。
第168 行,調(diào)用init_waitqueue_head 函數(shù)初始化等待隊列頭r_wait。
第200~206 行,采用等待事件來處理read 的阻塞訪問,wait_event_interruptible 函數(shù)等待releasekey 有效,也就是有按鍵按下。如果按鍵沒有按下的話進程就會進入休眠狀態(tài),因為采用了wait_event_interruptible 函數(shù),因此進入休眠態(tài)的進程可以被信號打斷。
第208~218 行,首先使用DECLARE_WAITQUEUE 宏定義一個等待隊列,如果沒有按鍵按下的話就使用add_wait_queue 函數(shù)將當前任務(wù)的等待隊列添加到等待隊列頭r_wait 中。隨后調(diào)用__set_current_state 函數(shù)設(shè)置當前進程的狀態(tài)為TASK_INTERRUPTIBLE,也就是可以被信號打斷。接下來調(diào)用schedule 函數(shù)進行一次任務(wù)切換,當前進程就會進入到休眠態(tài)。如果有按
鍵按下,那么進入休眠態(tài)的進程就會喚醒,然后接著從休眠點開始運行。在這里也就是從第213行開始運行,首先通過signal_pending 函數(shù)判斷一下進程是不是由信號喚醒的,如果是由信號喚醒的話就直接返回-ERESTARTSYS 這個錯誤碼。如果不是由信號喚醒的(也就是被按鍵喚醒的)那么就在217 行調(diào)用__set_current_state 函數(shù)將任務(wù)狀態(tài)設(shè)置為TASK_RUNNING,然后在
218 行調(diào)用remove_wait_queue 函數(shù)將進程從等待隊列中刪除。
使用等待隊列實現(xiàn)阻塞訪問重點注意兩點:
①、將任務(wù)或者進程加入到等待隊列頭,
②、在合適的點喚醒等待隊列,一般都是中斷處理函數(shù)里面。
2、編寫測試APP
本節(jié)實驗的測試APP 直接使用第51.3.3 小節(jié)所編寫的imx6uirqApp.c,將imx6uirqApp.c 復(fù)制到本節(jié)實驗文件夾下,并且重命名為blockioApp.c,不需要修改任何內(nèi)容。
運行測試
1、編譯驅(qū)動程序和測試APP
①、編譯驅(qū)動程序
編寫Makefile 文件,本章實驗的Makefile 文件和第四十章實驗基本一樣,只是將obj-m 變量的值改為blockio.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為blockio.o。
輸入如下命令編譯出驅(qū)動模塊文件:
編譯成功以后就會生成一個名為“blockio.ko”的驅(qū)動模塊文件。
②、編譯測試APP
輸入如下命令編譯測試noblockioApp.c 這個測試程序:
編譯成功以后就會生成blcokioApp 這個應(yīng)用程序。
2、運行測試
將上一小節(jié)編譯出來blockio.ko 和blockioApp 這兩個文件拷貝到rootfs/lib/modules/4.1.15目錄中,重啟開發(fā)板,進入到目錄lib/modules/4.1.15 中,輸入如下命令加載blockio.ko 驅(qū)動模塊:
驅(qū)動加載成功以后使用如下命令打開blockioApp 這個測試APP,并且以后臺模式運行:
./blockioApp /dev/blockio &按下開發(fā)板上的KEY0 按鍵,結(jié)果如圖52.2.3.1 所示:
當按下KEY0 按鍵以后blockioApp 這個測試APP 就會打印出按鍵值。輸入“top”命令,查看blockioAPP 這個應(yīng)用APP 的CPU 使用率,如圖52.2.3.2 所示:
從圖52.2.3.2 可以看出,當我們在按鍵驅(qū)動程序里面加入阻塞訪問以后,blockioApp 這個應(yīng)用程序的CPU 使用率從圖52.2.1 中的99.6%降低到了0.0%。大家注意,這里的0.0%并不是說blockioApp 這個應(yīng)用程序不使用CPU 了,只是因為使用率太小了,CPU 使用率可能為0.00001%,但是圖52.2.3.2 只能顯示出小數(shù)點后一位,因此就顯示成了0.0%。
我們可以使用“kill”命令關(guān)閉后臺運行的應(yīng)用程序,比如我們關(guān)閉掉blockioApp 這個后臺運行的應(yīng)用程序。首先輸出“Ctrl+C”關(guān)閉top 命令界面,進入到命令行模式。然后使用“ps”命令查看一下blockioApp 這個應(yīng)用程序的PID,如圖52.2.3.3 所示:
從圖圖52.2.3.3 可以看出,blockioApp 這個應(yīng)用程序的PID 為149,使用“kill -9 PID”即可“殺死”指定PID 的進程,比如我們現(xiàn)在要“殺死”PID 為149 的blockioApp 應(yīng)用程序,可是使用如下命令:
輸入上述命令以后終端顯示如圖52.2.3.4 所示:
從圖52.2.3.4 可以看出,“./blockioApp /dev/blockio”這個應(yīng)用程序已經(jīng)被“殺掉”了,在此輸入“ps”命令查看當前系統(tǒng)運行的進程,會發(fā)現(xiàn)blockioApp 已經(jīng)不見了。這個就是使用kill命令“殺掉”指定進程的方法。
非阻塞IO 實驗
硬件原理圖分析
本章實驗硬件原理圖參考15.2 小節(jié)即可。
實驗程序編寫
1、驅(qū)動程序編寫
本實驗對應(yīng)的例程路徑為:開發(fā)板光盤-> 2、Linux 驅(qū)動例程-> 15_noblockio。
本章實驗我們在52.2 小節(jié)中的“14_blockio”實驗的基礎(chǔ)上完成,上一小節(jié)實驗我們已經(jīng)在驅(qū)動中添加了阻塞IO 的代碼,本小節(jié)我們繼續(xù)完善驅(qū)動,加入非阻塞IO 驅(qū)動代碼。新建名為“15_noblockio”的文件夾,然后在15_noblockio 文件夾里面創(chuàng)建vscode 工程,工作區(qū)命名為“noblockio”。將“14_blockio”實驗中的blockio.c 復(fù)制到15_noblockio 文件夾中,并重命名為noblockio.c。接下來我們就修改noblockio.c 這個文件,在其中添加非阻塞相關(guān)的代碼,完成
以后的noblockio.c 內(nèi)容如下所示(因為是在上一小節(jié)實驗的blockio.c 文件的基礎(chǔ)上修改的,為了減少篇幅,下面的代碼有省略):
第32 行,修改設(shè)備文件名字為“noblockio”,當驅(qū)動程序加載成功以后就會在根文件系統(tǒng)中出現(xiàn)一個名為“/dev/noblockio”的文件。
第202~204 行,判斷是否為非阻塞式讀取訪問,如果是的話就判斷按鍵是否有效,也就是判斷一下有沒有按鍵按下,如果沒有的話就返回-EAGAIN。
第241~ 252 行,imx6uirq_poll 函數(shù)就是file_operations 驅(qū)動操作集中的poll 函數(shù),當應(yīng)用程序調(diào)用select 或者poll 函數(shù)的時候imx6uirq_poll 函數(shù)就會執(zhí)行。第246 行調(diào)用poll_wait 函數(shù)將等待隊列頭添加到poll_table 中,第
248~250 行判斷按鍵是否有效,如果按鍵有效的話就向應(yīng)用程序返回POLLIN 這個事件,表示有數(shù)據(jù)可以讀取。
第259 行,設(shè)置file_operations 的poll 成員變量為imx6uirq_poll。
2、編寫測試APP
新建名為noblockioApp.c 測試APP 文件,然后在其中輸入如下所示內(nèi)容:
第52~73 行,這段代碼使用poll 函數(shù)來實現(xiàn)非阻塞訪問,在while 循環(huán)中使用poll 函數(shù)不斷的輪詢,檢查驅(qū)動程序是否有數(shù)據(jù)可以讀取,如果可以讀取的話就調(diào)用read 函數(shù)讀取按鍵數(shù)據(jù)。
第75~101 行,這段代碼使用select 函數(shù)來實現(xiàn)非阻塞訪問。
運行測試
1、編譯驅(qū)動程序和測試APP
①、編譯驅(qū)動程序
編寫Makefile 文件,本章實驗的Makefile 文件和第四十章實驗基本一樣,只是將obj-m 變量的值改為noblockio.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為noblockio.o。
輸入如下命令編譯出驅(qū)動模塊文件:
編譯成功以后就會生成一個名為“noblockio.ko”的驅(qū)動模塊文件。
②、編譯測試APP
輸入如下命令編譯測試noblockioApp.c 這個測試程序:
編譯成功以后就會生成noblcokioApp 這個應(yīng)用程序。
2、運行測試
將上一小節(jié)編譯出來noblockio.ko 和noblockioApp 這兩個文件拷貝到
rootfs/lib/modules/4.1.15 目錄中,重啟開發(fā)板,進入到目錄lib/modules/4.1.15 中,輸入如下命令
加載blockio.ko 驅(qū)動模塊:
驅(qū)動加載成功以后使用如下命令打開noblockioApp 這個測試APP,并且以后臺模式運行:
./noblockioApp /dev/noblockio &按下開發(fā)板上的KEY0 按鍵,結(jié)果如圖52.3.3.1 所示:
當按下KEY0 按鍵以后noblockioApp 這個測試APP 就會打印出按鍵值。輸入“top”命令,查看noblockioAPP 這個應(yīng)用APP 的CPU 使用率,如圖52.3.3.2 所示:
從圖52.3.3.2 可以看出,采用非阻塞方式讀處理以后,noblockioApp 的CPU 占用率也低至0.0%,和圖52.2.3.2 中的blockioApp 一樣,這里的0.0%并不是說noblockioApp 這個應(yīng)用程序不使用CPU 了,只是因為使用率太小了,而圖中只能顯示出小數(shù)點后一位,因此就顯示成了0.0%。
如果要“殺掉”處于后臺運行模式的noblockioApp 這個應(yīng)用程序,可以參考52.2.3 小節(jié)講解的方法。
總結(jié)
以上是生活随笔為你收集整理的Linux 阻塞和非阻塞IO 实验的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 空间矢量脉冲宽度调制(SVPWM)Sim
- 下一篇: matlab求出拟合曲线的方程,已知数据