OS - "운영체제 아주 쉬운 세 가지 이야기" 정리노트 - 2. 가상화 - 3. 제한적 직접 실행 원리 - 1

송준섭 Junseop Song·2023년 8월 7일
0

운영체제

목록 보기
4/5
post-thumbnail

⛳️ 목적

이 글은 "운영체제 아주 쉬운 세 가지 이야기" 책을 읽고 공부한 내용들을 두고두고 보기 위해 정리하는 글이다.
읽을 때마다 그 날의 내용들을 꾸준히 이어서 업데이트 할 예정이다.


🗓️ 2023.08.07 작성 ▽

2. 가상화

운영체제의 세 주제중 첫 번째인 가상화

2-3. 제한적 직접 실행 원리

CPU를 가상화하기 위해서 운영체제는 여러 작업들이 동시에 실행되는 것처럼 보이도록 물리적인 CPU를 공유
한 프로세스를 잠시 동안 실행하고 다른 프로세스를 또 잠깐 실행하고, 이런 식으로 계속해서 잠깐씩 실행시키는 방식
-> CPU 시간을 나누어 씀으로써 가상화 구현

위와 같은 가상화 기법의 문제점

  • 성능 저하
    시스템에 과중한 오버헤드를 주지 않으면서 가상화를 구현하기는 어려움
  • 제어 문제
    CPU에 대한 통제를 유지하면서 프로세스를 효율적으로 실행시키는 방법은 어려움
    제어권을 상실하면 한 프로세스가 영원히 실행을 꼐속할 수 있고 컴퓨터를 장악하거나 접근해서는 안되는 정보에 접근할 수 있음

-> 제어권을 유지하면서 성능 저하가 없도록 하는 것이 운영체제를 구축하는 데 핵심적인 도전 과제

❓ 핵심 질문: 제어를 유지하면서 효과적으로 CPU를 가상화하는 방법

  • 운영체제가 효율적인 방식으로 CPU를 가상화하면서 시스템에 대한 제어를 잃지 않으려면 하드웨어와 운영체제의 지원이 필수적임
  • 운영체제는 작업을 효과적으로 수행하기 위해 하드웨어가 제공하는 기능을 신중하게 사용

📌  기본 원리: 제한적 직접 실행

운영체제 개발자들이 프로그램을 빠르게 실행하기 위하여 개발한 기법

"직접 실행"
프로그램을 CPU 상에서 그냥 직접 실행시키는 것
운영체제가 프로그램을 실행하기 시작할 때 프로세스 목록에 해당 프로세스 항목을 만들고 메모리를 할당하며 프로그램 코드를 디스크에서 탑재하고 진입점(예를 들어 main() 루틴)을 찾아 그 지점으로 분기하여 사용자 코드 실행

// 직접 실행 방식(제한 없음)

        운영체제                      프로그램
---------------------------------------------------
1. 프로세스 목록의 항목을 생성
2. 프로그램 메모리 할당
3. 메모리에 프로그램 탑재
4. argc/argv를 위한 스택 셋업
5. 레지스터 내용 삭제
6. call main() 실행
7.                                main() 실행
8.                            main에서 return 명령어 실행
9. 프로세스 메모리 반환
10. 프로세스 목록에서 항목 제거

위는 아무 제한이 없는 기본적인 직접 실행 방식의 예시
프로그램의 main()으로 분기하고 커널로 되돌아 가기 위해 일반적인 호출과 리턴을 사용
그러나 이 접근법은 CPU를 가상화함에 있어 몇 가지 문제점 발생

  1. 프로그램을 직접 실행시킨다면 프로그램이 운영체제가 원치 않는 일을 하지 않는다는 것을 어떻게 보장할 수 있는가?
  2. 프로세스 실행 시 운영체제는 어떻게 프로그램의 실행을 중단하고 다른 프로세스로 전환시킬 수 있는가? (CPU 가상화에 필요한 시분할(time sharing) 기법을 어떻게 구현할 수 있는가?)

위와 같은 질문에 답하면서 기법을 반전시킬 수 있음
그러한 과정에서 "제한적"이라는 이름이 비롯됨
프로그램에 제한을 두지 않으면 운영체제는 제어가 불가능하고 단순한 라이브러리 역할만 할 뿐


📌  문제점 1: 제한된 연산

직접 실행의 장점은 프로그램이 하드웨어 CPU에서 실행되기 때문에 빠르게 실행된다는 것
그러나 만약 프로세스가 특수한 종류(디스크 입출력, 메모리 추가 할당)의 연산을 수행하길 원한다면 문제가 발생

