구현하기

PintOS #1 : 진행 흐름도 파악하기

pwerty 2025. 5. 12. 23:49

정글의 커리큘럼으로 진행으로 진행 중인 PintOS의 테스트 데이터가 어떻게 입력되고 작동되는지에 대해 논해보겠다. 우리는 64bit의 KAIST PintOS에 대해 다루고 있다. 이 논함은 정규 커리큘럼은 아니고 개인 호기심에 의한다.

2025.05.10 - [구현하기] - PintOS #0 : 서론

 

PintOS #0 : 서론

넋이 다 나가는 와중에 최악의 영역에 진입했다. 농담으로 퇴소언급을 했지만 이정도로 막막한 정도가 차원이 다르다.예전에는 뭘 하면서 임하다보면 끝이 없을 것 같다는 분량이었지만 이젠

hyeonistic.tistory.com

계속 보고 있으니 이 내용은 테스트 케이스 분야를 비롯한 다양한 부분에 영향을 받는듯하다. 다시 말해 앞으로 이어질 커리큘럼도 일관된 내용으로 테스트 케이스가 작동하리라는 보장은 없다는 것.

 

pintos-kaist/threads/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);

	/* Initialize interrupt handlers. */
	intr_init ();
	timer_init ();
	kbd_init ();
	input_init ();

	thread_start ();
	serial_init_queue ();
	timer_calibrate ();

	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 ();
}

크게 세 가지로 나눌 수 있다 :

_init()을 비롯한 초기화 함수

각기 앞에 필요한 주제에 따른 초기화를 진행한다. 예를 들어 intr_init(); 은 인터럽트에 관한 초기화이다. 이런식으로 초기화하는 것은 쓰레드, 콘솔, palloc, malloc, 페이징, 인터럽트, 타이머, kbd, 기본 입력이 수행된다.

이어 thread_start(), serial_init_queue(), timer_calibrate() 를 연달아 호출하며 쓰레드에 대한 스케줄러와 인터럽트를 허용한다.

 

 

power_off

그냥 뭐 정직하게 컴퓨터를 끈다. 이 함수에서는 QEMU에 대한 Power off command를 보내고 종료하게 된다 :

void power_off (void) {

	print_stats (); // PintOS 실행에 대한 정보 출력 후 종료.

	printf ("Powering off...\n");
	outw (0x604, 0x2000);               /* QEMU 종료 커맨드*/
	for (;;);
}

 

run_actions

static void run_actions (char **argv) {
	/* An action. */
	struct action {
		char *name;                       /* Action name. */
		int argc;                         /* # of args, including action name. */
		void (*function) (char **argv);   /* Function to execute action. */
	};

	/* Table of supported actions. */
	static const struct action actions[] = {
		{"run", 2, run_task},
		{NULL, 0, NULL},
	};

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

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

		/* Check for required arguments. */
		for (i = 1; i < a->argc; i++)
			if (argv[i] == NULL)
				PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1);

		/* Invoke action and advance. */
		a->function (argv);
		argv += a->argc;
	}
}

진행도가 있을 만한 것만 보자. 사실 이 내용은 실제 pintos 실행 시 입력 받는 매개변수를 구분하고 그에 따른 커맨드를 구분짓는다.

그래서 매개변수 내용으로 실행할 테스트 케이스를 입력하게 되고 그에 따라 테스트 케이스가 작동한다.

우리가 봐야 할 내용은 run_task 이다.

static void run_task (char **argv) {
	const char *task = argv[1];
	printf ("Executing '%s':\n", task);

	if (thread_tests){
		run_test (task);
	} else {
		process_wait (process_create_initd (task));
	}
	run_test (task);
	printf ("Execution of '%s' complete.\n", task);
}

그냥 입력받는 내용에 대해 바로 run_test 를 진행하고 있다. 이제보니 run_test(tasks); 구문이 반복되는데, 이에 대한 이유 분석을 생각해 봐야 할 것 같다.. 이제 tests.c에 넘어와서 이어서 내용을 진행한다.

pintos-kaist/testgs/threads/tests.c

struct test 
  {
    const char *name;
    test_func *function;
  };

static const struct test tests[] = 
  {
    {"alarm-single", test_alarm_single},
    {"alarm-multiple", test_alarm_multiple},
    {"alarm-simultaneous", test_alarm_simultaneous},
    {"alarm-priority", test_alarm_priority},
    {"alarm-zero", test_alarm_zero},
    {"alarm-negative", test_alarm_negative},
    {"priority-change", test_priority_change},
    {"priority-donate-one", test_priority_donate_one},
    {"priority-donate-multiple", test_priority_donate_multiple},
    {"priority-donate-multiple2", test_priority_donate_multiple2},
    {"priority-donate-nest", test_priority_donate_nest},
    {"priority-donate-sema", test_priority_donate_sema},
    {"priority-donate-lower", test_priority_donate_lower},
    {"priority-donate-chain", test_priority_donate_chain},
    {"priority-fifo", test_priority_fifo},
    {"priority-preempt", test_priority_preempt},
    {"priority-sema", test_priority_sema},
    {"priority-condvar", test_priority_condvar},
    {"mlfqs-load-1", test_mlfqs_load_1},
    {"mlfqs-load-60", test_mlfqs_load_60},
    {"mlfqs-load-avg", test_mlfqs_load_avg},
    {"mlfqs-recent-1", test_mlfqs_recent_1},
    {"mlfqs-fair-2", test_mlfqs_fair_2},
    {"mlfqs-fair-20", test_mlfqs_fair_20},
    {"mlfqs-nice-2", test_mlfqs_nice_2},
    {"mlfqs-nice-10", test_mlfqs_nice_10},
    {"mlfqs-block", test_mlfqs_block},
  };

static const char *test_name;

이러한 테스트 이름 매핑을 우선 사용한다.

void run_test (const char *name) 
{
  const struct test *t;

  for (t = tests; t < tests + sizeof tests / sizeof *tests; t++)
    if (!strcmp (name, t->name))
      {
        test_name = name;
        msg ("begin");
        t->function ();
        msg ("end");
        return;
      }
  PANIC ("no test named \"%s\"", name);
}

t->function(); 에서 실행하고자 하는 function()이 제대로 시작된다. 을 예로 들어보자.

pintos --gdb -- -q run alarm-single

매핑된 함수는 유의미한 함수가 존재하는 파일로 연결된다. 그리고 실질적인 내용이 진행된다.

우린 위에 있는 tests 에서 alarm-single 이 test_alarm_single 로 연결 되는 것을 보았다. 

pintos-kaist/tests/threads/alarm-wait.c

void test_alarm_single (void) 
{
  test_sleep (5, 1);
}

이제 실제 코드가 이루어진다. 자세한 내용은 진짜 PintOS 코드에서 확인해보길 기대한다.