1. 程式人生 > >Ioctl命令組成、阻塞IO實現、IO多路複用 >>Linux裝置驅動程式

Ioctl命令組成、阻塞IO實現、IO多路複用 >>Linux裝置驅動程式

我又來了,一天學習一點點,一天成長一點點就會很開心的說!
今天的狀態很好,況且還有好喝的咖啡陪伴著我,元氣滿滿喲!^. ^?

文章目錄

[0x100]內容概述

  1. ioctl 命令編號組成
  2. 等待佇列 與 程序核心空間休眠
  3. IO多路複用呼叫與位掩碼作用
  4. 非同步I/O通知核心實現函式

[0x200] 編寫ioctl的命令編號

[0x210]命令編號組成

  1. NUMR : 命令順序編號;
  2. TYPE : 命令標識型別:通常需要查詢 ioctl-numbers.txt 來確定是否衝突;
  3. DIRT : 命令執行方向:拷貝到使用者空間 或者 從使用者空間拷貝,或者 雙向;
  4. SIZE : 使用者資料大小:通常用於校驗資料是否超容,核心不關心;
#include <asm-generic/ioctl.h>
/*參考 核心版本 3.4.39*/ #define _IOC_NRBITS 8 #define _IOC_TYPEBITS 8 #define _IOC_SIZEBITS 14 #ifndef _IOC_DIRBITS #define _IOC_DIRBITS 2 /*整體來說:總共 32位命令號組成*/ #define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) /*序列編號 = 8位 1 0000 0000-1=FFFF FFFF*/ #define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) /*型別編號 = 8位*/ #define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) /*資料容量 = 14位*/ #define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) /*讀寫標識 = 2位*/ #define _IOC_NRSHIFT 0 /*NR   Bit_0~Bit_7*/ #define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) /*TYPE Bit_8~Bit_15*/ #define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) /*SIZE Bit_16~Bit_29*/ #define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) /*DIR Bit_30~Bit_31*/

[0x220]建立命令編號

/*封裝 讀寫指令*/
# define _IOC_NONE      0U
# define _IOC_WRITE     1U
# define _IOC_READ      2U

/*IOCMD 位移當前數值對應位置 dir 起始29 type 起始16 nr 起始8 size 起始0*/
#define _IOC(dir,type,nr,size) \
        (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT))
/*核心空間的使用者資料檢測 是否大於0 且小於SIZESHIFT 正確返回資料大小,錯誤呼叫 */
extern unsigned int __invalid_size_argument_for_IOC;
#define _IOC_TYPECHECK(t) \
        ((sizeof(t) == sizeof(t[1]) && \
          sizeof(t) < (1 << _IOC_SIZEBITS)) ? \
          sizeof(t) : __invalid_size_argument_for_IOC)
/*建立命令種類起始序號*/
#define _IO(type,nr)            _IOC(_IOC_NONE,(type),(nr),0)
/*建立不同種類的命令編號 */
#define _IOR(type,nr,size)      _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)      _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))

[0x230]這裡有個例子

#define PYM_START 't'
#define PYM_IOCMD _IO(PYM_START,0)
#define PYM_RDCMD _IOR(PYM_START,1,int)
#define PYM_WRCMD _IOW(PYM_START,2,int)
#define PYM_IRDWR _IORW(PYM_START,3,int)

[0x300] 休眠與喚醒

資料未就緒、緩衝區已滿、裝置未準備就緒等情況;
阻塞IO主要用於 針對以上條件無法執行裝置讀寫操作的情況,程序調用於核心空間休眠;

[0x310] 產生休眠的必要條件

  • 概念 :由於無法達成下一步執行條件,當前程式碼自主放棄CPU 執行用於其他程序執行,等待固定事件再次呼叫;
  • 規則1:休眠不能用於原子上下文,以及遮蔽中斷的程序中;
  • 規則2:必須確定喚醒條件真實存在,才能執行休眠;
  • 規則3:必須存在wait_queue 用來記錄等待喚醒的程序狀態資訊;

