rcu(READ COPY UPDATE)
Rev.4を表示中。最新版はこちら。
a下のbのnameを更新するrcuのサンプルです。cにbを読込み更新し、rcuコールバックでcと差し替えます。リストの要素はstatic変数としていますが、実際は動的に取得し、rcuコールバックで解放する処理が実装されます。
#include <linux/kernel.h>
#include <linux/rcupdate.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/swap.h>
struct hoge {
struct rcu_head hoge_rcu;
struct hoge *down;
char name[6];
struct hoge *rcu_tmp;
};
static struct hoge a;
static struct hoge b;
static struct hoge c;
static void hoge_rcu_callback(struct rcu_head *head);
static void printk_hoge(char* msg, struct hoge* a)
{
struct hoge *tmp;
tmp = a->down;
printk("%s:%s\n", msg, tmp->name);
}
static int __init hoge_rcu_init(void)
{
strcpy(a.name, "hoge1");
strcpy(b.name, "hoge2");
a.down= &b;
rcu_read_lock();
memcpy((char *)&c, (char *)a.down, sizeof(struct hoge));
strcpy(c.name, "hoge3");
a.rcu_tmp = &c;
rcu_read_unlock();
printk_hoge("before call rcu", &a);
call_rcu(&a.hoge_rcu, hoge_rcu_callback);
schedule_timeout_interruptible(100);
printk_hoge("after call rcu", &a);
return -1;
}
static void hoge_rcu_callback(struct rcu_head *head)
{
struct hoge *hoge =
container_of(head, struct hoge, hoge_rcu);
hoge->down = hoge->rcu_tmp;
}
MODULE_LICENSE("GPL");
module_init(hoge_rcu_init);
動作[root@localhost lkm]# dmesg : [ 4669.301854] before call rcu:hoge2 [ 4669.402079] after call rcu:hoge3rcuの初期化で、open_softirq()でソフト割り込みにrcu_process_callbacks()を設定します。rcuは厳密に言えば、タスクレットでなくソフト割り込みでの実装となります。(実装は同じですが。)
void __init rcu_init(void)
{
int cpu;
rcu_bootup_announce();
rcu_init_one(&rcu_sched_state, &rcu_sched_data);
rcu_init_one(&rcu_bh_state, &rcu_bh_data);
__rcu_init_preempt();
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
cpu_notifier(rcu_cpu_notify, 0);
for_each_online_cpu(cpu)
rcu_cpu_notify(NULL, CPU_UP_PREPARE, (void *)(long)cpu);
check_cpu_stall_init();
}
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ,
NR_SOFTIRQS
};
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
static void rcu_process_callbacks(struct softirq_action *unused)
{
__rcu_process_callbacks(&rcu_sched_ctrlblk);
__rcu_process_callbacks(&rcu_bh_ctrlblk);
rcu_preempt_process_callbacks();
}
call_rcu()は更新コールバック関数をrcu_headに設定し、rcu_sched_stateにリストし、ソフト割り込みから起動されるrcu_process_callbacks()が、このリスト下のhead->funcをコールします。
#define call_rcu call_rcu_sched
void call_rcu_sched(struct rcu_head *head, void (*func)(struct rcu_head *rcu))
{
__call_rcu(head, func, &rcu_sched_state);
}
EXPORT_SYMBOL_GPL(call_rcu_sched);
static void __call_rcu(struct rcu_head *head,
void (*func)(struct rcu_head *rcu),
struct rcu_ctrlblk *rcp)
{
unsigned long flags;
debug_rcu_head_queue(head);
head->func = func;
head->next = NULL;
local_irq_save(flags);
*rcp->curtail = head;
rcp->curtail = &head->next;
RCU_TRACE(rcp->qlen++);
local_irq_restore(flags);
}
***補足
読込み中は、rcu_read_lock()/rcu_read_unlock()します。rcu_read_lock()はプリエンプション禁止/rcu_read_unlock()は許可するだけで、プリエンプション禁止だと、ハード割込みによる非同期カーネルパスの偏移はありません。ソフト割込みの発信元は、ハード割込みです。従ってプリエンプション禁止の間は、rcu更新のコールバックが動作しません。rcuの実際の利用は、リント要素の削除に特化しており、要素の更新は、メモリの取得/解放の負荷を考慮してか、ロックでの実装が主のようです。






