字符设备驱动开发基础—静态/动态注册设备号,使用cdev注册驱动

news2024/9/20 16:38:49

文章目录

    • 主次设备号介绍
      • 主设备号 (Major Number)
      • 次设备号 (Minor Number)
      • 设备号的表示与注册
        • 设备号的获取
        • 设备号的注册
      • 设备文件的创建
      • 实际应用中的主次设备号
    • cdev 结构体介绍
      • `cdev` 结构体
        • 主要字段
      • 使用 `cdev` 结构体的步骤
      • `file_operations` 结构体
    • 静态注册设备号
      • 示例代码:
      • 效果演示
    • 动态注册设备号
      • 示例代码
      • 效果演示

主次设备号介绍

在Linux和类Unix操作系统中,设备文件用于表示各种硬件设备和虚拟设备。每个设备文件通过一个唯一的设备号进行标识,该设备号由主设备号次设备号组成。设备号帮助操作系统将设备文件与实际的设备驱动程序关联起来,以便正确处理对设备的操作请求。

主设备号 (Major Number)

主设备号是设备号的高位部分,用于标识设备的类型或类别。它决定了操作系统使用哪个设备驱动程序来处理设备文件的操作请求。每种设备类型(如磁盘、终端、网络设备等)通常都有一个唯一的主设备号。

  • 主设备号的分配是由操作系统维护的,确保每个设备驱动程序都有一个唯一的主设备号。
  • 内核使用主设备号来查找与之关联的设备驱动程序。驱动程序注册时会声明其支持的主设备号。
  • 例如,主设备号为8的设备通常表示SCSI磁盘设备。

次设备号 (Minor Number)

次设备号是设备号的低位部分,用于标识同一类设备中的具体实例或单个设备。例如,多个硬盘驱动器或分区可能使用相同的主设备号,但有不同的次设备号。

  • 次设备号允许同一设备驱动程序管理多个设备实例。例如,多个硬盘、多个串口等。
  • 对于字符设备和块设备,次设备号的使用方式可能有所不同。字符设备的次设备号通常用于区分不同的设备,而块设备的次设备号通常用于区分不同的分区或设备上的逻辑单元。

设备号的表示与注册

设备号在Linux内核中通常使用dev_t类型表示,这是一个32位的整数,其中高12位表示主设备号,低20位表示次设备号。

设备号的获取

设备号可以通过以下方式获取:

  1. 静态分配:在驱动程序中直接指定主设备号和次设备号。

    int major = 240; // 静态指定主设备号
    dev_t dev = MKDEV(major, 0);
    
  2. 动态分配:通过内核提供的API函数动态分配主设备号。

    dev_t dev;
    int result = alloc_chrdev_region(&dev, 0, 1, "my_device");
    if (result < 0) {
        // 处理错误
    }
    int major = MAJOR(dev); // 获取分配的主设备号
    int minor = MINOR(dev); // 获取次设备号
    
设备号的注册

在驱动程序中,设备号的注册通常涉及以下步骤:

  1. 申请设备号:使用register_chrdev_region()alloc_chrdev_region()函数来申请设备号范围。

  2. 注册设备:在cdev结构体中设置设备号,并调用cdev_add()函数将设备注册到内核。

设备文件的创建

设备文件通常位于/dev目录下,通过mknod命令创建,指定主设备号和次设备号。例如:

mknod /dev/my_device c 240 0

这里,c表示字符设备,240是主设备号,0是次设备号。

实际应用中的主次设备号

  1. 块设备:如硬盘、分区等。主设备号区分设备类型,次设备号区分不同的硬盘或分区。
  2. 字符设备:如串口、终端等。主设备号区分设备类型,次设备号区分不同的端口或终端。

cdev 结构体介绍

在Linux内核中,cdev结构体是字符设备驱动程序的核心数据结构之一,用于表示和管理字符设备。字符设备是通过字符设备文件接口与用户空间进行交互的设备,例如终端、串口、鼠标等。cdev结构体帮助将字符设备与设备文件关联起来,并定义了设备的操作方法。

cdev 结构体

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;
};
主要字段
  1. kobject kobj

    • 这是cdev在内核中注册为对象的基础。kobject是内核中的通用对象结构,提供了内核对象的基础设施,包括引用计数和sysfs接口支持。
    • 通过kobject,可以将字符设备对象与内核其他部分进行关联和管理。
  2. module *owner

    • 指向拥有该字符设备的内核模块。通常,这个字段被设置为模块宏THIS_MODULE,用于表示当前的加载模块。
    • 通过该字段,内核可以管理模块的引用计数,防止在模块被卸载时还有未完成的操作。
  3. const struct file_operations *ops

    • 指向文件操作结构体(file_operations),该结构体定义了字符设备的各种操作函数,如openreadwriteioctl等。
    • 这是字符设备的核心,所有的用户空间操作请求都会通过这些函数实现。
  4. struct list_head list

    • 用于将cdev结构体链接到一个链表中。这在内部管理多个字符设备或设备实例时非常有用。
  5. dev_t dev

    • 设备号,包括主设备号和次设备号。设备号唯一标识一个字符设备文件,使其能够与特定的设备关联。
    • 主设备号用于标识设备类型,次设备号用于区分同一类型的不同设备实例。
  6. unsigned int count

    • 指定字符设备的设备号范围,通常为1,表示一个cdev结构体实例管理一个设备号。

