가상화

코승호딩·2022년 10월 17일
0

운영체제

목록 보기
2/10
post-thumbnail

앞장에서 운영체제는 여러 개의 가상의 CPU가 존재한다는 환상을 제공한다고 설명하였다.
대표적인 가상화 방법으로 시분할(Time Sharing)기법이 있다.
시분할은 하나의 프로세스를 실행하고 멈추고, 다른 프로세스를 실행하는 작업을 반복하며 마치 여러 개의 CPU가 존재하는 것처럼 보이게 한다.
따라서 원하는 수 만큼의 프로세스를 동시에 실행할 수 있지만 CPU를 공유하기 때문에 각 프로세스의 성능이 낮아지고 느려진다.


📌프로세스

프로세스는 실행중인 프로그램이다.
구성 요소는 다음과 같다.
메모리(주소 공간) : 명령어, 데이터
레지스터 : 프로그램 카운터(PC), 스택 포인터(SP), 일반 레지스터
CPU는 메모리에 있는 데이터만 사용이 가능하다. 따라서 하드디스크와 같은 저장 공간의 메모리를 가져와야 한다.
그러나 메모리만 존재한다면 아무일도 일어나지 않으며 CPU가 명령어를 꺼내어 데이터를 읽어 레지스터에 넣어야 실행이 된다.


프로세스 생성

  • .exe파일, 즉 프로그램 파일을 메모리에 탑재(load)한다.
    loading : 디스크에 있는 프로그램을 꺼내서 프로세스의 주소 공간에 복사한다.
    프로세스 주소 공간에 탑재한다.
    프로그램 파일 자체가 실행 가능한 포맷으로 되어 있다.
    게으른 적재 : 로딩할 때 등록만 해놓고 실제 디스크에서 읽는 것은 CPU가 그 데이터를 필요로 할 때 수행한다.
    -> 실제 사용하는 데이터만 읽고 사용하지 않는 데이터는 읽지 않는다.
  • 실행 스택(run-time stack)할당
    지역 변수, 함수 매개변수, 복귀 주소 저장용 공간을 할당
    main()의 argc와 argv를 스택에 저장한다.
  • 힙(heap)할당
    malloc이나 new가 사용하는 메모리 할당 공간을 확보한다.
  • 여러가지 초기화
    input/output초기화 : stdin, stdout, stderr 파일 설정
  • main()으로 jump
    실행의 시작

요약) 실행 가능한 포맷으로 작성, 로딩 -> 지역변수, 함수 매개변수, 복귀주소 등의 스택 할당 -> 힙 할당 -> 초기화 -> main으로 jump


📌프로세스의 상태

실행(Running) : CPU가 실행 중
준비(Ready, 실행 대기) : 실행할 준비가 다 되어 있음. CPU 사용 차례를 기다리고 있음
대기(Blocked) : 다른 사건이 발생하기를 기다리고 있음, 사건) 입출력 완료, 메시지 도착, 약속 시간, ex)scanf, printf


📌자료구조

PCB(Process Control Block)

  • 운영체제가 프로세스를 제어하기 위해 정보를 저장해 놓은 곳으로, 프로세스의 상태, 정보를 저장하는 구조체이다.
  • 프로세스 생성시 만들어지며 마음대로 읽고 쓰기 불가능하다.
  • 쉽게 말해 운영체제가 프로세스에 대한 중요한 정보를 저정해 놓을 수 있는 저장 장소를 뜻한다.

운영체제가 관리하는 핵심 자료구조들

  • Process 리스트 : Ready processes, Blocked processes, Current running process
  • Register 문맥(context) : 현재 프로세스의 실행 상태를 정의

현실에서 프로세스는 운영체제가 만든다.
어떠한 프로세스가 요청했기 때문에 프로세스를 만든다.
최초의 프로세스는 운영체제가 알아서 만든다.
최초의 프로세스는 Windows : winload.exe, Linux : init
시스템 호출을 통해 프로세스 생성을 요청한다. Windows : CreateProcess, Linux : fork, exec, clone


📌제한적 직접 실행 원리

