어제 정리한 강의 영상은 스탠포드 핀토스 기반,
깃북은 카이스트 핀토스 기반.
구조는 다르지만 원리 자체는 비슷.
깃북에서 하라는대로 하면 된다.
char *test
를 출력할 때printf("%s",*test)
하면 됨?
printf("%s", *test)
는 잘못된 코드입니다. 그 이유를 설명하겠습니다.
포인터의 역할 이해하기
char *test
는 문자열에 대한 포인터입니다. 즉, test
는 문자열의 첫 번째 문자를 가리키는 포인터입니다.
포인터 역참조(*
)의 역할
*test
는 test
가 가리키는 값을 역참조한 것이므로, 단일 문자를 반환합니다. 즉, "Hello"
라는 문자열에서 *test
는 'H'
가 됩니다.
printf
와 서식 지정자
%s
는 문자열을 출력할 때 사용됩니다. 즉, 문자 배열의 시작 주소를 전달해야 합니다.%c
는 단일 문자를 출력할 때 사용됩니다.문자열 전체를 출력하려면 printf("%s", test)
를 사용해야 하고, 첫 번째 문자를 출력하려면 printf("%c", *test)
를 사용해야 합니다.
char형 포인터를 냅다 넣으면 알아서 해석해서 널 문자 나올때까지 출력해주는 거 맞음?
맞다고 함
inode는 유닉스 계열 운영체제에서 쓰이는 데이터 구조임.
파일의 크기, 소유자, 접근 권한, 접근과 변경 시간 등
파일이나 디렉토리에 대한 메타데이터를 저장함.
파일 시스템에서는 파일 이름과 Inode가 분리되어 있습니다. 디렉토리 엔트리는 파일 이름과 해당 파일의 Inode 번호를 저장하는 테이블입니다. 따라서 운영체제는 파일 이름을 사용하여 Inode 번호를 찾고, 그 Inode를 통해 파일의 메타데이터와 실제 데이터 위치를 파악합니다.
이 분리 덕분에, 하드 링크를 사용하여 여러 이름이 같은 Inode를 참조할 수 있습니다. 하드 링크는 새로운 파일을 생성하는 것이 아니라, 기존 Inode에 새로운 파일 이름을 연결하는 방식입니다. 이 때문에 여러 파일 이름이 같은 데이터를 참조할 수 있게 됩니다.
Inode는 파일 시스템이 생성될 때 미리 고정된 개수로 생성됩니다. 즉, 파일 시스템에서 사용할 수 있는 Inode 수는 한정되어 있습니다. 이 Inode가 부족하면, 디스크 공간이 남아 있어도 새로운 파일을 생성할 수 없게 됩니다. 이러한 문제는 작은 파일이 매우 많이 생성되는 시스템(예: 이메일 서버)에서 발생할 수 있습니다. 일부 파일 시스템(XFS, Btrfs 등)은 동적으로 Inode를 할당하여 이러한 문제를 해결할 수 있습니다.
uint8_t는 C언어에서 정확히 8비트의 부호 없는 정수(unsigned integer)를 나타내는 자료형입니다. 이 자료형은 부호 없는 8비트 값을 저장하므로, 값의 범위는 0부터 255까지입니다.
uint8_t는 stdint.h 헤더 파일에 정의되어 있으며, 정확한 크기의 정수형을 사용하여 이식성(portability)을 보장합니다.
[]는 배열을 나타냅니다. 즉, uint8_t[]는 부호 없는 8비트 정수를 담는 배열을 의미합니다.
uint8_t[]의 사용 목적
메모리 효율성: uint8_t는 8비트 크기로 작은 양의 데이터를 저장할 때 메모리 사용을 최소화할 수 있습니다.
정확한 크기 보장: uint8_t는 정확히 8비트 크기를 가지기 때문에, 다양한 플랫폼에서 정수 크기가 달라질 수 있는 문제를 방지합니다.
비트 연산: 8비트 크기의 정수는 비트 연산을 수행하는 데 유리하며, 네트워크 패킷, 파일 포맷, 암호화 등의 작업에서 많이 사용됩니다.
pml4는 왜 빨간불? : 쓰레드 선언된 곳 가보면 if USER어쩌구 돼있는 거 안에 pml4 있음
process_exec() 함수를 확장 구현해서, 지금처럼 단순히 프로그램 파일 이름만을 인자로 받아오게 하는 대신 공백을 기준으로 여러 단어로 나누어지게 만드세요.
/bin/ls -l foo bar
와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해봅시다.
- 명령을 단어들로 쪼갭시다.
/bin/ls
,l
,foo
,bar
이렇게- 이 단어들을 스택의 맨 처음 부분에 놓습니다. 순서는 상관 없습니다. 왜냐면 포인터에 의해 참조될 예정이기 때문이죠.
- 각 문자열의 주소 + 경계조건을 위한 널포인터를 스택에 오른쪽→왼쪽 순서로 푸시합니다.
**이들은argv
의 원소가 됩니다.
널포인터 경계는argv[argc]
가 널포인터라는 사실을 보장해줍니다. C언어 표준의 요구사항에 맞춰서 말이죠.
그리고 이 순서는argv[0]
이 가장 낮은 가상 주소를 가진다는 사실을 보장해줍니다.
또한 word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림하여야 합니다.%rsi
가argv
주소(argv[0]
의 주소)를 가리키게 하고,%rdi
를argc
로 설정합니다.- 마지막으로 가짜 “리턴 어드레스”를 푸시합니다 : entry 함수는 절대 리턴되지 않겠지만, 해당 스택 프레임은 다른 스택 프레임들과 같은 구조를 가져야 합니다.
어떻게 해야할지 모르겠어서 한참 동안 코드만 봤다.
아무리 봐도 감이 안 와서 일단 내가 할 수 있는거 해보기.
단어 쪼개기는 strtok_r()를 이용하면 쪼갤 수야 있다.
for (token = strtok_r(s, " ", &save_ptr); token != NULL;
token = strtok_r(NULL, " ", &save_ptr))
printf("'%s'\n", token);
주석에 나와있는 예시대로 하면 토큰들이 차례대로 출력.
이제 이걸 스택에 넣으면 된다고 하는데...
스택의 맨 처음 부분이라는게 유저 스택이라는 걸까?
어제 정리한 거 보니까 맞는듯.
근데 유저 스택에 어떻게 넣어야 되지?
load에 넣는거 자체가 유저 스택에 넣는거인듯?
인자를 여러 개 넣어야 되는데
load 안에 들어가는 인자는 file_name 뿐이다.
그리고 이건
/* Open executable file. */
file = filesys_open(file_name);
이런 식으로 쓰인다.
분명 깃북에는
첫 번째 단어는 프로그램 이름이고, 두 번째 단어는 첫 번째 인자이며, 그런 식으로 계속 이어지게 만들면 됩니다.
따라서, 함수 process_exec("grep foo bar") 는 두 개의 인자 foo와 bar을 받아서 grep 프로그램을 실행시켜야 합니다.
두 개의 인자를 받아서 실행시켜야 한다고 나와있었는데???
일단 filesys_open을 따라가보면
struct file *
filesys_open (const char *name) {
struct dir *dir = dir_open_root ();
struct inode *inode = NULL;
if (dir != NULL)
dir_lookup (dir, name, &inode);
dir_close (dir);
return file_open (inode);
}
dir에서 name을 찾아서 inode라는 거에 경로를 넣고
또 다시 그 inode를 file_open에 넣음.
정리하자면,
1. process_exec(void *f_name)의 f_name은 file_name에 담겨 load 함수에 인자로 들어간다.
2. load 함수에서 file_name은 또 다시 filesys_open로 들어간다.
3. filesys_open()에서 file_name은 dir_lookup()에 들어가 경로를 찾는 데 이용된다.
그러니까, load 함수에 인자로 들어가는 친구는 오직 첫번째 인자라는 뜻.
그럼 나머지 인자들은 어디로 가야 하는가?
/* Set up stack. */
if (!setup_stack(if_))
goto done;
밑에 보니까 유저 스택에 넣는거 있긴 한듯? 인터럽트 프레임에 인자들을 넣어야 하는건가?
/* Create a minimal stack by mapping a zeroed page at the USER_STACK */
static bool
setup_stack(struct intr_frame *if_)
{
uint8_t *kpage;
bool success = false;
kpage = palloc_get_page(PAL_USER | PAL_ZERO);
if (kpage != NULL)
{
success = install_page(((uint8_t *)USER_STACK) - PGSIZE, kpage, true);
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page(kpage);
}
return success;
}
근데 들어가봐도 딱히 뭐 없고 그냥 초기화시켜주는 거라고 함.
일단 흐름 정리해봄. 도움 되는지 안되는지 잘 모르겠.
보다보니까 load() 밑에 떡하니 여기에 인자 푸는거 하세요~ 라고 돼있음. 어이없네.
process_exec()에서 인자 떼야하는 건지, load()에서 인자 떼야 하는 건지 잘 모르겠어서
process_exec()에서 filename이 쓰이는 곳을 확인함
load에서 할 거면 안에서 따로 뜯으면 된다 치고, palloc_free_page() 이건 뭐임?
/* Frees the page at PAGE. */
void
palloc_free_page (void *page) {
palloc_free_multiple (page, 1);
}
file_name이 페이지로써 들어가는데요???
/* Frees the PAGE_CNT pages starting at PAGES. */
void
palloc_free_multiple (void *pages, size_t page_cnt) {
struct pool *pool;
size_t page_idx;
ASSERT (pg_ofs (pages) == 0);
if (pages == NULL || page_cnt == 0)
return;
if (page_from_pool (&kernel_pool, pages))
pool = &kernel_pool;
else if (page_from_pool (&user_pool, pages))
pool = &user_pool;
else
NOT_REACHED ();
page_idx = pg_no (pages) - pg_no (pool->base);
#ifndef NDEBUG
memset (pages, 0xcc, PGSIZE * page_cnt);
#endif
ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt));
bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false);
}
palloc_free_multiple 들어가봤더니
주석 빼고 뭔 말인지 하나도 모르겠
void
_start (int argc, char *argv[]) {
exit (main (argc, argv));
}
깃북 다시 읽어보니까 유저 프로그램은 여기서부터 시작한다고 함
main 클릭해보니까 test 파일(args.c)로 가짐. 흠;
void thread_yield(void)
{
struct thread *curr = thread_current();
enum intr_level old_level;
if (intr_context())
{
intr_yield_on_return();
return;
}
make check에 yield 때문에 뜨는거 이렇게 고치면 된다고 함
threads에 돌아가서 make check 돌려봤더니 통과하는거 확인
문제 없었으면 좋겠
// #ifdef USERPROG
/* Owned by userprog/process.c. */
uint64_t *pml4; /* Page map level 4 */
// #endif
pml4에 빨간 줄 뜨는거 거슬려서 주석처리해놓음
밥을 먹으며 들은 이야기
유저 스택에 넣는 방법이 rsp???
/* 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;
주석의 핵심은, 스레드 구조체 내에 있는 intr_frame을 재사용할 수 없다는 이유가, 스레드가 재스케줄링될 때 해당 intr_frame이 스레드의 실행 정보를 저장하는 데 사용되기 때문이라는 설명입니다. 즉, 이미 스레드가 현재 상태를 저장하는 용도로 사용 중이기 때문에, 다른 용도로 intr_frame을 사용할 수 없다는 의미입니다.
thread struture 안에서 intr_frame 쓸 수 없다는게 아니라 thread structure 안에 있는 intr_frame을 사용하면 안된다는 뜻
process_exec
load
only_file_name
선언하고 첫 번째 인자만 넣은 후에 filesys_open에 인자로 넣음결과 : 어림 x. 아무것도 통과 못 함.
디버깅 좀 해보려고 디버깅 문구 넣어봤는데, 출력 안됨.
곳곳에 printf문 도배해봤는데, 아무것도 출력 안됨.
헤매이다가 우연히 명령줄 잘못 입력한거 backtrace 해보니까
parse_option에서 터졌다는거 확인
parse_option 부르는 놈이 main임
여기서 모든게 시작되는거였음
entry.c 컨트롤 왼쪽 클릭 하면 args.c로 가던건 그냥 함수 이름이 똑같아서라 치고,
아무튼 init.c에 있는게 진짜 main() 맞는 것 같음.
흐름을 따라가보려는데 running_action에서 막힘.
그냥 됐다고 믿고 유저 스택에 집어넣는 걸 해봐야겠음
rsp가 뭔지 찾아봤더니
RSP(Extended Stack Pointer)
현재 스택 주소. 그러니까 스택 맨 윗쪽 주소. 스택에 있는 데이터의 주소를 지정.
라고 함.
(다음 날 TIL에 정리)