字符设备不得不说的那些事:
一: 设备号:主设备号,次设备号:
数据类型 dev_t(unsigned int) 定义设备号 高12位主设备号 低20位次设备号;
二: 设备号的作用:
应用程序通过主设备号找到驱动程序;
三:如何分配设备号:
①:静态分配:
1: cat /proc/devices 查看linux系统哪个设备号没有被占用;
2: dev_t dev_id = MKDEV(主设备号,次设备号) 根据你的设备个数分配次设备号 如果设备个数只有一个,一般此设备号从0开始;
3: 调用 register_chrdev_region(dev_t from,unsigned count,const char *name);
from:待申请的设备号 count:待申请的设备号数目 name:设备名称(出现在 /proc/devices)
②:动态分配:
1:调用 alloc_chrdev_region 直接向内核去申请设备号,也就是让操作系统内核帮你分配设备号。
③:设备号的注销:
1:void unregister_chrdev_region(dev_t from, unsigned count);
四:重要的数据结构:
①:文件结构: struct file; //描述打开文件后的状态
生命周期:当打开文件后,内核自动创建一个结构体 struct file 文件关闭之后 struct file 被自动销毁。
重要成员:
struct file_operations *f_op; //指向驱动程序中实现的各个硬件操作方法;
unsigned int f_flags; //文件操作属性;
loff_t f_ops; //文件操作位置;
void *private_data; //存放私有数据
②:inode 结构: struct inode; //用于记录文件物理信息
生命周期:文件存在,内核创建;文件销毁,内核销毁对应的inode;
重要成员:
dev_t i_rdev; //存放设备号
struct cdev *i_cdev; //指向一个字符设备
注:一个文件只有一个inode,可以有多个file;
③:文件操作结构: struct file_operations; //一个函数指针的集合,这些函数定义了能够对设备进行的操作 在 <linux/fs.h> 中定义
④:字符设备 struct cdev
五:重要的函数:
①:分配设备号,见【三:如何分配设备号】
②:注册字符设备:
1: void cdev_init(struct cdev *cdev, struct file_operations *fops); //将字符设备与处理函数进行绑定,把 struct file_operations 注册到内核中
2: int cdev_add(struct cdev *dev, dev_t num, unsigned int count); //将 dev 添加到内核的cdev的数组之中 下标是以设备号为索引!一旦完成对 cdev的注册,就等于有了一个真实的字符设备,关键这个驱动就有了,对应的操作集合 fops;
3: void cdev_del(struct cdev *dev); //从系统中移除一个字符设备
六:简单的设备驱动程序例子:
Makefile:
ifeq ($(KERNELRELEASE), ) KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: clean $(MAKE) -C $(KERNELDIR) M=$(PWD) modules -rm -rf *.o *~ core .depend .*.cmd *.mod.c .tmp_versions Module.* Makefile.xen modules.order clean: -rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module.* Makefile.xen modules.order else MODULE_NAME=scull obj-m := $(MODULE_NAME).o $(MODULE_NAME)-objs := file_op.o my_project.o endif
my_project.c
#include <linux/init.h> #include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/kernel.h> #include <asm/uaccess.h> #include "file_op.h" uint dev_major = 60; char device_name[20] = "ngnetboy"; dev_t scull_dev; //设备号 static struct cdev scull_cdev; //字符设备 struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, }; static int __init scull_init(void){ int ret; printk("hello kernel!\n"); // make device num scull_dev = MKDEV(dev_major, 0); // register device ret = register_chrdev_region(scull_dev, 1, device_name); if (ret < 0){ printk("register chrdev region failed!\n"); ret = alloc_chrdev_region(&scull_dev, 0, 1, device_name); dev_major = MAJOR(scull_dev); } if (ret < 0){ printk("%s register failed!\n", device_name); return ret; } //scull_cdev = cdev_alloc(); cdev_init(&scull_cdev, &scull_fops); ret = cdev_add(&scull_cdev, scull_dev, 1); if (ret < 0){ printk("cdev add failed!\n"); goto chr_quit1; } return 0; chr_quit1: unregister_chrdev_region(scull_dev, 1); return ret; } static void __exit scull_exit(void){ cdev_del(&scull_cdev); unregister_chrdev_region(scull_dev, 1); printk("bye kernel!\n"); } module_init(scull_init); module_exit(scull_exit); MODULE_AUTHOR("ngnetboy"); MODULE_DESCRIPTION("a simple character utility for loading localities"); MODULE_LICENSE("GPL"); MODULE_VERSION("0.0.0.3");
file_op.c
1 #include <linux/fs.h> 2 #include <asm/uaccess.h> 3 #include <linux/init.h> 4 #include <linux/module.h> 5 #include <linux/types.h> 6 #include <linux/cdev.h> 7 #include <linux/kernel.h> 8 9 #include "file_op.h" 10 11 int scull_read(struct file *filep, char __user *buff, size_t count, loff_t *offp){ 12 char val[20] = "this is read!"; 13 int ret; 14 15 ret = copy_to_user(buff, val, count); 16 return 0; 17 } 18 int scull_write(struct file *filep, const char __user *buff, size_t count, loff_t *offp){ 19 char val[20]; 20 int ret; 21 22 printk("hello write!\n"); 23 ret = copy_from_user(val, buff, count); 24 printk("ret :%d copy from user %s\n", ret, val); 25 return 0; 26 } 27 int scull_open(struct inode *inode, struct file *filp){ 28 printk("hello open!\n"); 29 return 0; 30 } 31 int scull_release(struct inode *inode, struct file *filp){ 32 printk("hello release!\n"); 33 return 0; 34 } 35 int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){ 36 37 return 0; 38 }
使用 mknod /dev/netboy c 60 0 即可在 /dev 下创建一个可供应用程序可用的字符设备文件结点;
七:当应用程序 open 创建的字符设备时内核和驱动都做了那些事情?
1:当应用程序调用 open 打开设备文件(打开设备);
2:调用 C 库的open实现,会保存 open 的系统调用号;
3:C库的open实现调用 swi 触发一个软中断,跳转到内核态;
4:根据系统调用号,在系统调用表中找到open对应内核系统调用实现 sys_open;
5:调用 sys_open, sys_open 会做如下工作:
①:通过 inode.i_rdev 获取设备号;
②:根据设备号在内核cdev数组中找到对应的字符设备驱动;
③:然后将找到的cdev的地址赋值给 inode.i_cdev 用于缓存和别的用途;
④:创建 struct file 结构体内存 用于描述打开的设备文件信息;
⑤:根据已经获得的 cdev,从而获得其中的驱动操作集合ops();
⑥:将字符设备驱动的操作接口ops再赋值给 file->ops;
⑦:最后再调用一次 file->f_op->open = led_open;
八:四个重要的数据结构之间有什么联系?
1:struct file_operations fops ;是由驱动开发者实现,包括相应的函数实现;并且由驱动开发者把fops注册到内核中与struct cdev 绑定到一起;
2:使用 mknod 创建 /dev/netboy 时,struct inode node,便已被创建,并随着文件的销毁而被释放;
3:当驱动程序被调用,用户程序调用open打开字符设备的时候,struct file 便被创建,内核通过设备号找到对应的字符设备【struct cdev】,然后把fops赋值给 struct file;