본 글은 The Linux Kernel 을 정리한 것이며, 출처를 밝히지 않은 모든 이미지는 원글에 속한 것입니다.


리눅스 파일 시스템

  • 리눅스는 각 파일 시스템이 계층적인 트리 구조로 통합해서 나타내므로, 파일 시스템이 하나인 것처럼 보여줌
    • 윈도우즈는 드라이브 이름 등의 장치 식별자로 구분
    • 새로운 파일 시스템을 마운트하면 하나의 파일 시스템에 추가된 형태로 보여짐
    • 파일 시스템은 로컬 시스템이 아닌 네트워크 연결로 원격지에서 마운트된 디스크도 포함 가능
  • 모든 파일 시스템은 어떤 타입이든지 하나의 디렉토리에 마운트되어, 마운트된 파일 시스템의 파일들이 그 디렉토리의 내용을 구성
    • 이는 많은 파일 시스템을 지원할 수 있게 함
    • 이러한 디렉토리를 마운트 디렉토리 또는 마운트 포인트라고 부름
  • 파일 시스템의 마운트가 해제되면, 마운트 디렉토리가 원래 가지고 있던 파일들이 보여짐
  • 디스크가 초기화될 때, 파티션 구조를 가지며 물리 디스크를 논리적으로 나누는 작업을 파티셔닝(partitioning)이라고 함
  • 각 파티션은 하나의 파일 시스템을 가지며, 파일 시스템은 장치의 블럭에 파일을 디렉토리나 소프트 링크(윈도우즈의 바로가기 같은) 등과 함께 논리적인 계층 구조로 구성 (블럭 장치만이 파일 시스템을 저장할 수 있음)
  • 파일 시스템은 일련의 블럭 장치들의 집합이며, 운영체제는 물리 디스크가 어떤 형태(구조)인지 알 필요가 없음
    • 하드웨어의 추상화 단계로 물리 장치의 세부 사항을 알 필요가 없음
    • 블럭을 읽는 요청은 각 블럭 디바이스 드라이버의 책임(블럭이 읽어야 하는 위치를 매핑)으로, 파일 시스템은 어떤 장치에 블럭이 있는지 상관없이 동작함
  • 파일(file)이란 데이터의 집합이며, 파일에 담긴 데이터 외에 파일 시스템의 구조도 포함되는데, 이러한 정보들은 신뢰성있게 저장되어야 하며 파일 시스템은 무결성을 보장해야 함
  • 처음에 제안된 파일 시스템은 확장 파일 시스템(Extended File System, EXT)으로, 가상 파일 시스템(Virtual File System, VFS)이라는 인터페이스 계층을 통해 실제 파일 시스템이 운영체제와 운영체제의 서비스로부터 분리됨
    • 즉, 파일 시스템의 세부 사항들이 소프트웨어에 의해 변환되는 것이고, VFS를 지원하는 파일 시스템들은 모두 사용 가능
  • VFS는 마운트되어 사용중인 각 파일 시스템의 정보를 메모리에 캐싱하며, 파일이나 디렉토리가 생성, 삭제, 또는 자료가 입력될 때 캐시 안의 자료를 정확하게 수정함. 이는 메모리 내 캐시와 디스크의 내용의 일치성을 보장함
    • 이 과정에서 디바이스 드라이버에 의해 접근하려는 파일이나 디렉토리 관련 자료구조가 생성되거나 제거됨
  • 버퍼 캐시(Buffer Cache)는 각 파일 시스템이 블럭 장치에 접근할 때 사용되며, 블럭에 접근할 때마다 그 블럭은 버퍼 캐시에 들어가고 상태에 따라 다양한 큐에 추가됨. 버퍼 캐시는 데이터 버퍼를 캐시할 뿐만 아니라, 블럭 디바이스 드라이버와의 비동기적인 인터페이스 관리에도 도움이 됨.
  • EXT가 성능이 떨어지면서 2차 확장 파일 시스템(EXT2)가 등장

https://stackoverflow.com/questions/34253611/are-device-files-implemented-by-the-specific-file-systems-or-the-virtual-file-sy
공통 파일 시스템 인터페이스
https://scslab-intern.gitbooks.io/linux-kernel-hacking/content/chapter13.html

2차 확장 파일 시스템(Second Extended File System, EXT2)

