개발/프로젝트

Pintos Project 4-2: 루트 디렉터리 하나에서 경로를 읽는 파일 시스템으로

cedis 2026. 5. 21. 00:09
Project 4 File System 시리즈 2편

1편에서 FAT와 파일 확장을 만들었다면, 이번에는 파일 이름을 찾는 방식 자체를 바꾼다. Project 4의 디렉터리 테스트는 더 이상 모든 파일이 root 아래에 있다고 봐주지 않는다.

핵심부터 말하면

기존 파일 시스템은 파일 이름 하나를 root directory에서만 찾으면 됐다. Project 4에서는 /a/b/c, ../x, 현재 작업 디렉터리, ., ..까지 처리해야 한다. 그래서 파일 생성과 삭제도 “부모 디렉터리 찾기 + 마지막 이름 처리”로 나누었다.

경로 해석 흐름
입력../tmp/a
시작점절대경로면 root, 아니면 cwd
token 순회.., tmp, a
결과마지막 inode 또는 부모 dir

1. thread가 현재 작업 디렉터리를 가져야 한다

상대 경로를 지원하려면 process마다 기준 디렉터리가 있어야 한다. 그래서 thread에 cwd를 추가하고, user process 초기화 시 root를 기본값으로 잡았다.

#ifdef USERPROG
struct dir;
#endif

struct thread {
#ifdef USERPROG
	struct dir *cwd;                   /* Current working directory. */
#endif
};
void
process_user_init (struct thread *t) {
	ASSERT (t != NULL);

	if (t->cwd == NULL)
		t->cwd = dir_open_root ();
}

bool
process_duplicate_fds (struct thread *dst, struct thread *src) {
	process_user_init (dst);
	process_close_files (dst);

	if (src->cwd != NULL)
		dst->cwd = dir_reopen (src->cwd);
	else
		dst->cwd = dir_open_root ();

	/* fd table 복제 로직은 기존 흐름을 이어간다. */
	return true;
}
이 블록이 바꾼 것

이전에는 모든 경로가 root 기준인 것처럼 동작했다. 이제 각 process는 자기 cwd를 가진다. fork에서는 부모 cwd를 reopen해서 자식에게 넘긴다. 같은 inode를 가리키되, dir 객체는 따로 열어두는 방식이다.

2. 디렉터리는 생성될 때 . 과 .. 를 가져야 한다

하위 디렉터리는 단순히 inode flag만 directory로 표시한다고 끝나지 않는다. 현재 디렉터리를 가리키는 .와 부모 디렉터리를 가리키는 ..가 있어야 경로 순회가 자연스럽게 된다.

bool
dir_create (disk_sector_t sector, size_t entry_cnt, disk_sector_t parent_sector) {
	struct dir *dir;
	bool success;

	success = inode_create_dir (sector, entry_cnt * sizeof (struct dir_entry));
	if (!success)
		return false;

	dir = dir_open (inode_open (sector));
	if (dir == NULL)
		return false;
	success = dir_add (dir, ".", sector) && dir_add (dir, "..", parent_sector);
	dir_close (dir);
	return success;
}
주의할 점

사용자에게 보여주는 readdir()에서는 ...를 숨긴다. 내부 경로 해석에는 필요하지만, 테스트가 기대하는 디렉터리 목록에는 일반 파일처럼 나오면 안 된다.

bool
dir_readdir (struct dir *dir, char name[NAME_MAX + 1]) {
	struct dir_entry e;

	while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) {
		dir->pos += sizeof e;
		if (e.in_use && strcmp (e.name, ".") && strcmp (e.name, "..")) {
			strlcpy (name, e.name, NAME_MAX + 1);
			return true;
		}
	}
	return false;
}

3. open은 전체 경로를 따라가고, create는 부모와 이름을 나눈다

여기서 가장 많이 헷갈린 지점은 open("/a/b/c")create("/a/b/c")의 차이다. open은 마지막 c까지 이미 있어야 한다. create는 /a/b라는 부모 디렉터리를 찾은 뒤, 그 안에 c라는 이름을 새로 추가해야 한다.

static struct dir *
starting_dir (struct dir *base, const char *path) {
	struct thread *cur = thread_current ();

	if (path[0] == '/')
		return dir_open_root ();
	if (base != NULL)
		return dir_reopen (base);
	if (cur != NULL && cur->cwd != NULL)
		return dir_reopen (cur->cwd);
	return dir_open_root ();
}
bool
filesys_mkdir (const char *name) {
	disk_sector_t inode_sector = 0;
	char dir_name[NAME_MAX + 1];
	struct dir *parent = open_parent_dir (name, dir_name);
	bool success = false;

	if (parent != NULL
			&& allocate_inode_sector (&inode_sector)
			&& dir_create (inode_sector, 2,
				inode_get_inumber (dir_get_inode (parent)))
			&& dir_add (parent, dir_name, inode_sector))
		success = true;

	if (!success && inode_sector != 0)
		release_inode_sector (inode_sector);
	dir_close (parent);
	return success;
}

4. 삭제는 비어 있는 디렉터리만 허용한다

파일 삭제와 디렉터리 삭제는 위험도가 다르다. 디렉터리를 지울 때는 내부 항목이 비어 있는지 확인하고, ...는 삭제 대상에서 막아야 한다.

bool
dir_is_empty (struct dir *dir) {
	struct dir_entry e;
	off_t ofs;

	for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e;
			ofs += sizeof e) {
		if (e.in_use && strcmp (e.name, ".") && strcmp (e.name, ".."))
			return false;
	}
	return true;
}

5. 이 단계에서 의미 있는 테스트

테스트 의미 실패 시 먼저 볼 곳
dir-mkdir 하위 디렉터리 inode와 entry가 만들어지는가 filesys_mkdir(), dir_create()
dir-chdir 상대 경로가 cwd 기준으로 해석되는가 thread->cwd, starting_dir()
dir-mk-tree 깊은 경로 생성과 탐색이 가능한가 resolve_path_from(), open_parent_dir()
dir-rm-tree 비어 있는 디렉터리만 안전하게 지우는가 dir_remove(), dir_is_empty()

마무리

이 단계에서 파일 시스템은 “이름 하나를 root에서 찾는 구조”를 벗어난다. 각 process가 cwd를 갖고, path resolver가 token을 따라가며, create와 mkdir은 부모 디렉터리와 마지막 이름을 분리한다. 이 기준이 잡히면 다음 글의 디렉터리 fd syscall도 훨씬 자연스럽게 연결된다.