rcu(READ COPY UPDATE)
a下のbのnameを更新するrcuのサンプルです。cにbを読込み更新し、rcuコールバックでcと差し替えます。リストの要素はstatic変数としていますが、実際は動的に取得し、rcuコールバックで解放する処理が実装されます。
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の実際の利用は、リント要素の削除に特化しており、要素の更新は、メモリの取得/解放の負荷を考慮してか、ロックでの実装が主のようです。