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

字符设备驱动LKM中read()与write()函数实现问题求助

问题描述

尝试创建一个简单的字符设备LKM,但read()和write()函数实现存在问题:执行echo hi > /dev/simple_character_device能确认写入正确字节数,但执行cat读取设备内容时,会持续循环输出'hi'直到出现错误地址。尝试用全局计数器跟踪写入字节数但未解决问题,需要正确实现read和write函数。

原代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>

MODULE_LICENSE("GPL");

#define BUFFER 1024
char * buffer_data;

// Count open and closed
size_t current_count;

int my_open(struct inode *, struct file *);
int my_release(struct inode *, struct file *);
ssize_t my_read(struct file *, char __user *, size_t count, loff_t *);
ssize_t my_write(struct file *, const char __user *, size_t count, loff_t *offp); 

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


int reg_init(void)
{
    // Allocate memory to store information 
    buffer_data = kmalloc(BUFFER, GFP_KERNEL);  // Use Kernel Flag

    register_chrdev(240, "simple_character_device", &fops);
    
    printk(KERN_ALERT "Init Allocating Memory");
    return 0;
}

void reg_exit(void)
{
    // Free and unregister device and data
    kfree(buffer_data);
    unregister_chrdev(240, "simple_character_device");
    printk(KERN_ALERT "Deregister Simple Character Device");
}


int my_open(struct inode *inode, struct file *file){
    printk(KERN_ALERT "Open File Device.\n");
    return 0;
}

int my_release(struct inode *inode, struct file *file){
    printk(KERN_ALERT "Close File Device.\n");
    return 0;
}

ssize_t my_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){
    // Check if we are reading within the Buffer Size
    if(BUFFER - *offp < 0){
        printk(KERN_ALERT "Out of buffer range.\n");
        return -EFAULT;
    }
    
    // Check if we fail to copy to user
    if (copy_to_user(buff, buffer_data, current_count) != 0){
        printk(KERN_ALERT "Failed to send character to user\n");
        return -EFAULT;
    }

    (*offp) += current_count;

    printk(KERN_ALERT "Read %zu bytes.\n", current_count);
    return current_count;
}

ssize_t my_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp){
    // We need to get data FROM the user space
    
    // Make sure we are reading within the buffer
    if (*offp >= BUFFER || BUFFER - count < *offp){
        printk(KERN_ALERT "ATTEMPTING TO WRITE TO OUSIDE OF BUFFER!\n");
        return EFAULT;
    }
    
    // Get the amount of bytes from the user
    copy_from_user(buffer_data + *offp, buff, count);

    *offp += count;

    printk(KERN_ALERT "Wrote %zu to the device.\n", count);
    current_count = count;
    return current_count;
}
 
module_init(reg_init);
module_exit(reg_exit);
问题分析
  1. 全局计数器设计错误:current_count仅记录单次写入的字节数,未跟踪设备中实际存储的总字节数;且read函数始终从buffer起始位置读取,未利用offp偏移量定位当前读取位置,导致每次读取都返回相同内容。
  2. 未正确返回EOF:当offp偏移量超过实际写入的总字节数时,read函数未返回0(表示文件结束),用户空间工具如cat会持续发起读取请求,直到触发错误。
  3. write函数逻辑缺陷:边界检查逻辑错误,未计算实际可写入的字节数;未处理copy_from_user的错误返回值;每次写入覆盖current_count,丢失总长度信息。
  4. 未初始化全局变量:buffer_data分配后未清零,current_count未初始化,可能导致读取到垃圾数据。
修复后的代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/string.h>

MODULE_LICENSE("GPL");

#define BUFFER_SIZE 1024
char *buffer_data;
size_t total_written;  // 记录设备中存储的总字节数

int my_open(struct inode *, struct file *);
int my_release(struct inode *, struct file *);
ssize_t my_read(struct file *, char __user *, size_t count, loff_t *);
ssize_t my_write(struct file *, const char __user *, size_t count, loff_t *offp); 

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


int reg_init(void)
{
    // 分配内存并清零,避免垃圾数据
    buffer_data = kzalloc(BUFFER_SIZE, GFP_KERNEL);
    if (!buffer_data) {
        printk(KERN_ALERT "Failed to allocate buffer memory");
        return -ENOMEM;
    }

    // 注册字符设备,检查返回值
    int ret = register_chrdev(240, "simple_character_device", &fops);
    if (ret < 0) {
        printk(KERN_ALERT "Failed to register character device");
        kfree(buffer_data);
        return ret;
    }
    
    total_written = 0;
    printk(KERN_ALERT "Simple character device initialized");
    return 0;
}

void reg_exit(void)
{
    kfree(buffer_data);
    unregister_chrdev(240, "simple_character_device");
    printk(KERN_ALERT "Simple character device deregistered");
}


int my_open(struct inode *inode, struct file *file){
    printk(KERN_INFO "Device opened");
    return 0;
}

int my_release(struct inode *inode, struct file *file){
    printk(KERN_INFO "Device closed");
    return 0;
}

ssize_t my_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){
    // 检查偏移是否超出总写入长度,返回0表示EOF
    if (*offp >= total_written) {
        return 0;
    }

    // 计算实际可读的字节数:取用户请求数和剩余字节数的较小值
    size_t read_len = min(count, total_written - *offp);

    // 从当前偏移位置复制数据到用户空间
    if (copy_to_user(buff, buffer_data + *offp, read_len) != 0) {
        printk(KERN_ALERT "Failed to copy data to user");
        return -EFAULT;
    }

    // 更新偏移量
    *offp += read_len;
    printk(KERN_INFO "Read %zu bytes, offset now %lld", read_len, *offp);
    return read_len;
}

ssize_t my_write(struct file *filp, const char __user *buff, size_t count, loff_t *offp){
    // 计算实际可写入的字节数:取用户请求数和剩余缓冲区空间的较小值
    size_t write_len = min(count, BUFFER_SIZE - *offp);
    if (write_len == 0) {
        printk(KERN_ALERT "Buffer is full, cannot write more data");
        return -ENOSPC;
    }

    // 从用户空间复制数据到内核缓冲区
    if (copy_from_user(buffer_data + *offp, buff, write_len) != 0) {
        printk(KERN_ALERT "Failed to copy data from user");
        return -EFAULT;
    }

    // 更新偏移量和总写入字节数
    *offp += write_len;
    if (*offp > total_written) {
        total_written = *offp;
    }

    printk(KERN_INFO "Wrote %zu bytes, total stored %zu", write_len, total_written);
    return write_len;
}
 
module_init(reg_init);
module_exit(reg_exit);
关键修改说明
  • 用total_written全局变量记录设备中存储的总字节数,替代原有的current_count,避免单次写入覆盖长度信息。
  • read函数:
    • 当偏移量超过总写入长度时返回0,触发用户空间的EOF逻辑,终止循环读取。
    • 根据偏移量计算剩余可读字节,从正确的位置读取数据,而非始终从buffer起始位置读取。
    • 实际读取长度取用户请求数和剩余字节数的较小值,避免越界。
  • write函数:
    • 计算实际可写入的字节数,避免缓冲区溢出。
    • 处理copy_from_user的错误返回值,返回正确的错误码。
    • 更新total_written以反映设备中存储的总数据量。
  • 初始化阶段用kzalloc分配内存并清零,避免垃圾数据;添加设备注册的错误检查,确保资源清理。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 09:45:47