分类:
2012-04-11 14:40:18
原文地址:Linux字符驱动总结 作者:xgr07happy
(dev_t)-->主设备号、次设备号 |
MAJOR(dev_t dev) |
主设备号、次设备号-->(dev_t) |
MKDEV(int major,int minor) |
在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用。并且应当在不再使用它们时释放它。
int register_chrdev_region(dev_t first, unsigned int count,char *name); //指定设备编号 |
安排主编号最好的方式, 我们认为, 是缺省使用动态分配, 而留给自己在加载时或者甚至在编译时指定主编号的选项权.
以下是在scull.c中用来获取主设备好的代码:
if (scull_major) { |
动态分配的缺点是你无法提前创建设备节点, 因为分配给你的模块的主编号会变化. 对于驱动的正常使用, 这不是问题, 因为一旦编号分配了, 你可从 /proc/devices 中读取它.
2、一些重要数据结构
大部分的基础性的驱动操作包括 4 个重要的内核数据结构cdev,file_operations, file 和 inode.
file_operations结构定义在
struct file_operations scull_fops = { |
struct file,定义于
struct inode ,是在内核内部用来表示文件的。因此, 它和代表打开文件描述符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多文件结构, 但是它们都指向一个单个 inode 结构.
struct cdev ,Linux2.6内核与2.4内核不同2.6内核采用了。cdev结构体来描述管理字符设备
struct cdev *my_cdev = cdev_alloc();
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev.owner = THIS_MODULE;
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):
/* |
open 方法提供给驱动来做任何的初始化来准备后续的操作. 在大部分驱动中, open 应当进行下面的工作:
●检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误)
●如果它第一次打开, 初始化设备
●如果需要, 更新 f_op 指针.
●分配并填充要放进 filp->private_data 的任何数据结构
但是, 事情的第一步常常是确定打开哪个设备. 记住 open 方法的原型是:
int (*open)(struct inode *inode, struct file *filp); |
inode 参数有我们需要的信息,以它的 i_cdev 成员的形式, 里面包含我们之前建立的 cdev 结构.
container_of(pointer, container_type, container_field); |
这个宏使用一个指向 container_field 类型的成员的指针, 它在一个 container_type 类型的结构中, 并且返回一个指针指向包含结构. 在 scull_open, 这个宏用来找到适当的设备结构:
struct scull_dev *dev; /* device information */ |
识别打开的设备的另外的方法是查看存储在 inode 结构的次编号. 如果你使用 register_chrdev 注册你的设备, 你必须使用这个技术. 确认使用 iminor 从 inode 结构中获取次编号, 并且确定它对应一个你的驱动真正准备好处理的设备.
int scull_open(struct inode *inode, struct file *filp) |
release 方法
●释放 open 分配在 filp->private_data 中的任何东西
●在最后的 close 关闭设备
scull 的基本形式没有硬件去关闭, 因此需要的代码是最少的:
int scull_release(struct inode *inode, struct file *filp) |
以下是scull模型的结构体:
/* |
scull驱动程序引入了两个Linux内核中用于内存管理的核心函数,它们的定义都在
void *kmalloc(size_t size, int flags); |
以下是scull模块中的一个释放整个数据区的函数(类似清零),将在scull以写方式打开和scull_cleanup_module中被调用:
int scull_trim(struct scull_dev *dev) |
3、完整驱动模版
//=======================字符设备驱动模板开始 ===========================//
#define CHAR_DEV_DEVICE_NAME "char_dev" // 设备名
struct class *char_dev _class; // class结构用于自动创建设备结点
static int major = 0;/* 0表示动态分配主设备号,可以设置成未被系统分配的具体的数字。*/
static struct cdev char_dev_devs;// 定义一个cdev结构
// 设备建立子函数,被char_dev_init函数调用
static void char_dev_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)
{
int err, devno = MKDEV(major, minor);
cdev_init(dev, fops);
dev->owner = THIS_MODULE;
dev->ops = fops;
err = cdev_add(dev, devno, 1);
if( err )
{
printk(KERN_NOTICE "Error %d adding char_dev %d\n", err, minor);
}
}
// file_operations 结构体设置,该设备的所有对外接口在这里明确,此处只写出了几常用的
static struct file_operations char_dev_fops =
{
.owner = THIS_MODULE,
.open = char_dev_open, // 打开设备
.release = char_dev_release, // 关闭设备
.read = char_dev_read, // 实现设备读功能
.write = char_dev_write, // 实现设备写功能
.ioctl = char_dev_ioctl, //实现设备控制功能
};
// 进行初始化设置,打开设备,对应应用空间的open 系统调用
int char_dev_open (struct inode *inode, struct file *filp)
{
... // 这里可以进行一些初始化
return 0;
}
// 释放设备,关闭设备,对应应用空间的close 系统调用
static int char_dev_release (struct inode *node, struct file *file)
{
... // 这里可以进行一些资源的释放
return 0;
}
// 实现读功能,读设备,对应应用空间的read 系统调用
ssize_t char_dev_read (struct file *file,char __user *buff,size_t count,loff_t *offp)
{
...
return 0;
}
// 实现写功能,写设备,对应应用空间的write 系统调用
ssize_t char_dev_write(struct file *file,const char __user *buff,size_t count,loff_t *offp)
{
...
return 0;
}
// 实现主要控制功能,控制设备,对应应用空间的ioctl 系统调用
static int char_dev _ioctl(struct inode *inode,struct file *file,unsigned int cmd,unsigned long arg)
{
...
return 0;
}
// 设备初始化
static int char_dev_init(void)
{
int result;
dev_t dev = MKDEV(major, 0);
if( major )
{
// 给定设备号注册
result = register_chrdev_region(dev, 1, CHAR_DEV_DEVICE_NAME);
}
else
{
// 动态分配设备号
result = alloc_chrdev_region(&dev, 0, 1, CHAR_DEV_DEVICE_NAME);
major = MAJOR(dev);
}
char_dev_setup_cdev(&char_dev_devs, 0, &char_dev_fops);
printk("The major of the char_dev device is %d\n", major);
//==== 有中断的可以在此注册中断:request_irq,并要实现中断服务程序 ===//
// 创建设备结点
char_dev _class = class_create(THIS_MODULE,"ad_class");
if (IS_ERR(char_dev _class))
{
printk("Err: failed in creating class.\n");
return 0;
}
device_create(char_dev_class, NULL, dev, NULL, "char_dev");
return 0;
}
// 设备注销
static void char_dev_cleanup(void)
{
device_destroy(adc_class,dev);
class_destroy(adc_class);
cdev_del(&char_dev_devs);//字符设备的注销*/
unregister_chrdev_region(MKDEV(major, 0), 1);//设备号的注销
//======== 有中断的可以在此注销中断:free_irq ======//
printk("char_dev device uninstalled\n");
}
module_init(char_dev_init);//模块初始化接口
module_exit(char_dev_cleanup);//模块注销接口
// 以下两句不能省略,否则编译不通过
MODULE_AUTHOR("");
MODULE_LICENSE("GPL");
//==================== 字符设备驱动模板结束 ========================//
用Makefile模板编译,Makefile如下:
//======================= Makefile开始 ===========================//
ifeq ($(KERNELRELEASE),)
#KERNELDIR ?= /your/target/source/directory/
KERNELDIR ?= /opt/kernal/linux-2.6.32.10/
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
//========================= Makefile结束 =============================//
make编译后生成char_dev.ko,控制台输入加载和卸载命令,还可以使用lsmod查看已经加载的模块信息。
insmod char_dev.ko #加载驱动,会执行module_init中的语句
rmmod char_dev #卸载驱动,会执行module_exit中的语句