文章目錄
- 阻塞和非阻塞IO
- 1.阻塞和非阻塞簡介
- 2.等待佇列
- 2.1 等待佇列頭
- 2.2 等待佇列項
- 2.3 將佇列項添加/移除等待佇列頭
- 2.4 等待喚醒
- 2.5 等待事件
- 3.輪詢
- 3.1 select 函式
- 3.2 poll函式
- 3.3 epoll函式
- 4. Linux 驅動下的 poll 操作函式
- 阻塞 IO 實驗
- 1.程式撰寫
- 2.編譯驗證
阻塞和非阻塞IO
1.阻塞和非阻塞簡介
??這里的“IO”并不是我們學習 STM32 或者其他單片機的時候所說的“GPIO”(也就是引腳),這里的 IO 指的是 Input/Output,也就是輸入/輸出,是應用程式對驅動設備的輸入/輸出操作,當應用程式對設備驅動進行操作的時候,如果不能獲取到設備資源,那么阻塞式 IO 就會將應用程式對應的執行緒掛起,直到設備資源可以獲取為止,對于非阻塞 IO,應用程式對應的執行緒不會掛起,它要么一直輪詢等待,直到設備資源可以使用,要么就直接放棄,阻塞式 IO 如圖所示:

??應用程式呼叫 read 函式從設備中讀取資料,當設備不可用或資料未準備好的時候就會進入到休眠態,等設備可用的時候就會從休眠態喚醒,然后從設備中讀取資料回傳給應用程式,非阻塞 IO 如圖所示:

??從圖中可以看出,應用程式使用非阻塞訪問方式從設備讀取資料,當設備不可用或資料未準備好的時候會立即向內核回傳一個錯誤碼,表示資料讀取失敗,應用程式會再次重新讀取資料,這樣一直往復回圈,直到資料讀取成功,
??應用程式可以使用如下所示示例代碼來實作阻塞訪問:
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打開 */
ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
??如果應用程式要采用非阻塞的方式來訪問驅動設備檔案,可以使用如下所示代碼:
int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打開 */
ret = read(fd, &data, sizeof(data)); /* 讀取資料 */
2.等待佇列
2.1 等待佇列頭
??阻塞訪問最大的好處就是當設備檔案不可操作的時候行程可以進入休眠態,這樣可以將CPU 資源讓出來,但是,當設備檔案可以操作的時候就必須喚醒行程,一般在中斷函式里面完成喚醒作業, Linux 內核提供了等待佇列(wait queue)來實作阻塞行程的喚醒作業,如果我們要在驅動中使用等待佇列,必須創建并初始化一個等待佇列頭,等待佇列頭使用結構體wait_queue_head_t 表示, wait_queue_head_t 結構體定義在檔案 include/linux/wait.h 中,結構體內容如下所示:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
??定義好等待佇列頭以后需要初始化, 使用 init_waitqueue_head 函式初始化等待佇列頭,函式原型如下:
void init_waitqueue_head(wait_queue_head_t *q)
??引數 q 就是要初始化的等待佇列頭,
??也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 來一次性完成等待佇列頭的定義的初始化,
2.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 定義并初始化一個等待佇列項,宏的內容如下:
DECLARE_WAITQUEUE(name, tsk)
??name 就是等待佇列項的名字, tsk 表示這個等待佇列項屬于哪個任務(行程),一般設定為current , 在 Linux 內 核 中 current 相 當 于 一 個 全 局 變 量 , 表 示 當 前 進 程 , 因 此 宏DECLARE_WAITQUEUE 就是給當前正在運行的行程創建并初始化了一個等待佇列項,
2.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:要洗掉的等待佇列項,
??回傳值:無,
2.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 狀態的行程,
2.5 等待事件
??除了主動喚醒以外,也可以設定等待佇列等待某個事件,當這個事件滿足以后就自動喚醒等待佇列中的行程,
3.輪詢
??如果用戶應用程式以非阻塞的方式訪問設備,設備驅動程式就要提供非阻塞的處理方式,也就是輪詢, poll、 epoll 和 select 可以用于處理輪詢,應用程式通過 select、 epoll 或 poll 函式來查詢設備是否可以操作,如果可以操作的話就從設備讀取或者向設備寫入資料,當應用程式呼叫 select、 epoll 或 poll 函式的時候設備驅動程式中的 poll 函式就會執行,因此需要在設備驅動程式中撰寫 poll 函式,我們先來看一下應用程式中使用的 select、 poll 和 epoll 這三個函式,
3.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 函式對某個設備驅動檔案進行讀非阻塞訪問的操作示例如下所示:
void main(void)
{
int ret, fd; /* 要監視的檔案描述符 */
fd_set readfds; /* 讀操作檔案描述符集 */
struct timeval timeout; /* 超時結構體 */
fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式訪問 */
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 將 fd 添加到 readfds 里面 */
/* 構造超時時間 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
switch (ret) {
case 0: /* 超時 */
printf("timeout!\r\n");
break;
case -1: /* 錯誤 */
printf("error!\r\n");
break;
default: /* 可以讀取資料 */
if(FD_ISSET(fd, &readfds)) { /* 判斷是否為 fd 檔案描述符 */
/* 使用 read 函式讀取資料 */
}
break;
}
}
3.2 poll函式
??在單個執行緒中, select 函式能夠監視的檔案描述符數量有最大的限制,一般為 1024,可以修改內核將監視的檔案描述符數量改大,但是這樣會降低效率!這個時候就可以使用 poll 函式,poll 函式本質上和 select 沒有太大的差別,但是 poll 函式沒有最大檔案描述符限制, Linux 應用程式中 poll 函式原型如下所示:
int poll(struct pollfd *fds,
nfds_t nfds,
int timeout)
??函式引數和回傳值含義如下:
??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 函式對某個設備驅動檔案進行讀非阻塞訪問的操作示例如下所示:
void main(void)
{
int ret;
int fd; /* 要監視的檔案描述符 */
struct pollfd fds;
fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式訪問 */
/* 構造結構體 */
fds.fd = fd;
fds.events = POLLIN; /* 監視資料是否可以讀取 */
ret = poll(&fds, 1, 500); /* 輪詢檔案是否可操作,超時 500ms */
if (ret) { /* 資料有效 */
......
/* 讀取資料 */
......
} else if (ret == 0) { /* 超時 */
......
} else if (ret < 0) { /* 錯誤 */
......
}
}
3.3 epoll函式
??傳統的 selcet 和 poll 函式都會隨著所監聽的 fd 數量的增加,出現效率低下的問題,而且poll 函式每次必須遍歷所有的描述符來檢查就緒的描述符,這個程序很浪費時間,為此, epoll應運而生, epoll 就是為處理大并發而準備的,一般常常在網路編程中使用 epoll 函式,應用程式需要先使用 epoll_create 函式創建一個 epoll 句柄, epoll_create 函式原型如下:
int epoll_create(int size)
??函式引數和回傳值含義如下:
??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 函式原型如下所示:
unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
??函式引數和回傳值含義如下:
??filp: 要打開的設備檔案(檔案描述符),
??wait: 結構體 poll_table_struct 型別指標, 由應用程式傳遞進來的,一般將此引數傳遞給poll_wait 函式,
??回傳值:向應用程式回傳設備或者資源狀態,可以回傳的資源狀態如下:
POLLIN 有資料可以讀取,
POLLPRI 有緊急的資料需要讀取,
POLLOUT 可以寫資料,
POLLERR 指定的檔案描述符發生錯誤,
POLLHUP 指定的檔案描述符掛起,
POLLNVAL 無效的請求,
POLLRDNORM 等同于 POLLIN,普通資料可讀
??我們需要在驅動程式的 poll 函式中呼叫 poll_wait 函式, poll_wait 函式不會引起阻塞,只是將應用程式添加到 poll_table 中, poll_wait 函式原型如下:
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
??引數 wait_address 是要添加到 poll_table 中的等待佇列頭,引數 p 就是 poll_table,就是file_operations 中 poll 函式的 wait 引數,
阻塞 IO 實驗
??在上一篇博客 Linux 中斷實驗中,我們直接在應用程式中通過 read 函式不斷的讀取按鍵狀態,當按鍵有效的時候就列印出按鍵值,這種方法有個缺點,那就是 imx6uirqApp 這個測驗應用程式擁有很高的 CPU 占用率,大家可以在開發板中加載上一章的驅動程式模塊 imx6uirq.ko,然后以后臺運行模式打開 imx6uirqApp 這個測驗軟體,命令如下:
./imx6uirqApp /dev/imx6uirq &
??測驗驅動是否正常作業,如果驅動作業正常的話輸入“top”命令查看 imx6uirqApp 這個應用程式的 CPU 使用率,結果如圖:

