RCU (Read-Copy-Update)는 커널 동시성 제어 메커니즘 중 하나로, 특히 리더스와 업데이트 쓰레드 간의 동시성 문제를 해결하기 위해 사용됩니다. RCU의 기본 아이디어는 읽기가 매우 빈번하고 업데이트가 상대적으로 드문 경우에 최적화된 데이터 구조에 대한 안전한 동시 접근을 제공하는 것입니다.
RCU의 핵심 특징은 다음과 같습니다:
- 락없는 읽기: RCU로 보호되는 데이터 구조를 읽는 동안 락을 사용할 필요가 없습니다. 따라서 읽기는 빠르고 확장성이 높습니다.
- 업데이트 중인 데이터의 유지: 데이터를 업데이트할 때 기존 데이터를 즉시 삭제하지 않고, 아직 해당 데이터를 참조하고 있는 모든 읽기 쓰레드가 완료될 때까지 기다립니다.
- Grace Period: 이 기간 동안 모든 CPU는 RCU로 보호되는 데이터 구조에 대한 참조를 시작하거나 완료합니다. Grace Period가 종료되면, 이전에 삭제되거나 변경된 데이터는 안전하게 해제될 수 있습니다.
- 쿼리스 (Quiescent State): CPU가 RCU로 보호되는 데이터 구조에 대한 참조를 시작하거나 완료하는 상태를 의미합니다. 쿼리스 상태를 통해 Grace Period가 결정됩니다.
RCU는 다음과 같은 시나리오에서 특히 유용합니다:
- 읽기 연산이 쓰기 연산보다 훨씬 빈번한 경우.
- 데이터 구조에 대한 동시 접근이 필요한 경우.
- 락 경쟁이나 데드락과 같은 문제를 피하고 싶은 경우.
Linux 커널에서 RCU는 다양한 데이터 구조와 알고리즘에 사용되며, 그 활용 사례로는 네트워크 라우팅 테이블, 프로세스 ID 관리, 커널 모듈의 동적 로딩 및 언로딩 등이 있습니다.
call_rcu()
call_rcu()는 Linux 커널에서 RCU (Read-Copy-Update) 메커니즘을 사용하여 데이터 구조의 안전한 해제를 위한 핵심 함수입니다.
call_rcu() 함수는 데이터를 즉시 해제하지 않고, 현재 데이터를 참조하고 있는 모든 RCU 읽기 쓰레드가 완료될 때까지 대기한 다음 데이터를 해제하는 데 사용됩니다. 이렇게 하면 읽기 쓰레드와 업데이트 쓰레드 간의 동시성 문제 없이 데이터 구조에 안전하게 접근할 수 있습니다.
call_rcu()의 기본 동작은 다음과 같습니다:
- RCU로 보호된 데이터 구조의 업데이트가 필요한 경우, 업데이트된 새로운 데이터 구조의 복사본을 생성합니다.
- 모든 읽기 쓰레드가 현재 참조하고 있는 오래된 데이터 구조에 계속 접근할 수 있도록 합니다.
call_rcu()를 사용하여 오래된 데이터 구조를 안전하게 해제하기 위한 콜백 함수를 스케줄링합니다.
- RCU Grace Period가 종료되면,
call_rcu()에 전달된 콜백 함수가 실행되어 오래된 데이터 구조가 안전하게 해제됩니다.
void call_rcu(struct rcu_head head, void (func)(struct rcu_head *head));
head: RCU로 보호된 데이터 구조의 rcu_head 멤버에 대한 포인터입니다.
func: Grace Period 후에 실행될 콜백 함수입니다. 이 함수는 오래된 데이터 구조를 안전하게 해제하는 데 사용됩니다.
call_rcu()를 사용할 때 주의할 점은, 오래된 데이터 구조에 대한 모든 참조가 Grace Period 후에 종료되어야 한다는 것입니다. 이를 보장하기 위해 RCU 읽기 쓰레드는 rcu_read_lock() 및 rcu_read_unlock() 함수를 사용하여 RCU 보호 섹션을 정의해야 합니다.