http://www.kocw.net/home/search/kemView.do?kemId=978503
프로세스 동기화(Process Synchronization)
프로세스끼리의 관계는 Independent와 Cooperating로 나눌 수 있는데, Independent는 각각의 process가 서로에게 전혀 영향을 끼치지 않는 것을 의미하며 Cooperating은 process가 서로 영향을 주고받는 관계를 의미한다. 프로세스 간 통신의 예로는 전자우편 및 파일 전송 등이 있고 프로세스 간 자원 공유의 예로는 DB 등이 있다. 이러한 Cooperating 관계에서는 프로세스 동기화가 필요하다. 프로세스 동기화란 여러 프로세스가 공유하는 자원의 일관성을 유지하는 것을 뜻한다. 스레드끼리도 공유하는 자원이 있으므로 스레드 동기화도 유지되어야 한다. 공유하는 자원의 일관성이 지켜지지 않는다면 A라는 계좌가 있을 때 a가 A를 보면 5000원이 들어있다고 나오고 b가 A를 보면 500,000이 있다고 나오는 등의 문제가 발생할 수 있다. 따라서 프로세스 동기화는 아주 중요하다. 정리하자면 공유 자원에 대한 동시 접근으로 인해 데이터 불일치(data Inconsistency)가 발생할 수 있으므로 데이터 일관성(data consistency)이 유지되도록 하는 cooperating process 간 질서 있는 실행이 필요하다는 것이다.
BankAccount Problem
프로세스 동기화가 깨지는 유명한 예제로 은행 계좌 문제가 있다. 아래는 자식이 계좌에서 1000원을 100번 출금하고 부모는 계좌로 1000원을 100번 입금하는 코드이다. 당연히 1000원을 100번 출금/입금했으므로 계좌의 잔액은 0원이 찍혀야 할 텐데 막상 잔액을 출력해 보면 엉뚱한 결과가 나온다. 프로세스 동기화가 깨진 것이다. 이러한 문제가 발생하는 이유는 공통 자원인 balance에 접근하는 도중 문제가 발생하기 때문이다. balance에 접근하는 코드 중 하나인 balance = balance + amount를 예로 들어 생각해 보자. balance = balance + amount는 결국 어셈블리어로 해석될 것이다. 이를 어셈블리어로 보면 아래의 과정으로 나누어져 나타난다.
1. balance를 가져온다.
2. amount를 가져온다.
3. balance와 amount를 더한다.
4. balance에 3의 결과를 저장한다.
위 과정의 1번과 2번 사이 혹은 2번과 3번 사이 혹은 3번과 4번 사이에서 context switing이 발생하면 다른 스레드로 작업이 전환된다. 전환된 스레드가 balance를 보았을 때 아직 업데이트가 되지 않았으므로 전환된 새 스레드는 예전 값을 가지고 자신의 계산을 진행하게 된다. 이때 문제가 발생되는 것이다. 실제 동작으로 예시를 들면 잔액 0원인 계좌에 1000원을 입금하는 스레드와 1000원을 출금하는 스레드가 있다고 가정해 보자. 1000원을 입금하는 스레드가 먼저 동작하여 3번 작업을 하는 도중 context switcing이 일어난다. 이제는 출금하는 스레드가 동작하게 된다. 출금하는 스레드가 보았을 때 계좌의 잔액은 0원이다. 따라서 출금을 완료하면 계좌의 잔액은 -1000원이 된다. 이제 할 일을 다 했으므로 다시 입금하는 스레드로 context switing이 발생한다. 입금하는 스레드는 3번까지 한 일을 다시 가져와 동작을 하는데 본인이 가지고 있는 balance 값은 맨 처음 balance 값이므로 0원이다. 이 balance(0원)에 1000원을 더하면 1000원이 된다. 따라서 최종 계좌의 잔액은 1000원이 된다. 잔액이 0원인 계좌에 1000원을 입금/출금했는데 잔액이 1000원이 되는 이상한 상황이 발생했다. 이러한 문제가 발생하는 원인은 결국 계산의 과정 중 context switching이 일어났기 때문이다. 따라서 1~4의 작업은 중간에 끼어들지 못하는 atomic(원자, 더 이상 쪼갤 수 없는)한 작업이 되어야 하며 그렇게 된다면 문제가 해결될 것이다.
임계구역 문제(The Critical-Section Problem)
공통된 자원을 업데이트하는 부분(업데이트하는 코드 부분)을 critical section이라 한다. 위 은행 계좌 문제에서의 critical section은 balance = balance + amount와 balance = balance - amount이다. 다음 세 가지 사항은 이러한 임계 구역에서 발생되는 문제(= 임계구역 문제)를 해결하기 위해 지켜야 될 사항들이다. 아래 세 가지 사항을 모두 준수한다면 위의 은행 계좌 문제같이 동기화가 깨지는 문제는 생기지 않는다. (아래 세 가지 사항을 모두 지켜야 문제를 해결할 수 있음)
Mutual exclusion(상호 배타)
- 임계 구역에는 오직 한 스레드만 진입
- 한 스레드가 critical-section에서 작업 중이면 다른 스레드는 critical-section에 못 들어가도록 하는 것. 즉, 단 하나의 스레드만 critical-section에 존재하도록 하는 것을 뜻한다.
Progress(진행)
- 진입 결정은 유한 시간 내에 이루어져야 함
- 상호 배타를 지키기 위해 여러 스레드 중 하나를 골라서 critical-section의 작업을 수행하게끔 해야 하는데, 누가 들어갈지 고르는 시간은 유한 시간 내에 이루어져야 한다는 것을 뜻함
Bounded wait(유한 대기)
- 어느 스레드라도 유한 시간 내 진입할 수 있어야 함
- 기다리기만 하면 어느 유한 시간 내에 분명히 critical-section에 들어갈 수 있음을 보장하는 것
동기화 도구
동기화 도구로는 semaphores(가장 전통적인 동기화 도구), monitors(자바에서 사용하는 동기화 도구), MISC 등이 있다.
Semaphores
- 동기화 문제를 해결하기 위한 소프트웨어 도구이다.
- 네덜란드의 Edsger Dijkstra가 제안했다.
- 정수형 변수 하나와 두 개의 동작(P, V)로 이루어진 구조이다.
- P는 Proberen으로 영어로 하면 acquire()로 value(정수형 변수)를 -1 하고 value가 0보다 크면 그냥 실행, value가 0보다 작으면 acquire()를 부른 thread 혹은 process를 list에 넣고 누가 깨워줄 때까지 block 하는 함수이다.
- V는 Verhogen으로 영어로 하면 release()로 value를 +1 하고 value list에 있는 하나의 process 혹은 thread를 깨우는 역할이다.
세마포를 사용하여 bankAccount problem에서 Mutual exclusion(value를 1로 설정)를 형성해 보자.
parent와 child는 모두 critical-section에 진입하기 전 acquire()를 호출하고 ciritical-section에 들어간 뒤 작업을 마치고 release()를 호출한다. 우선 parent가 먼저 실행된다고 가정하면 parent는 acquire()를 호출한다. 현재 value가 1이므로 value는 0이 되고 value가 0 미만이 아니므로 block 되지 않는다. 이제 parent는 critical-section으로 들어가 balance = balance + amount 작업을 한다 해당 작업을 하는 도중 공교롭게 context swtiching이 일어난다 하더라도 현재 value는 0이므로 -1을 하면 value는 -1이 되고 따라서 child는 semaphore queue에 잡혀 들어가게 된다. 잡혀 들어간 child thread는 block이 되었으므로 아무것도 하지 못하고 context switching이 발생한다. parent는 다시 하던 작업을 완료하고 critical section에서의 작업이 모두 끝나면 release()를 호출한다. 그러면 -1이었던 value는 0이 되고 queue에 있던 child thread가 풀려난다. 모든 일을 마친 parent thread는 종료되거나 아직 할 일이 더 있다면 해당 작업을 하다가 다시 ready queue로 들어간다. context switching이 발생하면 child thread는 critical section에서의 일을 마치고 release()를 호출하며 0이었던 value를 1로 만들어 놓는다.
맨 처음 출금하는 것은 안 된다. 돈이 없는데 출금할 순 없기 때문이다. 이러한 상황을 방지하기 위해 세마포어를 사용하여 오더링을 해보자.
입금 스레드가 먼저 동작하도록 하기 위해서는 맨 처음 value를 0으로 설정해 놓고 parent thread(p1)와 child thread(p2)의 동작을 아래 그림과 같이 설정해 놓으면 된다.(s1과 s2는 critical-section을 뜻한다.) p1이 먼저 실행되면 문제없이 입금을 할 것이고 만약 p2가 먼저 실행되더라도 critical section에서의 작업을 하기 전에 acquire()를 호출하므로 value값을 -1로 만들고 따라서 semaphore의 queue에 갇히게 된다. 따라서 아래 그림처럼 세마포어를 이용하면 무조건 p1이 먼저 실행되게 된다.
만약 p2를 먼저 실행하고 싶다면 저 둘의 acquire()와 release()를 만대로 두면 된다. 그렇다면 번갈아가며 실행하고 싶을 때는 어떻게 해야 할까?(P-C-P-C-P-C-......) 입출금이 교대로 나오게 하려면 입금 세마포어, 출금 세마포어 총 2개의 세마포어가 필요하다. 우선 두 세마포어의 value는 모두 0으로 만든다. parent thread는 deposit()을 한 뒤 withdraw_semaphore.release()와 deposit_semaphore.acquire()를 하도록 만들고 child thread는 withdraw_semaphore.acquire()를 한 뒤 withdraw()를 하고 deposit_semaphore.release()를 하도록 만든다. 이렇게 하면 무조건 입금 먼저 발생하며 번갈아가며 출금-입금-출금-,......이 반복될 것이다. 아래 그림을 참고하자.
Monitor
- 세마포어 이후의 프로세스 동기화 도구다. 세모포어 보다 고수준 개념이며 자바에서 많이 쓰인다.
- 공유 자원과 공유 자원 접근 함수로 이루어져 있다.
- 공유 자원 접근 함수에는 최대 한 개의 스레드만 집입 가능하며 진입 스레드가 조건 동기로 블록 되면 새 스레드가 진입할 수 있다. 새 스레드는 조건 동기로 블록 된 스레드를 깨울 수 있으며 깨워진 스레드는 현재 스레드가 나가면 재진입할 수 있다.
- 왼쪽 queue는 mutual exclusion이 되도록 하는 queue이다. 어떤 스레드가 위 그림 common variable 밑에 있는 네모에 접근하여 작업하고 있다면 다른 스레드는 네모에 들어가지 못하며 왼쪽 queue에서 대기해야 한다.
- 오른쪽 queue는 조건 동기를 위한 queue이다. (= conditional synchronization) 실행 중인 스레드가 wait()을 만나면 오른쪽 queue로 이동하며 왼쪽 큐에서 대기하고 있던 스레드 하나가 네모에 진입한다. 그러다가 네모에 진입한 스레드가 notify()로 오른쪽 큐에 있는 스레드를 깨우면 깨워진 스레드는 현재 스레드가 나간 뒤 다시 재진입하게 된다.
모니터를 사용하여 bacnkAccount problem을 풀어보자. 세마포어와 비교했을 때 정말 간단하다. 그냥 deposit() 함수와 withdraw() 함수 앞에 synchronized만 붙이면 된다. 다시 말해 synchronized로 critical-section을 감싸기만 하면 끝이다.
이번에는 모니터를 사용하여 입금/출금 순서가 고정되도록 오더링을 해보자. 입금 스레드가 먼저 동작하도록 하기 위해서는 parent thread(p1)와 child thread(p2)의 동작을 아래 그림과 같이 설정해 놓으면 된다.(s1과 s2는 critical-section을 뜻한다.) 세마포어는 오더링을 하기 위해 두 개의 세마포어가 필요했지만 모니터는 한 개로 동일한 기능을 구현할 수 있다.
전통적 동기화 예제
동기화 문제 예제로는 은행 계좌 문제뿐만 아니라 생산자-소비자 문제(유한 버퍼 문제), Readers-Writers problem, Dining Philosopher problem 문제 등 여러 동기화 예제들이 있다. 여러 예제를 보며 동기화에는 어떤 타입이 있나 배우고 여러 동기화 예제들이 발생시키는 동기화 문제를 동기화 도구를 이용하여 해결해 보자.
Producer-Consumer Problem
생산자-소비자 문제이다. 생산자가 데이터를 생산하면 소비자는 그것을 소비하는 형태다. 이러한 형태의 예로는 컴파일러>어셈블러, 파일 서버>클라이언트, 웹서버>웹 클라이언트 등이 있다. 유한 버퍼(Bounded buffer)도 생산자 소비자 문제로 볼 수 있다. 생산자가 데이터를 생산하여 버퍼에 저장하면 소비자는 그것을 소비한다. 현실 시스템에서 버퍼 크기는 유한하기 때문에 생산자는 버퍼가 가득 차면 더 넣을 수 없다는 제약이 있다.
아래 코드를 실행하면 잘못된 결과가 발생한다. 최종적으로 버퍼 내에는 0개의 항목이 있어야 하는데 count != 0 이거나 아예 실행이 되지 않는 컴퓨터도 있다.
위 코드가 count != 0 혹은 실행 불가인 이유는 공통 변수 count, buf[]에 대한 동시 업데이트 때문이다. 다시 말해 임계 구역에 대한 동시 접근 때문이다. 따라서 임계구역에 대한 동시 접근을 방지(상호 배타)하면 문제를 해결할 수 있다. (mutual exclusion을 해결하는 세마포어이므로 mutex라 이름 짓겠음) mutex의 value는 1로 설정한다. producer는 buf의 count를 ++하기 전 mutex.acquire()를 한다. 그리고 buf count ++을 하고 mutex.release()를 한다. consumer도 마찬가지로 buf count --하기 전 mutex.acquire()를 하고 buf count --를 한 뒤 mutex.release()를 한다. 이렇게 하면 critical section에는 하나의 thread만 들어가므로 문제가 해결된다.
** Busy wait
위 유한 버퍼 문제에서 생산자는 버퍼가 가득 차면 기다려야 하고, 소비자는 버퍼가 비어있으면 기다려야 한다. 위 코드에서 기다리는 코드를 while(count == ?)로 했는데 이렇게 작성하면 CPU는 기다리는 동안 다른 일을 하지 못하고 공연히 while문만 계속 반복하게 된다. 이는 성능적인 측면에서 비효율적이며 CPU 자원 낭비다. 이는 empty와 full 이 두 개의 세마포어를 추가로 사용하여 해결할 수 있다. empty 세마포어의 value는 buf_size로 설정하고 full 세마포어의 value는 0으로 설정한다. 생산자는 생산하기 전 empty.acquire()를 호출하여 만약 buf가 꽉 차 있다면 스스로를 block 하도록 한다. 그리고 생산 코드 뒤에는 full.release()를 호출한다. 소비자는 소비하기 전 full.acquire()를 호출하여 만약 buf가 비어있다면 스스로를 block 하도록 한다. 그리고 소비 코드 뒤에는 empty.release()를 호출한다.
Readers-Writers Problem
공통 데이터베이스에서 일어날 수 있는 문제이다. readers는 data를 읽는 사람들, writers는 데이터 읽기, 쓰기를 하는 사람들을 뜻한다. reader와 writer의 입장에서 DB는 공유하는 자원이다. 그렇다고 DB 자체를 상호 배타 지역으로 만들면 한 번에 한 명의 사람만 읽거나 쓸 수 있다. 이는 매우 비효율적이다. 생각해 보면 reader는 몇 명이 들어오든 값을 업데이트하지 않으므로 여러 명이 들어와도 상관없다. 반대로 writer는 값을 변경할 수 있으므로 writer는 한 명만 들어와야 한다. 따라서 효율성 제고를 위해 reader는 몇 명이든 다 들어올 수 있게 하고 writer는 단 한 명만 들어가며 writer가 들어가 있으면 다른 reader와 writer는 못 들어오게 설정하는 것이 좋다.
Dining Philosopher Problem
5명의 철학자가 원탁에 앉아 식사를 한다. 철학자들 사이에는 포크가 하나씩 놓여 있고, 철학자들은 다음의 과정을 통해 식사를 한다.
1. 왼쪽 젓가락을 집는다. 만약 왼쪽 젓가락이 없다면 사용 가능해질 때까지 기다린다.
2. 오른쪽 젓가락을 집는다. 만약 오른쪽 젓가락이 없다면 사용 가능해질 때까지 기다린다.
3. 일정 시간 식사를 한다.
4. 왼쪽 젓가락을 내려놓는다.
5. 오른쪽 젓가락을 내려놓는다.
6. 일정 시간 생각을 한다.
7. 다시 1번으로 돌아간다.
위 상황을 세마포어를 사용하여 코드로 구현하면 아래와 같다. 그런데 아래 코드를 실행하면 어느 정도 시간이 지난 이후부터는 아무 출력도 나오지 않는다. 왜일까?
위 프로그램이 중간에 멈추는 이유는 교착 상태(Deadlock)가 발생하기 때문이다. 예를 들어 어느 순간 모든 철학자들이 동시에 자신의 왼쪽 젓가락을 집는다고 생각해 보자. 이제 모든 철학자들은 자기 오른쪽 젓가락이 사용 가능해질 때까지 기다릴 것이다. 그런데 모든 철학자들이 그러고 있으므로 모든 철학자가 영원히 2번 상태에 머물러있게 된다. 아무리 시간이 지나도 프로그램이 아무 진행도 되지 않는 것이다. 이러한 상황을 교착 상태(Deadlock)라 한다. 식사하는 철학자 문제는 아래에서 배울 환형 대기를 깸으로써 문제를 해결할 수 있다.
** Deadlock
프로세스는 실행을 위해 여러 자원을 필요로 한다. A라는 프로세스는 b라는 자원이 필요한데 b라는 자원은 B라는 프로세스가 가지고 있고 B라는 프로세스는 a라는 자원이 필요한데 a라는 자원은 A가 갖고 있을 때 교착 상태에 빠질 가능성이 있다. 교착 상태의 필요조건은 다음과 같다. (교착 상태가 발생하려면 이 네 가지를 모두 만족해야 한다. 단, 모두 만족하더라도 발생되지 않을 수 있다.)
Mutual exclusion
- 내가 갖고 있으면 다른 사람이 사용 못함
Hold and wait
- 내가 갖고 있는 거 잡고 안 놓음
No preemption
- 남이 갖고 있는 거 못 뺐음
Circular wait
- 환형 대기
- 자원 할당도를 그렸을 때 반복되는 부분이 있다면 환형 대기가 있는 것이다.
** 자원 할당도
'어떤 자원이 어떤 프로세스에게 할당되었는가'와 '어떤 프로세스가 어떤 리소스를 할당받으려고 기다리고 있는가'를 나타낸 그림이다. 자원은 사각형으로, 인스턴스는 점으로, 프로세스는 원으로, 할당은 화살표로 표현한다. 자원 할당도 상에 원이 만들어졌다면 교착 상태의 필요조건 중 하나인 환형 대기가 만족된 것이다. 아래 자원 할당도 1의 경우 원이 만들어지지 않았으므로 Circular wait이 없고 자원 할당도 2의 경우(위의 식사하는 철학자 문제를 그린 것) 원이 만들어지므로 Circular wait이 있는 것이다.
** 교착 상태 처리
교착 상태의 처리 방법은 교착 상태 방지(deadlock prevention), 교착 상태 회피(deadlock avoidance), 교착 상태 검출 및 복구(deadlock detection&recovery), 교착 상태 무시(deadlock ignore) 네 가지가 있다.
교착 상태 방지
- 교착 상태를 일으키는 네 가지 필요조건 중 한 가지라도 불만족시키면 교착 상태는 일어나지 않는 것을 이용한 방법이다. 상호 배타, 보유 및 대기, 비선점, 황형대기 중 하나라도 불만족시키면 된다.
- 상호배타를 막는 방법은 자원을 공유할 수 있게끔 만들면 된다. 단, 이 경우 애초의 목적인 프로세스 동기화를 이루지 못할 수 있으므로 원천적으로 불가능 한 방법일 수 있다.
- 보유 및 대기를 막는 방법은 자원을 가지고 있으면서 다른 자원을 기다리지 않게 만들면 된다. 예를 들어, 원하는 여러 자원 중 단 하나의 자원이라도 할당받지 못하는 상황이라면 아무것도 할당받지 않고 모든 자원을 할당받을 수 있을 때까지 기다리는 것이다. 자원을 할당받는 도중 다른 자원이 할당받지 못하게 된다면 할당받은 자원을 모두 놓는다. 이 경우 자원의 활용률 저하와 기아를 발생시킬 수 있다.
- 비선점을 막는 방법은 자원을 선점 가능하게 만들면 된다. 다만 프린터를 생각해 볼 때 어느 화면을 출력 중인 프린터를 중간에 다른 것을 출력하게 할 수는 없다. 왜냐하면 내용이 섞이기 때문이다. 따라서 대부분의 경우 원천적으로 불가능할 것이다.
- 환형 대기를 막는 방법은 여러 방법이 있는데 하나의 예로, 자원에 번호를 부여하고 번호 오름차순으로 자원을 요청하면 환형 대기를 막을 수 있다. 식사하는 철학자 문제에서의 교착 상태는 이 방법을 통해 쉽게 해결할 수 있다. 젓가락에 번호를 붙이고 좌-우 젓가락 중 번호가 낮은 젓가락부터 집으면 환형이 끊어진다.
교착 상태 회피
- 자원 요청에 대한 잘못된 승인을 방지하여 교착 상태를 회피하는 방법이다.
- 예를 들어, 12개의 magnetic type 및 3개의 process가 있다고 생각해 보자. 각각의 프로세스는 아래와 같은 특징을 지니고 있다.
- 이러한 상황에서 순서대로 할당을 한다면 다음과 같다. p0, p1, p2 순서대로 5개 2개 2개를 할당한다. (12 - (5+2+2) = 3) 그다음 다시 p0의 차례인데 현재 마그네틱 테이프는 3개인데 요구량은 5개이므로 줄 수 없고, 다음으로 넘어간다. p1의 요구량은 2개이므로 2개를 할당하면 p1의 전체 요구량인 4를 만족시킨다. 따라서 p1은 4개를 다시 반납한다. (3 - 2 + 4 = 5) 이제 p0에게 5개를 할당하면 p0의 전체 요구량인 10을 만족시킨다. 따라서 p0는 10개를 다시 반납한다. (5 - 5 + 10 = 10) 마지막으로 p2에서 2개씩 계속 주고 9개를 반납받아 12개의 마그네틱 테이프를 전부 회수한다. 정상적으로 할당하고 모두 회수한 이러한 상황을 안전한 할당(safe allocation)이라 한다.
- 안전한 할당을 보았으니 불안전 안 할당(unsafe allocation)의 예시도 확인해 보자.
- 12개의 magnetic type 및 3개의 process가 있다. 각각의 프로세스는 아래와 같은 특징을 지닌다.
- 이러한 상황에서 순서대로 할당을 한다면 다음과 같다. p0, p1, p2 순서대로 5개 2개 3개를 할당한다. (12 - (5+2+3) = 2) 그다음 다시 p0의 차례인데 현재 마그네틱 테이프는 3개인데 요구량은 5개이므로 줄 수 없고, 다음으로 넘어간다. p1의 요구량은 2개이므로 2개를 할당하면 p1의 전체 요구량인 4를 만족시킨다. 따라서 p1은 4개를 다시 반납한다. (2 - 2 + 4 = 4) 이제 시스템이 갖고 있는 전체 마그네틱 테이프는 총 4개가 되었는데 p0의 최소 요구량은 채울 수 없고 p2에게 주어도 p2의 최대 요구량을 채울 수 없다. 따라서 p0, p2 누구의 needs도 만족시킬 수 없게 된다. 이러한 할당 방식을 불안전 할당이라 하며, p0, p2는 더 이상 프로세스가 진행될 수 없고 데드락에 빠지게 된다.
- 이러한 상황을 회피하는 알고리즘은 Banker's Algorithm이 있다.
교착 상태 검출 및 복구
- 앞의 두 방법과 달리 deadlock이 일어나는 것을 허용한다. 주기적으로 검사를 하여 교착 상태 발생 시 복구를 실행하는 방법이다. 검출 시 검사에 따른 추가 부담(overhead)이 있다. (계산도 해야 하고 정보도 좀 추가로 저장할 테니 메모리도 좀 더 먹고...) 복구는 process 일부를 강제 종료하거나 자원을 선점하여 일부 프로세스에게 할당하는 등의 방법을 통해 이루어진다.
교착 상태 무시
- 교착 상태는 교착 상태의 네 가지 필요조건을 만족해야 하며 만족한다고 할지라도 반드시 교착 상태가 일어나는 것은 아니므로 교착 상태가 실제로는 자주 일어나는 것이 아니다. 따라서 그냥 무시한다. (사용자가 PC를 재시동하던지 하세요 하는 방법)
정리와 복습(운영 체제 기본 1부터 9까지 총 정리)
프로세스의 동기화는 무엇을 의미하는가.
- 여러 프로세스가 공유하는 자원의 일관성을 유지하는 것. 임계 구역에서의 mutual exclusion을 통해 이룰 수 있으며, 오더링을 통해 순서까지 통제할 수 있다.
mutual exclusion을 하면 프로세스의 동기화를 이룰 수 있는데 운 나쁘게 데드락이 걸릴 수 있다. 데드락은 교착 상태 방지(deadlock prevention), 교착 상태 회피(deadlock avoidance), 교착 상태 검출 및 복구(deadlock detection&recovery), 교착 상태 무시(deadlock ignore) 네 가지 처리 방법이 있다. 교착 상태 방지는 데드락의 네 가지 필요조건 중 하나를 불만족시켜 데드락을 없애는 방법이고 교착 상태 회피는 애초에 할당해줄 때 잘 할당하는 방법이고 교착 상태 검출 및 복구는 데드락을 사전에 방지하지 않고 데드락이 일어났을 때 복구를 하는 방법이며 교착 상태 무시는 그냥 아무 조치 안 하는 것이다.
시스템 콜이란 무엇인가. 시스템 콜과 소프트웨어 인터럽트는 어떤 관령성이 있는가.
- 시스템 콜은 OS의 커널이 제공하는 서비스에 대해 응용프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다. 보통 응용프로그램은 직접 커널에 접근할 순 없고 시스템 콜을 통해 간접 소통한다. 이러한 시스템 콜을 소프트웨어 인터럽트로 만든다.
- 사용자 모드에 있는 응용프로그램이 커널의 기능을 사용할 수 있도록 한다. 다시 말해, OS 서비스에 접근할 수 있게 해 준다. 시스템 호출을 하면 사용자 모드에서 커널 모드로 바뀌며 커널에서 시스템 호출 작업이 끝나면 다시 사용자 모드로 돌아가는 방식이다. 이러한 시스템 콜의 진행 과정은 아래와 같다.
1. 프로그램의 명령어 실행 중 인터럽트가 발생하면 먼저 인터럽트 가능 플래그(IE)와 인터럽트 요청 신호를 비교하여 실행 가능한 인터럽트인지 확인한다. 인터럽트의 종류에 따라 항상 응답을 해야 하는 중요한 인터럽트도 존재한다.
2. 실행 가능한 인터럽트가 아니라면 그에 따른 응답을 내고, 가능한 인터럽트라면 인터럽트 요청을 제어장치로 전달한다.
3. 제어장치는 현재 실행 중인 프로그램을 중단하고 프로그램의 위치, register 상태 등을 스택에 저장해 둔다.
4. 프로그램이 동작을 멈추면 커널 모드로 진입하고 인터럽트 디스크립터 테이블에서 알맞은 ISR 주소를 찾는다. 인터럽트 디스크립터 테이블이란 운영 체제에 따라 인터럽트가 발생하였을 때 실행시켜야 할 ISR들의 주소를 모아둔 테이블이다.
5. ISR을 실행시켜 인터럽트를 처리한다. ISR이란 인터럽트를 처리하기 위해 실행되어야 할 명령어들의 모임이다.
6. 처리가 끝나면 ISR의 반환 값과 함께 스택에 저장했던 프로그램 상태를 복구하고 프로그램을 다시 실행한다.
- 시스템 콜의 예시: exit(), fork(), open(), close(), read(), write(), ...
프로세스는 또 다른 프로세스로 만들어지는데 자신을 만든 프로세스를 부모 프로세스라고 한다.
Command interface는 명령어 해석기로, 주로 사용자한테 어떤 명령을 받아 그 명령을 번역한 뒤 실행해 주는 프로그램이다. 보통 운영체제에선 shell이라 한다.
Job scheduling이란 job queue에 있는 여러 프로세스 중 어느 것을 먼저 넘길 것인가를 정하는 일이다.
critical section이란 공통된 자원을 업데이트하는 구간을 지칭하는 단어이다.
'운영체제' 카테고리의 다른 글
운영체제 기본 마지막 - 파일 시스템 (0) | 2023.01.26 |
---|---|
운영체제 기본 10 - 주기억장치 관리 (0) | 2023.01.26 |
운영체제 기본 8 - 프로세스 생성과 종료 (0) | 2023.01.23 |
운영체제 기본 6 - 프로세스 관리 (0) | 2023.01.19 |
운영체제 기본 5 - 운영체제 서비스 (0) | 2023.01.19 |