Mutex
- Mutex는 "mutual exclusion(상호 배제) - 스레드 간의 발생할 수 있는 충돌 문제(전역변수를 선언하여 공유 변수로 선언하였어도 1번에 1개의 스레드만 작동하게끔 os에서 막아줌, 다른 스레드는 기다리게됨!"의 약자
- Mutex 변수는 스레드 동기화를 구현하고, 여러 번의 쓰기 작업이 발생할 때 공유 데이터를 보호하는 주요 수단 중 하나
- Mutex 변수는 공유 데이터 리소스에 대한 접근을 보호하는 "잠금(lock)"과 같은 역할을 합니다.
Mutex의 기본 개념
- 단 하나의 스레드만이 주어진 시점에 Mutex 변수를 잠그거나 소유할 수 있음, 나머지 스레드는 waiting queue에 들어가서 대기함
- 따라서 여러 스레드가 Mutex를 잠그려고 시도하더라도 단 하나의 스레드만 성공할 수 있음
- 소유한 스레드가 해당 Mutex를 잠금 해제하기 전까지 다른 스레드가 해당 Mutex를 소유할 수 없음 / 이때, 작업을 마친 스레드는 반드시 "락 권한"을 해제해야 함
- 스레드는 보호된 데이터에 접근하기 위해 순서를 지켜야 함
- Mutex는 "race" 조건(스레드 간의 충돌문제)을 방지하는 데 사용할 수 있음

은행 계좌에 돈을 입금하는 작업을 서로 다른 사이트에서 같은 계좌에서 발생하려고 하는 상황, 여기서 계좌는 2개의 스레드가 공유하는 "공유 데이터", 1200달러에서 1200달러를 예치하여 총 2400달러가 예치됨

read -> update -> write 함수를 사용하여 작업을 수행함, producer 스레드는 첫 번째 과정을 consumer 스레드는 두 번째 과정을 수행함 / 운이 좋아 1개의 스레드가 작업이 완료되고 다음 스레드가 실행이 되었다면 정상적으로 5값이 출력됨!
-> 동시에 여러 개의 스레드가 업데이트를 못하도록 critical section으로 만들어서 os에게 권한을 주어 먼저 권한을 요청한 스레드에게 os가 권한을 주고 나머지 스레드는 waiting queue에 들어가서 대기하게 됨
- Mutex를 소유한 스레드에 의해 수행되는 작업은 종종 전역 변수 업데이트임
- 업데이트되는 변수는 "임계 영역(critical section)"에 속함
- Mutex 사용의 일반적인 순서(메커니즘)는 다음과 같음:
- Mutex 변수를 생성하고 초기화함
- 여러 스레드가 Mutex를 잠그려고 시도함
- 단 하나의 스레드만 성공하며 해당 스레드가 Mutex를 소유함
- 소유한 스레드는 일련의 작업을 수행, lock을 얻지 못한 스레드는 waiting queue에 들어가서 대기하게됨!
- 소유한 스레드가 작업을 완료하고 Mutex의 잠금을 해제
- 다른 스레드가 Mutex를 획득하고 과정을 반복
- 마지막으로 Mutex가 소멸됨
- 공유 데이터를 보호할 때, Mutex를 사용해야 하는 모든 스레드가 이를 사용하도록 보장하는 것은 프로그래머의 책임
Creation / Initialization

- pthread_mutex_t
- 뮤텍스 잠금을 나타내는 변수 유형
- 사용 전에 반드시 초기화해야 함 - mutex 뿐만이 아닌 다른 동기화 객체도 초기화하여야 함
- (1) 정적 변수의 초기화 : 단순히 PTHREAD_MUTEX_INITIALIZER를 할당
- (2) 동적으로 할당된 변수의 초기화
- pthread_mutex_init() 함수를 호출 - 초기화 함수 호출 (초기화하려는 변수, 2번째 매개변수)
- attr 매개변수는 뮤텍스 속성 객체에 대한 것으로, 기본값으로는 NULL 사용 - 2번째 매개변수
- 반환값 : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 이미 초기화된 뮤텍스의 초기화 : 동작이 정의되지 않음 -> 그렇기에, 초기화는 1번만 진행할 것!
Destroy

