다음 코드에서 발생 가능한 문제
int value = 0;
const int threadCount = 10000;
void increment()
{
for (int i = 0; i < threadCount; ++i)
{
++value;
}
}
void decrement()
{
for (int i = 0; i < threadCount; ++i)
{
--value;
}
}
int main()
{
thread incrementThread(increment);
thread decrementThread(decrement);
incrementThread.join();
decrementThread.join();
cout << value;
}
- 공유 데이터인 value가 두 스레드에서 동시에 수정되는 과정에서 발생할 수 있는 race condition으로 임계구역 ++value와 --value에서의 연산에서 데이터 일관성이 보장되지 않을 수 있음
위에서 제시한 문제를 해결하는 방법
- Mutex 등을 통해 한 번에 하나의 스레드만 공유 자원에 대해 접근하도록 해야 함
스레드에 안전하지 않은 메서드를 동기화하지 않으면 발생할 수 있는 문제
- 여러 스레드가 동시에 실행될 경우 레이스 컨디션이 발생하여 데이터 일관성이 깨질 수 있으므로 동기화 도구를 적용하거나 스레드에 안전한 메서드를 사용해야 함
공유 메모리 기반 IPC가 소켓 통신보다 빠른 이유
- 공유 메모리는 동일한 메모리 공간에 직접 접근하여 데이터를 주고받고, 마치 자신의 메모리 공간을 읽고 쓰는 것처럼 IPC가 이루어지기 때문에 빠름
소켓 통신은 주고 받는 데이터가 커널을 통하므로 추가적인 오버헤드가 발생하여 더 느림
운영체제의 커널이 무엇이며 무슨 기능을 하는지
- 커널은 운영체제의 핵심 부분으로, 하드웨어와 응용 프로그램 간의 중재자 역할로서, 프로세스와 스레드가 하드웨어를 공정하게 할당받아 실행되도록 함
이중 모드를 운영해 사용자 응용 프로그램이 안전하고 효율적으로 시스템 자원을 사용할 수 있음
지나치게 Context Switch가 반복되면 발생 가능한 문제
- 실제 작업보다 Context 저장과 복구에 CPU 시간을 사용하게 되므로 효율성을 떨어뜨림
캐시 메모리의 데이터를 반복적으로 무효화하면서 캐시 미스율이 증가
4GB보다 큰 크기의 프로그램을 4GB인 물리 메모리로 실행할 수 있을까요? 실행할 수 있다면 어떤 방법으로?
- 가상 메모리 기술을 통해 가능
운영체제는 프로세스가 필요한 메모리 공간을 가상 주소로 할당하고 실제 물리 메모리에는 필요한 부분만 적재
필요하지 않은 부분은 디스크의 스왑 영역에 저장하고 필요할 때 다시 물리 메모리에 적재할 수 있음
페이지 폴트를 처리하는 과정
- CPU가 기존의 작업 내역을 백업
- 페이지 폴트 처리 루틴 실행
페이지 폴트 처리 루틴은 보조기억장치에서 메모리로 원하는 페이지를 가져와 유효 비트를 1로 변경해 주는 작업 - CPU가 해당 페이지를 접근
메모리 누수는 무엇이며 어떻게 해결
- 메모리 누수는 할당받은 메모리를 해제하지 않아 메모리가 지속적으로 점유되는 현상
메모리 누수 문제를 해결하기 위해서는 메모리를 사용한 후 명시적으로 해제하거나 스마트 포인터와 같은 RAII 객체를 사용하거나 가비지 컬렉터를 활용
스레드를 생성하고 실행할 때 사용하는 join
- join은 생성된 스레드의 실행이 완료될 때까지 기다리게 하는 메서드
생성된 스레드의 실행이 종료되기도 전에 스레드를 생성한 스레드가 종료하거나 다음 작업을 수행하는 것을 방지할 수 있음
동일한 코드를 여러 프로세스로 동시에 실행하는 것과 여러 스레드로 프로세스를 실행하는 것에는 어떤 차이
- 차이점은 자원 공유 여부에 있음
동일한 코드를 여러 프로세스에서 동시에 실행하는 경우 기본적으로 각 프로세스는 독립적으로 실행되며, 여러 스레드로 프로세스를 실행하는 경우 프로세스의 명령을 동시에 처리
프로세스는 메모리(데이터, 힙, 스택) 등의 자원을 공유하지 않으므로 컨텍스트 스위칭의 오버헤드가 비교적 큼
스레드는 스택 메모리만 공유하지 않으므로 컨텍스트 스위칭의 오버헤드가 비교적 작음
프로세스는 스레드와 달리 서로 독립적이기 때문에 한 프로세스의 문제가 다른 프로세스에 미치는 영향이 적음
여러 스레드로 프로세스를 실행하는 경우 동일한 프로세스 내에서 실행되며 자원을 공유하여 스레드 간에 통신과 협력은 쉽지만 한 스레드의 문제가 전체 스레드에 영향을 줄 수 있음
교착 상태(데드락)이 무엇인지, 왜 발생하는지 설명
- 데드락은 2개 이상의 프로세스나 스레드가 서로 상대방의 자원을 기다리며 무한정 대기하는 상황
네 가지 조건(상호 배제, 점유와 대기, 비선점, 원형 대기)을 충족할 때 발생할 수 있음
디버깅할 때 주로 볼 수 있는 주소는 실제 메모리 주소일까요?
- 실제 메모리의 물리 주소가 아니라 논리 주소
프로세스를 실행하는 CPU나 메모리에 적재된 프로세스 입장에서 메모리 내에 물리 주소를 모두 알기 어렵기 때문에 프로세스마다 부여되는 논리 주소를 활용
따라서 디버깅할 때의 메모리 주소는 프로세스의 논리 주소에 해당
이 논리 주소는 CPU와 메모리 사이에 위치하는 MMU에 의해 물리 주소로 변환됨
메모리가 부족하면 어떤 현상이 발생
- 메모리가 부족하면 운영체제는 부족한 메모리를 보완하기 위해 디스크에 메모리 페이지를 저장하고 필요한 페이지를 불러오는 과정인 스와핑을 사용
스와핑이 과도하게 발생할 경우 스레싱이라는 현상을 초래하며 시스템이 지속적으로 디스크 I/O에만 집중하여 프로세스 처리가 늦어짐
프로세스의 페이지 테이블이 모두 메모리에 적재될 경우에 발생할 수 있는 문제점과 해결방안
- 페이지 테이블 접근과 실제 데이터 접근으로 인해 메모리 접근이 두 배로 늘어나고, 페이지 테이블이 차지하는 메모리 용량이 커져 비효율적
TLB를 사용해 페이지 테이블의 일부를 캐싱하고 계층적 페이징 기법을 사용해 페이지 테이블의 일부만 메모리에 적재하여 메모리 접근 횟수와 메모리 사용량을 줄일 수 있음
#include <malloc.h>
void Func()
{
::malloc(0xFFFF);
Func();
}
int main()
{
Func();
}
Func() 첫 번째 호출
- malloc 직전 : Func()의 스택 프레임이 생성되었고, 지역 변수와 함수의 리턴 주소 등이 저장됨
- malloc 직후 : 힙 메모리에서 65,535 바이트가 할당됨
Func() 두 번째 호출
- malloc 직전
- 첫 번째 호출에서 할당된 65,535 바이트가 힙에 존재
- 첫 번째 Func() 호출의 스택 프레임이 스택 메모리에 남아 있고, 그 위에 두 번째 호출의 스택 프레임이 생성됨
- malloc 직후 : 힙에서 65,535 바이트가 추가로 할당됨
Func() 세 번째 호출
- malloc 직전
- 힙 메모리에 2개의 65,535 바이트 블록이 이미 할당된 상태
- 스택 메모리에는 첫 번째와 두 번째 호출의 스택 프레임이 쌓여 있고, 세 번째 호출의 스택 프레임이 추가로 생성됨
- malloc 직후 : 또다시 힙에서 65,535 바이트가 추가로 할당됨
예외 발생
Func()는 무한 재귀 호출을 포함하므로, 결국 다음과 같은 상황이 발생할 수 있음
- 스택 메모리가 계속 증가하여 한계를 초과하면 스택 오버플로우가 발생하여 운영체제는 프로세스를 종료하고 Segmentation Fault 또는 다른 메모리 접근 오류를 발생시킬 수 있음
- malloc을 통해 힙 메모리를 계속 할당하지만, 시스템의 힙 영역이 고갈되면 malloc이 NULL을 반환하며, malloc 실패 처리를 하지 않으므로 미정의 동작
'CS > 쉽게 배우는 운영체제' 카테고리의 다른 글
[쉽게 배우는 운영체제] 5장 프로세스 동기화 (0) | 2025.02.24 |
---|---|
[쉽게 배우는 운영체제] 4장 CPU 스케줄링 (0) | 2025.02.19 |
[쉽게 배우는 운영체제] 3장 프로세스와 스레드 (0) | 2025.02.12 |
[쉽게 배우는 운영체제] 1장 운영체제 개요 (0) | 2025.01.31 |
운영체제 파일 시스템 (0) | 2024.09.29 |