基本介绍
字符设备:指向以字节为单位进行读写的设备,不能随机读取设备中指向数据。字符设备是面向流的设备,例如:鼠标,键盘,串口,LED等。
字符设备驱动,用户空间和应用程序三者之间的关系:字符设备是3大类设备(字符设备,块设备,网络设备)中较简单的一类设备。如图所示,驱动中完成主要工作如下:
- 添加和删除设备:使用struct cdev结构体来抽象一个字符设备;
- 申请和释放设备号:通过dev_t类型的设备号(主设备号,次设备号)确定字符设备的唯一性;
- 抽象文件操作结构体:填充文件操作结构体,实现基本的读写控制操作,为系统调用系统VFS接口,应用程序只需要在用户空间,进行识读操作即可。
驱动模型
基本流程
- 申请设备号
- 分配cdev
- 创建class和device(也可以不要这一步)
- files_operation具体实现
alloc_chrdev_region() ... cdev_alloc() ... cdev_init() ... cdev_add() ... class_create() ... device_create()
相关数据结构介绍
cdev介绍
- 数据结构
//<include/linux/cdev.h> struct cdev { struct kobject kobj; /*内嵌内核对象*/ struct module *owner; /*字符设备所在内核模块所有者对象指针,一般时THIS_MODULE*/ const struct file_operations *ops;/*字符设备读写操作集*/ struct list_head list; dev_t dev; /*字符设备设备号*/ unsigned int count; /*同一主设备号的次设备号个数*/ ... };
- 相关操作
/* * 动态申请cdev设备对象 */ struct cdev *cdev_alloc(void);
/* * 初始化cdev成员,并建立cdev和file_operation之间的关联 * strcut cdev - 被初始化的cdev对象 * fops - 字符设备操作方法集 */ void cdev_init(struct cdev *p, const struct file_opration *fops);
/* * 注册cdev设备对象 * strcut cdev - 被初始化的cdev对象 * dev_t dev - 设备的第一个设备号 * unsigned - 这个设备连续的次设备数量 */ int cdev_add(struct cdev *, dev_t, unsigned);
/* * 将cdev对象从系统中注销 * strcut cdev - 被初始化的cdev对象 */ void cdev_del(struct cdev *));
设备号介绍
一个字符设备或块设备都有一个主设备号和一个次设备号。那种设备,用来区分同类型的设备。
- 数据结构
/* * 在32位系统中dev_t是4字节,高12位表示主设备号,低20位表示次设备号 */ typedef u_long dev_t;
MAJOR:从设备号中提取主设备号;
MINOR:从设备号中提取次设备号;
MKDEV:将主,次设备号拼凑为设备号
- 相关操作
/* * 静态申请设备号 * dev_t - 要申请设备号(起始) * unsigned - 要申请设备号数量 * const char * - 设备名 */ int register_chrdev_region(dev_t , unsigned , const char *);
/* * 动态申请设备号 * dev_t - 要申请设备号(起始) * unsigned - 起始次设备号 * unsigned - 要分配设备号数量 * const char * - 设备名 */ int alloc_chrdev_region(dev_t , unsigned ,unsigned, const char *);
/* * 释放设备号 * dev_t - 要释放设备号(起始) * unsigned - 要释放设备号数量 */ void unregister_chrdev_region(dev_t , unsigned );
file_operation
struct file_operations { struct module *owner; ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);unsigned long, loff_t); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); ... };
owner:模块拥有者,一般为THIS_MODULE
read:从设备读
write:从设备写
open:打开设备
release:关闭设备
ioctl:其他控制
简单使用
驱动代码
hello_world.c
#include <linux/vmalloc.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/major.h> #include <linux/device.h> #define DRIVER_NAME "hello_world" typedef struct _device_info { int major; dev_t dev; struct cdev *cdev; struct device *devices; struct class *class; } device_info_t; static device_info_t *context_device = NULL;
基本操作
ssize_t hello_world_read(struct file *file, char __user *buf, size_t size, loff_t *off) { printk("read data to hello world\n"); return 0; } ssize_t hello_world_write(struct file *file, char __user *buf, size_t size, loff_t *off) { printk("write data to hello world\n"); return 0; } int hello_world_open(struct inode *inode, struct file *file) { printk("open hello world driver successful!\n"); return 0; } int hello_world_release(struct inode *inode, struct file *file) { printk(" close hello world \n"); return 0; }
构建和销毁字符设备
/* fops*/ static struct file_operations hello_world_fops = { .owner = THIS_MODULE, .read = hello_world_read, .write = hello_world_write, .mmap = NULL, /* reserved for future use */ .open = hello_world_open, .release = hello_world_release, }; /* 创建字符设备 */ static inline int create_hello_world_device(void) { int ret; device_info_t *device = context_device; ret = alloc_chrdev_region(&device->dev, 0, 1, DRIVER_NAME); if (ret) { printk("ERROR: alloc_chrdev_region failed!\n"); goto err_chrdev; } device->major = MAJOR(device->dev); device->cdev = cdev_alloc(); if (!device->cdev) { printk("ERROR: cdev_alloc failed!\n"); goto err_cdev; } device->cdev->ops = &hello_world_fops; device->cdev->owner = THIS_MODULE; cdev_init(device->cdev, &hello_world_fops); ret = cdev_add(device->cdev, device->dev, 1); if (ret) { printk("ERROR: cdev_add failed!\n"); goto err_cdev_add; } device->class = class_create(THIS_MODULE, DRIVER_NAME); if (!device->class) { printk("ERROR: class_create failed!\n"); goto err_devclass; } device->devices = device_create(device->class, NULL, device->dev, NULL, DRIVER_NAME); if (!device->devices) { printk("ERROR: device_create failed!\n"); goto error_dev; } return 0; error_dev: class_destroy(device->class); err_devclass: cdev_del(device->cdev); err_cdev_add: unregister_chrdev(device->major, DRIVER_NAME); err_cdev: unregister_chrdev_region(device->dev, 1); err_chrdev: return -1; } /* 注销字符设备*/ static inline int destory_hello_world_device(void) { if (context_device) { device_info_t *device = context_device; device_destroy(device->class, device->dev); class_destroy(device->class); cdev_del(device->cdev); unregister_chrdev(device->major, DRIVER_NAME); unregister_chrdev_region(device->dev, 1); } return 0; }
驱动加载卸载
static int __init hello_world_init(void) { int ret; /* alloc context_device */ context_device = vzalloc(sizeof(device_info_t)); if (!context_device) { printk("ERROR: alloc memory for context_device failed!\n"); goto error_context; } ret = create_hello_world_device(); if (ret) { printk("ERROR: create hello_world device error\n"); goto error_create; } printk("hello world driver Register ok!\n"); return 0; error_context: error_create: return -1; } static void __exit hello_world_exit(void) { destory_hello_world_device(); } module_init(hello_world_init); module_exit(hello_world_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("hello world driver test");
Makefile
CROSS_COMPILE ?= your compile KERNEL_DIR = you kernel dir KDIR := ${KERNEL_DIR} MODULE_NAME := hello_world_drv all: modules .PHONY: modules clean $(MODULE_NAME)-objs += hello_world.o obj-m += $(MODULE_NAME).o modules: @$(MAKE) -C $(KDIR) M=$(shell pwd) $@ clean: @rm -rf *.o *~ .depend .*.cmd *.mod.c .tmp_versions *.ko *.symvers modules.order
测试
- insmod hello_world_drv.ko
- ls / dev / hello world
- 应用程序测试:应用层通过open函数打开/ dev / helle_world,然后通过调用read / write函数即可,这里暂不过多介绍。
总结
本文主要介绍了,字符设备驱动的编写框架,具体细节实现临时暂时没有填充,从而让读者熟悉字符设备驱动的编写。