驅動程序中(4種I/O模型)
1 阻塞: 在應用層調用read函數的時候,如果硬件中的數據沒有準備好,此時進程會進入休眠狀態,當硬件的數據準備好的時候會給驅動發送中斷。驅動收到中斷之後,喚醒休眠的進程。這個被喚醒的進程在driver_read讀取硬件的數據,並把數據 返回到用戶空間。(模型中斷)
可以使用隊列,把整個進程進入到隊列中,使進程進入阻塞的狀態,當有數據產生的時候,產生中斷,喚醒在隊列中的進程,從而讀取數據.
隊列:
wait_queue_head_t wq //定義等待隊列頭
init_waitqueue_head(&wq)//初始化等待隊列頭
wait_event(wq, condition) //不可中斷的等待態
wait_event_interruptible(wq, condition)//可中斷的等待態(當產生中斷信號就可以喚醒)
參數:
@wq :等待對列頭
@condition :如果條件爲假表示可休眠,如果爲真表示不休眠
返回值:成功返回0,失敗返回錯誤碼
wake_up(&wq)
wake_up_interruptible(&wq)
喚醒休眠
condition = 1;
分析:wake_up_interruptible()喚醒後,wait_event_interruptible(wq, condition)宏,自身再檢查“condition”這個條件以決定是返回還是繼續休眠,真(1)則返回,假(0)則繼續睡眠,不過這個程序中若有中斷程序的話,中斷來了,還是會繼續執行中斷函數的。只有當執行wake_up_interruptible()並且condition條件成立時纔會把程序從隊列中喚醒。
==========================================================
user
fd = open("hello",O_RDWR); //阻塞的方式打開文件
read(fd,buf,sizeof(buf));
-----------------------------------------------------
kernel:
fops:driver_read()
{
//2.阻塞方式打開的
那進入休眠狀態(等待喚醒)
3.讀取數據拷貝數據
}
========================================================
2 非阻塞:在應用層調用read函數的時候,不管硬件的數據是否準備好都需要立返回到用戶空間
open()函數默認的是阻塞模式,我們需要把它設置成非阻塞模式
int open(const char *pathname, int flags);
參數1:文件的路徑
參數2:打開的的方式如下圖
======================================================
user
fd = open("hello",O_RDWR|O_NONBLOCK); //非阻塞的方式打開文件
read(fd,buf,sizeof(buf));
-----------------------------------------------------------------
kernel:
fops:driver_read()
{
1.檢查用戶打開驅動的設備文件的方式
if(file->f_flags & O_NONBLOCK&!esswait)
return -EINVAL;
//非阻塞方式打開
2.讀取數據,向用戶空間拷貝數據
}
===========================================================
3 I/O多路複用(select/poll/epoll)
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
參數1 :一個結構體指針 struct pollfd *fds
struct pollfd {
int fd; //文件描述符
short events; //產生的事件如讀寫錯誤
POLLIN 讀寫
POLLOUT 寫
POLLERR 出錯
short revents; //結果描述,表示當前的fd是否可讀,寫初錯
//用於判斷出錯
POLLIN 讀寫
POLLOUT 寫
POLLERR 出錯
參數2 nfds:要監視的描述符的數目
參數3 timeout 超時時間 是指定poll在返回前沒有接收事件時應該等待的時間。
timeout值:
INFTIM 永遠等待(爲負數)
0 立即返回,不阻塞進程
>0 等待指定數目的毫秒數
返回值:
負數:表示出錯
大於0 表示fd有數據
小於0 表示時間到
如果在應用中使用了poll對設備文件進行了監控,那麼在驅動中必須必須實現poll
unsigned int xxx_poll(struct file *filp,struct poll_table_struct *pts)
{
//返回一個mask數值
unsigned int mask;
//調用poll_wait,將當前的等待隊列註冊系統中
poll_wait();
//當沒有數據的時候返回一個0(通過判斷隊列的狀態)
//當有數據的時候返回一個POLLIN
}
================================================================
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
功 能:select用於監測是哪個或哪些文件描述符產生件;
參數:nfds: 監測的最大文件描述個數
readfds: 讀事件集合; //讀
writefds: 寫事件集合; //NULL表示不關心
exceptfds: 異常事件集合(帶外集合); //一般爲NULL
timeout: 超時設置. NULL:一直阻塞,直到有文件描述符就緒或出錯
時間值爲0:僅僅檢測文件描述符集的狀態,然後立即返回
時間值不爲0:在指定時間內,如果沒有事件發生,則超時返回
struct timeval {
long tv_sec; /* seconds */秒
long tv_usec; /* microseconds */微妙
};
select返回值: <0 出錯
>0 表示有事件產生;
==0 表示超時時間已到
注意:當select()函數退出後,集合表示有數據的集合
當select()函數退出前,集合表示描述符的集合
void FD_CLR(int fd, fd_set *set);//把fd從集合中清除
int FD_ISSET(int fd, fd_set *set);//判斷fd是否在在集合中
void FD_SET(int fd, fd_set *set);//把描述符插入到集合中
void FD_ZERO(fd_set *set);//對集合清零
if( FD_ISSET(int fd, fd_set *set))//判斷fd是否在集合中
{
}
//在我們調用select時進程會一直阻塞直到以下的一種情況發生.
有文件可以讀.
有文件可以寫.
超時所設置的時間到
===============================================================
epoll的使用(現在常用的是epoll)
man epoll
#include <sys/epoll.h>
功能:創建epoll的實例
int epoll_create(int size);
參數:
@size :無效了
返回值:成功返回epoll實例的文件描述符,失敗-1;
//功能:向epoll實例中添加文件描述符,或者從中刪除文件描述符
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
參數:
@ epfd :epoll實例的文件描述符
@ op :EPOLL_CTL_ADD 添加文件描述符
EPOLL_CTL_MOD 更正對文件描述符監聽的事件
EPOLL_CTL_DEL 刪除文件描述符
@ fd :想要添加的文件描述符
@event :epoll_event
typedef union epoll_data {
int fd;
} epoll_data_t;
struct epoll_event {
uint32_t events; //監聽的事件的類型EPOLLIN讀/EPOLLOUT寫
epoll_data_t data; //fd
};
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏
返回值:成功返回0,失敗返回-1
//功能:阻塞等待文件描述符對應驅動的數據是否準備好 //死等
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
參數:
@epfd :e poll實例的文件描述符
@events :被返回的epoll_event
@maxevents :監聽文件描述符的最大值
@timeout :超時(-1表示或略超時時間)
返回值:>0 :返回文件描述符的個數
=0 :超時
-1 :失敗了
注意:支持管道,FIFO,套接字,POSIX消息隊列,終端,設備等,但是就是不支持普通文件或目錄的
==============================================================
4 異步通知
在應用層使用signal爲一個信號綁定一個處理函數,應用層執行signal之後接着往下執行。當硬件中的數據準備好的時候硬件會給驅動發送中斷,驅動收到中斷後給應用程序發送信號,應用程序收到信號後執行信號處理函數,並在信號處理函數中調用read函數讀取數據。
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
參數1 signum信號量
參數2 handler處理函數‘
功能描述:根據文件描述詞來操作文件的特性。
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
描述: fcntl()針對(文件)描述符提供控制.參數fd是被參數cmd操作(如下面的描述)的描述符.
針對cmd的值,fcntl能夠接受第三個參數(arg)
參數1 fd 文件描述符
參數2 cmd
複製一個現有的描述符(cmd=F_DUPFD).
獲得/設置文件描述符標記(cmd=F_GETFD或F_SETFD).
獲得/設置文件狀態標記(cmd=F_GETFL或F_SETFL).
獲得/設置異步I/O所有權(cmd=F_GETOWN或F_SETOWN).
獲得/設置記錄鎖(cmd=F_GETLK,F_SETLK或F_SETLKW)
注意:在修改文件描述符標誌或文件狀態標誌時必須謹慎,先要取得現在的標誌值 然後按照希望修改它, 最後設置新標誌值。不能只是執行F_SETFD或F_SETFL命令,這樣會關閉以前設置的標誌位。
[返回值]
fcntl()的返回值與命令有關:如果出錯,所有命令都返回-1,如果成功則返回某個其他值。下列三個命令有特定返回值:F_DUPFD , F_GETFD , F_GETFL以及F_GETOWN。
F_DUPFD 返回新的文件描述符
F_GETFD 返回相應標誌
F_GETFL , F_GETOWN 返回一個正的進程ID或負的進程組ID
cmd值的F_GETFL和F_SETFL :F_GETFL取得fd的文件狀態標誌,如同下面的描述一樣(arg被忽略),在說明open函數時,已說明了文件狀態標誌。不幸的是,三個存取方式標誌 (O_RDONLY , O_WRONLY , 以及O_RDWR)並不各佔1位。(這三種標誌的值各是0 , 1和2,由於歷史原因,這三種值互斥 — 一個文件只能有這三種值之一。) 因此首先必須用屏蔽字O_ACCMODE相與取得存取方式位,然後將結果與這三種值相比較。
F_SETFL 設置給arg描述符狀態標誌,可以更改的幾個標誌是:O_APPEND,O_NONBLOCK,O_SYNC 和 O_ASYNC。而fcntl的文件狀態標誌總共有7個:O_RDONLY , O_WRONLY , O_RDWR , O_APPEND , O_NONBLOCK , O_SYNC和O_ASYNC
可更改的幾個標誌如下面的描述:
O_NONBLOCK 非阻塞I/O,如果read(2)調用沒有可讀取的數據,或者如果write(2)操作將阻塞,則read或write調用將返回-1和EAGAIN錯誤
O_APPEND 強制每次寫(write)操作都添加在文件大的末尾,相當於open(2)的O_APPEND標誌
O_DIRECT 最小化或去掉reading和writing的緩存影響。系統將企圖避免緩存你的讀或寫的數據。如果不能夠避免緩存,那麼它將最小化已經被緩存了的數據造成的影響。如果這個標誌用的不夠好,將大大的降低性能
O_ASYNC 當I/O可用的時候,允許SIGIO信號發送到進程組,例如:當有數據可以讀的時候
cmd值的F_GETOWN和F_SETOWN:
F_GETOWN 取得當前正在接收SIGIO或者SIGURG信號的進程id或進程組id,進程組id返回的是負值(arg被忽略)
F_SETOWN 設置將接收SIGIO和SIGURG信號的進程id或進程組id,進程組id通過提供負值的arg來說明(arg絕對值的一個進程組ID),否則arg將被認爲是進程id
=-====================================================================
應用層:
1. signal(SIGIO,信號的處理函數);
2. fcntl(fd,F_SETFL,fcntl(fd, F_GETFL)|FASYNC);//重新設置文件狀態的標誌
3. fcntl(fd,F_SETOWN,getpid());//設置將接收SIGIO和SIGURG信號的進程id或進程組id
---------------------------------------------------------------------------------
驅動----發送信號
1 需要和進程進行關聯--記錄信號發送給誰
實現一個fasync()的接口
int key_drv_fasync (int fd, struct file *file, int on);
{
//記錄信號發送給誰
return fasync_helper(fd,file,on,&key->fasync);
}
然後在特定的環境中發送信號(函數),當有數據的時候:
kill_fasync(struct fasync_struct * * fp, int sig, int band);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.