zoukankan      html  css  js  c++  java
  • 字符设备驱动——申请设备号、注册字符设备

    1. 设备号

        主设备号:用来标识与设备文件相关的驱动程序,        ——反应设备类型
        次设备号:为内核所用,被驱动程序用来辨别操作那个设备文件    ——区分同类型的具体某个设备
     
    1.1 设备号的内部表达
        在内核中,保存设备号(包括主设备号和此设备好)使用类型
        dev_t   (<linux/types.h>) 
        这是一个unsigned int  是一个32位的无符号整型。。
        主设备号——高12位
        此设备号——低12位
        我们可以使用宏来取一个设备号(dev)的主设备号和此设备号    
         定义在 <linux/kdev_t.h>
          MAJOR(dev_t dev)        取主设备号
          MINOR(dev_t dev)         取次设备号
        也可以将主次设备号合成一个完整的dev_t类型的设备号
        MKDEV(int major, int minor)       将主次设备号转换成dev_t
     
     1.2 分配主次设备号
        linux可以采用静态申请和动态申请两种方法来分配主次设备号
        
        静态申请
          1. 根据Documentation/devices.txt, 确定一个没有使用的主设备号
          2. 使用register_chrdev_region(dev_t first, unsigned int count, char *name)
           定义在<linux/fs.h>    
            count 为所请求的连续设备编号个数, 如果count过大,可以会各下一个主设备号重叠。
              name 为设备名, 注册后 出现在/proc/devices和sysfs中
     
        动态分配
        作为一个新的驱动程序,应该使用动态分配机制获取主设备号
        alloc_chrdev_region(dev_t *dev, unsigned int first, unsigned int count, char *name)
     
        不管用何种方法分配, 不用时都要释放掉
         void unregister_chrdev_region(dev_t first, unsigned int count);
     
        静态申请与动态申请的优缺点:
        静态申请——简单(优);  一旦驱动程序被广泛命使用, 随机选定的主设备号可以造成冲突,使驱动程序无法注册。(劣)
        动态申请——简单,易于驱动推广(优);无法在驱动安装前创建设备文件, 因为不能保证分配的主设备号始终一致。(劣)

     

    2 创建设备文件

        设备文件的创建有    
         1. 使用mknod命令手工创建
         2. 自动创建
        两种方法。
        

        2.1 mknod手工创建

         mknod  用法:
        mknod  filename type  major  minor
     
            filename :  设备文件名
            type        :  设备文件类型
            major      :   主设备号
            minor      :   次设备号
        

    2.2 自动创建

        如果我们在驱动里面动态创建的话需要这样做
        struct class *cdev_class; 
        cdev_class = class_create(owner,name)
        device_create(_cls,_parent,_devt,_device,_fmt)


     

    2.3 模块退出时要销毁设备文件

        
        device_destroy(_cls,_device)
        class_destroy(struct class * cls)
     

     

    3. 一些重要的结构体

        
            大部分的基础性的驱动操作包括 3 个重要的内核数据结构, 称为 file_operations, file, 和 inode.
    •   文件结构 struct file
                 定义于<linux/fs.h>,   是一个内核结构, 不会出现在用户空间
       代表一个打开的文件。系统中每个找开的文件在内核空间一个关联的
       struct file, 它由内核在打开文件时创建, 在文件关闭后释放
    重要成员
    loff_t  f_ops     /* 文件读写位置 */
    struct file_operations  *f_op    /*  文件关联的操作 */
    mode_t  f_mode        /* 模式确定文件可读或者可写 */
    unsigned int f_flags /* 文件标志,一般用来判断是否请求非阻塞操  作, 标志定义<linux/fcntl.h> */  
    void *private_data; 

         open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.
    • 文件操作 struct file_operation
    file_operation 结构是一个字符驱动如何建立这个连接. 这个结构, 定
    义在 <linux/fs.h>, 是一个函数指针的集合. 每个打开文件(内部用一个 file 结构来代
    表, 稍后我们会查看)与它自身的函数集合相关连( 通过包含一个称为 f_op 的成员, 它指
    向一个 file_operations 结构). 这些操作大部分负责实现系统调用, 因此, 命名为 open,
    read, 等等. 我们可以认为文件是一个"对象"并且其上的函数操作称为它的"方法", 使用
    面向对象编程的术语来表示一个对象声明的用来操作对象的动作.
     
    下面是一个file_operationd的声明:
            struct file_operations my_fops = {
            .owner =  THIS_MODULE, 
            .llseek =  my_llseek, 
            .read =  my_read, 
            .write =  my_write, 
            .ioctl =  my_ioctl, 
             .open =  my_open, 
             .release =  my_release,  
         };  


    该声明使用标准的 C 标记式结构初始化语法. 这个语法是首选的, 因为它使驱动在结构定义的改变之间更加可移植  。
    下面列出file_operationd部分成员的含义:(其他成员自行百度) 
    struct module *owner 
         第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.
    loff_t (*llseek) (struct file *, loff_t, int); 
    llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32 位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).  
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
         用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -
        EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
        发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.
               int (*open) (struct inode *, struct file *); 
          尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知
    int (*release) (struct inode *, struct file *); 
         在文件结构被释放时引用这个操作. 相当于close
    • struct inode
            由内核在内部用来表示文件。因些,它和代表打开文件的file结构是不同的。一个文件可以对应多个file结构, 但只有一个inode结构
            
    重要成员
    dev_t   i_rdev:  / * 对于代表设备文件的节点, 这个成员包含实际的设备编号 */    
    struct cdev *i_cdev;  /* struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.*/
     
     

    4. 字符设备的注册

            Linux2.6内核中,字符设备使用struct  cdev来描述字符设备驱动的注册。
            字符设备驱动的注册主要有三个步骤
            (1) 分配cdev
            (2)初始化cdev
            (3)添加cdev
            
    分配
        struct cdev *my_cdev = cdev_alloc();
    初始化
        void cdev_init(struct  cdev *cdev,  const struct file_operations *fops);
        cdev:  待初始化的cdev结构
        fops:  设备对应的操作函数集
    注册, 告诉内核
        int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
        dev: 添加到内核的字符设备结构
        num: 设备响应的第一个设备号
        count: 关联到设备的设备号数目,通常为1
    去除字符设备
        void cdev_del(struct cdev *dev);
     
     
    使用 cdev_add 是有几个重要事情要记住
        1.第一个是这个调用可能失败. 如果它返回一个负的错误码, 你的设备没有增加到系统中.
     
        2. cdev_add 一返回成功, 你的设备就是"活的"并且内核可以调用它的操作.
            所以除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add.
     
     
     

    5 注册字符设备的一个例子

     
    1. 还是线上源代码:
    //memdev.h
    #ifndef _MEMDEV_H_
    #define _MEMDEV_H_
     
    #ifndef MEMDEV_MAJOR
    #define MEMDEV_MAJOR 200
    #endif
     
    #ifndef MEMDEV_NR_DEVS
    #define MEMDEV_NR_DEVS 2
    #endif
     
    #ifndef MEMDEV_SIZE
    #define MEMDEV_SIZE 4096
    #endif
     
    struct mem_dev{
        char* data;
        unsigned long size;
     
    };
     
    #endif

    //memdev.c  
    # include < linux / module.h >
    # include < linux / types.h >
    # include < linux / fs.h >
    # include < linux / errno.h >
    # include < linux / mm.h >
    # include < linux / sched.h >
    # include < linux / init.h >
    # include < linux / cdev.h >
    # include < asm / io.h >
    # include < asm / system.h >
    # include < asm / uaccess.h >
    # include < linux / wait.h >
    # include < linux / completion.h >
     
    # include "memdev.h"
     
    MODULE_LICENSE( "Dual BSD/GPL" );
     
    static int   mem_major = MEMDEV_MAJOR;
     
    struct mem_dev * mem_devp; /*设备结构体指针*/
     
    struct cdev cdev;
     
    /*文件打开函数*/
    int mem_open( struct inode * inode, struct file * filp)
    {
    printk( "open own file
    " );
         return 0 ;
    }
     
    /*文件操作结构体*/
    static const struct file_operations mem_fops =
    {
      .owner = THIS_MODULE,
      .open = mem_open,
    };
     
    /*设备驱动模块加载函数*/
    static int memdev_init( void )
    {
       int result;
       int i;
     
      dev_t devno = MKDEV(mem_major, 0 );
     
       /* 静态申请设备号*/
        result = register_chrdev_region(devno, 2 , "memdev" );
       if (result < 0 )
         return result;
     
       /*初始化cdev结构*/
      cdev_init( & cdev, & mem_fops);
     
       /* 注册字符设备 */
      cdev_add( & cdev, MKDEV(mem_major, 0 ), MEMDEV_NR_DEVS);
     
       return result;
    }
     
    /*模块卸载函数*/
    static void memdev_exit( void )
    {
      cdev_del( & cdev);    /*注销设备*/
      unregister_chrdev_region(MKDEV(mem_major, 0 ), 2 ); /*释放设备号*/
    }
     
    module_init(memdev_init);
    module_exit(memdev_exit);
     
    #Makefile
    ifneq ($(KERNELRELEASE),)
        obj-m := memdev.o
    else
        KERNELDIR ?= /lib/modules/$(shell uname -r)/build
        PWD = $(shell pwd)
    default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
    clean:
        rm memdev.mod*  module* memdev.o memdev.ko Module.*
    endif
     
     
    2. 测试
        首先先make下,生成memdev.ko
        然后insmod memdev.ko生成memdev模块
        创建设备节点:sudo mknod /dev/memdev_t c 200 0
        接下开使用设备文件
        下面是一个测试程序
    
      // memusr.c
    #include <stdio.h>
    #include <string.h>
     
    int main()
    {
        FILE *fp0;
        /*打开设备文件*/
        fp0 = fopen("/dev/memdev_t","r+");
        if (fp0 == NULL) {
            printf("Open Memdev0 Error!
    ");
            return -1;
        }
    }


     
    编译运行,然后使用dmesg可以看到日志文件里输出
    [38439.741816] Hello World!
    [38657.654345] Goodbye
    [40393.039520] open own file


     
    记得要使用sudo 运行memusr   否则会显示设备打开失败。

  • 相关阅读:
    temp
    Windows如何利用输入法简单的打出 ‘↑’ ‘↓’ ‘↖’等箭头
    Win10系统特别卡的一个原因
    巨蟒python全栈开发-第16天 核能来袭-初识面向对象
    在pycharm中误删了Python文件,怎么办,挺急的?
    巨蟒python全栈开发-第15天 装饰器
    巨蟒python全栈开发-第13天 内置函数 匿名函数lambda
    巨蟒python全栈开发-第12天 生成器函数 各种推导式 yield from
    360浏览器的收藏夹隐藏了,怎么处理?
    如何解决安装好的google浏览器打不开网页的问题?
  • 原文地址:https://www.cnblogs.com/Windeal/p/4284679.html
Copyright © 2011-2022 走看看