>시스템 튜토리얼 >리눅스 >Linux 커널의 RCU 메커니즘에 대한 자세한 설명

Linux 커널의 RCU 메커니즘에 대한 자세한 설명

WBOY
WBOY앞으로
2024-02-10 21:09:111351검색

Linux 커널은 프로세스 스케줄링, 메모리 관리, 장치 드라이버, 네트워크 프로토콜 등과 같은 다양한 동시성 문제를 처리해야 하는 복잡한 시스템입니다. 데이터의 일관성과 정확성을 보장하기 위해 Linux 커널은 스핀 잠금, 세마포어, 읽기-쓰기 잠금 등과 같은 다양한 동기화 메커니즘을 제공합니다. 그러나 이러한 동기화 메커니즘에는 다음과 같은 몇 가지 단점이 있습니다.

Linux 커널의 RCU 메커니즘에 대한 자세한 설명

  • 스핀 잠금은 CPU가 바쁜 대기 시간을 낭비하게 하며 선점형 커널에서는 사용할 수 없습니다. 세마포어는 프로세스를 절전 모드 및 절전 모드로 전환하여 컨텍스트 전환 오버헤드를 증가시킵니다.
  • 읽기-쓰기 잠금은 작가 기아 또는 독자 기아를 유발하며, 작가보다 독자가 더 많은 경우 작가는 잠금 획득에 대한 오버헤드도 지불해야 합니다.
  • 그렇다면 더 나은 동기화 메커니즘이 있을까요? 대답은 '예', 즉 RCU(Read Copy Update)입니다. RCU는 효율적인 읽기 작업과 낮은 지연 시간의 업데이트 작업을 달성할 수 있는 게시-구독 모델을 기반으로 하는 동기화 메커니즘입니다. RCU의 기본 아이디어는 다음과 같습니다.

읽기 작업에서는 잠금을 획득할 필요가 없습니다. rcu_read_lock() 및 rcu_read_unlock()을 사용하여 중요 영역을 표시하세요.
  • 업데이트 작업을 수행하려면 먼저 데이터 복사본을 만들고 복사본을 수정한 다음 rcu_sign_pointer()를 사용하여 새 데이터를 게시하고 call_rcu() 또는 sync_rcu()를 사용하여 모든 읽기 작업이 완료될 때까지 기다려야 합니다. 오래된 데이터.
  • RCU의 장점은 무엇인가요? 다음과 같은 기능이 있습니다:

RCU는 잠금 경쟁과 컨텍스트 전환을 줄이고 시스템의 동시성 성능과 응답성을 향상할 수 있습니다.
    RCU는 교착 상태 및 우선 순위 반전과 같은 문제를 방지하여 프로그래밍 모델을 단순화할 수 있습니다.
  • RCU는 실시간 시스템, NUMA 시스템, SMP 시스템 등과 같은 다양한 시나리오에 적응할 수 있습니다. RCU는 스핀 잠금, 원자 작업 등과 같은 다른 동기화 메커니즘과 함께 사용할 수 있습니다.
  • 그렇다면 RCU는 어떻게 작동하나요? 어떤 응용 시나리오가 있습니까? 이 기사에서는 원리와 적용이라는 두 가지 측면에서 RCU의 효율적인 동기화 메커니즘을 소개합니다. RCU의 설계 아이디어는 비교적 명확하며 기존 포인터와 새 포인터를 교체하여 잠금 없는 공유 보호를 달성합니다. 하지만 코드 수준으로 보면 여전히 이해하기가 다소 어렵습니다. "심층 Linux 장치 드라이버 커널 메커니즘"의 4장에서는 RCU 뒤에 따르는 규칙을 매우 명확하게 설명했습니다. 이러한 규칙은 너무 많은 코드 분석이 쉽다고 생각하기 때문에 상대적으로 높은 관점에서 볼 수 있습니다. 세부 사항에서. 최근 책을 받고 다시 RCU 부분의 텍스트를 주의 깊게 읽어보았고, 책에 쓰기에 적합하지 않은 내용도 있을 수 있으니 내용을 조금 더 추가해야겠다는 생각이 들었습니다.
  • RCU 읽기 측이 임계 섹션에 진입했다는 신호는 rcu_read_lock을 호출하는 것입니다.
  • 으아아아
  • 이 구현에는 세 가지 함수 호출이 있는 것으로 보이지만 실제 작업은 첫 번째 함수 __rcu_read_lock()에 의해 수행됩니다. __rcu_read_lock()은 preempt_disable()을 호출하여 커널 선점성을 끕니다. 그러나 인터럽트는 허용됩니다. 리더가 rcu 임계 섹션에 있고 공유 데이터 영역에서 포인터 p를 읽었다고 가정합니다(그러나 아직 p의 데이터 멤버에 액세스하지 않았습니다). 인터럽트 처리 예는 다음과 같습니다. ISR은 p가 가리키는 데이터 영역을 수정해야 합니다. RCU의 설계 원칙에 따라 ISR은 동일한 크기의 데이터 영역 new_p를 새로 할당한 다음 이전 데이터 영역 p의 데이터를 새 데이터 영역에 복사합니다. 데이터 영역, 그리고 new_p에서 기본적으로 데이터 수정 작업을 수행합니다(new_p 공간에서 수정되기 때문에 p에 대한 동시 액세스가 없으므로 RCU는 잠금 없는 메커니즘이고 이것이 이유입니다). 데이터 업데이트 작업을 완료하고 new_p를 p에 할당하고(p=new_p) 마지막으로 적절한 시점에 이전 포인터 p를 해제하는 콜백 함수를 등록합니다. 따라서 이전 포인터 p에 대한 모든 참조가 끝나면 p를 해제하는 데 문제가 없습니다. 인터럽트 처리 루틴이 이러한 작업을 완료하고 돌아올 때, 인터럽트된 프로세스는 여전히 p 공간의 데이터, 즉 이전 데이터에 액세스합니다. 이 결과는 RCU 메커니즘에 의해 허용됩니다. RCU 규칙은 판독기와 기록기 간의 포인터 전환으로 인해 발생하는 일시적인 리소스 보기 불일치를 허용합니다
  • .