앞서 설명했듯이 운영체제는 실제 CPU를 시분할(Time Sharing)방식으로 나눠 사용한다.
그러나 가상화 기법을 구현하는데 몇가지 문제가 있는데
우선 성능 저하이며 이를 해결하기 위해 시스템에 큰 오버헤드를 주지 않으며 가상화를 구현하는 방식이다.
두번 째는 제어 문제로 CPU에 대한 통제를 유지하며 프로세스를 효율적으로 실행할 수 있는 방법이다.
제어권을 상실한 프로세스가 영원히 실행을 계속하며 컴퓨터를 장악하고, 접근하면 안되는 정보에 접근하도록 두면 안된다.
따라서 제어권을 유지하며 성능저하가 없도록 운영체제를 구축해야 하는 것이 핵심적 과제이다.

운영체제 엔지니어들은 프로그램을 빠르게 실행하기 위해 제한적 직접 실행이라는 기법을 개발하였다.
제한적 직접 실행은 프로그램을 CPU상에서 그냥 직접 실행시키는 것이다.


운영체제는 프로그램을 실행하기 시작할 때, 프로세스 목록에 해당 프로세스 항목을 만들고, 프로그램 메모리를 할당하며, 디스크에서 프로그램을 꺼내어 탑재하고, 초기화하며 main으로 점프하여 사용자 코드를 실행한다.
! 그러나 이러한 방법은 몇 가지 문제가 있다.
프로그램은 제한 없이 뭐든지 할 수 있어 디스크의 파일을 지우거나 컴퓨터를 태울 수 있다.
OS는 프로그램 실행을 그저 바라보기만 해야 하고, "일반 라이브러리"처럼 호출 당해야 한다.
프로그램을 제어하지 못하는 운영체제는 그저 단순한 라이브러리에 불과하다.
다음으로 이러한 문제점에 대하여 자세히 알아보자


📌제한된 연산

제한된 연산

만약 프로세스가 다음과 같은 하면 안되는 동작을 한다면?

  • 디스크에 임의의 위치에 I/O 명령 내리기
  • CPU나 메모리를 직접 조작해서 더 많은 자원을 얻어내기

만약 프로세스가 원하는 대로 해준다면 프로세스는 전체 디스크를 읽고 쓸 수 있어 접근 권한을 검사하는 파일 시스템은 아무런 의미가 없게 된다.
따라서 사용자 모드(User mode), 커널 모드(Kernel mode)가 도입되었다.

  • 사용자 모드 : 하드 웨어 자원에 대한 접근을 제한한다. 프로세스가 사용자 모드라면 입출력 요청을 할 수 없도록 설정, 모든 프로세스에 적용
  • 커널 모드 : 기계의 모든 자원을 조작할 수 있으며 운영체제가 사용한다.

이러한 방식으로 우리는 디스크 입출력과 같은 명령을 실행할 때, 시스템 콜을 사용하여 파일 시스템에 접근, 프로세스 생성 및 제거 등을 한다.
따라서 시스템 콜을 통해 사용자 모드의 문제점을 해결할 수 있다.

시스템 콜

  • 안전하게 커널 모드로 바꾸는 것
  • 커널을 함부로 조작하는 것을 막는 것
  • 주의 깊게 열어 놓은 특정 기능만 수행할 수 있는 핵심 통로이다. -> Public 매소드를 사용
    ex) 프로세스 생성/제거, 다른 프로세스와의 통신, 메모리 추가 할당

Trap
Trap은 시스템 콜을 실행하기 위한 특수 명령어이다. 트랩 테이블에 정해진 주소 안으로 jump할 수 있고 동시에 커널 모드로 상향 조정한다.
커널 모드로 진입하게 되면 운영체제는 모든 명령어를 실행할 수 있고 이를 통해 프로세스가 요청한 작업을 처리할 수 있다.

Return-from-trap
Return-from-trap명령어는 특권 수준을 다시 사용자 모드로 하향 조정하며 호출한 사용자 프로그램으로 되돌아 간다.



