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:所要監視的這三類檔案描述集合中,最大檔案描述符加1。 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 |
#define SIGQUIT 3 |
#define SIGILL 4 |
#define SIGTRAP 5 |
#define SIGABRT 6 |
#define SIGIOT 6 |
#define SIGBUS 7 |
#define SIGFPE 8 |
#define SIGKILL 9 |
#define SIGUSR1 10 |
#define SIGSEGV 11 |
#define SIGUSR2 12 |
#define SIGPIPE 13 |
#define SIGALRM 14 |
#define SIGTERM 15 |
#define SIGSTKFLT 16 |
#define SIGCHLD 17 |
#define SIGCONT 18 |
#define SIGSTOP 19 |
#define SIGTSTP 20 |
#define SIGTTIN 21 |
#define SIGTTOU 22 |
#define SIGURG 23 |
#define SIGXCPU 24 |
#define SIGXFSZ 25 |
#define SIGVTALRM 26 |
#define SIGPROF 27 |
#define SIGWINCH 28 |
#define SIGIO 29 |
#define SIGPOLL SIGIO |
#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);/* 設定程序啟用異步通知功能 */
......
}