RCU에 대한 다음 흥미로운 질문은 이전 포인터를 언제 출시할 수 있느냐는 것입니다. 내가 많은 책에서 본 이에 대한 대답은 시스템의 모든 프로세서에서 프로세스 전환이 발생할 때입니다. 이 정형화된 대답은 종종 RCU 메커니즘을 처음 접하는 독자들을 혼란스럽게 합니다. 이전 포인터를 해제하기 위해 콜백 함수를 호출하기 전에 모든 프로세서에서 프로세스 전환이 발생할 때까지 기다려야 하는 이유는 무엇입니까? 이는 실제로 RCU의 설계 규칙에 의해 결정됩니다. 이전 포인터에 대한 모든 참조는 rcu_read_lock 및 rcu_read_unlock에 포함된 임계 섹션에서만 발생할 수 있으며 이 임계 섹션에서는 프로세스 전환이 불가능합니다. , 일단 임계 섹션은 더 이상 이전 포인터 p에 대한 참조 형식이 없습니다. 분명히 이 규칙은 리더가 임계 섹션의 프로세스를 전환할 수 없도록 요구합니다. 프로세스 전환이 발생하면 이전 포인터를 해제하는 콜백 함수가 호출되어 전환된 프로세스가 해제될 때 이전 포인터가 해제될 수 있기 때문입니다. 다시 예약되면 해제된 메모리 공간을 참조할 수 있습니다.

이제 rcu_read_lock이 커널 선점형만 꺼야 하는 이유를 알 수 있습니다. 임계 섹션에서 인터럽트가 발생하더라도 현재 프로세스를 전환하고 제거하는 것이 불가능하기 때문입니다. 커널 개발자, 아니 오히려 RCU 설계자가 할 수 있는 일은 너무 많습니다. 다음 단계는 사용자의 책임입니다. RCU의 임계 영역에서 함수가 호출되면 해당 함수가 휴면 상태가 되어 RCU의 설계 규칙을 위반하게 되고 시스템이 불안정한 상태에 들어가게 됩니다.

이것은 무언가를 사용하려면 그 내부 메커니즘을 이해해야 함을 다시 한번 보여줍니다. 방금 언급한 예처럼 지금은 프로그램에 문제가 없더라도 시스템에 숨겨진 위험은 시한폭탄과 같습니다. , 이는 언제든지 폭발할 수 있으며, 특히 문제가 갑자기 발생하기까지 오랜 시간이 걸리는 경우 더욱 그렇습니다. 대부분의 경우, 문제를 찾는 데 걸리는 시간은 진정하고 RCU의 원리를 주의 깊게 이해하는 데 걸리는 시간보다 훨씬 더 길 수 있습니다.

