Linux设备驱动开发:字符设备驱动的注册与注销

1、使用register_chrdev注册驱动程序

//内核中register_chrdev实现
static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops)
{
	return __register_chrdev(major, 0, 256, name, fops);
}

 解析一下各个参数

major:主设备号,如果写0可以动态申请

name:申请设备的设备名

fops:file_operations结构体

返回值:返回申请到的设备号

//实际使用示范
#define NAME "mycdev"
major = register_chrdev(0, NAME, &fops)

2、解析__register_chrdev

int __register_chrdev(unsigned int major, unsigned int baseminor,
		      unsigned int count, const char *name,
		      const struct file_operations *fops)

各个参数

major:主设备号

baseminor:次设备号的起始值

count:申请的个数

name:设备的名字

fops:file_operations结构体

返回值:char_device_struct结构体

3、为什么不用register_chrdevr

根据register_chrdev我们可以看到,如果用的是registe_chrdev的形式,会一次性申请256个次设备号,这对于项目开发非常的不友好,所以在实际项目开发中,一般不会用到register_chrdev,根据__register_chrdev,我们可以自己写出注册的方法。

所以,接下来我们来根据__register_chrdev写注册的方法

4、分配对象:cdev_alloc()

struct cdev *cdev;
cdev = cdev_alloc();

//下面为cdev_alloc的实现
struct cdev *cdev_alloc(void)
{
    //使用结构体前为结构体分配一块内存空间
	struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
	if (p) {
		INIT_LIST_HEAD(&p->list);
        //kobj是啥,我也不懂,先放在这里,后面再查
		kobject_init(&p->kobj, &ktype_cdev_dynamic);
	}
	return p;
}

//下面为INIT_LIST_HEAD的实现
static inline void INIT_LIST_HEAD(struct list_head *list)
{
    //实际上就是让链表全部指向自己,意思就是为了创建这个链表
	list->next = list;
	list->prev = list;
}

5、初始化对象:cdev_init()

const struct file_operations fops = {
	.open = mycdev_open,
	.read = mycdev_read,
	.write = mycdev_write,
	.release = mycdev_release,	
};
cdev_init(cdev, &fops);


//下面为cdev_init函数
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
    //初始化内存空间,清空结构体
	memset(cdev, 0, sizeof *cdev);

    //在前面创建的时候已经初始化过了,不知道为啥这里还要再来一遍
	INIT_LIST_HEAD(&cdev->list);

    //这里也是同理,不知道为啥还要再来一遍
    //那么应该可以自己把两个结合一下,把上面先创建cdev拿下来,然后再调用这个函数
	kobject_init(&cdev->kobj, &ktype_cdev_default);

    //初始化file_operations结构体,就是填入进去
	cdev->ops = fops;
}

6、申请设备号:register_chrdev_region()或alloc_chrdev_region()

在此先说明两者的区别,一种是静态的申请(直接写入major的值),一种是动态的申请(major是0,自动分配主设备号),两者看似有区别,但是进入到内部一看,都是调用了__register_chrdev_region,所以就直接看__register_chrdev_region吧

//先解释一下参数
//major:主设备号,写0动态申请
//baseminor:次设备号的起始值
//minorct:个数
//name:设备的名字
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
//申请一块内存
struct char_device_struct *cd;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

//然后就是一大段的初始化,例如
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));

//这个地方有点意思,放在下个代码块中解释一下
i = major_to_index(major);
//这是上面代码中的一部分
i = major_to_index(major);

//定义这个大小为255,意味着char_device_struct最长为256
//如果要放下比256更大的设备号说明要进行哈希查找,这就是名字中带HASH的原因
#define BLKDEV_MAJOR_HASH_SIZE	255

//这个是一个单向链表
static struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int baseminor;
	int minorct;
	char name[64];
	struct cdev *cdev;		/* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

//此处就是为哈希查找的原理
static inline int major_to_index(unsigned major)
{
	return major % BLKDEV_MAJOR_HASH_SIZE;
}

7、字符设备注册 cdev_add()

ret = cdev_add(cdev, MKDEV(major, minor), count);

//下面为cdev_add函数,函数功能就是注册字符设备驱动
//下面有关kobj的东西我都没有太留意,后面再说吧
//参数说明
//p:cdev的结构体指针
//dev:申请到的设备号
//count:设备的个数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;
    
	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}

8、动态创建设备的逻辑类:class_create()

struct class *cls;
#define NAME "mycdev"
cls = class_create(THIS_MODULE, NAME);
if (IS_ERR(cls)) {
	printk("class create error\n");
	return PTR_ERR(cls);
}	

9、创建设备:device_create()

struct device *dev;
for(i=0; i

10、移除驱动

//设备销毁
int i = 0;
for(i=0;i

你可能感兴趣的:(Linux驱动开发,驱动开发,linux)