[크래프톤 정글 3기] 12/4(월) TIL

ClassBinu·2023년 12월 4일
0

크래프톤 정글 3기 TIL

목록 보기
51/120

8:58 입실


정렬 알고리즘 복습

Bubble Sort

핵심 원리: 인접한 두 요소를 비교할 때 더 큰 요소를 뒤로 스왑. 끝까지 가면 마지막 요소는 정렬이 된다. 그 다음은 n - 1개의 요소에 대하여 같은 작업을 반복한다.


발제

다음주 (목) 1회 발표

Argument Passing
User Memory
두 항목은 테스트 패스 어렵다.
실제 출력 결과를 보고 통과 여부 체크해라.

extra 케이스는 옵션

아토믹 오퍼레이션을 보장하기 위한 장치가 무엇이 있는지 찾아보기

rax 레지스터 유래 찾아보기
원래 a, b, c였음
거기에 앞/뒤에 e, r, x를 붙이는 형식
이 레지스터의 사용 용도


키워드 학습

유저 모드 vs 커널 모드

운영체제 실행 수준

운영체제는 프로그램 실행 시 두 가지 상태를 지닌다.
운영체제 보안과 안정성을 유지하기 위한 목적이다.

Kernel Mode

  • 운영체제의 가장 권한이 높은 실행 수준
  • 이 모드에서 실행되는 코드는 하드웨어 직접 접근, 모든 자원(메모리 등)에 대한 통제권
  • 일반 애플리케이션은 커널 모드에서 실행되지 않음.

User Mode

  • 제한된 권한으로 실행되는 모드
  • 시스템과 격리되어 코드가 실행
  • 오류 발생 시 해당 어플리케이션만 영향, 나머지 시스템 영향 없음

CPL(Current Privilege Level)

CPU에서 실행 중인 코드의 권한 수준
코드 세그먼트 셀렉터 내에 저장된다.
코드 세그먼트 셀렉터는 현재 실행 중인 코드가 저장된 메모리 세그먼트 식별 정보

코드 세그먼트 레지스터(Code Segment Register, CS) 내에서 관리
x86기준 코드 세그먼트 레지스터의 하위 2비트로 CPL을 나타낸다.

  • 0: 커널 모드
  • 3: 유저 모드

DPL(Descriptor Privilege Level)

메모리 세그먼트 디스크립터 내에 설정된 권한 수준
이 세그먼트(코드, 데이터)에 접근하기 위한 최소 권한 수준
프로세스(스레드)의 CPL이 DPL보다 낮거나 같아야 이 세그먼트 접근 가능

시스템 자체가 단일적인 '커널 모드', '유저 모드'로 변환되는 게 아니다. 각각의 실행 컨텍스트가 자신의 권한 레벨에 따라 동작하는 것이다.

커널은 핵심, 핵, 알갱이 라는 뜻

모드 비트

CPU 상태 레지스터 내에 위치
현재 실행중인 프로세스의 권한에 따라 커널, 유저 모드가 비트로 기록된다.

전환

프로세스는 시스템 콜 등을 통해 커널 모드로 전환할 수 있음.
이런 전환은 해당 프로레스(스레드)에만 적용되는 것

유저 모드와 시스템 모드(feat. 스레드 + 프로세스)


시스템 콜

(11/25 학습 내용)

응용 프로그램은 시스템 콜을 호출하고,
커널이 호출된 시스템 콜을 실행한다.(특권 명령어 실행)

제한적 직접 실행(Limited Direct Execution)

CPU 가상화가 제한적 직접 실행임!
문맥 교환 등을 통해 cpu가 여러 개인 것처럼 쓸 수 있는데 이때, 커널와 유저를 나누어서 제한적으로 직접 실행한다.

프로세스를 CPU에서 제한 없이 직접 실행하면 하드웨어 접근 등 모든 명령어를 제한 없이 실행할 수 있음.

그래서 trap table을 만들고 이를 통해 시스템을 통제한다. 즉, 프로세스는 CPU에서 직접적으로 실행되기는 하지만 일부 제한을 받는다.

시스템 콜이 발생하면 trap table을 통해 어떤 핸들러(메모리 위치)를 실행해야 할지 알려준다.

너무 궁금했던 게 책에 있었다!!
시스템 콜은 왜 일반적인 프로시저 콜처럼 생겼을까?