[0x311] 等待佇列相關資料結構

#include <linux/poll.h> 
#include <linux/wait.h>
/*關聯等待佇列處理函式*/
typedef struct poll_table_struct {
        poll_queue_proc _qproc;
        unsigned long _key;
} poll_table;

/*將等待佇列入口與等待佇列處理函式,等待程序關聯起來*/
struct poll_wqueues {
        poll_table 
           {  poll_queue_proc _qproc;           /*等待佇列處理函式*/
              unsigned long _key;
           }pt;
        struct poll_table_page                  /*等待佇列入口連結串列結構*/
              {  struct poll_table_page * next;
                 struct poll_table_entry        /*等待佇列入口的結構關聯等待佇列頭,和等待佇列項*/
                       { struct file *filp;             /*檔案描述符指標*/
                         unsigned long key;
                         wait_queue_t                   /*等待佇列項*/
                            {  unsigned int flags;
                               #define WQ_FLAG_EXCLUSIVE       0x01 /*獨立等待佇列項位*/
                                void *private;
                                wait_queue_func_t func;  /*等待佇列項處理函式*/
                                struct list_head task_list;
                            }wait;
                         wait_queue_head_t              /*等待佇列頭*/
                            { spinlock_t lock;
                              struct list_head task_list;
                            }*wait_address;
                   } * entry;
                 struct poll_table_entry entries[0];
               }; *table;
        struct task_struct *polling_task;   /*程序資訊結構*/
        int triggered;
        int error;
        int inline_index;
        struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES]; /*輪詢入口項*/
};
typedef struct __wait_queue wait_queue_t;
typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

[0x320] 自動處理休眠與喚醒介面

[0x321]休眠巨集介面

  1. 置位條件等待 :condition == 0 則為休眠;
  2. 計時時限喚醒 :超時前等待條件喚醒,超時後忽略條件繼續執行;
  3. 裝置中斷喚醒 :非終止類中斷針對喚醒,否則一直阻塞等待
#include <linux/wait.h>
/*設定 休眠前必須設定 等待佇列頭*/
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

/*如果condistion 為 0 休眠 過載 __wait_event(),如果是1 結束休眠*/
#define wait_event(wq, condition)                                       \
do {                                                                    \
        if (condition)                                                  \
                break;                                                  \
        __wait_event(wq, condition);                                    \
} while (0)
/* condistion=0 休眠 否則由schedule_timeout(ret)返回放棄cpu時間 
*  ret = 小於0 或者等於 0 結束等待
*/
#define __wait_event_timeout(wq, condition, ret)                        \
do {                                                                    \
        DEFINE_WAIT(__wait);                                            \
                                                                        \
        for (;;) {                                                      \
                prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \
                if (condition)                                          \
                        break;                                          \
                ret = schedule_timeout(ret);                            \
                if (!ret)                                               \
                        break;                                          \
        }                                                               \
        finish_wait(&wq, &__wait);                                      \
} while (0)
/*通過 singal_pending 判斷有訊號處理非零,如果是中斷過載當前系統呼叫,是零則重新迴圈,直到資料已就緒*/
#define __wait_event_interruptible(wq, condition, ret)                  \
do {                                                                    \
        DEFINE_WAIT(__wait);                                            \
                                                                        \
        for (;;) {                                                      \
                prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);      \
                if (condition)                                          \
                        break;                                          \
                if (!signal_pending(current)) {                         \
                        schedule();                                     \
                        continue;                                       \
                }                                                       \
                ret = -ERESTARTSYS;                                     \
                break;                                                  \
        }                                                               \
        finish_wait(&wq, &__wait);                                      \
} while (0)

[0x322]喚醒巨集介面