프로세스가 원하는 대로 할 수 있게 방치하는 방안이 있지만 이것은 바람직한 시스템 구축의 방해 요인
파일에 대한 접근을 허용하기 전에 접근 권한을 검사하는 파일 시스템을 구현한다고 가정했을 때, 프로세스가 디스크에 대하여 입출력하는 것을 제한하지 않으면 프로세스는 전체 디스크를 읽고 쓸 수 있기 때문에 접근 권한을 검사하는 기능이 의미가 없음

❓ 핵심 질문: 제한 연산을 수행하는 방법

  • 프로세스는 입출력 연산을 비록한 다른 제한된 연산을 수행해야 함
  • 그러나 프로세스는 시스템에 대한 권한이 없기 때문에 제한된 연산을 수행할 수 없음
  • 이 일을 위해 운영체제와 하드웨어가 할 일은?

⭕ 팁: 보호된 제어 양도

  • 하드웨어는 두 가지 싱행 모드를 제공하여 운영체제를 도움
    • 사용자 모드: 응용 프로그램의 하드웨어 자원에 대한 접근 권한이 일부 제한
    • 커널 모드: 컴퓨터의 모든 자원에 대한 접근 권한을 가짐
  • 운영체제가 하드웨어에게 커널 모드로 진입하기 위한 메모리 주소를 알려주는 명령어도 함께 제공

🗓️ 2023.08.08 작성 ▽

접근 권한을 검사하는 기능이 의미가 없어진 상황때문에 사용자 모드라고 알려진 새로운 모드가 도입됨
사용자 모드에서 실행되는 코드는 할 수 있는 일이 제한됨
프로세스가 입출력 요청을 제한하는 등 만약 제한된 일을 요청하면 프로세서가 예외를 발생시키고, 운영체제는 해당 프로세스를 제거함

커널 모드는 사용자 모드와 대비되는 모드로서 운영체제의 중요한 코드들이 실행됨
모든 특수한 명령어를 포함하여 원하는 모든 작업을 수행 가능함

그렇다면 사용자 프로세스가 디스크를 읽기와 같은 특권 명령어를 실행해야 할 때는 어떻게 해야 할까?
이러한 제한된 작업의 실행을 허용하기 위하여 거의 모든 현대 하드웨어는 사용자 프로세스에게 시스템 콜을 제공함
대부분의 운영체제는 수백 개의 시스템 콜을 제공

시스템 콜을 실행하기 위해 프로그램은 trap 특수 명령어를 실행해야 함
이 명령어는 커널 안으로 분기하는 동시에 특권 수준을 커널 모드로 상향 조정함
이때 운영체제는 모든 명령어를 실행할 수 있고 완료되면 return-from-trap 특수 명령어를 호출함
이 명령어는 특권 수준을 사용자 모드로 다시 하향 조정되면서 호출한 사용자 프로그램으로 리턴함

하드웨어는 trap 명령어를 사용할 때 호출한 프로세스의 필요한 레지스터들을 저장함
운영체제가 return-from-trap 명령어 실행 시 사용자 프로세스로 제대로 리턴할 수 있도록 하기 위해서임
ex) x86에서는 프로그램 카운터, 플래그와 다른 몇 개의 레지스터를 각 프로세스의 커널 스택에 저장, return-from-trap 명령어가 이 값들을 스택에서 pop 하여 사용자 모든 프로그램의 실행을 다시 시작

trap 명령어가 운영체제 코드의 어디를 실행할지 어떻게 알까?
호출한 프로세서는 안정성 등의 이유로 분기할 커널 내부의 주소를 알지 못함
커널이 임의의 코드를 실행하기 전에 접근 권한 검사를 진행해야 함
이를 통해 커널은 trap 발생 시 어떤 코드를 실행할지 신중히 통제

커널은 부팅 시 트랩 테이블을 만들고 이를 이용하여 시스템을 통제함
컴퓨터 부팅은 커널 모드에서 동작하여 하드웨어를 원하는 대로 제어 가능함
운영체제의 초기 작업 중 하나가 하드웨어에게 예외 사건이 일어났을 때 어떤 코드를 실행해야 하는지 알려주는 것
이때 운영체제는 특정 명령어를 사용하여 하드웨어에게 트랩 핸들러의 위치를 알려줌
하드웨어는 이 정보를 전달받아 해당 위치를 기억하고 시스템 콜과 같은 예외적인 사건이 발생했을 때 무엇을 해야 할지 알 수 있음