- pthread_mutex_destroy(pthread_mutex_t *mutex); (사용이 완료된 함수는 destroy함수를 호출하여 해제해야함)
- 반환값 : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 정의되지 않은 동작 (destroy 함수 사용 시, 주의해야할 사항) :
- 뮤텍스가 파괴된 후 스레드가 해당 뮤텍스를 참조하는 경우
- 한 스레드가 파괴(destroy) 함수를 호출하고, 다른 스레드가 뮤텍스를 잠금 상태로 유지하고 있는 경우(뮤텍스가 사용중인 경우)
Locking / Unlocking - (초기화 이후 다음 단계)

- pthread_mutex_lock() : 뮤텍스가 사용 가능할 때까지 블록(대기) 상태 유지 - lock 함수를 호출한 것은 lock권한을 얻고 가장 처음 권한을 요청한 스레드만 lock을 가지고 나머지 스레드는 mutex 변수에 waiting queue에 대기하게됨 이때, waiting queue에 들어간 스레드들은 block상태가 됨, critical section에 들어간 변수가 작업을 완료하고 lock을 놓으면 순차적으로 다음 스레드가 lock권한을 받아 작업을 수행하게 됨 - blocking 함수
- pthread_mutex_trylock() : 즉시 반환 - lock을 요청할 수 있는지 한 번 체크해보는 함수, non-blocking 버전으로 요청하는 함수, lock을 얻으면 좋은 것 lock을 얻지 못하더라도 반환값을 제공
- 반환값 : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환 - (trylock(), lock을 얻으면 0, lock을 못 얻으면 오류 코드 반환)
사용 예제
PTHREAD_MUTEX_INITIALIZER로 초기화를 진행하고 lock을 호출하여 critical section으로 설정하여 작업을 진행하고 작업을 완료하면 unlock함수를 호출하여 lock권한을 해제(이때, 같은 mutex 함수를 lock하면 동일한 함수를 unlock을 진행해야함)
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mylock);
/* critical section */
pthread_mutex_unlock(&mylock);
At-Most-Once execution - (기껏해야 한 번 실행하는 함수 - 호출 x or 호출 1번)

- 뮤텍스에 접근하기 전에 pthread_mutex_init를 호출해야 하지만, 각 스레드가 개별적으로 뮤텍스를 초기화하는 것은 작동하지 않음
- pthread_once()
- 한 번만 실행되는 동작을 지원하는 POSIX 함수
- once_control은 PTHREAD_ONCE_INIT로 정적으로 초기화되어야 함
- init_routine은 pthread_once가 처음 호출될 때만 실행되며, 이후 호출에서는 실행되지 않음 (이때, void타입의 변수로 호출해야 하는 제약사항이 존재함)
pthread_once() 예제
- 초기화 함수 printinitonce를 구현
- var는 printinitonce에 의해 단 한 번만 변경되므로 뮤텍스로 보호되지 않음
- 수정은 printinitonce에서 반환되기 전에 발생함
#include <pthread.h>
#include <stdio.h>
static pthread_once_t initonce = PTHREAD_ONCE_INIT;
int var;
static void initialization(void) {
var = 1;
printf("The variable was initialized to %d\n", var);
}
int printinitonce(void) { /* 초기화는 최대 한 번만 호출됨 */
return pthread_once(&initonce, initialization);
}
PTHREAD_ONCE_INIT으로 초기화를 진행하고 initialization함수를 호출하여 pthread_once함수를 호출하여 printinitonce함수를 호출하여 바로 호출하지 않고 pthread_once함수를 호출하여 간접적으로 호출하고, 여러 스레드가 동시에 pthread_once 함수가 여러 번 호출되더라도 최초 1번만 실행되고, 그 다음 스레드에서는 실행이 안되도록 보장됨
대안 예제
- 정적으로 초기화된 뮤텍스를 사용하여 한 번만 초기화(At-most-once initialization) 수행- printinitmutex 함수
- 뮤텍스에 정적 저장 클래스를 부여하면 함수가 호출될 때마다 동일한 뮤텍스가 사용됨을 보장
#include <pthread.h>
#include <stdio.h>
int printinitmutex(int *var, int value) {
static int done = 0;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int error;
if (error = pthread_mutex_lock(&lock))
return error;
---------------critical section----------
if (!done) {
*var = value;
printf("The variable was initialized to %d\n", value);
done = 1;
}
-----------------------------------------
return pthread_mutex_unlock(&lock);
}
done 변수는 초기화가 완료가 안되었으면 0, 완료가 되었으면 1 -> 초기화된 정적변수로 선언하면 정적 변수 영역은 초기화 된 채로 존재하게 됨
"정적 변수"란 프로그램에서 한 번 메모리에 할당되면 프로그램이 종료될 때까지 유지되는 변수를 의미합니다.
- "적어도 한 번(at-least-once)" 실행 의미는 초기화에 중요함
- pthread_mutex_init를 사용하려면 "한 번만(at-most-once, 실행되면 오직 1번만 가능)"과 "적어도 한 번(at-least-once, 2번 이상 가능)" 실행 의미(= 정확히 한 번)이 모두 필요함
- 때로는 프로그램의 구조가 정확히 한 번 실행을 보장함
- 메인 스레드가 다른 스레드를 생성하기 전에 모든 필요한 초기화를 수행함
{Condition Variables (조건 변수)}
동기 부여 (Motivation)
- 어떤 임의의 조건이 만족될 때까지 스레드가 대기해야 하는 문제 - 예: 두 공유 변수 x와 y가 같아질 때까지 대기
바쁜 대기(Busy waiting) 해결책 - while( x != y );
비바쁜 대기(Non-busy waiting) 해결책
- 뮤텍스를 잠금 - x와 y도 공유변수로 참조할 수 있기에 일단 lock으로 권한을 얻고 수행해야함
- 조건 x == y를 검사
- 참이면, 뮤텍스를 해제하고 루프를 종료
- 거짓이면, 스레드를 중단하고 뮤텍스를 해제 - 반드시 unlock하고 스레드를 중단해야함 / wait함수로 x와 y가 같을 때까지 신호가 오면 깨어나서 판단 하고 다시 잠드는 과정을 반복
OverView
- 조건 변수(Condition variables)는 스레드가 동기화할 수 있는 또 다른 방법을 제공
- 뮤텍스(Mutexes)는 데이터 접근을 제어하여 동기화를 구현
- 조건 변수는 데이터의 실제 값에 따라 스레드가 동기화할 수 있도록 함 - 조건에 따라 공유 데이터의 값을 변경 및 wait하고 깨우는 시그널이 필요함
- 조건 변수가 없을 경우:
프로그래머는 스레드가 조건이 충족되었는지 확인하기 위해 계속 폴링해야 함 (임계 영역 내에서일 수도 있음)이는 스레드가 이러한 활동에 지속적으로 바빠야 하므로 매우 자원 소모적일 수 있음- 조건 변수는 폴링 없이 동일한 목표를 달성할 수 있는 방법을 제공
- 조건 변수는 항상 뮤텍스 잠금과 함께 사용됨
Condition Variable
- 조건 변수 (Condition variable) : 새로운 데이터 유형으로, 특정 조건이 충족되기를 기다리는 프로세스 waiting 큐와 연결됨
- pthread_cond_wait -> wait함수를 호출하면 waiting queue에 대기하게 됨
- 조건 변수와 뮤텍스를 매개변수로 받아 호출한 스레드를 원자적으로 중단시키고 동시에 뮤텍스 변수의 lock을 해제
- 스레드가 알림(신호)을 받을 때 뮤텍스의 lock권한을 다시 획득한 상태로 반환
- 반드시, wait함수를 호출하기 위해서는 뮤텍스의 lock권한을 소유한 스레드에서만 호출해야 함
- pthread_cond_signal
- 조건 변수를 매개변수로 받아 해당 큐에서 기다리는 스레드 중 최소 하나를 깨움
- 조건 변수 큐에서 뮤텍스 큐로 스레드를 이동시키는 효과를 가짐
조건 변수 예제 (Condition variable example)
- 조건 x == y를 기다리기 위해 POSIX 조건 변수 v와 뮤텍스 m을 사용하는 경우:
pthread_mutex_lock(&m); m이라는 mutex 변수에 대한 critical section while( x != y ) pthread_cond_wait(&v, &m); /* 필요하면 x 또는 y를 수정 */ pthread_mutex_unlock(&m);
- 다른 스레드가 x 값을 증가시키고 대기 중인 스레드에 알림을 보내는 경우
pthread_mutex_lock(&m); m이라는 mutex 변수에 대한 critical section
x++;
pthread_cond_signal(&v);
pthread_mutex_unlock(&m);
위와 아래의 조건이 wait하고 signal하는 과정을 반복하고 값이 같아지면 기다렸던 작업을 수행하고
unlock을 하여 해제하는 형식
조건 변수 생성 (Creating condition variables)

- pthread_cond_t
- 조건 변수를 나타내는 변수 유형
- 사용 전에 항상 초기화되어야 함
- 정적 변수 초기화 : 단순히 PTHREAD_COND_INITIALIZER를 할당
- 동적으로 할당된 변수 초기화
- pthread_cond_init를 호출
- attr 매개변수는 속성 객체에 대한 것으로, 기본값으로 NULL을 사용
- 반환값 : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 이미 초기화된 조건 변수의 초기화 : 동작이 정의되지 않음 - 반드시, 1번만 초기화하여 사용해야 됨
조건 변수 삭제 (Destroying condition variables)

- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 정의되지 않은 동작 (Undefined behaviors)
- 스레드가 삭제된 조건 변수를 참조하는 경우
- 다른 스레드가 해당 조건 변수에서 차단된 상태에서 한 스레드가 조건 변수를 삭제하려고 시도하는 경우 - 더이상 사용하지 않는 것을 확인하고 destroy해야 함
조건 변수 대기 (Waiting on condition variables)

- 매개변수 (Parameters)
- cond: 조건 변수의 포인터
- mutex: 호출 전에 스레드가 획득한 뮤텍스의 포인터
- 대기 함수는 스레드가 조건 변수 대기 큐에 들어갈 때 이 뮤텍스를 해제
- pthread_cond_timedwait
- 제한된 시간 동안 대기하는 데 사용
- abstime: 조건 변수 신호가 발생하지 않을 경우, 반환할 시간을 가리키는 포인터 (nsec 단위까지 지정 가능)
- 반환값 (Return values)
- 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- abstime에 지정된 시간이 만료된 경우 ETIMEDOUT 반환 -> 시그널에 의해서 종료된건지 or 시간이 다 되어서 종료된 건지 반환값을 보고 판단이 가능
- 조건 변수 대기 중 신호 전달 (Signal delivery while waiting) : 신호 핸들러에서 반환된 후 대기를 다시 시작할 수 있으며, 가짜 깨움(spurious wakeup)으로 인해 0을 반환할 수도 있음
- POSIX 명세 : 동일한 조건 변수에 대해 다른 뮤텍스 잠금을 사용하여 동시에 대기 작업을 수행하는 경우의 동작은 정의되지 않음
조건 변수에 대한 신호 전달 (Signaling on condition variables)

- pthread_cond_signal : cond로 지정된 조건 변수에서 차단된 스레드 중 최소 하나를 깨움 - waiting queue에 대기 중인 스레드 중 1개를 깨움
- pthread_cond_broadcast : cond로 지정된 조건 변수에서 차단된 모든 스레드를 깨움
- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
{Signal handling and threads}
다중 스레드에서의 신호 전달 (Signal delivery in threads)
- 프로세스 내 모든 스레드는 프로세스의 신호 핸들러를 공유함
- 각 스레드는 고유한 신호 마스크를 가짐
- 세 가지 신호 유형과 전달 방식
- 비동기 신호(Asynchronous) : 차단되지 않은 일부 스레드에 전달
- 동기 신호(Synchronous) : 신호를 발생시킨 스레드에 전달
- 지정된 신호(Directed) : 특정 스레드에 전달 (pthread_kill 사용)
신호 지정 (Directing a signal)

