PintOS Project 02 - User program

이진호·2022년 6월 9일
0

PintOS

목록 보기
3/4

01 주차에는 kernel에서 필요한 기능을 모두 만들었다면 02주차부터는 user program을 실행시키기 위한 작업을 준비한다.
command line을 읽어와서 file name과 argument를 추출하여 순서대로 stack에 push하는 argument passing부터 user의 system call을 처리하기 위한 기능들을 구현하는 것이 이번 주차의 목표이다.

아무래도 user 영역에서 kernel 영역으로 전환되는 과정이 잘 이해가지 않아 syscall 보다 오히려 훨씬 애먹었던 기억이 있다. syscall 함수들 자체는 크게 이해 안가는 부분들이 없었다. 물론 아직 구체적으로 공부하지 않은 virtual memory나 file system과 연관된 함수들을 100% 이해를 하진 못했지만 어느정도 system call이 어떤식으로 처리되는지에 대한 감은 잡은 것 같다.

본론으로 들어가기 전에 interrupt가 발생했을 때, kernel stack에 어떤것들이 쌓이는지 한번 간략하게 정리를 하고 시작하겠다. 물론 부정확한 부분들이 있을 수 있지만 현재 시점에서 내가 생각하는 내용들을 정리하고 추후 과제를 진행하면서 깨닫는 점들로 보완해나갈 생각이다.

아래의 내용은 https://uchicago-cs.github.io/mpcs52030/index.html 문서를 참고하여 정리하였다.


What happens during an interrupt

  1. 우선 interrupt가 발생하면 CPU는 다음의 Register 값을 stack에 push 한다.
  • segment selector에 대해서는 정확히 알진 못하지만 간략한 개요를 정리해놓았다.

  • 요점은 CPU가 thread 실행을 재개할 수 있는 충분한 정보를 저장하는 방법이며, 실행할 다음 명령어와 thread stack의 top pointer를 저장하여 interrupt 발생 시 정확한 함수 호출 chain으로 return 할 수 있다.

    - ss: Stack segment selector
    - esp: stack pointer
    - eflags: CPU flags
    	- 목적에 따라 상태 플래그, 제어 플래그, 시스템 플래그로 나눌 수 있고 1을 지정하면 set이고, 0을 지정하면 clear or reset을 의미한다.
       - ex) Interrupt Flag(IF: bit 9): 프로세스로부터 인터럽트가 발생하면 인터럽트 처리를 할 것인지 제어함
    - cs: Code segment selector
    - eip: Instruction point(=program counter)

    여기서 segment selector라는 register가 나와 한번 무슨 의미인지 찾아보았다.
    다음 그림은 X86 CPU에서 제공하는 세그멘테이션과 페이징 기능을 통해 Logical address를 physical address로 바꾸는 과정이다.

    위의 그림으로 보았을 때 운영체제 책에서 가상 메모리 관련 부분을 잠시 읽었을 때 paging과 segmentation을 같이 쓰는 hybrid 기법이 생각났다.
    segment selector를 통해 descriptor table에서 원하는 세그먼트 디스크립터를 선택하고, 여기서 base address에 offset을 더해 linear address를 얻는 과정으로 보인다.
    따라서, 정리해보면 segment register인 CS, DS, SS, ES, FS, GS 등의 세그먼트 셀렉터는 이를 이용해 디스크립터 테이블에서 해당 세그먼트의 위치를 찾기위해 사용되는 인자라고 생각할 수 있다. 결국 세그먼트 셀렉터는 세그먼트에 대한 16bit 식별자이지만 직접적으로 세그먼트를 가리키는 것이 아닌 세그먼트를 정의하는 디스크립터를 가리키는 인자로 생각할 수 있다.
    해당 내용은 http://egloos.zum.com/anster/v/2135644 에서 참고하여 작성하였다.

  1. 다음으로 CPU는 IDT(Interrupt Descriptor Table)를 검사하고, 방금 발생한 interrupt에 해당하는 entry를 찾는다.

    • Interrupt discriptor table
      :Interrupt vector table을 구현하기 위해 x86 architecture에서 사용되는 데이터 구조체로 프로세서가 interrupt와 exception에 대한 정확한 반응을 결정하기 위해 사용된다.
    • Interrupt vector table
      : Interrupt가 발생했을 때, 처리할 수 있는 서비스 루틴들의 주소를 가지는 공간으로 해당 루틴의 기능을 요청하면 저장된 주소로 분기하여 인터럽트를 수행함.
  2. 이 entry는 해당 interrupt에 대한 interrupt handler를 포함하는 memory location을 포함하며 CPU는 해당 위치로 jump한다.

    • pintOS에서 IDT는 실제로 x86 assembly에서 구현되는 intr_entry라는 함수로 항상 jump 하도록 되어 있고, 이 함수는 additional data를 stack으로 push(대부분 CPU register)한다.
    • 실제로 핀토스 내의 intr_entry 함수에서 확인할 수 있는데, TCB에 task context를 저장 후 intr_handler를 통한 ISR을 호출하고, ISR 완료 후 본래 context를 restore, iretq instruction을 사용하여 interrupt context에서 본래 실행되고 있던 program의 context로의 context switch를 순차적으로 수행하는 것을 확인할 수 있다.
  3. CPU와 intr_entry에 의해 push된 값들의 조합을 interrupt stack frame이라고 하며 인터럽트된 스레드의 정보를 저장하고, 인터럽트를 수행하는데 필요한 정보를 interrupt handler에 제공하는데 사용된다.

    • intr_entry는 c에서 구현되는 intr_handler 함수에 대한 호출을 set하고, 이 함수는 interrupt stack frame의 모든 값을 캡슐화하는 intr_frame 구조에 대한 포인터 단일 매개변수를 가진다.
    • Intr_handler는 인터럽트 유형에 따라 호출할 함수를 결정하는 함수
    • PintOS의 초기화 동안(init.c/main) 특정 기능은 특정 인터럽트와 연결된다. 예를 들어 timer_init 함수는 타이머의 인터럽트를 처리하기 위해 timer_interrupt 함수를 할당한다.
  • 다음은 timer_interrupt 시 kernel stack의 예시이다(thread_one이라는 함수를 실행시키는 thread case).

System call

  • 필요한 기능이나 시스템 환경에 따라 system call이 발생할 때 좀 더 많은 정보가 필요할 수 있는데 이 때, 정보가 담긴 매개변수를 운영체제에 전달하기 위해서는 대략 3가지 정도의 방법이 존재한다.
    • 매개변수를 CPU 레지스터 내에 전달
    • 위 방법에서 총 레지스터 개수보다 매개변수의 개수가 더 많을 수 있는데 이 경우 매개변수를 메모리에 저장하고, 메모리의 주소를 레지스터에 저장함.
    • 매개변수는 프로그램에 의해 stack으로 push 될 수도 있음.
  • 아래의 사진은 syscall이 처리되는 과정을 참고한 그림인데 앞의 interrupt 처리 과정과 대략적인 흐름이 비슷한 것을 확인할 수 있다.

  • 위의 과정으로 PintOS에서 user program이 write를 호출했을 때를 정리해보면 다음과 같다.

  • syscall 함수 구현은 notion에 정리가 잘 되어 있는 편이라 생략하고, syscall이 호출되어 진행되는 과정을 코드와 함께 정리해볼 생각이다.

0개의 댓글