使用 cdev 结构体的步骤

  1. 定义和初始化 cdev 结构体

    • 通过静态分配或动态分配定义cdev结构体。
    • 使用cdev_init()函数初始化cdev结构体,将file_operations结构体的指针赋给ops字段。
    struct cdev my_cdev;
    cdev_init(&my_cdev, &my_fops);
    
  2. 分配和注册设备号

    • 使用alloc_chrdev_region()函数动态分配设备号,或使用register_chrdev_region()函数注册已知设备号。
    • 将获得的设备号存储在dev_t类型的变量中。
    dev_t dev;
    alloc_chrdev_region(&dev, 0, 1, "my_device");
    
  3. 添加 cdev 到内核

    • 使用cdev_add()函数将初始化后的cdev结构体添加到内核中,关联到之前分配的设备号。
    cdev_add(&my_cdev, dev, 1);
    
  4. 清理 cdev 结构体

    • 在模块卸载或不再使用设备时,使用cdev_del()函数从内核中移除cdev结构体,并释放设备号。
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev, 1);
    

file_operations 结构体

file_operations结构体定义了字符设备的操作接口,是用户空间和设备之间的桥梁。常见的操作包括:

  • open:打开设备文件时调用。
  • release:关闭设备文件时调用。
  • read:从设备读取数据。
  • write:向设备写入数据。
  • ioctl:控制设备的操作。

示例:

static struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
    .unlocked_ioctl = my_ioctl,
};

通过使用cdev结构体和file_operations结构体,字符设备驱动程序可以在Linux内核中注册和管理设备,从而实现设备与用户空间的交互。

静态注册设备号

在驱动程序中直接指定主设备号和次设备号。

示例代码:

#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define DEVICE_NAME "my_char_device"
#define MAJOR 200
#define MINJOR 0
static struct cdev mydev;
static dev_t dev; //存储设备号
static int my_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "my_char_device: open()\n");
    return 0;
}
static int my_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "my_char_device: release()\n");
    return 0;
}
static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
    printk(KERN_INFO "my_char_device: read()\n");
    return 0;
}

static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
    printk(KERN_INFO "my_char_device: write()\n");
    return count;
}
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
};
//在模块加载到内核程序中被执行一次
int __init test_init(void)
{
    printk("module init success\n");
    int retval;
    //使用cdev 接口来注册字符设备驱动
    //1. 注册 /分配 主次设备驱动号
    dev = MKDEV(MAJOR,MINJOR);
    retval = register_chrdev_region(dev,1,DEVICE_NAME);
    if(retval){
        //说明注册失败了,可能已经被占用了
        printk(KERN_ERR "Unable to register minors for %s\n",DEVICE_NAME);
        return retval;
    }
    printk(KERN_INFO "staic register minors success\n");
    //2.注册字符设备驱动
    //传入cdev 和 文件操作结构体
    cdev_init(&mydev,&fops);
    retval = cdev_add(&mydev,dev,1);  //注册一个字符设备驱动
    if(retval<0){
        printk(KERN_ERR "Failed to add cdev\n");  
        unregister_chrdev_region(dev, 1);  //注销设备号
        return retval;
    }
    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;
}
//在模块从内核驱动中卸载时执行一次
void __exit test_exit(void)
{
    cdev_del(&mydev);
    unregister_chrdev_region(dev, 1);
}
module_init(test_init);		//注册此模块加载的回调函数
module_exit(test_exit);		//注册此模块卸载的回调函数
MODULE_LICENSE("GPL");		//声明遵循协议

其中直接指定了 主设备号200 次设备号为0

效果演示

使用dmesg 可以查看输出提示

在这里插入图片描述

查看当前注册的设备号

cat /proc/devices

可以看到200 主设备号的驱动程序已经注册成功了

在这里插入图片描述

动态注册设备号

示例代码

#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#define DEVICE_NAME "my_char_device"
static struct cdev mydev;
int major_number;
static int my_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "my_char_device: open()\n");
    return 0;
}

static int my_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "my_char_device: release()\n");
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "my_char_device: read()\n");
    return 0;
}

static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk(KERN_INFO "my_char_device: write()\n");
    return count;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
    .read = my_read,
    .write = my_write,
};

