본문 바로가기

디렉터리 구조

수많은 파일을 정리하는 디렉터리가 단일 단계에서 트리와 비순환 그래프로 발전한 과정을 살펴보고, 경로를 이름 하나씩 inode로 바꾸어 가는 해석 과정을 모델로 따라갑니다. 하드 링크와 심볼릭 링크의 차이와 상대 경로 링크의 해석 기준, 마운트와 VFS가 여러 파일 시스템을 하나의 트리로 합치는 방법을 정리합니다.

컴퓨터에 파일이 수백만 개 있다면, 이름 하나로 원하는 파일을 찾는 것은 사실상 불가능합니다. 같은 이름을 쓰고 싶은 파일도 많을 것입니다. 디렉터리(directory)는 파일들을 묶어 계층적으로 정리하는 구조입니다. 디렉터리도 파일의 한 종류로, 그 안에 든 파일들의 이름과, 각 이름이 가리키는 파일을 찾을 수 있는 정보를 담고 있습니다. Unix 계열 파일 시스템에서 디렉터리는 이름과 inode 번호의 짝을 모은 목록이며, inode는 파일의 메타데이터와 데이터 위치를 담은 자료구조로 다음 글에서 자세히 다룹니다.

이 글에서는 디렉터리 구조가 어떻게 발전해 왔는지, 경로가 어떻게 해석되는지, 한 파일에 여러 이름을 붙이는 링크와 여러 파일 시스템을 하나로 합치는 마운트가 어떻게 동작하는지 살펴봅니다.

디렉터리 구조는 어떻게 발전했나

가장 단순한 구조는 단일 단계(single-level) 디렉터리로, 모든 파일이 디렉터리 하나에 들어 있습니다. 초기의 단순한 시스템과 MS-DOS 1.0이 이 방식이었습니다. 이름이 시스템 전체에서 겹치지 않아야 하므로, 파일이 늘어날수록 이름을 짓기도 찾기도 어려워집니다.

이중 단계(two-level) 디렉터리는 사용자마다 디렉터리를 하나씩 줍니다. 서로 다른 사용자는 같은 이름의 파일을 가질 수 있지만, 한 사용자의 파일들은 여전히 한 디렉터리에 모여 있어 같은 문제가 남습니다. TOPS-10 같은 초기의 다중 사용자 시스템이 이런 방식이었습니다.

트리(tree) 구조 디렉터리는 디렉터리 안에 다시 디렉터리를 만들 수 있게 해, 원하는 만큼 계층을 쌓을 수 있습니다. 오늘날 파일 시스템의 표준이며, /home/user/documents/reports/2024/ 같은 경로가 가능한 것도 이 구조 덕분입니다. 경로의 최대 길이 같은 구현상의 한도는 남아 있습니다.

비순환 그래프(acyclic graph) 구조는 한 파일이 여러 디렉터리에 동시에 나타날 수 있게 합니다. 팀원들이 함께 쓰는 파일을 각자의 디렉터리에 두는 식입니다. 이것은 아래에서 볼 링크로 구현하며, 한 파일을 여러 이름이 가리키므로 파일을 지울 때 남은 이름이 몇 개인지 세어야 합니다. 디렉터리끼리 원을 이루면 디렉터리를 탐색하는 프로그램이 끝나지 않으므로, 원이 생기지 않게 막습니다.

디렉터리를 다루는 연산

디렉터리는 다음 연산으로 다룹니다.

연산하는 일
opendir()디렉터리를 열어 읽기 위한 핸들을 얻는다
readdir()다음 항목, 곧 이름과 inode 번호를 하나 읽는다
mkdir()새 디렉터리를 만든다
rmdir()비어 있는 디렉터리를 지운다
rename()파일이나 디렉터리의 이름을 바꾸거나 다른 디렉터리로 옮긴다

opendir()과 readdir()은 C 라이브러리 함수이고, Linux에서는 그 아래에서 openat과 getdents64 시스템 콜이 쓰입니다. 다음 C 프로그램은 디렉터리의 항목을 읽어 종류와 크기를 출력합니다.

