📌- Pintos VM layout
Stack :
- 프로그램이 자동으로 사용하는 임시 메모리 영역이며, 지역 변수, 매개변수, 리턴 값 등을 잠시 사용되었다가 사라지는 데이터를 저장하는 영역입니다.
- 함수의 호출과 함께 할당되며, 함수의 호출이 완료되면 소멸됩니다.
- Heap 영역에 생성된 Object 타입의 데이터 참조값이 할당됩니다.
- 힙 영역과 반대로 메모리 상위 주소에서 하위 주소 방향으로 추가 할당됩니다.
- 재귀 함수가 너무 깊게 호출되거나 stack영역을 초과하게 되면 stack overflow 발생
Heap
- 사용자에 의해 메모리 공간이 동적으로 할당되고 해제되는 영역입니다.
- 스택 영역과는 반대로 메모리의 하위 주소에서 상위 주소 방향으로 추가 할당됩니다.
- 클래스, 클로저와 같은 참조형의 데이터 값이 저장됩니다.
- 런타임에 크기가 결정됩니다.
DATA :
- 프로그램의 전역변수(global), 정적변수(static), 배열(array), 구조체(structure) 등이 저장되는 영역입니다.
- 프로그램의 시작과 함께 할당되며, 프로그램이 종료되면 소멸됩니다.
- 초기화 된 데이터는 data 영역에 저장되고, 초기화 되지 않은 데이터는 BSS (Block Stated Symbol) 영역에 저장 됩니다.
- 함수 내부에 선언된 Static 변수는 프로그램이 실행 될 때 공간만 할당되고, 그 함수가 실행 될 때 초기화 됩니다.
- 전역변수, static 값을 참조한 코드는 컴파일 후 Data 영역의 주소값을 가르키도록 변경됩니다.
Text :
- 작업을 실행할 프로그램의 코드를 저장하는 영역
📌- Argument passing
user 프로그램이 실행되기 전에 프로그램에 대한 인자를 설정한다.
- 인자를 처리하는 방식 알아보기
- 예제 ) /bin/ls -l foo bar
- 명령어를 널포인터 기준으로 /bin/ls, -l, foo, bar로 나눠서 스택에 오른쪽에서 왼쪽으로 순서대로 푸시한다.
- 스택에 저장된 인자를 응용 프로그램에 전달하는 기능을 구현합니다
- %rsi를 argv(즉, argv[0]의 주소)로 지정하고 %rdi를 argc로 설정한다
👉 - fake return address를 푸시하는 이유는?
- 진입 함수는 반환하지 않지만, 다른 함수들과 동일한 스택 프레임 구조를 가져야 하기 때문입니다.
✓ - 64비트 범용 레지스터
👉 - 포인터 레지스터(RSI, RDI, RBP, RSP)
RSI(Extended Source Index) / RDI(Extended Destination Index)
문자열 출발지/목적지 주소. 확장 소스 인덱스, 확장 목적지 인덱스 레지스터. 각각 메모리 출발지와 목적지를 나타냄. 고속 메모리 전송 명령어에서 사용.
RSP(Extended Stack Pointer)
현재 스택 주소. 그러니까 스택 맨 윗쪽 주소. 스택에 있는 데이터의 주소를 지정. 계산, 데이터 전송에는 거의 사용되지 않는다
RBP(Extended Base Pointer)
스택 복귀 주소. 고급언어에서 스택에 있는 함수 매개변수와 지역변수를 참조하기 위해서 사용. 고급 수준의 프로그래밍 이외에 일반적 계산과 데이터 전송에서 사용되지 않아야.RSP, RBP에 대한 자세한 설명
스택프레임
스택프레임EBP(베이스 포인터)레지스터를 사용하여 스택 내의 로컬 변수, 파라미터 ,복귀 주소에 접근하는 기법 ESP레지스터의 값은 프로그램 안에서 수시로 변경되기 때문에 스택에 저장된 변수
blog.kimtae.xyz
RIP
현재 명령 실행 주소
r8 ~ r15
일반적으로 함수의 매개변수로 사용
👉 - 데이터 레지스터 (RAX ~ RDX)
RAX(Extended Accumulator Register)
사칙연산 명령어에서 자동으로 사용, 리턴 레지스터시스템콜의 실질적인 번호를 가리키는 레지스터
RBX(Extended Base Register)
메모리 주소를 저장하는 용도로 사용
RCX(Extended Counter Register)
CPU는 루프 카운터로 ECX를 자동으로 사용
RDX(Extended Data Register)
EAX와 같이 사용됨.
📌 - 구현
💡 - process_execute() : 프로그램 이름 파싱
💡 - thread_create() : 쓰레드 생성 후 ready-list에 추가
💡 - start_process() : 인터럽트 프레임 초기화
💡 - load() : 메모리 할당 - 프로그램 탑재
🎶 - 코드의 흐름
init.c → int main(void) → run_actions(argv) → run_task(char **argv) → process_create_initd(task) → thread_create (file_name, PRI_DEFAULT, initd, fn_copy) → initd→process_exec → load, do_iret
🔥 - tid_t process_create_initd(const char *file_name)
tid_t process_create_initd(const char *file_name)
{
char *fn_copy;
tid_t tid;
/* Make a copy of FILE_NAME.
* Otherwise there's a race between the caller and load(). */
fn_copy = palloc_get_page(0);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy(fn_copy, file_name, PGSIZE);
// Argument Passing ~
char *save_ptr;
strtok_r(file_name, " ", &save_ptr);
// ~ Argument Passing
/* Create a new thread to execute FILE_NAME. */
tid = thread_create(file_name, PRI_DEFAULT, initd, fn_copy);
if (tid == TID_ERROR)
palloc_free_page(fn_copy);
return tid;
}
🔥 - int process_exec(void *f_name)
int process_exec(void *f_name)
{ // 인자: 실행하려는 이진 파일의 이름
char *file_name = f_name;
bool success;
/* We cannot use the intr_frame in the thread structure.
* This is because when current thread rescheduled,
* it stores the execution information to the member. */
struct intr_frame _if;
_if.ds = _if.es = _if.ss = SEL_UDSEG;
_if.cs = SEL_UCSEG;
_if.eflags = FLAG_IF | FLAG_MBS;
/* We first kill the current context */
process_cleanup();
// Argument Passing ~
char *parse[64];
char *token, *save_ptr;
int count = 0;
for (token = strtok_r(file_name, " ", &save_ptr); token != NULL; token = strtok_r(NULL, " ", &save_ptr))
parse[count++] = token;
// ~ Argument Passing
/* And then load the binary */
success = load(file_name, &_if);
// 이진 파일을 디스크에서 메모리로 로드한다.
// 로드된 후 실행할 메인 함수의 시작 주소 필드 초기화 (if_.rip)
// user stack의 top 포인터 초기화 (if_.rsp)
// 위 과정을 성공하면 실행을 계속하고, 실패하면 스레드가 종료된다.
// Argument Passing ~
argument_stack(parse, count, &_if.rsp); // 함수 내부에서 parse와 rsp의 값을 직접 변경하기 위해 주소 전달
_if.R.rdi = count;
_if.R.rsi = (char *)_if.rsp + 8;
hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true); // user stack을 16진수로 프린트
// ~ Argument Passing
/* If load failed, quit. */
palloc_free_page(file_name);
if (!success)
return -1;
/* Start switched process. */
do_iret(&_if);
NOT_REACHED();
}
🔥 - void argument_stack(char **parse, int count, void **rsp)
/* process.c */
void argument_stack(char **parse, int count, void **rsp) // 주소를 전달받았으므로 이중 포인터 사용
{
// 프로그램 이름, 인자 문자열 push
for (int i = count - 1; i > -1; i--)
{
for (int j = strlen(parse[i]); j > -1; j--)
{
(*rsp)--; // 스택 주소 감소
**(char **)rsp = parse[i][j]; // 주소에 문자 저장
}
parse[i] = *(char **)rsp; // parse[i]에 현재 rsp의 값 저장해둠(지금 저장한 인자가 시작하는 주소값)
}
// 정렬 패딩 push
int padding = (int)*rsp % 8;
for (int i = 0; i < padding; i++)
{
(*rsp)--;
**(uint8_t **)rsp = 0; // rsp 직전까지 값 채움
}
// 인자 문자열 종료를 나타내는 0 push
(*rsp) -= 8;
**(char ***)rsp = 0; // char* 타입의 0 추가
// 각 인자 문자열의 주소 push
for (int i = count - 1; i > -1; i--)
{
(*rsp) -= 8; // 다음 주소로 이동
**(char ***)rsp = parse[i]; // char* 타입의 주소 추가
}
// return address push
(*rsp) -= 8;
**(void ***)rsp = 0; // void* 타입의 0 추가
}
🔥 - int process_wait(tid_t child_tid UNUSED)
int process_wait(tid_t child_tid UNUSED)
{
/* XXX: Hint) The pintos exit if process_wait (initd), we recommend you
* XXX: to add infinite loop here before
* XXX: implementing the process_wait. */
for (int i = 0; i < 100000000; i++)
{
}
return -1;
}
🗒️ - Reference