EXT2 파일 시스템의 물리적 배치도

  • EXT2의 파일에 저장된 데이터는 블럭에 저장되는데, 데이터 블럭의 크기는 동일하며, 서로 다른 EXT2의 경우 크기가 다를 수 있음
  • 블럭 크기는 파일 시스템이 만들어질 때 결정되며, 모든 파일의 크기는 블럭의 크기에 따라 올림이 됨
    • 예를 들어, 블럭 크기가 1024바이트이고, 파일 크기가 1025바이트일 때, 이 파일은 1024바이트 블럭 2개를 차지함
    • 실제로 파일 하나당 평균 블럭 크기의 절반을 낭비하는데, 이는 CPU의 메모리 사용량과 디스크 공간의 활용도 사이에 트레이드 오프(trade off) 문제로, 리눅스는 CPU 부담을 줄이는 방향으로 디스크 공간의 활용도를 희생함
  • 파일 시스템의 모든 블럭이 데이터만 저장하는 것은 아니며, 몇몇 블럭에는 파일 시스템의 구조를 표현하는 정보가 있음
  • 파티션은 하나의 파일 시스템을 가지는데, 파일 시스템은 공간을 블럭 그룹으로 쪼개고, 각 블럭 그룹은 파일 시스템에서 무결성을 보장해야 하는 정보를 중복해서 갖고 있어 파일 시스템 복구 시 중복 정보를 이용
  • EXT2 inode
    • EXT2는 파일 시스템 배치도를 정의하기 위해 시스템 내의 각 파일을 inode 자료구조로 표현하며, 모든 inode는 inode 테이블에 저장되고, 파일은 일반 파일, 디렉토리, 소프트 링크, 하드 링크, 장치 파일을 포함
    • 즉, 디렉토리도 inode로 기술되며, 디렉토리에 속하는 파일들의 inode 포인터를 디렉토리 inode에서 보관
    • inode는 파일의 데이터가 위치한 블럭과 파일에 대한 접근 권한 및 파일 수정 시간, 파일 종류 등의 정보를 가지며, EXT2의 모든 파일은 각각 하나의 inode와 대응 (즉, inode는 구분 가능한 고유 번호를 가지고 식별됨)
    • Mode 접근 권한 정보 및 파일의 유형(일반 파일, 디렉토리, 심볼릭 링크, 블럭 장치, 문자 장치, FIFO)
    • Owner Information 해당 파일의 소유자 및 그룹 식별자
    • Size 바이트 단위의 파일 크기
    • Timestamp inode가 만들어진 시각과 최종 수정 시각
    • Datablocks 데이터가 저장된 블럭에 대한 포인터, 맨 앞 12개의 포인터는 데이터를 저장한 실제 블럭에 대한 포인터이고 마지막 3개는 점점 더 높은 수준의 간접 연결(블럭의 포인터의 포인터)을 포함.
    • 특수 장치 파일은 실제 디스크에 존재하지 않는 파일이지만 inode로 표현되며, 커널 내부에는 장치를 접근하기 위한 코드가 존재

  • EXT2 Superblock
    • 수퍼블럭은 파일 시스템의 기본 크기나 모양에 대한 설명을 기술하는 자료구조로, 이 정보를 이용하여 관리자가 파일 시스템을 활용하고 유지
    • 보통 파일 시스템이 마운트 될 때, 블럭 그룹 0에 있는 수퍼블럭을 읽어들이지만, 모든 블럭 그룹에는 동일한 복사본이 존재해 파일 시스템이 깨지는 경우를 대비함
    • Magic Number 마운트 프로그램에 EXT2의 수퍼블럭이라는 것을 알리는 용도 (=0xEF53)
    • Revision Level 메이저 개정 레벨과 마이너 개정 레벨로 구성되며, 마운트 프로그램이 어떤 특정 버전에서만 지원되는 기능이 현재 파일 시스템에서 지원되는지 확인하는 용도. 또한 마운트 프로그램이 파일 시스템에서 안전하게 사용가능한 기능 목록(기능 호환성 항목)을 판단할 때 사용
    • Mount Count & Maximum Mount Count 파일 시스템 전부를 검사할 필요가 있는지 확인할 때 사용. 마운트 횟수는 파일 시스템이 마운트될 때마다 1씩 증가하며, 그 값이 최대 마운트 횟수에 도달하면 e2fsck를 실행하라는 메시지가 출력됨
    • Block Group Number 현재 수퍼블럭 복제본을 가지는 블럭 그룹의 번호
    • Block Size 바이트 단위의 블럭 크기
    • Blocks per Group 그룹당 블럭 수 (파일 시스템 만들 때 지정됨)
    • Free Blocks 파일 시스템 내 프리 블럭 수 = 사용 가능한 블럭 수
    • Free Inode 파일 시스템 내 프리 inode 수
    • First Inode 첫번째 inode 번호, 루트 파일 시스템에 루트 디렉토리를 나타냄

  • EXT2 Group Descriptor
    • 각 블럭 그룹은 자신을 기술하는 자료구조를 가지며, 수퍼블럭처럼 모든 블럭 그룹을 위한 그룹 기술자는 각 블럭 그룹에 복제되어 있어 파일 시스템이 깨지는 경우를 대비 (실제로 블럭 그룹 0에 있는 첫번째 복사본만 사용됨)
    • Block Bitmap 블럭 그룹에서 블럭의 할당 상태를 나타내는 비트맵 (블럭 수만큼 존재, 블럭 할당 및 해제 시 사용)
    • Inode Bitmap 블럭 그룹에서 inode의 할당 상태를 나타내는 비트맵 (블럭 수만큼 존재, inode 할당 및 해제 시 사용)
    • Inode Table 블럭 그룹의 inode 테이블의 시작 블럭 (블럭 수만큼 존재, 각 inode는 EXT2 inode 구조체에 의해 표현)
    • Free Blocks Count & Free Inode Count 
    • Used Directory Count 
    • 그룹 기술자는 전체적으로 하나의 그룹 기술자 테이블을 형성하며, 각 블럭 그룹에는 수퍼블럭 뒤에 그룹 기술자 테이블이 존재
  • EXT2 Directory
    • 디렉토리는 파일에 대한 접근 경로를 생성하고 저장하는 특별한 파일
    • 메모리 상에서 디렉토리 파일은 디렉토리 엔트리의 리스트
    • 디렉토리 엔트리는 디렉토리에 저장된 파일과 1:1 대응이며, 모든 디렉토리에서 첫 2개의 엔트리는 각각 .과 ..으로 현재 디렉토리와 부모 디렉토리를 의미
    • Inode 엔트리에 해당하는 Inode로, 이 값은 블럭 그룹의 inode 테이블에 저장되어 있는 inode 배열에 대한 인덱스
    • name length 바이트 단위의 파일 이름 길이
    • name 파일 이름