RCU의 리더는 rwlock의 리더보다 자유도가 더 높습니다. RCU 리더는 공유 리소스에 접근할 때 작성자의 감정을 고려할 필요가 없기 때문에 rwlock 작성자와는 다릅니다. rwlock 리더는 공유 리소스를 읽을 때 해당 리소스를 작동하는 작성자가 없는지 확인해야 합니다. 두 가지의 차이점은 RCU가 리더와 작성자 간의 공유 리소스를 분리하는 반면, rwlock 리더와 작성자는 처음부터 끝까지 공유 리소스의 복사본 하나만 사용한다는 점에서 비롯됩니다. 이는 또한 RCU의 작성자가 더 많은 책임을 져야 함을 의미하며 동일한 공유 리소스를 업데이트하는 여러 작성자 간에 일종의 상호 배제 메커니즘이 도입되어야 하므로 RCU는 "잠금 없는 메커니즘"입니다. 작가. 따라서 우리는 읽기 작업이 많고 업데이트 작업이 상대적으로 적은 상황에서 RCU 메커니즘을 사용해야 함을 알 수 있습니다. 이때 RCU의 읽기 작업은 다른 잠금 메커니즘에 비해 잠금 오버헤드가 거의 없기 때문에 RCU는 시스템 성능을 크게 향상시킬 수 있습니다.

실제 사용 시 공유 리소스는 연결 목록 형태로 존재하는 경우가 많습니다. 커널은 RCU 모드에서 연결 목록 작업을 위한 여러 인터페이스 기능을 구현합니다. list_add_tail_rcu, list_add_rcu, hlist_replace_rcu 등과 같은 커널 기능을 사용해야 합니다. 특정 사용법에 대해서는 일부 커널 프로그래밍이나 장치 드라이버 정보를 참조하십시오.

오래된 포인터를 해제하는 측면에서 Linux 커널은 사용자가 사용할 수 있는 두 가지 방법을 제공합니다. 하나는 call_rcu를 호출하는 것이고, 다른 하나는 동기화_rcu를 호출하는 것입니다. 전자는 비동기 방식입니다. call_rcu는 이전 포인터를 노드에 해제하는 콜백 함수를 넣은 다음 현재 call_rcu를 실행 중인 프로세서의 로컬 연결 목록에 노드를 추가합니다. 클럭 인터럽트(RCU_SOFTIRQ)의 부분입니다. , rcu 소프트 인터럽트 처리 함수 rcu_process_callbacks는 현재 프로세서가 절전 기간(커널 프로세스 스케줄링 및 기타 측면을 포함하는 정지)을 경험했는지 여부를 확인합니다. rcu의 커널 코드 구현은 시스템의 모든 프로세서가 절전 후를 경험했는지 확인합니다. 기간(모든 프로세서에서 프로세스 전환이 발생했기 때문에 이때 이전 포인터를 안전하게 해제할 수 있음을 의미), call_rcu에서 제공하는 콜백 함수가 호출됩니다.

동기화_rcu 구현은 대기 대기열을 사용하며 call_rcu와 같은 현재 프로세서의 로컬 연결 목록에 노드를 추가합니다. call_rcu와의 차이점은 이 노드의 콜백 함수가 wakeme_after_rcu라는 점입니다. 시스템의 모든 프로세서에서 프로세스 전환이 발생할 때까지 대기 대기열에서 절전 모드로 전환되므로 rcu_process_callbacks에서 wakeme_after_rcu를 호출하여 절전 모드로 전환된 sync_rcu를 깨운 후 동기화_rcu는 이제 이전 포인터를 해제할 수 있음을 알게 됩니다.

그래서 등록된 콜백 함수는 call_rcu가 반환된 후에 호출되지 않았을 수 있음을 알 수 있습니다. 이는 이전 포인터가 해제되지 않았음을 의미하며, sync_rcu가 반환된 후에 이전 포인터가 해제되었음을 의미합니다. 따라서 call_rcu를 호출할지, 동기화_rcu를 호출할지 여부는 특정 요구 사항과 현재 컨텍스트에 따라 다릅니다. 예를 들어, 동기화_rcu 함수는 인터럽트 처리 컨텍스트에서 사용할 수 없습니다.

이 글에서는 Linux 커널의 효율적인 동기화 메커니즘인 RCU를 소개합니다. 이는 게시-구독 모델을 기반으로 하는 동기화 메커니즘입니다. RCU의 기본 아이디어, 주요 인터페이스 및 구현 세부 사항을 원리 측면에서 분석하고 해당 코드 예제를 제공했습니다. 또한 애플리케이션 관점에서 연결된 목록 작업, 타이머 관리, 소프트 인터럽트 처리 및 기타 시나리오에서 RCU 사용을 소개하고 해당 코드 예제를 제공했습니다. 이 기사를 연구함으로써 우리는 RCU의 기본 사용법을 익힐 수 있으며 실제 개발에서 RCU를 유연하게 사용하여 효율적인 동기화 요구 사항을 달성할 수 있습니다. 이 기사가 도움이 되기를 바랍니다!

위 내용은 Linux 커널의 RCU 메커니즘에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 lxlinux.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제