Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- 라즈베리파이
- 개발자 취준
- 라즈베리파이 카메라
- .so 라이브러리
- SQLite
- raspberry pi 4
- 가계부 개발
- qt widget
- 금융권 취준
- 노션
- 취준
- yocto
- 멀티클라이언트
- 디바이스 트리
- pthreads
- 데몬 프로세스
- rtsp
- ioctl
- IOT
- udev
- 바이브 코딩
- /dev
- cdev
- systemd
- 캐릭터 디바이스
- 디바이스 트리 작성법
- 커널 모듈
- wiringpi
- 리눅스 커널 드라이버
- 임베디드 리눅스
Archives
- Today
- Total
이로또
임베디드 리눅스 드라이버의 이해 7편: 세마포어 경쟁과 커널 동기화 메커니즘 본문
이 글은 리눅스 커널에서 발생하는 Race Condition 문제와 이를 해결하기 위한 동기화 기법들(세마포어, 뮤텍스, completion, spinlock, seqlock 등)을 정리한 글입니다. 각각의 메커니즘이 어떻게 동작하고, 어떤 상황에 적합하며, 실제 커널 코드에서는 어떻게 사용하는지를 예제와 함께 설명합니다. 공유 자원 보호를 위한 커널 스레드 동기화 실습 예제도 포함됩니다.
목차
- Race Condition과 Critical Section
- 동기화 방식 구분: Blocking vs Busy Waiting
- 세마포어 (Semaphore)
- 뮤텍스 (Mutex)
- 커널 스레드와 공유 자원 보호 실습 예제
- Completion: 작업 완료 대기
- Spinlock: 빠른 임계 구간 보호
- Seqlock: 읽기 성능 최우선 락
1. Race Condition과 Critical Section
- Race Condition: 두 개 이상의 쓰레드가 동시에 공유 자원에 접근하여 결과가 예측 불가능해지는 상황
- Critical Section: Race Condition이 발생할 수 있는 코드 영역
2. 동기화 방식 구분
방식 | 도구 | 설명 |
Blocking (잠들기) | semaphore, mutex, rw_semaphore, completion | context switch 발생 가능, CPU 자원 절약 |
Busy Waiting (불야성) | spinlock, seqlock | 짧고 빠른 작업에 적합, 루프를 돌며 기다림 |
3. 세마포어 (Semaphore)
- 여러 개의 자원에 대한 동시 접근 제어
- P 연산: 값을 1 감소시키고, 자원을 점유함 -> 즉, P 연산은 "나 지금 이 자원 써도 돼?" 라고 묻는 과정
함수 | 설명 |
void down(struct semaphore *sem) | 무조건 기다림. signal 무시 |
int down_interruptible(struct semaphore *sem) | 기다리다가 signal 오면 빠져나옴. → 리턴값 체크 필요 |
int down_trylock(struct semaphore *sem) | 지금 당장 못 얻으면 실패하고 리턴 (대기 없음) |
- V 연산: 값을 1 증가시키고, 자원을 반납함
void up(struct semaphore *sem);
- sema_init()로 초기화
struct semaphore sem;
sema_init(&sem, 1);
down(&sem); // 자원 획득
// 공유 자원 사용
up(&sem); // 자원 반납
4. 뮤텍스 (Mutex)
- 항상 1개의 자원만 보호
- mutex_init(), mutex_lock(), mutex_unlock() 사용
- 잠근 스레드만 해제 가능 → 더 안전함
struct mutex my_lock;
mutex_init(&my_lock);
mutex_lock(&my_lock);
// 공유 자원 사용
mutex_unlock(&my_lock);
세마포어 vs 뮤텍스 비교
항목 | 세마포어 | 뮤텍스 |
값 | 0 이상 (N개 자원) | 항상 Binary |
해제 권한 | 누구나 | 잠근 스레드만 |
사용 용도 | 다중 접근 자원 | 단일 자원 |
5. 커널 스레드 실습 예제 (mutex 사용)
- 두 개의 커널 스레드가 공유 변수 acme_global_variable을 증가시킴
- mutex_lock()으로 임계 구간 보호
주요 흐름 요약
구성요소 | 설명 |
mutex | 공유 자원 보호 |
kthread_run() | 커널 스레드 생성 |
mutex_lock() / mutex_unlock() | 자원 접근 시 동기화 |
6. Completion: 작업 완료 대기
- "다른 스레드가 어떤 작업을 끝낼 때까지 기다리는" 용도로 사용
- wait_for_completion()으로 대기
- complete()로 완료 통지
DECLARE_COMPLETION(my_comp);
wait_for_completion(&my_comp);
complete(&my_comp);
completion vs mutex/semaphore
항목 | completion mutex | semaphore |
목적 | 작업 완료 대기 | 자원 접근 동기화 |
대기 | 일회성 | 반복적 사용 |
7. Spinlock
- 짧은 시간 동안만 보호하는 임계 구간에 적합
- 짧은 시간 동안만 잠글 자원을 보호할 때 사용하는 락(Lock)
- 락이 안 풀려도 "기다리지 않고 계속 확인(루프)" 하며 도는 형태
- 일반적인 mutex는 잠들기 때문에 context switch(문맥 전환) 발생 → 이건 시간/자원 소모가 큼
- 스핀락은 잠들지 않고 루프를 돌면서 기다림 → 락이 곧 풀릴 것 같을 때 훨씬 빠르고 효율적
- spin_lock_irqsave() / spin_unlock_irqrestore() 사용
- context switch 없이 busy-wait
spinlock_t my_lock;
unsigned long flags;
spin_lock_irqsave(&my_lock, flags);
// critical section
spin_unlock_irqrestore(&my_lock, flags);
8. Seqlock
- 읽기-쓰기 동기화(read-write synchronization)를 위한 락
- Reader가 Writer보다 많을 때 유리 → writer를 좀 더 우대
- Reader는 락 없이 읽고, 중간에 Writer가 끼면 재시도
사용 예
unsigned int seq;
do {
seq = read_seqbegin(&mylock);
// 데이터 읽기
} while (read_seqretry(&mylock, seq));
Writer는 seqlock을 이렇게 사용한다
데이터를 쓰는 쪽(Writer)은 임계 구간에 들어가기 전에 lock을 걸고,
다 쓰고 나올 때 lock을 해제한다.
이 과정에서 seqlock 내부의 "시퀀스 번호"가 바뀐다.
Writer가 lock을 걸 때 조건
- 다른 Writer가 없다면 → lock 획득 성공 → 쓰기 가능
- 다른 Writer가 이미 쓰고 있다면 → lock 못 잡고 기다림
- Reader가 있는 건 상관없음 → Reader는 그냥 읽고 있기 때문에 Writer가 무시하고 진입해도 됨
시퀀스 값의 의미
- Writer가 임계 구간에 진입하면, seq 값을 홀수로 바꿈 → "지금 쓰는 중이다" (seq값이 홀수면 다른 writer가 있다는 소리다)
- Writer가 빠져나오면, seq 값을 짝수로 바꿈 → "쓰기 끝났다"
즉, 이 숫자가 홀수냐 짝수냐만 보고도 Reader는 "지금 누가 쓰고 있는가?" 를 알 수 있다.
특징 요약
항목 | 설명 |
Reader 락 없음 | 빠름, 재시도 필요 |
Writer | 시퀀스 번호 갱신, write_seqlock() / write_sequnlock() 사용 |
이 글을 통해 리눅스 커널에서 다양한 동기화 메커니즘이 어떤 상황에서 쓰이는지, 각 방식의 차이점과 특징을 실습 중심으로 이해할 수 있습니다.
'임베디드' 카테고리의 다른 글
드라이버 입문 1편: 드라이버란? 모듈이란? (1) | 2025.06.16 |
---|---|
임베디드 리눅스 드라이버의 이해 8편: 블로킹 입출력 (Blocking I/O) (1) | 2025.06.04 |
임베디드 리눅스 드라이버의 이해 6편: 커널 영역에서 쓰레드 만들기 (0) | 2025.06.04 |
임베디드 리눅스 드라이버의 이해 5편: 커널과 하드웨어의 연결 (0) | 2025.06.04 |
임베디드 리눅스 드라이버의 이해 4편: 인터럽트 처리의 원리 (1) | 2025.06.04 |