사실 시스템 콜도 평범한 C함수임.
대신 시스템 콜 부분은 어셈블리어로 작성되어 있음.
하지만 이 시스템 콜은 내부에서 trap을 호출한다.
시스템 콜이 호출되면 시스템 콜은 인자와, open()에 해당하는 시스템 콜을 커널과 약속된 메모리 공간이나 레지스터에 저장. (모든 시스템 콜은 고유 번호가 있음)

그리고 trap 명령어를 실행.
(trap은 하드웨어에서 발생하는 이벤트)

이때 제어권이 커널로 넘어감.

trap이 실행되고 특권 명령어가 실행되고 난 후 c라이브러리가 시스템 콜의 리턴값을 읽고, 제어권을 시스템 콜을 호출한 프로그램에게 다시 넘긴다.

트랩 테이블은 커널이 부팅 시에 만든다.

그럼 사용자가 직접 시스템 콜을 실행시키면 되는거 아닌가?
시스템 콜의 코드 위치는 운영체제만 알고 있고 응용 프로그램은 모른다.

하드웨어에게 트랩 테이블의 위치를 알려주는 건 매우 강력한 기능! 이것 또한 특권 명령어임.

시스템 콜을 공부하면서 느낀 점. 웹의 프론트와 백엔드와 비슷하다.
유저 영역을 프론트엔드, 크널 영역을 백엔드(데이터베이스)로 본다면 비슷하다.
즉, API요청(시스템 콜)을 통해 제한적으로 백엔드와 데이터베이스에 접근시키고 나머지는 유저 모드에서 동작해야 한다!
그래서 사용자의 입력값을 주의해야 한다. 시스템 콜도 인자로 넘어온 값으로 커널 영역에 메모리에 접근한다 거나 문제를 막아야 하는 것처럼, 웹도 sql 인젝션과 같은 기법 등으로 데이터베이스 등에 접근하게 되는 문제나, 백엔드의 관리자 권한을 탈취당하는 일 등을 방지해야 한다!


Register vs Memory

레지스터

  • 프로그램 카운터
  • 명령어 레지스터
  • 메모리 주소 레지스터
  • 메모리 버퍼 레지스터
  • 플래그 레지스터
  • 범용 레지스터
  • 스택 포인터
  • 베이스 레지스터

메모리

  • RAM(Random Access Memory): D램
  • ROM(Read Only Memory)
  • 캐시(L3, L2, L1) : S램
  • 보조 저장장치(HDD, SDD) : 자기, 낸드 플래쉬(비휘발성)

User Stack + Kernel Stack

커널은 user stack에 접근할 수 없다.
커널 스택은 각 프로세스마다 생성된다.

각 프로세스는 고유한 커널 스택을 갖고 있다.
커널 코드는 모든 프로세스가 공유하지만, 커널 스택은 각 프로세스마다 격리되어 있다.


Atomic Operation

여러 작업이 동시에 실행되는 다중 프로세스 환경에서 데이터 일관성과 안정성을 보장하기 위한 개념

여러 개의 연산 묶음이 마치 하나의 연산인 것처럼 0아니면 1로 실행되어야 함.

아토믹 오퍼레이션을 위한 여러 개념들 찾아보기


32bit vs 64bit

하드웨어와 소프트웨어 아키텍처 차이

--32bit OS64bit OS
주소 공간최대 약 4GB테라, 페타 수준
레지스터 크기32bit64bit
CPU 호환32bit CPU만 사용32/64bit CPU 호환

RAX

  • 범용 레지스터
  • 함수 호출할때 인자 전달, 결과 반환할 때 사용
  • 산술 논리 연산에 사용
  • 64비트 주소 저장할 때 사용
  • 서브 레지스터가 있음
    - RAX(64bit)
    • EAX(32bit)
    • AX(16bit)
    • AL(8bit) / AH(상위 8bit)

https://velog.io/@suseodd/Ch3.4-Accessing-Information


레지스터 이름(32비트 기준)

범용 레지스터

  • EAX(Extended Accumulator Register) : 함수의 리턴 값이나 연산 결과가 담긴다.
  • EBX(Extended Base Register) : 배열의 주소가 저장되고 이를 참조하는 데 사용한다.
  • ECX(Extended Count Register) : 루프문에서 카운트를 세는 데 사용한다.
  • EDX (Extended Data Register) : EAX의 보조 레지스터로 특수한 경우에 EDX-EAX 순서로 붙여서 임시 8바이트 레지스터로 사용한다.