모든 시스템 콜은 자신의 고유 번호를 가짐
사용자 프로그램은 해당 시스템 콜 번호를 레지스터 또는 스택의 지정된 위치에 저장
원할 때 시스템 콜을 호출하면 trap 명령어가 호출되고 그 후 trap 핸들러가 실행

trap 핸들러는 운영체제의 일부분
운영체제는 시스템 콜 번호를 읽어 사용자가 명시한 시스템 콜 번호가 유효한 시스템 콜에 해당하는지를 먼저 파악
유효하다면 운영체제의 해당 코드로 이동하여 실행

각 시스템 콜의 코드 위치는 운영체제만 알고 있음
만약 사용자 프로그램이 시스템 콜의 위치를 알고 있다면 그 위치로 이동하여 직접 실행할 수도 있음
커널 코드의 무분별한 실행을 방지하기위한 일종의 보안기법

하드웨어에게 트랩 테이블의 위치를 알려주는 것은 매우 강력한 기능이며 특권 명령어임
사용자 모드에서 이 명령어를 실행하려고 하면 실행할 수 없고, 에러 발생

// 제한적 직접 실행 방식

    운영체제 @부트(커널 모드)               하드웨어
------------------------------------------------------------
1. 트랩 테이블을 초기화함
2.                           syscall 핸들러의 주소를 기억함


    운영체제 @실행(커널 모드)               하드웨어              프로그램(사용자 모드)
------------------------------------------------------------------------------------
1. 프로세스 목록에 항목을 추가
2. 프로그램을 위한 메모리 할당
3. 메모리에 프로그램 탑재
4. argc/argv를 위한 스택 셋업
5. 레지스터와 PC(프로그램 카운터)를 커널 스택에 저장
6. return-from-trap
7.                              커널 스택으로부터 레지스터 복원
8.                                  사용자 모드로 이동
9.                                    main으로 분기
10.                                                              main() 실행
                                                              
                                                              --프로그램 실행--
                                                              
11.                                                             시스템 콜 호출
12.                                                            운영체제로 trap
13.                               레지스터를 커널 스택에 저장
14.                                  커널 모드로 이동
15.                                 트랩 핸들러로 분기
16. 트랩을 처리
17. syscall의 임무 수행
18. return-from-trap
19.                             커널 스택으로부터 레지스터 복원
20.                                  사용자 모드로 이동
21.                                 트랩 이후의 PC로 분기

                                                              --프로그램 실행--
                   
22.                                                             main에서 리턴
23.                                                         trap(exit()을 통하여)
24. 프로세스의 메모리 반환
25. 프로세스 목록에서 제거

위는 제한적 직접 실행 방식의 메커니즘을 요약해서 나타낸 것
프로세스는 커널 스택을 각자 가지고 있음
커널 스택은 커널 모드로 진입하거나 진출할 때 하드웨어에 의해 프로그램 카운터와 범용 레지스터 등의 레지스터가 저장되고 복원되는 용도로 사용

제한적 직접 실행 방식(LDE)은 두 단계로 진행
전반부(부팅 시)에서 커널은 트랩 테이블을 초기화하고 CPU는 나중에 사용하기 위하여 테이블의 위치를 기억
커널은 이러한 작업을 커널 모드에서만 사용할 수 있는 명령어(trap, return-from-trap)를 이용하여 수행

후반부(프로세스 실행)에서 return-from-trap을 이용하여 사용자 프로세스를 시작할 때 몇 가지 작업을 수행
새로운 프로세스를 위한 노드를 할당하여 프로세스를 리스트에 삽입하고, 메모리를 할당하는 등의 작업
return-from-trap 명령어가 CPU를 사용자 모드로 전환하고 프로세스 실행을 시작
프로세스가 시스템 콜을 호출하면 운영체제로 다시 트랩
운영체제는 시스템 콜을 처리하고 다시 return-from-trap으로 제어권을 프로세스에게 넘김
이 후 프로세스는 할 일을 다 하면 main()에서 리턴
이때 일반적으로 스텁 코드로 리턴하고 스텁 코드가 프로그램을 종료시킴
종료시 exit() 시스템 콜을 호출하고 다시 운영체제로 트랩됨
운영체제는 정리 작업을 하게 되고 모든 일이 완료됨

0개의 댓글