天天看點

注冊字元裝置 >>Linux裝置驅動程式[0x100] 字元裝置相關規則[0x200]操作函數接口核心實作[0x300]相關資料結構[0x400]注冊字元裝置過程

文章目錄

  • [0x100] 字元裝置相關規則
    • [0x110]裝置檔案特征
    • [0x120]裝置編号特征
  • [0x200]操作函數接口核心實作
    • [0x210]轉換裝置編号
    • [0x220]配置設定裝置編号
    • [0x230]釋放裝置編号
    • [0x240]初始化 cdev 結構
    • [0x250]注冊字元裝置
  • [0x300]相關資料結構
    • [0x310]檔案操作結構 >>struct file_operations
      • [0x311]open & release函數作用
      • [0x312]read & write函數接口解析
    • [0x320]檔案描述結構 >>struct file
    • [0x330]唯一檔案存儲位置辨別方式 >>struct inode
  • [0x400]注冊字元裝置過程
    • [0x410]手動注冊字元裝置

一個底層開發的開始位置,我可以享受這個過程嗎?

一切都還是未知,夢想和代碼很像需要用努力來實作:

[0x100] 字元裝置相關規則

[0x110]裝置檔案特征

  • 裝置檔案建構:由指令**“mknod”**所需參數:裝置檔案位置、類型、主裝置号、次裝置号;
  • 通路全局性:可以由多個裝置共享通路其中資料;
  • 存儲持久性:裝置重新打開,不清空裝置檔案内容

[0x120]裝置編号特征

  • 存儲容量:通常由共有32位組成,12位主裝置号,20位次裝置号;
  • 裝置類型:字元裝置驅動[辨別‘c’] 和 塊裝置驅動[辨別‘b’];
  • 主裝置号:辨別裝置使用哪種驅動程式;
  • 次裝置号:辨別差別使用相同驅動程式,不同裝置入口,{即裝置辨別};

[0x200]操作函數接口核心實作

[0x210]轉換裝置編号

#include<linux/types.h>    //define dev_t
#include<linux/kdev_t.h>     //define MAJOR MINOR MKDEV

/*MINORMASK = 1<<20-1= 0x100000-1 = 0xFFFFF */
#define MINORBITS       20
#define MINORMASK       ((1U << MINORBITS) - 1)

/*主裝置号擷取*/
#define MAJOR(dev)      ((unsigned int) ((dev) >> MINORBITS))
/*次裝置号擷取*/
#define MINOR(dev)      ((unsigned int) ((dev) & MINORMASK))
/*反向裝換*/
#define MKDEV(major,minor)    (((major) << MINORBITS) | (minor))
           

[0x220]配置設定裝置編号

  1. 靜态配置設定裝置号: int register_chrdev_region(dev_t from, unsigned count, const char *name)

Func : 通過指定裝置号的主裝置起始位置、次裝置号範圍、裝置名稱,靜态配置設定裝置号;

args1 : 使用MKDEV來設定dev_t 裝置号結構體;

args2 : 裝置号配置設定數量;

args3 : 指定裝置名稱,該名稱将以“主裝置号+名稱”出現在 “/proc/devices”檔案中;

retval : 成功傳回0,失敗傳回錯誤碼;

#include<linux/fs.h>
/*implement @ kernel_root_dir/fs/char_dev.c:196*/

int register_chrdev_region(dev_t from, unsigned count, const char *name)

{
    struct char_device_struct *cd;
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0); 
        if (next > to) 
            next = to; 
        cd = __register_chrdev_region(MAJOR(n), MINOR(n),
                   next - n, name);
        if (IS_ERR(cd))                                                                              
            goto fail;
    }   
    return 0;
fail:
    to = n;
    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
    return PTR_ERR(cd);
}
           
  1. 動态配置設定裝置号: int alloc_chrdev_region(dev_t dev, unsigned baseminor, unsigned count,const charname)

Func : 通過指定裝置号的主裝置起始位置、次裝置号範圍、裝置名稱,動态配置設定裝置号;

args1 : 輸出裝置編号,需要定義結構體dev_t變量;

args2 : 起始次裝置号,通常為0{表示從0開始配置設定次裝置号};

args3 : 裝置号配置設定數量;

args4 : 指定裝置名稱,該名稱将以主裝置号,名稱的方式出現在 “/proc/devices”檔案中;

retval : 成功傳回0,失敗傳回錯誤碼;

#include<linux/fs.h>
/*implement @ kernel_root_dir/fs/char_dev.c:232*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
{
    struct char_device_struct *cd;
    cd = __register_chrdev_region(0, baseminor, count, name);
    if (IS_ERR(cd))
        return PTR_ERR(cd);
    *dev = MKDEV(cd->major, cd->baseminor);
    return 0;
}
           

[0x230]釋放裝置編号

釋放裝置号: void unregister_chrdev_region(dev_t from, unsigned count)

Func : 銷毀不再使用的裝置号

args1 : 需要銷毀的裝置号;

args2 : 裝置号數量;

retval : void

#include<linux/fs.h>
/*implement @ kernel_root_dir/fs/char_dev.c:307*/
void unregister_chrdev_region(dev_t from, unsigned count)
{                                                                                                    
    dev_t to = from + count;
    dev_t n, next;

    for (n = from; n < to; n = next) {
        next = MKDEV(MAJOR(n)+1, 0);
        if (next > to)
            next = to;
        kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
    }
}
           

