一步一步走进字符驱动--字符驱动框架
框架介绍:
驱动开发中有一个很重要的工作,就是认识驱动框架,对于驱动,是执行在内核态的一部分,属于系统内核态的运行权限,那么在内核态的代码一定就需要严谨而且不失风骚,既然如此重要,那么内核就需要给我们更多的限制了,比如接口要怎么写,写多了我也不认识你对吧~~这就提出了一个字符驱动中很重的结构体之一:struct file_operations device_fops,有人说这个结构体是感动内核的十大结构体之一(中央电视台有个感动中国的十大杰出人物,改变出来的,感动内核结构体);其实这个形容是很贴切的.因为这个结构体确实很重要.下面就详细介绍下该结构体的作用,以及组成:
申请设备号:
驱动,首先必须得有设备号.设备号由主设备号和从设备号组成,
MKDEV(int major,int minor) 宏来组成设备号 MAJOR(dev_t dev) 获取主设备号 MINOR(dev_t dev) 获取从设备号 #define MAJOR(dev)((unsigned int) ((dev) >> MINORBITS)) #define MINOR(dev)((unsigned int) ((dev) & MINORMASK)) #define MKDEV(ma,mi)(((ma) << MINORBITS) | (mi))
设备号的申请函数:
extern int alloc_chrdev_region(dev_t *pdev, unsigned baseminor, unsigned count, const char *name); extern int register_chrdev_region(dev_t dev, unsigned count, const char *name); extern void unregister_chrdev_region(dev_t dev, unsigned count);
register_chrdev_region用于已知设备号的情况下.alloc_chrdev_region 函数用于自动申请设备号,函数成功会把设备号放在dev_t*这个参数中,alloc_chrdev_region比register_chrdev_region更方便些.
unregister_chrdev_region 函数用于注销已申请的设备号.
注册字符驱动:
当你有了设备号之后,就需要往内核中添加我们的字符驱动了.那么字符驱动的表示是怎么样的呢.下面就来介绍下这个结构体;
struct cdev { struct kobject kobj; /* 内嵌的kobject对象 */ struct module *owner; /* 模块所属 */ const struct file_operations *ops; /* 文件操作结构体(感动内核十大结构体之一) */ struct list_head list; dev_t dev; /* 设备号 */ unsigned int count; };
下面就对介绍下对字符驱动注册的相关函数:
void cdev_init(struct cdev *, const struct file_operations *); struct cdev *cdev_alloc(void); void cdev_put(struct cdev *p); int cdev_add(struct cdev *, dev_t, unsigned); void cdev_del(struct cdev *); cdev_init初始化cdev结构体并与struct file_operations device建立接连.相关代码如下. void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
cdev_alloc用于申请空间,相关代码如下:
struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
cdev_add和cdev_del分别是向内核添加和删除cdev,cdev_add一般放在模块加载函数内,当然cdev_del 一般也放在模块卸载函数内.详情一会参考下面的字符驱动框架.
创建设备文件:
Linux 中有这样一句话,一切皆文件.那么驱动也理应是文件的形式存在了.在/dev/目录下就是列出了当前系统存在的设备文件了.有人要问了,insmod加载之后不会自动产生吗?答案是的,除非我们添加创建设备文件的代码,所以这里就引出了如何创建设备文件.
设备文件的创建分2个部分.第一部分就创建设备类.然后在设备类的基础上创建设备文件.
structure class * class_create(struct module *owner, const char *name);
该函数是创建设备类,设备类可以在/sys/class看到
extern struct device *device_create(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, ...);
该函数创建一个设备文件位于/dev/目录下,注:这个设备文件创建并不是是内核内完成,只是通知udev文件系统响应,由用户态来创建的设备文件,设备类存放于sysfs下面,一旦创建好了这个类,再调用device_create(…)函数来在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create(…)函数,去/sysfs下寻找对应的类从而创建设备节点.
相关代码:
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/ioctl.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <asm/io.h> #include <asm/system.h> #include <asm/uaccess.h> #include <linux/device.h> #define _DEBUG_ #define DEVICE_NUM 1 //需要申请的设备数量 //结构体声明,字符设备结构体 struct device_dev{ struct cdev cdev; }; //全局变量 //默认主设备号,0为自动分配,否则外部设置 int g_major = 0; struct class *g_my_class; //设备结构指针 static struct device_dev *g_devp; //设置字符设备对应的fops static struct file_operations device_fops = { .owner = THIS_MODULE, }; /* 名称: device_setup_cdev 功能: 字符设备注册 参数: 输入: 1:struct cdev *_pcdev, //cdev指针 2:struct file_operations *_pdevice_fops, //文件操作结构体指针 3:int _major, //设备主设备号 4:int _index //设备从设备号 返回值: return 0; //注册成功 其他 //注册失败 */ static int device_setup_cdev(struct cdev *_pcdev,struct file_operations *_pdevice_fops, int _major,int _index) { int err; dev_t devno = MKDEV(_major,_index); cdev_init(_pcdev,_pdevice_fops); //初始化字符设备,并跟fops建立链接 _pcdev->owner = THIS_MODULE; err = cdev_add(_pcdev,devno,1); //向内核增加当前字符设备 if (err) { return err; } return 0; } /* 名称: register_region 功能: 申请设备号, *_pmajor = 0; 则自动分配 参数: 输出: 1:int *_pmajor, //返回主设备号 输入: 2:const int _dev_size //需要注册的设备数量 3:const char *_p_dev_name, //注册的设备名字 返回值: return 0; //注册成功 其他 //注册失败 */ static int register_region(int *_pmajor,const int _dev_size,const char *_p_dev_name) { int result = 0; dev_t devno; if (*_pmajor == 0) { result = alloc_chrdev_region(&devno,0,_dev_size,_p_dev_name); *_pmajor = MAJOR(devno); } else { devno = MKDEV(*_pmajor,0); result = register_chrdev_region(devno,_dev_size,_p_dev_name); } return result; } /* 名称: device_class 功能: 创建设备文件 参数: 输入: 1:int _major, //主设备号 3:const char *_szclass_name, //类名 4:const char *_szdevice_name //设备名 输出: 2:struct class **_p_class, //返回类名结构体地址 返回值: return 0; //注册成功 return -1 //注册失败 */ static int device_class(int _major,struct class **_p_class,const \ char *_szclass_name,const char *_szdevice_name) { //创建设备文件 *_p_class = class_create(THIS_MODULE, _szclass_name); if(IS_ERR(*_p_class)) { return -1; } device_create(*_p_class, NULL, MKDEV(_major, 0),0,_szdevice_name); return 0; } static int __init initialization_function(void) { /*初始化代码*/ int result; //添加设备号 if(register_region(&g_major,DEVICE_NUM,"interrupt")) { result = -1; goto fail_register; } //分配设备结构体空间 g_devp = kmalloc(sizeof(struct device_dev),GFP_KERNEL); if (!g_devp) { result = -ENOMEM; goto fail_malloc; } //注册字符设备 if (device_setup_cdev(&(g_devp->cdev),&device_fops,g_major,0)) { result = -1; goto fail_setup_cdev; } //生成设备文件 if(device_class(g_major,&g_my_class,"chass_inputer","device_interrupt")){ result = -1; goto fail_class; } printk(KERN_INFO "initialization_function Sucessful %d\n",g_major); return 0; fail_class: cdev_del(&(g_devp->cdev)); fail_setup_cdev: kfree(g_devp); //释放已申请的内存 fail_malloc: unregister_chrdev_region(MKDEV(g_major,0),DEVICE_NUM); fail_register: #ifdef _DEBUG_ printk(KERN_INFO "fail_malloc\n"); #endif return result; } static void __exit cleanup_function(void) { /*释放代码*/ device_destroy(g_my_class, MKDEV(g_major, 0)); //delete device node under /dev class_destroy(g_my_class); //delete class created by us cdev_del(&(g_devp->cdev)); //删除cdev结构 if(!g_devp) kfree(g_devp); //释放已申请的内存 unregister_chrdev_region(MKDEV(g_major,0),DEVICE_NUM); //注销设备区域 printk(KERN_INFO "Sucessful cleanup_function\n"); } //注册模块加载卸载函数 module_init(initialization_function); //指定模块加载函数 module_exit(cleanup_function); //指定模块卸载函数 //模块参数 module_param(g_major,int,S_IRUGO); //导出参数,为了人工设定主设备号 //模块信息及许可证 MODULE_AUTHOR("LvApp"); //作者 MODULE_LICENSE("Dual BSD/GPL"); //许可证 MODULE_DESCRIPTION("A simple module"); //描述 MODULE_ALIAS("LvApp's device"); //别名