들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제', ' 아브라함 실버샤츠 외 2명 저자인 운영체제(공룡책)' 를 참고로 작성되었습니다.

 

 

 프로세스(process)

 프로세스 디스크에 저장된 실행 파일에서 메모리에 적재되어 실행 중인 프로그램을 말한다. 

 프로세스는 메모리에 적재될 때 일반적으로 다음과 같이 배치된다.

 

  • 텍스트 - 실행 코드
  • 데이터 - 전역 변수(해당 영역은 초기화된 데이터와 초기화되지 않은 데이터 섹션으로 다시 나뉜다.)
  • 힙 - 동적으로 할당되는 메모리
  • 스택 - 임시 데이터 저장 장소(함수 매개변수, 복귀 주소 및 지역 변수)

 스택과 힙의 방향은 두 공간 모두 동적으로 줄어들었다 늘어났다 하기 때문에 주소의 겹침문제를 예방하기 위해 스택은 높은 주소에서 낮은 주소로, 힙은 낮은 주소에서 높은 주소로 할당된다.

 

 그럼 해당 프로세스를 운영체제는 어떻게 관리할까.

 

 프로세스 제어 블록(PCB: Process Control Block)

 cpu는 프로그램 카운터 레지스터에 실행할 명령어 주소를 저장한다고 했다. 그럼 그 주소는 누가 어떻게 알고 cpu에게 알려줄까 그 정보가 담긴 것이 바로 PCB다. PCB는 프로세스가 실행될 때 커널 영역에 생성된다. 운영체제는 해당 정보를 바탕으로 cpu와 상호작용하며 프로세스를 관리한다.

 

 PCB에는 일반적으로 다음과 같은 정보를 포함한다.

  • 프로세스 ID(PID: Process ID) : 프로세스를 식별하기 위한 프로세스 고유 번호다.
  • CPU 레지스터 값 : cpu작업 중에 인터럽트가 걸리면 현재 작업 중인 내용을 백업한다고 했다. 그 정보들이 담기는 곳이다.
  • 프로세스 상태 : 프로세스는 여러 상태를 가진다. 프로세스 생성 상태인 new, 실행 상태인 running, 대기 상태인 wating, 준비 상태인 ready, 종료 상태인 terminated를 가진다.
  • CPU 스케줄링 정보 : 프로세스가 언제, 어떤 순서로 cpu를 할당받을지에 대한 정보다.
  • 메모리 관리 정보 : 베이스 레지스터 주소 지정 방식을 위한 베이스 레지스터나, 한계 레지스터 값 등을 저장하는 장소다.
  • 사용한 파일과 입출력 장치 목록 : 해당 프로세스가 어떤 파일을 열었는지, 어떤 입출력 장치가 해당 프로세스에 할당되었는지를 명시하는 장소다.       

 

 해당 정보들을 바탕으로 다음과 같이 관리된다.

 

프로세스 상태 다이어그램

 

 프로세스가 생성이 되면 생성 상태가 된다. 그리고 준비 상태로 준비 큐에 들어가 자기 차례를 기다린다. 프로세스가 실행이 되고 타이머 인터럽트 등 인터럽트가 발생하면 준비 큐에 들어가 대기한다. 입출력 인터럽트가 발생하면 대기 큐에 들어가 해당 인터럽트가 처리되길 기다린다. 처리가 완료되면 준비 큐에 들어가 실행을 대기한다.

 

 준비 큐(ready queue)는 준비 큐는 일반적으로 연결 리스트로 저장된다. 큐 헤더에는 리스트의 첫 번째 PCB에 대한 포인터가 저장되고 각 PCB에는 준비 큐의 다음 PCB를 가리키는 포인터 필드가 포함된다.

 

 대기 큐(wait queue)는 입출력 요청이 있을 시 해당 요청이 완료되기를 기다리는 큐다. 요청이 완료되면 다시 준비 상태로 들어가 실행을 기다린다.

 

 사실 공룡책에서는 '프로세스 스케줄링' 이라고 cpu 스케줄링을 따로 분리하여 언급을 한 반면, 혼공운은 cpu스케줄링으로 통합하여 상태 다이어그램에 같이 다루었다. 혼공운 저자에게 문의하니 겹치는 내용도 많고 'CPU가 스케줄링한다', '프로세스가 스케줄링된다' 차이이기 때문에 같이 다루었다 한다.  

 

 

 다음은 문맥교환, cpu 스케줄링 순으로 계속 이야기 해 볼 생각이다.

'개발공부 > 운영체제 등' 카테고리의 다른 글

