linux應用程式獲取驅動資料
linux應用程式獲取驅動資料
一、應用程式獲取驅動資料的幾種方式和他們的關係
Linux應用層的程式獲取驅動層的資料有幾種方式:1、查詢 2、阻塞 3、非阻塞 4、非同步通知。查詢、阻塞、非阻塞都是應用層程式主動去獲取驅動層的資料,非同步通知是驅動主動告知應用程式。
多說一句,為什麼應用程式主動去獲取驅動程式要分查詢、阻塞、非阻塞那麼多種,這是多工系統和驅動資料非實時性的性質決定的,一個應用程式,只是系統執行的其中一個任務,如果驅動程式一直沒有資料返回,用查詢死等的方式,就會導致這個應用程式的CPU佔有率很高導致其他任務很卡。因此引入了阻塞的概念,就是資料還沒準備好時,該應用程式進入休眠,把CPU資源讓給別的任務,直到資料準備好。而非阻塞相比阻塞還多了一個定時喚醒的機制,允許休眠等待一段時間該應用程式能喚醒處理別的事情,不一定要一直掛在那裡等資料。
這幾種方式的關係簡單描述如下:
應用程式主動獲取資料 | 應用程式被動通知獲取資料 | ||
死等高CPU佔有率 | 休眠喚醒低CPU佔有率 | 4、非同步通知 | |
1、查詢 | 不帶定時喚醒 | 帶定時喚醒 | |
2、阻塞 | 3、非阻塞 |
二、獲取資料的程式示例和機制
-
查詢就是簡單的讀不做詳細說明
-
阻塞
用到了休眠喚醒和等待佇列的程式機制,下面是示例程式碼:
1、在驅動初始化函式初始化等待佇列頭 Wait_queue_head_t r_wait; //定義等待佇列頭 init_waitqueue_head(&r_wait); //初始化佇列頭 2、在驅動讀取函式定義等待佇列項並新增到佇列頭並進行任務切換 DECLARE_WAITQUEUE(wait,current); //定義等待佇列項 add_wait_queue(&r_wait, &wait); //新增等待佇列項到等待佇列頭 __set_current_state(TASK_INTERRUPTIBLE); // 設定任務狀態 schedule(); //進行任務切換 __set_current_state(TASK_RUNNING); //設定任務為執行狀態 remove_wait_queue(&r_wait, &wait); //移除等待佇列項 3、在驅動函式中斷裡面呼叫喚醒佇列函式 wake_up_interruptible(&r_wait); //喚醒程序
-
非阻塞
該方式就是輪詢,linux下可以用poll、select處理輪詢。應用程式通過select或poll函式來查詢裝置是否可以操作,如果可以操作的話就從裝置讀取或者向裝置寫入資料。當應用程式呼叫select或poll函式的時候裝置驅動程式中的poll函式就會執行,因此需要在裝置驅動程式中編寫poll函式。下面是他們的介紹:
函式 | 說明 |
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) | nfds:所要監視的這三類檔案描述集合中,最大檔案描述符加 readfds、writefds和exceptfds:這三個指標指向描述符集合,這三個引數指明瞭關心哪些描述符、需要滿足哪些條件等等,這三個引數都是fd_set型別的。當我們定義好一個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) timeout:超時時間 返回值:0,表示的話就表示超時發生,但是沒有任何檔案描述符可以進行操作;-1,發生錯誤;其他值,可以進行操作的檔案描述符個數。 |
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 nfds:poll函式要監視的檔案描述符數量。 imeout:超時時間,單位為ms。 |
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;
}
}
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) { /* 錯誤 */
......
}
}
Linux驅動下的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,普通資料可讀 |
void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) | 我們需要在驅動程式的poll函式中呼叫poll_wait函式,poll_wait函式不會引起阻塞,只是將應用程式新增到poll_table。引數wait_address是要新增到poll_table中的等待佇列頭,引數p就是poll_table,就是file_operations中poll函式的wait引數。 |
驅動示例程式碼:
3.1、在驅動讀取函式加入非阻塞讀取分支
if (filp->f_flags & O_NONBLOCK)
3.2、新增裝置操作函式
static struct file_operations xxx_fops = {
.owner = THIS_MODULE,
.open = xxx_open,
.read = xxx_read,
.poll = xxx_poll,
};
3.3、編寫驅動poll函式
unsigned int xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(filp, &r_wait, wait);
if(atomic_read(xxx)) { /* 按鍵按下 */
mask = POLLIN | POLLRDNORM; /* 返回PLLIN */
}
return mask;
}
-
非同步通知
非同步通知的核心是訊號,訊號類似於硬體的中斷的機制。
訊號型別 |
#define SIGHUP 1 /* 終端掛起或控制程序終止 */ |
#define SIGINT 2 /* 終端中斷(Ctrl+C組合鍵) */ |
#define SIGQUIT 3 /* 終端退出(Ctrl+\組合鍵) */ |
#define SIGILL 4 /* 非法指令 */ |
#define SIGTRAP 5 /* debug使用,有斷點指令產生 */ |
#define SIGABRT 6 /* 由abort(3)發出的退出指令 */ |
#define SIGIOT 6 /* IOT指令 */ |
#define SIGBUS 7 /* 匯流排錯誤 */ |
#define SIGFPE 8 /* 浮點運算錯誤 */ |
#define SIGKILL 9 /* 殺死、終止程序 */ |
#define SIGUSR1 10 /* 使用者自定義訊號1 */ |
#define SIGSEGV 11 /* 段違例(無效的記憶體段) */ |
#define SIGUSR2 12 /* 使用者自定義訊號2 */ |
#define SIGPIPE 13 /* 向非讀管道寫入資料 */ |
#define SIGALRM 14 /* 鬧鐘 */ |
#define SIGTERM 15 /* 軟體終止 */ |
#define SIGSTKFLT 16 /* 棧異常 */ |
#define SIGCHLD 17 /* 子程序結束 */ |
#define SIGCONT 18 /* 程序繼續 */ |
#define SIGSTOP 19 /* 停止程序的執行,只是暫停 */ |
#define SIGTSTP 20 /* 停止程序的執行(Ctrl+Z組合鍵) */ |
#define SIGTTIN 21 /* 後臺程序需要從終端讀取資料 */ |
#define SIGTTOU 22 /* 後臺程序需要向終端寫資料 */ |
#define SIGURG 23 /* 有"緊急"資料 */ |
#define SIGXCPU 24 /* 超過CPU資源限制 */ |
#define SIGXFSZ 25 /* 檔案大小超額 */ |
#define SIGVTALRM 26 /* 虛擬時鐘訊號 */ |
#define SIGPROF 27 /* 時鐘訊號描述 */ |
#define SIGWINCH 28 /* 視窗大小改變 */ |
#define SIGIO 29 /* 可以進行輸入/輸出操作 */ |
#define SIGPOLL SIGIO |
/* #define SIGLOS 29 */ |
#define SIGPWR 30 /* 斷點重啟 */ |
#define SIGSYS 31 /* 非法的系統呼叫 */ |
#define SIGUNUSED 31 /* 未使用訊號 */ |
我們使用中斷的時候需要設定中斷處理函式,同樣的,如果要在應用程式中使用訊號,那麼就必須設定訊號所使用的訊號處理函式,在應用程式中使用signal函式來設定指定訊號的處理函式。
函式 | 說明 |
sighandler_t signal(int signum, sighandler_t handler) | signum:要設定處理函式的訊號。 handler:訊號的處理函式。 返回值:設定成功的話返回訊號的前一個處理函式,設定失敗的話返回SIG_ERR。 |
typedef void (*sighandler_t)(int) | 訊號處理函式原型 |
示例程式碼:
#include "stdlib.h"
#include "stdio.h"
#include "signal.h"
void sigint_handler(int num)
{
printf("xxx");
exit(0);
}
int main(void)
{
signal(SIGINT, sigint_handler);
while(1);
return 0;
}
驅動中的訊號處理
1、定義一個fasync_struct結構體
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd;
struct fasync_struct *fa_next;
struct file *fa_file;
struct rcu_head fa_rcu;
};
2、在file_operations操作集中實現fasync函式
int (*fasync) (int fd, struct file *filp, int on)
fasync函式裡面一般通過呼叫fasync_helper函式來初始化前面定義的fasync_struct結構體指標,fasync_helper函式原型如下:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
fasync_helper函式的前三個引數就是fasync函式的那三個引數,第四個引數就是要初始化的fasync_struct結構體指標變數。
驅動中的fasync函式示例:
struct xxx_dev {
......
struct fasync_struct *async_queue; /* 非同步相關結構體 */
};
static int xxx_fasync(int fd, struct file *filp, int on)
{
struct xxx_dev *dev = (xxx_dev)filp->private_data;
if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
return -EIO;
return 0;
}
//在關閉驅動檔案的時候需要在file_operations操作集中的release函式中釋放fasync_struct,
//fasync_struct的釋放函式同樣為fasync_helper,release函式引數參考例項如下:
static int xxx_release(struct inode *inode, struct file *filp)
{
return xxx_fasync(-1, filp, 0); /* 刪除非同步通知 */
}
static struct file_operations xxx_ops = {
......
.fasync = xxx_fasync,
.release = xxx_release,
......
};
3、通過kill_fasync傳送訊號
函式 | 說明 |
void kill_fasync(struct fasync_struct **fp, int sig, int band) | fp:要操作的fasync_struct。 sig:要傳送的訊號。 band:可讀時設定為POLL_IN,可寫時設定為POLL_OUT。 返回值:無。 |
應用程式的訊號處理
1、註冊訊號處理函式
2、將本應用程式的程序號告訴給核心
使用fcntl(fd, F_SETOWN, getpid())將本應用程式的程序號告訴給核心。
3、開啟非同步通知
flags = fcntl(fd, F_GETFL); /* 獲取當前的程序狀態*/
fcntl(fd, F_SETFL, flags | FASYNC); /* 開啟當前程序非同步通知功能*/
重點就是通過fcntl函式設定程序狀態為FASYNC,經過這一步,驅動程式中的fasync函式就會執行。
示例程式碼:
static void xxx_signal_func(int signum)
{
......
}
int main(int argc, char *argv[])
{
......
fd = open(filename, O_RDWR);
/* 設定訊號SIGIO的處理函式 */
signal(SIGIO, sigio_signal_func);
fcntl(fd, F_SETOWN, getpid()); /* 將當前程序的程序號告訴給核心 */
flags = fcntl(fd, F_GETFD); /* 獲取當前的程序狀態 */
fcntl(fd, F_SETFL, flags | FASYNC);/* 設定程序啟用非同步通知功能 */
......
}