Userprogram
사용자메모리접근
커널은 사용자가 제공한 포인터로 메모리 접근할 필요가 있다.
사용자가 NULL포인터, 매핑되지 않은 가상메모리, 커널영역메모리
에 대한 포인터를 제공하면 이를 오류로 판단하고
- 자원을 해제한다음
- 프로세스를 종료 해야한다.
유저프로그램의 모든영역에 해당하므로 포인터를 넘기는
시스템콜에는 적용해주도록 하자
코드
간단한 버전(통과는 함)
// pintos/userprogram/systemcall.c
static void check_address(const void *addr)
{
if (addr == NULL || !is_user_vaddr(addr) ||
pml4_get_page(thread_current()->pml4, addr) == NULL)
{
exit(-1); // 프로세스 강제 종료
}
}
- if문에서 null, 커널영역, 페이지가 할당되었는지 판단
- 첫 1바이트만 검사하므로 완전한 검사는 아님
바이트마다 검사 + 커널영역으로 복사 후 사용
// pintos/userprogram/systemcall.c
bool copy_user_string(char *dst, const char *src, size_t max_len)
{
for (size_t i = 0; i < max_len; i++)
{
/* 매 바이트 접근 전에 해당 주소가 사용자 영역인지 검사한다. */
check_address(src + i);
char c = src[i];
dst[i] = c;
/* NULL 문자를 만났다면 복사가 완료된 것. */
if (c == '\0')
{
return true;
}
}
/* 문자열이 최대 허용 길이 안에서 끝나지 않았음. */
return false;
}
📌Argument Passing
| 주소 | 이름 | 데이터 | 타입 |
|---|---|---|---|
| 0x4747fffc | argv[3][…] | ‘bar\0’ | char[4] |
| 0x4747fff8 | argv[2][…] | ‘foo\0’ | char[4] |
| 0x4747fff5 | argv[1][…] | ‘-l\0’ | char[3] |
| 0x4747ffed | argv[0][…] | ‘/bin/ls\0’ | char[8] |
| 0x4747ffe8 | 워드 정렬 | 0 | uint8_t[] |
| 0x4747ffe0 | argv[4] | 0 | char * |
| 0x4747ffd8 | argv[3] | 0x4747fffc | char * |
| 0x4747ffd0 | argv[2] | 0x4747fff8 | char * |
| 0x4747ffc8 | argv[1] | 0x4747fff5 | char * |
| 0x4747ffc0 | argv[0] | 0x4747ffed | char * |
| 0x4747ffb8 | 반환 주소 | 0 | void (*)() |
| - 워드정렬은 성능향상 | |||
| - argv[argc] 는 규약 | |||
| - 반환주소도 규약 | |||
| ### 인자전달이란?? | |||
| 커널영역에서는 사용자프로그램을 실행시킬때 인자들을 | |||
| 사용자영역 스택에 미리 세팅을 해주어야한다. |
그래야 사용자모드로 넘어가서 사용자스택을 이용해 인자를
사용할 수 있다.
코드
process_exec() 함수에서 현재 스레드를 정리하고
프로그램을 로드할 때 추가해주면 된다.
필요한 과정은 arg_program arg1 arg2 arg3 이런식으로 입력명령이 들어오면
- 파싱한 후
- 유저메모리에 세팅
해주면 된다.
파싱은 내장된 strtok_r 함수를 사용하면 되는데
원본 문자열에 영향이 가므로 재사용 할 거면 복사한 후 사용
유저메모리 세팅은 유저스택의 상단 포인터를 가지고,
위 표에 맞게 데이터를 적재하면 된다.
load 함수
// userprog/process.c
static bool load(const char *file_name, struct intr_frame *if_) // echo 1 2
{
/* intr_frame은 유저프로그램을
시작하기전에 cpu 레지스터를 저장할 구조체 */
(생략)
uint64_t *addr[32]; // 인자를 저장할 문자열집합
// addr에 인자를 저장하고 인자의 개수를 반환
int argc = parse_args(file_name, addr);
(생략)
// 실행된 프로그램은 쓰기 작업 불가
file_deny_write(file);
/* 유저스택 최상단 세팅 */
if_->rip = ehdr.e_entry;
// 유저스택에 데이터를 적재하는 함수
setup_stack_args(if_, addr, argc);
success = true;
t->exec_file = file;
// hex_dump를 이용해 디버깅.
print_dump(if_, 128);
(생략)
return success;
}
load 함수는 레지스터 및 페이지를 세팅하는 함수
이때 스택도 세팅
pars_args(cmdline, argv)
// "argument-test 1 2 3 4" → ["argument-test", "1", "2", "3", "4"]
static int parse_args(char *cmdline, char **argv)
{
int argc = 0;
char *token, *save_ptr;
for (token = strtok_r(cmdline, " ", &save_ptr); token != NULL;
token = strtok_r(NULL, " ", &save_ptr))
{
argv[argc++] = token;
}
return argc;
}
setup_stack_args(if, argv, argc)
static void setup_stack_args(struct intr_frame *if_, char **argv, int argc)
{
uint8_t *ptr = (uint8_t *)if_->rsp; // 1바이트 단위 포인터
uint64_t *addr[32]; // 인자 최대 32개
// 1. 문자열들 복사
for (int i = argc - 1; i >= 0; i--)
{
int len = strlen(argv[i]);
ptr -= (len + 1);
memcpy(ptr, argv[i], len + 1);
addr[i] = (uint64_t *)ptr;
}
ptr = (uint8_t *)((uintptr_t)ptr & ~0xF); // align
ptr -= 8;
*(uint64_t *)ptr = 0; // 마지막인자 0
for (int i = argc - 1; i >= 0; i--)
{
ptr -= 8;
*(uint64_t *)ptr = (uint64_t *)addr[i];
}
ptr -= 8;
*(uint64_t *)ptr = 0; // fake address
if_->R.rsi = (uint64_t)(ptr + 8);
if_->R.rdi = argc;
if_->rsp = (uint64_t)ptr;
}'Develop' 카테고리의 다른 글
| MessageQueue를 써야하는 이유 (+카프카 사용후기) (0) | 2026.01.02 |
|---|---|
| node.js는 어떤식으로 동작할까? (0) | 2025.10.17 |
| Pintos_project1 - Priority (0) | 2025.09.10 |
| Pintos_project1 - Alarm Clock (0) | 2025.09.09 |
| malloc lab-implicit list에 관하여 + 구현 (3) | 2025.08.23 |