<kernel v5.4>
RCU 초기화
rcu의 초기화는 rcu_init() 함수 및 rcu_nohz_init() 함수에서 이루어진다.
rcu_init()
kernel/rcu/tree.c
void __init rcu_init(void) { int cpu; rcu_early_boot_tests(); rcu_bootup_announce(); rcu_init_geometry(); rcu_init_one(); if (dump_tree) rcu_dump_rcu_node_tree(); if (use_softirq) open_softirq(RCU_SOFTIRQ, rcu_core_si); /* * We don't need protection against CPU-hotplug here because * this is called early in boot, before either interrupts * or the scheduler are operational. */ pm_notifier(rcu_pm_notify, 0); for_each_online_cpu(cpu) { rcutree_prepare_cpu(cpu); rcu_cpu_starting(cpu); rcutree_online_cpu(cpu); } /* Create workqueue for expedited GPs and for Tree SRCU. */ rcu_gp_wq = alloc_workqueue("rcu_gp", WQ_MEM_RECLAIM, 0); WARN_ON(!rcu_gp_wq); rcu_par_gp_wq = alloc_workqueue("rcu_par_gp", WQ_MEM_RECLAIM, 0); WARN_ON(!rcu_par_gp_wq); srcu_init(); }
rcu를 사용하기 위해 rcu 관련 구조체들을 초기화하고, cpu pm 변화에 따라 호출되는 콜백함수를 등록한다.
- 코드 라인 5에서 “Running RCU self tests” 메시지를 출력하고 모듈 파라미터 “rcutree.rcu_self_test”가 설정된 경우 테스트 콜백 하나를 call_rcu()와 call_srcu() 두 API를 사용하여 등록시켜 “RCU test callback executed %d” 메시지가 출력되게 한다.
- 코드 라인 7에서 RCU 설정들이 동작하는지 관련 메시지 로그를 출력한다. “Preemptible hierarchical RCU implementation.” 메시지를 시작으로 여러 정보들이 출력된다.
- 코드 라인 8에서 rcu_state 구조체 내부의 rcu_node 들에 대한 트리 기하를 구성하기 위한 설정 값들을 산출한다.
- 코드 라인 9에서 rcu_state이하 모든 rcu 노드 및 rcu_data들을 초기화하고 구성한다.
- 코드 라인 10~11에서 “dump_tree” 커널 파라미터가 설정된 경우 “rcu_node tree layout dump” 메시지 출력 이후에 노드 구성을 출력한다.
- 코드 라인 12~13에서 rcu softirq 벡터에 rcu_core_si() 함수가 호출되도록 대입한다.
- 코드 라인 20에서 pm(power management)의 suspend/resume 동작시 호출되도록 rcu_pm_notify() 함수를 등록한다.
- 코드 라인 21~25에서 각 online cpu 만큼 순회하며 rcu용 cpu 정보들을 초기화하고 시작시킨다.
- 코드 라인 28~31에서 급행 gp와 srcu tree용으로 워크큐를 할당한다.
- 코드 라인 32에서 srcu를 초기화한다.
early 부트에서의 테스트
rcu_early_boot_tests()
kernel/rcu/update.c
void rcu_early_boot_tests(void) { pr_info("Running RCU self tests\n"); if (rcu_self_test) early_boot_test_call_rcu(); rcu_test_sync_prims(); }
“Running RCU self tests” 메시지를 출력하고 모듈 파라미터 “rcutree.rcu_self_test”가 설정된 경우 테스트 콜백 하나를 call_rcu()와 call_srcu() 두 API를 사용하여 등록시켜 “RCU test callback executed %d” 메시지가 출력되게 한다.
early_boot_test_call_rcu()
kernel/rcu/update.c
static void early_boot_test_call_rcu(void) { static struct rcu_head head; static struct rcu_head shead; call_rcu(&head, test_callback); if (IS_ENABLED(CONFIG_SRCU)) call_srcu(&early_srcu, &shead, test_callback); }
test_callback()
kernel/rcu/update.c
static void test_callback(struct rcu_head *r) { rcu_self_test_counter++; pr_info("RCU test callback executed %d\n", rcu_self_test_counter); }
“RCU test callback executed %d” 메시지가 출력되는 테스트용 콜백이다.
rcu_test_sync_prims()
kernel/rcu/update.c
/* * Test each non-SRCU synchronous grace-period wait API. This is * useful just after a change in mode for these primitives, and * during early boot. */
void rcu_test_sync_prims(void) { if (!IS_ENABLED(CONFIG_PROVE_RCU)) return; synchronize_rcu(); synchronize_rcu_expedited(); }
싱크용 rcu 명령이 eayly boot에서 어떻게 동작하는지 호출해본다.
- early 부트에서 gp 대기 없이 아무런 처리를 하지 않아야 한다.
부트업 어나운스
rcu_bootup_announce()
kernel/rcu/tree_plugin.h
/* * Tell them what RCU they are running. */
static void __init rcu_bootup_announce(void) { pr_info("Preemptible hierarchical RCU implementation.\n"); rcu_bootup_announce_oddness(); }
RCU 설정들이 동작하는지 관련 메시지 로그를 출력한다. “Preemptible hierarchical RCU implementation.” 메시지를 시작으로 여러 정보들이 출력된다.
rcu_bootup_announce_oddness()
kernel/rcu/tree_plugin.h
/* * Check the RCU kernel configuration parameters and print informative * messages about anything out of the ordinary. */
static void __init rcu_bootup_announce_oddness(void) { if (IS_ENABLED(CONFIG_RCU_TRACE)) pr_info("\tRCU event tracing is enabled.\n"); if ((IS_ENABLED(CONFIG_64BIT) && RCU_FANOUT != 64) || (!IS_ENABLED(CONFIG_64BIT) && RCU_FANOUT != 32)) pr_info("\tCONFIG_RCU_FANOUT set to non-default value of %d.\n", RCU_FANOUT); if (rcu_fanout_exact) pr_info("\tHierarchical RCU autobalancing is disabled.\n"); if (IS_ENABLED(CONFIG_RCU_FAST_NO_HZ)) pr_info("\tRCU dyntick-idle grace-period acceleration is enabled.\n"); if (IS_ENABLED(CONFIG_PROVE_RCU)) pr_info("\tRCU lockdep checking is enabled.\n"); if (RCU_NUM_LVLS >= 4) pr_info("\tFour(or more)-level hierarchy is enabled.\n"); if (RCU_FANOUT_LEAF != 16) pr_info("\tBuild-time adjustment of leaf fanout to %d.\n", RCU_FANOUT_LEAF); if (rcu_fanout_leaf != RCU_FANOUT_LEAF) pr_info("\tBoot-time adjustment of leaf fanout to %d.\n", rcu_fanout_leaf); if (nr_cpu_ids != NR_CPUS) pr_info("\tRCU restricting CPUs from NR_CPUS=%d to nr_cpu_ids=%u.\n", NR_CPUS, nr_cpu_ids); #ifdef CONFIG_RCU_BOOST pr_info("\tRCU priority boosting: priority %d delay %d ms.\n", kthread_prio, CONFIG_RCU_BOOST_DELAY); #endif if (blimit != DEFAULT_RCU_BLIMIT) pr_info("\tBoot-time adjustment of callback invocation limit to %ld.\n", blimit); if (qhimark != DEFAULT_RCU_QHIMARK) pr_info("\tBoot-time adjustment of callback high-water mark to %ld.\n", qhimark); if (qlowmark != DEFAULT_RCU_QLOMARK) pr_info("\tBoot-time adjustment of callback low-water mark to %ld.\n", qlowmark); if (jiffies_till_first_fqs != ULONG_MAX) pr_info("\tBoot-time adjustment of first FQS scan delay to %ld jiffies.\n", jiffies_till_first_fqs); if (jiffies_till_next_fqs != ULONG_MAX) pr_info("\tBoot-time adjustment of subsequent FQS scan delay to %ld jiffies.\n", jiffies_till_next_fqs); if (jiffies_till_sched_qs != ULONG_MAX) pr_info("\tBoot-time adjustment of scheduler-enlistment delay to %ld jiffies.\n", jiffies_till_sched_qs)) ; if (rcu_kick_kthreads) pr_info("\tKick kthreads if too-long grace period.\n"); if (IS_ENABLED(CONFIG_DEBUG_OBJECTS_RCU_HEAD)) pr_info("\tRCU callback double-/use-after-free debug enabled.\n"); if (gp_preinit_delay) pr_info("\tRCU debug GP pre-init slowdown %d jiffies.\n", gp_preinit_delay); if (gp_init_delay) pr_info("\tRCU debug GP init slowdown %d jiffies.\n", gp_init_delay); if (gp_cleanup_delay) pr_info("\tRCU debug GP init slowdown %d jiffies.\n", gp_cleanup_delay); if (!use_softirq) pr_info("\tRCU_SOFTIRQ processing moved to rcuc kthreads.\n"); if (IS_ENABLED(CONFIG_RCU_EQS_DEBUG)) pr_info("\tRCU debug extended QS entry/exit.\n"); rcupdate_announce_bootup_oddness(); }
커널 config RCU 설정들에 대해 다음과 같은 로그를 출력한다.
- CONFIG_RCU_TRACE를 설정한 경우 “RCU event tracing is enabled.”
- CONFIG_RCU_FANOUT 값이 default(32bit=32, 64bit=64) 값과 다른 경우 “CONFIG_RCU_FANOUT set to non-default value of %d”
- CONFIG_RCU_FANOUT_EXACT가 설정된 경우 “Hierarchical RCU autobalancing is disabled.”
- CONFIG_RCU_FAST_NO_HZ가 설정된 경우 “RCU dyntick-idle grace-period acceleration is enabled.”
- CONFIG_PROVE_RCU가 설정된 경우 “RCU lockdep checking is enabled.”
- rcu 노드 레벨이 4 이상(0~4까지 가능) 설정된 경우 “Four-level hierarchy is enabled.”
- CONFIG_RCU_FANOUT_LEAF 값이 default(16) 값과 다른 경우 “Boot-time adjustment of leaf fanout to %d.”
- online cpu와 커널 컴파일 시 설정된 NR_CPUS와 다른 경우 “RCU restricting CPUs from NR_CPUS=%d to nr_cpu_ids=%d.”
- CONFIG_RCU_BOOST가 설정된 경우 “RCU priority boosting: priority %d delay %d ms.”
- blimit가 DEFAULT_RCU_BLIMIT(10)와 다른 경우 “Boot-time adjustment of callback invocation limit to %ld.”
- qhimark가 DEFAULT_RCU_QHIMARK(10000)와 다른 경우 “Boot-time adjustment of callback high-water mark to %ld.”
- qlowmark가 DEFAULT_RCU_QLOMARK(10)와 다른 경우 “Boot-time adjustment of callback low-water mark to %ld.”
- jiffies_till_first_fqs가 설정된 경우 “Boot-time adjustment of first FQS scan delay to %ld jiffies.”
- jiffies_till_next_fqs가 설정된 경우 “Boot-time adjustment of subsequent FQS scan delay to %ld jiffies.”
- jiffies_till_sched_qs가 설정된 경우 “Boot-time adjustment of scheduler-enlistment delay to %ld jiffies.”
- rcu_kick_kthreads가 설정된 경우 “Kick kthreads if too-long grace period.”
- CONFIG_DEBUG_OBJECTS_RCU_HEAD가 설정된 경우 “tRCU callback double-/use-after-free debug enabled.”
- gp_preinit_delay가 설정된 경우 “RCU debug GP pre-init slowdown %d jiffies.”
- gp_init_delay가 설정된 경우 “RCU debug GP init slowdown %d jiffies.”
- gp_cleanup_delay가 설정된 경우 “RCU debug GP init slowdown %d jiffies.”
- use_softirq(디폴트=1)가 off된 경우 “RCU_SOFTIRQ processing moved to rcuc kthreads.”
- CONFIG_RCU_EQS_DEBUG가 설정된 경우 “RCU debug extended QS entry/exit.”
rcupdate_announce_bootup_oddness()
kernel/rcu/update.c
/* * Print any significant non-default boot-time settings. */
void __init rcupdate_announce_bootup_oddness(void) { if (rcu_normal) pr_info("\tNo expedited grace period (rcu_normal).\n"); else if (rcu_normal_after_boot) pr_info("\tNo expedited grace period (rcu_normal_after_boot).\n"); else if (rcu_expedited) pr_info("\tAll grace periods are expedited (rcu_expedited).\n"); if (rcu_cpu_stall_suppress) pr_info("\tRCU CPU stall warnings suppressed (rcu_cpu_stall_suppress).\n"); if (rcu_cpu_stall_timeout != CONFIG_RCU_CPU_STALL_TIMEOUT) pr_info("\tRCU CPU stall warnings timeout set to %d (rcu_cpu_stall_timeout).\n", rcu_cpu_stall_timeout); rcu_tasks_bootup_oddness(); }
boot 타임 RCU 설정들에 대해 다음과 같은 로그를 출력한다.
- rcu_normal이 설정된 경우 “No expedited grace period (rcu_normal).”
- rcu_normal_after_boot가 설정된 경우 “No expedited grace period (rcu_normal_after_boot).”
- rcu_expedited가 설정된 경우 “All grace periods are expedited (rcu_expedited).”
- rcu_cpu_stall_suppress가 설정된 경우 “RCU CPU stall warnings suppressed (rcu_cpu_stall_suppress).”
- rcu_cpu_stall_timeout가 CONFIG_RCU_CPU_STALL_TIMEOUT(21초)와 다르게 설정된 경우 “RCU CPU stall warnings timeout set to %d (rcu_cpu_stall_timeout).”
rcu_tasks_bootup_oddness()
kernel/rcu/update.c
/* * Print any non-default Tasks RCU settings. */
static void __init rcu_tasks_bootup_oddness(void) { #ifdef CONFIG_TASKS_RCU if (rcu_task_stall_timeout != RCU_TASK_STALL_TIMEOUT) pr_info("\tTasks-RCU CPU stall warnings timeout set to %d (rcu_task_stall_timeout).\n", rcu_task_stall_tt imeout); else pr_info("\tTasks RCU enabled.\n"); #endif /* #ifdef CONFIG_TASKS_RCU */ }
Tasks RCU 설정들에 대해 다음과 같은 로그를 출력한다.
- rcu_task_stall_timeout 가 RCU_TASK_STALL_TIMEOUT(600초)와 다르게 설정된 경우 “Tasks-RCU CPU stall warnings timeout set to %d (rcu_task_stall_timeout).”
cpu hot-plug 동작
rcu_pm_notify()
kernel/rcu/tree.c
/* * On non-huge systems, use expedited RCU grace periods to make suspend * and hibernation run faster. */
static int rcu_pm_notify(struct notifier_block *self, unsigned long action, void *hcpu) { switch (action) { case PM_HIBERNATION_PREPARE: case PM_SUSPEND_PREPARE: rcu_expedite_gp(); break; case PM_POST_HIBERNATION: case PM_POST_SUSPEND: rcu_unexpedite_gp(); break; default: break; } return NOTIFY_OK; }
cpu 상태 변화에 따른 통지를 받았을 때 action에 따라 처리할 함수를 호출한다.
트리 기하 초기화
rcu_init_geometry()
kernel/rcu/tree.c
/* * Compute the rcu_node tree geometry from kernel parameters. This cannot * replace the definitions in tree.h because those are needed to size * the ->node array in the rcu_state structure. */
static void __init rcu_init_geometry(void) { ulong d; int i; int rcu_capacity[RCU_NUM_LVLS]; /* * Initialize any unspecified boot parameters. * The default values of jiffies_till_first_fqs and * jiffies_till_next_fqs are set to the RCU_JIFFIES_TILL_FORCE_QS * value, which is a function of HZ, then adding one for each * RCU_JIFFIES_FQS_DIV CPUs that might be on the system. */ d = RCU_JIFFIES_TILL_FORCE_QS + nr_cpu_ids / RCU_JIFFIES_FQS_DIV; if (jiffies_till_first_fqs == ULONG_MAX) jiffies_till_first_fqs = d; if (jiffies_till_next_fqs == ULONG_MAX) jiffies_till_next_fqs = d; adjust_jiffies_till_sched_qs(); /* If the compile-time values are accurate, just leave. */ if (rcu_fanout_leaf == RCU_FANOUT_LEAF && nr_cpu_ids == NR_CPUS) return; pr_info("Adjusting geometry for rcu_fanout_leaf=%d, nr_cpu_ids=%u\n", rcu_fanout_leaf, nr_cpu_ids); /* * The boot-time rcu_fanout_leaf parameter must be at least two * and cannot exceed the number of bits in the rcu_node masks. * Complain and fall back to the compile-time values if this * limit is exceeded. */ if (rcu_fanout_leaf < 2 || rcu_fanout_leaf > sizeof(unsigned long) * 8) { rcu_fanout_leaf = RCU_FANOUT_LEAF; WARN_ON(1); return; } /* * Compute number of nodes that can be handled an rcu_node tree * with the given number of levels. */ rcu_capacity[0] = rcu_fanout_leaf; for (i = 1; i < RCU_NUM_LVLS; i++) rcu_capacity[i] = rcu_capacity[i - 1] * RCU_FANOUT; /* * The tree must be able to accommodate the configured number of CPUs. * If this limit is exceeded, fall back to the compile-time values. */ if (nr_cpu_ids > rcu_capacity[RCU_NUM_LVLS - 1]) { rcu_fanout_leaf = RCU_FANOUT_LEAF; WARN_ON(1); return; } /* Calculate the number of levels in the tree. */ for (i = 0; nr_cpu_ids > rcu_capacity[i]; i++) { } rcu_num_lvls = i + 1; /* Calculate the number of rcu_nodes at each level of the tree. */ for (i = 0; i < rcu_num_lvls; i++) { int cap = rcu_capacity[(rcu_num_lvls - 1) - i]; num_rcu_lvl[i] = DIV_ROUND_UP(nr_cpu_ids, cap); } /* Calculate the total number of rcu_node structures. */ rcu_num_nodes = 0; for (i = 0; i < rcu_num_lvls; i++) rcu_num_nodes += num_rcu_lvl[i]; }
노드 구성을 위한 트리 기하를 산출한다.
- 전역 jiffies_till_first_fqs 및 jiffies_till_next_fqs 산출
- 전역 rcu_num_lvls 산출
- 전역 num_rcu_lvl[] 배열에 각 rcu 노드 레벨별로 rcu_node 갯수 산출
- 전역 rcu_num_nodes 산출
- num_rcu_lvl[]을 모두 더한다
- 코드 라인 14에서 d 값으로 RCU_JIFFIES_TILL_FORCE_QS를 배정하지만 online cpu 수가 256개 단위를 초과할 때 마다 delay 값을 추가 한다.
- rpi2: RCU_JIFFES_TILL_FORCE_QS(1) + nr_cpu_ids(4) / RCU_JIFFIES_FQS_DIV(256) = 1
- RCU_JIFFIES_TILL_FORCE_QS 딜레이 값을 디폴트로 대입하되 시스템의 HZ가 250을 넘어가는 케이스 및 online cpu 수가 256개를 초과하는 케이스에 대해 추가로 delay값을 증가하여 설정하다.
- 코드 라인 15~16에서 모듈 파라미터 jiffies_till_first_fqs가 설정되어 있지 않은 경우 d 값으로 설정된다.
- 코드 라인 17~18에서 모듈 파라미터 jiffies_till_next_fqs 가 설정되어 있지 않은 경우 d 값으로 설정된다.
- 코드 라인 19에서 위에서 산출된 값으로 jiffies_to_sched_qs 값을 결정한다.
- 코드 라인 22~24에서 모듈 파라미터 rcu_fanout_leaf 값과 nr_cpu_ids가 커널 설정 시와 다르지 않은 경우 변동이 없어 함수를 빠져나간다.
- 코드 라인 25~26에서 변동이 생긴 경우이다. “RCU: Adjusting geometry for rcu_fanout_leaf=%d, nr_cpu_ids=%d” 메시지를 출력한다.
- 코드 라인 34~39에서 rcu_fanout_leaf 값이 2보다 작거나 시스템 한계(32bits=32, 64bits=64)를 초과하는 경우 CONFIG_RCU_FANOUT_LEAF(디폴트=16)으로 변경하고 함수를 빠져나간다.
- 코드 라인 45~47에서 rcu_capacity[] 배열에 각 레벨별 최대 노드 수가 산출된다.
- 코드 라인 53~57에서 산출된 하위 레벨의 rcu_capacity[]가 cpu 보다 작은 경우 rcu_fanout_leaf 값을 CONFIG_RCU_FANOUT_LEAF(디폴트=16)으로 변경하고 함수를 빠져나간다.
- 코드 라인 60~62에서 트리 레벨을 결정한다.
- 코드 라인 65~68에서 online cpu 수에 맞게 각 레벨에서 필요로 하는 rcu_node의 수를 num_rcu_lvl[]에 대입한다.
- 코드 라인 71~73에서 num_rcu_lvl[]을 모두 더해 rcu_num_nodes를 산출한다.
kernel/rcu/tree.h
#define RCU_JIFFIES_TILL_FORCE_QS (1 + (HZ > 250) + (HZ > 500)) /* For jiffies_till_first_fqs and */ /* and jiffies_till_next_fqs. */ #define RCU_JIFFIES_FQS_DIV 256 /* Very large systems need more */ /* delay between bouts of */ /* quiescent-state forcing. */
- RCU_JIFFIES_TILL_FORCE_QS
- fqs 대기 시간(틱)
- RCU_JIFFIES_FQS_DIV
- cpu가 많은 시스템에서 이 값 만큼의 cpu 마다 1틱씩 추가 delay를 준다.
- 예) cpus=512 -> fqs 대기 시간에 2틱 추가 delay
다음 그림은 jiffies_till_first_fqs 및 jiffies_till_next_fqs를 산출하는 과정을 보여준다.
다음 그림은 rcu_capacity[] 배열이 산출된 후의 모습을 보여준다. (32bits)
jiffies_till_sched_qs 조정
adjust_jiffies_till_sched_qs()
kernel/rcu/tree.c
/* * Make sure that we give the grace-period kthread time to detect any * idle CPUs before taking active measures to force quiescent states. * However, don't go below 100 milliseconds, adjusted upwards for really * large systems. */
static void adjust_jiffies_till_sched_qs(void) { unsigned long j; /* If jiffies_till_sched_qs was specified, respect the request. */ if (jiffies_till_sched_qs != ULONG_MAX) { WRITE_ONCE(jiffies_to_sched_qs, jiffies_till_sched_qs); return; } /* Otherwise, set to third fqs scan, but bound below on large system. */ j = READ_ONCE(jiffies_till_first_fqs) + 2 * READ_ONCE(jiffies_till_next_fqs); if (j < HZ / 10 + nr_cpu_ids / RCU_JIFFIES_FQS_DIV) j = HZ / 10 + nr_cpu_ids / RCU_JIFFIES_FQS_DIV; pr_info("RCU calculated value of scheduler-enlistment delay is %ld jiffies.\n", j); WRITE_ONCE(jiffies_to_sched_qs, j); }
jiffies_to_sched_qs 값을 산출한다.
- 코드 라인 6~9에서 모듈 파라미터 jiffies_till_sched_qs가 지정된 경우 이 값으로 jiffies_to_sched_qs를 갱신하고 함수를 빠져나간다.
- 코드 라인 11~16에서 jiffies_till_first_fqs + jiffies_till_next_fqs * 2 값을 jiffies_to_sched_qs에 기록하되 최소 값을 다음과 같이 제한한다.
- 0.1초에 해당하는 틱 수 + cpu 수 / 256
- rpi4 = 25
다음은 rpi4에서 알아본 초기 rcu에 대한 모듈 파라메터 값이다.
$ cat /sys/module/rcutree/parameters/jiffies_till_first_fqs 1 $ cat /sys/module/rcutree/parameters/jiffies_till_next_fqs 1 $ cat /sys/module/rcutree/parameters/jiffies_till_sched_qs 18446744073709551615 $ cat /sys/module/rcutree/parameters/jiffies_to_sched_qs 25 $ cat /sys/module/rcutree/parameters/rcu_fanout_leaf 16 $ cat /sys/module/rcutree/parameters/qlowmark 100 $ cat /sys/module/rcutree/parameters/qhimark 10000 $ cat /sys/module/rcutree/parameters/blimit 10 $ cat /sys/module/rcutree/parameters/kthread_prio 0 $ cat /sys/module/rcutree/parameters/gp_cleanup_delay 0 $ cat /sys/module/rcutree/parameters/gp_init_delay 0 $ cat /sys/module/rcutree/parameters/gp_preinit_delay 0 $ cat /sys/module/rcutree/parameters/rcu_divisior 7 $ cat /sys/module/rcutree/parameters/rcu_fanout_exact N $ cat /sys/module/rcutree/parameters/rcu_resched_ns 3000000 $ cat /sys/module/rcutree/parameters/sysrq_rcu N $ cat /sys/module/rcutree/parameters/use_softirq Y
빌드 타임 RCU 트리 구성
빌드 타임에 NR_CPUS에 해당하는 cpu 수로 rcu 노드 크기를 결정하여 사용한다.
- 1개로 구성된 rcu_state 구조체 내부에 rcu_node 구조체 배열이 static하게 구성된다. (기존에는 3개의 rcu_state가 사용되었었다)
- rcu_node 구조체 배열은 NR_CPUS 크기에 따라 1레벨부터 최대 4레벨 까지의 tree 구조를 지원한다.
- RCU 노드 트리는 최소 1레벨부터 최대 4레벨까지 구성된다.
- 32bit 시스템(CONFIG_RCU_FANOUT_LEAF=16(디폴트)기준)
- 1레벨에서 최대 16개 cpu 지원 가능
- 2레벨에서 최대 16×32개 cpu 지원 가능
- 3레벨에서 최대 16x32x32개 cpu 지원 가능
- 4레벨에서 최대 16x32x32x32개 cpu 지원 가능
- 64bit 시스템(CONFIG_RCU_FANOUT_LEAF=16(디폴트)기준)
- 1레벨에서 최대 16개 cpu 지원 가능
- 2레벨에서 최대 16×64개 cpu 지원 가능
- 3레벨에서 최대 16x64x64개 cpu 지원 가능
- 4레벨에서 최대 16x64x64x64개 cpu 지원 가능
- 32bit 시스템(CONFIG_RCU_FANOUT_LEAF=16(디폴트)기준)
- hotplug cpu를 지원하여 상태가 변화함에 따라 노드 구성이 바뀌게 설계되어 있다.
- rcu_node의 sub rcu 노드들은 최대 CONFIG_RCU_FANOUT까지 구성된다.
- 32bit 시스템에서 2~32개까지, 64bit 시스템에서 2~64개까지 설정 가능하다.
- default: 32bit에서 32, 64bit에서 64
- 최하단 leaf 노드의 경우 rcu_data(cpu)와의 구성에서 rcu_fanout_leaf까지 연결될 수 있다.
- CONFIG_RCU_FANOUT_LEAF
- 디폴트로 16개의 cpu(rcu_data)를 관리할 수 있고, 2~RCU_FANOUT 범위까지 설정 가능하다.
- 각 cpu에 대한 노드 락을 contention을 회피하기 위해 16개를 디폴트로 사용하고 있다.
- CONFIG_RCU_FANOUT_LEAF
다음 그림은 최소 1 레벨과 최대 4 레벨의 구성 차이를 보여준다.
다음 그림은 최대 4 레벨에서 관리 가능한 cpu 수를 보여준다.
다음 그림은 컴파일 타임에 NR_CPUS 크기에 따라 사용할 레벨이 결정되고 각 레벨별로 rcu 노드 수가 결정되는 것을 보여준다.
다음 그림은 20개의 CPU를 지원하는 설정으로 컴파일 시 구성되는 rcu 노드들의 수를 산출하는 것을 보여준다.
- CONFIG_RCU_FANOUT=64, CONFIG_RCU_FANOUT_LEAF=16 사용
다음 그림은 rcu_state 구조체 내부에서 4레벨로 구성된 rcu_node가 구성된 순서를 보여준다.
- rcu_data들은 최 하위 leaf 노드들과 직접 연결된다.
RCU 구조체 초기화
rcu_init_one()
kernel/rcu/tree.c
/* * Helper function for rcu_init() that initializes the rcu_state structure. */
static void __init rcu_init_one(void) { static const char * const buf[] = RCU_NODE_NAME_INIT; static const char * const fqs[] = RCU_FQS_NAME_INIT; static struct lock_class_key rcu_node_class[RCU_NUM_LVLS]; static struct lock_class_key rcu_fqs_class[RCU_NUM_LVLS]; int levelspread[RCU_NUM_LVLS]; /* kids/node in each level. */ int cpustride = 1; int i; int j; struct rcu_node *rnp; BUILD_BUG_ON(RCU_NUM_LVLS > ARRAY_SIZE(buf)); /* Fix buf[] init! */ /* Silence gcc 4.8 false positive about array index out of range. */ if (rcu_num_lvls <= 0 || rcu_num_lvls > RCU_NUM_LVLS) panic("rcu_init_one: rcu_num_lvls out of range"); /* Initialize the level-tracking arrays. */ for (i = 1; i < rcu_num_lvls; i++) rcu_state.level[i] = rcu_state.level[i - 1] + num_rcu_lvl[i - 1]; rcu_init_levelspread(levelspread, num_rcu_lvl); /* Initialize the elements themselves, starting from the leaves. */ for (i = rcu_num_lvls - 1; i >= 0; i--) { cpustride *= levelspread[i]; rnp = rcu_state.level[i]; for (j = 0; j < num_rcu_lvl[i]; j++, rnp++) { raw_spin_lock_init(&ACCESS_PRIVATE(rnp, lock)); lockdep_set_class_and_name(&ACCESS_PRIVATE(rnp, lock), &rcu_node_class[i], buf[i]); raw_spin_lock_init(&rnp->fqslock); lockdep_set_class_and_name(&rnp->fqslock, &rcu_fqs_class[i], fqs[i]); rnp->gp_seq = rcu_state.gp_seq; rnp->gp_seq_needed = rcu_state.gp_seq; rnp->completedqs = rcu_state.gp_seq; rnp->qsmask = 0; rnp->qsmaskinit = 0; rnp->grplo = j * cpustride; rnp->grphi = (j + 1) * cpustride - 1; if (rnp->grphi >= nr_cpu_ids) rnp->grphi = nr_cpu_ids - 1; if (i == 0) { rnp->grpnum = 0; rnp->grpmask = 0; rnp->parent = NULL; } else { rnp->grpnum = j % levelspread[i - 1]; rnp->grpmask = BIT(rnp->grpnum); rnp->parent = rcu_state.level[i - 1] + j / levelspread[i - 1]; } rnp->level = i; INIT_LIST_HEAD(&rnp->blkd_tasks); rcu_init_one_nocb(rnp); init_waitqueue_head(&rnp->exp_wq[0]); init_waitqueue_head(&rnp->exp_wq[1]); init_waitqueue_head(&rnp->exp_wq[2]); init_waitqueue_head(&rnp->exp_wq[3]); spin_lock_init(&rnp->exp_lock); } } init_swait_queue_head(&rcu_state.gp_wq); init_swait_queue_head(&rcu_state.expedited_wq); rnp = rcu_first_leaf_node(); for_each_possible_cpu(i) { while (i > rnp->grphi) rnp++; per_cpu_ptr(&rcu_data, i)->mynode = rnp; rcu_boot_init_percpu_data(i); } }
1개의 rcu_state에 포함된 rcu_node와 rcu_data를 초기화한다. (예전 커널에서 rcu_state가 3가지가 존재하여 이 함수가 3번 호출되었었다)
- 코드 라인 17~18에서 rcu 노드들의 하이 라키는 1~4 레벨로 제한되어 있다.
- 코드 라인 22~24에서 각 레벨별로 첫 rcu_node를 가리키게 한다.
- 코드 라인 25에서 각 rcu 레벨이 관리하는 sub 노드의 수를 산출한다. 모듈 파라미터 rcu_fanout_exact(디폴트=0) 값이 0일 때 nr_cpu_ids 수에 맞춰 spread 한다.
- 코드 라인 29~67에서 leaf 노드부터 최상위 노드까지 초기화한다.
- grplo와 grphi에는 각 노드가 관리하는 cpu 번호 범위가 지정된다.
- 코드 라인 69~70에서 두 개의 swait_queue를 초기화한다.
- 코드 라이 71~77에서 각 cpu에 해당하는 rcu 데이터를 초기화한다. 또한 ->mynode가 담당 leaf 노드를 가리키게 한다.
다음 그림은 rcu_node의 grplo 및 grphi를 산출하는 과정을 보여준다.
다음 그림은 rcu_node의 grpnum, grpmask 및 level을 산출하는 과정을 보여준다.
다음 그림은 3단계로 구성된 rcu_node 구성을 트리 구조로 보여준 사례이다.
rcu_init_levelspread()
kernel/rcu/rcu.h
/* * Compute the per-level fanout, either using the exact fanout specified * or balancing the tree, depending on the rcu_fanout_exact boot parameter. */
static inline void rcu_init_levelspread(int *levelspread, const int *levelcnt) { int i; if (rcu_fanout_exact) { levelspread[rcu_num_lvls - 1] = rcu_fanout_leaf; for (i = rcu_num_lvls - 2; i >= 0; i--) levelspread[i] = RCU_FANOUT; } else { int ccur; int cprv; cprv = nr_cpu_ids; for (i = rcu_num_lvls - 1; i >= 0; i--) { ccur = levelcnt[i]; levelspread[i] = (cprv + ccur - 1) / ccur; cprv = ccur; } } }
각 rcu 레벨이 관리하는 sub 노드의 수를 산출한다. rcu_fanout_exact=0(디폴트)을 사용하는 경우 노드 락을 최소화하기 위해 online된 cpu수에 맞춰 노드 배치를 spread하여 구성한다.
- 코드 라인 5~8에서 모듈 파라미터 rcu_fanout_exact가 설정된 경우 leaf 노드에서는 rcu_fanout_leaf(디폴트=16)로 설정하고, 나머지 노드는 RCU_FANOUT(디폴트=32/64 bits) 값으로 설정한다.
- 코드 라인 9~19에서 그 외의 경우 online cpu 수에 맞게 각 레벨의 노드가 관리하는 sub 노드 수를 spread 배치하여 구성한다.
다음 그림은 모듈 파라미터 rcu_fanout_exact 설정된 경우 노드 배치가 spread 되는 모습을 보여준다.
rcu_init_one_nocb()
kernel/rcu/tree_plugin.h
static void rcu_init_one_nocb(struct rcu_node *rnp) { init_waitqueue_head(&rnp->nocb_gp_wq[0]); init_waitqueue_head(&rnp->nocb_gp_wq[1]); }
CONFIG_RCU_NOCB_CPU 커널 옵션이 사용되는 경우 nocb_gp_wq[]에 있는 대기큐 두 개를 초기화한다.
rcu_boot_init_percpu_data()
kernel/rcu/tree.c
/* * Do boot-time initialization of a CPU's per-CPU RCU data. */ static void __init rcu_boot_init_percpu_data(int cpu) { struct rcu_data *rdp = per_cpu_ptr(&rcu_data, cpu); /* Set up local state, ensuring consistent view of global state. */ rdp->grpmask = leaf_node_cpu_bit(rdp->mynode, cpu); WARN_ON_ONCE(rdp->dynticks_nesting != 1); WARN_ON_ONCE(rcu_dynticks_in_eqs(rcu_dynticks_snap(rdp))); rdp->rcu_ofl_gp_seq = rcu_state.gp_seq; rdp->rcu_ofl_gp_flags = RCU_GP_CLEANED; rdp->rcu_onl_gp_seq = rcu_state.gp_seq; rdp->rcu_onl_gp_flags = RCU_GP_CLEANED; rdp->cpu = cpu; rcu_boot_init_nocb_percpu_data(rdp); }
cpu별로 구성되는 rcu_data 구조체의 멤버를 부트타임에 모두 초기화한다.
nohz 및 no-cb용 콜백 처리 커널 스레드 구성
rcu_init_nohz()
kernel/rcu/tree_plugin.h
void __init rcu_init_nohz(void) { int cpu; bool need_rcu_nocb_mask = false; struct rcu_data *rdp; #if defined(CONFIG_NO_HZ_FULL) if (tick_nohz_full_running && cpumask_weight(tick_nohz_full_mask)) need_rcu_nocb_mask = true; #endif /* #if defined(CONFIG_NO_HZ_FULL) */ if (!cpumask_available(rcu_nocb_mask) && need_rcu_nocb_mask) { if (!zalloc_cpumask_var(&rcu_nocb_mask, GFP_KERNEL)) { pr_info("rcu_nocb_mask allocation failed, callback offloading disabled.\n"); return; } } if (!cpumask_available(rcu_nocb_mask)) return; #if defined(CONFIG_NO_HZ_FULL) if (tick_nohz_full_running) cpumask_or(rcu_nocb_mask, rcu_nocb_mask, tick_nohz_full_mask); #endif /* #if defined(CONFIG_NO_HZ_FULL) */ if (!cpumask_subset(rcu_nocb_mask, cpu_possible_mask)) { pr_info("\tNote: kernel parameter 'rcu_nocbs=', 'nohz_full', or 'isolcpus=' contains nonexistent CPUs.\nn"); cpumask_and(rcu_nocb_mask, cpu_possible_mask, rcu_nocb_mask); } if (cpumask_empty(rcu_nocb_mask)) pr_info("\tOffload RCU callbacks from CPUs: (none).\n"); else pr_info("\tOffload RCU callbacks from CPUs: %*pbl.\n", cpumask_pr_args(rcu_nocb_mask)); if (rcu_nocb_poll) pr_info("\tPoll for callbacks from no-CBs CPUs.\n"); for_each_cpu(cpu, rcu_nocb_mask) { rdp = per_cpu_ptr(&rcu_data, cpu); if (rcu_segcblist_empty(&rdp->cblist)) rcu_segcblist_init(&rdp->cblist); rcu_segcblist_offload(&rdp->cblist); } rcu_organize_nocb_kthreads(); }
rcu nohz 처리를 위한 초기화를 수행한다.
- 코드 라인 7~10에서 “nohz_full=” 커널 파라미터로 지정된 cpu들이 있는 경우 임시 변수 need_rcu_nocb_mask에 true를 대입해둔다.
- 코드 라인 12~19에서 “rcu_nocbs=” 커널 파라미터로 지정되는 rcu_nocb_mask 비트마스크가 할당되지 않은 경우 할당한다.
- 코드 라인 21~24에서 nohz full이 지원되는 시스템인 경우 rcu_nocb_mask에 nohz full cpu들을 추가한다.
- 코드 라인 26~30에서 nocb용 cpu들이 possible cpu에 포함되지 않은 경우 경고 메시지를 출력하고, rcu_nocb_mask 비트마스크에서 possible cpu들을 모두 뺀다.
- 코드 라인 31~35에서 offload된(no-cb) cpu들을 출력한다.
- 코드 라인 36~37에서 “rcu_nocb_poll=” 커널 파라미터가 설정된 경우 no-cb 스레드가 polling을 지원한다고 해당 정보를 출력한다.
- 코드 라인 39~44에서 offload cpu들에 대해 콜백리스트의 offloaded=1을 설정한다.
- 코드 라인 45에서 no-cb용 cpu들 각각에 대해 no-cb용 gp 커널 스레드가 동작하는 cpu를 지정한다.
- no-cb로 동작할 때 각 cpu들은 그룹으로 나뉘어 관리되며, 각 그룹당 대표 cpu는 no-cb용 gp 커널 스레드도 생성한다.
참고
- RCU(Read Copy Update) -1- (Basic) | 문c
- RCU(Read Copy Update) -2- (Callback process) | 문c
- RCU(Read Copy Update) -3- (RCU threads) | 문c
- RCU(Read Copy Update) -4- (NOCB process) | 문c
- RCU(Read Copy Update) -5- (Callback list) | 문c
- RCU(Read Copy Update) -6- (Expedited GP) | 문c
- RCU(Read Copy Update) -7- (Preemptible RCU) | 문c
- rcu_init() | 문c – 현재글
- wait_for_completion() | 문c