본문 바로가기

대학교 강의/게임 서버 프로그래밍 입문

게임 서버 프로그래밍 입문 - 리눅스 파일 시스템 (Linux File System)

들어가며

 음, 아직까지는 본격적으로 뭔가를 시작했다는
느낌은 들지 않는다.

 교수님도 4주차까지는 Linux 사용에 익숙해질
수 있도록 한다고 하셨고

 그래도 배운 것이 있다면 간단하게 파일 시스템을
알아보았다.

 그것을 정리하고자 한다.

 

리눅스 파일 구조

 리눅스 파일 구조를 설명할 때 빠질 수 없는 것이
i-node라는 것이다.

 Index Node의 줄임말로,

 파일의 데이터 블록이 디스크 상의 어느 주소에
위치하고 있는가에 대한 정보를 기록한다.

 따라서 i-node를 메타데이터의 한 종류로 볼 수 있다.

 

 파일 시스템 내에서 i-node는 고유하다.

 그러면 파일 시스템은 한 컴퓨터에 하나만
존재하는가?

 그렇지 않다.

 SSD, HDD가 있고 C, D드라이브가 있듯이
각각의 파티션이나 드라이브에서

 독자적으로 파일 시스템이 존재한다.

 따라서 i-node는 각각 다른 파일 시스템이라면
동일한 i-node 번호가 중복될 수 있다.

 이 둘이 다르다는 것을 알 수 있는 방법은,

 사용되는 파일 시스템이 다르기 때문에
다른 i-node로서 식별이 가능하기 때문이다.

 그러면 다시 위의 그림을 보자.

 

 i-node가 하는 역할은 데이터 블록이
어디에 있는지 가리키고 있다.

 파일을 구성하는 데이터들이 저장공간 내에
흩어져서 저장되어 있기 때문에 (∵단편화)

 이들의 위치와 크기를 저장해두는 메타데이터가
바로 i-node인 것이다.

 

 위 사진처럼 말이다.

 즉, 파일이 실제로 저장된 위치와
심지어 size가 얼마인지도 알려준다.

 그렇게 우린 i-node에 대해서 배웠고,
이것을 써먹어야 하기에

 이것을 i-node를 활용한 첫 번째
File API라고 하자.

 "inode번호를 줄 테니 buf에 가서 nbyte만큼
읽고/쓰고 오십쇼" 라는 느낌이라 볼 수 있다.

 근데 문제가 발생한다.

 첫 번째로, i-node를 기억하는 것이 쉽지 않다.

 두 번째로, i-node 넘버 자체에는 의미가 없다.

 그리고 기타 등등이 있다.

 seek 함수를 이용해 항상 i-node에 접근하여
offset 만큼 이동하는 것이 귀찮다.

 그래서 생긴 것이 바로

 

 얘네들이다.

 읽기/쓰기를 할 때 애초에 offset을 줘버리자는
취지에서 생겨난 함수들인데

 근데 위에서 말한 한계를 벗어나고 있지 않다.

 그래도 하나의 문제는 해결한 모습을 볼 수 있다.


 다음은 디렉토리를 볼 차례이다.

 

 디렉토리 또한 파일로 취급되기에 low-level name,
즉 i-node를 가지고 있다.

 이때 디렉토리와 파일이 가지는 i-node 자체의
내용은 조금 다를 수도 있다.

 뭐 그건 그렇다 치고, 다음 내용을 보자.

 

 왼쪽은 디렉토리의 계층구조다.

 오른쪽은 그 계층구조를 path의 형태로
나타낸 것이다.

 path 또한 고유한 형태로 나타나기에,
i-node와 같이 고유하다.

 즉, 하나의 파일은 full-path로 나타내면
엄연히 고유하다.

또한 file name은 하나의 디렉토리 내에서만 고유하게 존재한다.

 그래서 숫자로 되어있는 i-node 보다는
의미가 있고 친숙한 string 형식의 path가 낫다.

 이것은 자명하기에 따로 증명하지 않는다.

 그럼 이제 i-node를 대신하여 path를
사용하는데,

 이것들을 mapping 시켜놓아야 한다.

 그럼 어떻게 해야 할까?


 자, 그럼 이런 i-node 상황이 있다고 가정하자.

 

 이때 i-node number 2는 항상 root 디렉토리
가리키기로 높으신 분들이 결정했다.

 파일이나 디렉토리의 최상위인 root 디렉토리에
접근해야지만,

 다른 파일이나 디렉토리의 path를 찾을 수 있기에
i-node 2번에 root를 mapping 시켜놓았다.

 즉, 매번 다른 root를 가리키는 i-node를 찾거나
