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

内核模块message_slot的read函数返回资源临时不可用问题排查

内核模块message_slot读写不一致问题:写入后读取通道字节数为0

问题背景

开发message_slot字符设备内核模块,核心规格:

  • 主设备号固定为235,支持最多256个次设备号
  • 每个次设备对应独立设备文件,单设备最多支持2^20个通道
  • 采用二叉树列表实现通道存储结构

已编译完成用户程序:message_writer.c(编译为sender)、message_reader.c(编译为reader)。

操作步骤与现象

  1. 执行流程:

    make
    insmod message_slot.ko
    mknod /dev/slot1 c 235 1
    sudo chmod o+rw /dev/slot1
    ./sender /dev/slot1 1 message1
    

    写入后通过printk确认:Write Done. curr_channel->bytes = 8,表明消息已写入slot1设备的1号通道。

  2. 异常现象:
    运行./reader /dev/slot1 1时返回错误:Resource temporarily unavailable。调试发现读取时curr_channel->bytes值为0,触发-EWOULDBLOCK错误。

相关代码片段

message_slot.h(通道节点定义)

typedef struct channel_node {
    unsigned int channel_id;
    char* message;
    size_t bytes;
    struct channel_node* left;
    struct channel_node* right;
} channel_node_t;

typedef struct device_data {
    channel_node_t* channel_root;
    // 其他设备相关数据
} device_data_t;

message_slot.c(读写关键逻辑)

// 写入函数简化版
static ssize_t message_slot_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) {
    device_data_t* dev_data = file->private_data;
    unsigned int channel_id = /* 从上下文获取通道号 */;
    channel_node_t* curr_channel = find_or_create_channel(dev_data, channel_id);
    
    if (copy_from_user(curr_channel->message, buf, count)) {
        return -EFAULT;
    }
    curr_channel->bytes = count;
    printk(KERN_INFO "Write Done. curr_channel->bytes = %zu", curr_channel->bytes);
    return count;
}

// 读取函数简化版
static ssize_t message_slot_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) {
    device_data_t* dev_data = file->private_data;
    unsigned int channel_id = /* 从上下文获取通道号 */;
    channel_node_t* curr_channel = find_channel(dev_data, channel_id);
    
    if (!curr_channel || curr_channel->bytes == 0) {
        printk(KERN_INFO "Read failed: curr_channel->bytes = %zu", curr_channel ? curr_channel->bytes : 0);
        return -EWOULDBLOCK;
    }
    
    if (copy_to_user(buf, curr_channel->message, curr_channel->bytes)) {
        return -EFAULT;
    }
    return curr_channel->bytes;
}

message_reader.c(核心逻辑)

#define BUF_SIZE 1024
#define MSG_SLOT_SET_CHANNEL _IOW('c', 1, unsigned int)

int main(int argc, char* argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <device path> <channel id>\n", argv[0]);
        exit(1);
    }
    
    int fd = open(argv[1], O_RDONLY);
    if (fd < 0) { perror("open"); exit(1); }
    
    unsigned int channel_id = atoi(argv[2]);
    if (ioctl(fd, MSG_SLOT_SET_CHANNEL, &channel_id) < 0) {
        perror("ioctl"); close(fd); exit(1);
    }
    
    char buf[BUF_SIZE];
    ssize_t bytes_read = read(fd, buf, BUF_SIZE);
    if (bytes_read < 0) {
        perror("read"); close(fd); exit(1);
    }
    
    close(fd);
    return 0;
}

strace输出(reader程序)

open("/dev/slot1", O_RDONLY)          = 3
ioctl(3, MSG_SLOT_SET_CHANNEL, 0x7ffdxxxx) = 0
read(3, "", 1024)                     = -1 EAGAIN (Resource temporarily unavailable)
close(3)                               = 0
exit_group(1)                          = ?

问题排查与解决方案

可能原因

  1. 通道查找逻辑不一致:写入时find_or_create_channel与读取时find_channel的实现存在差异,导致读取时找到新初始化的空节点(而非写入的节点),比如二叉树通道号匹配条件错误、次设备号未正确关联到对应device_data实例。
  2. 并发访问未加锁:读写操作未使用锁保护通道数据,多进程访问时内存未同步,读取到旧的bytes值。
  3. 内存可见性问题:curr_channel->bytes未用volatile修饰,或未添加内存屏障,导致内核读取到缓存中的旧值。
  4. 节点内存未持久化:通道节点使用临时栈内存分配,或在release函数中被错误释放。

排查建议

  1. 验证节点一致性:在读写函数中添加printk打印节点内存地址、次设备号、通道号,确认读写操作访问的是同一个节点:
    // 写入时
    printk(KERN_INFO "Write: dev_num=%d, channel=%d, node=%p, bytes=%zu", dev_num, channel_id, curr_channel, curr_channel->bytes);
    // 读取时
    printk(KERN_INFO "Read: dev_num=%d, channel=%d, node=%p, bytes=%zu", dev_num, channel_id, curr_channel, curr_channel->bytes);
    
  2. 添加锁保护:在device_data中添加互斥锁,读写通道时加锁:
    typedef struct device_data {
        channel_node_t* channel_root;
        struct mutex lock; // 新增互斥锁
    } device_data_t;
    
    // 初始化设备时
    mutex_init(&dev_data->lock);
    
    // 读写函数中
    mutex_lock(&dev_data->lock);
    // 查找/操作通道逻辑
    mutex_unlock(&dev_data->lock);
    
  3. 检查通道节点生命周期:确保通道节点使用kmalloc分配内存,且仅在模块卸载时统一释放,而非在release函数中清理。
  4. 验证inode关联:检查file_operations的open函数,确认每个次设备号对应独立的device_data实例,未出现共享错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:19:51