基本介绍
字符设备:指向以字节为单位进行读写的设备,不能随机读取设备中指向数据。字符设备是面向流的设备,例如:鼠标,键盘,串口,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函数即可,这里暂不过多介绍。
总结
本文主要介绍了,字符设备驱动的编写框架,具体细节实现临时暂时没有填充,从而让读者熟悉字符设备驱动的编写。