iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 13
0
自我挑戰組

Linux Inside - Synchronization & Interrupt系列 第 16

RCU API 原始碼閱讀

我一直在找 API 實作在哪個檔案,翻超久才找到==,都在 這個檔案 include/linux/rcupdate.h

read_lock & read_unlock

  • read_lockread_unlock 非常單純,只是禁止搶佔而已
static __always_inline void rcu_read_lock(void)
{
	__rcu_read_lock();
	__acquire(RCU);
	rcu_lock_acquire(&rcu_lock_map);
	RCU_LOCKDEP_WARN(!rcu_is_watching(),
			 "rcu_read_lock() used illegally while idle");
}

static inline void __rcu_read_lock(void)
{
	preempt_disable();
}

static inline void __rcu_read_unlock(void)
{
	preempt_enable();
}

Sparse

參考 内核工具 – Sparse 简介

__acquire(RCU) 中的 RCU 到底是什麼???並沒有把他傳入涵式阿,到處都找不到宣告,我一開始還猜測這是給 reclamier 看的數字,查資料後才發現,根本不是我想像的那樣== __acquire 是 「Sparse」這個工具裡面的涵式,他的作用是幫變數 Reference 加一,以下使用範例程式來說明

麻煩自行安裝 Sparse 程式以利測試

sudo apt install sparse
  • sparse.c
#include <stdio.h>

#define __acquire(x) __context__(x,1)
#define __release(x) __context__(x,-1)

int main(int argc, char *argv[])
{
    __acquire(lock);
    __release(lock);            /* 注释掉这一句 sparse 就会报错 */
    return 0;
}

這是 Sparse 指令執行方法

sparse sparse.c

注意 __acquire 需要與 __release 成對使用,你有沒有宣告這個 lock 變數都無所謂,他的重點在檢查你有沒有成對使用

Kernel 中的 __acquire 相關 Macro 宣告在 include/linux/compiler_types.h 中,有興趣的話可以自行搭配參考文章深入閱讀

rcu_assign_pointer

#define rcu_check_sparse(p, space) \
	((void)(((typeof(*p) space *)p) == p))

/*
 * @p: pointer to assign to
 * @v: value to assign (publish)
 * */
#define rcu_assign_pointer(p, v)					      \
do {									      \
	uintptr_t _r_a_p__v = (uintptr_t)(v);				      \
	rcu_check_sparse(p, __rcu);					      \
									      \
	if (__builtin_constant_p(v) && (_r_a_p__v) == (uintptr_t)NULL)	      \
		WRITE_ONCE((p), (typeof(p))(_r_a_p__v));		      \
	else								      \
		smp_store_release(&p, RCU_INITIALIZER((typeof(p))_r_a_p__v)); \
} while (0)

__rcu

這邊真是......又有一個變數完全找不到人影 __rcu,搜尋原始碼的時候發現一堆檔案在宣告變數的時候,也都會加上他,類似這樣,那我心裡大概就有個底了,八成又是 GCC Attributes 包裝成 Macro

static struct xfrm4_protocol __rcu *esp4_handlers __read_mostly;

一查後果然是@@,但看到這份檔案,就該明白這個又是使用「Sparse」在做檢查了,對我們一般程式執行沒有太大的影響

參考 linux 下__iomem 解析

# define __rcu		__attribute__((noderef, address_space(4)))
  • __attribute__ 就是 GNU attribute 的語法
  • noderef 指標位置
  • address_space 所在空間
    • address_space(0) Kernel Space
    • address_space(1) User Space
    • address_space(2) I/O Space(Device Space)
    • address_space(3) CPU Space
    • address_space(4) RCU

比如以下面這句變數宣告,就是在講這個資料是 RCU-Protected 哦,使用 Sparse 進行分析時,如果不是透過 rcu_assign_pointer rcu_deference 進行操作就會報錯

static struct xfrm4_protocol __rcu *esp4_handlers __read_mostly;

而我們 rcu_check_sparse(p, v) 則是在檢查這個指標是否是以 __rcu 的形式進行宣告的指標

rcu_deference

