같은 파일을 두 번 열고, 그중 하나를 dup()으로 복제하면 파일 디스크립터가 셋이 된다. 셋 다 같은 파일을 가리킨다. 그런데 하나로 5바이트를 읽고 나서 셋으로 한 바이트씩 읽으면 이렇게 나온다
fd 3 → F fd 4 → A fd 5 → G
fd 3과 fd 5는 이어서 읽고 fd 4만 처음부터 읽는다. 셋 다 같은 파일인데 읽는 위치가 둘로 갈렸다. 파일 디스크립터가 파일을 직접 가리키는 것이 아니라 중간에 한 겹이 더 있기 때문이다
마지막 편은 입출력 장치와 파일 시스템이다. 장치가 주소 공간 어디에 앉아 있는지, 디스크가 왜 느렸고 지금은 왜 덜 느린지, 파일 하나를 여는 일이 커널 안에서 몇 단계를 거치는지, 그리고 지운 파일이 어디까지 남는지를 다룬다
이 글에서 자주 나오는 용어
- 메모리 매핑 입출력(MMIO): CPU 주소 공간의 일부를 메모리가 아니라 장치로 해석하게 해 두는 방식
- DMA(Direct Memory Access): 장치가 CPU를 거치지 않고 메모리에 직접 읽고 쓰는 것
- 캐릭터 / 블록 디바이스: 바이트 단위로 주고받는 장치와 블록 단위로 주고받는 장치
- 파일 디스크립터(File Descriptor): 프로세스가 열어 둔 파일을 가리키는 작은 정수
- 열린 파일 설명(Open File Description): 커널이
open()한 번마다 만드는 항목. 읽는 위치와 플래그를 여기에 담는다 - inode: 파일의 실제 정보를 담은 구조체. 이름은 여기 들어 있지 않다
- 익스텐트(Extent): 연속된 블록 구간을 시작 위치와 길이로 한 번에 기록한 것
- TRIM / discard: 파일 시스템이 저장장치에게 “이 블록은 이제 안 쓴다”고 알려 주는 명령
장치는 메모리 주소에 앉아 있다
주변장치는 메인보드의 버스로 연결된다. 버스는 실제로 세 갈래다. 어디를 다룰지 알리는 주소 버스, 값이 오가는 데이터 버스, 읽는지 쓰는지를 알리는 제어 버스다
장치는 데이터를 주고받는 단위로 나뉜다. 키보드나 마우스처럼 바이트 단위로 조금씩 주고받는 캐릭터 디바이스, 디스크처럼 블록 단위로 크게 주고받는 블록 디바이스다. 리눅스는 이 구분을 파일 종류로 드러낸다
$ stat -c '%n %F' /dev/vda /dev/loop0 /dev/tty /dev/null /dev/random /dev/vda block special file /dev/loop0 block special file /dev/tty character special file /dev/null character special file /dev/random character special file 블록 디바이스 15개 캐릭터 디바이스 88개
/proc/devices도 두 목록으로 나뉘어 있고, 블록 쪽에는 virtblk, zram, loop처럼 저장장치 계열만 들어 있다. 그래픽카드는 블록 디바이스가 아니라 캐릭터 디바이스로 붙는다. 이 장비는 화면이 없는 서버라 /dev/dri가 없어서 직접 보이지는 않았다
그러면 CPU는 이 장치들과 어떻게 대화할까. 두 가지 방법이 있고 둘 다 지금 이 장비에서 쓰이고 있다
$ head -4 /proc/ioports 0000-0cf7 : PCI Bus 0000:00 0000-001f : dma1 0060-0060 : keyboard 0070-0071 : rtc_cmos $ grep -A1 '4000000000' /proc/iomem 4000000000-7fffffffff : PCI Bus 0000:00 4000000000-400007ffff : 0000:00:01.0 4000000000-400007ffff : virtio-pci-modern
위쪽은 포트 매핑 입출력이다. 메모리와 별개인 주소 공간이 따로 있고 전용 명령어로 접근한다. 키보드가 0x60번 포트 하나를 차지하고 있는 것이 보인다. 아래쪽이 메모리 매핑 입출력이다. 0x4000000000부터 512KiB 구간이 메모리가 아니라 virtio 장치의 레지스터로 잡혀 있다. 드라이버가 이 주소에 값을 쓰면 메모리에 쓰이는 것이 아니라 장치가 명령을 받는다
리눅스 커널 문서가 정의를 한 줄로 적는다
a part of the CPU's address space is interpreted not as accesses to memory, but as accesses to a device
출처는 Bus-Independent Device Accesses, Linux Kernel documentation이다. 메모리 매핑 입출력은 주소 공간을 나눠 쓰는 기법이 아니라, 주소 공간의 일부를 장치로 해석하게 만드는 기법이다. CPU가 메모리를 다루던 명령어를 그대로 써서 장치를 다룰 수 있다는 것이 이 방식의 값이다. 같은 문서는 포트 공간을 “일반 메모리 주소 공간과 분리된 주소 범위”로 적고 접근 속도도 메모리 매핑 쪽이 더 빠르다고 한다
DMA는 여기에 얹히는 별도의 이야기다. 입출력 제어기가 장치에서 읽은 데이터를 메모리에 넣으려면 원래는 CPU가 중간에서 옮겨 줘야 한다. DMA 제어기를 두면 장치가 CPU를 거치지 않고 메모리에 직접 쓴다. CPU는 “이만큼 옮겨 두라”고 지시하고 다른 일을 하다가 완료 인터럽트만 받는다. 운영체제 부팅 과정은 BIOS가 아니라 UEFI에서 시작한다에서 본 인터럽트와 폴링의 이야기가 여기서 다시 쓰인다
디스크가 느린 이유는 회전과 이동이었다
하드디스크는 움직이는 부품으로 데이터를 읽는다. 스핀들이라는 축에 플래터라는 자기 원판이 여러 장 붙어 있고, 디스크 암 끝의 헤드가 표면을 읽는다. 플래터는 동심원 모양의 트랙으로 나뉘고 트랙은 다시 섹터로 나뉜다. 헤드들이 암에 고정돼 함께 움직이므로, 여러 플래터에서 같은 번호의 트랙이 한 묶음이 되는데 이것을 실린더라고 부른다
특정 섹터를 읽으려면 두 가지를 기다려야 한다. 헤드를 목표 실린더까지 옮기는 시간, 그리고 목표 섹터가 헤드 밑으로 돌아올 때까지 기다리는 시간이다. 앞의 것이 탐색 시간이고 하드디스크가 느린 주된 이유다. 다른 전자 부품이 나노초로 움직이는 동안 헤드는 밀리초를 쓴다
플래시 메모리는 움직이는 부품이 없다. 전기적으로 읽으므로 빠르고 조용하고 충격에도 강하다. 대신 제약이 하나 있다. 이미 값이 들어 있는 자리에 덮어쓸 수 없고, 지우고 다시 써야 하는데 지울 수 있는 횟수가 정해져 있다. 그래서 컨트롤러가 쓰기를 여러 셀에 골고루 흩어 주는 웨어 레벨링을 한다. 같은 논리 주소에 계속 써도 물리 셀은 매번 다른 곳이 쓰인다. 이 동작이 마지막 절에서 다시 나온다
탐색 시간이 실제로 존재하는지는 재 보면 된다. 32MiB 파일을 4KiB씩 4,000번 읽되 한 번은 순서대로, 한 번은 무작위 위치로 읽었다. 페이지 캐시를 거치지 않도록 O_DIRECT로 열었다
4 KiB씩 4000회 순차 읽기 207.6 ms (1회 51.9 us) 4 KiB씩 4000회 무작위 읽기 219.2 ms (1회 54.8 us)
차이가 5.6%뿐이다. 회전하는 디스크였다면 무작위 읽기가 수십 배 느려야 한다. 이 장비의 가상 디스크는 스스로를 회전형이라고 보고하지만 실제 뒤에는 플래시가 있고, 그래서 탐색 시간이라는 항목 자체가 없다. 디스크 스케줄링 알고리즘들이 한때 중요한 주제였다가 지금은 덜 다뤄지는 이유가 이 표에 있다
섹터 크기에도 최근 변화가 있다. 이 디스크는 물리 섹터 4096바이트, 논리 섹터 512바이트로 보고한다
$ lsblk -o NAME,SIZE,PHY-SEC,LOG-SEC vda 256G 4096 512
물리적으로는 4KiB 단위로 다루면서 옛 소프트웨어와의 호환을 위해 512바이트인 척 보여 준다는 뜻이다. 4KiB 경계에 맞지 않게 쓰면 장치가 읽고 고쳐 쓰는 과정을 한 번 더 하게 된다
파일 디스크립터는 파일이 아니라 열린 파일 설명을 가리킨다
사용자가 파일에 접근하려면 open()을 부르고, 커널은 작은 정수를 돌려준다. 이 정수가 파일 디스크립터다. 여기까지는 널리 알려져 있는데, 이 정수가 무엇을 가리키는지는 한 단계 더 들어간다. 세 겹이다
프로세스 A 커널
┌──────────────┐ ┌─────────────────────┐ ┌──────────┐
│ fd 3 ────────┼───┬───▶│ 열린 파일 설명 #1 │─┐ │ │
│ fd 4 ────────┼───┼──┐ │ 읽는 위치 5 │ ├───▶│ inode │
│ fd 5 ────────┼───┘ │ └─────────────────────┘ │ │ 786700 │
└──────────────┘ │ ┌─────────────────────┐ │ │ │
프로세스마다 하나 └▶│ 열린 파일 설명 #2 │─┘ └──────────┘
있는 표 │ 읽는 위치 0 │ 파일마다 하나
└─────────────────────┘
open() 한 번에 하나
알파벳 26자가 든 파일을 두 번 열고 첫 번째 것을 dup()으로 복제한 뒤, 상태를 찍어 봤다
세 개의 파일 디스크립터가 같은 파일을 가리킨다 fd 3 open() 1회차 오프셋 0 inode 786700 fd 4 open() 2회차 오프셋 0 inode 786700 fd 5 dup(a) 오프셋 0 inode 786700 fd 3 로 5바이트를 읽는다 (읽은 내용: ABCDE) fd 3 open() 1회차 오프셋 5 inode 786700 fd 4 open() 2회차 오프셋 0 inode 786700 fd 5 dup(a) 오프셋 5 inode 786700
셋 다 inode 786700으로 같은 파일이다. 그런데 fd 3으로만 읽었는데 fd 5의 오프셋도 같이 5로 움직였고 fd 4는 0에 남았다. dup()으로 만든 디스크립터는 원본과 같은 열린 파일 설명을 공유하고, 따로 open() 한 디스크립터는 자기 것을 새로 갖기 때문이다. open(2) 문서가 이 구조를 그대로 적는다
A file descriptor is a reference to an open file description ... When a file descriptor is duplicated (using dup(2) or similar), the duplicate refers to the same open file description as the original file descriptor, and the two file descriptors consequently share the file offset and file status flags ... Each open() of a file creates a new open file description; thus, there may be multiple open file descriptions corresponding to a file inode.
출처는 open(2)다. 그래서 세 디스크립터로 한 바이트씩 읽으면 도입부의 F, A, G가 나온다
이 구조가 실무에서 드러나는 자리가 몇 군데 있다. fork()로 만든 자식 프로세스는 부모의 디스크립터를 복제하는데, 그 복제본은 dup()과 마찬가지로 같은 열린 파일 설명을 가리킨다. 부모와 자식이 같은 로그 파일에 쓸 때 서로 덮어쓰지 않고 이어서 쓰이는 이유가 오프셋을 공유하기 때문이다. 반대로 두 프로세스가 각자 open() 한 경우에는 오프셋이 따로이므로 서로를 덮어쓴다. 셸의 리다이렉션이 >와 >>로 갈리는 것도 같은 층위의 이야기다
파일 제어 블록이라는 이름으로 배우는 구조가 이 세 겹 중 어디인지는 시스템마다 다르다. 유닉스 계열에서는 위 그림의 가운데와 오른쪽이 나뉘어 있고, 파일 디스크립터는 그 둘 중 어느 것도 아니라 맨 왼쪽 표의 색인이다
파일의 정보는 이름이 아니라 inode에 있다
오른쪽 끝의 inode에는 크기, 권한, 소유자, 시각, 그리고 데이터가 어느 블록에 있는지가 들어 있다. 들어 있지 않은 것이 하나 있는데 파일 이름이다. 이름은 디렉터리가 들고 있고, 디렉터리는 “이름 → inode 번호” 짝을 담은 파일이다
이름과 파일이 분리돼 있으므로 이름을 여럿 붙일 수 있다
$ ln original.txt hardlink.txt $ ln -s original.txt symlink.txt $ ls -li inode 786698 -rw-r--r-- original.txt inode 786698 -rw-r--r-- hardlink.txt inode 786703 lrwxrwxrwx symlink.txt -> original.txt
original.txt와 hardlink.txt는 inode 번호가 같다. 사본이 아니라 같은 파일에 붙은 두 이름이다. 심볼릭 링크는 자기 inode를 따로 갖고 그 안에 대상의 경로 문자열을 담는다
여기서 “파일을 지운다”가 무슨 뜻인지가 정해진다. 지우는 것은 디렉터리에서 이름을 떼는 일이고, inode는 붙어 있는 이름이 모두 사라지고 열어 둔 프로세스도 없을 때 회수된다. 열어 둔 채로 지워 봤다
열었을 때 inode 786691, 링크 수 1 지운 뒤 inode 786691, 링크 수 0 디렉터리에 남아 있나: False 그래도 읽히나: '이 내용은 파일을 지운 뒤에도 남아 있다' /proc/self/fd 가 가리키는 곳: .../doomed.txt (deleted)
링크 수가 0이 되고 디렉터리에서도 사라졌는데 열어 둔 디스크립터로는 계속 읽힌다. 서버에서 로그 파일을 지웠는데 디스크 여유 공간이 늘어나지 않는 현상이 이것이다. 프로세스가 그 파일을 아직 열고 있으면 이름만 없어지고 블록은 잡혀 있다. 프로세스를 재시작하거나 로그를 다시 열게 해야 공간이 돌아온다
디렉터리 트리에 순환을 만드는 것은 바로가기가 아니다
디렉터리는 여러 층으로 쌓인다. 초기 파일 시스템은 최상위에만 디렉터리를 둘 수 있었지만 지금은 어디서나 하위 디렉터리를 만들 수 있는 트리 구조다. 디렉터리 안의 점 하나는 자기 자신, 점 두 개는 상위 디렉터리를 가리키고, 루트의 점 두 개는 자기 자신을 가리킨다
트리에 순환이 생기면 경로를 따라가는 코드가 무한히 돌게 된다. 그래서 커널은 순환이 생길 수 있는 연결을 아예 막는다
$ ln dir1 dir2 ln: dir1: hard link not allowed for directory
디렉터리에는 하드 링크를 걸 수 없다. link(2)가 이 경우를 EPERM으로 규정한다(link(2)). 하드 링크는 inode를 직접 가리키므로 디렉터리에 걸 수 있게 두면 곧바로 순환이 만들어지고, 그 순환은 파일 시스템 안에서 구분할 방법이 없다
심볼릭 링크는 다르다. 경로 문자열을 담은 별도의 파일이므로 만들 수는 있고, 순환도 만들 수 있다
$ mkdir -p loop/inner && ln -s ../.. loop/inner/up 한 번 따라가기: 성공 41번 겹쳐 따라가면: Too many levels of symbolic links
무한히 도는 대신 커널이 40번에서 끊는다. 리눅스는 경로를 푸는 동안 따라갈 수 있는 심볼릭 링크 수를 40으로 제한하고 넘으면 ELOOP을 돌려준다(path_resolution(7)). 관측한 41번째 실패가 그 한도와 맞는다
윈도우의 바로가기 아이콘은 이 이야기와 층이 다르다. .lnk는 탐색기가 해석하는 보통 파일이고 파일 시스템의 경로 해석에는 관여하지 않는다. 윈도우에서 순환을 만들 수 있는 것은 심볼릭 링크와 정션이다. 트리에 순환을 들이는 것은 사용자 인터페이스의 바로가기가 아니라 파일 시스템 수준의 링크다
블록을 잇는 방법이 파일 구조를 정한다
파일 안의 데이터를 어떻게 늘어놓을지는 세 가지로 나눠 배운다. 처음부터 끝까지 이어 붙이는 순차 구조, 키를 해시 함수에 넣어 저장 위치를 정하는 직접 구조, 앞의 둘을 합쳐 인덱스 테이블로 원하는 지점을 바로 찾는 인덱스 구조다. 다만 이 분류는 운영체제가 강제하는 것이 아니다. 오늘날의 파일 시스템은 파일을 그냥 바이트가 늘어선 것으로 보고, 그 안에 구조를 넣는 일은 응용 프로그램이나 라이브러리가 한다. 데이터베이스가 자기 파일 안에 B-트리 인덱스를 만들어 두는 것이 대표적이다. JSON처럼 텍스트로 값을 적어 두는 형식은 이 분류와 층이 다르다. 사람이 읽는 순서대로 적힌 문자열이므로 저장 위치를 해시로 정하는 구조와는 관계가 없다
파일 시스템이 실제로 맡는 것은 그 바이트들을 디스크의 어느 블록에 놓고 어떻게 다시 찾을지다. 파일 시스템은 디스크를 일정한 크기의 블록으로 나눈다. 이 장비의 ext4는 4096바이트를 쓴다. 한 파일은 보통 블록 여러 개로 이루어지고, 그 블록들을 어떻게 이어 두느냐가 설계의 갈림길이다
연속 할당은 블록을 디스크에 나란히 둔다. 시작 블록과 길이만 알면 되지만 가상 메모리 주소 변환은 세그멘테이션이 아니라 페이징 혼자 한다에서 본 외부 단편화가 그대로 생기므로 실제로는 쓰이지 않는다. 불연속 할당은 흩어 두고 위치를 따로 관리하는 방식이고 둘로 나뉜다. 연결 할당은 각 블록이 다음 블록의 번호를 들고 있게 해서 연결 리스트처럼 잇는다. 중간 블록 하나만 읽으려 해도 앞에서부터 따라가야 한다. 인덱스 할당은 블록 번호들을 모아 둔 인덱스 블록을 따로 두고 그것을 가리킨다. 파일이 커지면 인덱스 블록을 이어 붙여 2중, 3중 간접 포인터로 확장한다. 유닉스 계열의 inode가 이 방식이다
ext4는 여기서 한 걸음 더 갔다. 블록 번호를 하나씩 적는 대신 연속 구간을 시작 위치와 길이로 묶어 적는다. 익스텐트다. 커널 문서가 차이를 숫자로 설명한다
In ext4, the file to logical block map has been replaced with an extent tree ... 1,000 contiguous blocks ... the mapping is reduced to a single struct ext4_extent with ee_len = 1000.
출처는 ext4 Inode Data Structure, Linux Kernel documentation이다. 같은 문서에 따르면 익스텐트 하나가 최대 32,768블록을 담고, 처음 네 개는 inode 안에 바로 들어가 추가 블록이 필요 없다
실제 파일이 어떻게 놓였는지는 조회할 수 있다. 32MiB 파일을 한 번에 쓰고 확인했다
$ filefrag -v big.bin File size of big.bin is 33554432 (8192 blocks of 4096 bytes) ext: logical_offset: physical_offset: length: flags: 0: 0.. 8191: 3166208.. 3174399: 8192: last,eof big.bin: 1 extent found
블록 8,192개가 익스텐트 한 줄로 기록됐다. 연결 할당이었다면 8,192개의 포인터가, 옛 인덱스 방식이었다면 인덱스 블록 여러 개가 필요했을 자리다
조각나는 상황도 만들어 봤다. 파일 두 개를 번갈아 조금씩 키우면 서로의 블록 사이에 끼어들게 된다
a.bin: 10 extents found b.bin: 10 extents found
같은 크기인데 익스텐트가 열 개로 늘었다. 읽을 때 따라가야 할 항목이 그만큼 많아진다
블록 단위로 관리하므로 마지막 블록의 자투리는 버려진다. 12,456바이트짜리 파일을 만들어 실제 할당량을 확인했다
크기 12456 바이트 디스크 할당 16384 바이트 → 내부 단편화 3928 바이트
4,096바이트 블록 세 개로는 모자라 네 개가 쓰였고 3,928바이트가 남았다. 블록을 작게 잡으면 이 낭비가 줄지만 관리할 블록 수가 늘고, 크게 잡으면 반대가 된다. 4KiB가 널리 쓰이는 것은 페이지 크기와 맞아떨어져 페이지 캐시와 다루기 편하기 때문이기도 하다
지운 파일이 남아 있는지는 저장 매체가 정한다
파일을 지우면 파일 시스템은 디렉터리에서 이름을 떼고 쓰던 블록을 빈 블록 목록에 되돌린다. 블록 안의 내용은 건드리지 않는다. 그래서 회전 디스크에서는 덮어쓰이기 전까지 데이터가 남아 있고, 그것을 읽어 내는 것이 전통적인 복구와 포렌식이다
플래시에서는 이야기가 달라진다. 파일 시스템이 블록을 반납할 때 저장장치에게 “이 블록은 이제 안 쓴다”고 알려 주는 명령이 있고, 이것이 TRIM이다. 리눅스는 주기적으로 도는 fstrim이나 discard 마운트 옵션으로 이 일을 한다. fstrim은 “마운트된 파일 시스템에서 파일 시스템이 쓰지 않는 블록을 버리는 데 쓴다”고 문서에 적혀 있다(fstrim(8)). 통보를 받은 컨트롤러는 그 블록을 미리 지워 두므로 나중에 읽으면 0이 돌아온다. 이름만 지우면 데이터가 남는다는 전제가 여기서 깨진다
그렇다고 확실히 사라진 것도 아니다. NIST의 저장 매체 정리 지침은 반대 방향의 문제를 짚는다
flash memory-based storage devices that contain spare cells and perform wear levelling make it infeasible for a user to sanitize all previous data ... the device cannot support directly addressing all areas in which sensitive data has been stored using the native read and write interface
출처는 NIST SP 800-88r2, Guidelines for Media Sanitization이고 2025년 9월 판이다. 웨어 레벨링 때문에 같은 논리 주소에 쓴 값이 물리적으로는 여러 셀에 흩어져 있고, 예비 셀과 오버프로비저닝 영역은 일반 읽기·쓰기 경로로 닿지 않는다. 같은 문서는 자기 디스크에서 쓰던 덮어쓰기 방식을 플래시에 그대로 적용해 온 사용자들이 오히려 노출 위험을 키우고 있다고 적는다
두 이야기를 합치면 이렇게 된다. 플래시에서 지운 파일은 보통의 방법으로는 읽히지 않고, 그렇다고 물리적으로 없어졌다고 단정할 수도 없다. 논리적으로 닿지 않는 것과 물리적으로 사라진 것은 다르다. 기기를 처분할 때 파일을 지우거나 포맷하는 것으로 충분하지 않은 이유가 이것이고, 같은 지침이 플래시 매체에는 덮어쓰기 대신 장치 자체의 소거 명령이나 물리적 파괴를 권하는 이유이기도 하다
마무리
입출력과 파일 시스템은 층을 쌓아 추상을 만드는 방식이 가장 노골적으로 드러나는 곳이다. 장치는 주소 공간의 한 구간으로 위장해 메모리처럼 다뤄지고, 흩어진 블록들은 익스텐트로 묶여 하나의 파일처럼 보이고, 그 파일은 정수 하나로 줄어 프로그램에 건네진다
층을 세는 일이 실무에서 값을 하는 자리는 대체로 층이 어긋날 때다. 파일을 지웠는데 공간이 안 돌아오면 이름과 inode가 갈라진 자리를 보게 되고, 두 프로세스가 같은 로그를 덮어쓰면 열린 파일 설명이 공유되지 않은 자리를 보게 된다. 정수 하나가 세 겹을 가리킨다는 사실을 알면 두 증상이 같은 그림 위에 놓인다
시리즈를 마치며
일곱 편에 걸쳐 운영체제 개론의 개념들을 지금 돌아가는 커널 위에서 하나씩 확인했다. 개념은 거의 그대로 맞았고 구현은 상당 부분 달라져 있었다
| 편 | 확인한 것 |
|---|---|
| 1 | 부팅은 BIOS와 MBR이 아니라 UEFI와 ESP를 거친다 |
| 2 | fork는 메모리를 복사하지 않지만 페이지 테이블은 복사한다 |
| 3 | 리눅스 스케줄러는 MLFQ가 아니라 EEVDF이고 nice는 가중치다 |
| 4 | 잠금의 99.96%는 커널에 가지 않고 CPU 명령어로 끝난다 |
| 5 | x86-64에서 주소를 변환하는 것은 페이징 하나뿐이다 |
| 6 | 작업 집합이 물리 메모리를 넘는 순간 성능이 1000배 나빠진다 |
| 7 | 파일 디스크립터는 파일이 아니라 열린 파일 설명을 가리킨다 (이 글) |
교과서를 덮고 나서 할 일은 그 개념이 지금 쓰는 시스템에서 어떤 이름으로 살아 있는지 찾아보는 것이다. /proc 아래에는 이 글들에서 쓴 것보다 훨씬 많은 것이 노출돼 있고, 대부분은 명령어 한 줄로 읽힌다
출처와 범위
이 시리즈는 인프런의 그림으로 쉽게 배우는 운영체제(감자님) 강의를 따라 정리한 것이고, 강의 전체를 일곱 편으로 나눈 마지막 편이다. 개념 구성과 설명 순서의 출처는 강의이며, 장치와 파일 시스템의 동작을 말하는 문장은 본문에 건 커널 문서·man 페이지·NIST 지침으로 확인했다. 코드 블록에 실은 /proc/iomem, /proc/ioports, stat, filefrag, lsblk, /proc/self/fdinfo 출력과 읽기 지연 측정값은 ext4와 virtio 디스크를 쓰는 2코어 x86-64 리눅스 가상 머신에서 직접 돌린 결과 그대로다. 이 장비에는 화면 장치가 없어 그래픽카드의 장치 종류는 직접 보지 못했고, 회전하는 물리 디스크도 없어 탐색 시간이 있는 쪽의 측정값은 없다. 측정에 쓴 C·파이썬 코드는 본문에 인용한 부분 외에는 싣지 않았고 공개 저장소도 없다
참고 자료
- Bus-Independent Device Accesses — The Linux Kernel documentation
- open(2) — Linux manual page
- link(2) — Linux manual page
- path_resolution(7) — Linux manual page
- fstrim(8) — Linux manual page
- ext4 Inode Data Structure — The Linux Kernel documentation
- NIST SP 800-88r2, Guidelines for Media Sanitization (2025-09)