文章目錄
- 一、阻塞和非阻塞IO
-
- 1.阻塞和非阻塞簡介
- 2.等待隊列
-
- 1.等待隊列頭
- 2.等待隊列項
- 3.将隊列項添加/移除等待隊列頭
- 4.等待喚醒
- 5.等待事件
- 3.輪詢
-
- 1.select函數
- 2.poll
- 3.ePoll
- 4.linux驅動下的poll操作函數
- 二、阻塞IO實驗
-
- 1.硬體原理圖分析
- 2.實驗程式編寫
-
- 1.驅動程式編寫
- 2.編寫測試APP
- 3.運作測試
- 三、非阻塞IO實驗
-
- 1.硬體原理圖分析
- 2.實驗程式編寫
-
- 1.驅動程式編寫
- 2.編寫測試APP
- 3.運作測試
一、阻塞和非阻塞IO
阻塞和非阻塞 IO 是 Linux 驅動開發裡面很常見的兩種裝置通路模式,在編寫驅動的時候一定要考慮到阻塞和非阻塞。
本章我們就來學習一下阻塞和非阻塞 IO,以及如何在驅動程式中處理阻塞與非阻塞,如何在驅動程式使用等待隊列和 poll 機制。
1.阻塞和非阻塞簡介
這裡的 IO 指的是 Input/Output,也就是輸入/輸出,是應用程式對驅動裝置的輸入/輸出操作。
當應用程式對裝置驅動進行操作的時候,如果不能擷取到裝置資源,那麼阻塞式 IO 就會将應用程式對應的線程挂起,直到裝置資源可以擷取為止。對于非阻塞 IO,應用程式對應的線程不會挂起,它要麼一直輪詢等待,直到裝置資源可以使用,要麼就直接放棄。