// 在模块加载到内核程序中被执行一次
int __init test_init(void)

{
    printk("module init success\n");
    int retval;

    // 使用cdev 接口来注册字符设备驱动
    // 1. 动态分配 主次设备驱动号
    dev_t dev;
    retval = alloc_chrdev_region(&dev, 0, 1, "my_device");
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to allocate major number\n");
        return retval;
        // 处理错误
    }
    int major = MAJOR(dev); // 获取分配的主设备号
    int minor = MINOR(dev); // 获取次设备号
    printk(KERN_INFO "major number is:%d,  minor number is:%d\n",major,minor);
    major_number = MAJOR(dev);
    // 2.注册字符设备驱动
    // 传入cdev 和 文件操作结构体
    cdev_init(&mydev, &fops);
    retval = cdev_add(&mydev, dev, 1); // 注册一个字符设备驱动
    if (retval < 0)
    {
        printk(KERN_ERR "Failed to add cdev\n");
        unregister_chrdev_region(dev, 1); // 注销设备号
        return retval;
    }
    
    printk(KERN_INFO "my_char_device: module loaded\n");
    return 0;
}
// 在模块从内核驱动中卸载时执行一次
void __exit test_exit(void)
{
    cdev_del(&mydev);
    dev_t  dev = MKDEV(major_number,0);
    unregister_chrdev_region(dev, 1);
}
module_init(test_init); // 注册此模块加载的回调函数
module_exit(test_exit); // 注册此模块卸载的回调函数

MODULE_LICENSE("GPL"); // 声明遵循协议

与静态注册不同的是使用的函数为 retval = alloc_chrdev_region(&dev, 0, 1, "my_device");

推荐使用动态分配的方式,更加灵活

效果演示

动态分配了主设备号为235

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1970579.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

商业银行国际结算规模创新高,合合信息AI助力金融行业智能处理多版式文档

随着我国外贸新业态的快速增长&#xff0c;银行国际结算业务在服务实体经济发展、促进贸易投资便利化进程中发挥了越来越重要的作用。根据中国银行业协会近日发布的《中国贸易金融行业发展报告&#xff08;2023—2024&#xff09;》&#xff0c;2023年我国主要商业银行国际结算…

【征求意见】同济大学--城镇给水厂碳排放核算与评价方法

城镇给水厂保障城镇居民正常生活&#xff0c;是社会经济良性发展的重要基础性设施&#xff0c;对于我国双碳战略目标的实现至关重要。 随着城镇化的发展&#xff0c;城镇供水量不断升高&#xff0c;加上 水资源与生态环境问题不断涌现&#xff0c;人们对水的安全和品质的需求日…

关于在局域网如何避免内网IP被占用的问题

目录 前言 1.使用ipconfig命令 2.排查IP是否被占用&#xff0c;使用ping命令。 3.ip地址冲突&#xff0c;无法访问网站怎么办&#xff1f; 4.怎么避免重启再次出现IP冲突问题&#xff1f; 前言 在一些小型的公司&#xff0c;IP地址没有被规划好&#xff0c;每个人都是通过动…

基于 Rough.js 的 Vue 散点图绘制

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 基于 Rough.js 的 Vue 散点图绘制 应用场景 本代码展示了如何使用 Rough.js 库在 Vue 应用程序中绘制散点图。Rough.js 是一个轻量级 JavaScript 库&#xff0c;用于创建具有手绘风格的可视化效果。散点图是一…

c++学习笔记—内存概念

c学习笔记 Bit含义用途特点 字节含义用途特点注意事项 c内存基本概念1. 栈&#xff08;Stack&#xff09;2. 堆&#xff08;Heap&#xff09;3. 全局/静态存储区4. 常量存储区5. 内存管理关键字和函数6. 内存泄漏7. 指针和引用8. 虚拟内存9. 内存对齐10. 构造和析构 c中基本数据…

用神经网络求解方案

在处理密集、计算量大的数学问题时&#xff0c;计算机系统一直是人类最好的朋友。从计算器上的简单计算到 R 中的大型统计运算&#xff0c;这一技术前沿使我们很多人的生活变得更加轻松。 然而&#xff0c;即使是计算机&#xff0c;当数字变成字母并开始涉及代数时&#xff0c…

微信公众号使用介绍

写在前面&#xff0c;楼下有个医生小姐姐想做个公众号&#xff0c;整理了一份文档入门用 一 订阅号和服务号区别 1.1 关于功能上的差异 订阅号&#xff08;Subscription Account&#xff09; 定位&#xff1a;主要用于提供信息传播和互动&#xff0c;适合媒体、个人博主、机…

一文全面了解HPC高性能计算平台是什么、怎么选型?高性能计算平台CHPC 都能做什么?

