驅動程序中(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);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章