如何实现用户空间与Linux内核哈希表模块的交互操作?
实现用户空间与内核键值存储模块的交互方案
我来帮你梳理下实现用户空间与内核键值存储模块交互的几种常用方案,结合你现有的哈希表结构来具体说明——咱们先从补全哈希表的基础功能开始,再讲交互逻辑。
一、先补全哈希表的基础实现
你目前的代码只有哈希表节点的结构体,还需要初始化哈希桶、实现核心的增删改查逻辑,这是交互的基础:
#include <linux/init.h> #include <linux/module.h> #include <linux/hash.h> #include <linux/slab.h> #include <linux/uaccess.h> #define HASH_SIZE 256 #define MAX_KEY_LEN 100 // 哈希表节点结构体 struct hashtable { char name[MAX_KEY_LEN]; int data; struct hlist_node my_hash_list; }; // 哈希桶数组 static struct hlist_head hash_table[HASH_SIZE]; // 用于并发保护的自旋锁 static DEFINE_SPINLOCK(hash_lock); // 简单哈希函数:基于内核的jhash static unsigned int hash_func(const char *key) { return jhash(key, strlen(key), 0) % HASH_SIZE; } // 查找指定key的节点 static struct hashtable* hash_find(const char *key) { unsigned int idx = hash_func(key); struct hashtable *node; hlist_for_each_entry(node, &hash_table[idx], my_hash_list) { if (!strcmp(node->name, key)) { return node; } } return NULL; } // 添加/更新键值对 static int hash_set(const char *key, int data) { unsigned int idx = hash_func(key); struct hashtable *node; unsigned long flags; spin_lock_irqsave(&hash_lock, flags); node = hash_find(key); if (node) { // key已存在,更新数据 node->data = data; } else { // 新建节点 node = kmalloc(sizeof(struct hashtable), GFP_KERNEL); if (!node) { spin_unlock_irqrestore(&hash_lock, flags); return -ENOMEM; } strncpy(node->name, key, MAX_KEY_LEN-1); node->name[MAX_KEY_LEN-1] = '\0'; node->data = data; hlist_add_head(&node->my_hash_list, &hash_table[idx]); } spin_unlock_irqrestore(&hash_lock, flags); return 0; } // 删除键值对 static int hash_delete(const char *key) { unsigned int idx = hash_func(key); struct hashtable *node; unsigned long flags; spin_lock_irqsave(&hash_lock, flags); node = hash_find(key); if (node) { hlist_del(&node->my_hash_list); kfree(node); spin_unlock_irqrestore(&hash_lock, flags); return 0; } spin_unlock_irqrestore(&hash_lock, flags); return -ENOENT; } // 初始化哈希桶 static int hash_table_init(void) { int i; for (i = 0; i < HASH_SIZE; i++) { INIT_HLIST_HEAD(&hash_table[i]); } pr_info("Hash table module initialized\n"); return 0; } static void hash_table_exit(void) { int i; struct hashtable *node, *tmp; for (i = 0; i < HASH_SIZE; i++) { hlist_for_each_entry_safe(node, tmp, &hash_table[i], my_hash_list) { hlist_del(&node->my_hash_list); kfree(node); } } pr_info("Hash table module exited\n"); } module_init(hash_table_init); module_exit(hash_table_exit); MODULE_LICENSE("GPL");
二、实现用户空间交互的几种方案
补全基础哈希表后,就可以选择合适的方式让用户空间程序调用这些功能了,这里推荐两种最常用的方案:
1. 使用procfs实现简单读写(适合调试/轻量场景)
procfs是内核提供的虚拟文件系统,用户可以通过cat/echo等命令直接和内核模块交互,实现成本低:
在上面的代码中添加proc相关逻辑:
#include <linux/proc_fs.h> #include <linux/seq_file.h> // 处理proc文件的读操作(用户执行cat /proc/hash_kv) static int hash_proc_show(struct seq_file *m, void *v) { int i; struct hashtable *node; unsigned long flags; spin_lock_irqsave(&hash_lock, flags); seq_puts(m, "Key\tValue\n"); for (i = 0; i < HASH_SIZE; i++) { hlist_for_each_entry(node, &hash_table[i], my_hash_list) { seq_printf(m, "%s\t%d\n", node->name, node->data); } } spin_unlock_irqrestore(&hash_lock, flags); return 0; } // 处理proc文件的写操作(用户执行echo "set key 100" > /proc/hash_kv) static ssize_t hash_proc_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) { char cmd[200]; char op[10], key[MAX_KEY_LEN]; int data; if (count >= sizeof(cmd)) { return -EINVAL; } if (copy_from_user(cmd, buf, count)) { return -EFAULT; } cmd[count] = '\0'; // 解析命令:支持 set <key> <value> / delete <key> if (sscanf(cmd, "%s %s %d", op, key, &data) == 3 && !strcmp(op, "set")) { return hash_set(key, data) ? -EINVAL : count; } else if (sscanf(cmd, "%s %s", op, key) == 2 && !strcmp(op, "delete")) { return hash_delete(key) ? -ENOENT : count; } else { return -EINVAL; } } // proc文件操作结构体 static const struct proc_ops hash_proc_ops = { .proc_show = hash_proc_show, .proc_write = hash_proc_write, }; // 在init函数中创建proc文件 static int hash_table_init(void) { // ... 原有的哈希桶初始化代码 ... proc_create("hash_kv", 0666, NULL, &hash_proc_ops); pr_info("Hash table module initialized\n"); return 0; } // 在exit函数中删除proc文件 static void hash_table_exit(void) { // ... 原有的哈希表清理代码 ... remove_proc_entry("hash_kv", NULL); pr_info("Hash table module exited\n"); }
用户空间使用示例:
# 设置键值对 echo "set test_key 123" > /proc/hash_kv # 查询所有键值对 cat /proc/hash_kv # 删除键值对 echo "delete test_key" > /proc/hash_kv
2. 使用字符设备+ioctl(适合复杂/高性能场景)
如果需要更灵活的交互(比如批量操作、自定义数据结构),字符设备+ioctl是更合适的选择:
在基础哈希表代码中添加字符设备逻辑:
#include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #define HASH_DEV_NAME "hash_kv_dev" #define HASH_IOCTL_MAGIC 'H' // 定义ioctl命令:获取值、设置值、删除值 #define HASH_IOCTL_SET _IOW(HASH_IOCTL_MAGIC, 1, struct hash_op) #define HASH_IOCTL_GET _IOR(HASH_IOCTL_MAGIC, 2, struct hash_op) #define HASH_IOCTL_DEL _IOW(HASH_IOCTL_MAGIC, 3, struct hash_op) // 用户空间与内核空间传递数据的结构体 struct hash_op { char key[MAX_KEY_LEN]; int data; int ret; // 内核返回的结果:0成功,负数为错误码 }; static dev_t hash_dev_num; static struct cdev hash_cdev; static struct class *hash_class; // ioctl处理函数 static long hash_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct hash_op op; struct hashtable *node; unsigned long flags; if (_IOC_TYPE(cmd) != HASH_IOCTL_MAGIC) { return -ENOTTY; } if (_IOC_NR(cmd) > 3) { return -ENOTTY; } if (copy_from_user(&op, (struct hash_op __user *)arg, sizeof(op))) { return -EFAULT; } switch (cmd) { case HASH_IOCTL_SET: op.ret = hash_set(op.key, op.data); break; case HASH_IOCTL_GET: spin_lock_irqsave(&hash_lock, flags); node = hash_find(op.key); if (node) { op.data = node->data; op.ret = 0; } else { op.ret = -ENOENT; } spin_unlock_irqrestore(&hash_lock, flags); break; case HASH_IOCTL_DEL: op.ret = hash_delete(op.key); break; default: return -ENOTTY; } if (copy_to_user((struct hash_op __user *)arg, &op, sizeof(op))) { return -EFAULT; } return 0; } // 字符设备操作结构体 static const struct file_operations hash_fops = { .owner = THIS_MODULE, .unlocked_ioctl = hash_ioctl, .open = simple_open, .release = simple_release, }; // 在init函数中注册字符设备 static int hash_table_init(void) { // ... 原有的哈希桶初始化代码 ... // 动态申请设备号 if (alloc_chrdev_region(&hash_dev_num, 0, 1, HASH_DEV_NAME) < 0) { pr_err("Failed to allocate chrdev region\n"); return -ENODEV; } // 初始化cdev cdev_init(&hash_cdev, &hash_fops); hash_cdev.owner = THIS_MODULE; if (cdev_add(&hash_cdev, hash_dev_num, 1) < 0) { pr_err("Failed to add cdev\n"); unregister_chrdev_region(hash_dev_num, 1); return -ENODEV; } // 创建设备类和节点 hash_class = class_create(THIS_MODULE, HASH_DEV_NAME); if (IS_ERR(hash_class)) { pr_err("Failed to create class\n"); cdev_del(&hash_cdev); unregister_chrdev_region(hash_dev_num, 1); return PTR_ERR(hash_class); } device_create(hash_class, NULL, hash_dev_num, NULL, HASH_DEV_NAME); pr_info("Hash table module initialized\n"); return 0; } // 在exit函数中清理字符设备 static void hash_table_exit(void) { // ... 原有的哈希表清理代码 ... device_destroy(hash_class, hash_dev_num); class_destroy(hash_class); cdev_del(&hash_cdev); unregister_chrdev_region(hash_dev_num, 1); pr_info("Hash table module exited\n"); }
用户空间C程序示例:
#include <stdio.h> #include <fcntl.h> #include <sys/ioctl.h> #define HASH_DEV_PATH "/dev/hash_kv_dev" #define HASH_IOCTL_MAGIC 'H' #define HASH_IOCTL_SET _IOW(HASH_IOCTL_MAGIC, 1, struct hash_op) #define HASH_IOCTL_GET _IOR(HASH_IOCTL_MAGIC, 2, struct hash_op) #define HASH_IOCTL_DEL _IOW(HASH_IOCTL_MAGIC, 3, struct hash_op) struct hash_op { char key[100]; int data; int ret; }; int main() { int fd = open(HASH_DEV_PATH, O_RDWR); if (fd < 0) { perror("Failed to open device"); return 1; } struct hash_op op; // 设置键值对 snprintf(op.key, sizeof(op.key), "test_key"); op.data = 456; ioctl(fd, HASH_IOCTL_SET, &op); if (op.ret != 0) { printf("Set failed, ret: %d\n", op.ret); } // 获取键值对 snprintf(op.key, sizeof(op.key), "test_key"); ioctl(fd, HASH_IOCTL_GET, &op); if (op.ret == 0) { printf("Get value: %d\n", op.data); } else { printf("Get failed, ret: %d\n", op.ret); } // 删除键值对 snprintf(op.key, sizeof(op.key), "test_key"); ioctl(fd, HASH_IOCTL_DEL, &op); if (op.ret != 0) { printf("Delete failed, ret: %d\n", op.ret); } close(fd); return 0; }
三、关键注意事项
- 内存安全:内核空间不能直接访问用户空间内存,必须使用
copy_from_user/copy_to_user进行数据拷贝。 - 并发保护:哈希表是共享资源,必须用自旋锁(适合短时间操作)或互斥锁(适合长时间操作)保护,避免竞态条件。
- 错误处理:所有内核操作都要检查返回值,返回合适的错误码(比如
-ENOMEM表示内存分配失败,-ENOENT表示键不存在)。 - 权限控制:可以通过设置proc文件或字符设备的权限,限制哪些用户能访问模块功能。
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