인덱스 레지스터

  • ESI (Extended Source Index) : 데이터 이동 시 원본 데이터의 주소를
    담당한다.
  • EDI (Extended Destination Index) : 데이터 이동 시 저장될 주소를 담당한다.

포인터 레지스터

  • ESP (Extended Stack Pointer) : 현재 스택 프레임의 끝 주소를 담는다.
  • EBP (Extended Base Pointer) : 현재 스택 프레임의 기준이 되는 주소를 담는다.
  • EIP (Extended Instruction Pointer) : 수행할 명령어의 주소를 담는다.

반드시 목적대로 사용해야 하는 건 아니지만 가급적 목적대로 사용하는데 코드 가독성이 좋아진다.

8비트에서는 A(C), B, C, D 같은 형태.
16비트에서는 AX와 같은 형태.
32비트가 되면서 Extended가 붙어서 EAX.
64비트가 되면서 Register가 붙어서 RAX.

출처: https://5kyc1ad.tistory.com/32 [Re: 제로부터 시작하는 블로그 생활:티스토리]


Cache

캐시 메모리

  • L1 (32KB ~ 128KB) : 코어마다 할당, access 약 4클럭
  • L2 (128KB ~ 1MB) : 코어마다 할당, access 약 10클럭
  • L3 (1MB ~ 64MB) : 여러 코어에서 공유, access 약 50클럭
    (서버, 고성능 컴퓨터에서는 더 큰 캐시를 가질 수 있음.)


File Descriptor

파일 또는 I/O 리소스 식별자

STDIN: 0 (키보드 입력)
STDOUT: 1 (콘솔 출력)
STDER: 2 (오류 메시지 출력)


핀토스 User Program

FAIL tests/userprog/args-none
FAIL tests/userprog/args-single
FAIL tests/userprog/args-multiple
FAIL tests/userprog/args-many
FAIL tests/userprog/args-dbl-space
FAIL tests/userprog/halt
FAIL tests/userprog/exit
FAIL tests/userprog/create-normal
FAIL tests/userprog/create-empty
FAIL tests/userprog/create-null
FAIL tests/userprog/create-bad-ptr
FAIL tests/userprog/create-long
FAIL tests/userprog/create-exists
FAIL tests/userprog/create-bound
FAIL tests/userprog/open-normal
FAIL tests/userprog/open-missing
FAIL tests/userprog/open-boundary
FAIL tests/userprog/open-empty
FAIL tests/userprog/open-null
FAIL tests/userprog/open-bad-ptr
FAIL tests/userprog/open-twice
FAIL tests/userprog/close-normal
FAIL tests/userprog/close-twice
FAIL tests/userprog/close-bad-fd
FAIL tests/userprog/read-normal
FAIL tests/userprog/read-bad-ptr
FAIL tests/userprog/read-boundary
FAIL tests/userprog/read-zero
FAIL tests/userprog/read-stdout
FAIL tests/userprog/read-bad-fd
FAIL tests/userprog/write-normal
FAIL tests/userprog/write-bad-ptr
FAIL tests/userprog/write-boundary
FAIL tests/userprog/write-zero
FAIL tests/userprog/write-stdin
FAIL tests/userprog/write-bad-fd
FAIL tests/userprog/fork-once
FAIL tests/userprog/fork-multiple
FAIL tests/userprog/fork-recursive
FAIL tests/userprog/fork-read
FAIL tests/userprog/fork-close
FAIL tests/userprog/fork-boundary
FAIL tests/userprog/exec-once
FAIL tests/userprog/exec-arg
FAIL tests/userprog/exec-boundary
FAIL tests/userprog/exec-missing
FAIL tests/userprog/exec-bad-ptr
FAIL tests/userprog/exec-read
FAIL tests/userprog/wait-simple
FAIL tests/userprog/wait-twice
FAIL tests/userprog/wait-killed
FAIL tests/userprog/wait-bad-pid
FAIL tests/userprog/multi-recurse
FAIL tests/userprog/multi-child-fd
FAIL tests/userprog/rox-simple
FAIL tests/userprog/rox-child
FAIL tests/userprog/rox-multichild
FAIL tests/userprog/bad-read
FAIL tests/userprog/bad-write
FAIL tests/userprog/bad-read2
FAIL tests/userprog/bad-write2
FAIL tests/userprog/bad-jump
FAIL tests/userprog/bad-jump2

