wordpress搭建个人网站,广州免费打hpv疫苗预约条件,做维修电器网站,昌平石家庄网站建设一、字符设备基础知识 1、设备驱动分类 linux系统将设备分为3类#xff1a;字符设备、块设备、网络设备。使用驱动程序#xff1a; 字符设备#xff1a;是指只能一个字节一个字节读写的设备#xff0c;不能随机读取设备内存中的某一数据#xff0c;读取数据需要按照先后数… 一、字符设备基础知识 1、设备驱动分类 linux系统将设备分为3类字符设备、块设备、网络设备。使用驱动程序 字符设备是指只能一个字节一个字节读写的设备不能随机读取设备内存中的某一数据读取数据需要按照先后数据。字符设备是面向流的设备常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。 块设备是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。 每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件或称设备节点来使用驱动程序操作字符设备和块设备。 2、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系 如图在Linux内核中 a -- 使用cdev结构体来描述字符设备; b -- 通过其成员dev_t来定义设备号分为主、次设备号以确定字符设备的唯一性; c -- 通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数如常见的open()、read()、write()等; 在Linux字符设备驱动中: a -- 模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号; b -- 通过 cdev_init( ) 建立cdev与 file_operations之间的连接通过 cdev_add( ) 向系统添加一个cdev以完成注册; c -- 模块卸载函数通过cdev_del( )来注销cdev通过 unregister_chrdev_region( )来释放设备号; 用户空间访问该设备的程序: a -- 通过Linux系统调用如open( )、read( )、write( )来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数; 3、字符设备驱动模型 二、cdev 结构体解析 在Linux内核中使用cdev结构体来描述一个字符设备cdev结构体的定义如下 [cpp] view plaincopy include/linux/cdev.h struct cdev { struct kobject kobj; //内嵌的内核对象. struct module *owner; //该字符设备所在的内核模块的对象指针. const struct file_operations *ops; //该结构描述了字符设备所能实现的方法是极为关键的一个结构体. struct list_head list; //用来将已经向内核注册的所有字符设备形成链表. dev_t dev; //字符设备的设备号由主设备号和次设备号构成. unsigned int count; //隶属于同一主设备号的次设备号的个数. }; 内核给出的操作struct cdev结构的接口主要有以下几个: a -- void cdev_init(struct cdev *, const struct file_operations *); 其源代码如代码清单如下 [cpp] view plaincopy 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; } 该函数主要对struct cdev结构体做初始化最重要的就是建立cdev 和 file_operations之间的连接(1) 将整个结构体清零 (2) 初始化list成员使其指向自身 (3) 初始化kobj成员 (4) 初始化ops成员 b --struct cdev *cdev_alloc(void); 该函数主要分配一个struct cdev结构动态申请一个cdev内存并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在调用cdev_alloc后显式的做初始化即: .opsxxx_ops). 其源代码清单如下 [cpp] view plaincopy struct cdev *cdev_alloc(void) { struct cdev *p kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(p-list); kobject_init(p-kobj, ktype_cdev_dynamic); } return p; } 在上面的两个初始化的函数中我们没有看到关于owner成员、dev成员、count成员的初始化其实owner成员的存在体现了驱动程序与内核模块间的亲密关系struct module是内核对于一个模块的抽象该成员在字符设备中可以体现该设备隶属于哪个模块在驱动程序的编写中一般由用户显式的初始化 .owner THIS_MODULE, 该成员可以防止设备的方法正在被使用时设备所在模块被卸载。而dev成员和count成员则在cdev_add中才会赋上有效的值。 c -- int cdev_add(struct cdev *p, dev_t dev, unsigned count); 该函数向内核注册一个struct cdev结构即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。 当然这里还需提供两个参数 (1)第一个设备号 dev (2)和该设备关联的设备编号的数量。 这两个参数直接赋值给struct cdev 的dev成员和count成员。 d -- void cdev_del(struct cdev *p) 该函数向内核注销一个struct cdev结构即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了。 从上述的接口讨论中我们发现对于struct cdev的初始化和注册的过程中我们需要提供几个东西 (1) struct file_operations结构指针 (2) dev设备号 (3) count次设备号个数。 但是我们依旧不明白这几个值到底代表着什么而我们又该如何去构造这些值 三、设备号相应操作 1 -- 主设备号和次设备号二者一起为设备号 一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备用来区分同类型的设备。 linux内核中设备号用dev_t来描述2.6.28中定义如下 typedef u_long dev_t; 在32位机中是4个字节高12位表示主设备号低20位表示次设备号。 内核也为我们提供了几个方便操作的宏实现dev_t 1) -- 从设备号中提取major和minor MAJOR(dev_t dev); MINOR(dev_t dev); 2) -- 通过major和minor构建设备号 MKDEV(int major,int minor); 注这只是构建设备号。并未注册需要调用 register_chrdev_region 静态申请 [cpp] view plaincopy //宏定义 #define MINORBITS 20 #define MINORMASK ((1U MINORBITS) - 1) #define MAJOR(dev) ((unsigned int) ((dev) MINORBITS)) #define MINOR(dev) ((unsigned int) ((dev) MINORMASK)) #define MKDEV(ma,mi) (((ma) MINORBITS) | (mi))/span 2、分配设备号两种方法 a -- 静态申请 int register_chrdev_region(dev_t from, unsigned count, const char *name) 其源代码清单如下 [cpp] view plaincopy 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); } b -- 动态分配int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) 其源代码清单如下 [cpp] view plaincopy 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; } 可以看到二者都是调用了__register_chrdev_region 函数其源代码如下 [cpp] view plaincopy static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor, int minorct, const char *name) { struct char_device_struct *cd, **cp; int ret 0; int i; cd kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); if (cd NULL) return ERR_PTR(-ENOMEM); mutex_lock(chrdevs_lock); /* temporary */ if (major 0) { for (i ARRAY_SIZE(chrdevs)-1; i 0; i--) { if (chrdevs[i] NULL) break; } if (i 0) { ret -EBUSY; goto out; } major i; ret major; } cd-major major; cd-baseminor baseminor; cd-minorct minorct; strlcpy(cd-name, name, sizeof(cd-name)); i major_to_index(major); for (cp chrdevs[i]; *cp; cp (*cp)-next) if ((*cp)-major major || ((*cp)-major major (((*cp)-baseminor baseminor) || ((*cp)-baseminor (*cp)-minorct baseminor)))) break; /* Check for overlapping minor ranges. */ if (*cp (*cp)-major major) { int old_min (*cp)-baseminor; int old_max (*cp)-baseminor (*cp)-minorct - 1; int new_min baseminor; int new_max baseminor minorct - 1; /* New driver overlaps from the left. */ if (new_max old_min new_max old_max) { ret -EBUSY; goto out; } /* New driver overlaps from the right. */ if (new_min old_max new_min old_min) { ret -EBUSY; goto out; } } cd-next *cp; *cp cd; mutex_unlock(chrdevs_lock); return cd; out: mutex_unlock(chrdevs_lock); kfree(cd); return ERR_PTR(ret); } 通过这个函数可以看出register_chrdev_region和alloc_chrdev_region的区别register_chrdev_region直接将Major 注册进入而 alloc_chrdev_region从Major 0 开始逐个查找设备号直到找到一个闲置的设备号并将其注册进去二者应用可以简单总结如下 register_chrdev_region alloc_chrdev_region devno MKDEV(major,minor); ret register_chrdev_region(devno, 1, hello); cdev_init(cdev,hello_ops); ret cdev_add(cdev,devno,1); alloc_chrdev_region(devno, minor, 1, hello); major MAJOR(devno); cdev_init(cdev,hello_ops); ret cdev_add(cdev,devno,1) register_chrdev(major,hello,hello 可以看到除了前面两个函数还加了一个register_chrdev 函数可以发现这个函数的应用非常简单只要一句就可以搞定前面函数所做之事 下面分析一下register_chrdev 函数其源代码定义如下 [cpp] view plaincopy static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); } 调用了 __register_chrdev(major, 0, 256, name, fops) 函数 [cpp] view plaincopy int __register_chrdev(unsigned int major, unsigned int baseminor, unsigned int count, const char *name, const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; int err -ENOMEM; cd __register_chrdev_region(major, baseminor, count, name); if (IS_ERR(cd)) return PTR_ERR(cd); cdev cdev_alloc(); if (!cdev) goto out2; cdev-owner fops-owner; cdev-ops fops; kobject_set_name(cdev-kobj, %s, name); err cdev_add(cdev, MKDEV(cd-major, baseminor), count); if (err) goto out; cd-cdev cdev; return major ? 0 : cd-major; out: kobject_put(cdev-kobj); out2: kfree(__unregister_chrdev_region(cd-major, baseminor, count)); return err; } 可以看到这个函数不只帮我们注册了设备号还帮我们做了cdev 的初始化以及cdev 的注册3、注销设备号 void unregister_chrdev_region(dev_t from, unsigned count) 4、创建设备文件 利用cat /proc/devices查看申请到的设备名设备号。 1使用mknod手工创建mknod filename type major minor 2自动创建设备节点: 利用udevmdev来实现设备文件的自动创建首先应保证支持udevmdev由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class再为每个设备调用device_create创建对应的设备。 详细解析见Linux 字符设备驱动开发 二—— 自动创建设备节点 下面看一个实例,练习一下上面的操作 hello.c [cpp] view plaincopy #include linux/module.h #include linux/fs.h #include linux/cdev.h static int major 250; static int minor 0; static dev_t devno; static struct cdev cdev; static int hello_open (struct inode *inode, struct file *filep) { printk(hello_open \n); return 0; } static struct file_operations hello_ops { .open hello_open, }; static int hello_init(void) { int ret; printk(hello_init); devno MKDEV(major,minor); ret register_chrdev_region(devno, 1, hello); if(ret 0) { printk(register_chrdev_region fail \n); return ret; } cdev_init(cdev,hello_ops); ret cdev_add(cdev,devno,1); if(ret 0) { printk(cdev_add fail \n); return ret; } return 0; } static void hello_exit(void) { cdev_del(cdev); unregister_chrdev_region(devno,1); printk(hello_exit \n); } MODULE_LICENSE(GPL); module_init(hello_init); module_exit(hello_exit); 测试程序 test.c [cpp] view plaincopy #include sys/types.h #include sys/stat.h #include fcntl.h #include stdio.h main() { int fd; fd open(/dev/hello,O_RDWR); if(fd0) { perror(open fail \n); return ; } close(fd); } makefile: [cpp] view plaincopy ifneq ($(KERNELRELEASE),) obj-m:hello.o $(info 2nd) else KDIR : /lib/modules/$(shell uname -r)/build PWD:$(shell pwd) all: $(info 1st) make -C $(KDIR) M$(PWD) modules clean: rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order endif 编译成功后使用 insmod 命令加载 然后用cat /proc/devices 查看会发现设备号已经申请成功