#include <linux/wait.h >
#define wake_up(x)                      __wake_up(x, TASK_NORMAL, 1, NULL)
/*喚醒中斷等待除了獨佔等待的所有程序*/
#define wake_up_interruptible(x)        __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
/*喚醒中斷等待nr個獨佔等待*/
#define wake_up_interruptible_nr(x, nr) __wake_up(x, TASK_INTERRUPTIBLE, nr, NULL)
/*喚醒中斷等待的所有程序*/
#define wake_up_interruptible_all(x)    __wake_up(x, TASK_INTERRUPTIBLE, 0, NULL)
/*喚醒等待nr個獨佔等待*/
#define wake_up_nr(x, nr)               __wake_up(x, TASK_NORMAL, nr, NULL)
/*喚醒等待的所有程序*/
#define wake_up_all(x)                  __wake_up(x, TASK_NORMAL, 0, NULL)

/*implement at kernel-3.4.39/kernel/sched/core.c*/
void __wake_up(wait_queue_head_t *q, unsigned int mode,int nr_exclusive, void *key)
{
        unsigned long flags;

        spin_lock_irqsave(&q->lock, flags);
        __wake_up_common(q, mode, nr_exclusive, 0, key);
        spin_unlock_irqrestore(&q->lock, flags);
}

[0x330]兩種建立休眠程序方式

[0x331]手動建立程序休眠過程

  1. 定義wait_queue_head_t,
  2. 定義wait_queue_t,
  3. 初始化等待佇列項 [init_wait(&my_wait_queue)];
  4. 新增等待佇列項並調整程序狀態TASK_INTERRUPTIBLE[prepare_to_wait()]
  5. 放棄 CPU 處理處理程式碼==[schedule()]==;
  6. 裝置返回為 TASK_RUNNING 清理等待佇列項 ,finish_wait()
  7. 檢查是否為訊號中斷 singal_pending(current);
  8. 執行讀寫處理
#include <linux/wait.h>
/*  implement kernel_dir/kernel/wait.c*/
/*沒有WQ_FLAG_EXCLUSIVE 正常處理 */
prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state)
{
        unsigned long flags;
          
        wait->flags &= ~WQ_FLAG_EXCLUSIVE;
        /*自旋鎖 儲存當前irq 狀態 禁止irq 中斷*/
        spin_lock_irqsave(&q->lock, flags);
        /*檢查當前等待佇列連結串列是否為空*/
        if (list_empty(&wait->task_list))
        /*頭插*/
                __add_wait_queue(q, wait);
        /*設定程序狀態為傳入值*/ 
        set_current_state(state);
        /*自旋解鎖 恢復儲存irq 狀態 恢復irq 中斷*/
        spin_unlock_irqrestore(&q->lock, flags);
}
/*優先處理擁有WQ_FLAG_EXCLUSIVE的程序 獨佔等待*/
void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
{       
        unsigned long flags;
        
        wait->flags |= WQ_FLAG_EXCLUSIVE;
        /*自旋鎖 儲存當前irq 狀態 禁止irq 中斷*/
        spin_lock_irqsave(&q->lock, flags);
        /*檢查當前等待佇列連結串列是否為空*/
        if (list_empty(&wait->task_list))
        /*尾插*/
                __add_wait_queue_tail(q, wait);
        /*設定程序狀態為傳入值*/        
        set_current_state(state);
        /*自旋解鎖 恢復儲存irq 狀態 恢復irq 中斷*/
        spin_unlock_irqrestore(&q->lock, flags);
}
/*結束執行 清理工作*/
void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
{
        unsigned long flags;

        __set_current_state(TASK_RUNNING);
        
        if (!list_empty_careful(&wait->task_list)) {
                spin_lock_irqsave(&q->lock, flags);
                list_del_init(&wait->task_list);
                spin_unlock_irqrestore(&q->lock, flags);
        }
}

