# I/O (chap 36 - 40)

운영체제가 하는 일을 크게 분류하면 다음과 같은데,

- 가상화
- 병행성
- I/O

앞의 두개를 지나서 이제 그 마지막인 I/O 에 대해 간략하게 다뤄보고자 한다.

### Hardware Interface

소프트웨어가 인터페이스를 통해 다른 소프트웨어와 통신을 수행하듯이 하드웨어도 인터페이스를 제공하여 시스템 소프트웨어가 동작을 제어할 수 있도록 해야한다.

그래서 모든 하드웨어 장치들은 특정한 상호 동작을 위한 명시적인 인터페이스를 갖고 있다.

![Image.png](https://res.craft.do/user/full/8884c80f-6eec-6a29-2a03-049def967beb/doc/350B071C-EEDB-4CCF-B75C-9DF861353B89/EBB659A3-A366-4266-8C14-DDF0FEAF2E71_2/h85WrECZWosURVVoxjYFN1XCBoSrj5z9Tiz73rPNPcQz/Image.png)

> 어떻게 하면 운영체제를 장치 중립적으로 만들고, 장치와의 상호작용을 위한 상세 구현 내용을 운영체제로부터 숨길 수 있을까?

우리 컴퓨터의 파일 시스템은 수 많은 형태의 장치들과 상호작용한다. SCSI 디스크와 IDE 디스크, USB 이동식 드라이브 등과 같이 서로 다른 기기 위에서 동작할 수 있어야한다. 파일 시스템이 각 장치들의 구체적인 입출력 명령어 형식에 종속되어서는 안된다.

이를 위해서는 각 하드웨어는 자신의 동작 방식을 추상화하고 특정 규약을 따르는 통일된 인터페이스를 제공해야한다. 그리고 각 하드웨어 장치의 동작 방식을 알고있는 디바이스 드라이버(device driver)를 구현하여 상호작용을 캡슐화한다.

위 그림에서 보이듯이 파일 시스템은 자신이 어떤 디스크 종류를 사용하지는지 전혀 알지 못한다. 단지 인터페이스를 통해 범용 블록 계층에 r/w 명령을 요청할 뿐이다. 그리고 실제로 동작은 디바이스 드라이버가 수행한다.

물론 위 같은 캡슐화가 장점만 있는 것은 아니다. 장치가 아무리 자신만의 특출난 기능을 많이 가지고 있다고 해도 범용적인 인터페이스를 따라야 하기 때문에 그 특수 기능들은 사용할 수 없게 된다.

> 흥미로운 점은 어떤 장치를 시스템에 연결하든 디바이스 드라이버가 필요하기 때문에 시간이 흐름에 따라 디바이스 드라이버 코드가 커널 코드의 대부분을 차지하게 되었다는 것이다. Linux 커널에 대한 연구에 따르면 70% 이상의 운영체제 코드가 디바이스 드라이버를 위한 코드라고 한다.

## 파일 시스템

### File & Directory

> 우리가 보고있는 파일은 진짜 데이터가 아니다. 데이터에 대한 링크일 뿐이다.

파일은 단순히 읽거나 쓸 수 있는 순차적인 바이트의 배열이다.

그리고 우리는 각 파일에 대해 우리 마음대로 이름을 지어준다. 하지만 우리가 파일에 지어준 이름은 아무런 의미가 없으며 파일의 실제 이름은 이미 지어져있다. 이를 inode 번호라고 부른다.

디렉토리는 자신만의 inode 번호를 가지며 해당 디렉토리의 각 파일의 이름과 inode 번호 쌍을 가지고있다.
디렉토리의 각 항목은 파일 또는 다른 디렉토리를 가리킨다.

### Hard Link & Soft Link(Symbolic Link)

> 우리가 평소에 파일을 관리하기 위해 사용하는 파일 이름은 파일의 메타데이터(inode)에 대한 연결일 뿐이다.

link() 시스템 콜은 원래 경로명과 새로운 경로명을 인자로 받아서 원래 파일을 접근할 수 있는 새로운 파일 이름을 하나 더 만든다.

새로 링크하려는 이름을 해당 디렉토리에 생성하고 원래 파일과 같은 inode 를 가리키도록 하는 것이다.

이 경우 당연히 원래 파일 자체는 복사되지 않는다. 그래서 ls -i 명령어로 각 파일의 inode 번호를 출력해보면 동일한 것을 확인할 수 있다.

unlink() 시스템 콜은 반대로 해당 파일과 이를 가리키는 파일사이의 연결을 끊는다. 하나의 파일에 대해 여러 하드링크가 존재하는 상황에서 하드링크 하나가 사라진다고 해도 원본 파일과 다른 하드링크는 아무런 영향을 받지 않는다. 하나의 파일을 가리키는 링크의 갯수를 참조 횟수라고 하는데, unlink() 를 호출하면 참조 횟수를 하나 줄이는 것이다.

마침내 참조 횟수가 0에 도달하면 파일 시스템은 비로소 inode 와 관련된 데이터 블럭을 해제하여 파일을 '삭제’하는 작업을 수행한다.

> 소프트 링크는 파일, 디렉토리와 더불어 파일 시스템에 존재하는 세 번째 종류의 유형이다.

소프트 링크(심볼릭 링크)는 표면적으로는 하드 링크와 매우 유사하지만 그 이면은 매우 다르다. 소프트 링크는 하드 링크와는 다르게 형식이 다른 독립된 파일로 존재한다.

소프트 링크는 연결하는 파일의 경로명을 저장한다. 그래서 그 크기 자체는 매우 작다.(경로명의 크기이기 때문) 그래서 링크를 걸었던 원본 파일을 삭제하면 소프트 링크가 가리키는 파일이 사라지게 된다. dangling reference 문제가 발생할 수 있는 것이다.

### File System & Mount

> 다수의 파일 시스템을 연결하여 하나의 커다란 디렉토리 트리를 만들 수 있다.

파일 시스템을 생성하는 mkfs 명령어는 디스크 파티션("/dev/sda1”)과 파일 시스템 타입(“EXT3")를 인자로 받아 해당 파티션에 빈 파일 시스템을 생성한다. 이렇게 생성된 새로운 파일 시스템은 자체적인 디렉토리 트리를 가진다. 빈 루트 디렉토리 하나만 가진 상태인 것이다.

디스크에 따로 생성된 파일 시스템을 기존의 디렉토리 트리에 연결하여 접근할 수 있도록 하는 작업을 마웉느라 부른다. 기존 디렉토리 트리의 한 지점을 마운트 포인트로 지정하여 새로 생성된 파일 시스템을 붙인다.

## 파일 시스템 : 디테일

> 파일 시스템은 순수한 소프트웨어다. 모든 파일 시스템은 서로 다른 자료 구조를 갖고 각기 장단점이 있다.

### inode

> 파일 시스템은 데이터와 메타 데이터를 관리하기 위해 디스크 상에 어떤 자료 구조를 사용하는가?

파일 시스템의 자료 구조 중 가장 중요한 것은 아이노드(inode)이다. 파일 시스템은 아이노드를 아이노드 테이블에 배열의 형태로 저장한다. inode 라는 이름은 index node 의 줄임말인데, 아이노드 배열의 인덱스를 가리키기 위해 아이노드 번호를 사용했던 것에 기인한다.

아이노드에는 파일에 대한 모든 정보가 들어있다. 파일의 종류, 크기, 할당된 블럭 수 , 보호 정보, 시간 정보와 더불어 데이터 블럭이 디스크의 어디에 존재하는지와 같은 정보들이 담겨있다. 그리고 이와 같은 파일에 대한 정보들을 메타데이터(metadata)라고 부른다.

`open(“/foo/bar”, O_RDONLY)`  가 호출되면, 파일 시스템은 최종적으로 파일 bar 에 대한 아이노드를 찾아야한다. bar 파일을 읽기위한 과정을 따라가보자.

1. 제일 먼저 루트 디렉토리(“/“)의 아이노드를 찾는다.
   - 루트 디렉토리의 아이노드 번호는 미리 정해져있다. UNIX 에서는 2번이다.
1. 읽어들인 아이노드에서 데이터 블럭의 포인터를 추출하고, 해당 포인터를 따라 가서 루트 디렉토리의 실제 내용을 읽는다.
2. 읽은 정보에서 foo 라는 항목을 찾는다. foo 의 아이노드 번호를 얻는다.
3. 위 과정을 foo의 아이노드 번호로 다시 수행하여 foo 디렉토리의 내용을 읽는다.
4. bar 의 아이노드 번호를 얻는다.
5. 마지막으로 open() 은 bar 에 대한 아이노드를 메모리로 읽어 들인다.

이 과정을 표로 정리하면 다음과 같다.

![Image.png](https://res.craft.do/user/full/8884c80f-6eec-6a29-2a03-049def967beb/doc/350B071C-EEDB-4CCF-B75C-9DF861353B89/F80D6BB7-5E15-4F5A-9305-B85ECD14CA01_2/6hZxSnTG5RSM2v8IHl5feSj79sUUru8D7Sx8MfVP2yAz/Image.png)

위 그림에서 유의할 점은 I/O 발생 횟수는 경로의 깊이에 비례한다는 것이다. 경로가 하나 추가될 때 마다 해당 디렉토리의 아이노드와 이에 해당하는 데이터를 읽어야 한다.

위는 읽기 과정이라서 그나마 나은 편인데, 쓰기 과정의 경우 위보다 훨씬 복잡하고 많은 I/O 가 이루어진다.

> 파일 시스템은 이렇게 많은 I/O 에 드는 비용을 줄이기 위해 어떤 노력을 할까?

## 파일 시스템 캐싱 & 버퍼링

자주 발생하는 I/O 는 시스템 전체 성능에 안좋은 영향을 미친다. 그래서 대부분의 파일 시스템들은 자주 사용되는 블럭들을 메모리(DRAM)에 캐싱해놓는다.

캐시 교체 정책은 LRU 와 같은 다양한 정책중에 선택할 수 있다. 일반적으로 전체 메모리의 약 10% 가 파일 캐시로 사용되는데, 이렇게 미리 캐시의 크기를 고정해놓는 방식을 정적 기법이라고 부른다. 이는 간단하지만 메모리가 낭비될 수 있다.

가상 메모리와 파일 시스템을 융통성있게 배분하기 위해 현대의 시스템은 동적 파티션 방식을 사용한다.

가상 메모리 페이지들과 파일 시스템 페이지들을 통합하여 unified page cache 라는 개념을 만들었다. 이를 이용하면 어느 한 시점에 파일 시스템과 가상 메모리 중 어느 곳에 메모리를 더 많이 사용할 지 유동적으로 결정하여 할당할 수 있다.