應用程式調用 read 函數從裝置中讀取資料,當裝置不可用或資料未準備好的時候就會進入到休眠态。等裝置可用的時候就會從休眠态喚醒,然後從裝置中讀取資料傳回給應用程式。
可以看出,應用程式使用非阻塞通路方式從裝置讀取資料,當裝置不可用或資料未準備好的時候會立即向核心傳回一個錯誤碼,表示資料讀取失敗。應用程式會再次重新讀取資料,這樣一直往複循環,直到資料讀取成功。
應用程式可以使用如下所示示例代碼來實作阻塞通路:
1 int fd;
2 int data = 0;
3
4 fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打開 */
5 ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
對于裝置驅動檔案的預設讀取方式就是阻塞式的,是以我
們前面所有的例程測試 APP 都是采用阻塞 IO。
如果應用程式要采用非阻塞的方式來通路驅動裝置檔案,可以使用如下所示代碼:
1 int fd;
2 int data = 0;
3
4 fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打開 */
5 ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
使用 open 函數打開“/dev/xxx_dev”裝置檔案的時候添加了參數“O_NONBLOCK”,表示以非阻塞方式打開裝置,這樣從裝置中讀取資料的時候就是非阻塞方式的了
2.等待隊列
1.等待隊列頭
阻塞通路最大的好處就是當裝置檔案不可操作的時候程序可以進入休眠态,這樣可以将CPU 資源讓出來。但是,當裝置檔案可以操作的時候就必須喚醒程序,一般在中斷函數裡完成喚醒工作。
Linux 核心提供了等待隊列(wait queue)來實作阻塞程序的喚醒工作,如果我們要在驅動中使用等待隊列,必須建立并初始化一個等待隊列頭,等待隊列頭使用結構體wait_queue_head_t 表示,
wait_queue_head_t 結構體定義在檔案 include/linux/wait.h 中,結構體内容如下所示:
39 struct __wait_queue_head {
40 spinlock_t lock;
41 struct list_head task_list;
42 };
43 typedef struct __wait_queue_head wait_queue_head_t;
定義好等待隊列頭以後需要初始化,使用 init_waitqueue_head 函數初始化等待隊列頭,函數原型如下:
參數 q 就是要初始化的等待隊列頭。
也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 來一次性完成等待隊列頭的定義的初始化
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 , 在 Linux 内 核 中 current 相 當 于 一 個 全 局 變 量 , 表 示 當 前 進 程 。 因 此 宏DECLARE_WAITQUEUE 就是給目前正在運作的程序建立并初始化了一個等待隊列項。
3.将隊列項添加/移除等待隊列頭
當裝置不可通路的時候就需要将程序對應的等待隊列項添加到前面建立的等待隊列頭中,隻有添加到等待隊列頭中以後程序才能進入休眠态。當裝置可以通路以後再将程序對應的等待隊列項從等待隊列頭中移除即可,
等待隊列項添加 API 函數如下:
void add_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
函數參數和傳回值含義如下:
q :等待隊列項要加入的等待隊列頭。
wait:要加入的等待隊列項。
傳回值:無。
等待隊列項移除 API 函數如下:
void remove_wait_queue(wait_queue_head_t *q,
wait_queue_t *wait)
函數參數和傳回值含義如下:
q :要删除的等待隊列項所處的等待隊列頭。
wait:要删除的等待隊列項。
傳回值:無。
4.等待喚醒
當裝置可以使用的時候就要喚醒進入休眠态的程序,喚醒可以使用如下兩個函數:
void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
參數 q 就是要喚醒的等待隊列頭,這兩個函數會将這個等待隊列頭中的所有程序都喚醒。wake_up 函數可以喚醒處于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 狀态的程序,而 wake_up_interruptible 函數隻能喚醒處于 TASK_INTERRUPTIBLE 狀态的程序。
5.等待事件
除了主動喚醒以外,也可以設定等待隊列等待某個事件,當這個事件滿足以後就自動喚醒等待隊列中的程序,和等待事件有關的 API 函數如表 所示:
3.輪詢
如果使用者應用程式以非阻塞的方式通路裝置,裝置驅動程式就要提供非阻塞的處理方式,也就是輪詢。
poll、epoll 和 select 可以用于處理輪詢,應用程式通過 select、epoll 或 poll 函數來查詢裝置是否可以操作,如果可以操作的話就從裝置讀取或者向裝置寫入資料。當應用程式調用 select、epoll 或 poll 函數的時候裝置驅動程式中的 poll 函數就會執行,是以需要在裝置驅動程式中編寫 poll 函數。
我們先來看一下應用程式中使用的 select、poll 和 epoll 這三個函數。
1.select函數
select 函數原型如下:
int select(int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout)
函數參數和傳回值含義如下:
nfds :所要監視的這三類檔案描述集合中,最大檔案描述符加 1。
readfds 、writefds 和 和 exceptfds:這三個指針指向描述符集合,這三個參數指明了關心哪些描述符、需要滿足哪些條件等等,這三個參數都是 fd_set 類型的,fd_set 類型變量的每一個位都代表了一個檔案描述符。readfds 用于監視指定描述符集的讀變化,也就是監視這些檔案是否
可以讀取,隻要這些集合裡面有一個檔案可以讀取那麼 seclect 就會傳回一個大于 0 的值表示檔案可以讀取。如果沒有檔案可以讀取,那麼就會根據 timeout 參數來判斷是否逾時。可以将 readfs設定為 NULL,表示不關心任何檔案的讀變化。writefds 和 readfs 類似,隻是 writefs 用于監視
這些檔案是否可以進行寫操作。exceptfds 用于監視這些檔案的異常。
比如我們現在要從一個裝置檔案中讀取資料,那麼就可以定義一個 fd_set 變量,這個變量要傳遞給參數 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 添加一個檔案描述符,參數 fd 就是要加入的檔案描述符。FD_CLR 用于将 fd_set變量的某個位清零,也就是将一個檔案描述符從 fd_set 中删除,參數 fd 就是要删除的檔案描述符。
FD_ISSET 用于測試一個檔案是否屬于某個集合,參數 fd 就是要判斷的檔案描述符。timeout:逾時時間,
當我們調用 select 函數等待某些檔案描述符可以設定逾時時間,逾時時
間使用結構體 timeval 表示,結構體定義如下所示:
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
當 timeout 為 NULL 的時候就表示無限期的等待。
傳回值:0,表示的話就表示逾時發生,但是沒有任何檔案描述符可以進行操作;-1,發生錯誤;其他值,可以進行操作的檔案描述符個數。
使用 select 函數對某個裝置驅動檔案進行讀非阻塞通路的操作示例如下所示:
1 void main(void)
2 {
3 int ret, fd; /* 要監視的檔案描述符 */
4 fd_set readfds; /* 讀操作檔案描述符集 */
5 struct timeval timeout; /* 逾時結構體 */
6
7 fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式通路 */
8
9 FD_ZERO(&readfds); /* 清除 readfds */
10 FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 裡面 */
11
12 /* 構造逾時時間 */
13 timeout.tv_sec = 0;
14 timeout.tv_usec = 500000; /* 500ms */
15
16 ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
17 switch (ret) {
18 case 0: /* 逾時 */
19 printf("timeout!\r\n");
20 break;
21 case -1: /* 錯誤 */
22 printf("error!\r\n");
23 break;
24 default: /* 可以讀取資料 */
25 if(FD_ISSET(fd, &readfds)) { /* 判斷是否為 fd 檔案描述符 */
26 /* 使用 read 函數讀取資料 */
27 }
28 break;
29 }
30 }
2.poll
在單個線程中,select 函數能夠監視的檔案描述符數量有最大的限制,一般為 1024,可以修改核心将監視的檔案描述符數量改大,但是這樣會降低效率!這個時候就可以使用 poll 函數,poll 函數本質上和 select 沒有太大的差别,但是 poll 函數沒有最大檔案描述符限制,Linux 應用程式中 poll 函數原型如下所示:
函數參數和傳回值含義如下:
fds :要監視的檔案描述符集合以及要監視的事件,為一個數組,數組元素都是結構體 pollfd類型的,
pollfd 結構體如下所示:
struct pollfd {
int fd; /* 檔案描述符 */
short events; /* 請求的事件 */
short revents; /* 傳回的事件 */
};
fd 是要監視的檔案描述符,如果 fd 無效的話那麼 events 監視事件也就無效,并且 revents
傳回 0。events 是要監視的事件,
可監視的事件類型如下所示:
POLLIN 有資料可以讀取。
POLLPRI 有緊急的資料需要讀取。
POLLOUT 可以寫資料。
POLLERR 指定的檔案描述符發生錯誤。
POLLHUP 指定的檔案描述符挂起。
POLLNVAL 無效的請求。
POLLRDNORM 等同于 POLLIN
revents 是傳回參數,也就是傳回的事件,由 Linux 核心設定具體的傳回事件。
nfds:poll 函數要監視的檔案描述符數量。
timeout :逾時時間,機關為 ms。
傳回值:傳回 revents 域中不為 0 的 pollfd 結構體個數,也就是發生事件或錯誤的檔案描述符數量;0,逾時;-1,發生錯誤,并且設定 errno 為錯誤類型。
使用 poll 函數對某個裝置驅動檔案進行讀非阻塞通路的操作示例如下所示:
1 void main(void)
2 {
3 int ret;
4 int fd; /* 要監視的檔案描述符 */
5 struct pollfd fds;
6
7 fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式通路 */
8
9 /* 構造結構體 */
10 fds.fd = fd;
11 fds.events = POLLIN; /* 監視資料是否可以讀取 */
12
13 ret = poll(&fds, 1, 500); /* 輪詢檔案是否可操作,逾時 500ms */
14 if (ret) { /* 資料有效 */
15 ......
16 /* 讀取資料 */
17 ......
18 } else if (ret == 0) { /* 逾時 */
19 ......
20 } else if (ret < 0) { /* 錯誤 */
21 ......
22 }
23 }
3.ePoll
傳統的 selcet 和 poll 函數都會随着所監聽的 fd 數量的增加,出現效率低下的問題,而且poll 函數每次必須周遊所有的描述符來檢查就緒的描述符,這個過程很浪費時間。為此,epoll應運而生,epoll 就是為處理大并發而準備的,一般常常在網絡程式設計中使用 epoll 函數。應用程式需要先使用 epoll_create 函數建立一個 epoll 句柄,epoll_create 函數原型如下:
函數參數和傳回值含義如下:
size :從 Linux2.6.8 開始此參數已經沒有意義了,随便填寫一個大于 0 的值就可以。
傳回值:epoll 句柄,如果為-1 的話表示建立失敗。
epoll 句柄建立成功以後使用 epoll_ctl 函數向其中添加要監視的檔案描述符以及監視的事件,epoll_ctl 函數原型如下所示:
int epoll_ctl(int epfd,
int op,
int fd,
struct epoll_event *event)
函數參數和傳回值含義如下:
epfd :要操作的 epoll 句柄,也就是使用 epoll_create 函數建立的 epoll 句柄。
op :表示要對 epfd(epoll 句柄)進行的操作,可以設定為:
EPOLL_CTL_ADD 向 epfd 添加檔案參數 fd 表示的描述符。
EPOLL_CTL_MOD 修改參數 fd 的 event 事件。
EPOLL_CTL_DEL 從 epfd 中删除 fd 描述符。
fd:要監視的檔案描述符。
event :要監視的事件類型,為 epoll_event 結構體類型指針,
epoll_event 結構體類型如下
struct epoll_event {
uint32_t events; /* epoll 事件 */
epoll_data_t data; /* 使用者資料 */
};
結構體 epoll_event 的 events 成員變量表示要監視的事件,可選的事件如下所示:
EPOLLIN 有資料可以讀取。
EPOLLOUT 可以寫資料。
EPOLLPRI 有緊急的資料需要讀取。
EPOLLERR 指定的檔案描述符發生錯誤。
EPOLLHUP 指定的檔案描述符挂起。
EPOLLET 設定 epoll 為邊沿觸發,預設觸發模式為水準觸發。
EPOLLONESHOT 一次性的監視,當監視完成以後還需要再次監視某個 fd,那麼就需要将fd 重新添加到 epoll 裡面。
上面這些事件可以進行“或”操作,也就是說可以設定監視多個事件。
傳回值:0,成功;-1,失敗,并且設定 errno 的值為相應的錯誤碼。
一切都設定好以後應用程式就可以通過 epoll_wait 函數來等待事件的發生,類似 select 函數。
epoll_wait 函數原型如下所示:
int epoll_wait(int epfd,
struct epoll_event *events,
int maxevents,
int timeout)
函數參數和傳回值含義如下:
epfd :要等待的 epoll。
events :指向 epoll_event 結構體的數組,當有事件發生的時候 Linux 核心會填寫 events,調用者可以根據 events 判斷發生了哪些事件。
maxevents:events 數組大小,必須大于 0。
timeout :逾時時間,機關為 ms。
傳回值:0,逾時;-1,錯誤;其他值,準備就緒的檔案描述符數量。
epoll 更多的是用在大規模的并發伺服器上,因為在這種場合下 select 和 poll 并不适合。當設計到的檔案描述符(fd)比較少的時候就适合用 selcet 和 poll,本章我們就使用 sellect 和 poll 這兩個函數。
4.linux驅動下的poll操作函數
當應用程式調用 select 或 poll 函數來對驅動程式進行非阻塞通路的時候,驅動程式file_operations 操作集中的 poll 函數就會執行。是以驅動程式的編寫者需要提供對應的 poll 函數,poll 函數原型如下所示:
函數參數和傳回值含義如下:
filp :要打開的裝置檔案(檔案描述符)。
wait :結構體 poll_table_struct 類型指針,由應用程式傳遞進來的。一般将此參數傳遞給
poll_wait 函數。
傳回值:向應用程式傳回裝置或者資源狀态,可以傳回的資源狀态如下:
POLLIN 有資料可以讀取。
POLLPRI 有緊急的資料需要讀取。
POLLOUT 可以寫資料。
POLLERR 指定的檔案描述符發生錯誤。
POLLHUP 指定的檔案描述符挂起。
POLLNVAL 無效的請求。
POLLRDNORM 等同于 POLLIN,普通資料可讀
我們需要在驅動程式的 poll 函數中調用 poll_wait 函數,poll_wait 函數不會引起阻塞,隻是将應用程式添加到 poll_table 中,poll_wait 函數原型如下:
參數 wait_address 是要添加到 poll_table 中的等待隊列頭,
參數 p 就是 poll_table,就是file_operations 中 poll 函數的 wait 參數.
二、阻塞IO實驗
在上一章 Linux 中斷實驗中,我們直接在應用程式中通過 read 函數不斷的讀取按鍵狀态,當按鍵有效的時候就列印出按鍵值。這種方法有個缺點,那就是 imx6uirqApp 這個測試應用程式擁有很高的 CPU 占用率,大家可以在開發闆中加載上一章的驅動程式子產品 imx6uirq.ko,然後以背景運作模式打開 imx6uirqApp 這個測試軟體,指令如下:
測試驅動是否正常工作,如果驅動工作正常的話輸入“top”指令檢視 imx6uirqApp 這個應用程式的 CPU 使用率,結果如圖 所示:
imx6uirqApp 這個應用程式的 CPU 使用率竟然高達 99.6%,這僅僅
是一個讀取按鍵值的應用程式,這麼高的 CPU 使用率顯然是有問題的!原因就在于我們是直接在 while 循環中通過 read 函數讀取按鍵值,是以 imx6uirqApp 這個軟體會一直運作,一直讀取按鍵值,CPU 使用率肯定就會很高。
最好的方法就是在沒有有效的按鍵事件發生的時候,imx6uirqApp 這個應用程式應該處于休眠狀态,當有按鍵事件發生以後 imx6uirqApp 這個應用程式才運作,列印出按鍵值,這樣就會降低 CPU 使用率,本小節我們就使用阻塞 IO 來實作此功能。
1.硬體原理圖分析
2.實驗程式編寫
1.驅動程式編寫
建立名為“14_blockio”的檔案夾,然後在 14_blockio 檔案夾裡面建立 vscode 工程,工作區命名為“blockio”。将“13_irq”實驗中的 imx6uirq.c 複制到 14_blockio 檔案夾中,并重命名為 blockio.c。接下來我們就修改 blockio.c 這個檔案,在其中添加阻塞相關的代碼,完成以後的blockio.c 内容如下所示(因為是在上一章實驗的 imx6uirq.c 檔案的基礎上修改的,
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
檔案名 : block.c
作者 : 左忠凱
版本 : V1.0
描述 : 阻塞IO通路
其他 : 無
論壇 : www.openedv.com
日志 : 初版V1.0 2019/7/26 左忠凱建立
***************************************************************/
#define IMX6UIRQ_CNT 1 /* 裝置号個數 */
#define IMX6UIRQ_NAME "blockio" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按鍵值 */
#define INVAKEY 0XFF /* 無效的按鍵值 */
#define KEY_NUM 1 /* 按鍵數量 */
/* 中斷IO描述結構體 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中斷号 */
unsigned char value; /* 按鍵對應的鍵值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中斷服務函數 */
};
/* imx6uirq裝置結構體 */
struct imx6uirq_dev{
dev_t devid; /* 裝置号 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 裝置 */
int major; /* 主裝置号 */
int minor; /* 次裝置号 */
struct device_node *nd; /* 裝置節點 */
atomic_t keyvalue; /* 有效的按鍵鍵值 */
atomic_t releasekey; /* 标記是否完成一次完成的按鍵,包括按下和釋放 */
struct timer_list timer;/* 定義一個定時器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按鍵init述數組 */
unsigned char curkeynum; /* 目前init按鍵号 */
wait_queue_head_t r_wait; /* 讀等待隊列頭 */
};
struct imx6uirq_dev imx6uirq; /* irq裝置 */
/* @description : 中斷服務函數,開啟定時器
* 定時器用于按鍵消抖。
* @param - irq : 中斷号
* @param - dev_id : 裝置結構。
* @return : 中斷執行結果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定時 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定時器服務函數,用于按鍵消抖,定時器到了以後
* 再次讀取按鍵值,如果按鍵還是處于按下狀态就表示按鍵有效。
* @param - arg : 裝置結構變量
* @return : 無
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 讀取IO值 */
if(value == 0){ /* 按下按鍵 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按鍵松開 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标記松開按鍵,即完成一次完整的按鍵過程 */
}
/* 喚醒程序 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按鍵過程 */
/* wake_up(&dev->r_wait); */
wake_up_interruptible(&dev->r_wait);
}
}
/*
* @description : 按鍵IO初始化
* @param : 無
* @return : 無
*/
static int keyio_init(void)
{
unsigned char i = 0;
char name[10];
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且設定成中斷模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 緩沖區清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 組合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
}
/* 申請中斷 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 建立定時器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/* 初始化等待隊列頭 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
/*
* @description : 打開裝置
* @param - inode : 傳遞給驅動的inode
* @param - filp : 裝置檔案,file結構體有個叫做private_data的成員變量
* 一般在open的時候将private_data指向裝置結構體。
* @return : 0 成功;其他 失敗
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 設定私有資料 */
return 0;
}
/*
* @description : 從裝置讀取資料
* @param - filp : 要打開的裝置檔案(檔案描述符)
* @param - buf : 傳回給使用者空間的資料緩沖區
* @param - cnt : 要讀取的資料長度
* @param - offt : 相對于檔案首位址的偏移
* @return : 讀取的位元組數,如果為負值,表示讀取失敗
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
#if 0
/* 加入等待隊列,等待被喚醒,也就是有按鍵按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
#endif
DECLARE_WAITQUEUE(wait, current); /* 定義一個等待隊列 */
if(atomic_read(&dev->releasekey) == 0) { /* 沒有按鍵按下 */
add_wait_queue(&dev->r_wait, &wait); /* 将等待隊列添加到等待隊列頭 */
__set_current_state(TASK_INTERRUPTIBLE);/* 設定任務狀态 */
schedule(); /* 進行一次任務切換 */
if(signal_pending(current)) { /* 判斷是否為信号引起的喚醒 */
ret = -ERESTARTSYS;
goto wait_error;
}
__set_current_state(TASK_RUNNING); /* 将目前任務設定為運作狀态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将對應的隊列項從等待隊列頭删除 */
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按鍵按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;
wait_error:
set_current_state(TASK_RUNNING); /* 設定任務為運作态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将等待隊列移除 */
return ret;
data_error:
return -EINVAL;
}
/* 裝置操作函數 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
};
/*
* @description : 驅動入口函數
* @param : 無
* @return : 無
*/
static int __init imx6uirq_init(void)
{
/* 1、建構裝置号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注冊字元裝置 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、建立類 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、建立裝置 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、始化按鍵 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驅動出口函數
* @param : 無
* @return : 無
*/
static void __exit imx6uirq_exit(void)
{
unsigned i = 0;
/* 删除定時器 */
del_timer_sync(&imx6uirq.timer); /* 删除定時器 */
/* 釋放中斷 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
第 32 行,修改裝置檔案名字為“blockio”,當驅動程式加載成功以後就會在根檔案系統中出現一個名為“/dev/blockio”的檔案。
第 61 行,在裝置結構體中添加一個等待隊列頭 r_wait,因為在 Linux 驅動中處理阻塞 IO需要用到等待隊列。
第 107~110 行,定時器中斷處理函數執行,表示有按鍵按下,先在 107 行判斷一下是否是一次有效的按鍵,如果是的話就通過 wake_up 或者 wake_up_interruptible 函數來喚醒等待隊列r_wait。
第 168 行,調用 init_waitqueue_head 函數初始化等待隊列頭 r_wait。
第 200~206 行,采用等待事件來處理 read 的阻塞通路,wait_event_interruptible 函數等待releasekey 有效,也就是有按鍵按下。如果按鍵沒有按下的話程序就會進入休眠狀态,因為采用了 wait_event_interruptible 函數,是以進入休眠态的程序可以被信号打斷。
第 208~218 行,首先使用 DECLARE_WAITQUEUE 宏定義一個等待隊列,如果沒有按鍵按下的話就使用 add_wait_queue 函數将目前任務的等待隊列添加到等待隊列頭 r_wait 中。随後調用__set_current_state 函數設定目前程序的狀态為 TASK_INTERRUPTIBLE,也就是可以被信
号打斷。接下來調用 schedule 函數進行一次任務切換,目前程序就會進入到休眠态。如果有按鍵按下,那麼進入休眠态的程序就會喚醒,然後接着從休眠點開始運作。
在這裡也就是從第 213行開始運作,首先通過 signal_pending 函數判斷一下程序是不是由信号喚醒的,如果是由信号喚醒的話就直接傳回-ERESTARTSYS 這個錯誤碼。如果不是由信号喚醒的(也就是被按鍵喚醒
的)那麼就在 217 行調用__set_current_state 函數将任務狀态設定為 TASK_RUNNING,
然後在218 行調用 remove_wait_queue 函數将程序從等待隊列中删除。
使用等待隊列實作阻塞通路重點注意兩點:
①、将任務或者程序加入到等待隊列頭,
②、在合适的點喚醒等待隊列,一般都是中斷處理函數裡面。
2.編寫測試APP
本節實驗的測試 APP 直接使用第 51.3.3 小節所編寫的 imx6uirqApp.c,将 imx6uirqApp.c 複制到本節實驗檔案夾下,并且重命名為 blockioApp.c,不需要修改任何内容。
3.運作測試
depmod //第一次加載驅動的時候需要運作此指令
modprobe blockio.ko //加載驅動
驅動加載成功以後使用如下指令打開 blockioApp 這個測試 APP,并且以背景模式運作:
按下開發闆上的 KEY0 按鍵,結果如圖所示:
輸入“top”指令,檢視 blockioAPP 這個應用 APP 的 CPU 使用率,
當我們在按鍵驅動程式裡面加入阻塞通路以後,blockioApp 這個應用程式的 CPU 使用率從之前的 99.6%降低到了 0.0%。大家注意,這裡的 0.0%并不是說 blockioApp 這個應用程式不使用 CPU 了,隻是因為使用率太小了,CPU 使用率可能為0.00001%,但是圖中隻能顯示出小數點後一位,是以就顯示成了 0.0%。
我們可以使用“kill”指令關閉背景運作的應用程式,比如我們關閉掉 blockioApp 這個背景運作的應用程式。首先輸出“Ctrl+C”關閉 top 指令界面,進入到指令行模式。然後使用“ps”指令檢視一下 blockioApp 這個應用程式的 PID,如圖 所示:
blockioApp 這個應用程式的 PID 為 81,使用“kill -9 PID”即
可“殺死”指定 PID 的程序,比如我們現在要“殺死”PID 為 81 的 blockioApp 應用程式,可是使用如下指令
三、非阻塞IO實驗
1.硬體原理圖分析
2.實驗程式編寫
1.驅動程式編寫
建立名為“15_noblockio”的檔案夾,然後在 15_noblockio 檔案夾裡面建立 vscode 工程,工作區命名為“noblockio”。将“14_blockio”實驗中的 blockio.c 複制到 15_noblockio 檔案夾中,并重命名為 noblockio.c。接下來我們就修改 noblockio.c 這個檔案,在其中添加非阻塞相關的代碼,完成以後的 noblockio.c 内容如下所示
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
檔案名 : noblock.c
作者 : 左忠凱
版本 : V1.0
描述 : 非阻塞IO通路
其他 : 無
論壇 : www.openedv.com
日志 : 初版V1.0 2019/7/26 左忠凱建立
***************************************************************/
#define IMX6UIRQ_CNT 1 /* 裝置号個數 */
#define IMX6UIRQ_NAME "noblockio" /* 名字 */
#define KEY0VALUE 0X01 /* KEY0按鍵值 */
#define INVAKEY 0XFF /* 無效的按鍵值 */
#define KEY_NUM 1 /* 按鍵數量 */
/* 中斷IO描述結構體 */
struct irq_keydesc {
int gpio; /* gpio */
int irqnum; /* 中斷号 */
unsigned char value; /* 按鍵對應的鍵值 */
char name[10]; /* 名字 */
irqreturn_t (*handler)(int, void *); /* 中斷服務函數 */
};
/* imx6uirq裝置結構體 */
struct imx6uirq_dev{
dev_t devid; /* 裝置号 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 裝置 */
int major; /* 主裝置号 */
int minor; /* 次裝置号 */
struct device_node *nd; /* 裝置節點 */
atomic_t keyvalue; /* 有效的按鍵鍵值 */
atomic_t releasekey; /* 标記是否完成一次完成的按鍵,包括按下和釋放 */
struct timer_list timer;/* 定義一個定時器*/
struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按鍵init述數組 */
unsigned char curkeynum; /* 目前init按鍵号 */
wait_queue_head_t r_wait; /* 讀等待隊列頭 */
};
struct imx6uirq_dev imx6uirq; /* irq裝置 */
/* @description : 中斷服務函數,開啟定時器
* 定時器用于按鍵消抖。
* @param - irq : 中斷号
* @param - dev_id : 裝置結構。
* @return : 中斷執行結果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;
dev->curkeynum = 0;
dev->timer.data = (volatile long)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 10ms定時 */
return IRQ_RETVAL(IRQ_HANDLED);
}
/* @description : 定時器服務函數,用于按鍵消抖,定時器到了以後
* 再次讀取按鍵值,如果按鍵還是處于按下狀态就表示按鍵有效。
* @param - arg : 裝置結構變量
* @return : 無
*/
void timer_function(unsigned long arg)
{
unsigned char value;
unsigned char num;
struct irq_keydesc *keydesc;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;
num = dev->curkeynum;
keydesc = &dev->irqkeydesc[num];
value = gpio_get_value(keydesc->gpio); /* 讀取IO值 */
if(value == 0){ /* 按下按鍵 */
atomic_set(&dev->keyvalue, keydesc->value);
}
else{ /* 按鍵松開 */
atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
atomic_set(&dev->releasekey, 1); /* 标記松開按鍵,即完成一次完整的按鍵過程 */
}
/* 喚醒程序 */
if(atomic_read(&dev->releasekey)) { /* 完成一次按鍵過程 */
/* wake_up(&dev->r_wait); */
wake_up_interruptible(&dev->r_wait);
}
}
/*
* @description : 按鍵IO初始化
* @param : 無
* @return : 無
*/
static int keyio_init(void)
{
unsigned char i = 0;
char name[10];
int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key");
if (imx6uirq.nd== NULL){
printk("key node not find!\r\n");
return -EINVAL;
}
/* 提取GPIO */
for (i = 0; i < KEY_NUM; i++) {
imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
if (imx6uirq.irqkeydesc[i].gpio < 0) {
printk("can't get key%d\r\n", i);
}
}
/* 初始化key所使用的IO,并且設定成中斷模式 */
for (i = 0; i < KEY_NUM; i++) {
memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name)); /* 緩沖區清零 */
sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i); /* 組合名字 */
gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);
imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
}
/* 申請中斷 */
imx6uirq.irqkeydesc[0].handler = key0_handler;
imx6uirq.irqkeydesc[0].value = KEY0VALUE;
for (i = 0; i < KEY_NUM; i++) {
ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler,
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
if(ret < 0){
printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
return -EFAULT;
}
}
/* 建立定時器 */
init_timer(&imx6uirq.timer);
imx6uirq.timer.function = timer_function;
/* 初始化等待隊列頭 */
init_waitqueue_head(&imx6uirq.r_wait);
return 0;
}
/*
* @description : 打開裝置
* @param - inode : 傳遞給驅動的inode
* @param - filp : 裝置檔案,file結構體有個叫做private_data的成員變量
* 一般在open的時候将private_data指向裝置結構體。
* @return : 0 成功;其他 失敗
*/
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
filp->private_data = &imx6uirq; /* 設定私有資料 */
return 0;
}
/*
* @description : 從裝置讀取資料
* @param - filp : 要打開的裝置檔案(檔案描述符)
* @param - buf : 傳回給使用者空間的資料緩沖區
* @param - cnt : 要讀取的資料長度
* @param - offt : 相對于檔案首位址的偏移
* @return : 讀取的位元組數,如果為負值,表示讀取失敗
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char keyvalue = 0;
unsigned char releasekey = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
if (filp->f_flags & O_NONBLOCK) { /* 非阻塞通路 */
if(atomic_read(&dev->releasekey) == 0) /* 沒有按鍵按下,傳回-EAGAIN */
return -EAGAIN;
} else { /* 阻塞通路 */
/* 加入等待隊列,等待被喚醒,也就是有按鍵按下 */
ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));
if (ret) {
goto wait_error;
}
}
keyvalue = atomic_read(&dev->keyvalue);
releasekey = atomic_read(&dev->releasekey);
if (releasekey) { /* 有按鍵按下 */
if (keyvalue & 0x80) {
keyvalue &= ~0x80;
ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
} else {
goto data_error;
}
atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
} else {
goto data_error;
}
return 0;
wait_error:
return ret;
data_error:
return -EINVAL;
}
/*
* @description : poll函數,用于處理非阻塞通路
* @param - filp : 要打開的裝置檔案(檔案描述符)
* @param - wait : 等待清單(poll_table)
* @return : 裝置或者資源狀态,
*/
unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
poll_wait(filp, &dev->r_wait, wait); /* 将等待隊列頭添加到poll_table中 */
if(atomic_read(&dev->releasekey)) { /* 按鍵按下 */
mask = POLLIN | POLLRDNORM; /* 傳回PLLIN */
}
return mask;
}
/* 裝置操作函數 */
static struct file_operations imx6uirq_fops = {
.owner = THIS_MODULE,
.open = imx6uirq_open,
.read = imx6uirq_read,
.poll = imx6uirq_poll,
};
/*
* @description : 驅動入口函數
* @param : 無
* @return : 無
*/
static int __init imx6uirq_init(void)
{
/* 1、建構裝置号 */
if (imx6uirq.major) {
imx6uirq.devid = MKDEV(imx6uirq.major, 0);
register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
} else {
alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
imx6uirq.major = MAJOR(imx6uirq.devid);
imx6uirq.minor = MINOR(imx6uirq.devid);
}
/* 2、注冊字元裝置 */
cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
/* 3、建立類 */
imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.class)) {
return PTR_ERR(imx6uirq.class);
}
/* 4、建立裝置 */
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
if (IS_ERR(imx6uirq.device)) {
return PTR_ERR(imx6uirq.device);
}
/* 5、始化按鍵 */
atomic_set(&imx6uirq.keyvalue, INVAKEY);
atomic_set(&imx6uirq.releasekey, 0);
keyio_init();
return 0;
}
/*
* @description : 驅動出口函數
* @param : 無
* @return : 無
*/
static void __exit imx6uirq_exit(void)
{
unsigned i = 0;
/* 删除定時器 */
del_timer_sync(&imx6uirq.timer); /* 删除定時器 */
/* 釋放中斷 */
for (i = 0; i < KEY_NUM; i++) {
free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
}
cdev_del(&imx6uirq.cdev);
unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
device_destroy(imx6uirq.class, imx6uirq.devid);
class_destroy(imx6uirq.class);
}
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
第 32 行,修改裝置檔案名字為“noblockio”,當驅動程式加載成功以後就會在根檔案系統中出現一個名為“/dev/noblockio”的檔案。
第 202~204 行,判斷是否為非阻塞式讀取通路,如果是的話就判斷按鍵是否有效,也就是判斷一下有沒有按鍵按下,如果沒有的話就傳回-EAGAIN。
第 241~252 行,imx6uirq_poll 函數就是 file_operations 驅動操作集中的 poll 函數,當應用程式調用 select 或者 poll 函數的時候 imx6uirq_poll 函數就會執行。
第 246 行調用 poll_wait 函數将等待隊列頭添加到 poll_table 中,
第 248~250 行判斷按鍵是否有效,如果按鍵有效的話就向應用程式傳回 POLLIN 這個事件,表示有資料可以讀取。
第 259 行,設定 file_operations 的 poll 成員變量為 imx6uirq_poll。
2.編寫測試APP
建立名為 noblockioApp.c 測試 APP 檔案,然後在其中輸入如下所示内容
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
檔案名 : noblockApp.c
作者 : 左忠凱
版本 : V1.0
描述 : 非阻塞通路測試APP
其他 : 無
使用方法 :./blockApp /dev/blockio 打開測試App
論壇 : www.openedv.com
日志 : 初版V1.0 2019/9/8 左忠凱建立
***************************************************************/
/*
* @description : main主程式
* @param - argc : argv數組元素個數
* @param - argv : 具體參數
* @return : 0 成功;其他 失敗
*/
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
struct pollfd fds;
fd_set readfds;
struct timeval timeout;
unsigned char data;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞通路 */
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
#if 0
/* 構造結構體 */
fds.fd = fd;
fds.events = POLLIN;
while (1) {
ret = poll(&fds, 1, 500);
if (ret) { /* 資料有效 */
ret = read(fd, &data, sizeof(data));
if(ret < 0) {
/* 讀取錯誤 */
} else {
if(data)
printf("key value = %d \r\n", data);
}
} else if (ret == 0) { /* 逾時 */
/* 使用者自定義逾時處理 */
} else if (ret < 0) { /* 錯誤 */
/* 使用者自定義錯誤處理 */
}
}
#endif
while (1) {
FD_ZERO(&readfds);
FD_SET(fd, &readfds);
/* 構造逾時時間 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 逾時 */
/* 使用者自定義逾時處理 */
break;
case -1: /* 錯誤 */
/* 使用者自定義錯誤處理 */
break;
default: /* 可以讀取資料 */
if(FD_ISSET(fd, &readfds)) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) {
/* 讀取錯誤 */
} else {
if (data)
printf("key value=%d\r\n", data);
}
}
break;
}
}
close(fd);
return ret;
}
第 52~73 行,這段代碼使用 poll 函數來實作非阻塞通路,在 while 循環中使用 poll 函數不斷的輪詢,檢查驅動程式是否有資料可以讀取,如果可以讀取的話就調用 read 函數讀取按鍵資料。
第 75~101 行,這段代碼使用 select 函數來實作非阻塞通路。
3.運作測試
depmod //第一次加載驅動的時候需要運作此指令
modprobe noblockio.ko //加載驅動
驅動加載成功以後使用如下指令打開 noblockioApp 這個測試 APP,并且以背景模式運作:
按下開發闆上的 KEY0 按鍵,結果如圖所示:
輸入“top”指令,檢視 noblockioAPP 這個應用 APP 的 CPU 使用率,
用非阻塞方式讀處理以後,noblockioApp 的 CPU 占用率也低至0.0%,