注册字符设备 >>Linux设备驱动程序

一个底层开发的开始位置,我可以享受这个过程吗?
一切都还是未知,梦想和代码很像需要用努力来实现:

[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;
           }
            
 }

猜你喜欢

转载自blog.csdn.net/god1992/article/details/84929415