内核模块message_slot的read函数返回资源临时不可用问题排查
内核模块message_slot读写不一致问题:写入后读取通道字节数为0
问题背景
开发message_slot字符设备内核模块,核心规格:
- 主设备号固定为235,支持最多256个次设备号
- 每个次设备对应独立设备文件,单设备最多支持2^20个通道
- 采用二叉树列表实现通道存储结构
已编译完成用户程序:message_writer.c(编译为sender)、message_reader.c(编译为reader)。
操作步骤与现象
执行流程:
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号通道。异常现象:
运行./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) = ?
问题排查与解决方案
可能原因
- 通道查找逻辑不一致:写入时
find_or_create_channel与读取时find_channel的实现存在差异,导致读取时找到新初始化的空节点(而非写入的节点),比如二叉树通道号匹配条件错误、次设备号未正确关联到对应device_data实例。 - 并发访问未加锁:读写操作未使用锁保护通道数据,多进程访问时内存未同步,读取到旧的
bytes值。 - 内存可见性问题:
curr_channel->bytes未用volatile修饰,或未添加内存屏障,导致内核读取到缓存中的旧值。 - 节点内存未持久化:通道节点使用临时栈内存分配,或在
release函数中被错误释放。
排查建议
- 验证节点一致性:在读写函数中添加
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); - 添加锁保护:在
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); - 检查通道节点生命周期:确保通道节点使用
kmalloc分配内存,且仅在模块卸载时统一释放,而非在release函数中清理。 - 验证inode关联:检查
file_operations的open函数,确认每个次设备号对应独立的device_data实例,未出现共享错误。
内容的提问来源于stack exchange,提问作者Dori Pardess
相关产品推荐
相关产品推荐

