이 글은 핀토스 파일 시스템에 관한 글입니다.
파일시스템은 데이터를 일정 크기 블록으로 나누어 저장.
가장 큰 이유 중 하나는 외부단편화 방지.
외부단편화란 디스크에 남는 작은 공간들이 연속적으로 나열되지 않고 분산되어 발생하는 현상.
파일시스템은 공간을 할당하고 해제하는 작업(freemap)이 반복되면서 외부 단편화가 발생할 수.
블록 단위로 할당하면 비어있는 만큼을 다 활용함으로써 외부단편화를 줄일 수.
그러나 byte 단위로 나누지 않고 블록 단위로 나누는 이유는 '관리해야 할 개수' 문제 때문. 바이트 단위로 할당하면 매우 작은 크기의, 수많은 공간을 따로 추적하고 관리해야 하므로 블록 단위로 나누는 것이 효율적.
클러스터(cluster)는 파일시스템에서 데이터를 저장하는 최소 단위.
클러스터는 여러개의 섹터로 구성되는데, 클러스터 개념 없이 디스크의 한 조각을 섹터(512Byte) 단위로 매핑하여 저장하면 너무 잘게 쪼개게 되어 FAT 테이블 크기가 너무 커짐. 따라서 파일을 찾을 때나 추적할 때 속도나 용이성을 고려해 클러스터 사용
FAT는 연속된 클러스터의 'Linked list'로 작동. 클러스터 넘버로서 데이터 영역을 정의하며, 비연속적인 데이터 공간도 {entry : value}가 {cluster number : next cluster}로서 다음 파일조각을 찾아갈 수 있음.
disk usage 트래킹
FAT 비트맵의 value는 기본적으로 next cluster number 나타내지만, 0 또는 -1은 각각 empty, EOF라는 특별한 의미를 지님
0x000
: 클러스터가 비어있음0x002~MAX
: 클러스터가 할당되었음0xFFF
: 할당되었으나 end-of-file(EOF)를 나타냄Microsoft 사의 FAT spec 공식문서를 참조하면 다음과 같음.
파일내 오프셋을 디스크 섹터 넘버로 변환.
원래 파일은 { start, length }만 알면 온전히 읽어들일 수 있으나
이번 과제는 비연속적인 파일 저장을 감안해야 하므로
오프셋(pos)을 DIS_SECTOR_SIZE로 나눈 디스크 개수만큼
FAT를 인덱싱으로 다음 클러스터를 찾아가며 전체 파일크기를 구성해야 함.
byte_to_sector()
static disk_sector_t
byte_to_sector (const struct inode *inode, off_t pos) {
ASSERT (inode != NULL);
if (pos < inode->data.length) {
#ifdef EFILESYS
/* 실제 디스크내 data가 시작되는 sector를 clst로 */
cluster_t clst = sector_to_cluster(inode->data.start);
for (unsigned i = 0; i < (pos / DISK_SECTOR_SIZE); i++) {
clst = fat_get(clst); // next clst로 이동
if (clst == 0) return -1; // end of chain
}
return cluster_to_sector(clst);
#else
return inode->data.start + pos / DISK_SECTOR_SIZE;
#endif
}
else
return -1;
}
fat_get()
cluster_t
fat_get (cluster_t clst) {
return fat_fs->fat[clst - 1];
}
FAT 파일시스템에 클러스터를 추가하는 함수
clst
가 0이면 바로 new_clst를 리턴해 해당 지점부터 inode->data가 시작될 수 있도록 함clst
가 0이 아니면 빈 슬롯 앞 칸의 value에 new_clst를 넣음으로써 체인을 한 칸 늘림fat_create_chain()
cluster_t
fat_create_chain (cluster_t clst)
{
cluster_t new_clst = get_empty_cluster();
fat_put(new_clst, EOChain);
if (clst != 0) {
fat_put(clst, new_clst);
}
return new_clst;
}
cluster_t get_empty_cluster() {
size_t clst = bitmap_scan_and_flip(fat_bitmap, 0, 1, false) + 1;
if (clst == BITMAP_ERROR)
return 0;
else
return (cluster_t) clst; // type casting
}
inode_write_at()
FAT 파일시스템에 데이터를 쓸 때 고려해야 할 두 가지 사항
Zero Padding 로직 흐름
1. memset(0, 0, DISK_SECTOR_SIZE)
: 제로 버퍼를 0으로 초기화
2. endclst
: 파일을 확장하는 과정에서 파일의 마지막 클러스터를 식별
3. 마지막 클러스터가 0으로 완전히 채워지지 않은 경우, 클러스터를 zero 버퍼로 읽고 나머지 부분을 0으로 덮어쓴 다음 디스크에 다시 씀
여기서 실제 디스트에 쓰는 disk_write()
은 네 번 등장
1. 디스크에 쓸 chunck_size
가 섹터 크기(512 byte)로 나눠떨어질 때 섹터를 온전히 채워 저장하는 부분
2. 맞아떨어지지 않을 때 섹터내 오프셋을 쓰는 부분
3. 제로 패딩을 써주는 부분
4. 늘어난 파일 크기를 디스크에 반영해주는 부분
/* 4번 - File growth */
if (inode_length(inode) < origin_offset + bytes_written) {
inode->data.length = origin_offset + bytes_written;
disk_write(filesys_disk, inode->sector, &inode->data);
}
inode_write_at()
off_t
inode_write_at (struct inode *inode, const void *buffer_, off_t size, off_t offset) {
const uint8_t *buffer = buffer_;
off_t bytes_written = 0;
off_t origin_offset = offset;
uint8_t zero[DISK_SECTOR_SIZE]; // buffer for zero padding
while (size > 0) {
/* Sector to write, starting byte offset within sector. */
disk_sector_t sector_idx = byte_to_sector (inode, offset);
int sector_ofs = offset % DISK_SECTOR_SIZE;
/* 생략 */
/* Write full sector directly to disk. */
disk_write (filesys_disk, sector_idx, buffer + bytes_written);
/* Advance. */
size -= chunk_size;
offset += chunk_size;
bytes_written += chunk_size;
}
/* File growth */
if (inode_length(inode) < origin_offset + bytes_written)
{
off_t inode_len = inode_length (inode);
cluster_t endclst = sector_to_cluster (byte_to_sector(inode, inode_len -1));
cluster_t newclst = inode_len == 0? endclst : fat_create_chain (endclst);
/* Zero-padding */
memset (zero, 0, DISK_SECTOR_SIZE);
disk_write(filesys_disk, cluster_to_sector(newclst), zero);
off_t inode_ofs = inode_len % DISK_SECTOR_SIZE; // zero padding offset
if (inode_ofs != 0) {
inode->data.length += DISK_SECTOR_SIZE - inode_ofs; // update length
disk_read (filesys_disk, cluster_to_sector(endclst), zero); // read from zero
memset (zero + inode_ofs + 1, 0, DISK_SECTOR_SIZE - inode_ofs);
disk_write (filesys_disk, cluster_to_sector(endclst), zero); // write zero
}
disk_write(filesys_disk, inode->sector, &inode->data); // save updated inode->data to disk
}
return bytes_written;
}
루트 디렉토리가 모든 파일을 소유하던 구조를,
루트 산하 여러 폴더가 계층구조를 이룰 수 있게 함.
is_dir
플래그를 추가하는 것만으로도 서브디렉토리 구조를 수용할 수 있도록 변경하는 셈
inode_create()
filesys_create()
-> inode_create(), is_dir = 0
dir_create()
-> inode_create(), is_dir = 1
fat_create_chain()
은 두 번 등장start_clst = fat_create_chain(0)
target = fat_create_chain(target)
inode_create()
bool
inode_create (disk_sector_t sector, off_t length, bool is_dir) {
struct inode_disk *disk_inode = NULL;
cluster_t start_clst;
bool success = false;
ASSERT (length >= 0);
ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE);
/* create disk_node and initialize*/
disk_inode = calloc (1, sizeof *disk_inode); // inode_disk 구조체에 대한 메모리 할당 (on-disk inode)
if (disk_inode != NULL) { // 할당 성공시 disk_inode 구조 초기화
size_t sectors = bytes_to_sectors (length);
disk_inode->length = length;
disk_inode->is_dir = is_dir;
disk_inode->magic = INODE_MAGIC;
/* data cluster allocation */
if (start_clst = fat_create_chain(0)) { // inode에 대한 데이터 클러스터 할당
disk_inode->start = cluster_to_sector(start_clst); // 중요* 새 클러스터 chian의 시작 주소를 섹터로 변환, 거기서부터 inode->data가 시작되도록 함
/* write disk_inode on disk */
disk_write(filesys_disk, sector, disk_inode); // inode_disk를 디스크에 write. 지정된 sector에 inode_disk 내용을 씀.
if (sectors > 0) { // inode length가 0보다 크면
static char zeros[DISK_SECTOR_SIZE];
cluster_t target = start_clst;
disk_sector_t w_sector;
size_t i;
/* make cluster chain based length and initialize zero */
while (sectors > 1) {
w_sector = cluster_to_sector(target);
disk_write(filesys_disk, w_sector, zeros);
target = fat_create_chain(target);
sectors--;
}
}
success = true;
}
free (disk_inode); // FAT 빈공간이 없으면 해제
}
return success;
}
파일 생성시 inode_create(), dir_add()가 호출되는 맥락은 다음과 같음
filesys_create()
bool filesys_create (const char *name, off_t initial_size)
{
disk_sector_t inode_sector = 0;
struct dir *dir = dir_open_root ();
bool success = (dir != NULL
&& free_map_allocate (1, &inode_sector)
&& inode_create (inode_sector, initial_size, 0)
&& dir_add (dir, name, inode_sector));
if (!success && inode_sector != 0)
free_map_release (inode_sector, 1);
dir_close (dir);
return success;
}
dir_add()
디렉토리 배열의 빈 슬롯을 찾아 해당 오프셋에 inode data를 저장하는 함수.
dir_add()
bool
dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) {
struct dir_entry e;
off_t ofs;
bool success = false;
ASSERT (dir != NULL); // 해당 디렉토리 및 이름이 이미 존재하지 않음
ASSERT (name != NULL);
/* Check NAME for validity. */
if (*name == '\0' || strlen (name) > NAME_MAX)
return false;
/* Check that NAME is not in use. */
if (lookup (dir, name, NULL, NULL))
goto done;
/* Set OFS to offset of free slot.*/
for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
ofs += sizeof e)
if (!e.in_use)
break;
/* Write slot. */
e.in_use = true;
strlcpy (e.name, name, sizeof e.name);
e.inode_sector = inode_sector;
success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
done:
return success;
}