| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- PINTOS
- 마일섬
- 아직도 실험 중
- 황금 장미
- 글루민
- 핀토스 프로젝트 4
- 자 이제 시작이야
- 핀토스 프로젝트 1
- 바빠지나?
- 내일부터
- multi-oom
- 글리치
- alarm clock
- 핀토스 프로젝트 2
- 핀토스 프로젝트 3
- 일지 시작한 지 얼마나 됐다고
- 파란 장미
- Project 1
- 셋업
- 노가다
- 끝
- botw
- 핀토스 프로젝트
- Today
- Total
거북이의 쉼터
(2022.03.29) Indexed & Extensible 구현 (2) 본문
1. 서론 및 필요 내용 설명
수면 사이클을 강제로 돌린 뒤에 쓰는 첫 글이다. 다시는 하지 않았으면 좋겠는 짓거리...
2. 구현해야 하는 것
- free_map 사용 말소 및 FAT 함수 사용으로 대체
- file extension 구현
3. 구현 과정
일단 free_map이 사용되는 곳들과, 그에 맞춰져 있는 함수들을 수정하도록 한다. filesys.c에는 filesys_create가 free_map을 사용하고 있어, 이를 고쳐주었다.
bool
filesys_create (const char *name, off_t initial_size) {
cluster_t inode_clst = fat_create_chain (0); // newly create cluster(sector) to hold file metadata
struct dir *dir = dir_open_root ();
bool success = (dir != NULL
&& inode_clst != 0
&& inode_create (cluster_to_sector (inode_clst), initial_size)
&& dir_add (dir, name, cluster_to_sector (inode_clst)));
if (!success && inode_clst != 0)
fat_remove_chain (inode_clst, 0);
dir_close (dir);
return success;
}
다음으로 inode.c에서는 inode_create와 inode_close가 free_map을 사용하고 있어 수정하였다. 일단 수정하기 쉬운 inode_close 부터 수정하자.
void
inode_close (struct inode *inode) {
/* Ignore null pointer. */
if (inode == NULL)
return;
/* Release resources if this was the last opener. */
if (--inode->open_cnt == 0) {
/* Remove from inode list and release lock. */
list_remove (&inode->elem);
/* Deallocate blocks if removed. */
if (inode->removed) {
fat_remove_chain (inode->sector, 0);
fat_remove_chain (inode->data.start, 0); // removes all the chain
}
free (inode);
}
}
dir_open_root도 1번 섹터를 열고 있어, 1번 cluster를 섹터 번호로 변환해 열 수 있도록 만들어 주었다.
struct dir *
dir_open_root (void) {
return dir_open (inode_open (cluster_to_sector (ROOT_DIR_CLUSTER)));
}
byte_to_sector 또한 free_map을 사용하는 것에 기반하여 단순히 start부터 pos / DISK_SECTOR_SIZE 만큼 떨어진 sector를 반환하고 있었다. 이 또한 수정이 필요하다. 그런데, FAT를 사용하는 방식을 도입하려면 disk_sector_t로는 바로 일정 거리만큼 떨어진 cluster의 추적을 할 수 없다. 그래서 이제 disk_sector_t에서 cluster_t로 변환하는 함수를 만들어줄까 하다가, 해당 start 변수가 생각보다 사용되는 곳이 없다는 점을 발견했다. 이 때문에 편의를 위해 inode_disk에 disk_sector_t 형태의 start 대신 cluster_t 형태의 start_clst를 넣으면 괜찮을 것 같다는 생각이 들었다. 그래서 disk_inode를 아래와 같이 수정한다.
struct inode_disk {
cluster_t start_clst; /* First data sector in clst */
// disk_sector_t start; /* First data sector. */
off_t length; /* File size in bytes. */
unsigned magic; /* Magic number. */
uint32_t unused[125]; /* Not used. */
};
이에 기반해서 위의 inode_close를 포함한 코드를 수정한다. 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)
return cluster_to_sector (fat_follow_chain (cluster_to_sector (inode->data.start_clst), pos / DISK_SECTOR_SIZE));
else
return -1;
}
cluster_t
fat_follow_chain (cluster_t clst, size_t idx) {
cluster_t cur_clst = clst;
size_t i;
for (i = 0; i < idx; i++) {
cur_clst = fat_fs->fat[cur_clst];
}
return cur_clst;
}
이제 free_map 구조로 되어 있는 코드는 더 남아있지 않은 것 같으므로 free_map.c는 무시하고 진행할 수 있다.
마지막으로 inode_create와 inode_write_at에 file extension을 추가하면 해당 파트는 끝이다. inode_create를 하다보면서 느낀 것은 일정 길이만큼 FAT chaining을 해주는 함수가 따로 있으면 편할 것 같다는 생각이 들었다. 그래서 아래의 함수를 만들었다.
// if *clst_p == 0, starting new chain
// else, append more sectors behind *clst
bool
inode_append_chain (size_t sectors, cluster_t *clst_p) {
size_t i;
cluster_t clst = *clst_p;
cluster_t cur_clst, prev_clst;
static char zeros[DISK_SECTOR_SIZE] = {0, };
bool new_chain = (clst == 0);
if (new_chain) {
prev_clst = 0;
}
else {
prev_clst = clst;
}
for (i = 0; i < sectors; i++) {
cur_clst = fat_create_chain (prev_clst);
if (cur_clst == 0) {
return false;
}
if (i == 0 && new_chain) {
*clst_p = cur_clst;
}
disk_write (filesys_disk, cluster_to_sector (cur_clst), zeros);
prev_clst = cur_clst;
}
return true;
}
해당 함수는 sectors 길이의 새로운 섹터 체인을 만들거나, 기존 존재하던 체인의 끝에 sectors 만큼의 섹터를 추가로 할당하는 역할을 한다. 이를 사용해서 inode_create와 inode_write_at을 고치면 아래와 같다. inode_write_at은 다른 루틴은 건들이지 않고 파일 크기가 부족할 경우만 대비해 주었다.
bool
inode_create (disk_sector_t sector, off_t length) {
struct inode_disk *disk_inode = NULL;
cluster_t prev_clst = 0, cur_clst, start_clst;
bool success = false;
ASSERT (length >= 0);
/* If this assertion fails, the inode structure is not exactly
* one sector in size, and you should fix that. */
ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE);
disk_inode = calloc (1, sizeof *disk_inode);
if (disk_inode != NULL) {
size_t sectors = bytes_to_sectors (length);
disk_inode->length = length;
disk_inode->magic = INODE_MAGIC;
disk_inode->start_clst = 0;
if (!inode_append_chain (sectors, &disk_inode->start_clst)) {
// if allocation fails, unchain
if (disk_inode->start_clst != 0) {
fat_remove_chain (disk_inode->start_clst, 0);
}
goto fat_chain_fail;
}
// chain allocation done
disk_write (filesys_disk, sector, disk_inode);
success = true;
fat_chain_fail:
free (disk_inode);
}
return success;
}
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;
uint8_t *bounce = NULL;
if (inode->deny_write_cnt)
return 0;
if (size + offset > inode_length (inode)) {
size_t append_sectors = bytes_to_sectors (offset + size) - bytes_to_sectors (inode_length (inode));
// will contain zeroing
if (inode_length (inode) == 0) {
if (!inode_append_chain (append_sectors, &inode->data.start_clst)) {
if (inode->data.start_clst != 0) {
fat_remove_chain (inode->data.start_clst, 0);
return 0;
}
}
}
else {
cluster_t append_start = fat_chain_end (inode->data.start_clst);
if (!inode_append_chain (append_sectors, &append_start)) {
if (fat_get(append_start) != EOChain) {
fat_remove_chain (fat_get (append_start), append_start);
return 0;
}
}
}
inode->data.length = offset + size; // update the size
}
...
}
이 때 사용된 fat_chain_end는 아래와 같이 단순한 구조의 함수로, 체인의 끝에 해당하는 cluster를 반환한다.
cluster_t
fat_chain_end (cluster_t clst) {
cluster_t cur_clst = clst, nxt_clst;
size_t i;
while (true) {
nxt_clst = fat_fs->fat[cur_clst];
if (nxt_clst == EOChain)
break;
cur_clst = nxt_clst;
}
return cur_clst;
}
돌아갈지는 모르겠지만 이제 디버깅을 해 보자.
4. 디버깅
다른 건 몰라도 지금까지 건들인 것들은 free_map에서 FAT로 단순히 변경만 한 것이기 때문에 base 테스트 케이스는 여전히 돌아가야 할 것이다. Make.vars도 미리 고쳐주었다. 그리고 make check를 하니 userprog 테케부터 빵빵 터뜨려진다.