[0x240]初始化 cdev 結構

釋放裝置号: void cdev_init(struct cdev *cdev, const struct file_operations *fops)

Func : 初始化cdev 中連結清單結構、初始化核心裝置對象、檔案操作集結構

args1 : 字元裝置屬性資訊結構;

args2 : 裝置檔案操作函數集;

retval : void;

#include<linux/cdev.h>
/*implement @kernel_root_dir/fs/char_dev.c:542*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
        memset(cdev, 0, sizeof *cdev);
        INIT_LIST_HEAD(&cdev->list);
        kobject_init(&cdev->kobj, &ktype_cdev_default);
        cdev->ops = fops;
}
           

[0x250]注冊字元裝置

釋放裝置号: int cdev_add(struct cdev *p, dev_t dev, unsigned count)

Func : 添加裝置到核心裝置對象映射

args1 : 字元裝置屬性資訊結構;

args2 : 裝置辨別;

args3:注冊裝置數量;

retval : 成功傳回0,失敗傳回錯誤碼;

#include<linux/cdev.h>
/*implement @kernel_root_dir/fs/char_dev.c:472*/
 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
 {
         p->dev = dev;
         p->count = count;
        return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
 }
           

[0x300]相關資料結構

[0x310]檔案操作結構 >>struct file_operations

該結構描述了裝置可以使用操作函數指針集合,類似JAVA中接口,需要在驅動中實作後,方可由應用層調用;
#include<linux/fs.h>
struct file_operations {
           /*根據目前常用的縮略一部分内容,為了使結構更加清晰*/
           /*用于指定子產品程式的擁有者,通常使用”THIS_MODULE“來初始化*/
          struct module *owner;
          
          /*打開與釋放檔案描述符,不需要實作可以調用虛拟檔案系統中的 sys_open與sys_release*/
          int (*open) (struct inode *, struct file *);
          int (*release) (struct inode *, struct file *);
         
          /*指定裝置檔案中讀寫起始位置,成功傳回非負值:從檔案開始到目前位置的檔案偏移量*/
          loff_t (*llseek) (struct file *, loff_t, int);
          
          /*同步讀操作——裝置檔案讀寫函數指針,成功傳回非負值:讀寫的字元數量*/
          ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
          ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
          
          /*異步讀寫操作,成功傳回非負值:讀寫的字元數量*/
          ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
          ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 
          
          /*用于重新整理緩沖區,需要後端實作, 以下分别是同步版本\異步版本*/
          int (*fsync) (struct file *, loff_t, loff_t, int datasync);
          int (*aio_fsync) (struct kiocb *, int datasync);
        
          /*新版與老版ioctl函數指針,用于格式化使用者指令,需要實作自定義指令通過_TOC_ */
          long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
          long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
          
          /*IO多路複用與異步通知的函數指針,*/
          unsigned int (*poll) (struct file *, struct poll_table_struct *);
          int (*fasync) (int, struct file *, int);
          /*裝置記憶體映射到程序空間*/
          int (*mmap) (struct file *, struct vm_area_struct *);
          unsigned long (*get_unmapped_area)
          (struct file *, unsigned long, unsigned long, unsigned long,unsigned long);
  };
           

[0x311]open & release函數作用

1.int (*open) (struct inode *, struct file *)

初始化裝置并檢查硬體資料是否已準備好;

【可選】更新裝置檔案的 f_op (檔案遊标位置);

【可選】更新struct file * 中 private_data 中的資料;

2.int (*release) (struct inode *, struct file *);

釋放硬體使用的資源并反注冊字元裝置;

通常為裝置、程序、檔案關閉時自動調用該函數;

[0x312]read & write函數接口解析

1.__ssize_t (*read) (struct file *file, char user *ubuf, size_t count, loff_t * offp);

核心空間資料 -->使用者空間資料,即為 應用讀;

args 1:核心空間 檔案結構體首位址,其中儲存了 args 4 檔案位置;

args 2:段辨別【__user】表明該資料位于使用者空間的虛拟位址,是以核心無法直接通路;

args 3:使用者空間的緩沖區大小;

args 4:核心操作的檔案位置;

ret_val:成功>0 成功傳輸位元組數,=0 達到檔案末尾,錯誤<0 錯誤碼;

通常使用 copy_to_user() 輸出資料到使用者空間,成功傳回0 錯誤傳回已處理的位元組數;#include <asm/uaccess.h>

2.__ssize_t (*write) (struct file *file, char user *ubuf, size_t count, loff_t * offp);

使用者空間資料 -->核心空間資料,即為 應用寫

args 1:核心空間 檔案結構體首位址,其中儲存了 args 4 檔案位置;

args 2:段辨別【__user】表明該資料位于使用者空間的虛拟位址,是以核心無法直接通路;

args 3:使用者空間的緩沖區大小;