directory_listing.c
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    const char *path = argc > 1 ? argv[1] : ".";
    DIR *dir = opendir(path);
    if (dir == NULL) {
        perror("opendir");
        return 1;
    }

    struct dirent *entry;
    while ((entry = readdir(dir)) != NULL) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            continue;                                  /* 자신과 부모는 건너뛴다. */

        char full[4096];
        struct stat st;
        int len = snprintf(full, sizeof full, "%s/%s", path, entry->d_name);
        if (len < 0 || (size_t) len >= sizeof full || lstat(full, &st) != 0)
            continue;
        const char *type = S_ISDIR(st.st_mode) ? "DIR"
                         : S_ISLNK(st.st_mode) ? "LINK"
                         : S_ISREG(st.st_mode) ? "FILE" : "OTHER";
        printf("%-30s %-6s %10lld\n", entry->d_name, type, (long long) st.st_size);
    }
    closedir(dir);
    return 0;
}

readdir()가 돌려주는 순서는 파일 시스템이 디렉터리를 저장한 순서이며, 이름순으로 정렬된 것이 아닙니다. ls가 이름순으로 보여 주는 것은 ls가 따로 정렬하기 때문입니다. stat 대신 lstat을 쓴 것은, 심볼릭 링크를 만나면 링크가 가리키는 대상이 아니라 링크 자체의 정보를 얻기 위해서입니다. 모든 디렉터리에는 자기 자신을 가리키는 .과 부모를 가리키는 .. 항목이 있으므로 건너뜁니다.

경로를 해석하는 과정

경로에는 두 종류가 있습니다. 절대 경로(absolute path)는 루트 디렉터리에서 시작하는 전체 경로로, Unix에서는 /home/user/hello.c, Windows에서는 C:\Users\user\hello.c 같은 모양입니다. 상대 경로(relative path)는 프로세스의 현재 작업 디렉터리(current working directory)에서 시작합니다. .은 현재 디렉터리, ..은 부모 디렉터리를 뜻하므로, ../src는 한 단계 올라가 src로 들어가라는 뜻입니다.

운영체제는 경로를 왼쪽부터 이름 하나씩 따라가며 해석합니다. /home/user/hello.c를 연다면, 루트 디렉터리의 내용에서 home을 찾아 그 inode를 얻고, home 디렉터리의 내용에서 user를 찾고, user 디렉터리의 내용에서 hello.c를 찾아 마지막 inode를 얻습니다. 다음 코드는 작은 파일 시스템 모델에서 이 과정을 따라갑니다.

# 작은 파일 시스템 모델: inode 번호 -> 디렉터리라면 {이름: inode}, 파일이라면 내용
inodes = {
    2: {"home": 11, "etc": 12},
    11: {"user": 21},
    21: {"hello.c": 31, "docs": 32},
    31: "int main(void) { return 0; }",
    32: {},
    12: {},
}

def resolve(path, root=2):
    node = root
    print(f"  '/' -> inode {node}")
    for name in path.strip("/").split("/"):
        entries = inodes[node]
        if not isinstance(entries, dict):
            raise NotADirectoryError(name)
        if name not in entries:
            raise FileNotFoundError(name)
        node = entries[name]                 # 디렉터리 내용에서 이름을 찾아 다음 inode로
        print(f"  '{name}' -> inode {node}")
    return node

print("/home/user/hello.c:")
resolve("/home/user/hello.c")
print("/home/user/missing.c:")
try:
    resolve("/home/user/missing.c")
except FileNotFoundError as error:
    print("  찾지 못한 이름:", error)
/home/user/hello.c:
  '/' -> inode 2
  'home' -> inode 11
  'user' -> inode 21
  'hello.c' -> inode 31
/home/user/missing.c:
  '/' -> inode 2
  'home' -> inode 11
  'user' -> inode 21
  찾지 못한 이름: missing.c