자.. 해결해보자..


과제 분석

테스트 케이스를 보면 이번 과제는 함수를 호출하고, 파일을 CRUD하는 과정을 구현하는 건가..?
즉, 함수 호출과 파일 입출력 구현하는 것?


코드 분석

process.c / proccess.h

프로세스 초기화, 생성, 포크 등 프로세스를 제어하는 것 같음
ELF 바이너리를 실행한다고 함.
ELF: Executable and Linkable Format(유닉스 계열에서 실행 가능한 파일 포맷)

🔥 syscall.c / syscall.h

시스템 호출 처리기
프로젝트2에서 구현해야 할 코드!

🔥 exception.c / exception.h

유저 프로그램이 특권 명령어나, 금지된 명령어를 실행할 때 exception이나 fault로 트랩을 발생시키는 것!
프로젝트2에서는 page_fault()를 구현해야 함!

filesys.h / file.h

직접 관련은 없지만 간단한 파일 시스템이니까 흥미 차원에서 살펴보기!


가상 메모리

  1. 리눅스는 프로세스마다 4G의 가상 메모리를 할당한다.
  2. 3~4G는 커널, 0~3G은 사용자 영역으로 구분
    (32bit 기준이겠지..?)

출처: https://dlforbi.tistory.com/16

pintos gitbook 보고 가상 메모리 구조 그려봄!


사용자 메모리 액세스

방법 1. 사용자가 제공한 포인터의 유효성을 확인하고 다음에 역참조 하는 것

thread/mmu.c, include/threads/vaddr.h 살펴보기

방법2. KERN_BASE 아래인지만 확인.

비정상적인 포인터는 page fault를 유발한다. userprog/exception.c에 있는 page_fault()를 수정해서 핸들링.
방법2가 mmu(Memory Management Unit)를 활용해서 일반적으로 더 빠르다. 그리고 리눅스 등의 실제 커널에서 사용하는 방법임.


Argument Passing 구현

구현할 파일 및 함수

process.c
precess_exec()

호출 컨벤션

  1. 인수 전달 순서 레지스터: %rdi, %rsi, %rdx, %rcx, %r8, %r9
  2. 돌아갈 주소를 스택에 push후 첫 번째 instruction으로 점프
    (x86-64 CALL 명령어가 이 작업을 함)
  3. 호출 함수 실행
  4. 반환 값 있으면 RAX 레지스터 저장
  5. 반환 주소 팝해서 해당 주소로 점프
    (x86-64 RET 명령어 사용)

인수 전달을 구현합니다.
현재는 process_exec()새 프로세스에 인수 전달을 지원하지 않습니다. process_exec()단순히 프로그램 파일 이름을 인수로 사용하는 대신 공백에서 단어로 나누도록 확장하여 이 기능을 구현합니다 . 첫 번째 단어는 프로그램 이름이고 두 번째 단어는 첫 번째 인수 등입니다. 즉, process_exec("grep foo bar")두 개의 인수 foo와 bar를 전달하여 grep을 실행해야 합니다.

들어오는 값을 적당히 파싱해서 프로그램명과 인수명으로 분할해야 하는 것 같음.
분할은 알아서 하면 되는데 길을 잃으면(?) strtok_r()을 참고하라고 함.


/* The following uses strtok_r() to parse two strings using separate contexts: */

     char test[80], blah[80];
     char *sep = "\\/:;=-";
     char *word, *phrase, *brkt, *brkb;

     strcpy(test, "This;is.a:test:of=the/string\\tokenizer-function.");

     for (word = strtok_r(test, sep, &brkt);
          word;
          word = strtok_r(NULL, sep, &brkt))
     {
         strcpy(blah, "blah:blat:blab:blag");

         for (phrase = strtok_r(blah, sep, &brkb);
              phrase;
              phrase = strtok_r(NULL, sep, &brkb))
         {
             printf("So far we're at %s:%s\n", word, phrase);
         }
     }

이번 과제 정리
1. 프로그램 실행 코드 구현
2. 프로세스 관련 코드 구현
3. 시스템콜 관련 코드 구현
4. 번외

0개의 댓글