args 4:核心操作的檔案位置;

ret_val:成功>0 成功寫入位元組數,=0 沒有寫入資料,錯誤<0 錯誤碼;

通常使用 copy_from_user() 從使用者空間擷取資料,成功傳回0 錯誤傳回已處理的位元組數;#include <asm/uaccess.h>

[0x320]檔案描述結構 >>struct file

該資料結構表示一個打開的檔案的屬性資訊

#include<linux/fs.h>
struct file {   
  /*隻展示有關字元裝置驅動的,具體結構請與對應頭檔案中檢視*/
  /*檔案操作函數指針集合*/
    const struct file_operations    *f_op;
  /*IO操作辨別[f_flags]、讀寫權限[f_mode]、目前讀寫位置[f_ops],檔案使用者資訊[f_owner]
   * 盡量隻讀不要進行操作*/              
          unsigned int            f_flags;
          fmode_t                 f_mode; 
          loff_t                  f_pos;
          struct fown_struct      f_owner;
          const struct cred       *f_cred;
          struct file_ra_state    f_ra;
          void                    *private_data;
  }                                      
           

[0x330]唯一檔案存儲位置辨別方式 >>struct inode

#include<linux/fs.h>
struct inode {
        /*檔案權限屬性*/
        umode_t                 i_mode;
        unsigned short          i_opflags;
        uid_t                   i_uid;
        gid_t                   i_gid;
        unsigned int            i_flags;
        /*檔案在檔案系統中唯一辨別 inode号*/
        unsigned long           i_ino;
        /*裝置的 主裝置号和次裝置号*/
        dev_t                   i_rdev;
        /* former ->i_op->default_file_ops */
        const struct file_operations    *i_fop; 
       /*指向不同裝置的資料結構,最下面的是字元裝置*/
        union {
                struct pipe_inode_info  *i_pipe;
                struct block_device     *i_bdev;
                struct cdev             *i_cdev;
        };
       /*指向裝置自身的私有資料,需要額外釋放*/
        void                            *i_private; 
}
           

[0x400]注冊字元裝置過程

  • 配置設定裝置号[alloc_chrdev_region]–>填充[struct file_operations] --> 初始化[struct cdev]_(cdev_init)
  • 添加字元裝置(cdev_add)
  • 解除安裝字元裝置(cdev_del)

[0x410]手動注冊字元裝置

#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#define CDEV_NAME "m_chrdev";
struct file_operaions cdev_fops={
      .open    = chrdev_open,
      .read    = chrdev_read,
      .write   = chrdev_write,
      .release = chrdev_remove,
};

struct chrdev_mt{
      unsigned int      cdev_major;
      unsigned int      cdev_minor;
      dev_t             cdev_no;
      struct cdev       cdev_sign;
      struct semaphore  cdev_sem;    
};
struct chrdev_mt * cdev_info;
static int get_cdev_num(int cdev_count){
unsigned int err_ret;
/*從核心空間配置設定連續記憶體給struct chrdv_mt 結構*/
   cdev_info = (struct chrdev_mt *)kzalloc(sizeof(struct chrdev_mt *),GFP_KERNEL);
   if(NULL== cdev_info)
   {
       #ifdef CONFIG_CDEV_DEBUG
        printk(KERN_ERR " alloc memory AT%s:%d\n",__func__,__LINE__);
       #endif
   return -ENOMEM;
   }
     cdev_info->cdev_major =0;
     cdev_info->cdev_minor =0;
   
   if(cdev_info->cdev_major)
/*如果主裝置号為非0,使用靜态配置設定的方式*/
      err_ret = register_chrdev_region
                (MKDEV(cdev_info->cdev_major,cdev_info->cdev_minor),cdev_count,CDEV_NAME);
   else
/*如果主裝置号為0,使用動态配置設定的方式*/
      err_ret = alloc_chrdev_region
                (&cdev_info->cdev_no,cdev_info->cdev_minor,cdev_count,CDEV_NAME);
 /*如果傳回值非0,則列印錯誤資訊傳回錯誤碼*/     
     if(err_ret)
     {
        #ifdef CONFIG_CDEV_DEBUG
        printk(KERN_ERR " alloc cdev code AT%s:%d\n",__func__,__LINE__);
        #endif;
        kfree(cdev_info);
        return err_ret;
      }  
      return 0
 }      
static int setup_new_chrdev(struct chrdev_mt * cdev_info,int dev_index)
 {
    int err_ret;     
       /*初始化cdev資料結構 并綁定 fops*/    
           cdev_init(&cdev_info->cdev_sign,&cdev_fops);
           cdev_info->cdev.owner = THIS_MODULE;
       /*注冊裝置*/      
           err_ret = cdev_add(&cdev_info->cdev_sign,cdev_info->cdev_no,1);
       /*注冊失敗*/    
            if(err_ret){ 
                #ifdef CONFIG_CDEV_DEBUG
                printk(KERN_ERR "cdev register err AT%s:%d\n",__func__,__LINE__);
                kfree(cdev_info);
                #endif
                return err_ret;
           }
            
 }
           

繼續閱讀