ext4에서 루트 디렉터리의 inode 번호는 2로 정해져 있어, 이 모델도 2에서 시작했습니다. 경로의 이름마다 디렉터리의 내용을 읽어야 하므로, 이것을 매번 디스크에서 읽는다면 깊은 경로를 여는 데 디스크 접근이 여러 번 필요합니다. 그래서 Linux는 최근에 해석한 이름과 inode의 짝을 덴트리 캐시(dentry cache)에, inode를 inode 캐시에 담아 두어, 자주 쓰는 경로는 디스크를 읽지 않고 해석합니다. 실제 운영체제는 각 단계에서 권한도 확인합니다. 중간 디렉터리 가운데 하나라도 들어갈 권한(실행 권한)이 없으면, 마지막 파일의 권한과 상관없이 거기서 실패합니다.

하드 링크와 심볼릭 링크

하드 링크: 같은 파일의 또 다른 이름

하드 링크(hard link)는 이미 있는 파일에 이름을 하나 더 붙이는 것입니다. 디렉터리 항목은 이름과 inode 번호의 짝이므로, 다른 디렉터리 항목이 같은 inode 번호를 가리키게 하면 됩니다. 두 이름은 완전히 대등해서, 어느 쪽이 원본이라는 구분이 없습니다. inode에는 자기를 가리키는 이름의 수, 곧 링크 수가 기록되어 있고, 앞 글에서 본 것처럼 이 수가 0이 되고 열린 참조도 없어야 파일의 공간이 회수됩니다.

import os
import tempfile

with tempfile.TemporaryDirectory() as folder:
    original = os.path.join(folder, "original.txt")
    hardlink = os.path.join(folder, "hardlink.txt")
    with open(original, "w", encoding="utf-8") as f:
        f.write("shared data\n")
    os.link(original, hardlink)                          # 같은 파일에 이름을 하나 더 붙인다

    a, b = os.stat(original), os.stat(hardlink)
    print("같은 inode인가:", a.st_ino == b.st_ino, "/ 링크 수:", a.st_nlink)
    with open(hardlink, "a", encoding="utf-8") as f:
        f.write("added via hardlink\n")                  # 한 이름으로 쓰면
    with open(original, encoding="utf-8") as f:
        print("original로 읽기:", f.read().splitlines())  # 다른 이름으로도 보인다

    os.remove(original)                                  # 이름 하나를 지운다
    print("original을 지운 뒤 링크 수:", os.stat(hardlink).st_nlink)
    with open(hardlink, encoding="utf-8") as f:
        print("hardlink로 읽기:", f.read().splitlines())
같은 inode인가: True / 링크 수: 2
original로 읽기: ['shared data', 'added via hardlink']
original을 지운 뒤 링크 수: 1
hardlink로 읽기: ['shared data', 'added via hardlink']

두 이름은 같은 inode를 가리키므로 한 이름으로 쓴 내용이 다른 이름으로도 그대로 보이고, 링크 수는 2입니다. original.txt를 지워도 링크 수가 1로 줄 뿐 파일은 남아, hardlink.txt로 모든 내용을 읽을 수 있습니다. 이 코드는 Windows의 NTFS에서도 같은 결과를 냅니다.

하드 링크에는 두 가지 제약이 있습니다. 첫째, 디렉터리에는 하드 링크를 만들 수 없습니다. 디렉터리에 하드 링크를 허락하면 디렉터리가 자기 조상을 가리키는 원이 생길 수 있고, 그러면 디렉터리 트리를 탐색하는 프로그램이 끝나지 않거나 링크 수로 회수 시점을 판단할 수 없게 됩니다. 둘째, 다른 파일 시스템에 있는 파일에는 하드 링크를 만들 수 없습니다. inode 번호는 한 파일 시스템 안에서만 유일하기 때문입니다.

심볼릭 링크: 경로를 적어 둔 파일

심볼릭 링크(symbolic link) 또는 소프트 링크(soft link)는 다른 파일의 경로 문자열을 내용으로 담은 특별한 파일입니다. 자기만의 inode를 가지며, 운영체제는 경로를 해석하다 심볼릭 링크를 만나면 그 안에 적힌 경로로 이어서 해석합니다. Windows의 바로 가기와 비슷하지만, 바로 가기는 탐색기 같은 프로그램이 해석하는 일반 파일인 반면 심볼릭 링크는 운영체제가 경로를 해석할 때 따라갑니다.