#define __rcu_dereference_check(p, c, space) \
({ \
	/* Dependency order vs. p above. */ \
	typeof(*p) *________p1 = (typeof(*p) *__force)READ_ONCE(p); \
	RCU_LOCKDEP_WARN(!(c), "suspicious rcu_dereference_check() usage"); \
	rcu_check_sparse(p, space); \
	((typeof(*p) __force __kernel *)(________p1)); \
})

#define rcu_dereference_check(p, c) \
	__rcu_dereference_check((p), (c) || rcu_read_lock_held(), __rcu)
    
#define rcu_dereference(p) rcu_dereference_check(p, 0)

這邊就只是透過 READ_ONCE 把資料取出來,然後檢查一下指標位置

注意:這邊都沒有講到 Memory Barrier,因為 WRITE_ONCE READ_ONCE 已經有使用 Memory Barrier 了,他們都是宣告在 include/linux/compiler.h 中,有可以自己閱讀

synchronize_rcu

這個涵式並不是實作在 include/linux/rcupdate.h 中,被放在 kernel/rcu/tiny.c

void synchronize_rcu(void)
{
	RCU_LOCKDEP_WARN(lock_is_held(&rcu_bh_lock_map) ||
			 lock_is_held(&rcu_lock_map) ||
			 lock_is_held(&rcu_sched_lock_map),
			 "Illegal synchronize_rcu() in RCU read-side critical section");
}

可以看到他只是依序檢查 Lock 是否有被持有,至於為什麼要檢查三次,是因為 RCU 其實也像是其他 Lock ,針對不同的 Interrupt 還是有作一些分別,不過因為我還不了解 Softirq,也不知道怎麼介紹

lock_is_held 是 lockdep 裡面的涵式,反正就是 Kernel 用來檢查有沒有 Deadlock 的工具,只是我們這邊沒有在跟你作 Spinlock 的,只是單純利用他的涵式來確認還有沒有人在使用

call_rcu

他也是被放在 kernel/rcu/tiny.c

/*
 * Post an RCU callback to be invoked after the end of an RCU grace
 * period.  But since we have but one CPU, that would be after any
 * quiescent state.
 */
void call_rcu(struct rcu_head *head, rcu_callback_t func)
{
	unsigned long flags;

	debug_rcu_head_queue(head);
	head->func = func;
	head->next = NULL;

	local_irq_save(flags);
	*rcu_ctrlblk.curtail = head;
	rcu_ctrlblk.curtail = &head->next;
	local_irq_restore(flags);

	if (unlikely(is_idle_task(current))) {
		/* force scheduling for rcu_qs() */
		resched_cpu(0);
	}
}
/* Global control variables for rcupdate callback mechanism. */
struct rcu_ctrlblk {
	struct rcu_head *rcucblist;	/* List of pending callbacks (CBs). */
	struct rcu_head **donetail;	/* ->next pointer of last "done" CB. */
	struct rcu_head **curtail;	/* ->next pointer of last CB. */
};

/* Definition for rcupdate control block. */
static struct rcu_ctrlblk rcu_ctrlblk = {
	.donetail	= &rcu_ctrlblk.rcucblist,
	.curtail	= &rcu_ctrlblk.rcucblist,
};

就 ... 是讓 Callback Function 去排隊,都放在 rcu_ctrlblk 中,等 Grace Period 執行完,就會進行調用

Others

這邊了解了 RCU Core API 的實作原理,但一般在 Kernel 中並不會拿這裡的 Code 出來修改,而是將其包裝成更高層次的東西,以 List 的形式進行 RCU 的實現,在查閱過程還有一個很驚奇的發現,有一個資料結構叫做 rht 指的是 RCU Hash Table,我真的沒想到 Kernel 中存在 Concurrent Hash Table ,但是直接 Google 「Concurrent Hash Table」 什麼東西都查不到==


上一篇
RCU(Read, Copy, Update) - What is RCU
下一篇
list_head & hlist_head & RCU
系列文
Linux Inside - Synchronization & Interrupt18

尚未有邦友留言

立即登入留言