Linux下注册字符设备,linux2.6

字符设备驱动注册------Linux2.6驱动注册框架

关于字符设备驱动有多种方法,自己写了一个Linux2.6驱动的框架,仅仅是分享,交流使用,哪里有错误的还请批评指正,欢迎交流;

Linux2.6注册可以分为静态和动态两种方式进行注册:

1 设备号申请
1.1.静态方式申请设备号
静态方式需要自己指定驱动的设备号,设备号数量等,注册函数:

int register_chrdev_region(dev_t from, unsigned count, const char *name)
@from ---完整设备号,由主设备号major和次设备号minor合成,可以使用宏函数合成
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
@count ---指定生成设备数量,自己指定,一般来说1~255个(具体是否可以超不是很确定。。。)
@name ---设备名称
ret_value---成功返回zero,失败返回错误码。

1.2.动态方式申请设备号
动态方式需要指定开始次设备号和数量

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count)
@dev---完整设备号,需要给变量,会自动赋值
@baseminor---此设备号开始
@count---数目
ret_value---成功返回zero,失败返回错误码。

2 设备注册
2.1 结构体初始化(申请)
cdev结构体中需要填充owner项和ops项

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;//申请设备号的数量
};

2.1.1静态初始化结构体

void cdev_init(struct cdev *cdev, const struct file_operations *fops)
@cdev---结构体变量
@fops---函数指针集

2.1.2动态申请

struct cdev *cdev_alloc(void)
ret_vlaue---成功返回一个正确的地址,失败返回错误指针

2.2设备注册
函数:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
@p---struct cdev类型指针
@dev---完整设备号
@count---申请设备数量

到这里可以实现设备号的申请以及设备的注册了,但是并不会自动创建设备节点,可以使用mknod命令进行创建设备节点

mkond /dev/xxx -c major minor

但是这种方式十分的不方便,内核提供了自动创建设备节点的函数调用即可。
3.自动创建设备节点
3.1创建类
类可以在 /sys/class/ 目录下进行查看
创建class的函数:

#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})
struct class *__class_create(struct module *owner, const char *name,
			     struct lock_class_key *key)

class_creat函数只需要填充owner和name,owner还是 THIS_MODULE ,name的话可以自己随意起。
可以看出创建类的函数返回了一个struct class *类型的返回值,内核中的解释是
Returns &struct class pointer on success, or ERR_PTR() on error.
3.2创建设备节点

struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
@class---类的指针变量
@parent---父系,一般赋NULL即可
@devt---完整设备号
@drvdata---设备数据,后期可以修改,此处赋NULL即可
@fmt---格式控制符,显示文本,参考printf函数的使用
@...---参考printf函数的使用
ret_value---成功返回一个正确的地址,失败返回错误指针。

4.关于ops函数指针集合的api函数

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
};

根据自己的需要进行函数的编写,一般来说open,release,read,write等比较常用。
5.完整代码
可以通过注释CHANGE_INIT来选择静态申请注册或者动态申请注册,可以选择对major加载模块时传参确定申请方式

insmod xxx.ko major=253

字符设备驱动代码:

#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/fs.h"
#include "linux/cdev.h"
#include "linux/device.h"


/*-------------------define------------------*/
//#define CHANGE_INIT 			//选择结构体申请方式,分动态和静态

#define BASE_NUM		0
#define CDEV_COUNT		3
#define CDEV_NAME		"linux2_6_cdev"
#define CLASS_NAME		"class_chrdev"
#define DEV_NODE_NAME	"linux_2_6_"		

/*---------------func statement--------------*/
int cdev_open(struct inode *, struct file *);
int cdev_close(struct inode *, struct file *);


/*------------variable definition------------*/
static int major = 0;	//主设备号
static dev_t full_dev;	//完整设备号

#ifdef	CHANGE_INIT
static struct cdev cdev_str;	//静态结构体变量
#else
static struct cdev *p_cdev_str;	//动态申请结构体空间
#endif
static struct class *p_class_cdev;
static struct file_operations cdev_ops = {
	.owner = THIS_MODULE,
	.open = cdev_open,
	.release = cdev_close,
};

int cdev_open(struct inode *i, struct file *fs)
{
	printk("cddev_open:%s %d\n",__func__,__LINE__);
	return 0;
}

int cdev_close(struct inode *i, struct file *fs)
{
	printk("cddev_close:%s %d\n",__func__,__LINE__);
	return 0;
}

