主頁 >  其他 > 嵌入式Linux開發27——Linux阻塞和非阻塞IO

嵌入式Linux開發27——Linux阻塞和非阻塞IO

2021-09-20 14:09:28 其他

文章目錄

    • 阻塞和非阻塞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

標籤:其他

上一篇:關于EMQ(emqttd)你應該了解的

下一篇:STM32 Cubemax(十三) ——SPI時序讀寫RFID-RC522

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more