Kernel Synchronization에 관하여 Mar 9, 2008

커널의 동기화 관련함수들이 여럿 있다.
이 함수들을 어느 시점에서 어떤 함수들을 사용하느냐는 사실 숙련된 개발자가
아니라면 여전히 많이 혼동스러운 부분이며, 굉장히 찾기 어려운 버그를 만들어 내곤한다.

http://www.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/c214.html#MINIMUM-LOCK-REQIREMENTS

위의 URL은 Rusty russel이 작성한 문서이며 여러분들이 가지고 있는 커널의 /Documentation/에서도
구할 수 있는 문서이다. 커널의 locking에 관해서 예제와 함께 상당히 잘 정리된 문서이다.
하지만 기본적으로 동기화 facility들에 대한 개념이 없다면 위의 표가 잘 이해되지 않을 것이다.

커널에 관심이 많은 개발자라면 이미 softirq와 tasklet에 관해서는 이해하고 있을 것이다.
이미 많은 linux kernel 관련 서적에서 다루고 있으니 굳이 따로 설명하진 않는다.

위의 표를 이해하기 위해 우리가 알고 있어야 하는 중요한 것이 3 가지 있다.

1. 인터럽트 핸들러가 실행중일 때 다른 인터럽트 핸들러들이 저절로 block되어 지는 것은 아니다.
(많은 개발자들이 잘못 생각하고 있는 부분이다. 물론 IRQF_DISABLED 플래그를 통해 인터럽트 핸들러를 등록하면 커널은 사용자의 의해 등록된 ISR을 실행할 때 인터럽트 전체를 disable하여 시스템의 모든 인터럽트가 발생하지 않게 해줄 수 있긴하다. 이런 행동은 굉장히 지양해야 하는 행동이지만 제공하긴 한다. 커널의 default 행동은 그렇지 않다는 것이다.)

2. 한 CPU의 인터럽트가 disable되었다고, 다른 CPU도 disable되지 않는다. 즉 인터럽트 핸들러 A를 실행하지 못하게 하려고 CPU A에서 disable해봐야, 인터럽트 핸들러 A는 CPU B에서 실행될 수도 있다는 것이다. 참고로 하나의 인터럽트 핸들러가 동시에 여러 CPU에서 실행되지 않는 다는 것은 커널이 보장해 준다.

3. softirq는 다른 softirq를 선점하지 않는 다는 것이다. 이는 softirq를 이용하여 만들어진 tasklet에도 그대로 적용된다.

4. 부록으로 하나만 더 알고 넘어가자. User context가 무얼까? 커널에 관련된 책이나 자료들을 보다 보면 Interrupt Context, User Context(Process Context)에 관해 가끔 말이 나온다. User context는 실행중인 User 영역의 프로세스를 의미하는 것이 절대 아니다. 여러분들은 구분을 current로 하면 된다. 현재 시점에서 current(current가 무엇인지 모른다면 아직 이 글을 보지 말길 권유한다)가 지금 커널이 행하고 있는 일의 주체인가를 판단하면 된다. 예를 들어 인터럽트가 발생했다고 가정하자. 커널은 인터럽트 처리를 바쁘게 하고 있다. 그 시점에서 current는 누가 될지 모른다. 왜냐하면 인터럽트는 비동기적인 이벤트이기 때문이다. 인터럽트를 유발 시킨 프로세스 B, 또는 커널의 특정 서브 시스템이라고 하더라도 실제 그 인터럽트 처리는 프로세스 C가 실행 중인 시점에서 발생하여 처리를 하게 될지도 모르기 때문이다. 이것은 Interrupt Context이다.
반면, User Context는 현재 커널이 프로세스를 대신해서 수행하고 있는 경우이다. 일반적으로 system call(page fault와 같은 exception도 user context로 볼 수 있다. 왜냐하면 이때 커널은 current를 access하기 때문이다)이 이에 속한다. 이 때 프로세스란 user-mode process일 수도 있고 kernel thread일 수도 있다. Anyway, 현재 시점에서의 current는 그 일을 유발한 프로세스를 가리키고 있다는 것이다. 이해가 되었으면 좋겠다.

이 부분 만큼은 기억하고 있어야만 위의 표를 논리적으로 풀어 나갈 수 있다. (우리가 수학을 배울때도 기본 공식은 외우고 있어야 만 하듯이 위는 기본적으로 우리가 알고 있어야 하는 기본이라고 생각하길 바란다.)

