1. 程式人生 > 其它 >linux應用程式獲取驅動資料

linux應用程式獲取驅動資料

技術標籤:linuxlinux嵌入式驅動程式

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

readfdswritefdsexceptfds:這三個指標指向描述符集合,這三個引數指明瞭關心哪些描述符、需要滿足哪些條件等等,這三個引數都是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返回0events是要監視的事件,可監視的事件型別如下所示:

POLLIN 有資料可以讀取。

POLLPRI 有緊急的資料需要讀取。

POLLOUT 可以寫資料。

POLLERR 指定的檔案描述符發生錯誤。

POLLHUP 指定的檔案描述符掛起。

POLLNVAL 無效的請求。

POLLRDNORM 等同於POLLIN

nfdspoll函式要監視的檔案描述符數量。

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_operationspoll函式的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);/* 設定程序啟用非同步通知功能 */ 
......

}