컴퓨터 기본 구조에서 파일을 관리하는 방법을 하드웨어적 측면과 소프트웨어적 측면에서 공부한다.
파일 시스템은 디스크에 존재하는 데이터와 프로그램의 저장과, 접근할 수 있는 기법을 제공한다.
컴퓨터는 정보들을 자기디스크, 자기테이프, 광디스크와 같은 다양한 저장 매체에 저장할 수 있다. 운영체제는 저장장치의 물리적 특성을 추상화하여 논리적 저장 단위, 즉 파일을 정의한다. 파일은 운영체제에 의해 물리 장치들로 맵핑되고, 일반적으로 비휘발적 특성을 지니기 때문에, 전원이 끊어진 상황에서도 정보들을 영구히 보존할 수 있다.
사용자의 입장에서 파일은 논리적 보조저장장치의 가장 작은 할당 단위이다.
파일은 일반적으로 프로그램과 자료로 나누어진다.
간단 정리
- 파일 : 정보의 집합체. 정보란 비트, 바이트, 행, 레코드 등을 모두 포함한다. 파일 작성자나 사용자가 해당 정보를 정의한다.
- 파일은 보조기억장치에 저장되어 있고, 주로 프로그램파일 혹은 데이터파일이다.
- 프로그램 : 원시 프로그램(소스 프로그램, C, C++), 목적(바이너리) 프로그램으로 나누어 진다.
- 데이터 파일 : 수를 포함해 문자 또는 ASCII 코드로 된 문자 혹은 숫자로 구성되어 있다. 내용면에서는 텍스트 파일처럼 비정형화된 자유형식일 수도 있고, 데이터베이스처럼 엄격히 정형화된 형태일 수도 있다. 또한 동영상, 이미지, 소리 등 정해진 포맷을 따르고 있을 수도 있다.
- 어떤 파일이든 하드디스크와 같은 저장장치에 저장될 때 섹터 단위로 저장된다. 즉, 하나의 파일이 디스크 내 여러개의 섹터로 구성되는 것이다. 섹터는 논리적으로는 블록에 해당되고, 일반적 PC에서 한 섹터의 크기는 512바이트이다.
사용자의 편의를 위해 파일에 이름을 부여하고, 하나의 문자열로 나타낸다. 시스템에 따라 대소문자를 구분하기도, 안하기도 한다.
일단 파일이 만들어지면 그 파일은 프로세스, 사용자, 시스템으로부터 독립하게 된다. 한 사용자가 파일을 생성하고, 다른 사용자가 복사하거나 이메일로 첨부하여 보낼 수 있다.
일반적으로 파일의 속성은 다음과 같다.
운영체제가 파일을 관리하기 위해 사용하는 시스템 콜과 그것들의 조합
위에 있는 6개의 연산은 파일 조작에 필요한 최소한의 연산이다. 새로운 정보를 추가하는 첨가(appending)나 재명명(renaming)등이 더 있다.
운영체제는 모든 열린 파일에 대한 정보를 갖는 오픈 파일 테이블(open file table)을 유지한다.
시스템 콜 open()은 전형적으로 열린 파일 테이블의 항목에 대한 포인터를 리턴한다. 실제 파일이 아닌 포인터를 입출력 연산에 사용함으로써 더이상의 탐색과정을 피하고 시스템 호출 인터페이스를 단순화한다.
여러 프로세스가 동시에 파일을 열 수 있는 환경에서는 open()과 close() 연산의 구현은 더 복잡한데, 보통 운영체제는 프로세스별 테이블과 범시스템 테이블을 사용한다.
- 오픈 파일 테이블(open file table)
파일이 하나의 프로세스에 의해 열려 있다. 이때 다른 프로세스가 open()을 호출한 경우 오픈 파일 테이블에 범시스템 테이블에 있는 정보를 가리키는 새로운 포인터가 생긴다.
일반적으로 오픈 파일 테이블은 파일을 연 프로세스 수를 가리키는 오픈 계수(open count)를 각 파일에 연관지어 둔다. close() 콜은 이 오픈 계수를 감소시키고 계수가 0이 되면 해당 정보를 테이블에서 제거한다.
몇몇 운영체제는 열려진 파일을 잠금할 수 있는 기능을 제공한다. 한 프로세스가 파일을 잠그면 다른 프로세스는 접근이 불가하다.
디스크 시스템의 경우 보통 섹터에 의해 결정되는 블록 크기를 가진다. 또한 어떠한 경우든 일련의 블록으로 간주된다. 여러 개의 논리 레코드를 하나의 물리 레코드(블록)에 담기도 하는데 이를 팩킹이라고 한다. 기본적인 I/O기능(입출력)은 블록 단위로 실행되며, 그렇기 때문에 내부 단편화 문제가 발생할 수 있다.
파일은 정보를 저장한다. 파일이 사용될 때 정보가 반드시 접근되어 컴퓨터 메모리로 읽혀져야 한다. 그 때, 파일에 접근하여 데이터를 읽는 방법이다.
가장 간단한 방법으로 파일의 정보가 레코드 순서대로 차례차례 처리된다.
현재 위치를 가리키는 포인터에서 읽기/쓰기 시스템 콜이 발생한 경우 포인터를 앞으로 보내면서 읽거나 쓴다. 뒤로 돌아가기 위해서는 지정한 offset만큼 되감기를 해야 한다.
테이프 모델에 기반을 두고 있다.
직접 접근 또는 상대 접근은 특별한 순서 없이 빠르게 레코드를 읽고 쓸 수 있다. 디스크 모델에 기반을 두며 이는 무작위 파일 블록에 대한 임의 접근(Random Access)를 허용하기 때문이다. 직접 접근의 경우 읽기나 쓰기 순서에 제약을 가하지 않는다.
대규모 정보를 즉각적으로 접근하는 데 유용하여 데이터베이스에 이용된다.
직접 접근 파일에 기반을 두고 색인을 구축한다. 크기가 큰 파일을 입출력 탐색할 수 있게 도와준다.
통상 수천 수만 수십억 개의 파일을 하드디스크, 광학디스크, 반도체 디스크를 포함한 임의 접근장치에 저장하는 방법이다. 파일이 매우 많으므로 체계적으로 구성을 갖추어야 한다. 그래서 디렉토리의 사용을 수반한다.
범용 컴퓨터 시스템은 다수의 저장장치를 가지고 그 장치들은 파일 시스템을 저장할 수 있는 볼륨으로 분할된다.
파일 시스템이 없을 수도 있으며, 다양한 종류의 파일 시스템을 가질 수도 있다.
디렉토리는 파일 이름을 해당 디렉토리 항목으로 변환해주는 심벌 테이블로 볼 수 있다. 따라서 다양한 방법으로 구성될 수 있다.
파일 관리를 위해선 파일 찾기, 파일 생성, 파일 삭제, 파일의 재명명, 파일 시스템 순회를 제공해야하며, 삭제시 하위 디렉토리가 갖는 파일이 있는지 확인해야한다.
가장 간단한 구조의 디렉토리이다.
파일이 많아지거나 다수의 사용자가 사용할 경우 심각한 제약이 따른다.
각 파일들은 서로 유일한 이름을 가진다. 서로 다른 사용자라 하더라도 같은 이름을 사용할 수 없다.
사용자에게 개별적인 디렉토리를 만들어 준다.
2단계 구조 디렉토리를 확장하여 다단계 트리 구조로 만들 수 있다. 사용자들이 자신의 서브디렉토리를 만들어서 파일을 구성할 수 있게 한다. 트리 구조는 하나의 루트 디렉토리를 가지며 시스템의 모든 파일은 고유 경로를 가진다.
디렉토리의 각 항목은 한 비트를 사용하여 일반 파일인지(0) 디렉토리 파일인지(1)를 구분한다.
통상적으로 각 프로세스는 현재 디렉토리를 가지고 있다.
디렉토리의 경로명을 지정할 때에는 절대경로명과 상대경로명 두 가지가 있다.
사이클이 없는 그래프는 디렉토리들이 서브디렉토리와 파일들을 공유할 수 있도록 허용한다.
트리 구조의 디렉토리를 자연스럽게 일반화한 방식이다.
절대경로명/상대경로명을 이용하여 링크라고 불리는 새로운 디렉토리 항목을 만들 수 있다.
단순한 트리 구조보다는 융통성이 있는 대신에 더 복잡하다.
파일을 삭제할 때 대상이 없는 포인터(dandling pointer)를 남긴다.
참조되는 파일에 참조 계수를 두어 계산한다. 참조 계수가 0이 되면 현재 파일을 참조하는 링크가 존재하지 않으므로 파일을 삭제할 수 있다.
디렉토리에서 순환이 허용되는 경우 무한 루프에 빠질 수도 있다. 따라서 순환이 발생하지 않도록 하위 디렉토리가 아닌 파일에 대한 링크만 허용하거나 가비지 컬렉션을 이용해 전체 파일 시스템을 순회하고, 접근 가능한 모든 것을 표시한다. 디렉토리를 순회할 때 링크가 있으면 우회하여 순환을 피할 수도 있다.
디렉토리 구조가 사용자간의 파일 공유를 허용한다면, 시스템은 파일 공유를 중재해야 한다. 대부분의 시스템은 파일/디렉토리의 소유자, 그룹이라는 개념을 사용하는 형태로 발전해왔다.
UNIX 시스템의 소유자는 파일에 대한 모든 작업을 실행할 수 있지만 그룹 멤버는 일부 작업만을 실행할 수 있다.
네트워크를 이용하여 원거리 컴퓨터 간의 통신을 하면서 파일 시스템을 공유하는 방법이다.
열린 파일에 대한 사용자의 쓰기는 동일 파일을 연 다른 사용자들에게 즉시 보인다.
공유 모드 : 사용자들이 파일 내의 현재 위치 포인터를 공유한다. 여럿이서 파일 위치 포인터를 같이 쓴다.
앤드류 파일 시스템
열린 파일에 대한 쓰기는 동시에 같은 파일을 연 다른이에게 보이지 않는다.
파일이 닫히면 파일에 대한 변경들이 나중에 시작되는 세션에서만 보인다.
사용자들은 지연 없이 그들의 파일 이미지에 대해 병행적으로 읽기와 쓰기 모두를 실행할 수 있다.
파일이 공유된다고 선언되면, 더이상 변경이 불가능하게 만든다.
접근을 허용하지 않거나/자유롭게 접근하거나 이 두가지의 접근법으로는 다양한 방법의 파일 접근/공유 절차를 커버할 수 없다. 그래서 우리는 통제된 접근을 구현해야 한다.
사용자가 어떤 접근 타입을 가지고 오는지에 따라 파일 연산을 통제시킬 수 있다.
파일 연산 : 읽기, 쓰기, 실행, 추가, 삭제, 리스팅
파일과 디렉토리에 접근 제어 리스트를 둔다. (ACL, Access Control List)
특정 사용자가 어떤 파일에 접근할 경우 리스트를 보고 허용 여부를 결정한다.
유닉스의 경우 3파일에 3비트 rwx 필드를 두어 접근 권한을 관리한다.
윈도우의 경우 gui를 통해 접근이 가능하다.
컴퓨터 시스템 자원에서 가장 중요한 부분은 CPU이다. CPU를 어느 프로세스에 나누어 줄 것인가가 프로세스 관리를 하는 역할이다. 그 다음 중요한 자원이 메인 메모리인 주기억장치이다. 메인 메모리에 대해 페이징이나 가상 메모리와 같은 방법을 사용하는 것이 메인 메모리 관리를 하는 역할에 속했다. 다음으로 중요하다고 할 수 있는 컴퓨터 시스템 자원으로는 하드 디스크와 같은 보조기억장치라고 할 수 있다. 보조기억장치는 파일 시스템을 관리하는 역할을 수행한다.
대표적인 보조기억장치는 하드디스크이다. 하드 디스크는 트랙(track)과 트랙을 자른 섹터(sector)로 이루어져 있다. 또한 같은 거리에 존재하는 트랙들의 집합을 실린더(cylinder)라고 한다. 일반적으로 Sector size는 512bytes인데 크기가 너무 작으므로 Sector들을 여러개 모아놓은 것은 Block이라고 한다. 하드디스크는 이러한 Block들을 단위로 읽고 쓰기를 진행한다. 디스크는 처음에 free block인 비어있는 block으로 구성되게 된다. 그런데 Block 단위로 디스크가 돌아가기 때문에 파일에 Block에서 남는 공간이 발생하게 된다. 메모리 관리에서 했던 내부 단편화와 비슷한 현상이라고 할 수 있다. 이렇게 되면 공간이 낭비되므로 효율이 안 좋게 된다. 그런데 그렇다고 해서 Block의 크기를 줄이게 되면 디스크에서 읽고 쓰는 과정이 매우 오래 걸리게 될 것이다. 하나의 파일을 읽는데 더 많은 Block을 읽어야하기 때문에 속도가 떨어지게 되는 것이다. 어떻게 하면 free block을 잘 할당할 것인지, 속도를 감소시키지 않게 할 것인지를 따지면서 파일을 할당하는 것이 중요하다.
파일 할당에는 3가지 방법이 있다. 연속 할당, 연결 할당, 색인 할당이다.
연속 할당은 각 파일에 대해 디스크 상의 연속된 블록을 할당하는 방법이다. 하드 디스크에 block들이 나열되어 있는데 여기에 연속된 순서대로 파일을 저장하는 것이다. 이렇게 되면 디스크가 읽어 들어갈 때 이동 경로를 최소화할 수 있다는 장점이 있다. 빠른 I/O의 성능을 가질 수 있는 것이다. 순서적으로 읽을 수도 있고 특정 부분을 바로 읽을 수도 있게 한다. 동영상이나 음악, VOD와 같이 크기가 큰데 실시간인 자료들에 적합하다고 할 수 있다. 하지만 단점도 존재한다. 특정 파일이 삭제되면 중간에 빈 공간인 hole이 생성되게 된다. 계속해서 파일의 생성과 삭제를 반복하면 곳곳에 흩어지는 hole들이 생성되게 된다. 이는 외부 단편화가 생긴 것으로 생각할 수 있다. 그러면 디스크의 공간 낭비가 매우 심하게 될 것이다. 또한 연속되게 파일을 할당하면 중간에 파일의 크기를 계속 증가시킬 수 없다. 뒤의 block에는 다른 파일이 공간을 차지하고 있으니 크기 조절이 불가능하게 된다.
연결 할당은 파일을 linked list의 형태로 저장하는 것을 말한다. 파일은 각자 디렉토리를 가지고 있는데, 여기에는 어떤 block에 할당되어 있는지를 기억한다. 디렉토리에 제일 처음 저장되는 block을 가리키게 되고 각 block은 포인터를 저장을 위한 4바이트 이상을 가지고 있어서 다음 block을 지칭하게 된다. 새로운 파일이 만들어진다고 생각을 하면 비어있는 임의의 block을 첫 block으로 지정하고 파일이 커지면 다른 block을 할당 받고 포인터를 이용해 연결을 하면 된다. 이런 방법을 사용하면 외부 단편화를 없앨 수 있다. 임의의 자리에 넣고 포인터로 연결만 하면 되기 때문에 딱 block에 맞는 사이즈만 맞추어 넣으면 되기 때문이다. 하지만 단점도 존재한다. 연결 할당을 하게 되면 각각 순서대로 포인터로 연결되어 있기 때문에 순서대로 읽는 것 밖에 할 수 없다. 중간에 있는 자료만 읽고 싶어도 포인터를 따라 가지 않으면 읽을 수 없기 때문이다. 또한 포인터 저장을 위해 4바이트 이상을 손실시켜야 되고 만약 포인터가 끊어지면 이하 접근이 불가하기 때문에 낮은 신뢰성을 가진다. 그리고 포인터를 따라가는 과정을 거쳐야 하므로 속도 면에서 느릴 수 있다.
색인 할당은 파일당 인덱스 block을 사용하여 파일을 할당하는 방법이다. 인덱스 block은 각 파일에 할당된 포인터를 저장하는 모음이다. 연결 할당과 같은 방식으로 할당하지만 인덱스 block을 둔다는 점에서 다르게 된다. 인덱스 block에 각 포인터에 대한 정보를 저장하게 되면 연속 할당의 장점인 순서적으로 읽을 수도 있고 특정 부분을 바로 읽을 수도 있게 한다. 또한 연결 할당의 장점인 외부 단편화도 없어지는 효과를 가지게 된다. 하지만 인덱스 블록에 할당에 따른 저장 공간의 손실이 발생할 수 있다.
https://velog.io/@codemcd/%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9COS-4.-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EC%84%9C%EB%B9%84%EC%8A%A4
https://copycode.tistory.com/129
https://noep.github.io/2016/02/23/10th-filesystem/
https://drawdeveloper.tistory.com/27?category=670816