您的位置:首页 > 其它

字符设备驱动结构与开发

2013-10-13 16:00 183 查看
字符设备驱动框架结构体

下面来分析这段代码: struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; struct list_head list; dev_t dev; unsigned int count; }; 这实际上是通过C语言的结构体来模拟面向对象的封装特性。 struct kobject kobj;这是系统内核维护的数据结构,在开发过程中我们可以不用管。 struct module *owner;通常会赋值为一个宏THIS_MODULE,表示当前的模块。 const struct file_operations *ops;表示这个结构体的方法,这是实现驱动模块接口函数的定义,正是这个结构体提供了统一的函数接口。 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, loff_t, loff_t, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); long (*fallocate)(struct file *file, int mode, loff_t offset, loff_t len); }; 这个复杂的file_operations结构体就定义了我们可能需要实现的函数指针,我们只需要定义相应类型的函数,将这些函数赋值给相应的函数指针,就能完成框架函数和自己实现函数之间的关联。 struct list_head list;这是系统用来将这个结构体加入系统维护链表的方式,我们不需要关心。 dev_t dev;表示设备在系统中的设备号,在Linux2.6.35中高12位表示主设备号,低20位表示从设备号

由于Linux内核在不断的进化更新中,这种分配主从设备号的方式可能会改变,所以Linux系统提供了一个宏函数来帮助我们实现构建设备号的工作。 MKDEV(主设备号,从设备号),这个宏函数会帮我们构建这个系统需要的设备号;

同时系统还提供两个宏函数来提取设备号的主设备号和从设备号,分别为: MAJOR(设备号),返回主设备号;MINOR(设备号),返回从设备号。 #define MINORBITS 20 //定义从设备号为20位 #define MINORMASK ((1U << MINORBITS) - 1) //定义设备掩码,主要为了屏蔽主设备号使用 #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) //向右移动MINORBITS位,将从设备号全部剔除 #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) //和设备掩码与,去掉主设备号 #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) //主设备号移动MINORBITS位和从设备号或,构建设备号。 在这个结构体中,我们只关心dev_t dev和file_operations *ops。框架介绍完了,下面介绍基本的开发步骤。在一个模块的基础上构建驱动模块,步骤如下: A、申明许可证,MODULE_LICENSE(“Dual BSD/GPL”); 定义设备cdev struct cdev dev; B、加载函数 1、申请设备号 1、静态申请 register_chrdev_region 2、动态申请 alloc_chrdev_region 2、初始化设备cdev cdev_init 3、注册设备 cdev_add C、卸载函数 1、注销设备 cdev_del 2、注销设备号 静态申请和动态申请都使用:unregister_chrdev_region,来注销 定义方法file_operations,并注册函数 struct file_operations fops = { ..open = XXXX, .release = XXXX, .write = XXXXX, .read = XXXXXXX, } D、实现功能函数 根据不同的函数指针定义,定义相应的实现函数。 根据上面编写代码 my_hello.c #include <linux/kernel.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> //尽量避免出现整数常量 #define MAJOR_NO 365 #define MINOR_NO 0 #define DEV_COUNT 1 //声明许可证 MODULE_LICENSE("Dual BSD/GPL"); //声明方法 int my_open(struct inode *, struct file *); int my_close(struct inode *, struct file *); //定义属性cdev和方法file_operations,同时注册方法 static struct cdev dev; static struct file_operations fops = { .open = my_open, .release = my_close, }; dev_t dev_no; //设备号 static int my_hello_init(void) { int ret; //返回值 //申请设备号 //静态申请 dev_no = MKDEV(MAJOR_NO,MINOR_NO); ret = register_chrdev_region(dev_no, DEV_COUNT, "my_hello_test"); //出错判断,编写驱动必须做好出错判断,否则会给应用开发人员造成无穷的麻烦 if (ret < 0) { printk("Apply for device numbers error!\n"); return ret; } dev.owner = THIS_MODULE; //初始化设备 cdev_init(&dev, &fops); //注册设备 ret = cdev_add(&dev, dev_no, DEV_COUNT); if (ret < 0) { printk("Fail to add a char device to the system\n"); return ret; } printk("The device is initialzed,waiting for invokeing.\n"); return 0; } static void my_hello_cleanup(void) { //有注册必须要注销 cdev_del(&dev); //有申请必须有释放 unregister_chrdev_region(dev_no, DEV_COUNT); printk("The device is cleanup.\n"); } //定义方法 //定义open方法,函数原型参照file_operations中的 //int (*open) (struct inode *, struct file *); int my_open(struct inode *inode, struct file *file) { printk("This is my_open.\n"); return 0; } //定义close方法,函数原型参照 //int (*release) (struct inode *, struct file *); int my_close(struct inode *inode, struct file *file) { printk("This is my_close.\n"); return 0; } module_init(my_hello_init); //使用宏函数将自己定义的初始化函数转化为init_module,主要是防止加载时函数重名造成的问题 module_exit(my_hello_cleanup); //转化为cleanup_module 编译上面代码并测试: test.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <sys/types.h> int main(void) { int fd; int ret; fd = open("/dev/my_hello",O_RDWR); if(fd < 0) { perror("open"); exit(1); } printf("打开成功\n"); sleep(1); ret = close(fd); if (ret < 0) { perror("close"); exit(1); } printf("关闭成功\n"); return 0; } Makefile: ifeq ($(KERNELRELEASE),) KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) TEST = test modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean: rm -rvf *.ko *.o *.mod.c Module* module* test: gcc $(TEST).c -o $(TEST) mknod: sudo mknod /dev/my_hello c 365 0 insmod: sudo insmod my_hello.ko .PHONY:modules clean mknod test insmod else obj-m := my_hello.o endif



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: