스레드들 간 자원 공유하는것은 주의를 요함.
pintos는 이를 위한 다양한 synchronization 함수들을 제공함
Disabling Interrupts 인터럽트 비활성화
synchroniztation을 하는 가장 단순한 방법은
인터럽트(CPU의 실행을 잠시 멈추고, 특정 작업을 처리한 후 실행하던 작업으로 복귀)를 불가능하게 하는 것.
인터럽트가 꺼지면, 다른 스레드는 진행중인 스레드를 선점 불가.
스레드 선점은(preempt)은 timer interrupt에 의해 이뤄지기 때문.
암튼, pintos는 '선점가능한(preemptible) 커널' 임.
선점 가능한 커널은 더 명시적인 동기화가 필요함
인터럽트를 비활성화 시키는 주된 이유는 외부의 인터럽트 핸들러와 커널 스레드를 동기화 시키기 위해서임.
몇가지 외부 인터럽트는 비활성화로도 막을 수 없다. - 마스크 불가능 인터럽트(NMIs)
pintos는 이건 안다룸.
인터럽트 활성화, 비활성화 시키는 자료형 함수들은
include/threads/interrupt.h 에 있음
enum intr_level;
INTR_OFF or INTR_ON으로 상태 확인 알려줌
enum intr_level intr_get_level (void)
현재 인터럽트 상태(inter_level)를 리턴하는 함수.
enum intr_level intr_set_level (enum intr_level level);
현재 상태(inter_level)에 따라 인터럽트를 활성화하거나 비활성화. 이전 상태 리턴.
Semaphores 세마포어
세마포어는 비음수 정수 값을 갖는 변수로 두개의 연산자를 통해서 원자적으로 조작할 수 있습니다.
- "Down" or "P" : 값이 양수가 되기를 기다렸다가, 양수가되면 감소시킴
- "UP" or "V" : 값을 증가시킵니다. (그리고 P연산에서 wait 중인 쓰레드가 있다면 하나를 깨움)
0으로 초기화 된 세마포어는 한번만 일어날 이벤트를 기다리는데 사용됨.
스레드A가 스레드B를 시작시키고 B의 어떤 활동이 끝났다는 시그널을 기다린다고 치자.
A는 0으로 초기화된 세마포어를 만들어서 B에게 넘겨주고 A는 그 세마포어를 down 하는 것과 B가
up 하는건 순서 상관없이 일어남.
1로 초기화된 세마포어는 자원의 접근을 제어하는데 사용됨.
한 코드블럭이 자원을 사용하기 전에, 세마포어를 down 연산함. 다 사용한 후에 up 연산을 함.
1보다 큰 값으로 초기화 될 수 있음.
세마포어는 인터럽트 비활성화와 쓰레드 블로킹(thread_block()) 쓰레드 언브로킹(thread_unblock())으로 이뤄짐.
세마포어는 연결리스트를 사용해서 대기중인 쓰레드의 리스트를 유지함.
(연결 리스트 구현 - lib/kernel/list.c)
locks 락
락은 초기값을 1로 하는 세마포어와 같음 up은 락에서 release와 같고 down연산은 acquire 임.
각 자료형과 함수들은 include/threads/synch.h 에 선언되어 있음.
monitors 모니터
세마포어느 락 보다 더 높은 (추상화) 수준의 동기화 방법.
모니터는 동기화된 데이터, 모니터락이라 부르는 락, 한개 이상의 컨디션 변수로 이루어진다.
보호받는 데이터에 접근하기전에 쓰레드는 모니터락을 얻음. 모니터 안에서 쓰레드는 모든 보호받는
데이터에 접근 할 수 있다. 맘대로 확인 수정 가능.
컨디션 변수는 특정 조건이 참이 될 때까지 모니터안의 코드가 기다릴 수 있게 함
컨디션 변수 자료형과 함수들은 include/threads/synch.h에 선언됨.
최적화 장벽 - 컴파일러가 메모리 상태에 대해 어떤 가정을 못하게 막아주는 특별한 명령문.
컴파일러는 장벽에 막혀서 read와 write의 순서를 재정렬하지 않고, 변수의 값이 수정되지 않는다고 가정.
핀토스에선 include/threads/synch.h 가 barrier() 매크로를 최적화 장벽으로 정의해둠.
쓰는 이유는 컴파일러가 알지 못하는 사이에 다른 인터럽트 핸들러가 비동기적으로 데이터를 바꿀 때가 있기 때문.
devices/timer.c 안의 too_many_loops() 함수가 그 예임.
이 함수는 timer tick이 발생할 때 까지 반복문을 busy-wait(spin)하며 시작
결과적으로 메모리의 읽기, 쓰기 순서를 강제로 정해줄 때 사용될 수 있음.
장벽이 없으면 버그 생김. 컴파일러가 순서 유지해야 할 이유가 안보이면 맘대로 연산함.
같은 소스 파일 안에서 정의된 함수나 포함된 헤더를 최적화 장벽으로 기대하면 안됨.
최적화 장벽은 함수를 정의하기 전에도, 해당 함수의 사용에 대해 적용될 수 있음.
이유 - 컴파일러는 최적화를 수행하기 전에 먼저 전체 소스 파일을 읽고 파싱하기 때