[0x332]自動建立休眠程序例項

  1. 配置等待佇列頭 DECLARE_WAIT_QUEUE_HEAD();
  2. 設定condition 為 0 休眠;
  3. 於copy_to_user() 之前 呼叫 wait_event_interruptible();
  4. 發現數據後執行 設定 condition 為 1,執行 wake_up_interruptible();
  5. 執行 copy_to_user()後,將condition 再次設定為0,週期以此類推;
struct scull_pipe
{
   wait_queue_head_t inq,outq;    //等待佇列頭 輸入 輸出
   char *buffer,*end;             // 使用者空間緩衝區開始 與結束位置;
   char *rp,*wp;                  // 當前讀位置 、寫位置
   struct semaphore sem;          // 訊號量結構
   struct cdev cdev;              // 字元裝置結構體
};

static ssize_t scull_read(struct file *filep,char __user *buf,size_t count,loff_t f_ops)
{  
  /*使用檔案結構私有資料為結構體分配空間*/
   struct scull_pipe * pym_dev = filep->private_data;
   
    /*單入讀函式*/
     if(down_interruptible(&pym_dev->sem))
        return -ERESTARTSYS
        
      /*寫指標等於讀指標表示沒有輸出,進入迴圈*/
      while(pym_dev->wp == pym_dev->rp)
           {
            /*為了防止後續的問題返回,導致訊號量狀態消失,先解鎖*/
             up(&pym_dev->sem);
              /*檢查struct file 結構體中的檔案開啟標誌位 是否需要阻塞IO,如果不是退出重試,也可以返回 -EINVAL*/
               if(filep->f_flags & O_NONBLOCK)   
                   return -EAGAIN;
                 /*阻塞IO函式等待condition位= 1 或者中斷訊號*/  
                   if(wait_event_interruptible(pym_dev->inq,(pym_dev->wp != pym_dev->rp)))
                      return -ERESTARTSYS;
                      /*有資料可讀,訊號量上鎖讀取資料*/ 
                            if(down_interruptible(&pym_dev->sem))  
                               return -ERESTARTSYS;  
          }
          /*寫指標移動有輸出,可以開始使用者可以開始讀*/
           if(pym_dev->wp > pym_dev->rp)
              /*如果wp-rp 位置 獲取需要寫入的資料位元組值,與核心空間的緩衝區比較,以小的數值為應用讀取位元組值*/
               count = min(count,(pym_dev->wp - pym_dev->rp));
           else
              /*若wp已經釋放,則從當前緩衝區末尾獲取位元組值,與核心空間的緩衝區比較,以小的數值為應用讀取位元組值*/
               count = min(count,(pym_dev->end - pym_dev->rp));
              /*拷貝字元到使用者空間*/
               if(copy_to_user(buf, pym_dev->rp,count))
                 {
                    up(&pym_dev->sem);
                  return -EFAULT;
                 }
                   /*移動讀指標到讀取位置後*/
                   pym_dev->rp += count; 
                    /*判斷是否為緩衝區末尾*/    
                    if(pym_dev->rp == pym_dev->end)
                     {
                       /*如果是 則返回指標到緩衝區頭部*/
                        pym_dev->rp = pym_dev->buffer;
                     }
                      up(&pym_dev->sem);
                         /*讀緩衝區存在空閒,喚醒其它寫函式執行*/
                        wake_up_interruptible(pym_dev->outq);     
return count;
}

[0x400]其他 IO處理方式

[0x410] IO多路複用

  • 概念定義:允許程序阻塞等待或者非阻塞多個IO資料流,直到其中任何一個檔案IO資料流準備就緒,執行相關操作;
  • 函式原型:[unsigned int (* poll) (struct file *filp,poll_table *wait)] ;
  • 應用函式: select、poll、epoll;

[0x411] IO 可用位掩碼

  • 裝置可讀 或者 裝置可寫 : POLLIN | POLLRDNORM 或者 POLLOUT | POLLWRNORM;
  • 程序到達裝置檔案末尾 : POLLHUP
  • 標識裝置無阻塞可讀寫 : POLLERR
  • out_of_band 裝置讀寫 : POLLRDBAND 或者 POLLWRBAND

