驱动-注册字符设备

提示:
其实字符设备在使用之前,必须有三个基本步骤:
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
  • 涉及到的结构体 需要理解,后面才能举一反三,接下来还有其它基本知识需要掌握
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值