You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用alloc_chrdev_region后/dev无字符驱动设备条目问题

字符驱动使用alloc_chrdev_region后/dev无设备条目的问题

我正在开发一个简单的Linux字符驱动,知道获取主设备号有两种常见方式:alloc_chrdev_region(包含register_chrdev_region)和register_chrdev。最开始用register_chrdev的时候,不仅能成功获取主设备号,还能通过class_create和device_create自动在/dev目录下生成对应的设备条目。但换成alloc_chrdev_region配合cdev_init、cdev_add的新接口后,/dev里就没自动生成设备文件了,手动用mknod创建后驱动倒是能正常工作。以下是我的相关代码:

#include<linux/module.h> 
#include<linux/init.h> 
#include<linux/fs.h> 
#include<linux/device.h> 
#include<linux/kernel.h> 
#include<linux/slab.h> 
#include<linux/uaccess.h> 
#include<linux/stat.h> 
#include<linux/cdev.h> 
#include <linux/version.h> 
#include <linux/types.h> 
#include <linux/kdev_t.h> 

#define DEVICE_NAME "myCharDevice" 
#define MODULE_NAME "myCharDriver" 
#define CLASS_NAME "myCharClass" 

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("YASH BHATT"); 
MODULE_VERSION(".01"); 

static char *bufferMemory; 
static int bufferPointer; 
static int bufferSize = 15; 
static dev_t myChrDevid; 
static struct cdev *myChrDevCdev; 
static struct class *pmyCharClass; 
static struct device *pmyCharDevice; 
int majorNumber = 0; 

static int charDriverOpen(struct inode *inodep, struct file *filep); 
static int charDriverClose(struct inode *inodep, struct file *filep); 
static ssize_t charDriverWrite(struct file *filep, const char *buffer, size_t len, loff_t *offset); 
static ssize_t charDriverRead(struct file *filep, char *buffer, size_t len, loff_t *offset); 
static int charDriverEntry(void); 
static void charDriverExit(void); 

static ssize_t attrShowData(struct device*, struct device_attribute*, char*); 
static ssize_t attrStoreData(struct device*, struct device_attribute*, const char*, size_t); 
static ssize_t attrShowBuffer(struct device*, struct device_attribute*, char*); 
static ssize_t attrStoreBuffer(struct device*, struct device_attribute*, const char*, size_t); 

/* The following function is called when the file placed on the sysfs is accessed for read*/ 
static ssize_t attrShowData(struct device* pDev, struct device_attribute* attr, char* buffer) { 
    printk(KERN_INFO "MESG: The data has been accessed through the entry in sysfs\n"); 
    if (bufferPointer == 0) { 
        printk(KERN_WARNING "There is no data to read from buffer!\n"); 
        return -1; 
    } 
    strncpy(buffer, bufferMemory, bufferPointer); 
    /* Note : Here we can directly use strncpy because we are already in kernel space and do not need to translate address*/ 
    return bufferPointer; 
} 

static ssize_t attrStoreData(struct device* pDev, struct device_attribute* attr, const char* buffer, size_t length) { 
    printk(KERN_INFO "Writing to attribute\n"); 
    bufferPointer = length; 
    strncpy(bufferMemory, buffer, length); 
    return length; 
} 

static ssize_t attrShowBuffer(struct device* pDev, struct device_attribute* attr, char* buffer) { 
    int counter; 
    int temp = bufferSize; 
    char bufferSizeArray[4] = {0}; 
    counter = 3; 
    //printk(KERN_INFO "Buffer = %d\n",bufferSize % 10); 
    do { 
        bufferSizeArray[counter] = '0' + (bufferSize % 10); 
        //printk(KERN_INFO "Character at %d is : %c\n",counter,bufferSizeArray[counter]); 
        bufferSize /= 10; 
        counter--; 
    } while(counter != -1); 
    strncpy(buffer, bufferSizeArray, 4); 
    bufferSize = temp; 
    /* Note : Here we can directly use strncpy because we are already in kernel space and do not need to translate address*/ 
    return 4; 
} 

static ssize_t attrStoreBuffer(struct device* pDev, struct device_attribute* attr, const char* buffer, size_t length) { 
    int counter; 
    bufferPointer = length; 
    //printk(KERN_INFO "Length : %d With first char %c\n",length,buffer[0]); 
    bufferSize = 0; 
    for (counter = 0; counter < length-1 ; counter++) { 
        bufferSize = (bufferSize * 10) + (buffer[counter] - '0') ; 
    } 
    //printk(KERN_INFO "Buffer size new : %d\n",bufferSize); 
    return length; 
} 

