process_exec() 함수가 현재는 프로그램 이름만 받도록 구현되어 있다. 이를 확장하여 명령줄 인자를 처리할 수 있도로 구현해야한다.
process_exec()이 실행되는 로직 process_exec()가 넘겨 받는 문자열 인자들을 parshing argc, argv 인자를 레지스터에 설정process_exec()현재 프로그램에 덮어씌우는 방식
fork() + exec() -> 쉘 fork() -> 크롬 탭, 웹 서버 요청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; // 인터럽트 플래그 On | 필수
/* We first kill the current context */
process_cleanup (); //현재 실행 중인 프로세스를 종료하고 자원 정리
/* And then load the binary */
success = load (file_name, &_if);
/* If load failed, quit. */
palloc_free_page (file_name);
if (!success)
return -1;
/* Start switched process. */
do_iret (&_if);
NOT_REACHED ();
}
load()나중에 구현할 때 file_name -> cmd_line으로 변경 후 파싱 진행
static bool load (const char *file_name, struct intr_frame *if_) {
struct thread *t = thread_current ();
struct ELF ehdr;
struct file *file = NULL;
off_t file_ofs;
bool success = false;
int i;
// 새로운 빈 페이지 테이블 생성
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
// 실행 파일 열기
file = filesys_open (file_name); // 여기 파싱한 이름으로 변경해야한다.
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
// ELF 헤더 검증
if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7) // ELF 매직넘버 검사
|| ehdr.e_type != 2 // Executable Check
|| ehdr.e_machine != 0x3E // amd64 -> x86-64
|| ehdr.e_version != 1 // ELF version check
// ELF Program Header Entry size, Program Hdr
|| ehdr.e_phentsize != sizeof (struct Phdr)
|| ehdr.e_phnum > 1024) { // head count check
printf ("load: %s: error loading executable\n", file_name);
goto done;
}
// 프로그램 헤더(세그먼트 정보) 읽기
file_ofs = ehdr.e_phoff; // 프로그램 헤더 테이블 위치
for (i = 0; i < ehdr.e_phnum; i++) {
struct Phdr phdr; // 프로그램 헤더 하나를 담을 변수
if (file_ofs < 0 || file_ofs > file_length (file))
goto done;
file_seek (file, file_ofs); // file의 위치를 file_ofs 위치로 이동
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
goto done;
file_ofs += sizeof phdr; // 다음 헤더 시작 위치를 가리키도록 변경
// 세그먼트 타입 확인: 각 세크먼트 타입은 프로그램 헤더 항목에 있다. 1:1 mapping
// 세그먼트 타입을 확인하고, 어떻게 처리할지 결정하는 필터 역할
switch (phdr.p_type) {
case PT_NULL: // 비어있거나 사용X
case PT_NOTE: // 주석이나 정보
case PT_PHDR: // 이미 읽었으므로 로드X
case PT_STACK: // setup_stack() 함수로 직접 스택을 만들어야함.
default:
/* Ignore this segment. */
break;
case PT_DYNAMIC: // 다음 3가지는 동적 링킹과 관련된 세그먼트.
case PT_INTERP: // pinto는 동적 링킹 지원X
case PT_SHLIB:
goto done;
case PT_LOAD: // 실제로 메모리에 로드해야 한다.
if (validate_segment (&phdr, file)) { // 세그먼트 유효한지 검사
bool writable = (phdr.p_flags & PF_W) != 0;
// 페이지 크기를 4kb로 내림
uint64_t file_page = phdr.p_offset & ~PGMASK;
// 가상 메모리 주소
uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
// 가상 주소가 페이지의 시작에서부터 얼마나 떨어져 있는지 계산(offset)
uint64_t page_offset = phdr.p_vaddr & PGMASK;
// .bss 세그먼트를 처리하기 위해 존재한다.
uint32_t read_bytes, zero_bytes;
if (phdr.p_filesz > 0) { // .bss + .data
/* Normal segment.
* Read initial part from disk and zero the rest. */
read_bytes = page_offset + phdr.p_filesz;
// 0으로 채워할 바이트 수. 4kb로 올림. .bss 처리
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz,
PGSIZE) - read_bytes);
} else { .bss
/* Entirely zero.
* Don't read anything from disk. */
read_bytes = 0;
zero_bytes = ROUND_UP (page_offset + phdr.p_memsz,
PGSIZE);
}
// 실제 로딩(배치) 수행
// 실제 메모리 공간 할당 + 가상 메모리 맵핑
if (!load_segment (file, file_page, (void *) mem_page,
read_bytes, zero_bytes, writable))
goto done;
} else
goto done;
break;
}
}
/* Set up stack. */
// 스택 공간 할당 + 스택 포인터 설정
if (!setup_stack (if_))
goto done;
/* Start address. */
if_->rip = ehdr.e_entry;
success = true;
done:
/* We arrive here whether the load is successful or not. */
file_close (file);
return success;
}
사용자로부터 입력받은 command_line (예: "ls -l foo")을 쪼개서 커널이 이해할 수 있는 데이터 형태로 가공하는 단계입니다.
가장 먼저 고려해야 할 점은 "왜 원본 문자열을 그대로 쓰면 안 되는가?" 입니다. 여기에는 방어적 프로그래밍과 메모리 보호라는 두 가지 중요한 이유가 있습니다.
strtok_r의 동작 방식strtok_r 함수는 구분자(공백 등)를 찾으면 해당 위치의 문자를 \0 (NULL)으로 덮어씌워 문자열을 잘라냅니다. 즉, 원본 데이터를 직접 수정하는 파괴적인 함수입니다.process_exec에 넘어온 f_name 포인터가 가리키는 곳이 수정 불가능한 영역(Read-Only Data Segment, 리터럴 문자열)일 수 있습니다. 이곳을 수정하려 시도하면 Page Fault가 발생하여 커널 전체가 멈출(Panic) 위험이 있습니다.결론:
palloc_get_page(0)등을 사용해 커널 메모리에 새로운 페이지를 할당받고,strlcpy로 문자열을 안전하게 복사한 뒤 작업을 수행해야 합니다.
파싱한 데이터를 담아둘 자료구조를 설계합니다. C언어의 표준 main 함수 인자를 생각하면 이해가 쉽습니다.
argc (Argument Count)argv (Argument Vector)char *argv[64] (혹은 128) 정도로 선언하면 충분합니다.argv[0]에는 항상 프로그램의 이름(예: ls)이 들어갑니다.복사한 커맨드 라인을 파싱 한 후, x86-64 Calling Convention에 맞춰 스택을 빈틈없이 채워야 합니다. 스택은 높은 주소에서 낮은 주소로(High Address → Low Address) 자란다는 점을 항상 기억해야 합니다.
전체 과정은 크게 4단계로 나뉩니다.
1. 인자 데이터(String Data) 삽입
2. Word Align (8바이트 정렬)
3. 인자 주소 배열(String Pointers) 삽입
4. 가짜 반환 주소 (Fake Return Address) & 레지스터 설정