디렉토리 엔트리

EXT2 파일 연산

  • 파일 탐색
    • 파일 이름은 길이 제한이 없고 인쇄 가능한 문자면 가능. 단, 전체 경로에서 파일을 구분하기 위해 슬래시(/)를 사용
    • 파일을 나타내는 inode를 찾기 위해 시스템은 파일 이름을 해석해서 한 디렉토리씩 처리하면서 마지막 슬래시 뒤에 있는 이름을 가진 파일을 얻음
    • 루트 디렉토리의 inode 번호는 파일 시스템의 수퍼블럭에서 얻으며, 이 번호의 파일을 읽기 위해 해당 블럭 그룹의 inode 테이블을 이 번호로 인덱싱하여 엔트리를 얻음
    • 정리하자면, 전체 경로에서 파일을 찾을 때까지 디렉토리 엔트리를 반복적으로 찾아가면서 마지막 슬래시 뒤에 이름을 갖는 파일의 데이터 블럭을 얻음
  • 파일 크기 변경
    • 파일 시스템은 데이터를 가지고 있는 블럭들이 분할되는 경향(디스크 조각화)이 있는데, EXT2의 경우 어떤 파일에 대한 새로운 블럭을 현재 데이터 블럭들에 인접하도록 할당하거나 적어도 현재 블럭 그룹과 같은 그룹에 할당하는 것으로 이 문제를 극복
      • 둘 다 실패하면, 다른 블럭 그룹에 있는 데이터 블럭을 할당 (어쩔 수 없음)
    • 프로세스가 파일에 데이터를 쓰려고 할 때마다, 파일 시스템은 데이터가 파일에 마지막으로 할당된 블럭을 넘어가는지 검사하고, 넘어가면, 이 파일을 위한 새로운 데이터 블럭을 할당 (프로세스는 할당하고 기록이 끝날 때까지 대기)
      • 블럭 할당 루틴은 파일 시스템의 수퍼블럭에 락을 걸어 수퍼블럭에 있는 항목을 변경. 이는 둘 이상의 프로세스가 파일 시스템을 동시에 변경하는 것을 막기 위한 것으로, 다른 프로세스가 블럭을 할당하려고 하면 현재 프로세스는 대기
      • 수퍼블럭의 사용은 요청 순서에 따르며, 한 프로세스가 제어를 갖게 되면 작업을 종료할 때까지 제어를 가짐(원자적)
      • 수퍼블럭에 락을 걸고 나면, 프리 블럭(사용가능한 블럭)이 충분한지 검사. 만약 충분하지 않다면 할당 루틴은 실패하고 프로세스는 수퍼블럭에 대한 제어권을 양도
      • 만약 프리 블럭이 충분하다면, 프로세스는 새 블럭을 할당 받고 수퍼블럭에 대한 제어권을 양도
      • 미리 할당된 블럭을 사용할 수 있는데, 실제 존재하지는 않으며 단지 할당된 블럭 비트맵에 예약되어 있음
    • inode에는 새로운 데이터 블럭을 할당하기 위한 항목 2개가 존재
      • prealloc_block 처음에 미리 할당된 데이터 블럭 수
      • prealloc_count 그 중에서 남은 개수
    • 할당할 때는 마지막 데이터 블럭의 다음 데이터 블럭이 비었는지 검사해서 비었으면 할당하고 안비었으면 검색 범위를 넓혀 가까운 블럭에서 64블럭 이내의 데이터 블럭을 살펴봄 (순차 접근을 빠르게 하기 위함)
      • 대부분 그 파일에 속한 다른 데이터 블럭과 같은 블럭 그룹
      • 빈 블럭이 없다면 계속 탐색하는데, 한 블럭 그룹 안에 8개의 빈 데이터 블럭으로 된 덩어리를 찾으려고 함
      • 만약 블럭 미리 할당 기능이 필요하고 사용 가능할 경우, prealloc_block 과 prealloc_count 값을 각각 갱신
    • 빈 블럭을 찾을 때마다 블럭 그룹의 블럭 비트맵을 갱신하고 버퍼 캐시 내에 데이터 버퍼를 할당
      • 데이터 버퍼는 파일 시스템을 지원하는 장치 식별자와 할당된 블럭의 번호에 의해 식별됨
      • 버퍼 내의 데이터가 모두 0이고 버퍼가 더티(dirty)라고 표시되어 있으면 실제 디스크에 내용이 기록되지 않은 것
    • 수퍼블럭을 기다리는 프로세스가 있으면 큐에 있는 프로세스 중 첫번째 프로세스가 다시 실행되며 파일 처리에 필요한 수퍼블럭을 독점