一. 概述 随着技术的发展和数据量的爆炸性增长&#xff0c;企业面临的挑战日益复杂&#xff0c;对计算能力的需求也在不断增加。这些问题的解决超出了传统计算方法的能力范围&#xff0c;高性能计算&#xff08;HPC&#xff09;正是为解决这类问题而生。 高性能计算&#xff…

【Linux】Linux下的日志(日常级)

日志是日后工作中非常重要的一部分&#xff0c;现在写一份简单的日志项目可以帮助我们熟悉并理解原理。 目录 设计思路&#xff1a;一些实现细节&#xff1a;代码&#xff1a;日志的使用方法&#xff1a; 设计思路&#xff1a; 图示是我们的最终目的。 设计一个类&#xff0…

是否掌握数据结构对于IT职业发展至关重要?

算法跟代码实现有时候是两回事&#xff0c;在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「数据结构的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#…

Cesium 限高分析

Cesium 限高分析 原理&#xff1a;ClassificationPrimitive贴模型的运用 效果图

兼容性最好的模拟器,主要为高端平板设计

一、简介 1、蓝叠安卓模拟器是BlueStacks公司推出的全球知名Android模拟器。它可能是兼容性最好的模拟器&#xff0c;主要为高端平板设计&#xff0c;加入了图形硬件加速&#xff0c;并与AMD合作。 二、下载 1、文末有下载链接,不明白可以私聊我哈&#xff08;麻烦咚咚咚&#…

新迪天工CAD,国际水平的国产三维CAD软件

新迪数字 定位&#xff1a;国际领先的三维CAD软件厂商 使命&#xff1a;掌握核心技术&#xff0c;推动中国创造 愿景&#xff1a;让每个工程师使用中国CAD 上海新迪数字技术有限公司是一家拥有国际先进水平三维CAD核心技术的国产工业软件厂商&#xff0c;由国际工业软件领域知…

今日头条爬虫(zhanzhang.toutiao.com)IP及UA,真实采集数据

​ 一、数据来源&#xff1a; 1、这批今日头条爬虫(zhanzhang.toutiao.com)IP来源于尚贤达猎头网站采集数据&#xff1b; ​ 2、数据采集时间段&#xff1a;2023年10月-2024年7月&#xff1b; 3、判断标准&#xff1a;主要根据用户代理是否包含“zhanzhang.toutiao.com”和…

职场进阶还是智商税?一文看六西格玛绿带培训的真面目

随着企业对精细化管理需求的日益增长&#xff0c;六西格玛绿带培训逐渐成为职场人士争相追逐的热门课程。它不仅能够帮助学员掌握先进的质量管理工具&#xff0c;还能培养逻辑思维、数据分析能力以及团队合作精神&#xff0c;这些都是现代职场不可或缺的软实力。 职场助力or智商…

小程序商品图片有什么要求

在当今信息化的社会中&#xff0c;无论是组织活动、培训课程还是社交聚会&#xff0c;高效、准确地收集报名信息并进行有效匹配显得尤为重要。为此&#xff0c;我们开发了一款全新的报名匹配工具&#xff0c;旨在帮助活动组织者轻松管理报名流程&#xff0c;同时确保参与者能够…

机器学习 第8章-集成学习

机器学习 第8章-集成学习 8.1 个体与集成 集成学习(ensemble learning)通过构建并结合多个学习器来完成学习任务&#xff0c;有时也被称为多分类器系统(multi-classifersystem)、基于委员会的学习(committee-based learning)等。 图8.1显示出集成学习的一般结构:先产生一组“…

day15 Java基础——包机制

day15 Java基础——包机制 Java包机制是一种组织和管理类和接口的方式&#xff0c;通过将相关的类和接口放入同一个包中&#xff0c;可以避免命名冲突&#xff0c;提高代码的可重用性和可维护性。包采用层次化的命名空间&#xff0c;以点号分隔&#xff0c;如java.util。使用i…

标题:组合式API:优化Vue代码结构的艺术

摘要&#xff1a; 在Vue 3中&#xff0c;引入了组合式API&#xff0c;它提供了一种新的方式来组织组件逻辑。虽然组合式API带来了更高的灵活性和可维护性&#xff0c;但开发者也面临着代码组织和可读性的挑战。本文将探讨如何有效地利用组合式API&#xff0c;优化Vue代码结构&a…

深化理解电子商务领域的“二清”风险与合规路径

在电子商务的快速发展中&#xff0c;“二清”风险成为了不容忽视的话题。这一现象不仅触及金融监管红线&#xff0c;还潜藏诸多风险&#xff0c;包括资金安全、信息泄露、合规性挑战以及监管盲点。鉴于“二清”问题的复杂性与潜在危害&#xff0c;电商平台必须采取有效措施&…