다음 그림은 제한적 직접 실행을 요약하여 나타낸 것이다.

  • 부팅시 커널 모드에서 트랩 테이블에 적절한 값을 기록한다. 하드웨어에서는 메모리에 트랩 테이블이 저장된다.
  • 프로그램 실행 시 커널 모드에서 프로세스 목록을 추가, 프로그램을 위한 메모리 할당, 프로그램 메모리에 로딩, argv를 사용자 스택에 저장, 레지스터와 PC의 값을 커널 스택에 저장한 후 return-from-trap을 한다. 이 후 하드웨어서는 레지스터를 커널 스택에 저장하고 사용자 모드로 전환하여 main으로 이동 후 main 함수가 끝나면 리턴하여 또 운영체제로 Trap한다.
  • main()에서 시스템 콜을 하게 되면 다시 운영체제로 Trap을 하게 되고 하드웨어에서는 레지스터 값을 커널 스택에 저장하고 커널 모드로 전환 후 트랩 핸들러로 분기한다. 커널 모드에서 트랩을 처리하고 끝나면 return-from-trap을 하고, 다시 하드웨어에서 커널 스택에서 레지스터 값을 로드하고, 사용자 모드로 전환 후 main으로 이동하여 main()이 끝나면 운영체제로 트랩하고 exit()를 호출하여 프로세스의 메모리를 반환 후 목록에서 제거한다.

프로세스간 전환

두 번째 문제점은 프로세스간 전환이다. 프로세스간 전환은 간단해야 하고, 운영체제는 실행 중인 프로세스를 계속 실행할지, 멈추고 다른 프로세스를 실행할 것인지 결정해야 한다. 그러나 직접 실행으로 CPU에서 프로세스가 실행중이라는 것은 운영체제는 실행중이지 않다는 것을 의미한다. 그렇다면 어떻게 CPU를 운영체제가 다시 획득하여 프로세스를 전환할 수 있을까
여기에는 두 가지 방식이 있는데 첫 번째로 협조 방식이다.

  • 협조 방식(시스템 콜 대기) : 프로세스들이 자주 시스템 콜을 해서 CPU를 양보한다. ex)yield 또는 sleep
    이러한 시스템 콜이 아니더라도 오류(Divide by zero, 접근 불가 메모리 억세스)가 발생하면 운영체제로의 Trap이 일어난다.
    운영체제가 이어서 실행할 프로세스를 선택한다
    이러한 방식은 매우 수동적이기 때문에 다음과 같은 무한 루프 상황에서 CPU의 제어권을 획득할 수 없다.
    -> 프로세스가 무한루프에 빠지면 컴퓨터를 끄고 켜야만 한다.

위 무한루프와 같은 경우 컴퓨터를 재부팅 할 수 밖에 없다. 그러면 어떻게 CPU 제어권을 다시 운영체제가 획득할 수 있을까

  • 비협조 방식(운영체제 전권행사) : 타이머 인터럽트로 구현을 하면 부팅 시 운영체제는 타이머를 키고 타이머 장치는 몇 백분의 1초마다 인터럽트를 발생시킨다. 인터럽트가 발생하게 되면 시스템 호출과 똑같이 하드웨어가 자동으로 레지스터 값을 저장하고 커널모드로 바꾼 후 OS가 등록해 놓은 주소로 이동한다. 따라서 타이머 인터럽트는 일정한 시간 간격으로 OS가 다시 CPU를 차지하도록 해준다.

📌Context 저장과 복원

시스템 콜을 협조로 하든, 타이머 인터럽트를 통해 강제로 하든, 운영체제가 제어권을 다시 획득하면 현재 실행중인 프로세스를 계속 실행할 것인지, 다른 프로세스로 전환할 것인지를 결정해야 한다. 이 결정은 운영체제의 스케쥴러에 의해 결정된다.
다른 프로세스로 전환된다면 운영체제는 문맥 교환(Context switch)을 한다.

문맥 교환(Context Switch)

  • 현재 프로세스에서 사용 중인 레지스터를 PCB에 저장한다.
  • 실행할 프로세스의 PCB에서 레지스터를 로드한다.
  • 실행할 프로세스의 커널 스택으로 변경한다.
  • return-from-trap



profile
코딩 초보 승호입니다.

0개의 댓글