미새문지

크래프톤 정글 week08, day66 - userprog 뜯기 본문

크래프톤 정글/TIL

크래프톤 정글 week08, day66 - userprog 뜯기

문미새 2024. 3. 14. 11:01
728x90

계속 쉬운것만 잔디를 심다보니 최근에는 올리지 않는게 더 나을 것 같아 pintos가 끝날 동안엔 잔디심기를 안 올릴 것 같다.

%rdi 주로 첫 번째 인자 전달에 사용되며, 문자열이나 배열 같은 데이터의 목적지 주소를 지정하는데 사용된다.
%rsi 두 번째 인자 전달에 사용되며, 문자열이나 배열같은 데이터의 원본 주소를 지정하는데 사용된다.
%rdx 세 번째 인자 전달에 사용되며, 입출력 관련 시스템 호출에서 파일 디스크립터나 버퍼의 크기 등을 전달하는데에도 활용된다.
%rcx 네 번째 인자 전달에 사용되며, 반복(loop) 카운트를 관리하는데에 사용될 수 있다. 또한 몇 명령어에서는 반복 횟수를 지정하는데 사용된다.
%r8 다섯 번째 인자 전달에 사용되며, x86-64 아키텍쳐에서 새롭게 추가된 레지스터 중 하나로, 추가적인 인자를 전달하는데 사용된다.
%r9 여섯 번째 인자 전달에 사용되며, %r8과 마찬가지로, x86-64 아키텍쳐에서 추가된 레지스터로, 더 많은 인자를 함수에 전달한다.
x86-64 RET return의 약자이며 함수 호출 완료 후, 제어 반환한다. 수행작업은 스택 포인터로 반환된 주소 가져오기, 프로그램 카운터를 업데이트하여, 해당 주소의 명령어로 실행 흐름을 이동하는 작업이 있다.

 

userprog의 기본적인 시작은 init.c의 메인에서 시작한다.

/* Pintos main program. */
int
main (void) {
	uint64_t mem_end;
	char **argv;
	/* Clear BSS and get machine's RAM size. */
	bss_init ();
	/* Break command line into arguments and parse options. */
	argv = read_command_line ();
	argv = parse_options (argv);

	/* Initialize ourselves as a thread so we can use locks,
	   then enable console locking. */
	thread_init ();
	console_init ();

	/* Initialize memory system. */
	mem_end = palloc_init ();
	malloc_init ();
	paging_init (mem_end);

#ifdef USERPROG
	tss_init ();
	gdt_init ();
#endif
	/* Initialize interrupt handlers. */
	intr_init ();
	timer_init ();
	kbd_init ();
	input_init ();
#ifdef USERPROG
	exception_init ();
	syscall_init ();
#endif
	/* Start thread scheduler and enable interrupts. */
	thread_start ();
	serial_init_queue ();
	timer_calibrate ();

#ifdef FILESYS
	/* Initialize file system. */
	disk_init ();
	filesys_init (format_filesys);
#endif

#ifdef VM
	vm_init ();
#endif

	printf ("Boot complete.\n");

	/* Run actions specified on kernel command line. */
	run_actions (argv);

	/* Finish up. */
	if (power_off_when_done)
		power_off ();
	thread_exit ();
}

우리는 터미널에 작성한 명령어를 뜯어봐야 하기 때문에 커맨드 라인의 작업을 실행하는 run_actions로 진입

 

/* ARGV[]에 지정된 모든 액션을 널 포인터 센티널까지 실행합니다. */
static void
run_actions (char **argv) {
	/* An action. */
	struct action {
		char *name;                       /* 작업 이름 */
		int argc;                         /* # 액션 이름을 포함한 인수 개수입니다. */
		void (*function) (char **argv);   /* 액션을 실행하는 함수입니다. */
	};

	/* 지원되는 작업 표입니다. */
	static const struct action actions[] = {
		{"run", 2, run_task},
#ifdef FILESYS
		{"ls", 1, fsutil_ls},
		{"cat", 2, fsutil_cat},
		{"rm", 2, fsutil_rm},
		{"put", 2, fsutil_put},
		{"get", 2, fsutil_get},
#endif
		{NULL, 0, NULL},
	};

	while (*argv != NULL) {
		const struct action *a;
		int i;

		/* 작업 이름 찾기. */
		for (a = actions; ; a++)
			if (a->name == NULL)
				PANIC ("unknown action `%s' (use -h for help)", *argv);
			else if (!strcmp (*argv, a->name))
				break;

		/* 필수 인수가 있는지 확인합니다. */
		for (i = 1; i < a->argc; i++)
			if (argv[i] == NULL)
				PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1);

		/* 작업을 호출하고 진행합니다. */
		a->function (argv);
		argv += a->argc;
	}
}