가상 파일 시스템(Virtual File System, VFS)

VFS 구조

  • 전체 파일 시스템과 특정 마운트된 파일 시스템을 기술하는 자료구조를 관리하기 위한 인터페이스로, EXT2와 비슷한 방법으로 시스템에 있는 파일을 수퍼블럭과 inode로 표현 (VFS inode는 EXT2 inode처럼 시스템에 있는 파일과 디렉토리를 나타냄)
  • 각 파일 시스템들은 부팅 중 초기화될 때, 자신을 VFS에 등록하여 커널에 파일 시스템이 포함될 수 있으며, 특정 파일 시스템을 마운트하는 경우 모듈에 의해 VFS에 등록됨
  • 블럭 장치에 기반한 파일 시스템이 마운트되었고, 루트 파일 시스템이 존재한다면, VFS는 이것의 수퍼블럭을 읽어와 해당 파일 시스템의 배치도를 VFS 수퍼블럭 자료구조에 매핑시킴
  • VFS는 마운트된 파일 시스템과 VFS 수퍼블럭의 리스트를 관리하며, 각각의 VFS 수퍼블럭은 특정 기능을 수행하는 루틴에 대한 정보와 포인터 및 파일 시스템의 첫번째 VFS inode에 대한 포인터를 포함 (루트 파일 시스템의 경우 이 inode는 루트 디렉토리의 것)
    • 예를 들어, 마운트된 파일 시스템을 나타내는 수퍼블럭은 고유의 inode 읽기 루틴에 대한 포인터를 가지며, 호출 시 적절한 파일 시스템의 블럭을 읽을 수 있게 됨
  • 프로세스가 파일이나 디렉토리에 접근할 때, VFS inode를 탐색하는 시스템 루틴이 호출되며, 수많은 inode가 반복적으로 접근
    • 접근 속도를 빠르게 하기 위해 inode는 캐시에 저장됨
    • 어떤 inode가 캐시에 존재하지 않으면, 해당 inode를 읽기 위해(디스크->메모리) 각 파일 시스템 고유의 루틴을 호출
    • 읽어들인 inode는 캐시에 저장되어 다음번 접근 시 캐시에서 탐색됨
    • 덜 사용되는 VFS inode는 캐시로부터 제거 (LRU Cache)
  • 모든 파일 시스템은 실제 장치에 대한 접근 속도를 높이기 위해 버퍼 캐시(Buffer Cache)를 사용
    • 같은 데이터가 자주 필요할 때를 대비하여 디스크 접근 횟수를 줄임
    • 버퍼 캐시는 파일 시스템과는 상호 독립적(비동기 요청 인터페이스를 제공)이며, 커널이 데이터 버퍼를 할당하고 읽고 쓰는 메커니즘에 통합되어 있음
  • inode를 읽기 위해, 커널은 블럭 디바이스 드라이버에게 제어하는 장치로부터 블럭을 읽도록 요청하여 디스크에서 데이터를 가져옴
    • 이러한 블럭 장치 인터페이스는 버퍼 캐시에 통합되어 있음 (버퍼 캐시에서 데이터가 없는 경우 즉시 호출)
  • 파일 시스템이 어떤 블럭을 읽으면 그 블럭은 버퍼 캐시에 저장되어 모든 파일 시스템과 커널에 의하여 공유되며, 버퍼 캐시에 있는 각 데이터 버퍼는 블럭 번호와 그 블럭을 읽은 장치의 고유 식별자에 의하여 구분됨
  • 자주 사용되는 디렉토리의 inode를 빨리 찾기 위해 디렉토리 캐시도 제공, 이 캐시는 전체 디렉토리 이름과 해당되는 inode 번호와의 매핑을 저장하며 디렉토리 자체에 대한 inode는 inode 캐시에 저장됨

