리눅스 - 커널 동기화
커널 동기화
1. 개요
커널 동기화에서 동기화라는 것은 실행순서에 대해서 맞추는 것을 말한다.
이는 경쟁 조건(Race Condition)에 대한 이야기인데, 이 경쟁 조건에 대해서는 아래의 포스팅을 참고하면 좋다.
경쟁상태(Race condition) 1
경쟁상태(Race condition) 2
경쟁상태(Race condition) 3
위 포스팅에서 말하는 경쟁 조건 해결을 위한 방법으로 락과 세마포어가 있는데, 리눅스에서는 두 가지 방법을 다 지원한다.
2. 원자적 동작
락과 세마포어를 언급하기에 앞서 원자적 동작에 대해서 설명하고 넘어가야한다.
이는 이전에 컴퓨터 기초에 대해 포스팅했을 때 원자적 동작을 보장하는 TestAndSet이라는 함수에 대해서 말한 적이 있다. (기억이 안난다면 이곳 을 참고하라)
리눅스 또한 이러한 원자적 동작에 대해 보장하는 자료형을 지원한다.
1
2
3
4
5
6
7
typedef struct {
int counter;
} atomic_t;
typedef struct {
s64 counter;
} atomic64_t;
위의 atomic_t와 atomic64_t는 “include/linux/type.h”에 정의되어있다. (linux 6.7 커널 기준)
이 둘의 차이는 32bit냐 64bit냐의 차이다. 일반적인 int를 써야할 곳에 원자적 동작을 보장하는 자료형을 쓰지 않게 하기 위함도 있고, 그 반대의 경우도 방지하기 위해 별도의 자료형을 두는 것이다. 또한 컴파일러가 의도치 않게 최적화해서 원자적 동작이 안되게 하는 것을 막는 효과도 있다. 일반적으로 int는 32bit이지만 여기서 쓰는 atomic_t의 경우 24비트라고 가정하는게 좋다. 이는 SPARC 장비에서 24비트를 사용하기 때문인데, 나머지 8bit로 락을 걸어 보호하는 방식이기 때문이다.
원자적 자료형뿐 아니라, 원자적 정수연산, 원자적 비트 연산 모두 지원한다. 이 모든 것들이 지원되어야 아래에서 이야기하는 동기화 처리를 할 수 있기 때문이다.
3. 세마포어
경쟁상태(Race condition) 3 에서 설명한 내용과 크게 다르지 않다.
당장 락을 얻을 수 없다면 휴면 상태로 들어가고 락을 가진 스레드가 락을 반환할 때 락이 필요한 스레드를 깨워서 락을 쥐어준다. 이 경우 스핀락처럼 프로세서를 사용하면서 대기하고 있지 않기 때문에 프로세서 사용률은 높아진다.
하지만 스레드가 휴면 상태로 들어갈때와 휴면상태에서 깨어날 때 시간 소모가 있으므로 차라리 대기하고 있는 상황이 더 시간 소모가 적을 경우에는 사용하지 않는게 좋다.
세마포어는 아키텍처에 따라 다르게 구현되며 “arch/{대상 아키텍처}/include/asm/semaphore.h”에 정의되어있다.
4. 뮤텍스
세마포어의 count를 1로 하면 바이너리 세마포어 혹은 뮤텍스라고 부른다. 어차피 세마포어의 count를 1로 해두면 뮤텍스인데 굳이 별도로 정의할 필요가 있을까 싶지만 리눅스에서는 뮤텍스가 별도로 정의되어있다.
이는 좀 더 명시적으로 휴면 가능한 간단한 락을 도입하기 위해 만든 것이다.
별도로 정의 되어있으며 세마포어와는 달린 count 값을 관리할 필요도 없다.
5. 스핀락
커널에서 가장 일반적으로 사용하는 락의 종류이다.
계속 루프를 돌면서 락을 얻을 수 있을때까지 기다리는 것으로 잠긴 문안에서 사람이 나올때까지 문 앞에서 대기하고 있는 것을 생각하면 편하다. (기억이 안난다면 이곳 을 참고하라)
앞에서 대기 중이다가 바로 사용할 수 있으니 빠른 것 아닌가 하는 생각이 들 수 있다. 빠르긴 하다.
하지만 이는 프로세서가 그만큼 대기하고 있다는 뜻이므로 프로세서 낭비가 될 수 있다.
따라서 하나의 락만 유지해야하며 그것도 단기간에 사용되어야하는 코드에 사용되어야한다.
이럴 바에 그냥 세마포어를 써서 휴면시키고 락을 사용할 수 있을때 깨우면 되지 않나 하는 생각을 할 수도 있겠지만 이는 컨텍스트 전환 시간이 두 번 필요하다. 이로 인한 시간보다 스핀락으로 대기하는 시간이 더 짧을 때 사용하는 것이다.
또한 인터럽트 핸들러에서 락을 사용해야 할 경우에는 무조건 스핀락을 사용해야하는데, 이는 인터럽트 핸들러간에 휴면상태로 들어갈 수 없고, 또한 Deadline이 빠른 작업을 처리하는 것을 염두에 둬야하기 때문이다.
이 스핀락에 대해서는 아키텍처별로 다른 어셈블리어를 이용해 구현된다. (arch/{대상 아키텍처}/include/asm/spinlock.h)
실제 사용 인터페이스는 “/include/linux/spinlock.h”에 정의되어있다.
참고문헌
- 리눅스 커널 심층분석 (에이콘 임베디드 시스템프로그래밍 시리즈 33, 로버트 러브 저자(글) · 황정동 번역)
- 다익스트라 세마포어 논문 영어번역 - About the sequentiality of process descriptions
- 리눅스 커널 6.6.7 버전