- pthread_kill : 지정된 스레드(1)에 신호 번호(sig)(2)를 생성하고 전달하도록 요청
- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
스레드에 대한 신호 마스킹 (Masking signals for threads)

- pthread_sigmask : 스레드의 신호 마스크를 검사하거나 설정
- how 매개변수
- SIG_SETMASK: 기존 시그널을 무시하고 새로 지정된 시그널로 다시 설정, 스레드의 신호 마스크를 set으로 교체
- SIG_BLOCK: set에 포함된 추가 신호를 차단, 기존 시그널을 그대로 두고 새로이 신호를 추가하는 역할
- SIG_UNBLOCK: 현재 차단된 상태인 set의 신호를 차단 해제, 현재 신호에서 지정된 시그널을 빼는 역할
- oset 매개변수 : NULL이 아닌 경우, 함수는 *oset에 스레드의 이전 신호 마스크를 설정
- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
신호 처리를 위한 스레드 전용화 (Dedicating threads for signal handling)
- 다중 스레드 프로세스에서 신호를 처리하는 권장 사항
- 특정 스레드를 신호 처리에 전용화하기:
- 메인 스레드는 모든 신호를 차단함
- 시그널 전용 스레드를 생성함
- 전용 스레드는 해당 신호에 대해 sigwait()을 실행함
- 대안적으로, 스레드는 pthread_sigmask를 사용하여 신호를 차단 해제할 수 있음
- 특정 스레드를 신호 처리에 전용화하기:
- 프로그램 13.14 - 여러 개의 스레드 중에서 특정 시그널이 오면 그 시그널을 처리하는 전담 시그널을 두는 프로그램
- signalthreadinit()을 호출하여 signo 신호를 차단하고, 해당 신호를 대기하는 전용 신호 스레드를 생성함
- signo 신호가 보류 상태(block)가 되면, sigwait이 반환되고 신호 스레드는 setdone() 함수를 호출하여 작업 중인 것을 중단시킴
- sigwait이 보류 중인 신호를 제거하기 때문에 별도의 신호 핸들러는 필요하지 않음
{Readers and Writers}
Reader-writer 문제 (Reader-writer problem)
- 상황 설명
- 자원이 읽기 및 쓰기라는 두 가지 접근 방식을 허용하는 경우
- 쓰기: 독점적으로 허용되어야 함
- 읽기: 공유 가능
- 자원이 읽기 및 쓰기라는 두 가지 접근 방식을 허용하는 경우
- 두 가지 일반적인 전략 - 우선순위에 따라 배분, 둘 중 1개 선택
- 강력한 읽기 동기화 (Strong reader synchronization) : 읽기 작업에 우선권을 부여하며, 현재 쓰기 작업이 없는 경우 읽기 작업 허용 - reader 스레드가 전부 일고 나면 writer 스레드가 수정 가능토록 함
- 강력한 쓰기 동기화 (Strong writer synchronization) : 쓰기 작업에 우선권을 부여하며, 대기 중이거나 활성화된 모든 쓰기 작업이 완료될 때까지 읽기 작업 지연 - writer에게 우선 순위를 줘서,writer 스레드가 값을 변경할 때까지 reader 스레드는 기다리고 수정이 완료되면 writer스레드를 수행토록 함
- POSIX는 읽기-쓰기 잠금을 제공 : 잠금 상태에서 쓰기 작업이 차단된 경우, 읽기 작업에 잠금을 허용할지는 구현에 따라 다름
읽기-쓰기 잠금의 초기화 (Initialization of read-write locks)

- pthread_rwlock_t
- 읽기-쓰기 잠금을 나타내는 변수 유형
- 사용 전에 반드시 초기화되어야 함
- attr : 읽기-쓰기 잠금 속성 객체에 대한 포인터, 기본값 null로 지정
- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 이미 초기화된 읽기-쓰기 잠금의 초기화 : 동작이 정의되지 않음 - 반드시, 초기화를 1번만 수행토록 해야 함
읽기-쓰기 잠금의 삭제 (Destroying read-write locks)