운영체제 개괄  (0) 2023.12.10
CISC와 RISC  (1) 2023.11.21
레지스터와 CPU 동작원리  (0) 2023.11.21
여러 입출력 방법 (프로그램 , 인터럽트 , DMA)  (0) 2023.11.20
인터럽트(Interrupt)  (0) 2023.11.16

 들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 

 

 

 컴퓨터 구조에서 프로그램이 어떤 방식으로 동작하는 지 알아봤다. 메모리에 프로그램이 할당되고, cpu가 레지스터를 이용하여 연산을 하여 실행하고, 인터럽트를 거는 등 여러가지 동작을 통해 프로그램이 실행 된다.

 

 그럼 그전에 메모리에 프로그램을 올리는 건 누가 할까? cpu 또 어떻게 알고 메모리에 접근하여 그 프로그램을 실행시킬까? 그 일을 하는 것이 바로 흔히 말하는 운영체제라는 프로그램이다.

 

 컴퓨터에서 실행되는 많은 프로그램이 마음대로 cpu나 하드웨어에 접근하면서 작업을 해버리면 프로그램이 제대로 실행되지 못하기 때문에 모든 프로그램은 운영체제의 관리하에 하드웨어에 접근한다.   

 

   

 

 

 운영체제는 메모리의 커널 영역에 적재되어 실행된다. 그 외는 사용자 영역이며 사용자 영역은 다시 세부적으로 나뉜다. 

 

 응용 프로그램은 실행 중 하드웨어의 접근이 필요하면 커널 모드로 전환이 필요한데, 이를 위해 시스템 호출(system call)을 통해 커널 모드로 전환 후 운영체제의 서비스를 받게된다. 시스템 호출은 일종의 인터럽트인데, 이와 같이 특정 명령어에 의해 발생하는 인터럽트를 소프트웨어 인터럽트라고 한다. 해당 인터럽트는 인터럽트 후 커널모드 전환, 해당 인터럽트 처리, 복귀 순으로 실행된다.

 

 

 메모리에서 실행되는 많은 프로세스들은 정해진 실행 타임이 맞춰 순차적으로 실행이 되는데, 이렇게 순차적으로만 실행 해서는 이상하게 동작할 수도 있다. 해당 문제를 해결하기 위해 동기화(synchronization) 작업이 필요하다. 이 때 교착상태라는 또 하나의 문제가 발생하게 되는데, 해당 문제를 해결하는 것 까지가 핵심이라 할 수 있겠다.

 

 각 프로세스는 여러 스레드(thread) 를 가질 수 있다. 스레드는 쉽게 말해 실행의 단위인데, 스레드가 여러개면 프로세스는 스레드의 수만큼 동시적인 작업을 할 수 있다. 다만, 스레드가 많다고 꼭 좋은 것은 아니다.

 

 이상의 모든 작업들을 cpu에게 넘겨줄 때 또한 관리가 필요하다. 일반적으로 하나의 cpu(코어)에는 하나의 프로세스만 실행 가능하다. 이에 운영체제는 프로세스에 공정하게 cpu를 할당하기 위해 우선순위, 시간 등을 정하여 프로세스를 실행 시켜야 한다. 이를 CPU 스케쥴링이라 한다.  

 

  

 

 여기까지가 운영체제의 큰 틀이다. 사실 메모리 적재 방법과 문제점, 멀티 스레드 등 이야기할 것들이 많다.

 

 책을 한번 정독 하고 공룡책도 참고했지만, 아직 머릿속에서 지식들이 이어지지 않는다. 다시 계속 읽어가며 컴퓨터 구조와 같이 하나하나 뜯어 볼 생각이다.

 

 dx12를 공부하면서 필요하다 생각하여 시작한 운영체제 공부인데, 일단 이 정도만 해도 공부하며 이해하는데는 지장이 없을 것 같다.

 

 dx12에서 먼저 맛보고 컴퓨터 구조 이해가 수월 했던 거 처럼 계속 무언가 작업하다 보면 아마 운영체제 또한 감이 잡히지 않을까 생각한다.

 

 

'개발공부 > 운영체제 등' 카테고리의 다른 글

