字符设备框架:
框架:
通用的接口(主体是由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");
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 }
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
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);
如何自动创建设备文件?
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);
系统移植的根文件系统移植中:
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] 对于一个函数返回值是否出错,内核本质上在判断返回值是否在这个范围内。