[0x412]POLL讀時返回規則

  1. 阻塞讀:輸入緩衝區有空間 資料就緒= POLLIN,無資料可用= 休眠等待;
  2. 非阻塞:輸入緩衝區有空間 資料就緒= POLLIN,無資料可用= -EAGIN 或者 POLL返回裝置不可讀;
  3. 資料達到末尾= POLLHUP

[0x413]POLL寫時返回規則

  1. 輸出緩衝區有空間:資料就緒= POLLOUT;
  2. 阻塞寫 :輸出緩衝區無空間資料就緒= 寫函式將被阻塞;
  3. 非阻塞 :輸出緩衝區無空間資料就緒= 返回 -EAGIN 或者 -ENOSPC;

[0x414]輪詢入口表中新增等待佇列項

#include <linux/poll.h>
void poll_wait(struct file * filep,wait_queue_head_t *qh,poll_table *wq);

將多個檔案描述符放入等待佇列中,返回哪個描述符可以立即操作的位掩碼;
args 1 : 檔案描述符
args 2 : 等待佇列頭指標
args 3 : 等待佇列的結構指標

[0x420] 非同步通知

[0x421] 產生非同步通知的必要條件

  • 概念 :訊號中斷提示處理的非同步I/O 方式替代阻塞和輪詢處理I/O的方式;
  • 規則1:對應檔案結構體filep->f_flags 被設定為FASYNC 標誌;
  • 規則2:定義struct fasync_struct ;
  • 規則2:準備工作呼叫 fasync_helper(),形成一個 struct fasync_struct 結構連結串列,類似等待佇列的結構;
  • 規則3:條件觸發呼叫 kill_fasync(),通知程序執行I/O;

[0x422] 相關函式處理與資料結構

#include <linux/fs.h>
/*implement kernel_dir/fs/fcntl.c */
struct fasync_struct {
        spinlock_t               fa_lock;
        int                      magic;
        int                      fa_fd;
        struct fasync_struct    *fa_next; /* 結構的單向連結串列 */
        struct file             *fa_file; /* 確定是否開啟了 非同步通知 f_flags */
        struct rcu_head         fa_rcu;
};  
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
{
        if (!on)
                return fasync_remove_entry(filp, fapp);
        return fasync_add_entry(fd, filp, fapp);
}

static int fasync_add_entry(int fd, struct file *filp, struct fasync_struct **fapp)
{
        struct fasync_struct *new;
        /*在核心空間分配地址*/
        new = fasync_alloc();   
        if (!new)               
                return -ENOMEM; 
        /*執行連結串列插入*/
        if (fasync_insert_entry(fd, filp, fapp, new)) {
                fasync_free(new);
                return 0;
        }

        return 1;
}
struct fasync_struct *
fasync_insert_entry(int fd, struct file *filp, struct fasync_struct **fapp, struct fasync_struct *new)
{       
        struct fasync_struct *fa, **fp;
        /*如果fp指標存在,執行連結串列連線*/
        spin_lock(&filp->f_lock);
        spin_lock(&fasync_lock);
        for (fp = fapp; (fa = *fp) != NULL; fp = &fa->fa_next) {
                if (fa->fa_file != filp)
                        continue;

                spin_lock_irq(&fa->fa_lock);
                fa->fa_fd = fd;
                spin_unlock_irq(&fa->fa_lock);
                goto out;
        }
        /*首次進入 fp指標不存在,進行fp指標初始化*/
        spin_lock_init(&new->fa_lock);
        new->magic = FASYNC_MAGIC;
        new->fa_file = filp;
        new->fa_fd = fd;
        new->fa_next = *fapp;
        rcu_assign_pointer(*fapp, new);
        filp->f_flags |= FASYNC;

out:
        spin_unlock(&fasync_lock);
        spin_unlock(&filp->f_lock);
 /*返回struct fasync_struct */
        return fa;
}