ln -s /home/user/docs/report.txt report_link   # 절대 경로를 담은 심볼릭 링크
readlink report_link                            # 담긴 경로 문자열 출력
ls -li report_link /home/user/docs/report.txt   # 두 파일의 inode 번호가 다르다

심볼릭 링크는 하드 링크의 제약이 없어서, 디렉터리를 가리킬 수도 있고 다른 파일 시스템의 파일을 가리킬 수도 있습니다. 대신 경로만 적어 둔 것이므로, 대상 파일을 지우거나 옮기면 링크는 아무것도 가리키지 않는 끊어진 링크(dangling link)가 되어 열 때 "No such file or directory" 오류가 납니다. 반대로 같은 경로에 새 파일을 만들면 링크는 새 파일을 가리킵니다. 링크가 서로를 가리키는 원을 이루면, 운영체제는 경로를 해석하며 따라간 링크의 수를 세다가 한도를 넘으면 ELOOP 오류로 멈춥니다. Linux에서 이 한도는 40입니다.

파일 시스템 · 이름과 참조

하드 링크는 같은 inode, 심볼릭 링크는 저장한 경로로 대상을 찾습니다

하드 링크의 두 이름은 같은 파일 시스템의 같은 inode를 가리킵니다. 심볼릭 링크는 별도 inode에 경로를 저장하며, 접근할 때 그 경로의 존재와 권한을 다시 확인합니다.

같은 inode의 두 이름과 절대 경로를 담은 심볼릭 링크original.txt와hardlink.txt두이름은같은inode를가리킨다. report_link는별도inode에/home/user/docs/report.txt절대경로문자열을저장하며접근시에대상경로를다시찾는다. 두영역은다른참조모델이지하드링크가심볼릭링크로변하는시간단계가아니다. 본문C의original.txt대상경로와그림의별도절대경로예를혼동하지않는다.경로 문자열경로 해석original.txthardlink.txt같은 inode같은 파일 데이터report_link별도 링크 inode저장한 절대 경로/home/user/docs/report.txt대상 경로 해석존재·권한 확인하드 링크 · 동일 inode심볼릭 링크 · 경로
이름과 경로의 참조 대상original.txt와hardlink.txt는같은inode를가리킨다. report_link는별도링크파일에대상경로문자열을저장하여경로를다시해석한다.하드 링크 · 동일 inode심볼릭 링크 · 경로 재해석original.txthardlink.txt같은 inode같은 파일 데이터report_link별도 링크 inode저장한 경로/home/user/docs/report.txt대상 경로 해석존재·권한 확인
이름과 경로의 참조 대상original.txt와hardlink.txt는같은inode를가리킨다. report_link는별도링크파일에대상경로문자열을저장하여경로를다시해석한다.하드 링크 · 동일 inode심볼릭 링크 · 경로 재해석original.txthardlink.txt같은 inode같은 파일 데이터report_link별도 링크 inode저장한 경로/home/user/docs/report.txt대상 경로 해석존재·권한 확인

하드 링크에는 원본 이름이라는 우선권이 없습니다. 심볼릭 링크는 대상 이름이 사라지면 경로 해석이 실패할 수 있습니다.

심볼릭 링크 영역의 /home/user/docs/report.txt는 별도 절대 경로 예입니다. 본문 C 코드가 가리키는 original.txt와 같은 경로를 뜻하지 않습니다.

그림처럼 original.txt와 hardlink.txt는 같은 inode를 가리키는 두 이름이고, report_link는 자기 inode를 가진 별도의 파일로 그 안에 경로를 담고 있습니다. 하드 링크의 두 이름은 같은 파일 시스템 안에서 같은 inode를 공유하며, 심볼릭 링크는 접근할 때마다 담긴 경로가 지금도 존재하는지, 권한이 있는지를 다시 확인합니다.