프로세스  (1) 2023.12.17
CISC와 RISC  (1) 2023.11.21
레지스터와 CPU 동작원리  (0) 2023.11.21
여러 입출력 방법 (프로그램 , 인터럽트 , DMA)  (0) 2023.11.20
인터럽트(Interrupt)  (0) 2023.11.16

 들어가기 앞서 해당 글은 언리얼 엔진 공식문서와 인프런 '이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해' 강의 를 참고로 작성되었습니다.

 

 

 언리얼의 핵심 기능 중 하나가 바로 프로퍼티 시스템(property system)이다. 해당 기능에 대해선 공식문서에서 설명하고 있다. 

 

 첫 문단을 요약하면 c++에는 없는 리플렉션 기능을 구현하여 엔진의 근간을 이룬다는 것이다. 그 후에 서술되는 것은 언리얼 리플렉션. 즉, 프로퍼티 시스템을 간략한 주의점과 함께 어떤식으로 활용하는지를 설명한다.

 

 강의는 공식문서를 참고하면서 CDO(ClassDefaultObject)를 언급하며 CDO의 특징과 프로퍼티를 어떻게 쓰는지를 보여준다.

 

 필자는 이미 언리얼을 이용하여 c++로 구현하면서 맛을 봤기 때문에 사용과 어떤식으로 동작하는지는 어려움이 없었으나, 시원하게 해결되지 않는 부분이 있었다.

 명확히 '어떤 시스템이다!' 라는 것이 정리 되지않았고 무엇보다 프로퍼티 시스템과 CDO의 연결점을 찾지 못해 고민을 했다. 강의에서는 CDO가 뜬금없이 튀어나와 이런 것이라고 설명을 해주는데 직접적인 프로퍼티 시스템과의 연관성을 설명해주진 않았다.(물론 필자가 그냥 놓친 것 일수도 있다)

 

 해당 글에서는 그래서 왜 프로퍼티가 중요한지. CDO와는 대체 어떻게 이어지는지를 이야기하려 한다.

 

 

 

 우선 리플렉션이(Reflection)란 무엇이냐?

 구글에 검색해보면 흔히, 아니 거의 모든 글에서 다음과 같이 설명하고 있다.

 

 "리플렉션(Reflection)은 프로그램이 실행 중(런타임)에 자기 자신의 구조를 검사하여 변수나 매소드에 접근할 수 있는 기능."

 

 언리얼 프로퍼티 시스템 공식문서의 첫 줄 또한 똑같이 설명하고 있다. 그럼 해당 기능이 왜 중요한 것일까? 다른 글에서는 흔히 자바로 설명을 하지만 여기서는 언리얼 프로퍼티 시스템을 가지고 설명하겠다.

 

 

 위는 third person 프로젝트의 기본 캐릭터의 헤더파일이다.

 

 여기서 눈여겨 볼 키워드는 UCLASS() 매크로다. 언리얼의 프로퍼티 시스템을 사용하려면 해당 매크로 선언을 해 줘야한다. 

 

 UCLASS() 매크로를 선언하면 컴파일 과정에서 UHT(Unreal Header Tool)이 실행되고 UHT는 컴파일 단계에서 헤더의 매크로를 분석하여 해당 매크로에 해당하는 프로퍼티 시스템 코드를 담은 .generated.h 파일을 생성한다. 이로서 해당 클래스는 언리얼의 프로퍼티 시스템을 쓸 수 있게 되었다. 정확하게는 언리얼의 관리를 받게됐다는 표현이 맞겠다. 이 부분이 중요하다.

 

UCLASS()를 선언한 클래스는 UClass타입으로 하나의 정보를 들고 있게 되는데 그것이 바로 CDO(ClassDefaultObject)이다.

 

 CDO(ClassDefaultObject)

 CDO(ClassDefaultObject)는 말 그대로 해당 클래스의 기본값. 클래스의 이름, 변수, 함수의 정보를 들고 있다. 아래는 간단한 실습이다.

 

UMyGameInstance 클래스는 UPROPERTY() 매크로 선언을 한 MyName변수를 들고 있다.

 

 

 CDO는 생성자가 호출될 때 초기화 되는데(정확히는 생성자 끝에서), CDO의 값에 대해 알아보기 위해 로그를 찍어 보았다.

 

 MyName변수는 생성자에서 '기본 이름'으로 초기화 되었고 Init()함수에서 '놀고있는메모리' 로 변경 해 주었다. 그리고 Line: 24~25에 각각 로그를 찍어줄 건데 하나는 그대로 MyName변수를 출력하고 하나는 GetDefaultObject로 CDO에 있는 MyName을 출력할 것이다.

 

