디스크에 파일을 저장할 때는 보통 동일한 크기의 섹터로 나누어 저장한다. 이런 섹터들을 논리적인 블록
이라고 본다.
이를 할당해서 저장하는 방법이 세가지가 있다.
하나의 파일이 디스크 상에 연속해서 저장되는 방법이다. 예를 들어 블록 두개로 구성되는 파일은 0, 1 이렇게 연속해서 들어가게 된다.
(위 그림에서 list 란 파일은 28번부터 31번까지 저장된 길이가 4인 파일이다)
swap area
로 디스크를 쓰는 경우에 좋다. file system은 영구적인 공간이지만 그러나 swap area는 임시로 저장해놓고 프로세스가 끝나면 없애버린다. 따라서 swapping 용도는 공간 효율성보다는 속도 효율성이 더 중요하다. (어차피 금방 지워질 것이기 때문이다.)random access
)이 가능하다. 파일의 특정 위치의 블록을 보고 싶은 경우 start에 보고 싶은 위치만큼 더해주면 바로 그 부분을 볼 수 있다.external fragmentation
이 생길 수 있다.
비연속적으로 저장되며 빈 위치 어디든지 들어갈 수 있다. 블록이 다음 블록의 위치를 알려주고, 마지막 블록인 경우 -1을 저장한다. start와 end 위치만 디렉토리에 따로 기록해둔다.
예를 들어 jeep 파일의 첫번째 블록이 9번에 있다. 9번에 데이터 블록과 두번째 블록 위치가 함께 들어 있다.
bad sector
가 발생하면 포인터가 유실되어 뒤에 이어지는 많은 부분을 잃는다.한 sector : 512bytes/sector
한 포인터 : 4bytes/pointer
⇒ 내용을 저장하면 한 섹터에 저장될 데이터가 포인터를 저장하면 두 섹터에 저장되는 등의 일이 발생할 수 있다.
FAT(File-allocation table)
Linked allocation을 변형해 효율적인 할당방법
직접 접근을 가능하게 하기 위해 디렉토리에 index block 위치를 표시하고 그 블록에는 데이터가 저장된 위치정보들을 열거해둔다.
linked scheme
multi-level index
Partition에 파일 시스템을 설치해둔다. 저장되는 구조가 4가지가 있다.
어떤 파일 시스템이든 가장 앞에 boot block
이 나오는 것이 약속이다. 컴퓨터 전원을 켜면 부팅을 하게 되는데 어떤 파일 시스템을 쓰든 0번 블록을 올리면 이 파일시스템에서 운영체제 위치를 찾아 메모리에 올려 정상적인 부팅이 가능하게 한다.
부팅에 필요한 정보를 가지고 있다. (bootstrap loader)
파일 시스템에 관한 총체적인 정보를 담고 있다.
어디가 빈 블록이고, 어디가 사용중인 블록인지를 관리한다.
어디가에 Inode list 가 있고 어디부터 실제 data block이 있는지 관리한다.
실제 파일 시스템에서는 전체 메타데이터가 디렉토리에 저장되어 있지는 않다. 특히 유닉스의 파일 시스템의 경우 디렉토리는 극히 일부분만 가지고 있고 실제 파일의 메타데이터는 별도의 위치에 빼서 저장해두는데, 그 위치가 Inode list
이다. (Inode = Index Node)
파일 하나 당 Inode 하나가 할당된다. Inode는 그 파일의 메타데이터를 가지고 있는다.
단, 파일의 이름은 디렉토리에 저장되어 있다. 나머지 메타데이터들은 inode에 저장되어 있기 때문에 디렉토리는 inode의 번호를 가지고 있는다.
그럼 파일의 위치는 어떻게 저장하냐?
기본적으로 Indexed Allocation
을 사용한다. inode의 크기는 고정되어 있다. 그래서 위치 정보를 나타내는 포인터 개수가 유한하지만, 큰 파일까지 나타낼 수 있어야 한다.
direct blocks
: 작은 파일은 이것만으로 다 나타낼 수 있다.single indirect
: single indirect를 따라가면 인덱스 블록이 있다. 그 인덱스 블록에는 포인터가 여러 개 들어갈 수 있다.double indirect
: 더 큰 파일을 표현하는 경우. 이단계 인덱스 구조triple indirect
: 더 큰 파일을 표현하는 경우. 삼단계 인덱스 구조⇒ 대부분의 파일은 크기가 매우 작기 때문에 direct block만으로 파일의 위치를 바로바로 알 수 있다. 가끔매우 큰 파일인 경우 추가적으로 접근해서 파일의 위치를 찾는다.
마이크로소프트에서 MS DOS를 만들었을 때 처음 만든 file system이다.
(아직도 모바일 기기나 일부 윈도우에서 사용하는 경우도 있다)
마찬가지로 부트 관련 정보
파일의 메타데이터의 일부를 담고 있다. 위치 정보만 FAT
에 저장한다.
나머지 메타데이터는 디렉토리에 저장한다. (file 이름, 소유주, 첫 시작 블록 위치 등)
linked allocation에서 발생하는 문제(한 섹터가 깨지면 뒤에 이어지는 섹터도 사용 못함, 블록의 공간 낭비, 직접접근 불가)를 해결할 수 있다. 한 블록의 다음 블록이 무엇인지를 별도의 배열인 FAT이 저장하고 있는 것이다.
실제로는 이 두개 외에도 많은 파일 시스템이 사용되고 있고 더 많이 개선되어 있다.
비어있는 블록들은 어떻게 관리할지에 대한 설명.
각각의 블록별로 번호가 있다. 비트맵의 크기는 블록의 개수만큼이다.
비트맵은 약간의 부가적인 공간을 필요로한다.
이 방법의 장점은 연속적인 N개의 free block을 찾는 데 효과적이라는 것이다. contiguous allocation을 사용하지 않더라도 디스크 헤더의 이동량을 줄이기 위해 가능하다면 연속적으로 저장하면 좋다.
모든 free 블록들을 링크로 연결해둔다. 비어 있는 블록의 첫번째 위치만 포인터로 가지고 있고, 그 다음의 빈 블록의 위치는 비어있는 블록들이 가지고 있어 연결리스트로 연결되어 있다.
연속적인 가용 공간을 찾는 것이 쉽지 않아 잘 사용되지는 않지만 공간의 낭비가 없다는 장점이 있다.
인덱스 형식으로 그룹핑한 것.
비어있는 블록을 한번에 찾기에는 linked list보단 낫지만 연속적인 빈 블록을 찾기에는 효과적이진 않다.
연속적인 빈 블록들을 찾기 위해 (빈블록의 첫번째 위치, 거기서부터 몇개가 비어있는지)
를 쌍으로 가지고 있는다.
디렉토리를 관리하는 방법.
디렉토리 파일의 내용을 어떻게 저장할까?
단순하게 파일의 이름과 파일의 메타데이터의 리스트를 순차적으로 저장해둔다.
구현이 간단하지만 디렉토리 내에 파일이 있는지 찾기 위해서는 Linear search가 필요하기 때문에 time-consuming(비효율적)
파일의 이름을 그냥 저장하지 않고 해시 함수를 적용해서 저장한다.
해시함수를 적용하면 특정 범위 안으로 값이 바뀐다. 해시함수 결과값에 해당하는 엔트리에 그 파일의 메타데이터를 저장한다.
파일을 찾을 때 파일 이름에 해시함수를 적용한 엔트리만 찾으면 되기 때문에 효율적으로 search 할 수 있다.
해시함수의 특성상 collision이 발생할 수 있다는 가능성이 있다.
<file name, file의 메타데이터>
의 list에서 각 entry는 일반적으로 고정된 크기이다. file name이 고정 크기의 entry 길이보다 길어지는 경우에는 entry의 마지막 부분에 이름의 뒷부분이 위치한 곳의 포인터를 두는 방법이 있다.
사용자가 파일 시스템에 접근하기 위해선 시스템 콜을 해야 한다. 파일 시스템 종류별로 서로 다른 시스템 콜 인터페이스를 써야 한다면 사용자가 혼란스러울 것이다. 따라서 이를 방지하기 위해 개별 파일 시스템 윗 계층에 VFS
를 둔다.
다양한 파일 시스템에 대해 사용자 입장에서는 동일한 시스템 콜 인터페이스(API)를 통해 접근할 수 있게 해주는 운영체제의 layer
파일 시스템이 로컬 스토리지에 저장될 수도 있지만 원격에 저장되는 경우도 있다. 그럴 경우 NFS
를 사용해서 접근해야 한다.
위 그림에서 컴퓨터가 client 컴퓨터와 server 컴퓨터, 이렇게 두대가 있으며 두 컴퓨터가 네트워크로 연결되어 있다.
클라이언트 컴퓨터는 VFS 인터페이스를 써서 로컬 파일 시스템처럼 접근 요청을 하고 NFSclient와 NFS server를 통해 마치 로컬에서 일어난 요청처럼 처리해서 원격에 저장된 파일에 접근할 수 있다.
server와 client 쪽 모두에게 NFS 모듈이 있어야 한다.
페이징 시스템에서 사용하는 page frame
을 caching 관점에서 말하는 용어이다.
swap area backing store 보다 빠르다.
운영체제에게 주어지는 정보가 지극히 제한적이기 때문에 정확한 접근 시간 등을 알 수 없어 clock alg 를 사용 했다.
파일의 데이터를 사용자가 요청 했을 때 디스크에서 운영체제가 읽어온 내용을 자기의 영역 중 일부에 저장해두고 똑같은 파일을 다른 프로세스가 요청하게 되면 다시 읽어오지 않고 운영체제가 저장해둔 걸 바로 전달한다.
이미 파일 데이터가 메모리에 올라와있든 디스크에 있든 간에 파일에 접근하기 위해선 시스템콜을 해야하기 때문에 cpu 제어권이 운영체제에게 넘어와 파일의 요청이 언제 일어났는지 알 수 있다. 그 정보를 이용해서 Replacement alg로 LRU나 LFU를 사용할 수 있다.
최근에는 page cache와 buffer cache를 합쳐서 사용한다.
버퍼 캐시도 페이지 단위로 관리한다. 운영체제가 페이지 프레임(물리적 메모리) 관리하는 루틴에 페이지 캐시와 버퍼캐시를 같이 관리한다는 이야기.
합쳐졌다해서 관리 방법이 달라진다는 건 아니다.
파일 입출력 방법 중 Read나 write 시스템 콜을 이용하는, 버퍼 캐시를 이용하는 방법이 있고, memory-mapped i/o를 이용해서 접근하는 방법이 있다. 원래는 파일을 오픈한 다음에 read나 write 시스템콜을 사용했다. 그러나 이 방법에서는 file의 일부를 virtual memory 에 mapping 시켜 놓는다. 매핑을 해놓고 나면 그 다음부턴 read, write 시스템 콜을 하는 게 아니라 메모리에 읽고 쓰는 것이다. 그런데 실제로는 파일에 데이터를 읽고 쓰는 효과가 난다.