Linux设备注册的实现步骤与流程是怎样的?

Linux设备注册是驱动开发中的核心环节,其本质是将硬件设备抽象为Linux内核可管理的设备对象,并建立与驱动的关联,从而实现用户空间对设备的访问,整个过程依托Linux设备模型展开,涉及设备号分配、设备结构体初始化、设备添加到系统模型等多个步骤,以下从设备模型基础、字符设备注册流程、其他设备类型注册及注意事项等方面详细说明。

linux如何注册设备

Linux设备模型基础

Linux设备模型采用“总线(Bus)—设备(Device)—驱动(Driver)”三层架构,通过总线匹配设备与驱动,当设备注册到总线上时,驱动会通过总线提供的match函数判断是否支持该设备,若支持则调用probe函数完成设备初始化,常见的总线类型有platform总线(用于嵌入式平台设备)、PCI总线(用于PCI设备)、USB总线(用于USB设备)等,设备注册的核心是将设备信息加入内核设备链表,并暴露相应的sysfs接口,方便用户空间查看和管理。

字符设备注册详细流程

字符设备是Linux中最常见的设备类型(如串口、LED灯等),其注册流程主要包括设备号分配、cdev结构体初始化、设备添加及设备节点创建四个步骤。

设备号分配

设备号是内核识别设备的唯一标识,由主设备号(major)和次设备号(minor)组成,主设备号标识设备类型,次设备号标识同一类型下的不同设备,设备号分配分为静态分配和动态分配两种方式。

  • 静态分配:通过register_chrdev_region函数手动指定设备号范围,适用于设备号已知且固定的场景。

    dev_t dev_num;
    int major = 100, minor = 0;
    int dev_count = 1; // 设备数量
    int ret = register_chrdev_region(MKDEV(major, minor), dev_count, "my_char_dev");
    if (ret < 0) {
        printk(KERN_ERR "Failed to register device numbern");
        return ret;
    }

    其中MKDEV(major, minor)用于将主次设备号合并为dev_t类型,第三个参数为设备名(会在/proc/devices中显示)。

  • 动态分配:通过alloc_chrdev_region函数让内核自动分配可用设备号,适用于设备号不确定的场景。

    dev_t dev_num;
    int ret = alloc_chrdev_region(&dev_num, 0, 1, "my_char_dev");
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device numbern");
        return ret;
    }
    major = MAJOR(dev_num); // 获取分配的主设备号
    minor = MINOR(dev_num); // 获取分配的次设备号

设备号分配函数对比
| 函数名 | 功能 | 参数说明 | 返回值 |
|———————-|————————–|————————————————————————–|—————–|
| register_chrdev_region | 静态分配设备号 | dev_t from(起始设备号)、count(设备数量)、const char name(设备名) | 成功0,失败负错误码 |
| alloc_chrdev_region | 动态分配设备号 | dev_t
dev(输出分配的设备号)、int baseminor(起始次设备号)、count、name | 成功0,失败负错误码 |

cdev结构体初始化与添加

cdev(character device)结构体是字符设备的核心,用于描述字符设备的属性和操作方法,注册设备前需初始化cdev并关联文件操作集合(file_operations)。

  • 初始化cdev

    linux如何注册设备

    struct cdev my_cdev;
    my_cdev.owner = THIS_MODULE; // 模块所属,防止模块卸载后驱动仍被调用
    cdev_init(&my_cdev, &fops);  // fops为file_operations结构体,定义设备的读写等操作

    file_operations是关键结构体,需实现readwriteopenrelease等成员函数,

    const struct file_operations fops = {
        .owner = THIS_MODULE,
        .read = my_dev_read,   // 读函数
        .write = my_dev_write, // 写函数
        .open = my_dev_open,   // 打开函数
        .release = my_dev_release, // 释放函数
    };
  • 添加cdev到系统
    使用cdev_add函数将初始化后的cdev添加到内核设备链表,完成设备注册。

    ret = cdev_add(&my_cdev, MKDEV(major, minor), 1);
    if (ret < 0) {
        unregister_chrdev_region(MKDEV(major, minor), 1); // 失败时释放设备号
        printk(KERN_ERR "Failed to add cdevn");
        return ret;
    }

创建设备类与设备节点