출력 결과

 

 출력 결과는 위와 같이 나온다. 값을 다시 설정 해 줬는데도 CDO의 값은 변함이 없다는 것을 알 수 있다. 

 

 

 그럼 바뀌지도 않는 이 정보를 언리얼 엔진에서 어떻게 쓰이나?

 프로그래머가 매크로 선언(UPORPERTY,UFUNCTION 등)으로 디테일 창이나 블루프린트에서 볼수 있는 변수나 메서드가 바로 이 CDO의 정보를 가져와 '초기화'한 것이다.

 

 이 CDO는 위에서 말했듯 생성자가 호출될 때 만들어 지기 때문에 안전하게 접근할 수 있다.

 

해당 지점에 브레이크 포인트를 걸면
엔진 초기화 75% 단계에서 멈추고
해당 브레이크 포인트에 걸리게된다.
f10을 누르고 생성자의 끝에서 f11을 누르면
GetDefaultObject에서 CreateDefaultObject가 호출되는 걸 볼 수 있다.

 

이와 같이 엔진 초기화 단계에서 생성되기 때문에(만약 문제가있다면 여기서 걸러지기 때문에) 프로그래머가 엔진을 이용하여 프로그래밍을 할 때(런 타임 때) 안전하게 접근하여 인스턴스를 생성하는 강력한 시스템이 아닐 수 없다.  

 

