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