天天看點

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:所要監視的這三類檔案描述集合中,最大檔案描述符加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);/* 設定程序啟用異步通知功能 */ 
......

}
           

繼續閱讀