이 CDO의 정보들은 findclass함수나 newobject등으로 (런타임중)클래스를 생성할 때 안전하게 접근하여 쓰이게 된다.

 

 또한 CDO가 없다면 블루프린트를 생성할 때 어떤 값을 참조하여 초기값을 설정할지 모르게 된다. 즉, 새로운 인스턴스가 생성될 때 그 새로운 인스턴스를 어떤값으로 초기화해야 하는지가 불분명해지며, 이로 인해 초기화 과정이 복잡해질 수 있다는 것이다. 디테일 패널에서 보는 초기값이 그러하다. 

 

 이상이 바로 프로퍼티 시스템(리플렉션)의 '자기 자신의 구조를 검사하여 런 타임 때 변수와 매소드에 접근 가능한 강력한 기능' 의 의미와 CDO의 중요성이다. 

 

 또한 라이브 코딩 시 엔진의 디테일창에 변경된 값이 엔진이 꺼진 후 다시 킬 때 그 값이 날아 가버리는 현상이 있다. 이것이 바로 컴파일 단계에서 만들어지는 복사 값인 CDO값을 가져온 것이기 때문에, 컴파일 시 생성된 CDO의 기본값이 적용되어 라이브 코딩 시 해당 정보는 업데이트 되지 않아 생기는 문제로 볼 수 있겠다.

 들어가기 앞서 해당 글은 '혼자 공부하는 컴퓨터구조 + 운영체제'를 참고로 작성되었습니다. 

 

 

 

 CISC와 RISC는 각각 ISA(Instruction Set Architecture)의 한 종류이다. 

 ISA(Instruction Set Architecture)

 전공책이나 다른 정보글에서 '아키텍쳐에 따라 어쩌구 저쩌구'하는 그 아키텍쳐가 바로 이것이다. ISA는 cpu가 이해할 수 있는 명령어의 모음을 가리킨다.

 

 같은 cpu라고 해서 같은 명령어를 이해할 수 있는 것이 아니다. 이를테면 컴퓨터에 cpu가 들어있듯이 스마트폰에도 cpu가 있다. 하지만 컴퓨터의 실행 프로그램을 아무런 설정없이 스마트폰에서 실행하면 스마트폰의 cpu는 해당 프로그램을 이해하지 못해 실행이 되지 않는다는 말이다.  

 

 이것이 바로 아키텍쳐의 차이로 인한 것이다. 그럼 해당 아키텍처의 대표인 CISC와 RISC에 대해 알아보자.

 

 

 CISC(Complex Instruction Set Computer)

 결론부터 말하자면 'x86 아키텍처' 가 대표적인 CISC인데, 이를 기반으로 만든 cpu가 바로 인텔과 Amd가 되시겠다.

 

 CISC의 약자에서 볼 수 있듯이 말그대로 '복잡한 명령어 집합을 활용하는 컴퓨터'를 의미한다. CISC의 특징을 그대로 보여주는 약자인데, CISC는 하나의 명령어가 다양한 기능을 담당하기 때문에 내부 구조가 상당히 복잡하다.

 

 

 하나의 명령어가 다양한 기능을 하는만큼 컴파일된 어셈블리어를 까보면 같은 명령어로 다양한 내용을 처리하는 것을 볼 수 있다. 때문에 명령어가 짧게 끝나고 이는 명령어를 적게 사용하기 때문에 메모리를 절약할 수 있다는 장점이 있다. 

 

 하지만 여기서 이상한 점이 있다. 장점으로 '메모리 공간을 절약할 수 있다.' 라고 하는데, 이는 실행 프로세스를 올리는 메모리 영역에 한에서는 맞는 말일지 몰라도, 한 명령어에 다양한 기능을 넣었다는 건 그만큼 명령어가 크다는 말이고 이는 다시 하나의 명령어가 차지하는 메모리 영역이 크다는 말이된다. 참으로 이상한 장점이 아닐 수 없다.

 

 이는 CISC가 지향하는 바를 보면 의문이 풀리는데, CISC는 '명령어 하나'로 다양한 기능을 제공한다는 것이다. 이는 위에서 봤듯이 프로그램을 실행시키는데 필요한 명령어가 적다는 것이고, 큰 명령어가 저장된 것 보다 프로그램이 실행 될 때 동적으로 할당되는(또는 cpu가 읽을) 명령어가 적기 때문에 부담이 덜하다 라고 해석할 수 있겠다. 

 

 '하나의 명령어로 다양한 기능을 제공'은 명령어의 길이가 가변적이라는 특징으로 이어진다. 이 특징은 다시 치명적인 단점으로 이어지는데 가변적이라는 것은 상황에 따라 실행시간이 불규칙하다는 것이다. 이렇게 되면 명령어 파이프라인의 병렬 처리 또한 효율적이 못하게 된다는 단점이 된다. 

 

 이런 치명적인 단점이 있는 CISC을 왜 쓰는 걸까? 지금 말한 CISC의 특징들은 아주 옛날 CISC를 개념이 나온 초기의 특징이다. 현대에는 당연하게도 해당 단점을 보완하여 CISC지만 내부적으로 RISC와 비슷하게 동작하게 해 놓았다.

 

 

 RISC(Reduced Instruction Set Computer)

 약자에서 볼 수 있듯이 명령어 크기를 줄인 아키텍처다. CISC를 보완하기위해 명령어 크기를 줄인 대신 아래 이미지와 같이 컴파일된 명령어가 늘어났다.

 

 RISC는 규격화된 고정 길이 명령어를 활용한다. 때문에 하나의 명령어가 웬만해선 1클럭 내외로 실행되기 때문에 명령어 파이프라이닝에 유리하게 되었다. 이는 실행 속도의 향상으로 이어진다.

 

 또한 메모리에 직접 접근하는 명령어를 load, store 두 개로 제한했는데, 이는 메모리 접근을 단순화 하고 최소화하는 것을 추구하기 때문이다. 대신 레지스터를 적극적으로 활용하기 때문에 레지스터를 이용한 연산이 많아 일반적으로 범용 레지스터의 개수도 더 많다. 

 

 대표적인 아키텍처로 ARM 아키텍처가 있으며, 모바일 기기, 태블릿, 스마트폰 등에서 널리 사용된다.

  

 

 

 

 책에서는 CISC에 대해 '많은 명령어 수로 적은 명령으로 프로그램을 실행 시킬 수 있다.' 라고 나와있다. 처음에 이게 무슨 말인가 싶어 고민을 많이 했더랬다. 언뜻 보면 맞는 말 같지만 가만히 생각해 보면 이상한 말이 아닐 수 없었다. RISC에선 '명령어 종류가 적기 때문에 컴파일된 명령어들이 많다.'를 보면 더 이상하다. 종류가 적은데 왜 컴파일 된 명령어가 많단 말인가. 

 

결론적으로 명령어 종류와 개수에 포커스를 맞추는 것이 아니고, 명령어 하나에 다양한 기능을 하느냐 아니면 명령어 하나는 딱 그 역할만 하느냐에 초점을 맞춰야 하는 게 맞는 것 같다.

 

 아마 책에서는 '명령어'와  컴파일 '명령'을 구분한 게 아닐까 한다. 이 부분을 놓쳐 고민을 많이 한 거 같다.

 

 책과 같은 설명으로는 헷갈릴 수 있으니 위에서 말한 '명령어 하나'를 기준으로 삼는 게 좋은 것 같다.

 

   

 

 

'개발공부 > 운영체제 등' 카테고리의 다른 글

프로세스  (1) 2023.12.17
운영체제 개괄  (0) 2023.12.10
레지스터와 CPU 동작원리  (0) 2023.11.21
여러 입출력 방법 (프로그램 , 인터럽트 , DMA)  (0) 2023.11.20
인터럽트(Interrupt)  (0) 2023.11.16

+ Recent posts