[NOTE] 인자 전달 과정 확인해보기 1편 ‐ fsutil_put() 실행 과정 - Week9-11-team03/Pintos-User-Program GitHub Wiki

실습을 위한 명령어

pintos -v -k -T 6000 -m 20 --gdb --fs-disk=10 -p tests/userprog/args-none:args-none -- -q -f run args-none

  • -v: verbose mde. show VGA display output
  • -k: display call stack when kernel panic occured
  • —m 20: memory size
  • —fs-disk=10: set file system disk size to 10MB
  • -p tests/userprog/args-single:args-sinngle: copy tests/userprog/args-single into file system as name of args-single
  • —q: quite option. turn off the kernel when the run is over
  • -f: format file system

Step1. 실행되는 함수 순서 보기

안녕하세요, 저는 여러분에게 커널의 응용 프로그램 실행 순서를 보여드릴 파일 요정입니다. 먼저 아래 코드는 아시겠죠? 이 run action을 타고 들어가면 우리는 다음과 같은 argv 포인터를 볼 수 있습니다. 보시다시피, 우리는 위에서 args-none만 디스크에 카피해 왔는데, 이게 로드하는 과정에 언제! put과 run이 이렇게 추가가 된 걸까요??

run_actions (argv); 

Screenshot 2025-05-16 at 4.39.37 PM.png

“put은 언제 꼽사리를 끼었나.”

Screenshot 2025-05-16 at 4.43.07 PM.png

딱 걸렸습니다! 바로 기존 커널 커맨드 -q -f run args-none가 argv로 넘어간 상태에서, parse_options를 실행한 순간 argv에 put이 들어 갔습니다! 일단 이 친구가 어떻게, 왜, 이렇게 파싱을 했는지는 이따가 더 알아보도록 하죠

parse_option은 응용 프로그램 실행을 위한 커맨드를 어떻게, 왜 그렇게 파싱하는지 알아보기

아무튼 이렇게 파싱이 된 argv는 그대로 흘러가서 아래에서 파싱되며 커널은 ‘put’ 액션이 주어졌음을 알게 됩니다. 그러면 그때 필요한 인수를 action 구조체로 구성된 actions 배열에서 확인합니다. put에 필요한 인수는 자기자신 포함 2개라서 그것만 체크하고 다음, invoke action and advance를 위한 a→function(argv)로 넘어가게 됩니다.

Screenshot 2025-05-16 at 4.47.00 PM.png

이때! a→function은 put을 키로 사용해 매핑된 fsutil_put이고, argv는 아까 설정된 put, args-none, run, args-none 전체입니다.

자, 이제 fsutil_put으로 넘어왔습니다. 대체 이 친구가 뭐 하는 친구인지 주석을 통해 알아봅시다.

/* Copies from the "scratch" disk, hdc or hd1:0 to file ARGV[1]
 * in the file system.
 *
 * The current sector on the scratch disk must begin with the
 * string "PUT\0" followed by a 32-bit little-endian integer
 * indicating the file size in bytes.  Subsequent sectors hold
 * the file content.
 *
 * The first call to this function will read starting at the
 * beginning of the scratch disk.  Later calls advance across the
 * disk.  This disk position is independent of that used for
 * fsutil_get(), so all `put's should precede all `get's. */

자 그렇다면 여기서 일어나는 일을 순서대로 한 번 봅시다.

Screenshot 2025-05-16 at 10.26.39 PM.png

이렇게 512바이트를 말록해서 버퍼에 할당해줍니다.

Screenshot 2025-05-16 at 10.27.51 PM.png

다음으로 소스 디스크를 열고, 거기서 파일 사이즈를 읽어 옵니다.

Screenshot 2025-05-16 at 10.28.49 PM.png

그 다음, 파일 사이즈를 읽는다고 하는데, 이건 무슨 의미일까요?

/* Reads sector SEC_NO from disk D into BUFFER, which must have
   room for DISK_SECTOR_SIZE bytes.
   Internally synchronizes accesses to disks, so external
   per-disk locking is unneeded. */
