现在的位置: 首页 > 综合 > 正文

一步一步走进字符驱动–字符驱动框架

2013年01月30日 ⁄ 综合 ⁄ 共 5930字 ⁄ 字号 评论关闭

一步一步走进字符驱动--字符驱动框架

框架介绍:

驱动开发中有一个很重要的工作,就是认识驱动框架,对于驱动,是执行在内核态的一部分,属于系统内核态的运行权限,那么在内核态的代码一定就需要严谨而且不失风骚,既然如此重要,那么内核就需要给我们更多的限制了,比如接口要怎么写,写多了我也不认识你对吧~~这就提出了一个字符驱动中很重的结构体之一: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_regionregister_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_addcdev_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");								//别名

抱歉!评论已关闭.