상대 경로 링크는 어디를 기준으로 해석할까

심볼릭 링크에 상대 경로를 담으면, 그 경로는 링크를 여는 프로세스의 현재 디렉터리가 아니라 링크가 놓인 디렉터리를 기준으로 해석됩니다. 다음 코드는 /lab/links/report_link에 ../original.txt가 담겨 있을 때의 두 해석을 비교합니다.

import posixpath

link = "/lab/links/report_link"         # 심볼릭 링크가 놓인 자리
stored = "../original.txt"              # 링크에 저장된 상대 경로 문자열
cwd = "/tmp"                            # 링크를 여는 프로세스의 현재 디렉터리

right = posixpath.normpath(posixpath.join(posixpath.dirname(link), stored))
wrong = posixpath.normpath(posixpath.join(cwd, stored))
print("링크가 있는 디렉터리 기준:", right)
print("현재 디렉터리 기준(잘못된 해석):", wrong)
링크가 있는 디렉터리 기준: /lab/original.txt
현재 디렉터리 기준(잘못된 해석): /original.txt

프로세스가 어느 디렉터리에서 링크를 열든, ../original.txt는 링크가 있는 /lab/links의 부모인 /lab을 기준으로 해석되어 /lab/original.txt를 가리킵니다. 그래서 링크와 대상을 함께 다른 곳으로 옮기는 경우에는 상대 경로 링크가 깨지지 않고, 링크만 다른 곳으로 옮기면 깨집니다. 절대 경로 링크는 그 반대입니다. 규칙은 symlink(2)와 path_resolution(7)에 정리되어 있습니다.

더 깊이: 심볼릭 링크를 따라가는 횟수와 openat2

심볼릭 링크는 다른 심볼릭 링크를 가리킬 수 있으므로, 서로를 가리키는 두 링크처럼 끝없이 따라가야 하는 경로도 만들 수 있습니다. path_resolution(7)에 따르면 Linux는 커널 스택이 넘치는 것과 서비스 거부를 막으려고, 경로 하나를 해석하는 동안 따라가는 심볼릭 링크를 40개로 제한합니다. 이를 넘으면 "Too many levels of symbolic links"라는 ELOOP 오류가 납니다. Linux 2.6.18 전에는 링크 안의 링크를 따라가는 깊이도 5로, 그 뒤에는 8로 제한했지만, Linux 4.2에서 경로 해석 코드가 재귀를 쓰지 않게 바뀐 뒤로는 경로 전체에 걸친 40개 제한만 남았습니다.

Linux 5.6에서 추가된 openat2()는 경로를 해석하는 방법 자체를 호출마다 제한하게 해 줍니다. 매뉴얼은 이 기능의 주된 쓰임새를 믿을 수 있는 프로그램이 믿을 수 없는 경로, 또는 믿을 수 없는 디렉터리 안의 경로를 해석할 때 범위를 묶는 것이라고 설명합니다. RESOLVE_BENEATH는 해석 중에 기준 디렉터리 밖으로 나가는 경로를 거부하고, RESOLVE_IN_ROOT는 그 호출 하나에서만 기준 디렉터리를 루트로 보고 해석합니다. RESOLVE_NO_SYMLINKS는 심볼릭 링크를 따라가지 않고, RESOLVE_NO_XDEV는 마운트 지점을 넘지 않습니다. 마지막 이름의 링크만 따라가지 않는 O_NOFOLLOW와 달리, 이 플래그들은 경로의 모든 단계에 적용됩니다.

마운트와 VFS

Unix 계열에서는 모든 파일 시스템이 루트에서 시작하는 하나의 디렉터리 트리에 합쳐집니다. USB 디스크의 파일 시스템을 /mnt/usb에, 네트워크 저장소를 /mnt/nas에 연결하면, 그 디렉터리 아래로 각 파일 시스템의 내용이 보입니다. 이렇게 파일 시스템을 트리의 한 디렉터리에 연결하는 것을 마운트(mount)라고 하고, 연결한 디렉터리를 마운트 지점(mount point)이라고 합니다. 연결은 자동으로 일어나지 않으며, 관리자나 데스크톱 환경의 자동 마운트 기능이 해야 합니다.