http://www.haifux.org/lectures/119/linux-2.4-vfs/linux-2.4-vfs.html

  • VFS Super Block
    • device 파일 시스템이 저장되어 있는 블럭 장치의 식별자
    • mounted inode 파일 시스템의 첫번째 inode를 참조하는 포인터
    • covered inode 파일 시스템이 마운트된 디렉토리를 표현하는 inode를 참조하는 포인터
      • 루트 파일 시스템의 VFS 수퍼블럭은 covered inode 가 없음
    • block size 바이트 단위의 블럭 크기
    • superblock operations 마운트된 파일 시스템의 file_system_type 구조체를 참조하는 포인터
    • file system specific 파일 시스템이 필요로 하는 정보를 참조하는 포인터
  • VFS inode
    • 각 VFS inode의 정보는 파일 시스템의 정보로부터 파일 시스템 고유 루틴에 의해 생성되며, VFS inode는 커널 메모리에만 존재하고 시스템이 필요한 동안 VFS inode 캐시에 저장됨
    • device 해당 VFS inode가 나타내는 파일 또는 장치의 식별자
    • inode 파일 시스템 안에서 유일한 inode 번호, 장치와 inode 번호의 조합은 VFS 내에서 유일
    • mode VFS inode가 해당하는 파일의 종류(파일, 디렉토리, 기타 등)와 적븐 권한 등을 나타냄
    • user id 파일의 소유자
    • times 생성 시각, 변경 시각, 읽은 시각 등을 나타냄
    • block size 바이트 단위의 블럭 크기
    • inode operations 연산 루틴의 주소들에 대한 포인터 (만약 0이면 inode가 프리되어 제거되거나 재사용됨)
    • count VFS inode를 현재 사용하는 시스템 요소(프로세스 등)의 수 (만약 0이면 inode가 프리되어 제거되거나 재사용됨)
    • lock VFS inode를 락 걸기 위해 사용 (inode를 읽을 때)
    • dirty VFS inode가 기록되어 파일 시스템 아래 계층에서 변경이 필요한지 나타냄
    • file system specific information

 

VFS 자료구조
https://myaut.github.io/dtrace-stap-book/kernel/fs.html
https://www.programmersought.com/article/50821832993/

