운영체제 기본/Linux/RCU/Tree RCU와 유예 기간: 두 판 사이의 차이
잔글 관리자님이 운영체제 기본/Linux의 RCU/Tree RCU와 유예 기간 문서를 운영체제 기본/Linux/RCU/Tree RCU와 유예 기간 문서로 이동했습니다: Linux 각론 제목을 계층형 경로로 통일 |
잔글 Linux 각론 경로 변경에 따른 본문·목차·틀 링크 갱신 |
||
| 1번째 줄: | 1번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|이전=← 이전 문서<br>[[운영체제 기본/ | |이전=← 이전 문서<br>[[운영체제 기본/Linux/RCU/갱신과 메모리 회수|갱신과 메모리 회수]] | ||
|목차=[[운영체제 기본/ | |목차=[[운영체제 기본/Linux/RCU|RCU 목차]] | ||
|다음=마지막 문서 | |다음=마지막 문서 | ||
}} | }} | ||
| 79번째 줄: | 79번째 줄: | ||
{{이전 다음 문서 | {{이전 다음 문서 | ||
|이전=← 이전 문서<br>[[운영체제 기본/ | |이전=← 이전 문서<br>[[운영체제 기본/Linux/RCU/갱신과 메모리 회수|갱신과 메모리 회수]] | ||
|목차=[[운영체제 기본/ | |목차=[[운영체제 기본/Linux/RCU|RCU 목차]] | ||
|다음=마지막 문서 | |다음=마지막 문서 | ||
}} | }} | ||
{{운영체제 기본 둘러보기}} | {{운영체제 기본 둘러보기}} | ||
2026년 11월 1일 (일) 02:21 기준 최신판
Tree RCU는 모든 독자가 하나의 전역 카운터를 증가·감소시키는 대신, 태스크와 CPU별로 상태를 기록하고 트리에서 유예 기간의 완료를 모은다. 독자에게 가벼운 경로를 제공하는 대가로 갱신·회수 측의 상태 추적이 복잡해진다. Linux v6.12의 CONFIG_PREEMPT_RCU=y, 다중 CPU, 일반 유예 기간을 기준으로 내부 연결을 살펴본다.
읽기 구간의 태스크별 기록
rcu_read_lock() 아래의 __rcu_read_lock()은 현재 태스크의 rcu_read_lock_nesting을 증가시킨다. 대응하는 unlock은 감소시키고, 가장 바깥 읽기 구간이 끝났을 때 미뤄둔 특별 처리가 있으면 rcu_read_unlock_special()을 실행한다. 모든 독자가 같은 전역 원자 카운터를 갱신하지 않는 것이 읽기 비용을 줄이는 중요한 이유다.[1]
태스크의 읽기 구간
read_lock nesting 0 → 1
read_lock nesting 1 → 2
read_unlock nesting 2 → 1
read_unlock nesting 1 → 0 : 바깥 구간 종료
읽기 도중 태스크가 선점되면 CPU는 다른 일을 시작해도 그 태스크는 이전 객체의 포인터를 가지고 있다. CPU가 한 번 스케줄링했다는 이유만으로 회수하면 안 된다. rcu_note_context_switch()는 선점된 독자를 해당 rcu_node의 blkd_tasks 목록으로 연결한다. 현재 유예 기간을 막는 독자는 gp_tasks 등으로 추적하며, 태스크가 다시 실행되어 바깥 읽기 구간을 끝내야 이 조건을 해소할 수 있다.[1]
이 기록 덕분에 읽기 구간 전체에서 선점을 금지하지 않아도 수명을 지킬 수 있다. CONFIG_PREEMPT_RCU=n에서는 읽기 구간과 선점 제어의 관계가 다르므로 위 태스크 목록 경로를 그대로 적용하지 않는다.
CPU별 상태와 집계 트리
struct rcu_data는 CPU별 상태와 콜백 목록을 보관한다. struct rcu_node는 여러 CPU 또는 하위 노드의 진행 상태를 묶고, rcu_state는 유예 기간의 순서와 전체 트리를 관리한다.[2]
rcu_state
유예 기간 순서
|
루트 rcu_node
/ \
rcu_node rcu_node
/ \ / \
CPU 0 CPU 1 CPU 2 CPU 3
rcu_data 각각: CPU 진행 상태 + 콜백 목록
정지 상태(Quiescent State)는 여기서 추적하는 이전 RCU 읽기를 CPU가 계속 수행하고 있지 않음을 보고할 수 있는 상태다. 문맥에 맞는 컨텍스트 스위치나 idle·사용자 모드 상태의 관찰 등이 이 판단에 쓰인다. CPU를 물리적으로 멈춘다는 뜻은 아니다. 선점된 독자가 다른 곳에 남아 있을 수 있으므로 CPU의 보고와 앞 절의 태스크 목록을 함께 확인한다.
각 노드의 qsmask는 이번 유예 기간에 아직 보고를 기다리는 CPU 또는 하위 노드를 비트로 나타낸다. rcu_report_qs_rnp()는 보고된 비트를 지우고, 그 노드의 비트가 모두 지워졌으며 해당 유예 기간을 막는 선점 독자도 없을 때 상위 노드로 완료를 전달한다. 마지막 보고만 위로 올라가므로 모든 CPU가 매번 루트의 락에 몰리지 않는다.[3]
유예 기간의 시작과 완료
rcu_gp_kthread()는 유예 기간을 관리하는 커널 스레드다. 회수 요청이 들어오면 필요한 유예 기간을 시작하고, rcu_gp_init()에서 순서와 노드별 대기 상태를 준비한다. 이후 CPU 보고와 선점 독자의 종료가 모여 완료 조건을 만족하면 rcu_gp_cleanup()이 완료 상태를 각 노드에 전파한다.
회수 요청
↓
필요한 유예 기간 순서 결정
↓
CPU별 정지 상태 보고 ─── 선점된 기존 독자의 읽기 종료
└────────────┬─────────────────┘
↓
rcu_node 트리에서 완료 집계
↓
유예 기간 완료 기록
↓
해당 기간을 기다린 콜백 실행 가능
gp_seq는 단순한 시각 대신 유예 기간의 진행 순서를 표시한다. 회수 요청이 어느 완료 시점 이후 안전한지를 구분해야, 요청보다 앞서 시작되어 충분한 보장을 주지 못하는 기간을 잘못 사용하지 않는다. 여러 요청은 같은 유예 기간을 함께 기다릴 수 있다. 각 객체의 참조를 세는 대신 기존 읽기 구간들이 지나갔음을 묶어서 확인하는 방식이다.
CPU가 오래 보고하지 않으면 RCU는 상태를 다시 관찰하거나 스케줄링을 촉진하는 등 진행을 요구한다. 이 처리가 타이머 만료만으로 안전성을 대신하는 것은 아니다. 종료하지 않는 독자가 있으면 유예 기간이 지연되고, 콜백과 메모리 회수도 밀린다.[3]
콜백 대기열과 실행
call_rcu()는 __call_rcu_common()으로 이어져 rcu_head에 콜백을 기록하고 CPU별 콜백 경로에 넣는다. 기본 비오프로딩 경로에서 콜백은 rcu_data의 rcu_segcblist에 연결된다. 이 목록은 실행 가능한 콜백과 유예 기간을 기다리는 콜백을 구분한다. rcu_accelerate_cbs()는 필요한 기간과 연결하고, 완료가 관찰되면 rcu_advance_cbs()가 실행 가능한 부분을 앞으로 진행시킨다.
rcu_core() 경로의 rcu_do_batch()가 준비된 콜백들을 꺼내 실행한다. 따라서 유예 기간 완료는 콜백 실행의 안전 조건이고, 그 순간 모든 콜백이 즉시 실행된다는 뜻은 아니다. 콜백 오프로딩이나 지연 회수 설정은 실행 주체와 시점을 바꿀 수 있지만, 안전한 유예 기간 이후라는 조건은 유지한다.[3]
synchronize_rcu()는 같은 완료 보장을 동기적으로 기다리는 인터페이스다. v6.12의 일반 경로 synchronize_rcu_normal()에는 유예 기간 완료 요청을 등록하고 completion을 기다리는 경로와 wait_rcu_gp(call_rcu_hurry) 경로가 있다. 이를 항상 콜백 하나를 등록하는 단일 구현이라고 고정해서 읽으면 현재 코드를 놓치게 된다. 호출자가 돌아왔을 때 기존 읽기 구간이 끝났다는 공통 보장이 중요하다.[3]
비용과 적용 범위
Tree RCU가 줄이는 것은 읽는 쪽의 공유 락 경쟁이다. 쓰기 측의 직렬화, 새 객체 복사, 유예 기간 추적, 이전 객체를 보관하는 메모리 비용은 남는다. 읽기가 많고 일정 시간 이전 버전을 관찰해도 되는 구조에 특히 잘 맞는다. 쓰기가 잦거나 읽기 구간이 길면 회수 지연이 커지므로 단순 뮤텍스보다 항상 유리한 것은 아니다.
x86-64와 RISC-V RV64에서 이 트리와 태스크 추적의 의미는 같다. 아키텍처별 원자 연산·장벽이 그 아래의 순서를 구현한다. Linux 커널 안의 RCU를 사용자 프로세스의 일반 포인터에 그대로 적용할 수는 없으며, 사용자 공간 RCU는 별도의 라이브러리와 실행 환경을 사용한다.
참고 문헌
- ↑ 1.0 1.1 Linux v6.12, kernel/rcu/tree_plugin.h, __rcu_read_lock, __rcu_read_unlock, rcu_note_context_switch와 rcu_read_unlock_special.
- ↑ Linux v6.12, kernel/rcu/tree.h, rcu_data, rcu_node, rcu_state.
- ↑ 3.0 3.1 3.2 3.3 Linux v6.12, kernel/rcu/tree.c, rcu_gp_init, rcu_gp_kthread, rcu_report_qs_rnp, rcu_gp_cleanup, rcu_do_batch.