Linux驱动入门篇:基本的字符设备模块

Posted salvare

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux驱动入门篇:基本的字符设备模块相关的知识,希望对你有一定的参考价值。

  上一节中介绍了设备号的申请和释放,这一节开始了解字符设备的相关操作。

 

  首先定位到<linux/cdev.h>文件,查看内核提供给字符设备的接口。

cdev结构

struct cdev {
	struct kobject kobj;	//内嵌的kobject对象
	struct module *owner;	//此结构所属模块
	const struct file_operations *ops;	//文件操作结构
	struct list_head list;	//通用双向链表
	dev_t dev;		//设备号
	unsigned int count;
};

  owner成员一般初始化为 THIS_MODULE,THIS_MODULE 是一个指向当前模块的 struct module结构指针,也就是指向当前模块。

字符设备的函数接口

void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);

  以上是<linux/cdev.h>提供的部分函数接口。接下来一个一个地解决掉它们。

 

  首先是 cdev_init 函数,先看源码。

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 结构。参数 cdev 即为要进行初始化的结构,参数 fops则是此设备的 file_operations(具体作用留到后面)。

 

  可以看到 cdev_init 的主要作用就是初始化 cdev 结构,把 fops 指针连接到 cdev结构。做好此设备被添加到系统的准备。

 

  接下来看 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 结构申请了一块内存,并返回它的地址,失败时返回NULL。

 

  下一个函数是 cdev_add,它将一个字符设备添加到系统。

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;
}

  参数 p 是设备的 struct cdev指针,dev 是首个设备号,count 是连续的次设备号的数量。函数通过把指针 p 添加到系统,来描述设备的添加,使设备立即生效。若添加失败,则返回负数的错误码。

 

  最后一个函数是 cdev_del,它的功能是从系统移除一个 cdev 结构。

void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}

  此函数从系统中移除指针 p,有可能会释放 p 指向的结构。

字符设备的注册和注销

  还记得上一节中未实现的 mycdev_setup 函数和 mycdev_del 函数吗?现在我们已经可以实现它们啦。

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
 
dev_t devno;    //设备号
 
static struct class *my_class;
static struct cdev my_cdev;
static struct file_operations my_fops;


static int __init mycdev_init(void)
{
        int ret;
        ret = alloc_chrdev_region(&devno, 0, 1, "mycdev");
        if(ret != 0){
                printk(KERN_NOTEICE "Alloc device number failed.");
                return -1;
        }

	//开始实现cdev_setup()
	cdev_init(&my_cdev, &my_fops);
	my_cdev.owner = THIS_MODULE;

	ret = cdev_add(&my_cdev, devno, 1);
	if(ret < 0){
		printk(KERN_NOTEICE "Add cdev failed.");
                return -2;
	}
	//cdev_setup()结束

        my_class = class_create(THIS_MODULE, "mycdev");
        device_create(my_class, NULL, devno, NULL, "mycdev");
 
        return 0;
}
 
static void mycdev_exit(void)
{
	//mycdev_del()实现
	cdev_del(&my_cdev);
	//mycdev_del()结束
 
        device_destroy(my_class, devno);
        class_destroy(my_class);
 
        unregister_chrdev_region(devno);
}
 
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE(“Dual BSD/GPL”);

  现在我们就完成了一个基本的字符设备模块,它实现了设备号的申请与注销,设备文件的创建与销毁以及字符设备的初始化、注册与注销。

 

  但是,这还不够。我们的目的是使用字符设备,至少需要读或者写此设备。如何让字符设备模块提供读写功能呢?这就和 struct file_operations 结构有关了,留待下一节详细叙述。

 

以上是关于Linux驱动入门篇:基本的字符设备模块的主要内容,如果未能解决你的问题,请参考以下文章

一张图掌握 Linux 字符设备驱动架构!建议收藏

一张图掌握 Linux 字符设备驱动架构!建议收藏

Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)

Linux驱动入门-最简单字符设备驱动(基于pc ubuntu)

深入浅出Linux内核模块篇 15.2Linux 设备驱动模型的初始化

linux设备驱动第二篇:构造和运行模块