??從上圖可以看出, imx6uirqApp 這個應用程式的 CPU 使用率竟然高達 99.6%,這僅僅是一個讀取按鍵值的應用程式,這么高的 CPU 使用率顯然是有問題的!原因就在于我們是直接在 while 回圈中通過 read 函式讀取按鍵值,因此 imx6uirqApp 這個軟體會一直運行,一直讀取按鍵值, CPU 使用率肯定就會很高,最好的方法就是在沒有有效的按鍵事件發生的時候,imx6uirqApp 這個應用程式應該處于休眠狀態,當有按鍵事件發生以后 imx6uirqApp 這個應用程式才運行,列印出按鍵值,這樣就會降低 CPU 使用率,接下來我們就使用阻塞 IO 來實作此功能,
1.程式撰寫
??驅動程式撰寫:
#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>
#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;
}
}
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");
??撰寫測驗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 "linux/ioctl.h"
/*
* @description : main主程式
* @param - argc : argv陣列元素個數
* @param - argv : 具體引數
* @return : 0 成功;其他 失敗
*/
int main(int argc, char *argv[])
{
int fd;
int ret = 0;
char *filename;
unsigned char data;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0) {
printf("Can't open file %s\r\n", filename);
return -1;
}
while (1) {
ret = read(fd, &data, sizeof(data));
if (ret < 0) { /* 資料讀取錯誤或者無效 */
} else { /* 資料讀取正確 */
if (data) /* 讀取到資料 */
printf("key value = %#X\r\n", data);
}
}
close(fd);
}
return ret;
}
2.編譯驗證
??驅動加載成功以后使用如下命令打開 blockioApp 這個測驗 APP,并且以后臺模式運行:
./blockioApp /dev/blockio &
??按下開發板上的 KEY0 按鍵,結果如圖所示:

??當按下 KEY0 按鍵以后 blockioApp 這個測驗 APP 就會列印出按鍵值,輸入“top”命令,查看 blockioAPP 這個應用 APP 的 CPU 使用率,如圖所示:

??從圖中可以看出,當我們在按鍵驅動程式里面加入阻塞訪問以后, blockioApp 這個應用程式的 CPU 使用率從99.6%降低到了 0.0%,大家注意,這里的 0.0%并不是說 blockioApp 這個應用程式不使用 CPU 了,只是因為使用率太小了, CPU 使用率可能為0.00001%,但是圖中只能顯示出小數點后一位,因此就顯示成了 0.0%,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301601.html
標籤:其他