VFS 연산

  • 파일 시스템 등록
    • 커널을 빌드할 때 지원할 파일 시스템을 지정할 수 있음.
    • 파일 시스템 시작 코드는 내장된 모든 파일 시스템의 초기화 루틴을 호출
    • 파일 시스템 모듈은 로드될 때마다 자신을 커널에 등록하고, 언로드될 때 자신을 커널로부터 해제함
    • 각 파일 시스템의 초기화 루틴은 자신을 VFS에 등록하며, file_system_type 구조체로 표현
    • 이 구조체에는 파일 시스템의 이름과 VFS 수퍼블럭 읽기 루틴에 대한 포인터가 저장되어 있음
    • *read_super() 파일 시스템이 마운트될 때 VFS에 의해 호출
    • name e.g., ext2
    • required_dev 이 파일 시스템을 실제로 지원하는 장치가 필요한지를 나타냄
    • 어떤 파일 시스템이 등록되어 있는지는 /proc/filesystems 파일 내용을 출력해서 알 수 있음

  • 파일 시스템 마운트
    1. 수퍼유저가 파일 시스템을 마운트하려고 할 때, 커널은 시스테 콜로 전달된 인자가 옳은지 파악
      • e.g., mount -t iso9660 -o ro /dev/cdrom /mnt/cdrom
      • 커널에 3가지 정보를 전달: 파일 시스템 이름, 파일 시스템을 포함하고 있는 블럭 장치, 마운트할 위치
    2. VFS는 먼저 파일 시스템 리스트(file_systems)를 탐색하여 각 file_system_type 구조체를 살펴보고, 인자로 넘어온 이름의 구조체를 발견하면 커널에서 지원하는 타입인지 검사
      • 일치하는 것을 찾지 못하더라도, 커널이 그 파일 시스템의 모듈을 요구시 로드하도록 빌드되었다면 커널 데몬이 모듈을 로드
    3. 새로운 파일 시스템의 마운트 지점이 될 디렉토리의 VFS inode를 탐색
      • 이 VFS inode는 캐시에 있거나, 마운트 지점의 파일 시스템을 저장하고 있는 블럭 장치에서 읽어옴
    4. inode를 찾으면 디렉토리인지 혹은 이미 다른 파일 시스템이 마운트되었는지 검사
      • 한 디렉토리는 단 하나의 파일 시스템의 마운트 지점으로만 사용 가능
    5. 새로운 VFS 수퍼블럭을 할당하고, 이를 마운트 정보와 함께 해당 파일 시스템을 위한 수퍼블럭 읽기 루틴에 전달
      • 모든 VFS 수퍼블럭은 super_block 구조체의 배열인 super_blocks에 저장됨
      • 이번 마운트를 위해 super_block 구조체는 하나만 할당됨
    6. 수퍼블럭 읽기 루틴은 물리 장치에서 읽은 정보에 따라 VFS 수퍼블럭을 채워넣음
      • 만약 그 파일 시스템의 블럭 장치로부터 읽지 못한다면(블럭 장치가 해당 파일 시스템의 타입으로 사용 불가한 경우) 마운트 명령은 실패
    7. 마운트된 각 파일 시스템은 vfsmount 구조체로 기술되며, 이들은 vfsmntlist 가 참조하는 리스트에 추가됨
      • vfsmnttail 포인터는 리스트의 마지막 항목을 가리키고, mru_vfsmnt 포인터는 가장 최근에 사용된 파일 시스템을 참조
      • 각 vfsmount 구조는 파일 시스템을 담고 있는 블럭 장치의 장치 번호, 이 파일 시스템이 마운트된 디렉토리, 마운트될 때 할당된 VFS 수퍼블럭에 대한 포인터 등을 포함

마운트 커널 자료구조

  • 파일 시스템 마운트 해제
    • 어떤 프로세스도 마운트된 디렉토리나 그 하위 디렉토리를 사용하지 않는 상태에서만 마운트 해제 가능
    • 사용중이면 VFS inode 캐시에 해당 파일 시스템이 속하는 VFS inode가 존재하기 때문에, 캐시의 inode 리스트를 검사
    • 마운트된 파일 시스템의 VFS 수퍼블럭이 더티(dirty) 상태이면, 수퍼블럭을 디스크에 기록
    • 디스크 기록 후, VFS 수퍼블럭이 차지하고 있던 메모리를 커널 메모리 풀(memory pool)에 보내고, 마지막으로 이 파일 시스템의 마운트에 필요했던 vfsmount 구조체를 vfsmntlist 리스트에서 제거한 뒤 메모리 해제

