본 글은 notebooklm으로 정리한 글임을 서두에 밝힙니다.
운영체제 설계 및 구현의 핵심 개념과 구조는 다음과 같습니다.
핵심 개념 및 구조
듀얼 모드 (Dual Mode) 및 보호 (Protection) : 운영체제는 최소한 사용자 모드 (user mode)와 커널 모드 (kernel mode)의 두 가지 운영 모드를 가집니다. 커널 모드는 감독 모드, 시스템 모드, 또는 특권 모드라고도 불립니다. 커널 모드와 사용자 모드를 구분하는 것은 운영체제가 사용자의 작업을 안전하게 제어하고 중요한 시스템 자원을 보호하며, 시스템 호출과 같은 서비스를 효율적으로 처리하기 위한 기본적인 메커니즘입니다.
- 컴퓨터 하드웨어에는 현재 모드를 나타내는 모드 비트 (mode bit)가 추가됩니다 : 커널(0) 또는 사용자(1).
- 모드 비트를 사용하여 운영체제를 대신하여 실행되는 작업과 사용자를 대신하여 실행되는 작업을 구분할 수 있습니다.
- 사용자 애플리케이션을 대신하여 시스템이 실행될 때는 사용자 모드에 있습니다. 사용자 애플리케이션이 시스템 호출(system call)을 통해 운영체제에 서비스를 요청할 때는 요청을 이행하기 위해 시스템이 사용자 모드에서 커널 모드로 전환해야 합니다.
- I/O 명령어 및 정지 명령어(halt instructions)와 같은 다양한 명령어는 특권(privileged)이 부여되어 커널 모드에서만 실행될 수 있습니다.
- 운영체제가 상주하는 메모리도 사용자에 의한 수정으로부터 보호되어야 합니다.
- 타이머(timer)는 무한 루프를 방지합니다. 이러한 기능들(듀얼 모드, 특권 명령어, 메모리 보호, 타이머 인터럽트)은 운영체제가 올바른 작동을 달성하기 위한 기본적인 구성 요소입니다.
- 모든 최신 Windows 운영체제에서 사용되는 Ctrl-Alt-Delete 조합과 같은 특정 키 조합은 운영체제가 인터럽트하여 시스템 상태를 보장하는 데 사용될 수 있습니다.
커널 (Kernel): 운영체제의 핵심 부분으로, 항상 컴퓨터에서 실행되는 프로그램입니다.
- 일부 시스템(예: 휴대폰, 태블릿, 게임 콘솔)은 전체 운영체제를 ROM에 저장합니다. ROM에 저장하는 것은 소규모 운영체제, 단순한 지원 하드웨어 및 견고한 운영에 적합합니다.
- 일부 시스템은 운영체제를 펌웨어에 저장하고 빠른 실행을 위해 RAM으로 복사합니다.
- Linux 커널 모듈은 커널과 상호 작용하는 상대적으로 쉬운 방법을 제공하여 커널 함수를 직접 호출하는 프로그램을 작성할 수 있게 합니다. 커널 수준 코드를 개발할 때 신중한 메모리 관리, 특히 메모리 누수를 방지하기 위한 메모리 해제가 중요합니다.
◦ 솔라리스 DTrace와 같은 도구는 커널 내에서 시스템 호출을 추적할 수 있습니다. DTrace는 커널에서 실행되는 바이트 코드를 생성하는 컴파일러를 특징으로 합니다. 이 코드는 컴파일러에 의해 "안전"함이 보장됩니다. DTrace는 커널의 개인 데이터 검색 및 수정이 가능하므로 DTrace "권한"(또는 "root" 사용자)이 있는 사용자만 사용할 수 있습니다.
시스템 호출 (System Calls): 사용자 애플리케이션이 운영체제에 서비스를 요청하는 메커니즘입니다.
- printf와 같은 표준 C 라이브러리 함수 호출이 내부적으로 write() 시스템 호출로 이어지는 예시가 있습니다.
- 시스템 호출에는 프로세스 제어, 파일 관리, 장치 관리, 정보 유지 관리, 통신 등 다양한 유형이 있습니다.
- DTrace와 같은 도구는 사용자 수준 작업이 커널 수준 반응을 어떻게 유발하는지 보여줄 수 있으며, 이는 성능 모니터링 및 코드 최적화에 유용합니다.
프로세스 (Processes) 및 스레드 (Threads)
- 프로세스는 실행 중인 프로그램입니다.
- 쉘 인터페이스 구현 기술 중 하나는 부모 프로세스가 사용자가 명령줄에 입력한 내용을 먼저 읽은 다음, 명령을 수행하는 별도의 자식 프로세스를 생성하는 것입니다. 자식 프로세스가 백그라운드에서 실행되도록 허용할 수도 있습니다.
- 스레드는 단일 프로세스 내에서 병렬 실행의 기본 단위입니다. 스레드는 동일한 주소 공간을 공유하는 프로세스보다 더 작은 단위의 병렬성을 제공합니다.
- 비동기 스레딩(asynchronous threading)에서는 부모가 자식 스레드를 생성한 후 부모는 실행을 재개하여 부모와 자식이 동시에 실행됩니다. 각 스레드는 다른 스레드와 독립적으로 실행되며 부모 스레드는 자식이 언제 종료되는지 알 필요가 없습니다.
- OpenMP와 같은 기술은 컴파일러 지시어를 사용하여 병렬 영역을 식별함으로써 스레딩의 세부 사항 대부분을 관리합니다. OpenMP는 #pragma omp parallel 지시어를 만나면 시스템의 처리 코어 수만큼 스레드를 생성합니다.
- Grand Central Dispatch (GCD)는 Apple의 Mac OS X 및 iOS 운영체제를 위한 기술로, 애플리케이션 개발자가 병렬로 실행될 코드 섹션을 식별할 수 있도록 C 언어 확장, API, 런타임 라이브러리를 결합한 것입니다. GCD는 블록(^{ }로 지정되는 자체 포함 작업 단위)이라는 C 및 C++ 언어 확장을 식별합니다. GCD는 디스패치 큐(dispatch queue)에 블록을 배치하여 런타임 실행을 스케줄링합니다.
프로세스 간 통신 (Interprocess Communication, IPC)
- 공유 메모리(shared memory) 및 메시지 전달(message passing)과 같은 메커니즘을 사용하여 프로세스가 서로 통신하고 동기화 할 수 있습니다.
- 공유 메모리
- 생산자-소비자 문제를 해결하기 위해 공유 메모리를 사용할 수 있습니다. 생산자가 버퍼를 채우고 소비자가 비우는 항목 버퍼는 생산자 및 소비자 프로세스가 공유하는 메모리 영역에 상주합니다.
- POSIX API의 경우 shm_open() 함수는 공유 메모리 객체를 생성하거나 엽니다. ftruncate() 함수는 객체의 크기를 설정합니다. mmap() 함수는 공유 메모리 객체를 포함하는 메모리 매핑 파일을 설정하고 객체에 접근하는 데 사용되는 포인터를 반환합니다. MAP_SHARED 플래그는 공유 메모리 객체에 대한 변경 사항이 객체를 공유하는 모든 프로세스에 표시되도록 지정합니다.
- Windows 시스템에서는 파일 매핑(file mapping)을 통해 공유 메모리를 사용합니다. 생산자 프로세스는 명명된 파일 매핑 객체를 생성하고(CreateFileMapping()), 해당 객체의 뷰를 생성하여 공유 메모리에 씁니다(MapViewOfFile(), sprintf()). 소비자 프로세스는 기존의 명명된 공유 메모리 객체에 대한 매핑을 생성하고(OpenFileMapping()) 뷰를 생성하여 공유 메모리에서 읽습니다.
- 메시지 전달
- 메시지 전달은 send() 및 receive() 작업으로 구성됩니다.
- send() 작업은 차단(blocking) 방식과 비차단(nonblocking) 방식이 있습니다. 차단 전송은 메시지가 수신 프로세스나 사서함에 의해 수신될 때까지 보내는 프로세스를 차단합니다. 비차단 전송은 메시지를 보내고 작업을 재개합니다.
- receive() 작업도 차단 방식과 비차단 방식이 있습니다. 차단 수신은 메시지가 도착할 때까지 수신자를 차단합니다. 비차단 수신은 유효한 메시지 또는 null을 검색합니다.
- send()와 receive()가 모두 차단될 때 랑데부(rendezvous)가 발생합니다.
- Solaris와 같은 일부 시스템에서는 시스템 호출조차 메시지로 이루어집니다. 태스크가 생성될 때 Kernel 사서함과 Notify 사서함의 두 가지 특별한 사서함도 생성됩니다. 커널은 Kernel 사서함을 사용하여 태스크와 통신하고 이벤트 발생 알림을 Notify 포트로 보냅니다.
- 수신 작업은 메시지를 수신할 사서함 또는 사서함 세트를 지정해야 합니다. 사서함 세트는 태스크에 의해 선언된 사서함들의 컬렉션으로, 태스크 목적을 위해 하나의 사서함처럼 그룹화하여 처리할 수 있습니다.
동기화 (Synchronization)
- 동시 실행 시 발생할 수 있는 경쟁 상태(race condition) 문제를 해결하기 위해 필요합니다.
- 크리티컬 섹션 문제(critical-section problem) : 여러 프로세스가 공유 데이터에 동시에 접근할 때 발생할 수 있는 문제를 다룹니다.
- 하드웨어 기반 동기화
- test_and_set() 명령어: atomically(원자적으로) 실행되며, 대상 포인터의 원래 값을 반환하고 대상 값을 true로 설정합니다. 이 명령어를 사용하여 상호 배제를 구현할 수 있지만, 다중 프로세서 환경에서는 효율성이 떨어질 수 있습니다. 대기 중인 프로세스가 경합 상태를 만족하는 데 도움이 될 수 있도록 개선된 test_and_set() 기반 알고리즘은 유계 대기 요구 사항을 만족합니다.
- compare_and_swap() 명령어: atomically 실행되며, 세 개의 피연산자를 사용합니다. _value == expected가 true인 경우에만 *value가 new_value로 설정됩니다. 항상 *value의 원래 값을 반환합니다. 이 명령어를 사용하여 상호 배제를 구현할 수 있습니다.
- 세마포어 (Semaphores) : wait() (또는 P) 및 signal() (또는 V) 연산을 사용하여 동기화를 제공하는 정수 변수입니다. wait()은 값이 양수이면 세마포어 값을 감소시키고 계속 진행하며, 값이 0 이하이면 프로세스를 차단합니다. signal()은 세마포어 값을 증가시키고 차단된 프로세스 중 하나를 재개합니다. 생산자-소비자 문제와 식사하는 철학자 문제(dining-philosophers problem)와 같은 고전적인 동기화 문제를 해결하는 데 사용됩니다.
- 모니터 (Monitors) : 상호 배제와 함께 제공되는 프로그래머 정의 연산 집합을 포함하는 추상 데이터 타입(ADT)입니다. 모니터 내에서 정의된 함수는 모니터 내에서 로컬로 선언된 변수와 형식 매개변수에만 접근할 수 있습니다. 로컬 변수는 로컬 함수에 의해서만 접근할 수 있습니다.
- 모니터는 *_조건 변수(condition variables)**를 포함할 수 있습니다. 조건 변수에 대해 호출할 수 있는 유일한 연산은 wait()와 signal()입니다. x.wait()는 해당 연산을 호출하는 프로세스를 다른 프로세스가 x.signal()을 호출할 때까지 중단시킵니다. x.signal() 연산은 정확히 하나의 중단된 프로세스를 재개합니다. 중단된 프로세스가 없으면 signal() 연산은 효과가 없습니다.
- x.signal() 연산이 프로세스 P에 의해 호출될 때 조건 x와 관련된 중단된 프로세스 Q가 있는 경우, P는 Q가 모니터를 나가거나 다른 조건을 대기할 때까지 대기하거나(signal and wait), Q가 P가 모니터를 나가거나 다른 조건을 대기할 때까지 대기합니다(signal and continue).
- Java 및 C#과 같은 많은 프로그래밍 언어는 모니터의 개념을 통합했습니다. Java에서는 synchronized 키워드를 사용하여 메서드 또는 코드 블록에 대한 접근을 동기화할 수 있습니다. synchronized 메서드를 호출하려면 해당 객체 인스턴스에 대한 락을 소유해야 합니다.
메모리 관리 (Memory Management)
- 유효-무효 비트(valid–invalid bit) : 일반적으로 페이지 테이블의 각 엔트리에 첨부됩니다. 이 비트가 유효로 설정되면 해당 페이지는 프로세스의 논리적 주소 공간에 있으며 유효한 페이지입니다. 무효로 설정되면 페이지는 프로세스의 논리적 주소 공간에 없습니다.
- 단편화 (Fragmentation) : 내부 단편화와 외부 단편화가 있습니다.
- 페이징 (Paging): 가상 메모리 구현 기술 중 하나로, 메모리를 고정 크기의 블록(페이지)으로 나눕니다. 논리적 주소를 물리적 주소로 변환합니다.
- 세그먼테이션 (Segmentation) : 메모리를 가변 크기의 블록(세그먼트)으로 나눕니다. 순수 세그먼테이션에서는 세그먼트를 교체할 때 필요한 세그먼트에 대한 연속적인 공간이 부족할 수 있습니다.
- 커널 메모리 할당
- 버디 시스템(Buddy System) : 메모리를 2의 거듭제곱 크기의 블록으로 나눕니다. 요청을 만족시키기 위해 다음으로 높은 2의 거듭제곱으로 반올림해야 하므로 할당된 세그먼트 내에서 단편화가 발생할 가능성이 높습니다. 할당된 단위의 50% 미만이 내부 단편화로 인해 낭비될 것이라고 보장할 수 없습니다.
- 슬랩 할당(Slab Allocation) : 커널 데이터 구조(예: 프로세스 디스크립터, 파일 객체, 세마포어)별로 캐시를 사용합니다. 캐시는 하나 이상의 물리적으로 연속적인 페이지인 슬랩으로 구성됩니다. 각 캐시는 해당 데이터 구조의 인스턴스인 객체로 채워집니다. 메모리가 낭비되지 않는 할당 기법입니다. Linux 시스템에서 커널이 새 태스크를 생성할 때 struct task_struct 객체에 필요한 메모리를 해당 캐시에서 요청합니다. 캐시는 슬랩에 이미 할당되어 있고 사용 가능으로 표시된 객체를 사용하여 요청을 처리합니다.
I/O 시스템 (I/O Systems)
프로그래밍된 I/O (PIO): CPU가 상태 비트를 감시하고 데이터를 한 번에 한 바이트씩 컨트롤러 레지스터에 공급하는 방식입니다. CPU가 제어 비트를 폴링(polling)하여 장치가 준비되었는지 계속 확인합니다. 장치와 컨트롤러가 빠르면 합리적이지만, 대기 시간이 길면 비효율적입니다.
인터럽트 기반 I/O: 장치가 다음 바이트를 위해 준비되면 CPU가 인터럽트를 받습니다. CPU가 제어 비트를 폴링하는 대신 인터럽트를 받습니다.
DMA (Direct Memory Access): 디스크 드라이브와 같이 대규모 전송을 하는 장치의 경우, PIO로 CPU를 부담시키는 것을 피하기 위해 DMA 컨트롤러라는 특수 목적 프로세서에 작업을 오프로드합니다. DMA 전송을 시작하기 위해 호스트는 DMA 명령 블록을 메모리에 작성하고, CPU는 이 블록의 주소를 DMA 컨트롤러에 작성한 다음 다른 작업을 계속합니다. DMA 컨트롤러는 메인 CPU의 도움 없이 직접 메모리 버스를 작동하여 전송을 수행합니다.
원시 I/O (Raw I/O): 운영체제 자체 및 데이터베이스 관리 시스템과 같은 특수 애플리케이션은 블록 장치를 단순한 선형 배열의 블록으로 접근하는 것을 선호할 수 있습니다. 파일 시스템을 사용하는 것보다 추가 버퍼링이 필요하지 않을 때 유용합니다.
STREAMS: UNIX System V Release 3에서 개발된 I/O 시스템의 모듈식 상향식 구조입니다. 장치 드라이버와 사용자 프로세스 사이에 여러 처리 모듈이 있을 수 있습니다. 각 모듈은 읽기 큐와 쓰기 큐를 가지고 있습니다.
파일 시스템 (File Systems) 및 저장 장치 (Storage)
- 디스크 스케줄링: FCFS, SSTF, SCAN, LOOK, C-SCAN, C-LOOK과 같은 알고리즘을 사용하여 디스크 헤드 움직임을 최적화합니다.
- SSD (Solid-State Disks): 자기 디스크보다 일반적으로 빠르며 비휘발성입니다. 플래시 메모리 형태도 있습니다. SSD를 캐싱 계층으로 또는 디스크 드라이브 대체로 사용하는 것에는 장단점이 있습니다.
- 안정적인 저장 장치 (Stable Storage): 실패에도 데이터가 손실되지 않도록 보장하기 위해 두 개 이상의 물리적 블록에 데이터를 이중으로 쓰는 등의 기술을 사용합니다. NVRAM을 캐시로 추가하면 안정적인 저장 장치의 성능이 크게 향상될 수 있습니다.
- WAFL (Write-Anywhere File Layout): Network Appliance의 파일 시스템으로, 임의 쓰기에 최적화되어 있습니다. 스냅샷 기능이 매우 효율적이며, 클론(clones)이라는 읽기/쓰기 스냅샷도 지원합니다. WAFL은 RAID 레벨 4를 사용합니다.
보호 (Protection) 및 보안 (Security)
- 보호 영역 (Protection Domains): 프로세스가 접근할 수 있는 객체와 연산 집합을 지정합니다. 스택 검사(stack inspection)는 자바와 같은 시스템에서 보호 영역 간의 권한 흐름을 관리하는 메커니즘입니다.
- 역량 (Capabilities): 보호 메커니즘의 한 형태로, 프로세스에게 특정 객체에 대한 특정 연산을 수행할 권한을 부여합니다. 소프트웨어 역량은 보호되지만 CAP 마이크로코드가 아닌 특권 절차에 의해 해석됩니다. 권한 증폭(rights amplification)은 구현 절차가 추상 데이터 타입의 표현 변수에 접근할 수 있도록 합니다.
- 위협 (Threats)
- 트로이 목마 (Trojan horse): 합법적인 프로그램으로 위장하여 악성 작업을 수행하는 프로그램입니다.
- 스파이웨어 (Spyware): 사용자의 시스템에서 정보를 수집하거나 광고를 표시하는 프로그램으로, 트로이 목마의 한 변형입니다.
- 버퍼 오버플로우 (Buffer overflow): 스택에 저장된 반환 주소를 덮어쓰는 등 취약점을 이용하여 악성 코드를 실행시키는 공격입니다.
- 바이러스 (Viruses): 실행 가능한 코드를 감염시켜 확산되는 프로그램입니다. 매크로 바이러스와 소스 코드 바이러스 등이 있습니다. Sobig.F와 같은 예시는 이메일을 통해 자체적으로 확산되는 웜 바이러스입니다.
- 비대칭 암호화 (Asymmetric encryption): 공개 키와 개인 키 쌍을 사용하여 통신을 보호합니다. RSA 알고리즘이 예시로 설명됩니다.
- 침입 감지 (Intrusion detection): 침입 시도를 감지하려 하지만, 높은 오탐률(false-alarm rate)은 문제가 될 수 있습니다.
분산 시스템 (Distributed Systems)
- 프로세스 마이그레이션 (Process migration): 프로세스를 한 시스템에서 다른 시스템으로 이동시키는 것입니다.
- 사이트 간 통신: 메시지 기반 통신 방식을 사용하며, 링크 실패와 사이트 실패를 구분하는 것은 어려울 수 있습니다.
이러한 개념과 구조는 운영체제가 하드웨어 리소스를 관리하고, 애플리케이션 실행을 지원하며, 사용자에게 서비스를 제공하는 방식의 기초를 이룹니다.
레퍼런스
Abraham-Silberschatz-Operating-System-Concepts
'CS > OS' 카테고리의 다른 글
[OS] 동기와 비동기, 블록과 논블럭의 차이 (0) | 2023.10.16 |
---|
댓글