void
disk_read (struct disk *d, disk_sector_t sec_no, void *buffer) {
	struct channel *c;

	ASSERT (d != NULL);
	ASSERT (buffer != NULL);

	c = d->channel;
	lock_acquire (&c->lock);
	select_sector (d, sec_no);
	issue_pio_command (c, CMD_READ_SECTOR_RETRY);
	sema_down (&c->completion_wait);
	if (!wait_while_busy (d))
		PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
	input_sector (c, buffer);
	d->read_cnt++;
	lock_release (&c->lock);
}

disk_read 함수는 이렇게 생겼습니다. 주석을 통해 몇 가지 사실을 알 수 있군요

첫째, 이 함수는 디스크 섹터 사이즈만큼의 크기를 가진 버퍼에다가 디스크의 파일을 복사해 옵니다. 하나의 섹터에만 한정되는 것으로 보이는 군요. 하긴, 한 번에 전체 디스크의 파일을 버퍼로 가져오는 건 50년 후에나 가능하겠죠?

둘째, 이 함수는 내부적으로 디스크 접근을 동기화하기 때문에 disk_read 자체를 여러 쓰레드에서 사용할 때 동기화 시킬 필요는 없다고 합니다.

아무튼 이렇게 해서, 우리가 실행하려는 응용 프로그램의 executable object file이 우리의 파일 시스템으로 복사되겠죠?

  • 도대체 섹터는 뭔가요?

    디스크 섹터란?

    디스크 섹터는 하드 디스크의 가장 작은 물리적 저장 단위입니다:

    1. 정의: 디스크에서 한 번에 읽거나 쓸 수 있는 고정된 크기의 데이터 블록입니다.
    2. 크기: 전통적으로 512바이트였으나, 현대 디스크에서는 4KB 등 더 큰 크기를 사용하기도 합니다.
    3. 물리적 구조: 하드 디스크에서는 동심원 트랙의 일부분을 차지합니다.

    Pintos에서 **DISK_SECTOR_SIZE**는 일반적으로 512바이트로 정의됩니다.

    disk_read 함수 분석

    disk_read 함수는 다음과 같이 동작합니다:

    1. 특정 디스크(d)에서
    2. 특정 섹터 번호(sec_no)의 데이터를
    3. 제공된 버퍼(buffer)로 읽어옵니다

    이 함수는 한 번에 하나의 섹터만 읽습니다. 맞게 지적하셨듯이, 전체 디스크를 한 번에 읽는 것은 메모리 제약 때문에 불가능합니다. 큰 파일을 읽으려면 여러 섹터를 순차적으로 읽어야 합니다.

이제 실행 파일 복사 과정입니다.

fsutil_put 함수를 통해 실행 파일(executable object file)이 파일 시스템으로 복사되는 과정은 다음과 같습니다:

  1. 첫 섹터에서 파일 크기를 읽습니다.
  2. 파일 시스템에 해당 크기의 파일을 생성합니다.
  3. 이후 섹터들에서 실제 파일 내용을 순차적으로 읽어 파일에 기록합니다:
while (size > 0) {
    int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size;
    disk_read(src, sector++, buffer);
    file_write(dst, buffer, chunk_size);
    size -= chunk_size;
}