위의 3가지를 암기하였다면 위의 표를 풀어 나가기 위해서는 두가지 질문을 던져 보면 된다.

1. A가 B에 의해 선점될 수 있는가?
=> If yes, you should use "[irq/bh] disalbe"
2. A의 critical section이 다른 CPU에 의해 접근될 수 있는가?
=> If yes, you should use "spin_lock".

자, 위의 법칙을 적용해 보자. (문제를 풀 때는 항상 약한(?) 놈을 A로 하며 당신의 시스템은 무조건 SMP라고 하자. SMP로 가정하는 것은 중요하다. 리눅스 커널은 general한 OS이다. 당신의 코드가 반드시 UP의 NON-PREMMPTIBLE에서만 실행된다는 보장은 없다. 그러므로 무조건 SMP를 고려해야 한다. )

예제 1) User Context A, Takelet B

1법칙의 답변 ) yes
2법칙의 답변) yes

당연하다. user context는 인터럽트에 의해 선점될 수 있다.
인터럽트 종료시 softirq가 실행되고 결국 tasklet이 실행되게 된다. 그러므로 tasklet은 interrupt context에서
동작하며 user context A에게는 일종의 인터럽트로 인한 선점으로 볼수 있다. 그러므로 A는 B에 의해
선점될 수 있으므로 1법칙의 답변은 yes이다. 그러므로 우리는 bh를 disable해야 한다. (이 경우, 굳이 irq까지 disable할 필요는 없다. 이유는 따로 설명하지 않는다.)

2법칙의 답변 또한 당연하다.
A의 코드는 SMP 환경에서 언제나 다른 프로세서의 의해 접근 될 수 있기 때문이다. 그러므로 spin lock을 사용해야 한다.

위를 종합해보면 결국 우리는 spin_lock_bh를 사용해야 한다는 것이다.

예제 2) Taklet A, Tasklet B

1법칙의 답변 ) No
2법칙의 답변) yes

2번이 왜 yes인지는 조금만 생각하면 알 수 있다. tasket은 softirq에 의해 실행된다. softirq는 인터럽트에 의해 실행된다. 인터럽트는 어느 CPU에 의해서나 처리 될 수 있다. 즉, tasklet A가 CPU A에 의해 실행중일 때 CPU B는 tasklet B를 실행시킬 수 있다는 것이다. 그러므로 A와 B 두 tasklet들이 서로 critical section을 가지고 있다면 보호되어야 마땅하다. 만일, 두 tasklet 가운데 공유되는 변수가 없다면 굳이 lock을 사용할 필요는 없어지므로 답이 NO일수도 있다.

그럼 왜 1은 NO일까? 미리 얘기했듯이 softirq는 선점되지 않는다라고 이미 얘기하였다.
결국 위를 종합해보면 spin_lock만으로 충분하다.


예제 3) softirq A, Interrupt B

1법칙의 답변 ) yes
2법칙의 답변) yes

이뙈 왜 2법칙의 답변이 yes인지 궁굼할 수도 있을 것이다. 이건 여러분들의 몫.

예제 4) Interrupt A, Interrupt B

1법칙의 답변 ) yes
2법칙의 답변) yes

하지만 이는 주의가 필요하다. 이때는 spin_lock_irqsave를 사용해야 한다는 것이다.
그 이유는 앞의 예제 3가지는 모두 이미 커널의 디자인 단계에서 irq가 enable되어 있다는 것을 보장하고 있기 때문에 우리는 아무 생각 없이 spin_lock_irq를 사용할 수 있었다. 즉 softirq, tasklet, user context에서는 항상 irq가 enable되도록 커널이 설계되었기 때문에 우리는 아무 생각 없이 spin_lock_irq를 사용하면 됐다는 것이다.

하지만, 커널의 인터럽트 핸들러가 호출되는 시점은 irq가 enable되어 있을 수도 있고 disable되어 있을 수도 있다.
(이는 인터럽트 핸들러의 등록시 SA_INTERRUPT와 같은 파라미터를 통해 결정되기도 하며, kernel의 interrupt handler들은 nesting될 수 있기 때문이기도 하다. 즉, interrupt handler A가 수행되기 전, 어떤 interrupt handler들이 어떤 순서로 어떤 일을 했을지 모르기 때문이다.) 그러므로 우리는 함부로 spin_lock_irq를 사용할 수 없고 대신, spin_lock_irqsave를 사용하여 interrupt on/off 상태를 기억하고 있어야 한다.

0 개의 덧글: