提示:
其实字符设备在使用之前,必须有三个基本步骤:
1)申请设备号
2)注册字符设备
3) 创建设备节点。
只有这三点满足了,那么接下来才开始对字符设备进行操作。
申请字符设备号
上一篇内容 了解了 字符设备号的申请,那么这里开始进入到字符设备的注册。 何为注册? 就是把字符设备添加到内核里面去。
文章目录
注册字符设备
注册字符设备可以分为两个步骤:
- 字符设备初始化
- 字符设备的添加
字符设备初始化 cdev_init
cdev 结构体
Linux 内核中将字符设备抽象成一个具体的数据结构 (struct cdev), 我们可以理解为字符
设备对象, cdev 记录了字符设备号、 内核对象、 文件操作 file_operations 结构体(设备的打开、
读写、 关闭等操作接口) 等信息, struct cdev 结构体定义在“内核源码/include/linux/cdev.h”
文件中(在编写驱动程序的时候要加入该文件的引用)
struct cdev {
struct kobject kobj; //内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块的对象指针. 可以理解为权限吧。
const struct file_operations *ops; //该结构描述了字符设备所能实现的方法, 是极为关键的一个结
构体.
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev; //字符设备的设备号, 由主设备号和次设备号构成. 这个就是前面总结的设备号申请部分的内容,就是设备号结构体
unsigned int count; //隶属于同一主设备号的次设备号的个数.
};
cdev_init 结构体初始化
上面对结构体进行了介绍,那么有了结构体,就可以开始进行初始化了
设备初始化所用到的函数为 cdev_init(),该函数在“内核源码/include/linux/cdev.h”
void cdev_init(struct cdev *, const struct file_operations *);
我们看看 cdev_init 方法在源码里面的实现:
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);//将整个结构体清零;
INIT_LIST_HEAD(&cdev->list);//初始化 list 成员使其指向自身;
kobject_init(&cdev->kobj, &ktype_cdev_default);//初始化 kobj 成员;
cdev->ops = fops;//初始化 ops 成员, 建立 cdev 和 file_operations 之间的连接
}
对于参数含义,如下:
初始化传入的 cdev 类型的结构体, 并与自定义的 file_operations * 类型的结构体进行链
接。
参数含义:
- cdev: 要传入的 cdev 类型结构体, 为要初始化的字符设备。
- fops: 要传入的 file_operations * 类型结构体, 关于 file_operations 结构体的相关的知识会在后面进行分析
字符设备的注册 - cdev_add
字符设备添加所用到的函数为 cdev_add(), 该函数在“内核源码/include/linux/cdev.h” 文
件中所引用
int cdev_add(struct cdev *, dev_t, unsigned);
函数原型:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
函数作用:
该函数向内核注册一个 struct cdev 结构体
参数含义:
- 第一个参数为要添加的 struct cdev 类型的结构体
- 第二个参数为申请的字符设备号
- 第三个参数为和该设备关联的设备编号的数量。
这两个参数直接赋值给 struct cdev 的 dev 成员和 count 成员。
函数返回值: 添加成功返回 0, 添加失败返回负数。
字符设备的注销 - cdev_del
函数原型:
void cdev_del(struct cdev *p)
函数作用:
该函数会向内核删除一个 struct cdev 类型结构体
字符设备注册实验
源码程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
static dev_t dev_num; // 定义 dev_t 类型(32 位大小) 的变量 dev_num,用来存放设备号
struct cdev cdev_test; // 定义 cdev 结构体类型的变量 cdev_test
struct file_operations cdev_test_ops = {
.owner = THIS_MODULE // 将 owner 字段指向本模块, 可以避免在模块的操作正在被使用时卸载该模块
}; // 定义 file_operations 结构体类型的变量 cdev_test_ops
static int __init module_cdev_init(void) // 驱动入口函数
{
int ret; // 定义 int 类型变量 ret, 进行函数返回值判断
int major, minor; // 定义 int 类型的主设备号 major 和次设备号 minor
ret = alloc_chrdev_region(&dev_num, 0, 1, "chrdev_name"); // 自动获取设备号, 设备名 chrdev_name
if (ret < 0)
{
printk("alloc_chrdev_region is error\n");
}
printk("alloc_register_region is ok\n");
major = MAJOR(dev_num); // 使用 MAJOR()函数获取主设备号
minor = MINOR(dev_num); // 使用 MINOR()函数获取次设备号
printk("major is %d\n", major);
printk("minor is %d\n", minor);
cdev_init(&cdev_test, &cdev_test_ops); // 使用 cdev_init()函数初始化 cdev_test 结构体, 并链接到cdev_test_ops 结构体
cdev_test.owner = THIS_MODULE; // 将 owner 字段指向本模块, 可以避免在模块的操作正在被使用时卸载该模块
ret = cdev_add(&cdev_test, dev_num, 1); // 使用 cdev_add()函数进行字符设备的添加
if (ret < 0)
{
printk("cdev_add is error\n");
}
printk("cdev_add is ok\n");
return 0;
}
static void __exit module_cdev_exit(void) // 驱动出口函数
{
cdev_del(&cdev_test); // 使用 cdev_del()函数进行字符设备的删除
unregister_chrdev_region(dev_num, 1); // 释放字符驱动设备号
printk("module exit \n");
}
module_init(module_cdev_init); // 注册入口函数
module_exit(module_cdev_exit); // 注册出口函数
MODULE_LICENSE("GPL v2"); // 同意 GPL 开源协议
MODULE_AUTHOR("wang fang chen "); // 作者信息
源码逐字分析:
- dev_t 设备号的结构体定义,如前面的笔记分析
- cdev 字符设备结构体定义
- file_operations 文件操作结构体,对文件的打开、关闭、读、写 指向都是在这个结构体里面定义的
- alloc_chrdev_region 动态申请设备号,前面文章已经分析了的撒
- MAJOR 通过这个api ,在动态申请到设备号后 获取主设备号和次设备号
- cdev_init 初始化字符设备,传入参数为:字符设备结构体地址、文件操作结构体地址
- cdev_test.owner 赋值一次,指向THIS_MODULE ,规避模块的操作正在被使用时卸载该模块
- 字符设备添加到内核
- cdev_del 卸载注册的字符设备
- unregister_chrdev_region 卸载动态申请的设备号
Makefile 编译文件
#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += cdev.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
测试程序
insmod cdev.ko 加载驱动
cat /proc/devices 查看设备号
通过日志打印和设备号创建成功来说明 设备是否注册成功。

总结
- 字符设备号申请-字符设备注册 基本知识进一步熟悉
- 字符设备注册几个函数要掌握 cdev_init - cdev_add - cdev_del
- 涉及到的结构体 需要理解,后面才能举一反三,接下来还有其它基本知识需要掌握
289

被折叠的 条评论
为什么被折叠?