VFS 캐시

  • VFS inode 캐시
    • VFS는 마운트된 파일 시스템에 대한 접근 속도를 높이기 위해 해시 테이블로 구현된 inode 캐시를 유지
    • inode를 접근할 때마다 VFS inode 캐시를 먼저 탐색
    • 해시 테이블 내의 각 엔트리는 같은 해시 값을 갖는 VFS inode 리스트를 참조
    • 해시 값은 inode 번호와 그 파일 시스템을 갖는 장치의 장치 식별자로 계산됨
    • VFS inode 를 얻으려면 찾으려는 것과 같은 inode 번호와 장치 식별자를 가진 inode를 찾을 때까지 리스트를 순회
    • 만약 inode가 있으면, 카운트 값을 증가시키면서 그 inode를 사용하는 프로세스가 있음을 알리고 파일 시스템에 접근
    • 만약 찾을 수 없다면, 파일 시스템이 메모리로 파일을 가져오기 위해 빈 VFS inode를 탐색
    • VFS inode를 더 할당할 수 있으면, 커널 페이지를 할당하고 이를 여러 새 빈 inode로 쪼갠 뒤, 할당된 VFS inode는 first_inode가 가리키는 리스트와 캐시의 리스트에 추가됨
    • VFS inode를 더 할당할 수 없으면, 재사용할만한 inode 후보들을 탐색 (사용횟수가 0인 inode는 좋은 후보)
      • 만약 VFS inode가 더티 상태이면 파일 시스템에 그 내용을 기록
      • 만약 락이 걸려 있으면 풀릴 때까지 대기
      • 재사용 후보가 선택되면 내용을 지움
    • 할당된 VFS inode는 파일 시스템에서 읽어온 정보를 채우는 루틴이 호출되며, 카운트 값은 1이 되고 락이 걸림
      • 그 inode가 완전한 정보를 가지기 전까지 아무도 접근 할 수 없음
    • VFS inode 캐시가 사용되어 꽉 차면, 덜 사용되는 inode는 버려지고 더 많이 사용되는 것들만 남음 (LRU Cache)
  • 디렉토리 캐시
    • VFS는 디렉토리에 대한 접근 속도를 높이기 위해 해시 테이블로 구현된 디렉토리 엔트리에 대한 캐시를 유지
    • 디렉토리는 실제 파일 시스템에 의하여 참조되므로, 그 내용도 디렉토리 캐시에 저장됨
    • 짧은 이름(최대 15자)을 가진 디렉토리 엔트리만 캐시되며, 이는 짧은 것이 더 자주 사용되기 때문
    • 캐시의 테이블 엔트리는 같은 해시 값을 가진 디렉토리 캐시 엔트리들의 리스트를 참조
    • 해시 함수는 파일 시스템을 갖고 있는 장치의 장치 번호 및 디렉토리 이름을 사용하여 해시 테이블 내의 인덱스를 산출
    • 캐시 값을 유효하게 하고 최신 값을 유지하는 LRU 방식으로 디렉토리 캐시 엔트리 리스를 관리
      • 디렉토리 엔트리는 처음에 참조되면 1단계 LRU 리스트의 맨 뒤에 추가
      • 1단계 리스트가 꽉 차서 자리가 없으면, 이 리스트의 맨 앞 엔트리를 제거 (가장 적게 사용된 것)
      • 디렉토리 엔트리가 다시 참조되면 2단계 LRU 리스트로 엔트리를 옮김
      • 2단계 리스트가 꽉 차서 자리가 없으면, 이 리스트의 맨 앞 엔트리를 제거