设备注册后,需在sysfs中创建设备类(class)和设备(device)对象,并自动生成设备节点(如/dev/my_char_dev),供用户空间访问。

  • 创建设备类:设备类是sysfs中设备的逻辑集合,用于管理同类设备。

    struct class *my_class = class_create(THIS_MODULE, "my_char_class");
    if (IS_ERR(my_class)) {
        cdev_del(&my_cdev);    // 失败时删除cdev
        unregister_chrdev_region(MKDEV(major, minor), 1);
        printk(KERN_ERR "Failed to create classn");
        return PTR_ERR(my_class);
    }
  • 创建设备对象:设备对象代表具体设备,关联设备号和设备类。

    struct device *my_device = device_create(my_class, NULL, MKDEV(major, minor), NULL, "my_char_dev");
    if (IS_ERR(my_device)) {
        class_destroy(my_class); // 失败时销毁类
        cdev_del(&my_cdev);
        unregister_chrdev_region(MKDEV(major, minor), 1);
        printk(KERN_ERR "Failed to create devicen");
        return PTR_ERR(my_device);
    }

    执行成功后,/dev目录下会生成设备节点,用户可通过openread等系统调用操作设备。

其他设备类型注册简介

除字符设备外,Linux还支持块设备(如硬盘)和网络设备,其注册流程与字符设备类似,但涉及不同的内核接口。

  • 块设备注册:通过alloc_blkdev_region分配设备号,初始化gendisk结构体(描述块设备属性),调用add_disk将设备添加到系统,块设备支持随机访问,需实现make_request等操作函数。

  • 平台设备注册:用于嵌入式系统中的平台设备(如I2C、SPI设备),通过platform_device_register注册,需定义platform_device结构体,包含设备名称、资源(地址、中断号)等信息,驱动通过platform_driver_register注册,match函数通过设备名称与设备匹配。

    linux如何注册设备

  • PCI设备注册:内核已实现PCI总线驱动,设备注册由PCI控制器自动完成,驱动只需调用pci_register_driver注册PCI驱动,通过pci_device_id表匹配设备。

设备注销流程

设备注销是注册的逆过程,需释放已分配的资源,避免内存泄漏,顺序与注册相反:先删除设备对象和类,再删除cdev,最后释放设备号。

void __exit my_dev_exit(void) {
    device_destroy(my_class, MKDEV(major, minor)); // 删除设备对象
    class_destroy(my_class);                      // 销毁设备类
    cdev_del(&my_cdev);                           // 删除cdev
    unregister_chrdev_region(MKDEV(major, minor), 1); // 释放设备号
}
module_init(my_dev_init);
module_exit(my_dev_exit);

代码示例与注意事项

以下是一个简单的字符设备注册模板(关键步骤):

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define DEVICE_NAME "my_char_dev"
#define CLASS_NAME "my_class"
static dev_t dev_num;
static struct cdev my_cdev;
static struct class *my_class;
static int my_dev_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Device openedn");
    return 0;
}
static ssize_t my_dev_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos) {
    printk(KERN_INFO "Device readn");
    return 0;
}
static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = my_dev_open,
    .read = my_dev_read,
};
static int __init my_dev_init(void) {
    // 动态分配设备号
    if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) {
        printk(KERN_ERR "Failed to allocate device numbern");
        return -1;
    }
    // 初始化cdev
    cdev_init(&my_cdev, &fops);
    my_cdev.owner = THIS_MODULE;
    // 添加cdev
    if (cdev_add(&my_cdev, dev_num, 1) < 0) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdevn");
        return -1;
    }
    // 创建类和设备
    my_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(my_class)) {
        cdev_del(&my_cdev);
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(my_class);
    }
    device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
    printk(KERN_INFO "Device registered successfullyn");
    return 0;
}
static void __exit my_dev_exit(void) {
    device_destroy(my_class, dev_num);
    class_destroy(my_class);
    cdev_del(&my_cdev);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Device unregisteredn");
}
module_init(my_dev_init);
module_exit(my_dev_exit);
MODULE_LICENSE("GPL");

注意事项

  1. 错误处理:每一步注册操作都可能失败,需检查返回值并释放已分配资源(如设备号、cdev等)。
  2. 并发安全:设备操作函数(如readwrite)可能被多进程并发调用,需使用互斥锁(mutex)或自旋锁(spinlock)保护共享数据。
  3. 设备号管理:静态分配需确保设备号未被占用(可通过cat /proc/devices查看),动态分配需处理分配失败的情况。
  4. 模块引用计数file_operations中的owner必须设置为THIS_MODULE,防止模块卸载后驱动函数仍被调用。

