• 驱动开发之字符设备框架


    字符设备框架:

    框架:

    通用的接口(主体是由linux社区的内核维护者实现的,小部分由某些厂家实现的)

    1.字符设备框架
    每次读写数据都是按照字节的方式,对应的设备文件类型是c
    常见的字符设备——鼠标、显示器、摄像头、按键、蜂鸣器、adc
    2.块设备框架
    每次读写是一个块的大小,每个块是512字节或者是大于512的2的n次方。
    对应的设备文件类型是b
    常见的块设备——硬盘,u盘,emmc,nandflash,norflash
    3.网络设备框

    为什么需要使用框架?

    当需要通过应用程序调用驱动,继而操作硬件,此时需要使用框架

    mknod "/dev/设备文件" c 主设备号 次设备号
    在/dev下产生设备文件,这个文件会和设备号绑定。

    设备号本身是一个32位无符号整数,前12位是主设备号,后20位是次设备号。

    主设备号一般代表一类设备,次设备号代表某类设备的具体设备。

    驱动代码中必须要和设备号绑定。
    所以一旦设备文件和设备号也绑定了,就可以说明通过设备文件找到驱动。
    应用程序是可以打开文件的,一旦应用程序打开了一个设备文件进而可以让应用程序找到驱动。

    设备号 = 主设备号 << 20 | 次设备号;

    设备号 = MKDEV(主设备号,次设备号);
    1 int register_chrdev_region(dev_t from, unsigned count, const char *name)
    2 
    3 功能:将设备号注册到文件系统中(静态)
    4 参数1:起始设备号
    5 参数2:连续的设备的个数
    6 参数3:以字符串的形式出现在/proc/devices文件中,用来和主设备号绑定。
    7 注意:肯定不是设备文件名
    8 只能保证驱动和设备文件可以匹配,但是驱动还未给应用程序提高交互接口。
    1 void unregister_chrdev_region(dev_t from, unsigned count)
    2 功能:注销设备号

    在驱动中如何给应用程序提高交互接口?

     1 void cdev_init(struct cdev *cdev, const struct file_operations *fops)
     2 功能:给cdev结构体初始化
     3 struct cdev {在内核中专门用来描述字符设备的一个结构体
     4  struct kobject kobj;//见到kobject或者kset作用就是用来创建文件夹。
     5  struct module *owner;//只需要传递THIS_MODULE
     6  const struct file_operations *ops;
     7  struct list_head list;
     8  dev_t dev;//设备号
     9  unsigned int count; //设备个数 
    10  };

    1 int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    2 功能:在驱动层中封装接口
    3 参数1:
    4 参数2:起始设备号
    5 参数3:连续的设备个数
    1 void cdev_del(struct cdev *p)
    2 功能:删除字符设备
     1 struct inode 
     2 {
     3   unnion
     4   {
     5     struct cdev *i_cdev;——> 驱动中定义的cdev结构体
     6   };
     7 };
     8 在内核中每当创建出一个文件时就会在内核中产生一个inode结构体。专门用来描述文件的静态信息。
     9 
    10 
    11 
    12 struct file 
    13 {
    14    struct inode *f_inode;——》指向inode结构体
    15    const struct file_operations *f_op;——》驱动中的file_operations
    16 };
    17 只要打开一次文件在内核中就会产生一个新的file结构体,专门描述动态信息。

    代码:

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 #include <linux/cdev.h>
     5 
     6 dev_t devno;
     7 struct cdev cdev;
     8 
     9 int demo_open(struct inode *inode,struct file *filp)
    10 {
    11     printk("demo_open
    ");
    12     return 0;
    13 }
    14 
    15 struct file_operations fops = {
    16     .owner = THIS_MODULE,//当前模块
    17     .open = demo_open,
    18 };
    19 //fops.open = demo_open;
    20 
    21 int demo_init(void)
    22 {
    23     int ret;
    24     //让设备号和驱动绑定
    25     devno = MKDEV(500,0);    
    26     ret = register_chrdev_region(devno,1,"demo");
    27 
    28     cdev_init(&cdev,&fops);
    29     cdev_add(&cdev,devno,2);
    30     return 0;
    31 }
    32 module_init(demo_init);
    33 
    34 void demo_exit(void)
    35 {
    36     cdev_del(&cdev);
    37     unregister_chrdev_region(devno,1);
    38     return ;
    39 }
    40 module_exit(demo_exit);
    41 MODULE_LICENSE("GPL");
    demo.c
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 
     6 int main(int argc, const char *argv[])
     7 {
     8     int fd;
     9 
    10     fd = open("/dev/demo",O_RDWR);
    11     if(fd == -1)
    12     {
    13         perror("open");
    14         return -1;
    15     }
    16 
    17     sleep(5);
    18 
    19     close(fd);
    20     return 0;
    21 }
    app.c
     1 ifeq ($(KERNELRELEASE),)
     2 PWD = $(shell pwd)
     3 KERNEL_DIR = /home/linux/linux-3.14/
     4 #KERNEL_DIR = /lib/modules/$(shell uname -r)/build/
     5 
     6 #start:
     7 modules:
     8     make -C $(KERNEL_DIR) M=$(PWD) modules
     9 
    10 #end:
    11 clean:
    12     make -C $(KERNEL_DIR) M=$(PWD) clean
    13 else 
    14 obj-m += demo3.o
    15 endif
    Makefail
    1 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
    2 参数1:
    3 参数2:起始次设备号
    4 参数3:次设备号个数
    5 参数4:出现在/proc/devices文件中,用来和主设备号绑定
    1 static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
    2 功能:搭建字符设备框架(包含了动态或静态注册设备号,动态给cdev申请空间,cdev_init,cdev_add)
    3 参数1:主设备号或者0
    4 入果传递的是主设备号,那么会使用静态注册
    5 如果传递的是0,会使用动态注册
    6 参数2:出现在/proc/devices文件中,用来和主设备号绑定。
    7 参数3:
    8 返回值:如果参数1传递主设备号,正常返回0
    9 如果参数1传递的是0,返回值为主设备号
    1 static inline void unregister_chrdev(unsigned int major, const char *name)
    2 功能:注销字符设备框架
    3 参数1:主设备号 
    4 参数2:/proc/devices文件中的名称

    代码:

    alloc_chrdev_region
     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 
     5 MODULE_LICENSE("GPL");
     6 
     7 int major;
     8 
     9 int demo_open(struct inode *inode,struct file *filp)
    10 {
    11     return 0;
    12 }
    13 
    14 int demo_close(struct inode *inode,struct file *filp)
    15 {
    16     return 0;
    17 }
    18 struct file_operations fops = {
    19     .owner = THIS_MODULE,
    20     .open = demo_open,
    21     .release = demo_close,
    22 };
    23 
    24 int demo_init(void)
    25 {
    26     major = register_chrdev(0,"demo",&fops);
    27     return 0;
    28 }
    29 module_init(demo_init);
    30 
    31 void demo_exit(void)
    32 {
    33     unregister_chrdev(major,"demo");
    34     return;
    35 }
    36 module_exit(demo_exit);
    register_chrdev

    如何自动创建设备文件?

    1 struct class *class_create(struct module *owner, const char *name);
    2 功能:在/sys/class目录下创建出一个文件夹(设备类),文件夹名称就是参数2
    1 struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
    2 功能:创建设备文件
    3 参数1:class_create的返回值
    4 参数2:父类设备,如果没有传递NULL
    5 参数3:设备号 
    6 参数4:给当前设备传递的参数
    7 参数5:出现在/sys/class/demo目录下的一个软链接文件,同时固定了设备文件的名称
    8 参数6:不可描述

    代码:

     1 #include <linux/init.h>
     2 #include <linux/module.h>
     3 #include <linux/fs.h>
     4 #include <linux/device.h>
     5 
     6 MODULE_LICENSE("GPL");
     7 
     8 int major;
     9 struct class *cls;
    10 struct device *devs;
    11 
    12 int demo_open(struct inode *inode,struct file *filp)
    13 {
    14     return 0;
    15 }
    16 
    17 int demo_close(struct inode *inode,struct file *filp)
    18 {
    19     return 0;
    20 }
    21 struct file_operations fops = {
    22     .owner = THIS_MODULE,
    23     .open = demo_open,
    24     .release = demo_close,
    25 };
    26 
    27 int demo_init(void)
    28 {
    29 //    int i;
    30     major = register_chrdev(0,"demo",&fops);
    31     cls = class_create(THIS_MODULE,"demo");
    32     devs = device_create(cls,NULL,MKDEV(major,0),NULL,"demo");
    33 
    34 #if 0
    35     for(i = 0;i < 3;i ++)
    36     {
    37         device_create(cls,NULL,MKDEV(major,i),NULL,"demo%d",i);
    38     }
    39 #endif
    40     return 0;
    41 }
    42 module_init(demo_init);
    43 
    44 void demo_exit(void)
    45 {
    46     device_destroy(cls,MKDEV(major,0));
    47     class_destroy(cls);
    48     unregister_chrdev(major,"demo");
    49     return;
    50 }
    51 module_exit(demo_exit);
    class_create device_create

    系统移植的根文件系统移植中:

    1 etc/fstab:
    2 sysfs /sys sysfs defaults 0 0 

    将sysfs文件系统挂载到/sys,在驱动中需要调用class_create在/sys目录下创建文件夹

    1 etc/init.d/rcS:
    2 echo /sbin/mdev > .../hotplug 支持热插拔(动态创建设备文件)

      挂载根文件系统的过程中,mdev会被执行,切换工作路径到/dev,执行时会去检查/sys/class目录下的所有的子目录,会在所有的子目录中去寻找dev属性文件,为了读取出dev属性文件中的主次设备号。基于device_create提供的软链接名称(设备文件名)来调用mknod函数创建设备文件。

    字符设备框架
    
    1、为什么使用框架?
       通过应用程序访问驱动
    
    2、框架的基本操作
        模块声明
        
        struct file_operations fops = {
            .open = demo_open,
            .release = demo_close,
        };
        
        加载函数
        {
            devno = MKDEV(主设备号,次设备号);
            register_chrdev_region(起始设备号,连续设备个数,名称);
                                     //静态注册设备号,让驱动和设备号绑定
                                     //mknod "/dev/设备文件" c 主设备号 次设备号;
                                     //让设备文件和设备号绑定
                                     //间接实现通过设备号让应用程序和驱动匹配。
            cdev_init(struct cdev *,&fops);
            cdev_add(struct cdev *,起始设备号,连续的设备个数);
            //对cdev结构体初始化
            //在驱动层中封装接口
                                     
        }
        
        卸载函数
        {
            cdev_del();
            unregister_chrdev_region();
        }
        
        
        
        模块声明
        struct cdev *p;
        struct file_operations fops = {
            .open = demo_open,
            .release = demo_close,
        };
        
        加载函数
        {
            alloc_chrdev_region(dev_t *,起始次设备号,连续的设备个数,名称);
            //动态申请并且注册设备号
            
            p = kzalloc(sizeof(struct cdev),GFP_KERNEL);
            
            cdev_init(p,&fops);
            cdev_add(p,起始设备号,连续的设备个数);
            //对cdev结构体初始化
            //在驱动层中封装接口
                                     
        }
        
        卸载函数
        {
            cdev_del();
            kfree();
            unregister_chrdev_region();
        }
        
        
        模块声明
        struct cdev *p;
        struct file_operations fops = {
            .open = demo_open,
            .release = demo_close,
        };
        
        加载函数
        {
            //主设备号 = register_chrdev(0,名称,&fops);//搭建字符设备框架,动态注册
            register_chrdev(主设备号,名称,&fops);//返回值为0,静态注册                     
        }
        
        卸载函数
        {
            unregister_chrdev(主设备号,名称);//注销字符设备框架
        }
        
    如何自动创建设备文件?
    根文件系统中:
    etc/fstab文件:sysfs     /sys     sysfs     defaults     0     0
                  将sysfs文件系统挂载到/sys目录下
                  
                  tmpfs     /dev     tmpfs     defaults     0     0
                  
    etc/init.d/rcS: echo /sbin/mdev > .../hotplug
                    /sbin/mdev -s 
                    用于动态创建设备文件
    
    根文件系统会执行mdev程序,它的源码是busybox/util-linux/mdev.c 
    这个源文件执行时会先将工作路径切换到/dev 
    然后扫描/sys/class下的所有子目录,目的是寻找dev属性文件(包含了主次设备号)
    最后调用mknod函数来创建设备文件
                    
    cls = class_create(THIS_MODULE,"demo");//名称出现在/sys/class/demo 
    device_create(cls,NULL,设备号,NULL,"xxx");//出现在/sys/class/demo/xxx软链接,同时是设备文件名称
    
    -1原码: 1  30个0  1
      反码: 1  30个1  0
      补码: 32个1  《==》 FFFFFFFF
      
    -4096原码: 1     18个0    1 0000 0000 0000  
         反码: 1    18个1   0 1111 1111 1111 
         补码: 1    18个1   1 0000 0000 0000   FFFFF000
    
    (FFFFF000,FFFFFFFF] 对于一个函数返回值是否出错,内核本质上在判断返回值是否在这个范围内。     
                  
    总结
  • 相关阅读:
    xshel链接linuxl安装nginx
    nginx学习笔记
    sweiper做一个tab切换
    bootstrap中tab切换的使用
    pc页面自动缩放到手机端
    日程表
    页面嵌套iframe时,怎样让iframe高度根据自身内容高度自适应
    mysql5.7版本以上下载安装
    电脑快捷键操作汇总
    关于.eslintrc.js代码检测的一些配置
  • 原文地址:https://www.cnblogs.com/hslixiqian/p/9642522.html
Copyright © 2020-2023  润新知