https://www.researchgate.net/figure/The-Linux-original-path-lookup-mechanism-The-block-means-a-dentry-structure-and-p_fig1_325982529
https://scslab-intern.gitbooks.io/linux-kernel-hacking/content/chapter13.html

  • 버퍼 캐시
    • 디스크에서 데이터 블럭을 읽고 쓸 때 디스크 접근 횟수를 줄이기 위해 해시 테이블로 구현된 블럭 버퍼에 대한 캐시
    • 파일 시스템이 데이터 블럭을 읽고 쓰는 요청을 보내면, 표준 커널 함수를 호출하여 블럭 디바이스 드라이버에게 buffer_head 구조체를 전달. 이 구조체는 블럭 디바이스 드라이버가 필요로 하는 모든 정보를 제공
      • 장치 식별자는 장치를 유일하게 구별해주고, 블록 번호는 읽어야 할 위치를 알려줌
    • 모든 블럭 버퍼들은 새 것이거나 안 쓰이거나 상관없이 버퍼 캐시 어딘가에 존재하며, 모든 블럭 장치들이 이 캐시를 공유
    • 지원하는 버퍼 크기별로 각각 하나의 리스트가 존재하며, 시스템의 프리 블럭 버퍼는 처음 만들어질 때나 블럭이 해제될 때 이 리스트에 추가됨 (현재 지원하는 버퍼 크기는 512, 1024, 2048, 4096, 8192바이트)
    • 버퍼 캐시는 똑같은 해시 값을 가지는 버퍼들의 리스트를 참조하는 포인터들의 배열로, 해시 값은 블럭 버퍼를 소유하는 장치 식별자와 버퍼의 블럭 번호로 산출되며, 각 버퍼 유형마다 LRU 리스트가 있고 캐시에 추가되면 이 리스트에도 추가됨
      • LRU 리스트는 특정 유형의 버퍼에 대해 작업할 때 사용되며, 새로운 데이터를 가진 버퍼를 디스크에 기록
    • 버퍼의 유형은 버퍼의 상태를 반영한 것
      • clean 사용하지 않은 새 버퍼
      • locked 버퍼에 락이 걸려 있으며, 기록되기를 기다리는 버퍼
      • dirty 새롭고 유효한 데이터를 가지며, 언제 기록될지 스케줄되지 않은 버퍼
      • shared 공유 버퍼
      • unshared 이전에 공유되었으나 지금은 공유하지 않는 버퍼
    • 블럭 버퍼는 프리 리스트 중 어떤 하나의 리스트나, 버퍼 캐시 둘 중 하나에만 속함
    • 파일 시스템이 물리 장치로부터 버퍼를 읽을 때마다 가장 먼저 버퍼 캐시에 접근해서 블럭을 얻으려고 시도
      • 만약 버퍼 캐시에서 얻을 수 없다면, 프리 리스트에서 적당한 크기의 버퍼를 하나 얻고, 이는 버퍼 캐시에 추가됨
      • 만약 버퍼 캐시에서 얻을 수 있다면, 최근 것이 아니고 새로운 버퍼일 경우 파일 시스템은 디바이스 드라이버에게 해당하는 데이터 블럭을 디스크에 요청해서 읽어옴
    • bdflush 커널 데몬  버퍼 캐시를 사용하는 블럭 장치들 사이에 공평하게 캐시 엔트리를 할당하는 데몬
      • 시스템에 있는 더티 버퍼의 개수가 충분히 많아지기를 기다리며 잠들어 있다가 버퍼가 할당되거나 버려질 때 시스템에 있는 더티 버퍼의 개수를 검사
      • 전체 버퍼의 개수 중 더티 버퍼의 비율이 너무 커지면 bdflush 가 깨어나 BUF_DIRTY_LRU 리스트에 데이터 버퍼를 연결 (비율은 기본값이 60%이지만, 시스템에서 버퍼가 필요하다면 이 데몬은 언제든 깨어남)
      • bdflush는 이중에서 적당한 개수(디폴트 500)를 디스크에 기록
    • update 명령어 실행될 때마다 시스템에 있는 모든 더티 버퍼에서 시간이 만료된 것들만 디스크에 기록. 더티 버퍼가 다 쓰여지면 시스템 시간을 표시

버퍼 캐시 커널 자료구조

/proc 파일 시스템

  • 실제로 존재하는 것이 아니며, 내부 파일과 디렉토리를 커널에 있는 정보를 가지고 생성
  • 실제 파일 시스템과 마찬가지로 자신을 VFS에 등록하고 파일이나 디렉토리를 열면 VFS가 inode를 요청
  • 커널의 /proc/devices 파일은 장치들을 나타내는 커널 자료구조로부터 생성됨
  • /proc 파일 시스템은 사용자에게 커널 내부 작업을 볼 수 있는 뷰(view)를 제공
  • 커널 모듈같은 몇몇 리눅스 서브시스템들은 /proc 파일 시스템에 엔트리를 생성함

장치 특수 파일(Device Special File)

  • 리눅스는 하드웨어 장치들을 특수 파일로 저장하며, /dev/null의 경우 널 장치라 해서 윈도우즈의 휴지통과 같은 역할을 함
  • 장치 파일은 파일 시스템에서 어떤 데이터 영역도 차지하지 않으며, EXT2와 VFS는 모두 장치 파일을 inode의 특수한 유형으로 구현
  • 장치 파일에는 2가지 형태가 존재: 문자 특수 파일, 블럭 특수 파일
  • 문자 장치는 문자 모드로 I/O 작업을 할 수 있고, 블럭 장치는 모든 I/O가 버퍼 캐시를 통하도록 되어 있음
  • 장치 파일로 I/O를 요구하면, 시스템 내에 있는 해당 디바이스 드라이버가 요청을 받음
    • 디바이스 드라이버는 실제하지 않고 SCSI 디바이스 드라이버 같은 어떤 서브시스템을 위한 유사 디바이스 드라이버일 수 있음
  • 커널에서 모든 장치는 각각 kdev_t 구조체로 기술되고, 이는 2바이트 길이로 첫번째 바이트는 마이너 장치 번호, 두번째 바이트는 메이저 장치 번호를 의미. (장치의 유형을 구별하는 메이저 번호와, 장치 유형의 하나의 케이스를 구별하는 마이너 번호로 장치 파일이 참조됨)

 

+ Recent posts