1. 概述
RCU (Read-Copy Update) 是Linux内核中一种高效的同步机制,特别适合读多写少的场景。本文介绍RCU的原理、使用方法及在RK3588驱动开发中的应用。
1.1 RCU特点
- 无锁读取:读者无需加锁,性能极高
- 延迟释放:等待所有读者完成后才释放旧数据
- 适用场景:读多写少(读写比例 > 10:1)
- 零开销读:读操作几乎无性能损耗
1.2 RCU vs 其他锁机制
| 机制 | 读性能 | 写性能 | 适用场景 |
|---|---|---|---|
| RCU | 极高 | 较低 | 读多写少 |
| 读写锁 | 中等 | 中等 | 读写均衡 |
| 自旋锁 | 低 | 中等 | 临界区短 |
| 互斥锁 | 低 | 低 | 可睡眠场景 |
2. RCU原理
2.1 基本思想
读者:
1. rcu_read_lock() // 标记进入读临界区
2. 访问共享数据 // 无锁读取
3. rcu_read_unlock() // 标记离开读临界区
写者:
1. 复制旧数据
2. 修改副本
3. 替换指针(原子操作)
4. 等待所有读者完成(宽限期)
5. 释放旧数据
2.2 宽限期(Grace Period)
时间线:
T0: 写者开始更新
T1: 写者替换指针
T2: 读者A进入临界区(看到旧数据)
T3: 读者B进入临界区(看到新数据)
T4: 读者A离开临界区
T5: 宽限期结束,释放旧数据
2.3 内存屏障
RCU依赖内存屏障保证顺序:
rcu_assign_pointer(): 写屏障rcu_dereference(): 读屏障
3. RCU API详解
3.1 读者API
// 进入RCU读临界区
rcu_read_lock();
// 访问RCU保护的指针
ptr = rcu_dereference(global_ptr);
// 离开RCU读临界区
rcu_read_unlock();
注意事项:
- 读临界区内不能睡眠
- 读临界区应尽量短
- 可以嵌套使用
3.2 写者API
// 分配新数据
new_data = kmalloc(sizeof(*new_data), GFP_KERNEL);
// 复制并修改
memcpy(new_data, old_data, sizeof(*new_data));
new_data->value = new_value;
// 原子替换指针
rcu_assign_pointer(global_ptr, new_data);
// 等待宽限期
synchronize_rcu();
// 释放旧数据
kfree(old_data);
3.3 常用API
// 读者API
rcu_read_lock() // 进入读临界区
rcu_read_unlock() // 离开读临界区
rcu_dereference(p) // 读取RCU保护的指针
// 写者API
rcu_assign_pointer(p, v) // 原子更新指针
synchronize_rcu() // 同步等待宽限期
call_rcu(head, func) // 异步等待宽限期
// 链表API
list_for_each_entry_rcu() // RCU遍历链表
list_add_rcu() // RCU添加节点
list_del_rcu() // RCU删除节点
4. RCU链表示例
4.1 数据结构定义
// 设备信息结构
struct device_info {
int id;
char name[32];
int status;
struct list_head list;
struct rcu_head rcu;
};
// 全局链表头
static LIST_HEAD(device_list);
static DEFINE_SPINLOCK(device_lock); // 保护写操作
4.2 读操作(无锁)
struct device_info *find_device_rcu(int id)
{
struct device_info *dev;
rcu_read_lock();
list_for_each_entry_rcu(dev, &device_list, list) {
if (dev->id == id) {
rcu_read_unlock();
return dev;
}
}
rcu_read_unlock();
return NULL;
}
// 遍历所有设备
void print_all_devices(void)
{
struct device_info *dev;
rcu_read_lock();
list_for_each_entry_rcu(dev, &device_list, list) {
printk("Device %d: %s (status=%d)\n",
dev->id, dev->name, dev->status);
}
rcu_read_unlock();
}
4.3 写操作(需要锁)
// 添加设备
int add_device_rcu(int id, const char *name)
{
struct device_info *dev;
dev = kmalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->id = id;
strncpy(dev->name, name, sizeof(dev->name) - 1);
dev->status = 1;
spin_lock(&device_lock);
list_add_rcu(&dev->list, &device_list);
spin_unlock(&device_lock);
return 0;
}
// RCU回调函数
static void device_free_rcu(struct rcu_head *head)
{
struct device_info *dev = container_of(head, struct device_info, rcu);
kfree(dev);
}
// 删除设备
int remove_device_rcu(int id)
{
struct device_info *dev;
spin_lock(&device_lock);
list_for_each_entry(dev, &device_list, list) {
if (dev->id == id) {
list_del_rcu(&dev->list);
spin_unlock(&device_lock);
// 异步释放
call_rcu(&dev->rcu, device_free_rcu);
return 0;
}
}
spin_unlock(&device_lock);
return -ENOENT;
}
// 更新设备(Copy-Update)
int update_device_rcu(int id, int new_status)
{
struct device_info *old_dev, *new_dev;
spin_lock(&device_lock);
list_for_each_entry(old_dev, &device_list, list) {
if (old_dev->id == id) {
// 分配新节点
new_dev = kmalloc(sizeof(*new_dev), GFP_ATOMIC);
if (!new_dev) {
spin_unlock(&device_lock);
return -ENOMEM;
}
// 复制并修改
memcpy(new_dev, old_dev, sizeof(*new_dev));
new_dev->status = new_status;
// 替换节点
list_replace_rcu(&old_dev->list, &new_dev->list);
spin_unlock(&device_lock);
// 异步释放旧节点
call_rcu(&old_dev->rcu, device_free_rcu);
return 0;
}
}
spin_unlock(&device_lock);
return -ENOENT;
}
5. RCU指针示例
5.1 简单指针保护
struct config {
int value1;
int value2;
char name[32];
};
static struct config __rcu *global_config;
// 读取配置(无锁)
int get_config_value(void)
{
struct config *cfg;
int value;
rcu_read_lock();
cfg = rcu_dereference(global_config);
if (cfg)
value = cfg->value1;
else
value = -1;
rcu_read_unlock();
return value;
}
// 更新配置
int update_config(int v1, int v2, const char *name)
{
struct config *old_cfg, *new_cfg;
new_cfg = kmalloc(sizeof(*new_cfg), GFP_KERNEL);
if (!new_cfg)
return -ENOMEM;
new_cfg->value1 = v1;
new_cfg->value2 = v2;
strncpy(new_cfg->name, name, sizeof(new_cfg->name) - 1);
old_cfg = global_config;
rcu_assign_pointer(global_config, new_cfg);
synchronize_rcu();
kfree(old_cfg);
return 0;
}
5.2 数组保护
#define MAX_DEVICES 256
struct device_entry {
int id;
char name[32];
};
static struct device_entry __rcu *device_array[MAX_DEVICES];
// 读取设备
struct device_entry *get_device(int index)
{
struct device_entry *dev;
if (index >= MAX_DEVICES)
return NULL;
rcu_read_lock();
dev = rcu_dereference(device_array[index]);
rcu_read_unlock();
return dev;
}
// 更新设备
int set_device(int index, int id, const char *name)
{
struct device_entry *old_dev, *new_dev;
if (index >= MAX_DEVICES)
return -EINVAL;
new_dev = kmalloc(sizeof(*new_dev), GFP_KERNEL);
if (!new_dev)
return -ENOMEM;
new_dev->id = id;
strncpy(new_dev->name, name, sizeof(new_dev->name) - 1);
old_dev = device_array[index];
rcu_assign_pointer(device_array[index], new_dev);
synchronize_rcu();
kfree(old_dev);
return 0;
}
6. 完整驱动示例
6.1 设备管理驱动
// rcu_device.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/spinlock.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
struct device_node {
int id;
char name[32];
int value;
struct list_head list;
struct rcu_head rcu;
};
static LIST_HEAD(device_list);
static DEFINE_SPINLOCK(list_lock);
// RCU回调
static void device_free_rcu(struct rcu_head *head)
{
struct device_node *node = container_of(head, struct device_node, rcu);
pr_info("RCU: Freeing device %d\n", node->id);
kfree(node);
}
// 添加设备
static int device_add(int id, const char *name, int value)
{
struct device_node *node;
node = kmalloc(sizeof(*node), GFP_KERNEL);
if (!node)
return -ENOMEM;
node->id = id;
strncpy(node->name, name, sizeof(node->name) - 1);
node->value = value;
spin_lock(&list_lock);
list_add_tail_rcu(&node->list, &device_list);
spin_unlock(&list_lock);
pr_info("RCU: Added device %d: %s\n", id, name);
return 0;
}
// 删除设备
static int device_remove(int id)
{
struct device_node *node;
spin_lock(&list_lock);
list_for_each_entry(node, &device_list, list) {
if (node->id == id) {
list_del_rcu(&node->list);
spin_unlock(&list_lock);
call_rcu(&node->rcu, device_free_rcu);
pr_info("RCU: Removed device %d\n", id);
return 0;
}
}
spin_unlock(&list_lock);
return -ENOENT;
}
// 查找设备
static struct device_node *device_find(int id)
{
struct device_node *node;
rcu_read_lock();
list_for_each_entry_rcu(node, &device_list, list) {
if (node->id == id) {
rcu_read_unlock();
return node;
}
}
rcu_read_unlock();
return NULL;
}
// proc文件显示
static int devices_show(struct seq_file *m, void *v)
{
struct device_node *node;
seq_printf(m, "ID\tName\t\tValue\n");
seq_printf(m, "--------------------------------\n");
rcu_read_lock();
list_for_each_entry_rcu(node, &device_list, list) {
seq_printf(m, "%d\t%-16s%d\n", node->id, node->name, node->value);
}
rcu_read_unlock();
return 0;
}
static int devices_open(struct inode *inode, struct file *file)
{
return single_open(file, devices_show, NULL);
}
static const struct proc_ops devices_fops = {
.proc_open = devices_open,
.proc_read = seq_read,
.proc_lseek = seq_lseek,
.proc_release = single_release,
};
static int __init rcu_device_init(void)
{
// 创建proc文件
proc_create("rcu_devices", 0, NULL, &devices_fops);
// 添加测试设备
device_add(1, "device1", 100);
device_add(2, "device2", 200);
device_add(3, "device3", 300);
pr_info("RCU device module loaded\n");
return 0;
}
static void __exit rcu_device_exit(void)
{
struct device_node *node, *tmp;
remove_proc_entry("rcu_devices", NULL);
// 清理所有设备
spin_lock(&list_lock);
list_for_each_entry_safe(node, tmp, &device_list, list) {
list_del_rcu(&node->list);
call_rcu(&node->rcu, device_free_rcu);
}
spin_unlock(&list_lock);
// 等待所有RCU回调完成
rcu_barrier();
pr_info("RCU device module unloaded\n");
}
module_init(rcu_device_init);
module_exit(rcu_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RCU Device Management Example");
7. 性能对比测试
7.1 测试代码
// 读写锁版本
static DEFINE_RWLOCK(rwlock);
static void rwlock_read_test(void)
{
int i;
ktime_t start, end;
start = ktime_get();
for (i = 0; i < 1000000; i++) {
read_lock(&rwlock);
// 读取数据
read_unlock(&rwlock);
}
end = ktime_get();
pr_info("RWLock read: %lld ns\n", ktime_to_ns(ktime_sub(end, start)));
}
// RCU版本
static void rcu_read_test(void)
{
int i;
ktime_t start, end;
start = ktime_get();
for (i = 0; i < 1000000; i++) {
rcu_read_lock();
// 读取数据
rcu_read_unlock();
}
end = ktime_get();
pr_info("RCU read: %lld ns\n", ktime_to_ns(ktime_sub(end, start)));
}
7.2 性能结果
测试结果(100万次读操作):
- RWLock: 150ms
- RCU: 5ms
RCU读性能提升:30倍
8. 使用注意事项
8.1 适用场景
✅ 适合使用RCU:
- 读操作远多于写操作(10:1以上)
- 数据结构较小,复制开销低
- 读者不需要长时间持有数据
- 可以接受写操作的延迟
❌ 不适合使用RCU:
- 读写操作频率相当
- 数据结构很大,复制开销高
- 需要立即释放旧数据
- 读者需要长时间持有数据
8.2 常见错误
// 错误1:读临界区内睡眠
rcu_read_lock();
ptr = rcu_dereference(global_ptr);
msleep(100); // 错误!不能睡眠
rcu_read_unlock();
// 错误2:未使用rcu_dereference
rcu_read_lock();
ptr = global_ptr; // 错误!缺少内存屏障
rcu_read_unlock();
// 错误3:未使用rcu_assign_pointer
global_ptr = new_ptr; // 错误!缺少内存屏障
// 错误4:过早释放
rcu_assign_pointer(global_ptr, new_ptr);
kfree(old_ptr); // 错误!应该等待宽限期
8.3 调试技巧
# 启用RCU调试
echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress
# 查看RCU统计
cat /proc/rcu/rcudata
# 查看RCU宽限期
cat /sys/kernel/debug/rcu/rcu_data
9. 总结
RCU机制要点:
- 核心思想:读者无锁,写者Copy-Update
- 关键API:rcu_read_lock/unlock, rcu_dereference, rcu_assign_pointer
- 适用场景:读多写少(10:1以上)
- 性能优势:读操作性能提升10-30倍
- 注意事项:读临界区不能睡眠,必须使用专用API