2022. 3. 8. 22:05ㆍMajor`/운영체제
OS는 "자원 관리자"이자 "제어 프로그램"이다
결국 컴퓨터 시스템의 하드웨어 자원들을 관리해주고, 시스템에 에러를 일으키거나 부적절하게 사용되는 부분을 제어해준다
컴퓨터의 전원 ON 과정
1단계
전원을 키고 파워가 공급되면 메인보드에 부착된 장치들에게 전력 공급 (CPU / ROM / RAM / 디스크)
2단계
ROM에 저장된 펌웨어인 BIOS가 동작한다
→ BIOS는 "POST"라는 검사 과정을 수행해준다
- 메모리에 bad sector는 없는지
- 키보드, 마우스 등 I/O 장치들이 잘 꽂혀져 있는지
- 하드디스크에 문제는 없는지
>> 컴퓨터가 동작하기 위한 "주변 장치"들에 문제가 없는지 확인하고 문제가 있으면 시스템은 종료된다
3단계
POST 과정에 문제가 없다면 BIOS는 "부트스트랩 프로그램"을 실행해서 부트 로더를 메모리로 읽어 온다
4단계
부트로더는 디스크에서 OS kernel의 위치를 찾아내서 메인 메모리로 적재해준다
- 메인 메모리에 올라온 kernel은 하드웨어를 초기화 시켜준다
- 이 시점 이후부터 시스템의 모든 제어권은 kernel이 가지게 된다
5단계
OS는 첫 번째 프로세스 (데몬)을 실행시켜주고 이 이후부터는 event가 발생할때까지 기다린다
프로그램 실행 단계
1. 프로그램 로드 (반드시 메인 메모리에 적재)
2. 명령어 인출
3. 명령어 해독
4. 명령어로부터 operand 인출
5. 명령어 실행
6. 결과 저장
--> 2 ~ 6까지 계속 반복한다
- but, 메인 메모리에 모든 프로그램을 로드하기에는 메인 메모리의 size가 감당하지 못한다
- 그리고, 메인 메모리는 휘발성이므로, 파워를 끄면 내용이 전부 사라진다
>> 따라서, 메인 메모리를 보조할 수 있는 Extention이 필요하다
- 보조저장장치 : SSD / HHD / 디스크 / 자기테이프
I/O 구조
- I/O장치와 CPU는 동시에 작업이 가능하다
- 그러나 I/O장치는 반드시 각 I/O장치별로 device controller가 존재한다
- 장치제어기 내부에는 여러가지 레지스터가 존재하고, 각 레지스터들을 목적에 맞게 설정해서 I/O장치에 액세스한다
- 장치제어기는 OS 내부에 존재하는 "장치 드라이버"가 control한다
- 장치제어기는 "local buffer"를 보유하고 있다
CPU → I/O장치
1. CPU는 I/O장치에 직접 Data를 쓰는게 아니라, I/O장치의 장치 제어기 내부에 있는 "local buffer"에 Data 작성
2. 그러면 "local buffer"에 저장된 Data들을 device controller가 직접 I/O장치에 쓰게 된다
I/O장치 → CPU
1. I/O가 전달하려는 Data를 device controller가 "local buffer"에 저장
2. 그러면 장치 제어기의 "local buffer"에 존재하는 Data를 CPU/메모리로 전달된다
Interrupt
Interrupt가 발생하면 controll이 ISR로 넘어가게 된다
- 마우스가 눌렸다면 "마우스 눌림"을 처리할 수 있는 ISR로 controll이 넘어가게 된다
- 해당 ISR의 주소는 "interrupt vector table"에 존재한다
1) Polling
- CPU는 주변 I/O장치들의 변화를 주기적으로 감시한다
- CPU가 주기적으로 I/O 장치들을 감시 → I/O 장치들이 CPU를 오래 점유하기 때문에 CPU의 효율이 저하된다
- I/O를 수행할 필요가 없는 시간에도 감시하기 때문에 쓸데없이 시간/효율을 낭비한다
- Device가 매우 많으면 CPU가 하나하나 contact해야 하기 때문에 시간이 굉장히 오래걸려서 효율이 떨어진다
Ex)
CPU는 1초에 매우 많은 연산을 할 수 있다. 그러나 User는 1초에 아무리 빠르게 키보드나 마우스를 클릭해도 CPU의 1초동안 연산횟수만큼 절대 누르지 못한다. 이런 상황에서 CPU가 I/O장치들을 주기적으로 감시하면 본인의 능력을 최대한으로 발휘하지 못하고 I/O를 감시하는 일에 본인의 능력이 많이 사용하게 된다
- context switch overhead가 없어서 response는 굉장히 빠르지만 CPU 주기를 낭비하는 단점이 존재한다
2) Vectored Interrupt System
Interrupt가 발생하게 된다면 해당 interrupt를 처리하는 ISR이 interrupt vector table에 존재하게 된다
- CPU 입장에서는 "Polling" 방식은 매 주기마다 I/O장치들을 감시하느라 본인의 능력을 발휘하지 못한다
- "Vectored Interrupt System"방식은 요청이 들어오면 그때 ISR을 수행해주면 되기 때문에 본인의 능력을 많이 발휘할 수 있다
- 인터럽트 요청이 들어오면 CPU는 현재 실행중이던 동작을 중지하고 ISR로 이동하고, ISR이 종료되면 다시 원래 실행중이던 동작을 수행한다
1. 실행중이던 작업을 중단하고 ISR을 수행
- 이 때, 실행중이던 작업의 상태 (PC, SR)을 반드시 저장해야 한다 (SP에 해당 명령어 주소 저장)
- 왜냐면 ISR을 끝내고 중단되었던 작업의 마지막 주소로 돌아가서 다시 수행해야 하기 때문이다
2. 인터럽트를 처리하기 위해서 인터럽트 벡터 테이블을 참조해서 ISR 주소값을 얻는다
- 인터럽트가 들어오면 인터럽트 벡터 테이블에서 해당 ISR 주소를 뒤져서 PC에 적재해서 수행해야 한다
- ISR는 인터럽트를 처리하기 위한 코드이고, 함수의 형태로 존재한다
3. ISR 실행
- PC에 ISR의 주소를 적재 -> 이 말은 다음 실행할 명령어의 주소가 ISR의 주소이다 -> 따라서, ISR이 실행
4. ISR이 끝나면 원래 작업으로 돌아가기 위해서 SP에 저장해두었던 주소를 다시 PC에 적재한다
5. PC의 주소를 통해서 중단되었던 작업을 다시 수행한다
우선순위가 굉장히 높은 인터럽트의 경우 중간에 다른 인터럽트가 끼어들면 안되니까 "interrupt disable"시키고 ISR을 수행해준다
하지만 "Reset" 같은 인터럽트는 disable이 불가능하다
▶ H/W 인터럽트
- CPU의 외부에서 인터럽트 요청을 해서 발생하는 인터럽트
- 하드웨어의 흐름에 의해 생기는 인터럽트이므로, 비 동기적 특성을 보유
- I/O 인터럽트 : I/O 작업에 대한 결과를 return하거나, I/O 작업도중 오류에 의해 정지되었을 때
- Power-Fail 인터럽트 : 전원이 이상현상에 의해서 공급이 중단되었을 때
- Machine-Check 인터럽트 : CPU의 기능이 잘못되었을 때
- External 인터럽트 : 외부 장치로부터 인터럽트가 오거나, 타이머에 종료되었을 때
▶ S/W 인터럽트 (trap)
- CPU 내부에서 발생하는 인터럽트 & 잘못된 명령/데이터를 사용할 때 발생
- 프로그램 내부 잘못된 명령어에 의해서 발생하므로 동기적 특성을 보유
- 프로그램 검사 인터럽트 : 프로그램의 오류에 의해 발생 (0으로 나누기, 오버/언더 플로우, Exception, ...)
- System Call에 의해 발생
- System Call : user가 OS에게 특정 서비스를 받기 위해서 요청하는 것
- 다른 프로세스 문제 : 무한 루프 프로세스 / 허가되지 않은 메모리 접근
- 이런 문제들은 시스템에 굉장한 악영향을 줄 수 있다
- 부적절한 명령어에 대한 보호 : Dual-Mode Execution / Timer
Dual-Mode Execution
- 사용자 모드(1) / 커널 모드(0)로 나누어서 동작
- CPU 레지스터 내부에 "슈퍼바이저 플래그(Mode Bit)"라는 것이 존재해서 이 플래그를 통해서 user? Kerner?을 구분한다
- 커널 모드일 때만 실행가능한 명령어들이 존재하는데 이런 명령어들을 "특권 명령"이라고 하고 이 "특권 명령"은 사용자 모드에서 절대로 실행할 수 없고, 반드시 커널 모드에서만 실행 가능하다
ex)
타이머 설정 : 커널 모드
시간 읽기 : 사용자 모드
인터럽트 turn off : 커널 모드
I/O장치 액세스 : 커널 모드
trap 명령어 발생 : 사용자 모드
- "trap 명령어가 발생"했다는것은 "소프트웨어 인터럽트"가 발생했다는 것이다
- 이 중 대표적으로 System Call이 있는데 System Call은 User가 OS의 특정 서비스를 받으려고 요청하는 것이기 때문에 사용자 모드에서 발생된다
- 물론 System Call은 user 모드를 커널 모드로 변경시켜주고, System Call로부터 복귀하면 다시 user 모드가 된다
Timer
- 프로세스에게 타이머(CPU 할당 시간)을 주어서 해당 시간내에 작업을 끝내지 못하면 OS가 즉시 Context-Switching을 통해서 다른 프로세스에게 CPU를 할당해준다 :: 무한루프 프로세스 방지
3) DMA : Direct Memory Access
Question) 대용량 데이터를 읽고 쓰는 데이터 장치
매 I/O 요청마다 CPU에 계속 인터럽트를 걸게된다. 만약에 1byte : 1 interrupt라고 가정하면 1Kbyte는 1K interrupt가 발생하게 된다. 이러면 굉장한 overhead가 발생하게 된다
- user program이 1K번이나 중단되었다가 재실행해야 하기 때문에 속도가 느려진다
>> 이를 해결하기 위해 "DMA" 방식을 활용 (대용량 데이터 전송에 더 좋다)
- CPU의 개입없이 (I/O장치, 메모리) 간에 직접 burk data를 한번에 전송하는 방식
- 이러면 CPU는 본인의 능력을 최대로 발휘할 수 있기 때문에 CPU의 효율이 극대화된다
- 그리고 프로그램 수행중 인터럽트의 발생 횟수를 최소화 시킬 수 있다
- 대신 CPU에게 "시스템 버스를 사용하겠다"라는 요청을 반드시 보내줘야 한다
- 만약 이 요청을 보내지 않으면 (I/O장치, 메모리) 간에 데이터를 전송하는 작업 도중, CPU가 시스템 버스를 사용하게 되면 전송하던 데이터에 손실이 발생할 수 있기 때문이다
1. I/O 장치가 DMA controller에게 DMA 서비스 요청
2. DMAC는 CPU에게 시스템 버스 요청 신호 보내기
3. CPU는 DMAC에게 시스템 버스 허락 신호 보내기
4. I/O 장치 ↔ 메모리 간의 데이터 전송을 DMAC가 직접 해준다
- 전송할 데이터 남아 있으면 계속 반복
5. 모든 데이터 전송이 끝나면 DMAC는 CPU에게 interrupt 신호 보내기
멀티-프로세서 시스템 구조
1) Symmetric Multiprocessing Architecture
- CPU가 여러개이고, 각 CPU가 하나의 메모리를 공유하는 구조
- CPU는 여러개고 메모리는 하나이므로 결국 "병목현상"이 발생하게 된다
2) Multi-Core Design
- 하나의 CPU내에 코어가 여러개 존재하고, 각 코어내에 캐시가 존재하는 구조
- 코어들끼리 공유하는 캐시도 존재한다
- 이러면 결국 하나의 CPU가 하나의 메모리에 접근하기 때문에 "병목현상"을 해결할 수 있다
3) NUMA : Non-Uniform Memory Access System
- 각 CPU는 독자적인 메모리를 보유
- 그리고 CPU는 다른 CPU의 메모리에도 액세스가 가능 :: CPU가 상호 연결되어 있다
- "병목현상" 해결
- 그러나 OS 구현이 굉장히 복잡해진다 :: Scheduling
운영체제 구조
1) "Batch System → 멀티프로그래밍"로의 발전
컴퓨터의 프로그램 흐름에 따라 순차적으로 프로그램을 처리하는 방식
System의 실행시간을 최대한 정확하게 예측 가능
하지만 하나의 작업이 끝나기 전에 다른 작업을 할 수 없다
>> 따라서 해당 프로세스가 I/O 작업을 요청한다면 그 작업이 완료되는 동안 CPU는 아무것도 하지 못한다
멀티프로그래밍 방식은 메인 메모리에 여러개의 프로그램을 동시에 올려서 CPU와 I/O를 항상 바쁘게 만들려고 하는 방식이다
만약 프로세스가 I/O를 요청한다면 바로 "context switch"에 의해서 CPU는 다른 프로세스에게 할당된다
2) TimeSharing (멀티태스킹)
- Multiprogramming과 취지는 동일하다 : 여러 프로세스를 메인 메모리에 올려서 CPU, I/O를 바쁘게 만들자
- 그러나 Multiprogramming의 경우 프로세스가 자발적으로 할당된 CPU를 반납하기 전에는, 다른 프로세스는 절대로 CPU를 사용하지 못한다
- TimeSharing의 경우 이 문제를 해결하기 위해서 각 프로세스별로 CPU 시간을 할당해준다 (고정적 Time Quantum)
- 어느 프로세스의 할당된 CPU 시간이 종료되면 즉시 Context Switching을 통해서 다른 프로세스를 시작한다
- 대화형 시스템에 굉장히 유리한 OS 구조이다
▶ Job Scheduling
- "디스크에 존재하는 여러 프로그램들 중에서 어느 프로그램을 메인 메모리로 올려줄까"에 대한 스케줄링
- 여러 프로그램들은 "Job Queue"에서 대기하다가 Job Scheduling에 의해서 메인 메모리에 올라간다
▶ CPU Scheduling
- "메인 메모리에 존재하는 여러 프로세스들 중 어떤 프로세스에게 CPU를 할당해줄까"에 대한 스케줄링
- CPU의 할당을 받길 원하는 "Ready Queue"에 존재하는 프로세스들 중 CPU를 할당해줄 프로세스를 결정
▶ Swapping
- 어떤 프로세스를 메인 메모리에 올리려고 하는데, 현재 메인 메모리의 허용가능 범위를 벗어날 경우, 메인 메모리에서 일을 안하는 프로세스를 잠시 보조저장장치로 쫓아내서 공간을 확보 : Swap Out
- 어느정도 시간이 경과하고, 쫓아낸 프로세스로부터 Request가 오면, 해당 프로세스를 다시 실행시켜줘야 하기 때문에 해당 프로세스를 다시 메인 메모리로 올려서 실행 : Swap In
- Swap Out, Swap In할 때마다 Context-Switching이 발생하기 때문에 Overhead가 많아진다
- 하지만, 부족한 메모리에 대해서 Swapping을 통해서 많은 프로세스를 실행할 수 있다
▶ 가상 메모리
- 물리적 메모리 크기의 한계를 극복하기 위해 나온 기술
- 물리적 메모리 크기보다 더 큰 프로세스를 수행하기 위해서 가상 메모리를 사용한다
How?
"필요한 부분만 메모리에 적재? = Demanding Paging