[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);
“put은 언제 꼽사리를 끼었나.”
딱 걸렸습니다! 바로 기존 커널 커맨드 -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)로 넘어가게 됩니다.
이때! 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. */
-
퍼플렉시티의 답변
fsutil_put 함수 분석
이 코드는 Pintos 운영체제의 파일 시스템 유틸리티 중 하나인
fsutil_put
함수를 구현한 것입니다. 이 함수는 스크래치 디스크(hdc 또는 hd1:0)에서 파일 시스템으로 파일을 복사하는 기능을 수행합니다.함수 목적
fsutil_put
함수는 외부 디스크(스크래치 디스크)에 저장된 데이터를 파일 시스템 내의 파일로 복사하는 역할을 합니다. 이는 주로 테스트나 개발 과정에서 외부 데이터를 Pintos 파일 시스템으로 가져올 때 사용됩니다.동작 과정
- 초기화 및 메모리 할당:
- 정적 변수
sector
를 사용하여 현재 읽고 있는 디스크 섹터 위치를 추적합니다. - 디스크 섹터 크기(DISK_SECTOR_SIZE)만큼의 버퍼를 할당합니다.
- 정적 변수
- 소스 디스크 열기:
disk_get(1, 0)
을 호출하여 스크래치 디스크(hdc 또는 hd1:0)를 엽니다.
- 파일 크기 읽기:
- 첫 번째 섹터를 읽어 "PUT\0" 시그니처를 확인합니다.
- 시그니처 다음에 오는 32비트 정수를 파일 크기로 해석합니다.
- 대상 파일 생성:
filesys_create
를 호출하여 지정된 이름과 크기로 파일 시스템에 새 파일을 생성합니다.filesys_open
으로 생성된 파일을 엽니다.
- 데이터 복사:
- 파일 크기만큼 디스크에서 데이터를 읽어 파일에 기록합니다.
- 한 번에 최대 DISK_SECTOR_SIZE 바이트씩 복사합니다.
- 각 읽기 후 섹터 위치(sector)를 증가시킵니다.
- 정리:
- 파일을 닫고 할당된 버퍼를 해제합니다.
주요 특징
- 섹터 추적: 정적 변수
sector
를 사용하여 연속적인 호출 간에 디스크 위치를 유지합니다. - 데이터 형식: 스크래치 디스크의 첫 섹터는 "PUT\0" 시그니처와 파일 크기를 포함해야 합니다.
- 독립적인 디스크 위치: 주석에 언급된 대로, 이 함수가 사용하는 디스크 위치는
fsutil_get()
이 사용하는 위치와 독립적입니다. 따라서 모든put
작업은 모든get
작업보다 먼저 수행되어야 합니다. - 오류 처리: 메모리 할당, 디스크 열기, 파일 생성/열기, 파일 쓰기 등의 작업에서 오류가 발생하면 PANIC을 호출하여 시스템을 중단시킵니다.
이 함수는 Pintos 교육용 운영체제에서 외부 데이터를 파일 시스템으로 가져오는 기본적인 메커니즘을 제공합니다.
Perplexity로부터의 답변: [pplx.ai/share](https://www.perplexity.ai/search/pplx.ai/share)
- 초기화 및 메모리 할당:
-
디스크의 파일을 파일시스템으로 복사하게 되면 이제 Open을 할 수 있는 건가? 맞다고 합니다.
디스크(예: scratch disk)에서 파일을 파일시스템으로 복사하면, 해당 파일은 이제 파일시스템의 데이터 영역과 디렉터리 엔트리에 정상적으로 등록됩니다. 이 상태가 되면 파일시스템의 표준 파일 오픈(open) 함수(예: filesys_open 또는 시스템 콜 open)를 통해 파일을 열 수 있습니다.
즉, 복사 작업이 성공적으로 완료된 후에는 파일의 이름이 파일시스템의 디렉토리 엔트리에 추가되고, 파일의 데이터가 파일시스템 내부 구조(FAT, inode 등)에 맞게 저장됩니다. 따라서 사용자는 파일 이름을 지정해 파일을 열 수 있으며, 파일 디스크립터를 받아 파일 읽기/쓰기 등 표준 파일 연산을 수행할 수 있습니다[3](https://velog.io/@bgshin13/PintOS-project.[4](https://bearn-soo.tistory.com/entry/PintOS-Project2-3-System-calls-File-System-Call))[4](https://bearn-soo.tistory.com/entry/PintOS-Project2-3-System-calls-File-System-Call)[[5](https://e-juhee.tistory.com/entry/Pintos-KAIST-Project-2-%08System-Calls-3-File-System-User-Memory)](https://e-juhee.tistory.com/entry/Pintos-KAIST-Project-2-%08System-Calls-3-File-System-User-Memory).
Pintos 예시에서
fsutil_put
함수로 파일을 복사한 뒤에는,filesys_open
또는 open 시스템 콜을 통해 해당 파일을 열 수 있습니다. 만약 복사 과정에서 파일 생성이나 데이터 기록이 실패하지 않았다면, 파일 오픈에 아무런 제약이 없습니다[1](https://velog.io/@ngngs/Pintos.-File-systems-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-vyc8djqe)[[3](https://velog.io/@bgshin13/PintOS-project.4)](https://velog.io/@bgshin13/PintOS-project.[4](https://bearn-soo.tistory.com/entry/PintOS-Project2-3-System-calls-File-System-Call))[4](https://bearn-soo.tistory.com/entry/PintOS-Project2-3-System-calls-File-System-Call).요약하면, "디스크의 파일을 파일시스템으로 복사하면 이제 Open을 할 수 있는가?"라는 질문에 대한 답은 예, 복사에 성공했다면 파일시스템의 open 함수로 파일을 열 수 있습니다입니다.
Citations:
- https://velog.io/@ngngs/Pintos.-File-systems-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-vyc8djqe
- https://witch.work/ko/posts/pintos-3
- https://velog.io/@bgshin13/PintOS-project.4
- https://bearn-soo.tistory.com/entry/PintOS-Project2-3-System-calls-File-System-Call
- https://e-juhee.tistory.com/entry/Pintos-KAIST-Project-2-%08System-Calls-3-File-System-User-Memory
- https://d-cron.tistory.com/59
- https://jamiehun.tistory.com/134
- https://mudata0101.tistory.com/22
- https://one-step-a-day.tistory.com/133
Perplexity로부터의 답변: [pplx.ai/share](https://www.perplexity.ai/search/pplx.ai/share)
자 그렇다면 여기서 일어나는 일을 순서대로 한 번 봅시다.
이렇게 512바이트를 말록해서 버퍼에 할당해줍니다.
다음으로 소스 디스크를 열고, 거기서 파일 사이즈를 읽어 옵니다.
그 다음, 파일 사이즈를 읽는다고 하는데, 이건 무슨 의미일까요?
/* 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이 우리의 파일 시스템으로 복사되겠죠?
-
도대체 섹터는 뭔가요?
디스크 섹터란?
디스크 섹터는 하드 디스크의 가장 작은 물리적 저장 단위입니다:
- 정의: 디스크에서 한 번에 읽거나 쓸 수 있는 고정된 크기의 데이터 블록입니다.
- 크기: 전통적으로 512바이트였으나, 현대 디스크에서는 4KB 등 더 큰 크기를 사용하기도 합니다.
- 물리적 구조: 하드 디스크에서는 동심원 트랙의 일부분을 차지합니다.
Pintos에서 **
DISK_SECTOR_SIZE
**는 일반적으로 512바이트로 정의됩니다.disk_read 함수 분석
disk_read
함수는 다음과 같이 동작합니다:- 특정 디스크(d)에서
- 특정 섹터 번호(sec_no)의 데이터를
- 제공된 버퍼(buffer)로 읽어옵니다
이 함수는 한 번에 하나의 섹터만 읽습니다. 맞게 지적하셨듯이, 전체 디스크를 한 번에 읽는 것은 메모리 제약 때문에 불가능합니다. 큰 파일을 읽으려면 여러 섹터를 순차적으로 읽어야 합니다.
이제 실행 파일 복사 과정입니다.
fsutil_put
함수를 통해 실행 파일(executable object file)이 파일 시스템으로 복사되는 과정은 다음과 같습니다:
- 첫 섹터에서 파일 크기를 읽습니다.
- 파일 시스템에 해당 크기의 파일을 생성합니다.
- 이후 섹터들에서 실제 파일 내용을 순차적으로 읽어 파일에 기록합니다:
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"는 바로 이 과정을 설명하는 것입니다.함수의 동작 과정을 좀 더 자세히 살펴보면:
- 정적 변수
sector
를 사용해 현재 읽고 있는 디스크 섹터의 위치를 추적합니다. - 첫 번째 호출에서는 섹터 0부터 시작하여 파일 시그니처("PUT")와 크기를 읽습니다.
- 그 다음 루프에서는
sector++
를 통해 섹터 위치를 계속 증가시키면서:- 디스크에서 데이터를 청크 단위로 읽고(
disk_read
) - 파일 시스템의 대상 파일에 쓰는(
file_write
) 작업을 반복합니다.
- 디스크에서 데이터를 청크 단위로 읽고(
- 이 과정은 전체 파일 크기(
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"
파일 크기를 미리 읽는 이유는 파일 시스템에 정확한 크기의 파일을 생성하기 위함입니다. 이는 파일 시스템의 공간 할당과 관리에 중요합니다.
이 과정이 끝나면, 파일을 닫아 주고, 버퍼를 프리 해줍니다.
자, 이렇게 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)
- 실행 파일을 읽어옴: 호스트(로컬) 시스템에서 실행 파일(executable, 예: