驱动开发之字符设备框架

字符设备框架:

框架:

通用的接口(主体是由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] 对于一个函数返回值是否出错,内核本质上在判断返回值是否在这个范围内。     
              
总结
原文地址:https://www.cnblogs.com/hslixiqian/p/9642522.html