https://github.com/Week12-13-GOAT/pintos-filesys
GitHub - Week12-13-GOAT/pintos-filesys: 파일시스템 재밌게 구현해보자~
파일시스템 재밌게 구현해보자~. Contribute to Week12-13-GOAT/pintos-filesys development by creating an account on GitHub.
github.com
전체 코드는 깃허브에 ~
늘 하듯이 과제 설명서 먼저 ~

이제 파일 확장을 구현해보자. 그 전에 먼저 !! 루트 디렉토리를 만들어주자.
왜 루트 디렉토리를 먼저 만들어주어야 하냐? 그럼 기존에는 파일 시스템이 어떻게 초기화되었는지부터 알아봐야 한다.
init.c의 main으로 가보자
/* init.c/main */
#ifdef FILESYS
/* 9. 파일 시스템 초기화 */
disk_init(); // 디스크 하드웨어 초기화
filesys_init(format_filesys); // 파일 시스템 구조체 및 루트 초기화
#endif
이 과정은 efilesys가 아닌 filesys이기 때문에 무조건 실행되는 코드이다. 여기서 filesys_init으로 들어가보자
/* filesys/filesys.c */
void
filesys_init (bool format) {
...
#ifdef EFILESYS
fat_init ();
if (format)
do_format ();
fat_open ();
#else
/* Original FS */
free_map_init ();
if (format)
do_format ();
free_map_open ();
#endif
...
}
여기서는 우리가 filesys 테스트를 진행하느냐 아니냐에 따라 코드가 갈린다. 파일시스템을 구현하고 들어간다면 EFILESYS 인자가 정의된 fat_init이 실행될 것이고, 그렇지 않다면 free_map_init가 실행될 것이다.
우리는 free_map 대신에 fat를 사용해야 한다고 헀다.
free_map_init에서는 기존에 어떻게 했었을까?
void
free_map_init (void) {
free_map = bitmap_create (disk_size (filesys_disk));
if (free_map == NULL)
PANIC ("bitmap creation failed--disk is too large");
bitmap_mark (free_map, FREE_MAP_SECTOR);
bitmap_mark (free_map, ROOT_DIR_SECTOR);
}
이렇게 루트 디렉토리를 위한 비트맵을 마크하고 사용했었다.
그래서 이 기능이 비활성화 되므로 루트 디렉토리를 따로 만들어주자
루트 디렉토리 초기화
void create_root_dir_inode(void)
{
// 1. 루트 디렉토리용 FAT 클러스터 하나 예약
cluster_t clst = ROOT_DIR_CLUSTER;
fat_put(clst, EOChain);
// 2. 해당 클러스터 섹터를 0으로 초기화
uint8_t *zero_buf = calloc(1, DISK_SECTOR_SIZE);
if (zero_buf == NULL)
PANIC("create_root_dir_inode: OOM during zero padding");
disk_write(filesys_disk, cluster_to_sector(ROOT_DIR_CLUSTER), zero_buf);
free(zero_buf);
// 3. inode_disk 생성 및 설정
struct inode_disk root_inode;
ASSERT(sizeof(root_inode) == DISK_SECTOR_SIZE);
memset(&root_inode, 0, sizeof root_inode);
root_inode.start = ROOT_DIR_CLUSTER; // 루트 디렉토리의 데이터 시작 위치
root_inode.length = 0; // 초기에는 파일 크기 0
root_inode.magic = INODE_MAGIC;
root_inode.isdir = true;
// 4. 루트 inode를 디스크의 ROOT_DIR_SECTOR에 저장 (보통 sector 1)
disk_write(filesys_disk, ROOT_DIR_SECTOR, &root_inode);
}
미리 예약된 ROOT_DIR_CLUSTER를 클러스터 번호를 섹터 번호로 변환해주는 cluster_to_sector를 이용해 섹터 번호로 변환시킨 후 디스크에 write 해준다. 처음엔 아무 내용도 없으므로 0으로만 채워진 zero_buf를 써주면 된다.
그 후 루트 디렉토리의 inode정보를 담을 inode_disk를 만들어 루트 디렉토리 정보를 만들어준 뒤, 미리 예약된 ROOT_DIR_SECTOR에 write 해주자.
아마 루트 클러스터와 루트 섹터를 헷갈릴 사람들이 많을거라 생각하는데, 파일이나 디렉토리가 저장될 때 실제 데이터가 담긴 섹터와 파일이나 디렉토리의 메타데이터를 담을 아이노드는 다른 섹터에 저장되어야 한다. 루트 클러스터에는 루트 디렉토리의 실제 데이터, 예를 들어 루트 디렉토리에 담긴 디렉토리나 파일들의 정보가 저장될 것이고, 루트 섹터에는 루트 디렉토리가 어느 클러스터부터 시작되는지, 루트 디렉토리의 크기는 몇인지 등이 저장되는 아이노드(정확히는 inode_disk)가 저장될 것이다.
그 다음, 파일시스템 초기화 로직에 이 함수를 추가해주자.
나의 경우에는 filesys/filesys.c의 do_format에 추가해주었다.
static void
do_format(void)
{
...
#ifdef EFILESYS
/* FAT을 생성하여 디스크에 저장합니다. */
fat_create();
fat_close();
create_root_dir_inode();
...
}
이제 섹터 1번에 루트 디렉토리의 아이노드가 저장되어있음을 알고 있으니 어디서든 루트 디렉토리를 열 수 있다.
/* filesys/directory.c */
/* 루트 디렉터리를 열어 그 디렉터리 객체를 반환합니다.
* 성공하면 true, 실패하면 false를 반환합니다. */
struct dir *
dir_open_root(void)
{
return dir_open(inode_open(ROOT_DIR_SECTOR));
}
바로 이렇게 !!
그러면 이제 본격적으로 파일 확장 기능을 구현해보자.
파일 확장 구현
파일 확장은 어디서 구현해야 하는가? 이전 포스팅(https://gooch123.tistory.com/33#2)에서 보면 inode_disk의 start 번호가 클러스터 체인의 시작 번호가 된다고 했고, 우리는 이 시작 번호를 통해 체인을 따라가면서 확장을 해주면 된다.
그러면 inode_disk의 정보를 바꿔주어야 하고, 아이노드가 가리키는 파일에 write를 진행하는 함수는 inode_write_at 이다.
inode_write_at
이 함수에는
while (size > 0)
{
disk_sector_t sector_idx = byte_to_sector(inode, offset);
...
}
이렇게 실제로 write를 진행하는 while 루프가 있다. 여기는 우리가 건드릴 필요가 없고, 이 위에서 파일 확장 로직을 추가해주면 된다.
확장 로직을 추가하기 전에 이 함수의 인자를 보자.
off_t inode_write_at(struct inode *inode, const void *buffer_, off_t size, off_t offset)
- inode : 쓰거나, 확장을 할 대상이 되는 파일의 inode
- buffer : 파일에 쓸 데이터
- size : write 할 사이즈
- offset : 파일에서 write를 시작할 오프셋
이제 write를 할 오프셋 + 사이즈를 확인해 보자.
off_t extend_size = offset + size;
여기서 3가지 상황을 체크해야 한다.
- 파일 길이 이내에서 쓰기를 진행하는가?
- 파일 끝에 걸쳐서 쓰기를 진행하는가?
- 파일 끝을 넘어서 쓰기를 진행하는가?
1번과 같은 경우에는 기존의 파일 내용을 덮어쓰고 확장을 할 필요는 없으므로 우리의 고려 대상은 아니다.
2번과 3번은 파일 끝을 넘기 때문에 확장이 되어야 하는지 체크를 또 해줘야 한다.
if (extend_size > inode_length(inode)) // 파일 끝보다 크게 써야 한다면
바로 이렇게 확장 로직을 시작해주면 된다. 하지만 파일은 섹터 단위로 쓰인다. 당연히 확장도 섹터 단위로 될 것이다. 그 말은 곧 확장의 크기가 마지막 섹터를 넘지 않으면 확장하지 않아도 된다는 뜻이다 !!
off_t last_sector_use_size = inode_length(inode) % DISK_SECTOR_SIZE; // 마지막 섹터의 사용 공간
off_t last_sector_remain_size = DISK_SECTOR_SIZE - last_sector_use_size; // 마지막 섹터의 남은 공간
off_t remain_length = extend_size - inode_length(inode); // 확장해야할 크기 찾기
그러면 먼저 마지막으로 쓰는 섹터를 알뜰하게 사용해보도록 하자.
이를 위해 last_sector_use_size와 last_sector_remain_size를 두었다. 아까 말했다시피 섹터 사이즈로 파일이 나뉘어서 저장되어있으므로 나머지 연산으로 마지막 섹터의 사용 중인 사이즈를 알 수 있다. 그리고 512바이트에서 마지막 섹터 사용 중 사이즈를 빼면 마지막 섹터의 빈 공간을 찾아낼 수 있다.
그리고 extend_size에서 현재 파일의 길이 (inode_length)를 빼면 확장해야할 사이즈를 알 수 있다.
inode->data.length += remain_length < last_sector_remain_size ? remain_length : last_sector_remain_size;
remain_length -= last_sector_remain_size;
이제 마지막 섹터 내에서 확장을 해줄 차례이다. inode의 data.length에 remain_length와 last_sector_remain_size 둘 중 더 작은 사이즈를 추가해주자. 만약 remain_length가 last_sector_remain_size보다 더 크다면, 마지막 섹터를 넘어 다른 섹터를 할당받아서 거기까지 확장을 해줘야 한다는 것을 의미한다.
확장을 해준 다음, remain_length에서 last_sector_remain_size를 빼주자. 이는 추가 확장이 필요한지를 검사하기 위해 필요한 로직이다. 이렇게 빼줬는데도 0보다 크다면, 마지막 섹터를 모두 썼음에도 확장이 필요하다는 의미이다.
if (inode->data.start == 0) // 현재 할당받은 클러스터 아무것도 없음
inode->data.start = fat_create_chain(0);
그리고 start가 0이라는 것은 현재 할당받은 클러스터, 즉 섹터가 아무것도 없을을 의미하기에 fat_create_chain 함수에 0을 줘서 새 클러스터 체인을 시작하고, 그 시작 번호를 inode의 data.start에 저장해주자.
while (remain_length > 0)
{
cluster_t new_clst = fat_create_chain(inode->data.start); // 클러스터 확장
off_t add_size = remain_length < DISK_SECTOR_SIZE ? remain_length : DISK_SECTOR_SIZE;
inode->data.length += add_size; // 이 파일의 길이를 확장
remain_length -= DISK_SECTOR_SIZE; // 남은 길이 - 512
uint8_t zero_pad_buf[DISK_SECTOR_SIZE] = {0}; // 512 바이트 0 패딩 버퍼
disk_sector_t new_sector = cluster_to_sector(new_clst);
disk_write(filesys_disk, new_sector, zero_pad_buf);
}
이제 본격적인 확장 로직이다. 위에서 remian_length에서 last_sector_remain_size를 빼 주었음에도 0보다 크다면, 클러스터 즉 섹터를 추가해주어야 하므로 fat_create_chain 함수에 시작 클러스터 번호를 넘겨줘서 이 파일의 클러스터 체인을 확장해 주자.
그리고 클러스터 체인을 확장한 만큼 파일의 길이도 증가시켜주어야 하는데, 이 때는 remain_length 가 섹터 크기보다 큰지 확인한 다음 더 작은 사이즈만큼만 확장해준다. 어차피 섹터 하나 할당받는거니까 512바이트 크대로 확장시키면 되는거 아니냐고 생각할 수도 있지만, write는 이미 인자로 받은 오프셋 + 사이즈까지만 진행되고, 이후의 값들은 어떤 값이 될지 보장할 수가 없다.
그 다음 루프를 위해 reamin_length에서 512 바이트만큼 빼주자. 남은 확장 크기가 512바이트보다 크다면 다음 루프가 진행될 것이고, 아니라면 여기서 멈출 것이다.
그리고 과제 설명서를 다시 보면 sparse file이라는 개념이 나온다. 예를 들어 현재의 파일 끝이 100 바이트인데, 오프셋은 1500바이트로 받아서 200바이트부터 확장을 시작해야 한다면? 101부터 1499 바이트까지는 어떤 데이터가 들어가야 할까?
일단은 중간 영역은 0으로 채워줘야 할 것이다. 기존에 쓰던 첫번째 섹터는 문제가 없다. 101부터 512바이트까지 0으로 채워주자. 그런데 두번째 섹터인 513 바이트부터 1024바이트는 모조리 0인데, 굳이 할당을 해줄 필요가 있을까? 여기에서 명시적인 할당이 일어나기 전에는 섹터를 아예 할당하지 않는 것이 sparse file이다. 이를 구현하기에는 복잡도가 너무 올라가 시간이 없을 것 같아 실제로 할당을 해주기로 했다. 어떻게 할지는 당신의 자유.
중간 영역에 0을 넣어주기 위해 zero_pad_buf 라는 0으로만 이루어진 버퍼를 하나 만들어준다. 그리고 할당받은 클러스터와 매핑되어있는 섹터에 disk_write로 이 버퍼를 줘서 0으로 채워준다. 이후의 write 로직에서 0으로 채워진 곳에 쓰기를 진행하여 실제 데이터를 덮어쓰기할 수도 있으니 크게 복잡하게 생각할 필요 없다.
여기까지만 하면 되는거냐?? 그런거냐??

어림도 없지롱 ~
하지만 이런 커널 패닉은 별거 아니다. 다음 포스팅에서 고쳐보자.
'크래프톤 정글' 카테고리의 다른 글
| [나만무] 나만무 프로젝트 끝 (5) | 2025.07.31 |
|---|---|
| [pintOS] 파일 확장 기능 구현 -2 (0) | 2025.06.13 |
| [pintOS] FAT 구현기 (0) | 2025.06.10 |
| [pintOS] 파일 시스템 찍먹하기 (4) | 2025.06.08 |
| [pintOS] Copy-on-write 구현기 (4) | 2025.06.07 |