-> 블로그 이전

[OS Week_2] 3/15 정리

2022. 3. 15. 17:22Major`/운영체제

MS-DOS

- MS-DOS의 경우에 메인 메모리에 프로그램이 only 하나만 올라가게 된다

  • 이 경우, CPU의 효율이 굉장히 떨어진다
  • Why?? 프로세스가 I/O 요청을 받으면 interrupt가 발생하게 되고, 그에 따른 ISR을 수행하기 때문에 CPU는 idle status가 된다

- I/O 작업이 많아질수록 CPU의 idle time 역시 증가하게 된다
>> 이러한 문제 해결 : Multiprogramming


Multiprogramming

"CPU를 바쁘게 만들어라"

- 메인 메모리에 여러개의 프로그램을 올려서 I/O Interrupt가 발생하게 되면 즉시 "context switching"을 통해서 다른 프로세스에게 CPU를 할당해준다

  • 이렇게 구현되면 CPU는 idle status에 빠지지 않고 계속해서 일을 한다

- Multiprogramming은 "time quantum"이 굉장히 길다

  • I/O Interrupt를 통해서 "context switching"이 되면 원래 할당되었던 프로세스는 다시 CPU를 할당받으려면 오랜시간 기다려야 하기 때문에 CPU를 독점으로 사용한다고 인식하지 못한다

 

문제점

- 프로세스가 자진해서 CPU를 반납하거나 & 해당 프로세스에 대해서 I/O Interrupt가 발생하지 않는 이상 다른 프로세스들은 CPU의 할당을 받지 못한다 (강제로 "context switching"이 되지 않는다)

>> user와 interactive가 거의 없는 분야에서 사용

>> 이러한 문제점 해결 : TimeSharing


Time Sharing

- 각각의 프로세스에게 "고정적인 CPU 할당 시간"을 부여해준다 :: 굉장히 짧은 time quantum
- 주어진 CPU 할당 시간 내에 프로세스를 완료하거나 & 완료하지 못하거나 & I/O Interrupt가 발생하면 즉시 "context switching"을 통해서 강제로 CPU의 할당을 중지하고 다른 프로세스에게 CPU를 할당해준다
- time quantum이 굉장히 짧아서 빨리빨리 "context switching" 된다 :: 각 프로세스가 자신이 CPU를 독점적으로 사용한다고 느낄수 있다

  • CPU의 모든 성능을 최대한으로 활용하고 있다고 생각할 수 있다

>> interactive job들에게 굉장히 효율이 좋은 방식이다

  • I/O가 굉장히 많이 발생하고, 그에 따른 "context switching"이 자주 발생하기 때문에 time quantum이 빨라야 한다

 

고려사항

Job Scheduling

- 보조저장장치에서 메인 메모리로 어떤 프로세스를 올릴까?

CPU Scheduling

- 메인 메모리에 존재하는 여러 프로세스 중 어떤 프로세스에게 CPU를 할당해줄까?

Swapping

- Swap Out : 새로운 프로그램을 메인 메모리로 올리려고 하는데, 메인 메모리에 해당 프로그램을 허용할 만큼의 용량이 없을 경우, 메인 메모리에서 현재 필요없는 프로세스를 잠시 디스크로 빼주는 행위

  • 여기서 원래 프로세스에게 허용된 메모리 영역을 새로운 프로세스에게 할당해주기 위해서 많은 I/O가 발생

- Swap In : 아까 디스크로 빼준 프로세스를 다시 실행시키기 위해 메인 메모리로 다시 올리는 행위
>> Swap Out/Swap In간에 프로세스는 다시 동일한 메모리 영역에 올라간다는 보장은 없다

가상 메모리

- 메모리가 거의 무한이라고 생각할 수 있는 논리적 메모리
- 물리적 메모리를 가상적 메모리로 증가시켜주는 형태 :: Demand Paging

  • Swap Out/Swap In을 가상 메모리가 지원해준다

# OS is "Interrupt-Driven" #

H/W Interrupt

- CPU와 I/O사이의 Interrupt

S/W Interrupt

- Software가 발생시키는 Interrupt (=trap / =exception)

1) Software적 error 발생

  • DivideByZero
  • NPE
  • NFE
  • Overflow
  • Underflow
  • .....

2) user가 OS에게 어떠한 서비스를 요청

"System Call"
- user가 OS에게 'System Call'을 요청할 때는 "user mode"로 요청을 한다
- 'System Call'이 요청된 즉시 System Call이 user mode를 kernel mode로 변환해준다

 

※ 시스템에 악의적인 영향을 주는 프로세스가 실행될 경우?

  • 임의로 다른 프로세스에게 할당된 메모리 영역에 access
  • 임의로 H/W에 access
  • infinite loop?

>> 이러한 동작들은 프로세스가 직접 하는게 아니라 "반드시" OS를 통해서 수행되어야 한다

  • 이 때 발생하는 것이 "System Call"이다
  • 이렇게 잘못된 프로그램에 대해서 대처하는 보호장치 : 이중모드/Timer

이중 모드 : Dual-Mode

- 명령어의 모드를 2가지로 설정 :: user mode / kernel mode
- H/W의 mode bit명령어의 모드를 비교해서

  • 동일 : 그대로 실행
  • 다름 : 오류 발생(즉시 trap 발생) >> 해당 프로세스 즉시 kill

>> H/W적으로 현재 mode bit & 명령어의 모드를 지원해줘야 한다

  • 서로 다른 CPU간에는 여러개의 machine code를 동일하게 실행할 수 없다 -> H/W적으로 mode bit & 명령어 모드를 설계한 방식이 당연히 다르기 때문

 

Timer

- 각각의 프로세스에게 CPU 할당 시간을 설정해준다 (time quantum)
- Timer를 설정하는 mode는 반드시 "kernel mode"
- time quantum이 끝나면 해당 프로세스로부터 즉시 CPU를 반환해서 다른 프로세스에게 CPU를 할당해준다


System Call

- OS에 의해 제공되는 서비스에 대한 인터페이스를 프로그래밍한 것
- high-level language로 구현해야 한다 (C/C++)

※ 원래 System Call을 사용하려면 어셈블리어로 사용해야 한다

mov $0x6e, %eax
syscall

- 하지만 user들은 그냥 사용한다

  • System call은 (어셈블리어 - high level language)가 1:1로 Mapping되어 있다

 

※ 모든 System Call에는 반드시 Unique Num이 할당되어야 한다

- 해당 번호를 통해서 OS는 어떤 System Call이 호출되었는지 파악이 가능하다

  1. System Call 요청이 오면 trap이 발생하고
  2. System Call에 의해서 user mode가 kernel mode로 변경되고
  3. OS의 kernel은 System Call에 대한 Unique Num을 "System Call Table"에서 찾아낸다
  4. 그리고, 해당 번호에 맞는 ISR을 호출한다
    • System Call Table에는 System Call를 Unique Num별로 구분되어있다

System Call Table

 

※ System Call의 파라미터 (OS에게 파리미터를 전달하는 방식)

방식 1) 레지스터 사용

- 레지스터의 개수는 한정적이고, 각 레지스터별로 저장할 수 있는 byte에도 한계가 존재한다

>> 이를 해결 : memory block & stack 사용

 

방식 2) memory block & stack 사용

- 임의의 메모리 공간 or stack을 할당받고, 그 공간에 System Call의 파라미터들을 저장한다
- 그리고 해당 공간의 위치를 레지스터에 저장한다

※ System Call 구현

1. System Call 함수 정의 (커널 상에서 코딩을 하기 때문에 일반 코딩과 방식이 다르다)
2. 해당 System Call에 대한 Unique Num 할당
3. 만든 System Call을 "System Call Table"에 등록

  • "System Call Table"은 어셈블리어로 작성되어 있으므로, 정확히 번호에 맞게 mapping해야 한다

4. 커널을 rebuild 후 teset하기

- 원래 어떤 System Call 함수에 대한 System Call을 대신 호출해주는 Wrapper가 존재한다
- 그러나, 새로운 System Call을 생성하면 그 System Call을 호출해주는 또 다른 System Call을 통해서 실행해야 한다 (in Linux)

  • _syscall(~~) / syscallx(~~) / ....

OS 구조

1) MS-DOS OS

- 메모리에 프로그램이 only 하나만 올라감
- 최소한의 공간에서 최대한의 기능을 제공하도록 작성
- 각 layer가 잘 구분되지 않는다

2) UNIX

- 2가지 파트로 OS가 구성되어 있다 :: Kernel & Systems programs

▶ kernel

- H/W위에 존재하고, "System Call & H/W"에 대한 인터페이스 역할을 수행해준다

>> 1), 2)의 방식은 시스템의 한 부분을 변경하게 되면 다른 부분에 광범위한 영향을 줄 수 있으므로 "밀접하게 결합된 시스템"이라 불린다

3) Layered Approach

"느슨하게 결합된 시스템"
- OS가 여러개의 layer로 나누어지고 각각의 layer마다 기능이 서로 다르다
- 이 모든 구성요소들이 합쳐져서 kernel을 구성하게 된다
- 이 모듈의 장점은 한 구성요소의 변경이 해당 구성요소에만 영향을 미치고 다른 구성요소에는 영향을 미치지 않으므로 시스템의 내부 작동을 더 자유롭게 생성하고 변경할 수 있다

$ Bottom layer = H/W & Highest Layer = UI

장점

- 구성 & debuging이 굉장히 쉬워진다
- 오류에 대한 boundary가 layer별로 명확하다

단점

- 각 layer를 명확히 구분하기 힘들다

  • 만약 디스크에 접근하고싶다?
  • 디스크는 H/W이므로 최하위 layer에 존재한다
  • 따라서, 위에서부터 모든 layer를 순차적으로 읽어야 한다
  • 이 때, 위의 layer들은 밑의 layer를 호출하는 역할만 수행 :: 중간 layer는 의미없이 밑의 layer만 호출
  • 호출할때마다 stack에 할당해야 하고, 그에 따라서 메모리 사용이 효율적이지 않다

 

4) Microkernel System

- 핵심적인 기능을 제외한 나머지 기능들을 "user space"로 이동시킨 구조이다

장점

- Debuging이 쉬워지고 확장성이 좋아진다 :: 훨신 reliable한 구조이다
- 새로운 OS로 copy하기가 편리하다
- 훨씬 안전

단점

- 일부 기능을 "user space"로 보내기 때문에 이를 실행하려면 message passing방법을 써야 한다
- kernel이 직접 다루는 공간이 줄어듬에 따라서 overhead가 발생한다

5) Modules

- kernel의 기능을 "모듈 단위"로 나눠준다 (이것만 보면 'Layered Approach'와 비슷하다)
- 하지만, 다른 모듈을 직접적으로 호출이 가능하다는 점이 'Layered Approach'와의 차이점이다

  • 모듈 간의 communication에 message passing이 필요없다

- 훨씬 효율적으로 기능을 수행할 수 있다
- 기능들을 kernel에 넣었다가 뺏다가를 동적으로 수행할 수 있다 : "loadable kernel modules"

  • 기능들을 kernel에 넣었다가 빼는 과정에서 OS를 stop시킬 필요가 없고, root 권한을 보유한 user가 특정 기능을 kernel에 넣었다가 뺏다가 할 수 있다

- Device Driver가 대표적인 모듈이다

  • Device Driver는 자신에게 필요한 driver만 설치하면 되고, 필요가 없어졌을 경우 삭제하면 된다

- kernel이 모듈을 control하는 방식이 존재함에 따라서 특정 rule에 따라서 modules를 코딩해야 한다