/* These macros converts the function in to instances dev_attr_<_name>*/ 
/* Defination of the macro is as follows : DEVICE_ATTR(_name, _mode, _show, _store) */ 
/* Note the actual implementation of the macro makes an entry in the struct device_attribute. This macro does that for us */ 
static DEVICE_ATTR(ShowData, S_IRWXU, attrShowData, attrStoreData); // S_IRUSR gives read access to the user 
static DEVICE_ATTR(Buffer, S_IRWXU, attrShowBuffer, attrStoreBuffer); // S_IRUSR gives read access to the user 

static struct file_operations fops = { 
    .open = charDriverOpen, 
    .release = charDriverClose, 
    .read = charDriverRead, 
    .write = charDriverWrite, 
}; 

static int __init charDriverEntry() { 
    int returnValue; 
    //majorNumber = register_chrdev(0, DEVICE_NAME, &fops); 
    returnValue = alloc_chrdev_region(&myChrDevid, 0, 1, DEVICE_NAME); 
    /* This function takes 4 arguments - dev_t address, sta

问题原因

register_chrdev是较旧的字符驱动注册接口,它内部会自动完成一些与设备节点创建相关的关联工作;而alloc_chrdev_region + cdev_init + cdev_add是更灵活的新接口,它只负责设备号分配和字符设备对象的注册,不会自动触发/dev设备节点的创建——你需要手动确保class和device的创建流程完整且正确。

从你的代码片段来看,可能存在以下几个问题:

  • 代码被截断,不确定你是否在alloc_chrdev_region之后正确初始化并添加了cdev对象
  • 可能漏掉了class_create和device_create的调用,或者调用时参数错误(比如没有正确传递分配到的dev_t)

解决步骤

按照新接口的标准流程,你需要在驱动入口函数中补充以下步骤:

  1. 初始化并添加cdev对象
    在alloc_chrdev_region成功后,添加:

    // 分配cdev内存
    myChrDevCdev = cdev_alloc();
    if (!myChrDevCdev) {
        printk(KERN_ERR "Failed to allocate cdev\n");
        unregister_chrdev_region(myChrDevid, 1);
        return -ENOMEM;
    }
    // 绑定file_operations到cdev
    cdev_init(myChrDevCdev, &fops);
    // 将cdev添加到内核
    returnValue = cdev_add(myChrDevCdev, myChrDevid, 1);
    if (returnValue < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        cdev_del(myChrDevCdev);
        unregister_chrdev_region(myChrDevid, 1);
        return returnValue;
    }
    
  2. 创建class和device以生成/dev节点
    继续在入口函数中添加:

    // 创建设备类
    pmyCharClass = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(pmyCharClass)) {
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(myChrDevCdev);
        unregister_chrdev_region(myChrDevid, 1);
        return PTR_ERR(pmyCharClass);
    }
    // 创建设备节点,这一步会自动在/dev下生成对应文件
    pmyCharDevice = device_create(pmyCharClass, NULL, myChrDevid, NULL, DEVICE_NAME);
    if (IS_ERR(pmyCharDevice)) {
        printk(KERN_ERR "Failed to create device\n");
        class_destroy(pmyCharClass);
        cdev_del(myChrDevCdev);
        unregister_chrdev_region(myChrDevid, 1);
        return PTR_ERR(pmyCharDevice);
    }
    
  3. 在出口函数中清理资源
    确保在驱动卸载时释放所有资源:

    static void __exit charDriverExit(void) {
        // 销毁设备节点
        device_destroy(pmyCharClass, myChrDevid);
        // 销毁设备类
        class_destroy(pmyCharClass);
        // 删除cdev对象
        cdev_del(myChrDevCdev);
        // 释放设备号
        unregister_chrdev_region(myChrDevid, 1);
        // 释放buffer内存(如果之前用kmalloc分配的话)
        kfree(bufferMemory);
        printk(KERN_INFO "Char driver unloaded successfully\n");
    }
    module_init(charDriverEntry);
    module_exit(charDriverExit);
    

关键注意点

  • 每一步操作都要检查返回值,出错时要按顺序回滚已分配的资源,避免内存泄漏或资源残留
  • device_create的第三个参数必须是你通过alloc_chrdev_region获取到的完整dev_t(包含主、次设备号),这样udev才能正确识别并生成/dev节点
  • 确保你的驱动模块有足够的权限(一般需要root权限加载),否则class_create和device_create可能会失败

内容的提问来源于stack exchange,提问作者yashC

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:37:30