0813 운영체제
https://github.com/VSFe/Tech-Interview
면접 대비 질문 정리 깃허브를 참고해서 CS를 대비해 보고 있다.
스터디원 4명과 함께 질문들을 각자 정리하고, 파트를 맡아 발표를 진행한다.
이번 주제는 운영체제. 범위는 질문 #1 ~ #15 까지이다
우선 #1 ~ #8 까지 정리한 내용이다.
시스템 콜 (=시스템 호출(System Call)) 은 유저 레벨 프로그램이 운영체제 커널에게 서비스나 기능을 요청할 수 있도록 하는 개념입니다.
운영 체제는 시스템 호출을 통한 인터페이스를 제공해서 사용자 프로그램이 하드웨어나 다른 시스템 리소스와 상호 작용할 수 있게 해줍니다. 시스템 호출은 애플리케이션이 low-level 작업(메모리 할당이나 프로세스 관리) 등을 수행하거나 권한이 필요한 작업을 수행하는 것을 가능하게 하는 중요한 역할을 합니다.
더 자세한 특징)
사용자 영역은 사용자 수준 응용 프로그램이 실행되는 곳이며, 커널 영역은 운영 체제 자체가 실행되는 보호된 공간입니다.
사용자 프로그램은 일반적으로 하드웨어나 커널 데이터 구조에 직접적인 접근 권한이 없기 때문에, 시스템 호출은 사용자 프로그램이 커널이 제공하는 서비스를 요청할 수 있는 통제된 방법을 제공합니다.
시스템 콜이 담당하는 작업들
어떻게 시스템 호출을 실행하는가
특별한 명령(소프트웨어 Interrupt, trap, exception)을 실행하여 사용자 영역에서 커널 영역으로 제어를 전달합니다.
운영 체제의 Interrupt handler 또는 exception handler가 요청된 시스템 호출을 식별하고 필요한 작업을 수행한 후 시스템 호출이 완료되면 제어를 사용자 프로그램에 다시 반환합니다.
고유한 시스템 콜 번호로 식별됨
사용자 프로그램은 이 번호를 사용하여 원하는 시스템 호출을 지정할 수 있고, 운영 체제는 이 번호를 사용하여 요청을 적절한 시스템 호출 핸들러로 라우팅할 수 있습니다.
매개변수로 추가 정보 전달함
추가 정보(파일 작업이나 메모리 할당을 위한 매개변수) 등이 필요할 때는 일반적으로 시스템 호출에 대한 argument 로 전달할 수 있습니다.
권한과 보호:
시스템 호출은 권한이 필요한 작업에 대해 접근을 가능하게 합니다.
시스템 콜의 종류
open()
: 파일 열기read()
: 파일에서 데이터 읽기write()
: 파일에 데이터 쓰기fork()
: 새로운 프로세스 생성exec()
: 현재 프로세스에서 새 프로그램 실행exit()
: 현재 프로세스 종료시스템 콜이, 운영체제에서 어떤 과정으로 실행되는지 설명해 주세요.
- 사용자 모드:
사용자 수준 프로그램이 권한 있는 작업을 수행하거나 시스템 리소스에 액세스해야 할 때 시스템 호출을 실행합니다. 예를 들어, 프로그램이 파일에서 데이터를 읽고 싶으면read()
시스템 호출을 호출합니다.- 시스템 호출:
사용자 수준 프로그램은read()
와 같은 함수 호출을 코드 내에서 실행하여 시스템 호출을 호출합니다. 이 함수 호출은 사용되는 프로그래밍 언어와 관련된 라이브러리 내의 일반적인 함수로 구현됩니다.- Trap 명령어:
시스템 호출의 라이브러리 함수 내에는 실행되는 특정 명령어(일반적으로 "트랩" 또는 "소프트웨어 인터럽트" 명령어라고 함)가 있습니다. 이 명령어는 사용자 모드에서 커널 모드로 제어를 전환시키는 역할을 합니다.- 모드 전환:
트랩 명령어는 하드웨어에서 생성된 인터럽트를 트리거하여 CPU를 사용자 모드에서 커널 모드로 전환시킵니다. 이는 종종 "컨텍스트 전환"이라고 하는 메커니즘을 통해 수행되며, CPU는 사용자 수준 프로그램의 현재 상태를 저장하고 커널 컨텍스트로 전환합니다.- 커널 모드:
이제 커널 모드에서 CPU는 운영 체제 커널의 코드를 실행하기 시작합니다. 커널 코드는 요청된 시스템 호출의 기능을 구현합니다.- 시스템 호출 식별:
커널이 커널 모드로 진입한 후 처음으로 하는 일은 요청된 특정 시스템 호출을 식별하는 것입니다. 이는 일반적으로 시스템 호출을 호출할 때 사용자 수준 프로그램에서 전달한 시스템 호출 번호를 사용하여 수행됩니다.- 시스템 호출 처리:
시스템 호출이 식별되면 커널은 해당 시스템 호출에 대한 코드를 실행합니다. 이 코드는 파일에서 읽기, 메모리 할당 또는 기타 요청된 작업과 같은 필요한 동작을 수행합니다.- 인수 전달:
사용자 수준 프로그램이 시스템 호출 함수에 인수를 전달했습니다(예:read()
의 파일 디스크립터, 메모리 할당을 위한 메모리 크기 등). 이러한 인수는 커널 모드에서도 접근할 수 있어야 합니다. 대부분의 시스템에서는 인수를 사용자 공간에서 커널 공간으로 복사하여 데이터의 안전성과 보안을 보장합니다.- 시스템 호출 실행:
커널은 시스템 호출과 관련된 코드를 실행하여 사용자 프로그램을 대신하여 요청된 작업을 수행합니다.- 결과 반환:
시스템 호출 작업이 완료되면 커널은 결과를 준비합니다. 이는 데이터를 읽은 내용이나 작업의 상태와 같은 결과입니다. 이러한 결과는 커널 공간에서 사용자 공간으로 복사됩니다.- 사용자 모드로 복귀:
CPU는 사용자 모드로 다시 전환되며, 사용자 수준 프로그램의 상태가 복원됩니다. 시스템 호출의 결과가 프로그램에 제공되며, 프로그램은 아무런 변화가 없었던 것처럼 실행을 계속합니다.
왜 유저모드와 커널모드를 구분해야 하나요?
- 보안 및 보호:
사용자 모드와 커널 모드를 명확히 구분함으로써 운영체제는 접근 제어를 강화할 수 있습니다.
사용자 프로그램을 사용자 모드에서 실행되도록 해서 하드웨어 리소스와 시스템 내부에 제한된 액세스 권한을 가지게 하면, 악의적인 해킹이나 오류가 있는 사용자 프로그램이 중요한 시스템 기능에 직접 개입하거나 민감한 데이터를 조작하는 것을 방지할 수 있습니다.
커널은 하드웨어 장치, 메모리 및 기타 리소스에 대한 액세스를 제어하기 때문에 사용자 프로그램은 이러한 리소스에 액세스하기 위해 시스템 호출을 통해 서비스를 요청해야 하므로 리소스를 공정하게 할당하고 관리할 수 있습니다.
커널은 사용자 수준 응용 프로그램과 독립적으로 개발하고 업데이트할 수 있습니다. 이 모듈성을 통해 사용자 프로그램에 영향을 미치지 않고 운영 체제를 개선하거나 수정하기가 더 쉬워집니다.
서로 다른 시스템 콜을 어떻게 구분할 수 있을까요?
System call의 고유한 식별자로 구분합니다.
유저 프로그램이 system call을 날리면, 커널에게 이 식별자를 제공하고 커널은 이 식별자를 이용해서 어떤 작업을 요청했는지 알 수 있습니다.
인터럽트는 프로그램의 정상 실행을 일시중단하고 특수한 루틴인 'Interrupt Handler' 또는 'Interrupt Service Routine(ISR)' 로 제어를 전달하는 메커니즘입니다.
인터럽트는 하드웨어 이벤트, 시스템 이벤트 또는 외부의 비동기 신호와 같이 즉각적인 주의가 필요한 이벤트를 처리하는 데 필수적입니다.
인터럽트를 통해서 다중 작업 환경에서 반응성을 보장하고 작업을 효율적으로 관리할 수 있습니다.
인터럽트의 작동 방식
- 트리거 이벤트:
키보드, 마우스, 소프트웨어 생성 신호 같은 다양한 소스에서 이벤트가 트리거 되면,- 인터럽트 요청 (IRQ: Interrupt Request)
트리거 이벤트가 발생하면 인터럽트 요청 (IRQ)이 생성됩니다. 하드웨어는 이 요청을 감지하고 CPU에게 인터럽트 처리가 필요하다고 알립니다.- 현재 실행을 일시 중지:
CPU는 현재 프로그램 또는 작업의 실행을 일시적으로 중지하여 인터럽트를 처리합니다. 중지된 프로그램을중단된 컨텍스트
라고 합니다.- 컨텍스트를 전환:
CPU는 현재 프로그램의 상태 (프로그램 카운터, 레지스터 등)를 메모리에 저장하는 컨텍스트 전환을 수행합니다. 이를 통해 CPU는 인터럽트가 서비스되고 나서도 인터럽트가 발생한 프로그램의 상태로 돌아갈 수 있습니다.- 인터럽트 핸들러 실행:
그런 다음 CPU는 해당 인터럽트와 관련된 Handler Routine 으로 제어를 전환합니다.
Interrupt Handler는 특정 유형의 인터럽트를 처리하는 데 필요한 방식을 알고 있는 미리 정의된 코드 부분입니다.
Polling 방식?
조건이 변경되었는지 또는 추가 작업을 위해 리소스가 준비되었는지 여부를 반복적으로 쿼리하거나 확인하는 것을 의미합니다.
Polling 방식을 인터럽트 체크에 사용하게 된다면, 특정 시간 간격에 인터럽트 여부를 매시간 확인하고 모니터링 하게 될 것입니다.
이런 경우의 폴링은 Busy wait
를 하는 것인데, 원하는 조건이 충족될 때까지 루프 내에서 계속해서 확인하는 것을 의미합니다. 이건 리소스 낭비의 요인이 될 수 있습니다.
두 개 이상의 인터럽트가 동시에 들어오면 어떻게 반응함?
운영 체제에서 사용되는 인터럽트 처리 메커니즘에 따라 정확한 동작이 달라질 수는 있지만 주로 사용하는 매커니즘에는,
인터럽트에 우선순위를 할당
더 높은 우선순위는 그 인터럽트가 더 긴급하거나 중요함을 나타내기 때문에 여러 개의 인터럽트가 발생하면 시스템은 먼저 가장 높은 우선순위를 가진 인터럽트를 처리합니다.
인터럽트 마스킹:
인터럽트 처리 중에 다른 인터럽트를 일시적으로 비활성화해서 우선순위를 선점하지 못하도록 방지하는 기술도 있습니다. 이를 통해서 현재 처리중인 인터럽드를 다른 인터럽트가 제어를 가져가지 못하도록 보장합니다.
인터럽트 큐, 버퍼:
인터럽트 큐 또는 버퍼를 사용해서 받은 순서대로 대기 중인 인터럽트를 저장할 수 있습니다. 이를 통해 인터럽트 처리 순서를 관리할 수 있습니다.
운영 체제에 의해서 실행되는 프로그램의 독립적인 인스턴스를 뜻합니다.
프로세스는 프로그램 코드
, 관련된 데이터
, 프로그램이 실행되기 위해서 필요한 실행 컨텍스트
로 구성됩니다.
각 프로세스는 고유한 메모리 공간, 리소스 및 시스템 관련 정보를 갖고 있으며, 별도의 실행 단위입니다.
프로그램, 프로세스, 스레드 간의 차이점은?
- 프로그램:
디스크나 메모리에 저장된, 프로그래밍 언어로 작성된 명령의 집합입니다.
프로그램은 수동적인 개념으로, 스스로는 어떤 작업도 수행하지 않습니다. 메모리로 로드되어 운영 체제에 의해 실행되면 활성화되어 작동합니다.
즉, 실행 중인 프로그램의 인스턴스이고, 코드, 데이터, 실행 컨텍스트를 포함합니다.
각 프로세스는 독립적인 메모리 공간에서 작동하며 파일 핸들, 변수, 시스템 리소스와 같은 자원을 갖고 있습니다.
프로세스는 프로세스 간 통신 메커니즘을 사용하여 서로 통신할 수 있습니다.
같은 프로세스 내의 스레드는 같은 메모리 공간과 리소스를 공유하므로 별도의 프로세스보다 가벼운 개념입니다.
스레드는 운영 체제의 스레드 스케줄러에 의해서 관리되며 하나의 프로세스 내에서 동시에 실행될 수 있습니다.
프로세스 내의 여러 스레드는 동시에 다른 작업을 수행할 수 있으며 프로세스와 비교해 더 쉽게 서로 통신할 수 있는 장점이 있습니다.
프로세스는 멀티태스킹 작업에 유용하다면, 스레드는 하나의 프로세스 내에서 효율적인 병렬 처리에 유용합니다.
PCB? (Process Control Block)
PCB는 "프로세스 제어 블록"의 줄임말이고, 운영 체제에서 실행 중인 프로세스에 대한 정보를 관리하고 저장하기 위한 데이터 구조입니다.
PCB에는 프로세스를 효과적으로 관리하고 멀티태스킹 기능을 제공하기 위해 필요한 핵심 정보가 포함되어 있습니다. 즉, PCB는 프로세스의 중앙 정보 저장소 같은 역할을 하며, 운영 체제의 스케줄러가 프로세스 실행에 관한 결정을 내리는 데 PCB 정보가 사용됩니다.
프로세스 제어 블록에 저장되는 정보
그럼 스레드도 PCB가 있나요?
말 그대로의 PCB는 없지만 스레드도 "Thread Control Block"(TCB) 라고, 프로세스 제어 블록과 유사한 데이터 구조를 가지고 있습니다. 이 데이터 구조도 마찬가지로 스레드에 대한 필수적인 정보를 저장하고 운영 체제가 프로세스 내 스레드를 관리하고 제어하는 데 사용됩니다.즉, 스레드 제어 블록의 목적은 프로세스의 PCB와 유사합니다.
PCB와 마찬가지로 TCB에도
스레드 ID, 스레드 상태, 우선순위, 리소스, 레지스터 등의 정보를 가지고 있습니다.
스레드 제어 블록은 프로세스 내에서 스레드 실행, 동기화 및 통신을 관리하는 데 중요합니다.
각 스레드는 프로세스 내에서 같은 메모리 공간과 리소스를 공유하지만, 개별적인 스레드 제어 블록을 갖고 있어서 운영 체제가 각각의 속성과 상태를 관리하고 있다는 사실이 중요합니다.
리눅스에서 어떻게 생성되는지
- 프로세스 생성
fork()
시스템 콜을 호출합니다.
현재 프로세스의 복사본을 생성하는데, 현재 프로세는 부모 프로세스, 그리고 복사된 프로세스는 자식 프로세스가 됩니다.
자식 프로세스는 부모 프로세스가 실행 중이었던 포인트부터 실행하기 시작하는데, 이후의 작업은 역할에 따라 독립적으로 행동하게 됩니다.
Example in C using `fork()`:
```c
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// Child process
printf("Child process\\n");
} else if (pid > 0) {
// Parent process
printf("Parent process\\n");
} else {
// Fork failed
perror("fork");
}
return 0;
}
```
pthread 의 pthread_create()
함수를 호출합니다.이 함수는 같은 프로세스에서 새로운 스레드를 생성해주는데, 생성된 스레드들은 모두 같은 메모리 공간과 리소스를 공유하게 됩니다.
Example in C using `pthread_create()`:
```c
#include <pthread.h>
#include <stdio.h>
void *thread_function(void *arg) {
printf("Thread created\\n");
return NULL;
}
int main() {
pthread_t thread_id;
pthread_create(&thread_id, NULL, thread_function, NULL);
pthread_join(thread_id, NULL); // Wait for the thread to finish
printf("Main thread\\n");
return 0;
}
```
시나리오)
- 자식 프로세스가 부모 프로세스에게 죽음을 알리지 않고 죽는다면?
- 부모 프로세스가 먼저 죽어버리면?
OS는 부모 프로세스에게 이 사실을 자동으로 알리지 않습니다. 왜냐하면 프로세스들은 독립적으로 행동하고, 자식 프로세스가 죽었다고 해서 부모 프로세스가 멈춰서는 안되기 때문입니다.
하지만 로직 상으로 자식 프로세스를 기다려야 할 때는 wait()
or waitpid()
같은 시스템 콜을 사용해서 확
자식 프로세스는 종료되지 않습니다. 마찬가지 이유로, 자식 프로세스는 독립적으로 행동을 계속 하게 됩니다. 하지만 이런 프로세스는 orphaned process
, 즉 고아 프로세스라고 불리게 되고 이런 프로세스들은 init
process 에 의해 거둬진(??)다고 표현해야 할까ㅎㅎ Linux에 존재하는 모든 프로세스의 조상 프로세스를 부모 프로세스로 가지게 됩니다. 자바 객체의 Object
와 같은 느낌인 것 같습니다.
init
프로세스의 역할은 이런 고아 프로세스가 좀비 프로세스(즉, 리소스와 상태가 정리되지 않은 상태에서 종료되는 것)가 되지 않도록 관리하는 것입니다.
"프로세스 주소 공간"은 프로세스가 실행되는 동안 사용하거나 접근할 수 있는 메모리 주소 범위를 나타냅니다.
프로세스는 각자 고유한 독립적인 가상 주소 공간을 가집니다. 즉, 프로세스는 마치 전체 메모리에 접근할 수 있는 것처럼 느끼지만, 실제로는 운영 체제에 의해 관리되는 물리적 메모리의 일부를 사용하고 있습니다.
프로세스 주소 공간의 영역
텍스트 (코드) 영역
프로그램의 실행 코드와 프로세서가 실행하는 명령이 포함됩니다. 일반적으로 읽기 전용으로 표시되어 의도하지 않은 수정을 방지합니다.
데이터 영역
컴파일 시 초기화된 전역 및 정적 변수가 포함됩니다.
이 영역은 초기 값이 명시적으로 지정된 변수에 사용되는 초기화된 데이터 섹션과 초기화되지 않은 데이터 섹션으로 구분됩니다.
후자는 종종 "bss" (Block Started by Symbol) 섹션으로 알려져 있습니다.
BSS 섹션에서는 초기화되지 않은 변수에 대해 메모리는 할당되지만, 특정 값으로 초기화되지는 않습니다.
대신, 운영 체제에 따라서 기본 값 (보통 0)으로 변수를 초기화해주는 경우도 있습니다.
Heap
힙은 프로그램의 실행 중에 동적 메모리 할당에 사용되는 영역입니다.
프로그램은 실행 중에 malloc()
또는 C/C++의 new
와 같은 함수를 사용하여 운영 체제로부터 메모리를 요청할 수 있습니다.
Stack
스택은 지역 변수와 함수 호출 관리에 사용됩니다.
함수가 호출될 때마다 새로운 스택 프레임이 생성되고, 함수의 지역 변수, 반환 주소 같은 정보가 포함됩니다.
매핑된 파일, 라이브러리
스택과 힙의 영역 크기는 큰가요? 아니라면 언제 그 크기가 결정되나요?
아니요.
운영 체제에 따라, 그리고 하드웨어의 메모리 용량 등에 따라서 영향을 받을 수 있는데 일반적으로 크지 않습니다. 스택과 힙 공간의 크기는 제한적이고요, 프로세스의 생성과 초기화 중에 결정됩니다.
새로운 프로세스나 스레드가 생성될 때, 운영 체제는 스택에 대한 고정된 양의 메모리를 할당합니다. 이 메모리 할당은 보통 몇MB 입니다.
함수 호출이나 깊은 재귀로 스택 공간을 고갈시키면 Stack overflow 가 발생하는 것도 이 스택의 크기가 크지 않기 때문입니다. Segmentation fault 나 메모리 보호 관련 exception 과도 관련이 있습니다.
프로그램은 `malloc()` 이나`new`와 같은 함수로 운영 체제로부터 메모리를 요청할 수 있습니다.
힙 크기는 시스템 내 전체 사용 가능한 메모리와 운영 체제의 정책에 의해 제한되는데, 힙이 가득 차서 메모리 할당 요청을 충족시킬 수 없는 경우에는 메모리 할당 실패가 발생할 수 있습니다.
그럼 접근속도는 누가 빠른가?
일반적으로 Stack 이 힙보다 접근 속도가 빠릅니다.
Stack 의 구조상,
메모리 할당과 제거가 함수가 호출되고 리턴되는 것과 같이 고정된 순서에 의해 동작하는 단순한 시스템이기 때문에 메모리 접근에 효율적입니다.
또 스택은 사이즈가 작기 때문에 프로세서의 캐시 메모리에 저장될 수 있습니다. 따라서 이 부분 때문에 접근 속도가 더 빨라지기도 합니다.
하지만 Heap은 동적인 메모리 관리를 해야 되기 때문에 더 복잡한 시스템을 가지고 있고 오버헤드도 더 많습니다.
또 힙의 저장된 메모리들은 Pointer로 관리 되기 때문에, 직접적인 접근이 아닌 중간에 한 겹의 레이어가 더 있다는 점에서 조금 더 느려지게 되는 이유가 될 수도 있습니다.
또한 힙은 사이즈가 클 수 있기 때문에 항상 프로세서의 캐시 메모리에 올라가지 않습니다.
근데 왜 이렇게 공간을 나누는 거임?
스택과 힙 공간을 분리하는 건 컴퓨터 메모리 관리 측면에서 이점이 많습니다.
스택은 후입선출 (LIFO) 구조를 이용하여 함수 호출과 지역 변수 관리가 쉽도록 특별히 설계되었기 때문에, 빠른 호출과 반환 작업이 가능해집니다.
스택의 구조화된 형태와 고정된 할당 패턴은 “메모리 조각화”와 “할당 관련 문제”를 예방하는 데 도움이 됩니다.
스택은 종종 작고 메모리 액세스 패턴이 예측 가능하기 때문에 캐시에 저장해두고 쓰기 좋습니다.
따라서 스택에 저장된 데이터는 프로세서의 캐시에 더 자주 존재하므로 메모리 액세스 시간이 더 빠릅니다.
그리고 힙은 한 번의 함수 호출 범위를 넘어서 지속되어야 되는 동적 수명을 갖는 데이터를 관리하기 위해 필요합니다.
힙을 사용하면 데이터를 구조화되지 않은 방식으로 할당, 해제할 수 있으므로 다양한 크기의 데이터 구조를 관리하는 데 유연성을 제공합니다.
Stack과 Heap. 자료구조의 그것들과 같은 건가요? 어떤 관계인가요?
네. 이 용어들은 스택과 힙 데이터 구조와 관련성이 있는 건 맞지만, 운영체제에서 뜻하는 stack 과 heap은 특정한 개념을 가리킵니다.
"스택"은 함수 호출과 지역 변수에 사용되는 메모리 영역을 의미하며,
"힙"은 동적 메모리 할당에 사용되는 메모리 영역을 의미합니다.
스택에서의 주요 작업은 push 와 pop 입니다.
운영체제에서 "스택" 메모리 영역은 이 데이터 구조를 기반으로 이름이 지어졌습니다.
이 영역은 함수 호출 컨텍스트와 지역 변수를 관리하는 데 사용됩니다. 함수가 호출되면 새로운 스택 프레임 (또는 활성화 레코드라고도 함)이 스택에 푸시됩니다. 이 프레임에는 함수의 매개변수, 지역 변수 및 반환 주소에 관한 정보가 포함됩니다. 함수가 반환되면 스택 프레임이 팝되고 제어가 호출한 함수로 돌아갑니다.
메모리 관리에서 "힙" 메모리 영역은 동적 메모리 할당을 관리하는 유사한 목적으로 사용됩니다. 프로그램은 `malloc()` 또는 `new`와 같은 함수를 사용하여 런타임에 메모리를 요청할 수 있습니다. 그러나 힙 내의 메모리 관리는 스택과 비교했을 때 더 복잡합니다. 할당된 메모리 블록의 자유 리스트를 유지하고 조각화를 처리하는 것이 포함되기 때문입니다.
운영 체제에서 "단기 스케줄러", "중기 스케줄러", "장기 스케줄러"는 프로세스 스케줄링과 관리의 수준을 나타내는 용어입니다.
장기 스케줄러의 결정은 시스템 성능에 큰 영향을 미치고, CPU 이용률과 시스템 처리량 사이의 균형을 결정합니다. 그리고 시스템이 너무 많은 프로세스로 과다하게 가득 차지 않도록 보장합니다.
장기 스케줄러는 다른 스케줄러보다 덜 호출되고, 결정은 디스크에서 메모리로 프로세스를 로드하는 상대적으로 시간이 많이 걸리는 작업을 수행합니다.
단기 스케줄러의 결정은 빈번하게 빠르게 이루어지며, 밀리초 단위로 이루어집니다.
중기 스케줄러는 메모리 thrashing 문제를 방지하는데 도움을 줍니다. 메모리 Thrasing 이란 실행을 하는 시간보다 프로세스가 메모리와 보조 저장소 간에 교환하는 시간이 더 길어지는 상황을 의미합니다.
현대에도 셋 다 쓰입니까?
네, 현대의 운영 체제는 여전히 장기, 단기 및 중기 스케줄러의 개념을 사용합니다.
하지만 하드웨어와 컴퓨팅 환경이 발전함에 따라서 스케줄러의 역할과 기능이 현재 요구 사항에 맞게 발전했습니다.
현대 운영 체제에서의 활용
프로세스의 스케줄링 상태
- New (생성)
프로세스가 생성되면 "new" 상태로 들어갑니다. 이 상태에서 프로세스가 초기화되며 실행을 위해 필요한 리소스가 할당됩니다. 프로세스는 아직 실행 준비가 안 된 상태입니다.
Ready (준비)
프로세스가 초기화되고 실행 준비가 된 경우 "ready" 상태로 전환됩니다. 그러나 CPU 시간 할당을 기다리는 동안에 있으며 "ready" 큐에 보관되어 CPU 스케줄러에 의해 실행 대상으로 선정됩니다.
Running (실행)
CPU 스케줄러가 "ready" 큐에서 프로세스를 선택하고 CPU 시간을 할당하면 프로세스는 "running" 상태로 전환됩니다. 이 상태에서는 프로세스의 명령이 CPU에 의해 실행됩니다.
Terminated
프로세스가 실행을 완료하면 "terminated" 상태로 전환됩니다. 이 상태에서 프로세스는 작업을 마치고 리소스를 해제하며 시스템에서 제거됩니다. 종료 상태에서는 프로세스의 종료 상태가 보고될 수 있습니다.
Blocked (또는 Waiting, 차단)
특정 리소스의 부재로 인해 프로세스가 실행을 계속할 수 없는 경우 "blocked" 상태로 전환됩니다. 이는 입력/출력 작업을 기다리거나 어떤 이벤트가 발생하기를 기다리는 경우 등입니다. 필요한 리소스가 사용 가능해질 때까지 프로세스는 이 상태에 머무릅니다.
프로세스는 다양한 이벤트와 스케줄링 결정에 따라 이러한 상태간을 이동할 수 있습니다. 예를 들어 프로세스는 "ready"에서 "running"으로 CPU 스케줄러에 의해 선택되는 경우, 리소스를 요청하여 "running"에서 "blocked"로 이동할 수 있으며, 필요한 리소스가 사용 가능해지면 "blocked"에서 다시 "ready"로 이동할 수 있습니다.
이러한 프로세스 스케줄링 상태를 이해하는 것은 효율적인 멀티태스킹에 필수적입니다. 운영 체제는 어떤 프로세스를 실행할지, 언제 리소스를 할당할지, CPU 이용률과 응답성 사이의 균형을 유지할지에 대한 결정을 내리기 위해 이러한 상태에 대한 정보를 활용합니다.
Preemptive(선점) / Non-Preemptive 스케줄링에서 존재할 수 없는 상태는?
선점 스케줄링에서 존재할 수 없는 상태는 "Running" 상태입니다.
선점 스케줄링에서 운영 체제는 실행 중인 프로세스를 중단하고 더 높은 우선순위의 프로세스가 실행 가능한 경우 CPU를 다른 프로세스에 할당할 수 있습니다. 그렇기 때문에 프로세스는 전체 실행 시간 슬라이스를 완료하지 않고 "Running" 상태에서 "Ready" 상태로 전환될 수 있습니다.
반면에 비선점 스케줄링에서는 프로세스가 CPU를 자발적으로 반환하거나 프로세스가 실행을 완료할 때까지 CPU 제어권을 유지합니다. 이 경우 "Running" 상태는 현재 CPU를 사용하는 프로세스를 나타내며, 이 프로세스는 명시적으로 양도하거나 완료할 때까지 CPU를 보유하게 됩니다.
따라서 선점 스케줄링에서는 "Running" 상태가 일시적이며 언제든지 중단될 수 있지만, 비선점 스케줄링에서는 "Running" 상태가 프로세스가 CPU를 제어하는 상태를 나타내며 해당 프로세스가 제어를 양도할 때까지 계속 CPU를 보유합니다.
메모리가 부족한 경우 상태변화?
“Blocked” 나 “Waiting” 상태로 변할 확률이 높을 것 같습니다.
작업을 실행하는데 충분한 메모리가 확보되었을 때 다시 “Running” 이나 “Ready” state로 이동할 것입니다.
"컨텍스트 스위칭"은 CPU의 실행을 한 프로세스에서 다른 프로세스로 전환하는 과정을 의미합니다.
컨텍스트 스위칭이 발생하면 운영 체제는 현재 실행 중인 프로세스의 상태(레지스터 값, 프로그램 카운터 등)를 저장합니다. 그리고 다음 실행될 프로세스의 컨텍스트를 가져와 PCB에 업데이트하고 실행을 전환합니다.
컨텍스트 스위칭이 발생하면?
우선 컨텍스트 저장.
현재 프로세스의 상태를 저장합니다.
이는 CPU 레지스터 값, 프로그램 카운터, 스택 포인터 등 프로세스의 실행 상태를 나타내는 관련 데이터 값을 의미합니다.
컨텍스트 불러오기
운영 체제는 실행될 다음 프로세스의 저장된 컨텍스트를 가져옵니다.
프로세스 제어 블록(PCB) 업데이트
프로세스와 관련된 데이터 구조인 프로세스 제어 블록(PCB)을 프로세스 상태와 실행 기록의 변경 사항을 반영하도록 업데이트 합니다.
실행 전환
CPU는 새로운 프로세스의 컨텍스트로 실행을 전환합니다.
컨텍스트 스위칭은 운영 체제가 여러 프로세스를 동시에 실행하는 것처럼 보이게 하는 역할을 합니다.
이를 통해 프로세스는 서로 간섭하지 않으면서 CPU 시간을 공유할 수 있습니다. 그러나 컨텍스트 스위칭은 프로세스의 실행 컨텍스트를 저장하고 복원하는 데 필요한 시간과 리소스로 인해 비용이 발생합니다.
단점 / 장점
컨텍스트 스위칭이 일어날 때, 프로세스와 스레드에게 일어나는 일의 차이점?
프로세스와 스레드 모두 컨텍스트를 저장하고, 다음 실행될 데이터를 불러오고, 정보를 업데이트하고, 다음을 실행한다는 점에서 일어나는 과정이 같습니다.
하지만, 주요하게 다른 점은 프로세스는 각자 자신의 메모리와 리소를 따로 가지고 있는 독립된 프로그램 인스턴스이며, 스레드는 프로세스 내 자원을 공유하는 더 가벼운 개념이라는 점입니다.
따라서, 상태를 전환하고 리소스를 해제하는 과정에서 스레드의 경우 필수적이지 않은 작업일 수 있습니다. 왜냐하면 리소스는 일반적으로 프로세스 레벨에서 관리되기 때문입니다.
컨텍스트 스위칭은 언제 일어날까요?
우선 가장 먼저 생각해 볼 수 있는 의도적인 스위칭은,
I/O 인터럽트 와 같이 하드웨어 인터럽트가 발생하는 경우입니다.
사용자 영역에서의 시스템 콜
이 경우 CPU는 사용자 모드에서 커널 모드로 전환됩니다.
그리고 스케줄링 방식 변화에 따라 사용자 의도와 관계없이 일어나는 스위칭은,
CPU는 도착한 순서대로 프로세스에 할당되며, 해당 프로세스는 완료되거나 I/O를 위해 차단될 때까지 계속 실행됩니다. 간단하지만 FCFS는 짧은 프로세스가 긴 프로세스로 인해 지연되는 "convoy effect(호위 효과)” 를 일으킬 수 있습니다.
SJF를 선점 가능한 상황으로 사용할 경우, 이 경우는 "남은 실행 시간이 가장 짧은 프로세스 먼저"를 의미합니다.
프로세스는 원형 큐에 배치되며, 각 프로세스는 할당된 시간 조각 동안 실행한 후 큐의 맨 뒤로 이동합니다. RR은 공정성을 보장하지만 빈번한 컨텍스트 스위치로 인한 오버헤드가 높아질 수 있습니다.
우선순위 스케줄링은 선점 가능한 경우(우선순위 선점)나 선점 불가능한 경우(우선순위 비선점)로 구현될 수 있습니다. 그러나 우선순위 역전(낮은 우선순위 프로세스가 우선순위가 높은 프로세스가 필요한 리소스를 보유하고 있는 경우)이 문제가 될 수 있습니다.
각 대기열은 자체 스케줄링 알고리즘을 사용할 수 있습니다(높은 우선순위 대기열에 RR을 사용하거나 낮은 우선순위 대기열에 FCFS를 사용하는 등). 프로세스는 우선순위와 동작에 따라 대기열 사이를 이동합니다.
RR 방식에서 time slice 와 성능 간의 관계
Time slice가 너무 크다면, queue에 있는 다음 작업으로 이동하기 까지의 평균 대기 시간이 커진다는 뜻입니다.
따라서, CPU-bound task가 많다면 성능적으로 이득일 순 있겠지만, IO-bound task가 많은 경우 심하게 시간이 낭비될 수 있습니다.
또한 주어진 time slice 전에 작업이 끝나게 된다면, 남는 시간동안 시간이 또 낭비되는 상황이 일어날 수 있습니다.
하지만 또 만약 time slice가 너무 작다면, context switching 이 과도하게 많이 일어나게 되어 switching overhead가 커질 수 있습니다.
싱글 스레드 CPU 에서 상시로 돌아가야 하는 프로세스가 있다면, 어떤 스케쥴링 알고리즘을 사용하는 것이 좋을까요?
이런 상황에서는 우선 비선점 알고리즘이 유리할 것입니다. 왜냐하면 상시로 돌아가야 하기 때문에 스케줄링 정책 변화에 영향을 받아 교체되지 않아야 하기 때문입니다.
그리고 오직 하나의 프로세스가 존재하는 환경이기 때문에, 컨텍스트 스위칭에 대한 오버헤드를 걱정하지 않아도 됩니다.
따라서 이런 경우에는 우선 순위, 오버헤드 걱정이 없이 단순한 비선점 알고리즘인 FCFS 알고리즘을 사용하는게 좋아보입니다.
동시성과 병렬성의 차이?
동시성:
동시성은 시스템이 동시에 여러 작업이나 프로세스를 처리할 수 있는 능력을 말합니다.
동시성은 작업이 반드시 동시에 실행되는 것을 의미하지는 않습니다. 데이터가 서로 인접하지 않도록 배열하는 방식이 잘 수행되고 있다면 동시성이 높다고 판단합니다.
따라서 하나의 작업만이 실행되고 있는 상황일지라도 동시성이 높을 수 있습니다.
동시성 시스템에서 운영 체제나 스케줄러는 작업 간에 CPU의 주의를 빠르게 전환하여 각 작업이 일부 작업을 실행한 후 다른 작업으로 전환할 수 있도록 합니다. 동시성은 자원 활용 및 응답성을 향상시키기 위해 사용됩니다.
병렬성:
반면에 병렬성은 여러 작업이나 프로세스를 동시에 실행하는 것을 의미합니다.
병렬 시스템에서는 여러 개의 CPU나 처리 장치가 함께 작업을 병렬로 실행하며, 각 작업은 별도의 처리 장치에서 동시에 실행됩니다.
병렬성은 일반적으로 독립적인 하위 작업으로 분할할 수 있는 작업을 빠르게 실행하고 더 높은 성능을 달성하기 위해 사용됩니다.
차이점:
즉,동시성과 병렬성의 주요 차이점은 작업이 실제로 동시에 실행되는지 여부에 있습니다.
동시성 시스템에서 작업은 하나의 처리 장치에서 교대로 실행되며, 작업 간 전환은 스케줄러에 의해 관리됩니다. 반면에 병렬 시스템에서 작업은 별도의 처리 장치에서 동시에 실행되어 실제로 동시 실행을 달성합니다.
"뮤텍스"와 "세마포어"는 동시 프로그래밍에서 공유 자원에 대한 접근을 조정하는 데 사용되는 동기화 메커니즘입니다. 하지만 이 둘의 주요 차이점은 사용 사례와 능력에 있습니다.
즉, 뮤텍스는 리소스에 배타적인 접근을 보장하기 위한 시나리오에 딱 사용되는 반면에
세마포어는 더 유연하게 사용되어서 제한된 리소스에 대한 액세스를 제어하거나, 복잡한 방식으로 동시성을 관리하는 데 사용됩니다.
더 구체적으로는)
뮤텍스:
세마포어:
그럼 binary 세마포어랑 뮤텍스가 다른 점은 무엇?
- 상태:
- 이진 세마포어: "0" 또는 "1"과 같이 두 가지 상태를 가질 수 있으며, 리소스의 가용성이나 임계 영역의 점유 여부를 나타냅니다.
- 뮤텍스: 이진 세마포어와 달리 명확한 상태가 없습니다. 대신 잠겨있는(소유) 상태와 잠금 해제된(미소유) 상태로 구분됩니다.
- 목적과 사용:
- 이진 세마포어: 주로 신호 및 상호 배제를 위해 사용되지만 더 넓은 응용이 가능합니다.
- 뮤텍스: 특히 상호 배제를 위해 설계되었으며, 종종 복잡한 동기화 시나리오를 다루기 위한 추가 기능을 제공합니다.
- 소유권:
- 이진 세마포어: 소유권은 명시적으로 스레드에 연결되어 있지 않습니다.
- 뮤텍스: 소유권은 뮤텍스를 잠그는 스레드에 연결되어 있습니다. 소유한 스레드가 이를 잠금 해제하는 책임을 가집니다.
요약하자면, 이진 세마포어와 뮤텍스는 모두 상호 배제를 제공하는 데 사용되지만,
뮤텍스는 특히 상호 배제를 위해 더 특수화되어 있으며
이진 세마포어는 리소스의 수에 대한 제한과 같이 카운팅이나 동기화 같은 목적에 넓게 응용이 가능합니다.