하는 것이 비효율적이라서 박아놓았다.

 

 자, 그러면 대충 연습을 해보자.

 아래에는 시각적으로 표현한 파일 계층구조가 있다.

 root 하위에는 Readme.txt가 있고 hello라는
디렉토리가 있다.

 

 각각의 파일과 디렉토리에 접근하기 위해서는
먼저 root에 접근 해야하고,

 root에는 하위 파일들의 i-node 위치 정보를 갖고 있다.

 이렇게 root→(참조)→(...) 를 통해서 파일에 접근할 수 있다.

 

 그럼 위와 같은 구조의 계층이 있다고 가정하자.

 위의 구조는 root(i-node number 2)부터 시작하여
참조→참조→... 를 통해서 6번의 참조가 이루어진다.

 즉, 6번의 읽기가 발생한다.

 화살표를 잘 따라가 보자.

 

 이것을 통해 알 수 있는 사실은,

 열고자 하는 파일의 depth가 깊으면 깊을수록
읽기의 수가 많아져서

 결국 열리는 속도가 느려진다는 것이다.

 

 원래의 문제로 돌아와서, path 또한 한계가
있음을 알게 되었다.


 그렇게 한계가 있는 i-node 접근법과
path 접근법을 알아보았다.

 이번에는 File Descriptor라는 것을 알아본다.

 

 누가 봐도 프로그래밍을 조금이나마 했다면
이것이 파일을 여는 코드임을 짐작할 수 있다.

 open 함수를 사용하여 system call을 던져서
file desciptor(fd)라는 것을 리턴받는다.

 이 리턴 값은 int형이다.

 같은 프로세스에서 파일을 하나씩 만들거나
열면 fd의 값은 1씩 증가한다.

fd 0번 값은 표준 입력(키보드)이며, fd 1번 값은 표준 출력(모니터)이다.
참고로 fd 2번은 에러(error)다.

 fd는 프로세스에서 고유하게 존재한다.

 

 그래서 fd가 뭐냐?

 fd는 path를 통해 i-node들을 거쳐간 경로들을
저장해 놓아야 겠다는 아이디어에서 출발한다.

 어떤 i-node에 접근하면 바로 해당 파일에
접근할 수 있게끔 말이다.

 그것이 바로 fd이다.

파일을 읽고 쓸 때마다 path를 타고 내려간다면 속도의 문제가 발생한다. 이를 방지하기 위해 해당 파일을 바로 찾는 i-node를 찾고, 그 i-node를 fd에 저장한다.

 

 

 

 

 예를 들어 file.txt 파일을 열었다고 하자.

 이 파일을 처음 열었을 때 받을 fd는 3이며,
offset은 0, i-node는 해당 데이터블럭을 가리킨다.

 

 그리고 file.txt를 12만큼 read하면 offset이
12만큼 이동하게된다.

 

 이때 동일한 파일 file.txt를 또 열었을 때,
프로세스에서는 다른 fd를 할당한다.

 하지만 해당 파일 자체는 동일하기 때문에
동일한 i-node를 가리키게 된다.

 그리고 그 i-node는 file.txt의 데이터블럭을
가리킨다.

 이때 duplicate와 open의 차이점은 fd 구조체를
공유하여 사용하는가에 있다.

 공통점은 당연하게도 다른 fd number를
할당받는다는 것이다.

 

 

 최초 1회 open할 때만 경로를 탐색하고,

 그 이후로는 fd를 활용하여 traverse overhead 없이
read/write가 가능하게끔 만들었다.


 그렇다면 파일을 지울 때는 어떤 일이 일어날까.

 사실 파일을 생성, 읽기, 쓰기 할 때는
system call이 발생하지만,

 파일을 삭제할 때에는 system call이
발생하지 않는다.

 파일을 삭제할 때는 Garbage Collection이
발생한다.

 

 파일을 삭제하면, 해당 저장소의 공간에는
데이터들이 그대로 남아있다.

 삭제한 파일의 데이터를 굳이 다른 값으로
바꾸지 않고 내버려두고,

 해당 공간에 다른 값들을 덮어쓸 수 있도록
만든다.

 

 i-node가 가리키고 있는 데이터들이 있는지
확인하고,

 어떤 i-node도 데이터 블록을 가리키고 있지
않다면 그 데이터블록은 삭제되었다고 판단,

 나중에 OS는 이러한 데이터 블록(Garbage)들을
모았다가 free page로 다시 재활용한다.

 그럼 그 자리에 다른 데이터들이 덮어써진다.

 이것이 데이터 삭제의 진실이다.

 

 그래서 i-node에서는 reference count라는
것을 가지고 있다.

 어떤 파일이 자신(i-node)을 참조하고 있는지를
알려주는 데이터이다.

 만약 파일을 열면 reference count가 ++ 된다.
파일을 닫으면 --된다.

 그다음?