sudo mount /dev/sdb1 /mnt/usb                         # USB 디스크 연결
sudo mount -t nfs 192.168.1.100:/share /mnt/nas       # NFS 연결
sudo mount -t tmpfs -o size=1G tmpfs /mnt/ramdisk     # 메모리 위의 파일 시스템
df -hT                                                # 마운트된 파일 시스템과 종류, 사용량
sudo umount /mnt/usb                                  # 연결 해제(쓰는 중이면 실패)
fuser -mv /mnt/usb                                    # 이 마운트를 쓰고 있는 프로세스

위 명령은 해당 장치와 서버가 있고 권한이 있는 Linux 환경에서 쓰는 예입니다. 장치 이름과 주소는 환경에 맞게 확인해야 합니다. 어떤 프로세스가 마운트 지점 아래의 파일을 열고 있거나 그 안을 현재 디렉터리로 쓰고 있으면 umount가 "target is busy"로 실패하므로, fuser나 lsof로 그 프로세스를 찾습니다. 부팅할 때 자동으로 마운트할 파일 시스템은 /etc/fstab에 한 줄씩 적어 두며, 각 줄은 장치, 마운트 지점, 파일 시스템 종류, 옵션, 백업 여부, 부팅할 때 검사하는 순서로 이루어집니다.

마운트가 매끄럽게 동작하는 것은 Linux 커널의 VFS(virtual file system) 계층 덕분입니다. VFS는 ext4, XFS, Btrfs, NFS, FAT 같은 서로 다른 파일 시스템 위에 공통의 인터페이스를 둡니다. 프로그램이 open()이나 read()를 부르면 VFS가 받아, 그 파일이 속한 파일 시스템의 구현 함수를 부릅니다. 경로를 해석하다 마운트 지점을 만나면, VFS는 그 아래에 연결된 파일 시스템의 루트로 넘어가 나머지 경로를 해석합니다. 그래서 /mnt/usb/report.txt에서 report.txt는 USB의 FAT 파일 시스템 구현이 찾아 줍니다. 프로그램은 로컬 디스크든, USB든, 네트워크 저장소든 같은 경로와 같은 시스템 콜로 다룰 수 있고, 새 파일 시스템을 추가하려면 VFS의 인터페이스만 구현하면 됩니다.

Windows는 C:, D:처럼 드라이브 문자로 파일 시스템을 나누어 보여 주지만, 내부적으로는 비슷한 계층을 두고 있고, NTFS의 빈 폴더에 다른 볼륨을 연결하는 기능도 있습니다.

정리

  • 디렉터리는 이름과 inode 번호의 짝을 모은 특별한 파일입니다. 단일 단계와 이중 단계를 거쳐 트리 구조가 표준이 되었고, 링크로 한 파일을 여러 곳에 두는 비순환 그래프로 확장됩니다.
  • 경로는 왼쪽부터 이름 하나씩 디렉터리의 내용에서 찾아 inode로 바꾸며 해석합니다. 덴트리 캐시가 이를 빠르게 하고, 중간 디렉터리마다 권한도 확인합니다.
  • 하드 링크는 같은 inode에 이름을 하나 더 붙이는 것으로, 모든 이름이 대등하며 디렉터리와 다른 파일 시스템에는 만들 수 없습니다.
  • 심볼릭 링크는 경로 문자열을 담은 별도의 파일로, 디렉터리와 다른 파일 시스템도 가리킬 수 있지만 대상이 사라지면 끊어집니다. 상대 경로는 링크가 놓인 디렉터리를 기준으로 해석됩니다.
  • 마운트는 파일 시스템을 하나의 트리에 연결하고, VFS는 여러 파일 시스템을 같은 시스템 콜로 다룰 수 있게 합니다.

연습 문제

1. 하드 링크와 심볼릭 링크의 운명