작업을 실행시켜야 하기 때문에 {"run", 2, run_task}로 진입한다.

 

/* ARGV[1]에 지정된 작업을 실행합니다. */
static void
run_task (char **argv) {
	const char *task = argv[1];
	printf ("Executing '%s':\n", task);
	printf ("end\n");
#ifdef USERPROG
	if (thread_tests){
		run_test (task);
	} else {
		process_wait (process_create_initd (task));
	}
#else
	run_test (task);
#endif
	printf ("Execution of '%s' complete.\n", task);
}

커맨드라인에 입력한 명령어를 각각 쪼개서 인자로 받는데 args[1]은 두 번째 명령어를 뜻한다.

thread_tests가 true라면 특정 테스트를 실행하고, false라면 프로세스 생성하고 실행에 들어간다.

process_wait의 process_create_initd로 진입한다.

 

/* FILE_NAME에서 로드된 "initd"라는 첫 번째 유저랜드 프로그램을 시작합니다.
 process_create_initd()가 반환되기 전에 새 스레드가 예약될 수 있으며 종료될 수도 있습니다. 
 initd의 스레드 ID를 반환하거나, 스레드를 생성할 수 없는 경우 TID_ERROR를 반환합니다. 
 이 함수는 한 번만 호출해야 한다는 점에 유의하세요. */
tid_t
process_create_initd (const char *file_name) {
	char *fn_copy;
	tid_t tid;

	/* FILE_NAME의 복사본을 만듭니다.
	 * 그렇지 않으면 호출자와 load() 사이에 경합이 발생합니다. */
	fn_copy = palloc_get_page (0);
	if (fn_copy == NULL)
		return TID_ERROR;
	strlcpy (fn_copy, file_name, PGSIZE);

	/* 새 스레드를 생성하여 FILE_NAME을 실행합니다. */
	tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
	if (tid == TID_ERROR)
		palloc_free_page (fn_copy);
	return tid;
}

file_name을 인자로 받아 fn_copy에 복사본을 만들고 스레드를 생성하여 file_name을 실행하기 때문에 initd로 진입

 

/* 첫 번째 사용자 프로세스를 시작하는 스레드 함수입니다. */
static void
initd (void *f_name) {
#ifdef VM
	supplemental_page_table_init (&thread_current ()->spt);
#endif
	process_init ();
	if (process_exec (f_name) < 0)
		PANIC("Fail to launch initd\n");
	NOT_REACHED ();
}

VM은 project3쪽이라 아직 볼 필요가 없다. 프로세스를 생성하고 생성이 실패했으면 PANIC에 걸려 종료된다. 확인하기 위해 exec로 진입한다.

 

/* 현재 실행 컨텍스트를 f_name으로 전환합니다. 실패하면 -1을 반환합니다. */
int
process_exec (void *f_name) {
	char *file_name = f_name;
	bool success;

	// 현재 스레드 받아오기
	struct thread *cur_thread = thread_current();

	/* 스레드 구조에서는 intr_frame을 사용할 수 없습니다. 
	현재 스레드가 스케줄을 변경할 때 실행 정보를 멤버에 저장하기 때문입니다. */
	struct intr_frame _if;
	_if.ds = _if.es = _if.ss = SEL_UDSEG;
	_if.cs = SEL_UCSEG;
	_if.eflags = FLAG_IF | FLAG_MBS;
	// 토큰 선언
	char *token, *save_ptr;

	/* 먼저 현재 컨텍스트를 죽입니다. */
	process_cleanup ();

	// 명령줄 인자 배열
	char *argv[10];
	// 인자 개수
	int *argc;
	// 인자 셀거
	int i = 0;

	for (token = strtok_r (file_name, " ", &save_ptr); token != NULL && i < 10;
   token = strtok_r (NULL, " ", &save_ptr)) {
		argv[i] = token;
    	// printf("'%s'\n", token);
    	i++;
   }
	argc = i; // 인자 개수 설정
	printf("%s\n", token);

	/* 그런 다음 바이너리를 로드합니다. */
	success = load (file_name, &_if);

	

	/* 로드에 실패하면 종료합니다. */
	palloc_free_page (file_name);
	if (!success)
		return -1;

	/* 전환된 프로세스를 시작합니다. */
	do_iret (&_if);
	NOT_REACHED ();
}

exec에서 인자를 받아와 저장 후 스택에 넣어줘야 하는데 그 부분은 아직 코드 작성 중이다.  token 변수를 사용해서 인자들을 받아왔지만 스택 코드를 어떻게 작성해야 하는지 몰라서 막혔다.

 

어제 TIL을 못써서 오늘 아침에 작성했기 때문에 다 쓰고 다시 코드 작성 진행하려고 한다.

학습 시간 : 10 ~ 26시

728x90