다음주 면접에 대비해 면접 예상 질문을 적어보려고 한다.
운영체제란 커널과 시스템 프로그램으로 이루어져 있어서 커널이 하드웨어 자원을 관리해주고 시스템 프로그램이 제공하는 인터페이스를 통해 사용자가 커널에 하드웨어 자원 요청을 하여 활용할 수 있는 시스템 소프트웨어입니다.
먼저 CPU가 있습니다. 어떤 프로세스가 CPU를 얼마나 할당받을지를 결정합니다.
그리고 메모리가 있습니다. 메모리를 어떻게 분할해서 프로세스에 어떻게 할당할지를 결정합니다.
그리고 입출력장치 자원과 파일 자원을 관리합니다.
먼저 프로세스를 설명해야합니다. 프로세스는 CPU를 할당받아야 작업을 실행할 수 있습니다. 많은 프로세스 중 누구에게 CPU를 줄지를 운영체제가 결정합니다.
대표적인 CPU 스케쥴링 기법에는 FCFS, SJF, RR 등이 있습니다. 각 알고리즘이 계산하는 방법에 따라서 운영체제가 어떤 프로세스가 CPU를 할당받을지를 결정합니다.
먼저 선점형 방식이 무엇이냐면 어떤 프로세스가 CPU를 받아서 수행되고 있는데 CPU를 뺏을 수 있다면 선점형 방식이고, CPU를 뺏을 수 없다면 비선점형 방식입니다.
비선점형 방식은 짧은 작업을 수행하는 프로세스가 긴 작업이 종료될 때까지 기다려야 하는 상황이 생길 수 있습니다. 따라서 처리율이 떨어질 수 있지만 대신 컨텍스트 스위칭 오버헤드가 적고 응답 시간을 예측하기가 쉽습니다. 따라서 일괄 처리 시스템에 적합합니다.
선점형 방식은 우선순위가 높은 프로세스가 들어오면 해당 프로세스를 먼저 수행하도록 선점할 수 있어서 높은 우선순위의 프로세스들이 긴급한 경우에 유용합니다. 또한 첫 응답을 하기까지의 시간이 상대적으로 짧아 응답 시간이 빨라야 하는 경우에 적합합니다. 따라서 실시간 시스템이나 대화형 시분할 시스템에 적합합니다.
비선점형 방식은 처리 시간을 예측하기 쉬워 정확한 시간에 작업을 완료해야 하는 배치 처리 시스템에 유용합니다.
운영체제 중 항상 메모리에 올라가 있는 운영체제의 핵심 부분으로써 하드웨어와 응용 프로그램 사이에서 인터페이스를 제공하는 역할을 하며 컴퓨터 자원들을 관리하는 역할을 합니다.
다만 이러한 커널은 항상 컴퓨터 자원들을 바라만 보고 있기에 사용자와의 상호작용은 지원하지 않습니다. 따라서 사용자와의 직접적인 상호작용을 위해 프로그램을 제공하게 되는데, 대표적으로 쉘(Shell)이라는 명령어 해석기등이 있다.
커널은 운영체제의 핵심 구성요소이고, 이러한 커널에 사용자 애플리케이션과 유틸리티가 추가되면 운영체제가 된다. 즉, 커널 + 애플리케이션, 유틸리티 = 운영체제 이다. 그러므로 운영체제는 커널 공간과 사용자 공간으로 나뉠 수 있다.
커널은 다양한 자원을 관리하는데 이는 사용자가 물리적인 하드웨어에 접근하고 사용할 수 있도록 매개하기 위해서이다. 즉, 인터페이스로써 사용자가 컴퓨터만의 언어와 규칙으로 하드웨어와 통신할 수 있도록 도와주는 역할을 한다.
인터럽트의 정의. 종류. 역할 왜 필요한지
인터럽트란 예상치 못한 상황이 작업 중 발생했을 때 진행하던 작업을 잠시 중단하고 인터럽트를 처리한 후 다시 작업으로 돌아오는 것을 말합니다.
인터럽트의 종류에는 하드웨어 인터럽트, 소프트웨어 인터럽트가 있는데 하드웨어 인터럽트는 전원 중단이나 마우스를 움직이고 프린트 출력 등의 I/O 장치 요청, 그리고 타이머에 의한 인터럽트 요청이 있습니다. 소프트웨어 인터럽트는 StackOverFlow 같은 exception 발생으로 인한 인터럽트와 시스템 콜 호출로 인한 인터럽트가 있습니다.
인터럽트를 통해 선점형 스케쥴링 방식을 구현할 수 있고, I/O 디바이스와 커뮤니케이션, 에러 처리 등을 수행할 수있습니다.
인터럽트가 발생하면 먼저 진행중이던 작업을 PCB와 PC에 저장하고, 인터럽트 번호에 따라 인터럽트벡터에서 인터럽트 서비스 루틴 주소를 찾습니다. 그리고 찾은 인터럽트 서비스 루틴을 실행하여 처리한 후 PC와 PCB를 통해 다시 진행중이던 작업으로 돌아옵니다.
인터럽트벡터란 인터럽트 번호와 해당 인터럽트가 실행해야하는 인터럽트 서비스 루틴 코드가 저장된 곳의 시작위치를 저장해놓은 테이블 구조입니다.
프로그램, 프로세스의 개념. PCB.
프로세스란 컴퓨터에서 작업을 실행하는 단위입니다. 프로그램(디스크에 저장되어있는 실행가능한 파일이자 명령어와 정적 데이터의 집합임)이 메모리에 올라가 실행 상태가 되면 프로세스라고 합니다.
프로그램이 메모리에 올라가면 메모리 공간을 할당받는데 이 공간은 코드 데이터 힙 스택 레지스터 등의 구조로 이루어져 있습니다.
또한 컨텍스트 스위칭을 위해 PCB라는 자료구조를 통해 프로세스 정보를 저장하고 있습니다.
컨텍스트 스위칭은 프로세스를 진행 중이던 걸 잠시 내려놓고 다른 프로세스를 실행하는 것을 말합니다. 이 때 이전 프로세스는 종료 상태가 되는 것이 아니라 대기 상태로 접어들고 이후에 다시 CPU를 할당받으면 이어서 실행할 수 있습니다. 이때 어디까지 프로세스가 작업을 진행했는지 프로세스에 관한 정보를 보관하기 위해 PCB라는 자료구조에 프로세스 정보를 저장해놓습니다.
PCB는 프로세스 번호, 프로세스 상태, 프로그램 카운터, 메모리 관리 정보, 우선순위 정보, 스택 포인터 등을 가지고 있습니다. 컨텍스트 스위칭이 발생하면 기존에 진행하던 프로세스의 정보를 해당 프로세스의 PCB에 저장하고 CPU를 회수합니다. 그리고 새로 진행할 프로세스의 PCB에서 정보를 가져와서 작업을 수행하게 됩니다.
컨텍스트 스위칭이 너무 많이 일어나게 되면 실제 작업을 수행하는 시간보다 이러한 교환을 하는 시간이 더 많이 소요되어 오버헤드가 매우 커지고 성능이 떨어질 수 있습니다. 따라서 라운드로빈과 같은 스케쥴링에서 time slice를 정할 때 컨텍스트 스위칭으로 인한 오버헤드를 잘 고려해야합니다.
스레드는 프로세스에서 파생되어 나온 작업 흐름입니다.
스레드도 메모리 공간을 할당받게 되는데 이 공간은 프로세스 메모리 공간 안에 존재하며, 힙 코드 데이터 영역은 프로세스의 공간을 공유하고, 스택 레지스터 등의 영역은 스레드마다 별개로 할당받게 됩니다.
스레드도 컨텍스트 스위칭이 일어날 수 있는데 스레드는 프로세스와 달리 스택 레지스터 등 별개의 영역에 대해서만 정보를 가지고 있다가 교환해주면 됩니다. 따라서 컨텍스트 스위칭을 위해 가지고 있는 스레드 정보가 훨씬 적고 이를 TCB라는 구조 안에 저장합니다. TCB에는 스레드 아이디, 스레드 상태, Register 정보, PC 정보 등을 가지고 있습니다.
유저 레벨 스레드와 커널 레벨 스레드가 있는데 커널에서 관리하며 스케쥴링하는 스레드는 커널 레벨 스레드고, 유저 레벨 스레드는 유저 레벨에서 구현한 스레드이다. 차이점은 유저 레벨 스레드는 커널에서 보기에는 process와 스레드 비율이 1:1로 보이는데 프로세스가 cpu를 할당받으면 내부적으로 유저 레벨에서 구현된 스케쥴링을 통해 유저레벨 스레드에 cpu를 할당해준다.
유저 레벨 스레드는 시스템 콜이 호출되면 프로세스 자체가 블록되는 반면 커널 레벨 스레드는 시스템 콜이 호출되면 해당 스레드만 블록된다. 단 유저 레벨 스레드는 운영체제에 따른 의존성이 적어 이식성이 좋다.
스레드를 하나만 쓰면 싱글 스레드고, 스레드를 여러개 쓰면 멀티 스레드이다.
멀티 스레드의 장점은 여러 작업을 동시에 진행할 수 있다는 것이다. 예를 들어 웹 브라우저에서 이미지를 다운로드받으면서 동시에 사용자가 웹과 상호작용을 할 수 있다. 단, 멀티스레드의 경우 컨텍스트 스위칭이 발생하게 되는데 싱글 코어에서 멀티스레드를 쓰는 경우 컨텍스트 스위칭으로 인한 오버헤드가 커서 성능이 싱글 스레드보다 낮을 수 있다.
그리고 싱글 스레드는 공용 자원에 대한 동시성 걱정이 없는데 멀티 스레드는 공용 자원에 동시에 접근하는 경우 문제가 발생할 수 있으므로 동기화에 대한 비용도 소모된다.
동기화란 프로세스나 스레드가 여러개 존재하는 상황에서 어떤 하나의 자원에 여러 개의 프로세스가 동시에 접근하려고 할 때 일관성이 깨질 수 있어 한번에 하나의 프로세스만 접근하게 하는 것을 말합니다.
이때 여러 프로세스가 접근하면서 접근순서에 따라 결과가 달라질 수있는 상황을 경쟁 조건이라고 하며 이와 관련된 예시 상황에는 생산자-소비자 문제, 은행 계좌 문제가 있습니다.
생산자 소비자 문제는 생산자와 소비자가 있을 때, 생산자는 어떤 요청을 생성해 버퍼나 큐에 집어넣고, 소비자는 이 큐에서 요청을 꺼내 소비하는 역할을 합니다. 이때 생산자와 소비자가 동일한 양만큼을 생성하고 소비한다면 최종적으로 남은 양은 0이 되어야 합니다.
하지만 동기화가 되어있지 않은 경우 공유자원에 동시에 접근하여 값을 업데이트하려고 하여 업데이트가 정상적으로 이루어지지 않을 수도 있습니다.
이 경우 어떻게 해결을 하냐면 먼저 한번에 공유 자원인 버퍼에 동시에 접근할 수 없도록 생산과 소비하는 로직에 상호 배제 세마포어를 겁니다.
그리고 만약 버퍼가 아무것도 없을 때는 소비자가 cpu가 대기하지 않고, 버퍼가 꽉찼을 때는 생산자가 cpu를 점유해서 대기하지 않도록 생산자용 세마포어와 소비자용 세마포어를 만듭니다.
생산 로직에서는 처음에 생산 세마포어를 겁니다. 그러면 생산 세마포어의 값이 -1이 됩니다. 이때 생산자가 버퍼 사이즈를 넘는 양을 생산하면 안되므로 생산 세마포어의 초기값을 버퍼 사이즈로 줍니다. 그럼 한번에 버퍼 사이즈를 넘는 생산을 할 수없게 됩니다. 그리고 생산이 완료되면 소비자용 세마포어를 +1해줍니다. 소비자용 세마포어는 버퍼가 0일 때 소비 로직이 돌면 안되기 때문에 0으로 시작을 합니다. 그리고 생산이 완료되면 소비 세마포어가 1이상이 되기 때문에 이 때 소비 로직이 세마포어를 가져와 돌 수 있게 됩니다. 소비 로직이 완료되면 생산 세마포어를 +1해줍니다. 그럼 다시 생산 로직이 그만큼 돌 수 있게 됩니다.
/* 상호배제 */
sem_init(&m, 0, 1); // critical section 에는 한 스레드만 들어갈 수 있다.
/* 조건 동기화 조건변수 */
sem_init(&empty, 0, MAX); // 빈공간(생산할 수 있는 버퍼 남은 크기)이 MAX 이다.
sem_init(&fill, 0, 0); // (소비할 수 있는 자원, 생산된 자원)는 처음에 아무것도 없다.
//[Producer]
void producer() {
while (1) {
sem_wait(&empty);
sem_wait(&m); // 상호배제, 공유자원인 버퍼 guard
put(value);
sem_post(&m); // 상호배제, 공유자원인 버퍼 guard
sem_post(& fill);
}
}
//[Consumer]
void consumer() {
while (1) {
sem_wait(& fill);
sem_wait(&m); // 상호배제, 공유자원인 버퍼 guard
get();
sem_post(&m); // 상호배제, 공유자원인 버퍼 guard
sem_post(& empty);
}
}
동시에 접근하면 동시성 문제가 발생하는 코드 영역을 임계 구역이라고 하는데, 이 임계 구역에 한번의 하나의 프로세스만 접근하게 하면 됩니다.
가장 쉬운 방법은 락을 거는 것입니다. 락에는 세마포어와 뮤텍스 등이 있습니다.
만약 두개 이상의 공유 자원이 있고 서로 다른 프로세스가 각각 자원을 하나씩 점유한 상태에서 다른 나머지 공유자원을 선점하기 위해 대기하는 경우 락이 풀리지 않아 무한히 대기하는 데드락에 빠질 수 있습니다.
이를 해결하기 위해 피터슨 알고리즘이 있습니다.
멀티 프로세스는 프로세스가 각각 독립적이기 때문에 하나가 에러가 발생해도 다른 프로세스는 잘 동작한다는 장점이 있습니다. 따라서 안정적으로 프로그램이 돌아가야하는 경우 유용합니다. 크롬이나 웨일 등의 웹 브라우저에서 하나의 탭에 문제가 생겨도 다른 탭은 정상적으로 작동하도록 멀티 프로세스로 탭과 창을 구현합니다.
대신 멀티 프로세스는 컨텍스트 스위칭이 일어나는 경우 멀티 스레드에 비해 오버헤드가 크다는 단점이 있습니다.
멀티 스레드는 멀티 프로세스보다 컨텍스트 스위칭이 가벼워 일반적인 경우 멀티 프로세스보다 성능이 유리합니다. 또한, 스레드간 공유할 수 있는 메모리 영역이 있어, 스레드 간에 결합이 강한 경우 멀티 프로세스보다 낫습니다. 멀티 프로세스의 경우 프로세스간 데이터 공유를 위해서는 쉐어드 메모리나 메모리 패싱 등의 IPC 기법을 사용해야 하는데 이것은 스레드간 통신보다 무겁습니다.
단, 하나의 스레드에 문제가 생기면 다른 스레드에도 문제가 생길 가능성이 높습니다.
둘 이상의 프로세스가 다른 프로세스가 점유하고 있는 자원을 서로 기다릴 때 무한 대기에 빠지는 상황입니다.
상호 배제, 점유 대기, 비선점, 순환대기
상호 배제는 하나의 자원에 한번에 하나의 프로세스만 선점할 수 있다는 것입니다.
점유 대기는 프로세스들이 이미 점유한 자원이 있으면서 동시에 다른 자원을 점유하기 위해 대기하는 것을 말합니다.
비선점은 프로세스들이 이미 점유하고 있는 자원을 뺏을 수 없다는 것을 말합니다.
순환대기는 프로세스들간의 점유하고 있는 자원과 점유하려고 대기하고 있는 자원이 순환 구조를 이루는 것을 말합니다. 쉽게 말해 A 프로세스가 어떤 자원을 점유하고 있고 B 프로세스가 점유하고 있는 자원을 점유하려고 대기하고 있고, B 프로세스는 C 프로세스가 점유하고 있는 자원을 점유하려고 대기하고 있을 때 이게 돌고 돌아 최종적으로 마지막 프로세스가 다시 A 프로세스가 점유하고 있는 자원을 점유하려고 대기하고 있는 것을 말합니다.
먼저 데드락을 에방하는 방법에는 데드락이 발생하기 위한 네가지 조건중 아무거나 하나가 충족되지 않도록 하는 방법이 있습니다.
비선점이 충족되지 않도록 우선순위가 높은 프로세스가 이미 점유된 자원을 뺏을 수 있게 할 수 있습니다. 또는, 순환 대기가 충족되지 않도록 어떤 자원을 점유할 때 정해진 한 방향 순서대로만 점유하도록 강제하는 것이 있습니다.
단 이러한 방법들은 서비스의 처리량이나 효율성을 떨어뜨릴 수 있습니다.
그래서 다른 방법으로 데드락을 회피할 수 있는 방법이 있는데 여기에는 피터슨 알고리즘과 뱅커스 알고리즘이 있습니다.
피터슨 알고리즘은 두 개의 프로세스가 존재하는 상황에서 흔히 쓰이는데 turn 이라는 전역 변수를 두는 것입니다. 프로세스가 i번째 자원을 점유하고 j 번째 자원을 점유하기 위해 대기하는 경우 turn을 j로 업데이트합니다. 그리고 j번째 자원이 점유되지 않았거나 turn이 j가 아닌 경우 임계 영역에 진입합니다.
만약 j번째 자원이 점유되지 않았다면 바로 임계 영역에 진입할 것입니다. 하지만 만약 다른 프로세스가 거의 동시적으로 j번째 자원을 먼저 점유했고 i번째 자원을점유하려고 한다고 가정해봅시다. 이때는 turn이 다시 i로 업데이트됩니다. 따라서 첫번째 프로세스가 turn이 j가 아니게되었으므로 먼저 임계영역에 진입하게 됩니다. 그 후 i번째 자원을 풀어주고 두번째 프로세스도 i번째 자원의 점유가 풀렸으므로 임계영역에 들어섭니다. 간단하게 설명하면 두 개의 프로세스가 동시에 임계영역에 진입하려 한다면 turn 변수가 늦게 수행 된 프로세스가 기회를 양보하는 것으로 데드락을 회피합니다.
뱅커스 알고리즘은 https://charles098.tistory.com/100 참고
https://techblog-history-younghunjo1.tistory.com/511 참고