/lab/original.txt와 /lab/hardlink.txt는 inode A를 가리키는 두 이름이고, /lab/links/report_link는 ../original.txt를 담은 심볼릭 링크입니다. 열린 참조는 없습니다. 현재 디렉터리가 /tmp인 프로세스가 report_link를 열면 어느 파일이 열리나요? 그다음 /lab/original.txt만 지웠을 때와, 같은 자리에 inode B인 새 파일을 만들었을 때 두 링크가 각각 무엇을 가리키는지 설명하세요.

해설 보기

상대 경로는 링크가 놓인 /lab/links를 기준으로 해석되므로, 프로세스의 현재 디렉터리와 상관없이 /lab/original.txt, 곧 inode A가 열립니다. /lab/original.txt를 지우면 inode A는 /lab/hardlink.txt라는 이름 하나로 남아 링크 수가 1이 되고, 심볼릭 링크는 담긴 경로에 파일이 없으므로 끊어진 링크가 됩니다. 같은 자리에 inode B인 새 파일을 만들면, 심볼릭 링크는 경로만 보므로 새 파일 B를 가리키지만, 하드 링크 hardlink.txt는 여전히 원래의 A를 가리킵니다. 심볼릭 링크가 B를 가리켜도 B의 링크 수는 늘지 않습니다.

2. 권한 때문에 열리지 않는 파일

/srv/data/report.txt의 권한이 rw-r--r--여서 누구나 읽을 수 있습니다. 그런데 일반 사용자가 이 파일을 열면 "Permission denied"가 납니다. /srv/data 디렉터리의 권한이 rwx------이고 소유자는 root라면, 왜 열리지 않는지 경로 해석 과정으로 설명하세요.

해설 보기

경로를 해석할 때 운영체제는 /, srv, data를 차례로 지나가며 각 디렉터리에 들어갈 권한, 곧 실행(x) 권한을 확인합니다. /srv/data의 권한이 rwx------이면 소유자인 root만 들어갈 수 있으므로, 일반 사용자는 data 디렉터리의 내용에서 report.txt를 찾는 단계에 이르지도 못하고 거기서 실패합니다. 파일 자체의 권한이 누구나 읽을 수 있게 되어 있어도, 그 파일에 이르는 경로의 디렉터리를 지나갈 수 없으면 소용이 없습니다. 이 사용자가 파일을 읽게 하려면 /srv/data에 실행 권한을 주거나, 파일을 사용자가 접근할 수 있는 다른 디렉터리로 옮겨야 합니다.

3. 링크와 마운트

/home과 /mnt/backup이 서로 다른 파일 시스템입니다. /home/user/notes.txt를 /mnt/backup에서도 보이게 하려 할 때 하드 링크와 심볼릭 링크 가운데 무엇을 쓸 수 있는지, 그리고 백업이 목적이라면 링크로 충분한지 설명하세요.

해설 보기

두 디렉터리가 서로 다른 파일 시스템이므로 하드 링크는 만들 수 없습니다. inode 번호는 한 파일 시스템 안에서만 의미가 있어, /mnt/backup의 디렉터리 항목이 /home 파일 시스템의 inode를 가리킬 수 없기 때문입니다. 심볼릭 링크는 경로만 담으므로 만들 수 있습니다. 하지만 백업이 목적이라면 링크로는 충분하지 않습니다. 심볼릭 링크는 원래 파일을 가리킬 뿐이어서, 원래 파일이 지워지거나 망가지면 링크도 쓸모가 없어집니다. 같은 파일 시스템의 하드 링크도 데이터는 하나뿐이어서, 한 이름으로 내용을 고치거나 망가뜨리면 다른 이름으로도 그대로 보입니다. 백업은 데이터 자체를 다른 장치에 복사해 두어야 합니다.

다음 글 파일 시스템 구현에서는 파일 시스템이 디스크 블록을 파일에 어떻게 나누어 주고 inode로 관리하는지, 그리고 갑자기 전원이 꺼져도 망가지지 않게 하는 저널링을 살펴봅니다.