크래프톤 정글

Pintos Project2 - User Program (커널/사용자 모드 이해하기)

Jerry_K 2024. 11. 26. 08:41

주의 !!! 

User Program 해결에는 큰 도움이 안됨 !!

단지 아래와 같은 궁금증을 가지고 있으면 읽어 볼 만 하다.

 

 

✨선행 Key word

세그먼트 선택자 

  • CPU에게 이 코드나 데이터가 어디에 있는지 알려주는 역할
  • CPU에게 어떤 권한으로 접근해야 하는지를 알려주는 역할
  • 세그먼트 선택자로 GDT (Global Descriptor Table)에서 정보를 찾는 key

 

GDT (Global Descriptor Table)

  • 세그먼트의 속성과 접근 권한을 정의하는 테이블
  • 필드값
    • Base (세그먼트 시작 주소)
    • Limit (세그먼트 끝 주소)
    • DPL (권한 레벨)
      • Descriptor Privilege Level  
        • CPL (Current Privilege Level)
          • CPL이 0인 경우 : 커널  모드
          • CPL이 3인 경우 : 사용자 모드
    • Type (코드/데이터/시스템 세그먼트 구분)
    • Granularity (주소 계산 단위 (바이트/페이지) )

 

 

논리적 세그먼트 세그멘테이션 차이 (레지스터 기반)

논리적 세그먼트

  • OS가 프로세스의 메모리 공간을 논리적으로 나눈 것 (소프트웨어적)

 

세그멘테이션 (레지스터 기반)

  • CPU가 세그먼트 레지스터를 사용해 메모리 접근 관리 (하드웨어적 구현)

궁금증 ❗

아래는 세그먼트 선택자들이다. 

/* GDT selectors defined by loader.
   More selectors are defined by userprog/gdt.h. */
#define SEL_NULL        0x00    /* Null selector. */
#define SEL_KCSEG       0x08    /* Kernel code selector. */
#define SEL_KDSEG       0x10    /* Kernel data selector. */
#define SEL_UDSEG       0x1B    /* User data selector. */
#define SEL_UCSEG       0x23    /* User code selector. */
#define SEL_TSS         0x28    /* Task-state segment. */
#define SEL_CNT         8       /* Number of segments. */

 

 

thread_create() 함수를 실행하면 아래와 같이 (커널) 스레드가 된다. 

    t->tf.rip = (uintptr_t)kernel_thread;
    t->tf.R.rdi = (uint64_t)function;
    t->tf.R.rsi = (uint64_t)aux;
    t->tf.ds = SEL_KDSEG;
    t->tf.es = SEL_KDSEG;
    t->tf.ss = SEL_KDSEG;
    t->tf.cs = SEL_KCSEG;
    t->tf.eflags = FLAG_IF;

 

 

그리고 process_exec() 함수를 사용하면 세그먼트의 값들을 바꿔 (사용자) 스레드가 된다.

struct intr_frame if_;
if_.ds = if_.es = if_.ss = SEL_UDSEG;
if_.cs = SEL_UCSEG;
if_.eflags = FLAG_IF | FLAG_MBS;

 

 

단순히 저 세그먼트들의 값만 바꾼다고 커널과 사용자 모드로 가는게 이해가 되지 않는다. 

 


맨 처음 내가 저 코드를 보았을 때, 해당 값들이 가상 메모리의 논리적 세그먼트 부분들을 가르키는 줄 알았다. 

cs,ds,ss,es는 그런 느낌이 아니라 세그먼트 레지스터들이다. 

이는 하드웨어적으로 제공되는 메모리 접근 보호 및 분리 매커니즘이다 . 

좀 더 구체적으로, 세그먼트 레지스터는 코드, 데이터, 스택 등의 메모리 위치와 접근 권한을 정의한다.

 

가상 메모리의 코드 영역은 프로그램이 실행될 때 가장 먼저 설정되고 CPU는 이를 시작점으로 사용한다.

이는 프로그램의 시작 점으로 데이터 초기화, 스택 설정, 힙 관리 등의 모든 작업이 코드 실행에 의해 이뤄진다.

따라서 코드 영역이 없으면 데이터와 스택도 의미가 없고, 코드가 실행되어야 프로그램이 정상적으로 동작한다. 

 

 

 

이 때문에 CS 레지스터는 다른 세그먼트 레지스터와 구별되는 특별한 역할을 한다.

t->tf.cs = SEL_KCSEG;

 

위와 같은 경우 GDT에서 커널 코드 세그먼트를 참조하여 CPL=0이 된다.

이로 인해 CPU는 커널 모드에서 명령을 실행하게 되는 것이다. 

이렇게 되는 경우 CS 레지스터의 하위 2 비트가 CPL로 사용되는 것이다. 

 

CS를 제외한 다른 세그먼트 레지스터(DS, ES, SS)는 CPL을 저장하지 않는다

대신 CPL과 DPL을 비교하여 메모리 접근 권한을 확인한다. 

DS,ES,SS 들이 참조하는 세그먼트의 DPL과  CPL을 비교해 접근 권한을 가지게 되는 것이다 . 

 

CPL(Current Privilege Level)은 현재 CPU의 권한 레벨을 나타내며,

DPL(Descriptor Privilege Level)은 특정 자원에 접근할 수 있는 최소 권한 레벨을 정의한다.

 

따라서 CPLDS, ES, SS에서 참조하는 세그먼트의 DPL보다 작거나 같아야 해당 논리적 세그먼트에 접근할 수 있다.

 

 

 

아래는 일반적인 프로그램의 실행 순서이다. 

프로그램 실행 순서

  1. OS가 프로그램 실행 시킬 때
    1. 프로그램 실행 파일을 메모리에 로드
    2. 코드 섹션(.text)를 메모리에 매핑
    3. CS 레지스터를 코드 세그먼트로 설정 
    4. CPU의 Instruction Pointer를 프로그램의 진입점으로 설정
    5. CPU가 명령어를 실행하여 프로그램 동작 시작
  2. 데이터 초기화 
    1. 전역/정적 변수는 데이터 섹션(.data / .bss)에 저장
    2. 코드 실행 중에 이 데이터를 메모리 적재 또는 초기화
  3. 스택 및 힙 설정 
    1. 스택은 프로그램이 실행되기 시작하면 함수 호출에 의해 동적으로 변화
    2. 힙은 코드 실행 중 동적 메모리 할당 

 

 

✨ 결론

  • VA 의 주요 매핑은 실행 파일이 메모리에 로드될 때 (Linking time 또는 Run time) 설정
  • 이후 세그먼트 선택자는 이 주소 매핑을 보호하는 역할 만 수행 (세그먼트 선택자는 VA 자체에 영향을 미치지는 않음)

진짜 있는 그대로 말해면,

CS 레지스터 값이 어떻든 이미 VA의 목적지는 정해져있고

CS 레지스터의 값으로 커널 / 사용자 모드 구분한다.