이 과정을 통해 디스크의 실행 파일이 파일 시스템으로 복사되고, 이후 이 파일은 파일 시스템의 일부로서 filesys_open 함수로 열어 실행할 수 있게 됩니다. 아하! 이게 fsutil_put의 주석에 있던 내용 중 “Later calls”에 해당하는 군요! 사이즈를 청크 사이즈만큼 줄여가면서 전체 섹터를 가로질러, 디스크의 내용을 버퍼로 복사(disk_read)하고, 그것을 다시 파일 시스템 내의 목적지 파일로 복사(file_write)하는 것으로 보입니다.

  • 퍼플렉시티도 얼추 맞다고 합니다!

    맞습니다! 정확히 이해하셨습니다. fsutil_put 함수의 주석에 있는 "Later calls advance across the disk"는 바로 이 과정을 설명하는 것입니다.

    함수의 동작 과정을 좀 더 자세히 살펴보면:

    1. 정적 변수 sector를 사용해 현재 읽고 있는 디스크 섹터의 위치를 추적합니다.
    2. 첫 번째 호출에서는 섹터 0부터 시작하여 파일 시그니처("PUT")와 크기를 읽습니다.
    3. 그 다음 루프에서는 sector++를 통해 섹터 위치를 계속 증가시키면서:
      • 디스크에서 데이터를 청크 단위로 읽고(disk_read)
      • 파일 시스템의 대상 파일에 쓰는(file_write) 작업을 반복합니다.
    4. 이 과정은 전체 파일 크기(size)가 0이 될 때까지 계속됩니다.

    각 반복에서 처리할 데이터 크기(chunk_size)는 남은 파일 크기와 디스크 섹터 크기 중 작은 값으로 결정됩니다. 이렇게 함으로써 마지막 청크에서 필요한 만큼만 정확히 복사할 수 있습니다.

    이 방식은 메모리 효율적이며, 파일 크기에 상관없이 일정한 크기의 버퍼만 사용하여 큰 파일도 처리할 수 있게 해줍니다. 또한 정적 변수 sector를 통해 연속적인 호출 간에 디스크 위치를 유지함으로써, 여러 파일을 순차적으로 복사할 수 있게 해줍니다.

    이 과정이 완료되면, 파일은 파일 시스템에 완전히 복사되어 filesys_open 함수로 열고 실행할 수 있게 됩니다.


    Perplexity로부터의 답변: [pplx.ai/share](https://www.perplexity.ai/search/pplx.ai/share)

* The first call to this function will read starting at the
 * beginning of the scratch disk.  Later calls advance across the
 * disk"

파일 크기를 미리 읽는 이유는 파일 시스템에 정확한 크기의 파일을 생성하기 위함입니다. 이는 파일 시스템의 공간 할당과 관리에 중요합니다.

이 과정이 끝나면, 파일을 닫아 주고, 버퍼를 프리 해줍니다.

Screenshot 2025-05-16 at 11.06.18 PM.png

자, 이렇게 put args-none이 끝났습니다! 우리는 이제 성공적으로 args-none 실행 파일을 파일 시스템에 로드했습니다.

  • 내용 검증

    네, 진술의 내용은 대체로 정확합니다. pintos 프로젝트에서 fsutil_put 연산은 사용자가 프로그램 실행 파일을 파일 시스템에 올릴 때 사용하는 커맨드입니다.

    아래에서 과정과 진술의 적절성을 단계별로 확인하겠습니다.

    1. fsutil_put 연산의 과정

    • 실행 파일을 읽어옴: 호스트(로컬) 시스템에서 실행 파일(executable, 예: args-none)을 읽기 위해 파일을 엽니다.
    • 파일 시스템에 씀: pintos의 파일 시스템 파티션 이미지에 파일을 씁니다.
    • 파일 닫기: 복사가 끝나면 소스 파일과 타겟 파일(파일 시스템 내)을 닫아줍니다.
    • 버퍼 해제: 복사/쓰기 과정에서 메모리 버퍼를 사용했다면, 그 버퍼도 해제(free)합니다.

    이후, fsutil_put args-none 커맨드가 끝나면 해당 실행 파일이 pintos 파일 시스템 내에 정상적으로 들어간 것입니다.

    2. 진술의 적합성

    • "파일을 닫아 주고, 버퍼를 프리 해줍니다."

      맞는 설명입니다. I/O 연산에서 자주 등장하는 자원 해제 패턴입니다.

    • "put args-none이 끝났습니다. 성공적으로 실행 파일을 파일 시스템에 로드했습니다."

      맞는 설명입니다. fsutil_put의 목적과 성공적인 완료에 따른 상황 설명입니다.

    3. 추가 설명

    하지만 아직 pintos 실행 환경에서 실제로 args-none을 실행한 것은 아닙니다.

    즉, "파일 시스템에 올릴 수 있었다"는 것과 "pintos에서 해당 프로그램을 실행했다"는 것은 다릅니다.

    실행은 pintos 커널에서 해당 파일을 run 등으로 실행했을 때 일어납니다.

    결론

    • fsutil_put 연산의 끝에서 언급한 행위(파일 닫기, 버퍼 해제)는 맞는 설명입니다.
    • "성공적으로 파일 시스템에 로드함"도 맞지만, 아직 "실행"한 것은 아님에 유의하세요.

    필요하다면 더 구체적인 파일 복사 흐름이나 pintos 명령어 사용법, 실행 절차에 대해 상세히 설명드릴 수 있습니다!


    Perplexity로부터의 답변: [pplx.ai/share](https://www.perplexity.ai/search/pplx.ai/share)