相关问答FAQs

Q1:为什么设备注册后无法在/dev下看到设备文件?
A:可能原因包括:

  1. 未创建设备类或设备对象:检查是否调用了class_createdevice_create,且返回值是否成功。
  2. udev服务未运行:设备节点的创建依赖udev(或systemd-udevd),需确保服务正在运行(systemctl status udev)。
  3. 设备名称冲突:device_create中的设备名可能与已有设备重复,导致创建失败。
  4. 模块未加载:设备注册在模块初始化时执行,需确保模块已成功加载(lsmod查看)。

Q2:设备号分配失败(alloc_chrdev_region返回负值)如何解决?
A:设备号分配失败通常是因为系统中可用设备号不足,解决方法:

  1. 检查当前设备号使用情况:cat /proc/devices查看已分配的主设备号,避免冲突。
  2. 扩展设备号范围:动态分配时,baseminor参数可指定起始次设备号(通常为0),若失败可尝试调整count(设备数量)或更换设备名。
  3. 释放闲置设备号:若某些设备号已被分配但不再使用,可检查对应驱动是否正确卸载,必要时手动释放(需谨慎操作)。
  4. 使用静态分配:若动态分配持续失败,可尝试手动指定未使用的主设备号(如cat /proc/devices中的空闲号)。

原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/36203.html

(0)
酷番叔酷番叔
上一篇 2025年10月5日 06:38
下一篇 2025年10月5日 06:52

相关推荐

  • Linux服务器杀毒该怎么做?实用方法、常用工具及安全防护技巧有哪些?

    Linux服务器因其稳定性、开源特性和灵活性,被广泛应用于企业级服务部署,但并非绝对安全,随着针对Linux的恶意程序(如挖矿木马、勒索软件、后门程序等)逐渐增多,服务器杀毒成为运维工作的重要组成部分,本文将从Linux病毒特点、常用杀毒工具、预防措施、应急处理流程等方面,详细说明Linux服务器的杀毒方法,L……

    2025年9月28日
    11300
  • Linux强制关机危害大?安全关机命令揭秘!,(注,28字,疑问句式引发好奇,包含核心关键词Linux安全关机命令,同时点出用户痛点强制关机危害提升点击欲)

    在Linux虚拟机中正确退出至关重要,不仅能避免数据丢失或系统损坏,还能确保虚拟化环境稳定运行,以下是三种主流退出方法,适用于VirtualBox、VMware Workstation/Player等常见虚拟机软件,操作前请务必保存工作文件,此方法模拟物理机关机流程,确保所有进程正常结束,步骤:在虚拟机内打开终……

    2025年7月21日
    13600
  • Linux修改文件后保存不了,该怎么解决?

    在Linux系统中,修改文件后无法保存是常见问题,可能涉及权限、磁盘空间、文件占用、系统状态等多种因素,以下从常见原因出发,逐步排查并提供解决方法,帮助快速定位并解决问题,常见原因及解决方法文件权限不足症状:保存时提示“Permission denied”(权限拒绝),通常发生在普通用户修改root权限文件或非……

    2025年9月20日
    13200
  • Linux删除账户如何避免误删?

    核心命令:userdeluserdel 是Linux删除用户的专用命令,需root权限执行:sudo userdel [选项] 用户名常用选项:选项作用-r删除用户主目录及邮件文件-f强制删除(即使用户已登录)-Z同时清除SELinux用户映射详细操作步骤基本删除(保留主目录)sudo userdel user……

    2025年7月19日
    13900
  • Linux密码怎么改最安全?

    更改当前登录用户密码打开终端Ctrl+Alt+T 快捷键启动终端(适用于大多数图形界面),或通过系统菜单搜索”Terminal”,执行密码修改命令输入命令后按回车:passwd系统提示输入当前密码(输入时无显示),输入新密码并确认(需输入两次),密码强度要求:建议包含大小写字母、数字、符号(如 S3cur!ty……

    2025年6月20日
    14800

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

联系我们

400-880-8834

在线咨询: QQ交谈

邮件:HI@E.KD.CN

关注微信