驱动程序(注册字符设备)

注册字符设备

注册字符设备可以分为初始化字符设备和注册字符设备俩个步骤。

初始化字符设备

Linux 内核中使用 struct cdev 结构体表示字符设备, cdev 结构体中记录了设备号、内核对象、文件操作 file_operations 结构体(设备的打开、读写、关闭等操作接口)等信息, struct cdev 结构体定义在内核源码 /include/linux/cdev.h 文件中( 在编写驱动程序的时候要加入该文件的引用 ),如代码下 所示。

cpp 复制代码
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() 函数 , 该函数原型同样在内核源码 /include/linux/cdev.h 文件中,如代码下所示。

cpp 复制代码
void cdev_init(struct cdev *, const struct file_operations *);

cdev_init() 函数详细介绍如下:

函数作用:
初始化传入的 cdev 类型的结构体,并与自定义的 file_operations * 类型的结构体进行链
接。
参数含义:
cdev: 要传入的 cdev 类型结构体,为要初始化的字符设备。
fops:要传入的 file_operations * 类型结构体。
函数返回值: 无返回值。
该函数定义在内核源码 /include/fs/char_dev.c 文件中,核心作用是建立 cdev 结构体和 file_operations 结构体之间的连接。如代码下 所示。

cpp 复制代码
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_add() ,该函数在内核源码 /include/linux/cdev.h 文件中。
如代码下 所示。

cpp 复制代码
int cdev_add(struct cdev *, dev_t, unsigned);

cdev_add() 函数 详细介绍如下:

函数原型:
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
函数作用:
该函数向内核注册一个 struct cdev 结构体
参数含义:
(1) 第一个参数为要添加的 struct cdev 类型的结构体
(2) 第二个参数为申请的设备号
(3) 第三个参数为和该设备关联的设备编号的数量。
这两个参数直接赋值给 struct cdev 的 dev 成员和 count 成员。
函数返回值: 添加成功返回 0 ,添加失败返回负数。
字符设备删除所用到的函数为 cdev_del() ,该函数同样在内核源码 /include/linux/cdev.h 文
件中,如代码下 所示。

cpp 复制代码
void cdev_del(struct cdev *);

cdev_del() 详细介绍如下:

函数原型:
void cdev_del(struct cdev *p)
函数作用:
该函数会向内核删除一个 struct cdev 类型结构体
参数含义:
该函数只有一个参数,为要删除的 struct cdev 类型的结构体
函数返回值: 无返回值

实验程序的编写

本实验采用动态申请设备号的方式进行设备号的申请,然后注册字符设备,并将申请到的主设备号和次设备号以及字符设备注册情况打印到终端上。
实验代码 cdev.c 如代码下 所示。

cpp 复制代码
#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("moss"); //作者信息

运行测试

编译驱动程序

Makefile 文件内容如代码下所 示。

cpp 复制代码
export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += cdev.o #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel #这里是你的内核目录
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules #make 操作
clean:
    make -C $(KDIR) M=$(PWD) clean #make clean 操作

使用make编译驱动程序;编译完成会生成cdev.ko目标文件。

运行测试

开发板启动后,使用insomd cdev.ko命令加载驱动程序,如图:

可以看到动态申请设备号成功,主设备号为 236 ,次设备号为 0 ,并打印" cdev_add is ok",说明字符设备已经注册成功。可以使用 rmmod cdev.ko 命令卸载驱动程序,如图:

相关推荐
尼喃2 小时前
PW2605Z,专为系统安全护航的高可靠性负载开关
stm32·单片机·嵌入式硬件
延延oO2 小时前
zyzyzyzyzy
linux
小白不想白a2 小时前
linux排障:服务端口被打满
linux·服务器·网络
CryptoPP2 小时前
对接API获取马来西亚历史数据
linux·运维·服务器·金融·区块链
阿昊真人2 小时前
stm32 按键中断
stm32·单片机·嵌入式硬件
Cyber4K2 小时前
【Kubernetes专项】K8s集群1.31版本安装手册
linux·docker·云原生·容器·kubernetes
代码游侠3 小时前
学习笔记——51单片机学习
笔记·stm32·单片机·嵌入式硬件·51单片机
陈让然3 小时前
WSL2 ubuntu18.04扩容
linux·运维·ubuntu