위 다이어그램의 구조(x86-64 Calling Convention)를 코드로 구현합니다. 이 작업은 load() 내부에서 수행됩니다.
Parsing & Data Push: 문자열을 파싱하여 스택에 문자열 데이터를 밀어 넣습니다.Alignment: 8바이트 단위로 스택 포인터를 정렬합니다.Pointer Push: 문자열들의 주소(argv)를 스택에 넣습니다.Register: 프로그램 시작 시 인자를 받을 수 있도록 레지스터를 세팅합니다.수정된 load() :
static bool load (const char *cmd_line, struct intr_frame *if_) {
struct thread *t = thread_current ();
struct ELF ehdr;
struct file *file = NULL;
off_t file_ofs;
bool success = false;
int i;
char *file_name;
char *copy_cmd_line;
char *token;
int count = 0;
char *save_ptr;
char *address[64];
copy_cmd_line = palloc_get_page (0);
if (copy_cmd_line == NULL)
goto done;
strlcpy (copy_cmd_line, cmd_line, PGSIZE);
token = strtok_r(copy_cmd_line, " ", &save_ptr);
file_name = token;
if (file_name == NULL) {
goto done;
}
/* Allocate and activate page directory. */
t->pml4 = pml4_create ();
if (t->pml4 == NULL)
goto done;
process_activate (thread_current ());
/* Open executable file. */
file = filesys_open (file_name);
if (file == NULL) {
printf ("load: %s: open failed\n", file_name);
goto done;
}
// rox
file_deny_write(file);
/* Read and verify executable header. */
if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7)
|| ehdr.e_type != 2
|| ehdr.e_machine != 0x3E // amd64
|| ehdr.e_version != 1
|| ehdr.e_phentsize != sizeof (struct Phdr)
|| ehdr.e_phnum > 1024) {
printf ("load: %s: error loading executable\n", file_name);
goto done;
}
/* Read program headers. */
file_ofs = ehdr.e_phoff;
for (i = 0; i < ehdr.e_phnum; i++) {
struct Phdr phdr;
if (file_ofs < 0 || file_ofs > file_length (file))
goto done;
file_seek (file, file_ofs);
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
goto done;
file_ofs += sizeof phdr;
switch (phdr.p_type) {
case PT_NULL:
case PT_NOTE:
case PT_PHDR:
case PT_STACK:
default:
/* Ignore this segment. */
break;
case PT_DYNAMIC:
case PT_INTERP:
case PT_SHLIB:
goto done;
case PT_LOAD:
if (validate_segment (&phdr, file)) {
bool writable = (phdr.p_flags & PF_W) != 0;
uint64_t file_page = phdr.p_offset & ~PGMASK;
uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
uint64_t page_offset = phdr.p_vaddr & PGMASK;
uint32_t read_bytes, zero_bytes;
if (phdr.p_filesz > 0) {
/* Normal segment.
* Read initial part from disk and zero the rest. */
read_bytes = page_offset + phdr.p_filesz;
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
- read_bytes);
} else {
/* Entirely zero.
* Don't read anything from disk. */
read_bytes = 0;
zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
}
if (!load_segment (file, file_page, (void *) mem_page,
read_bytes, zero_bytes, writable))
goto done;
}
else
goto done;
break;
}
}
/* Set up stack. */
if (!setup_stack (if_))
goto done;
/* Start address. */
if_->rip = ehdr.e_entry;
while (token != NULL && count < 64) {
if_->rsp -= (strlen(token) + 1);
memcpy(if_->rsp, token, strlen(token) + 1);
address[count++] = if_->rsp;
token = strtok_r(NULL, " ", &save_ptr);
}
// 배열의 마지막 NULL 넣기.
address[count] = NULL;
//8바이트 정렬하기
if_->rsp = if_->rsp & ~0x7;
// 스택에 배열 넣기
for (int i = count; i >= 0; i--) {
if_->rsp -= sizeof(address[i]);
*((char **)(if_->rsp)) = address[i];
}
// 레지스터 설정하기
if_->R.rdi = count;
if_->R.rsi = if_->rsp;
if_->rsp -= sizeof(char *);
*((char **)(if_->rsp)) = 0;
success = true;
done:
if (copy_cmd_line != NULL) {
palloc_free_page(copy_cmd_line);
}
/* We arrive here whether the load is successful or not. */
if (success) {
t->hold_file = file;
} else {
file_close (file); // 무조건 닫으면 안된다. file의 쓰기 금지 락을 유지해야하기 때문이다.
}
return success;
}