- 설명
- 매개변수로 지정된 읽기-쓰기 잠금을 삭제함
- 이 함수로 삭제된 pthread_rwlock_t 변수는 pthread_rwlock_init을 통해 다시 초기화할 수 있음
- 반환값 (Return values) : 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환
- 삭제된 읽기-쓰기 잠금의 참조 : 동작이 정의되지 않음
잠금 및 잠금 해제 (Locking/unlocking)

- rdlock / tryrdlock : 읽기용 읽기-쓰기 잠금을 획득하도록 스레드에 허용 - Blocking or Non-Blocking
- wrlock / trywrlock : 쓰기용 읽기-쓰기 잠금을 획득하도록 스레드에 허용 - Blocking or Non-Blocking
- unlock : 잠금을 해제
- 반환값 (Return values)
- 성공 시 0 반환, 실패 시 0이 아닌 오류 코드 반환 - (Blocking 모드로 호출할 경우)
- tryrdlock 및 trywrlock은 잠금을 이미 다른 스레드가 보유하고 있어 획득할 수 없는 경우 EBUSY 반환 - (Non-Blocing 모드로 호출할 경우)
- 주의사항 : 스레드가 이미 wrlock으로 획득한 잠금에 대해 rdlock을 호출하면 교착 상태(데드락)가 발생할 수 있음, 다중 스레드에게만 허용되는 경우는 스레드가 read용 lock을 요청했을 경우에만 적용됨 - getdata_r() 함수일 때
예제 (Example)
- initialize_r() : pthread_once를 사용하여 읽기-쓰기 잠금이 한 번만 초기화되도록 보장
{A sterror_r implementation}
strerror()
- 문제점 (Problem)
- strerror()는 스레드 안전하지 않은 몇 안 되는 함수 중 하나
- strerror()를 동시에(concurrently) 사용할 경우, 뮤텍스 잠금을 사용하여 보호해야 함
- perror()와 strerror() 모두 비동기 신호(async-signal) 안전하지 않음
- 해결 방법 (Solution)
- 동기화를 래퍼(wrapper)로 캡슐화 (Program 13.17 참조)
- perror_r()와 strerror_r()는 모두 스레드 안전하며 비동기 신호 안전
- 뮤텍스를 사용하여 strerror가 사용하는 정적 버퍼에 대한 동시 접근을 방지
- perror()도 동일한 뮤텍스로 보호되어 동시 실행 방지
- 뮤텍스가 잠기기 전에 모든 신호를 차단, 그렇지 않으면 신호 핸들러 내부에서 이를 호출할 경우 데드락이 발생할 수 있음
{Deadlocks}
교착 상태(Deadlocks)와 기타 까다로운 문제들
- 교착 상태 발생 가능성 : 동기화 구성을 사용하는 프로그램은 POSIX 기본 표준 구현에서 감지되지 않는 교착 상태의 가능성을 가질 수 있음
- 상황별 문제점
- 뮤텍스를 이미 보유한 상태에서 pthread_mutex_lock 실행 : pthread_mutex_lock이 실패하고 EDEADLK를 반환할 수 있지만, 표준에서 이를 필수로 요구하지는 않음
- 락을 보유한 스레드에서 오류 발생 : 반환하기 전에 반드시 락을 해제해야 함
- 우선순위가 있는 스레드들 (우선순위 반전)
- 중간 우선순위의 통신 스레드가 저우선순위 스레드(기상 데이터를 수집하는)의 작업을 가끔씩 선점(스레드가 실행 중이다가 자신 보다 높은 우선순위의 스레드가 등장하면 우선순위를 넘겨주는 것)
- 저우선순위 스레드가 뮤텍스를 보유하고 있을 경우, 높은 우선순위 스레드가 긴 시간 동안 지연될 수 있음
'Computer Science > 시스템 프로그래밍' 카테고리의 다른 글
Chapter 14. Critical Sections and Semaphores (1) | 2024.12.09 |
---|---|
Chapter 12. POSIX Threads (0) | 2024.11.26 |
시스템 프로그래밍 chapter09. (타임과 타이머) (1) | 2024.11.19 |
시스템 프로그래밍 chapter08. (신호) (0) | 2024.11.07 |
시스템 프로그래밍 chapter 06. (UNIX 스페셜 파일) (0) | 2024.11.05 |