크래프톤 정글 TIL : 0906

lazyArtisan·2024년 9월 6일
0

정글 TIL

목록 보기
68/147

📚 Journal


어제 정리한 강의 영상은 스탠포드 핀토스 기반,
깃북은 카이스트 핀토스 기반.
구조는 다르지만 원리 자체는 비슷.
깃북에서 하라는대로 하면 된다.



📝 배운 것들


🏷️ C 문자열 출력 방법

char *test를 출력할 때 printf("%s",*test) 하면 됨?

printf("%s", *test)는 잘못된 코드입니다. 그 이유를 설명하겠습니다.

  1. 포인터의 역할 이해하기
    char *test는 문자열에 대한 포인터입니다. 즉, test는 문자열의 첫 번째 문자를 가리키는 포인터입니다.

  2. 포인터 역참조(*)의 역할
    *testtest가 가리키는 값을 역참조한 것이므로, 단일 문자를 반환합니다. 즉, "Hello"라는 문자열에서 *test'H'가 됩니다.

  3. printf와 서식 지정자

  • %s는 문자열을 출력할 때 사용됩니다. 즉, 문자 배열의 시작 주소를 전달해야 합니다.
  • %c는 단일 문자를 출력할 때 사용됩니다.

문자열 전체를 출력하려면 printf("%s", test)를 사용해야 하고, 첫 번째 문자를 출력하려면 printf("%c", *test)를 사용해야 합니다.

char형 포인터를 냅다 넣으면 알아서 해석해서 널 문자 나올때까지 출력해주는 거 맞음?

맞다고 함

🏷️ inode

inode는 유닉스 계열 운영체제에서 쓰이는 데이터 구조임.
파일의 크기, 소유자, 접근 권한, 접근과 변경 시간 등
파일이나 디렉토리에 대한 메타데이터를 저장함.

Inode와 파일 이름

파일 시스템에서는 파일 이름과 Inode가 분리되어 있습니다. 디렉토리 엔트리는 파일 이름과 해당 파일의 Inode 번호를 저장하는 테이블입니다. 따라서 운영체제는 파일 이름을 사용하여 Inode 번호를 찾고, 그 Inode를 통해 파일의 메타데이터와 실제 데이터 위치를 파악합니다.

이 분리 덕분에, 하드 링크를 사용하여 여러 이름이 같은 Inode를 참조할 수 있습니다. 하드 링크는 새로운 파일을 생성하는 것이 아니라, 기존 Inode에 새로운 파일 이름을 연결하는 방식입니다. 이 때문에 여러 파일 이름이 같은 데이터를 참조할 수 있게 됩니다.

Inode와 파일 시스템

Inode는 파일 시스템이 생성될 때 미리 고정된 개수로 생성됩니다. 즉, 파일 시스템에서 사용할 수 있는 Inode 수는 한정되어 있습니다. 이 Inode가 부족하면, 디스크 공간이 남아 있어도 새로운 파일을 생성할 수 없게 됩니다. 이러한 문제는 작은 파일이 매우 많이 생성되는 시스템(예: 이메일 서버)에서 발생할 수 있습니다. 일부 파일 시스템(XFS, Btrfs 등)은 동적으로 Inode를 할당하여 이러한 문제를 해결할 수 있습니다.

🏷️ uint8_t[]

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비트 크기의 정수는 비트 연산을 수행하는 데 유리하며, 네트워크 패킷, 파일 포맷, 암호화 등의 작업에서 많이 사용됩니다.



🖥️ PintOS


🔷 Argument passing

1. 구현 시작


pml4는 왜 빨간불? : 쓰레드 선언된 곳 가보면 if USER어쩌구 돼있는 거 안에 pml4 있음

process_exec() 함수를 확장 구현해서, 지금처럼 단순히 프로그램 파일 이름만을 인자로 받아오게 하는 대신 공백을 기준으로 여러 단어로 나누어지게 만드세요.

/bin/ls -l foo bar 와 같은 명령이 주어졌을 때, 인자들을 어떻게 다뤄야 하는지 생각해봅시다.

  1. 명령을 단어들로 쪼갭시다. /bin/lslfoobar 이렇게
  2. 이 단어들을 스택의 맨 처음 부분에 놓습니다. 순서는 상관 없습니다. 왜냐면 포인터에 의해 참조될 예정이기 때문이죠.
  3. 각 문자열의 주소 + 경계조건을 위한 널포인터를 스택에 오른쪽→왼쪽 순서로 푸시합니다.
    **이들은 argv의 원소가 됩니다.
    널포인터 경계는 argv[argc] 가 널포인터라는 사실을 보장해줍니다. C언어 표준의 요구사항에 맞춰서 말이죠.
    그리고 이 순서는 argv[0]이 가장 낮은 가상 주소를 가진다는 사실을 보장해줍니다.
    또한 word 크기에 정렬된 접근이 정렬되지 않은 접근보다 빠르므로, 최고의 성능을 위해서는 스택에 첫 푸시가 발생하기 전에 스택포인터를 8의 배수로 반올림하여야 합니다.
  4. %rsiargv 주소(argv[0]의 주소)를 가리키게 하고, %rdiargc 로 설정합니다.
  5. 마지막으로 가짜 “리턴 어드레스”를 푸시합니다 : 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() 밑에 떡하니 여기에 인자 푸는거 하세요~ 라고 돼있음. 어이없네.

  1. process_exec()에서 filename에서 인자를 떼고 파일 이름만 받는다. << 일단 이거부터 하기
  2. load()에서 인자들을 스택에 넣는다??

process_exec()에서 인자 떼야하는 건지, load()에서 인자 떼야 하는 건지 잘 모르겠어서
process_exec()에서 filename이 쓰이는 곳을 확인함

  1. load()에 들어감
  2. palloc_free_page()에 들어감

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)로 가짐. 흠;

yield 오류

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???

proc_exec 안에 있는 intr_frame 주석

	/* 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에는 인자 안 쪼개고 전부 다 넣음
  • palloc_free_page에는 첫번째 인자만 넣음

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에 정리)

0개의 댓글