static __init int linux2_6_init(void)
{
	int i = 0;
	struct device *p_ret_dev = NULL;
	long ret_errno = -EBUSY;
	
	/*静态申请设备号*/
	if (major) {
		full_dev = MKDEV(major,BASE_NUM);	
		if (register_chrdev_region(full_dev,CDEV_COUNT,CDEV_NAME)) {
			printk("register chrdev is failed!\n");
			goto reg_err;
		}
		printk("register succeed!\n");
		
	}
	/*动态申请设备号*/
	else {
		if (alloc_chrdev_region(&full_dev,BASE_NUM,CDEV_COUNT,CDEV_NAME)) {
			printk("alloc is faile!\n");
			goto reg_err;
		}
		printk("alloc register succeed!\n");
	}
	/*打印设备号*/
	for(i=BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++) 
		printk("major = %d  minor = %d\n",MAJOR(full_dev),MINOR(full_dev+i));
	
#ifdef	CHANGE_INIT
	/*静态---结构体初始化*/
	printk("static init\n");
	cdev_init(&cdev_str,&cdev_ops);
	cdev_str.owner = THIS_MODULE;
	if (cdev_add(&cdev_str,full_dev,CDEV_COUNT)) {
		printk("cdev_add is fail!\n");
		goto cdev_err;
	}
#else
	/*动态---结构体初始化*/
	printk("alloc init\n");
	p_cdev_str = cdev_alloc();
	if (IS_ERR(p_cdev_str)) {
		ret_errno = PTR_ERR(p_cdev_str);
		printk("alloc struct cdev is failed!\n");
		goto cdev_err;
	}
	p_cdev_str->owner = THIS_MODULE;
	p_cdev_str->ops = &cdev_ops;
	if (cdev_add(p_cdev_str,full_dev,CDEV_COUNT)) {
		printk("cdev_add is fail!\n");
		goto class_err;
	}
#endif	

	/*类创建*/
	p_class_cdev = class_create(THIS_MODULE,CLASS_NAME);
	if (IS_ERR(p_class_cdev)) {
		ret_errno = PTR_ERR(p_class_cdev);
		printk("class creat is faile!\n");
		goto class_err;
	}
	/*设备节点创建*/
	for(i=BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++) {
		p_ret_dev = device_create(p_class_cdev,NULL,full_dev+i,NULL,"%s%d",DEV_NODE_NAME,i);
		if(IS_ERR(p_ret_dev)) {
			ret_errno = PTR_ERR(p_ret_dev);
			goto device_err;
		}
	}
	return 0;
device_err:
	for(i--;i>=BASE_NUM;i--) 
		device_destroy(p_class_cdev,full_dev+i);
	class_destroy(p_class_cdev);
class_err:
#ifdef	CHANGE_INIT
	cdev_del(&cdev_str);
	printk("static del\n");
#else
	cdev_del(p_cdev_str);
	printk("alloc del\n");
#endif
cdev_err:
	unregister_chrdev_region(full_dev,CDEV_COUNT);
reg_err:
	return (int)ret_errno;
}


static __exit void linux2_6_exit(void)
{
	int i = 0;
	for(i = BASE_NUM;i<BASE_NUM+CDEV_COUNT;i++) 
		device_destroy(p_class_cdev,full_dev+i);
	class_destroy(p_class_cdev);
#ifdef	CHANGE_INIT
	cdev_del(&cdev_str);
	printk("static del\n");
#else
	cdev_del(p_cdev_str);
	printk("alloc del\n");
#endif
	unregister_chrdev_region(full_dev,CDEV_COUNT);
}

module_param(major,int,0644);

module_init(linux2_6_init);
module_exit(linux2_6_exit);

MODULE_LICENSE("GPL");

测试程序代码:

#include "stdio.h"
#include <unistd.h>
 
  
int main(int argc,char *argv[])
{
	FILE *fp = NULL;
	fp = fopen(argv[1],"r");
	if(!fp) {
	printf("main:file open failed\n");
	return -1;
	}
	printf("main: file is open!\n");
	sleep(5);
	fclose(fp);
	printf("main: file is close!\n");
	return 0;
}

note:在内核开发中,如果调用返回值为指针的函数,出错判断条件不能简单地写为“if (ptr == NULL)”,正确的做法是先使用 IS_ERR() 或者 IS_ERR_OR_NULL() 判断指针返回值,然后再用 PTR_ERR() 将错误指针转为错误码,最后执行相应的出错处理。例如字符设备驱动开发中常见的device_create()函数,应该使用如下出错判断及处理:

if (IS_ERR(p_cdev_str)) {
	ret_errno = PTR_ERR(p_cdev_str);
	return ret_errno ;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值