뭐가 문제인지 살펴보았다. 기존에 do_format 함수에는 free_map_create 이후 바로 root directory에 해당하는 directory inode를 생성했는데, 현재 구현에는 빠져있어서 문제가 된 것이었다. 바로 수정해준다.
/* Formats the file system. */
static void
do_format (void) {
printf ("Formatting file system...");
#ifdef EFILESYS
/* Create FAT and save it to the disk. */
fat_create ();
if (!dir_create (cluster_to_sector (ROOT_DIR_CLUSTER), 16))
PANIC ("root directory creation failed");
fat_close ();
#else
free_map_create ();
if (!dir_create (ROOT_DIR_SECTOR, 16))
PANIC ("root directory creation failed");
free_map_close ();
#endif
printf ("done.\n");
}
다시 돌린다. 여전히 터진다... 않이 왜지?
args-none: dying due to interrupt 0x0d (#GP General Protection Exception).
Interrupt 0x0d (#GP General Protection Exception) at rip=400c81
cr2=0000000000400c7f error= 0
rax 000080042721d8d8 rbx 000080042700b000 rcx 00008004214b4600 rdx 00008004272f6000
rsp 000000004747ffd8 rbp 000080042721d800 rsi 000000004747ffe0 rdi 0000000000000001
rip 0000000000400c81 r8 0000800427200000 r9 0000800420af1400 r10 00008004272f3000
r11 0000010422fe3000 r12 00008004272f3000 r13 0000800422fe4800 r14 00008004272f1000
r15 0000800420a9ce00 rflags 00000286
es: 001b ds: 001b cs: 0023 ss: 001b
args-none: exit(-1)
터질 이유가 없는데... 계속 찾아봐도 터질만한 이유가 없다... 3시간 디버깅 시도 후 결국 최후의 수단을 쓰기로 했다. 리셋한 뒤 다시하는 것이다... 썅...
일단 git reset을 해 준 뒤 EFILESYS를 지우고 기능이 제대로 동작하는 것을 확인하였다. 이제 다시 코딩을 해 준다. 일단 extension 기능은 배제하고 구현하였다. 또 다시 무수한 뻘짓 후에 정말 ㅄ같은 짓을 저지르고 있는 걸 발견했다.
static disk_sector_t
byte_to_sector (const struct inode *inode, off_t pos) {
ASSERT (inode != NULL);
if (pos < inode->data.length)
return cluster_to_sector (fat_follow_chain (cluster_to_sector (inode->data.start_clst), pos / DISK_SECTOR_SIZE));
else
return -1;
}
대체 왜 clst를 인자로 받는 걸 sector로 기어코 바꿔서 넣고 있던걸까 ㅎㅎㅎ... 젠장.. 바로 고쳐준다.
static disk_sector_t
byte_to_sector (const struct inode *inode, off_t pos) {
ASSERT (inode != NULL);
if (pos < inode->data.length)
return cluster_to_sector (fat_follow_chain (inode->data.start_clst, pos / DISK_SECTOR_SIZE));
else
return -1;
}
어쩐지 이상한 섹터에 접근을 한다 했다. root가 158인테 갑자기 섹터 316에 접근하기에 어디서 두 번 적용이 되는건가 했는데 정말이었다. 이런 실수좀 하지 마라....
문제를 고쳐준 뒤에는 해당 케이스가 깔끔히 돌아가는 것을 확인하였다. 다시 extension까지 적용해주고 make check를 해 본다. 이번에는 inode_close에서 문제가 생겨서 syn_remove가 제대로 동작하지 않았다. 이번에는 문제를 꽤 빨리 발견했다. 이번에도 cluster로 넣어야 할 것을 sector로 넣고 있었다. ㅋㅋㅋㅋㅋ 이번에는 고쳐주기 위해서는 sector를 cluster로 변환해주는 함수가 필요했기에 어쩔 수 없이 구현을 하고 넘어가기로 했다.
cluster_t
sector_to_cluster (disk_sector_t sec) {
if (sec == 0)
return 0;
return (cluster_t) (sec - fat_fs->data_start + 1);
}
void
inode_close (struct inode *inode) {
/* Ignore null pointer. */
if (inode == NULL)
return;
/* Release resources if this was the last opener. */
if (--inode->open_cnt == 0) {
/* Remove from inode list and release lock. */
list_remove (&inode->elem);
/* Deallocate blocks if removed. */
if (inode->removed) {
fat_remove_chain (sector_to_cluster (inode->sector), 0);
if (inode->data.start_clst != 0) {
fat_remove_chain (inode->data.start_clst, 0);
}
}
free (inode);
}
}
이후에 다시 돌려본다. 이젠 기존 케이스들은 아무 문제 없이 잘 돌아간다. 매뉴얼에서 설명한 대로 아직 새로운 테스트 케이스의 persistence는 통과하지는 않는다. 문제는 일반 실행의 경우 통과했어야 할 테케인 grow 테스트 케이스가 대부분 통과하지 않는 것이다. 어디가 잘못되었는지 생각해보았다. 파일은 정상적으로 커졌지만, 수정된 inode의 data가 다시 file system으로 써지지 않는데에 문제가 있다. 파일이 삭제된 경우를 제외한 일반적인 inode_close의 경우, inode->data가 inode->sector에 작성되도록 수정해주었다.
void
inode_close (struct inode *inode) {
/* Ignore null pointer. */
if (inode == NULL)
return;
/* Release resources if this was the last opener. */
if (--inode->open_cnt == 0) {
/* Remove from inode list and release lock. */
list_remove (&inode->elem);
/* Deallocate blocks if removed. */
if (inode->removed) {
fat_remove_chain (sector_to_cluster (inode->sector), 0);
if (inode->data.start_clst != 0) {
fat_remove_chain (inode->data.start_clst, 0);
}
}
else {
disk_write (filesys_disk, inode->sector, &inode->data);
}
free (inode);
}
}
다시 make check를 해 주면 subdirectory가 필요한 테케를 제외한 extension 테케는 모두 통과하는 것을 볼 수 있다.
pass tests/filesys/extended/grow-create
pass tests/filesys/extended/grow-dir-lg
pass tests/filesys/extended/grow-file-size
pass tests/filesys/extended/grow-root-lg
pass tests/filesys/extended/grow-root-sm
pass tests/filesys/extended/grow-seq-lg
pass tests/filesys/extended/grow-seq-sm
pass tests/filesys/extended/grow-sparse
pass tests/filesys/extended/grow-tell
pass tests/filesys/extended/grow-two-files
이제 다음 단계를 구현할 준비가 되었다.
5. 후기
별 것도 아닌 문제를 가지고 너무 오래 시간을 끌었다.... 이게 문제가 터지기 시작한 코드를 짜기 시작한 시점이 밤샘을 했을 때인 것으로 봐서는 밤샘 코딩은 해롭다는 것을 알 수 있다. 난 쓰레기야...
'코딩 삽질 > KAIST PINTOS (CS330)' 카테고리의 다른 글
| (2022.03.29) Subdirectories 가이드라인 (2) (0) | 2022.03.29 |
|---|---|
| (2022.03.29) Subdirectories 가이드라인 (1) (0) | 2022.03.29 |
| (2022.03.28) Indexed & Extensible 구현 (1) (0) | 2022.03.28 |
| (2022.03.27) Indexed & Extensible 가이드라인 (2) (0) | 2022.03.27 |
| (2022.03.27) Indexed & Extensible 가이드라인 (1) (0) | 2022.03.27 |