이번 장에서는 HDD나 SSD와 같은 영속 저장 장치(persistent storage)에 대해 알아볼 것이다.
핵심 질문 : 어떻게 영속 장치를 관리하는가
- 운영체제가 영속 장치를 어떻게 관리해야 할까?
- API들은 어떤 것이 있는가?
- 구현의 중요한 측면은 무엇인가?
저장 장치의 가상화에 대한 두 가지 주요 개념인 파일과 디렉토리에 대해 알아보자.
파일
디렉터리
<사용자가 읽을 수 있는 이름, 저수준의 이름> 쌍으로 이루어진 목록을 갖고있다.
/foo/bar.text)이번에는 기본적인 파일 시스템 인터페이스를 좀 더 상세하게 논의해 볼 것이다.
파일 시스템 인터페이스
- 파일 생성
- 파일 접근
- 파일 삭제
open 시스템 콜을 사용하여 파일을 생성할 수 있다.
int fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC);
open()을 호출하면서 O_CREAT 플래그를 전달하면 프로그램은 새로운 파일을 만들 수 있다.open()은 다수의 플래그를 받으며, 이 예제에서는 O_WRONLY와 O_TRUNC 플래그도 추가로 전달하였다.O_WRONLY: 파일이 열렸을 때 쓰기만 가능하게끔 허용O_TRUNC: 파일이 이미 존재할 때는 기존 내용을 모두 삭제파일이 있으면, 그 파일들을 당연히 읽거나 쓰고 싶을 것이다.
커맨드 라인을 사용 중이라면 cat 명령어로 파일을 읽을 수 있다.
> echo hello > foo
> cat foo
hello # 화면에 hello 출력
cat프로그램은 어떻게 파일foo에 접근할까?
> strace cat foo
...
open("foo", O_RDONLY|O_LARGEFILE) = 3 # foo 파일 열기
read(3, "hello\n", 4096) = 6 # foo 파일 읽기
write(1, "hello\n", 6) = 6 # stdout에 파일 쓰기
hello # 화면에 hello 출력
read(3, "", 4096) = 0 # foo 파일 읽기
close(3) = 0 # foo 닫기
...O_RDONLY 플래그 줘서 읽기만 가능O_LARGEFILE 플래그 줘서 64bit 오프셋 사용cat은 read() 시스템 콜을 사용하여 파일에서 몇 바이트씩 반복적으로 읽는다.fd가 들어간다"hello\n"가 들어간 것을 표현한 것이다."hello\n"는 6바이트)write(1,buf,size)을 해준다.foo를 close파일에 쓰는 것도 비슷한 단계를 거친다.
지금까지 파일을 읽고 쓰는 과정을 논의하였는데, 모든 접근은 순차적이었다. 그렇지만 때로는 파일의 특정 오프셋(위치)부터 읽거나 쓰는 것이 유용할 때가 있다.
임의의 오프셋에서 읽기/쓰기 수행
off_t lseek(int fildes, off_t offset, int whence);
SEEK_SET: 오프셋은 offset byte로 설정SEEK_CUR: 오프셋은 현재 위치에 offset byte를 더한 것으로 설정SEEK_END: 오프셋은 파일 크기에 offset byte를 더한 것으로 설정N 바이트를 읽거나 쓸때, offset+N 으로 암묵적 갱신lseek를 사용하여 명시적으로 오프셋 갱신write() 호출은 성능상 이유로 파일 시스템은 쓰기들을 일정 시간동안 메모리에 모아(버퍼링) 한 번에 저장 장치에 전달된다.
어떤 프로그램은 쓰기에 있어서 좀 더 강력한 보장을 필요로 한다 (ex. DBMS의 복원 모듈). 이럴 땐 강제적으로 즉시 디스크에 기록할 수 있는 기능이 필요하다.
fsync(int fd)
fd에 대해 파일의 모든 더티(dirty 즉, 갱신된) 데이터를 디스크로 강제로 내려보내는 유닉스 APIfoo가 존재하는 디렉터리도 fsync() 해주어야 한다.커맨드 라인에서는 mv 명령으로 파일명을 변경할 수 있다.
> mv foo bar # foo를 bar로 rename
mv가 rename(char *old, char *new) 라는 시스템 콜을 호출하는 것을 확인할 수 있다. (각각 옛날 이름, 새 이름)rename()은 시스템 크래시에 대해 원자적으로 구현되었다.foo.txt 이다.int fd = open("foo.txt.tmp", O_WRONLY|O_CREAT|O_TRUNC);
write(fd , buffer , size); // 파일의 새로운 버전 쓰기
fsync(fd);
close(fd);
rename("foo.txt.tmp", "foo.txt");foo.txt.tmp 생성 후 여기에 기록foo.txt.tmp를 foo.txt로 rename파일 시스템은 각 파일에 대한 정보를 보관하고, 이를 메타 데이터라고 한다.
메타 데이터(metadata)
stat()이나 fstat() 같은 시스템 콜을 사용한다.stat의 구조struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for filesystem I/O */
blkcnt_t st_blocks; /* number of blocks allocated */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};리눅스에서는 rm 명령으로 파일을 삭제할 수 있다.
strace를 통해 rm이 어떻게 동작하는지 알아보자.
> strace rm foo
...
unlink("foo") = 0
...
unlink()라는 시스템 콜이 호출되는 것을 알 수 있다.unlink()는 지워져야 하는 파일 이름을 인자로 받은 후에 성공하면 0을 리턴한다.그런데 왜 remove나 delete가 아닌 unlink(연결을 끊다) 일까? 답을 이해하기 위해서는 파일뿐만 아니라 디렉터리에 대해서도 이해해야 한다.
디렉터리 관련 시스템 콜들은 디렉터리를 생성하고, 읽고, 삭제하지만, 디렉터리에는 절대로 직접 쓸 수 없다.
대표적인 디렉터리 생성 명령어는 mkdir이 있다.
mkdir 동작 과정 (strace)> strace mkdir foo
...
mkdir("foo", 0777) = 0
...디렉터리 읽기는 ls와 같은 명령어가 있다. ls와 유사한 도구를 직접 만들어 어떻게 동작하는지 알아보자.
int main(int argc , char *argv[]) {
DIR *dp = opendir("."); // 디렉터리 열기
assert(dp != NULL);
struct dirent *d;
while ((d = readdir(dp)) != NULL) { // 디렉터리 반복적으로 읽기
printf("%d %s\n", (int) d−>d_ino , d−>d_name);
}
closedir(dp); // 디렉터리 닫기
return 0;
}
opendir(), readdir(), 및 closedir() 시스템 콜을 사용한다.디렉터리에는 많은 정보가 있지 않기 때문에 (단순하게 이름과 아이노드 번호를 매핑하는 것 이외에 몇 가지만 제공함) 프로그램은 각 파일에 stat()을 호출하여 파일 크기와 같은 구체적인 정보를 얻는다.
struct dirent {
char d_name[256]; /* filename */
ino_t d_ino; /* inode number */
off_t d_off; /* offset to the next dirent */
unsigned short d_reclen; /* length of this record */
unsigned char d_type; /* type of file */
};마지막으로 rmdir() 시스템 콜을 사용하여 디렉터리를 삭제할 수 있다.
rmdir()은 디렉터리를 지우기 전에 디렉터리가 비어 있어야 한다는 조건이 붙는다.파일 삭제 시 왜 unlink()를 사용하는지를 이해하기 위해서 이제 파일 시스템 트리에 항목을 추가하는 새로운 시스템 콜 link()를 알아보자.
하드 링크 <- link() 시스템 콜 사용
link(char *old, char *new): 원래 경로명, 새 경로명을 인자로 받는다.ln이 그 일을 한다.> echo hello > file1
> cat file1
hello
> ln file1 file2 # linking file2 to file
> cat file2
hello link는 새로이 링크하려는 이름 항목을 디렉터리에 생성하고, 원래 파일과 같은 아이노드 번호를 가리키도록 한다.> ls −i file1 file2
67158084 file1
67158084 file2 # inode가 같다link는 동일한 아이노드 번호(이 예제에서는 67158084)에 대한 새로운 링크를 생성한다.
unlink()가 왜unlink()가 되었는지 이제 이해되기 시작했을 것이다.
파일을 생성할 때 사실은 두 가지 작업을 하게 된다.
unlink() 시스템 콜
unlink()를 호출한다.> rm file1
removed 'file1'
> cat file2
hellounlink하면 아이노드 번호의 참조 횟수(reference count)를 검사한다.unlink()가 호출되면 이름과 해당 아이노드 번호 간의 "연결"을 끊고 참조 횟수를 하나 줄인다. > echo hello > file1
> stat file1
... Inode: 67158084 Links: 1 ...
> ln file1 file2
> stat file1
... Inode: 67158084 Links: 2 ...
> stat file2
... Inode: 67158084 Links: 2 ...
> ln file2 file3
> stat file1
... Inode: 67158084 Links: 3 ...
> rm file1
> stat file2
... Inode: 67158084 Links: 2 ...
> rm file2
> stat file3
... Inode: 67158084 Links: 1 ...
> rm file3하드 링크의 제한
심볼릭 링크 (또는 소프트 링크)
ln -s 명령어 사용> echo hello > file1
> ln −s file1 file2
> cat file2
hellostat 명령어를 실행시키면 일반 파일이나 디렉토리와 다른 "symbolic link" 유형이라고 출력된다.ls 명령어를 통해서도 다르다는 것을 알 수 있다. (첫 글자가 l임)> echo hello > file1
> ln −s file1 file2
> cat file2
hello
> rm file1
> cat file2
cat: file2: No such file1 or directory다수의 파일 시스템들이 존재할 때 이들을 묶어서 어떻게 하나의 큰 디렉터리 트리를 구성할까?
mkfs 명령어
/dev/sda1에 시스템 타입 EXT3의 형식으로 구성된 파일 시스템을 생성할 수 있다.mount 명령어
마운트: 새로이 생성된 파일 시스템을 루트 디렉터리에서 시작하는 기존의 디렉터리 구성을 통해 접근할 수 있도록 해주는 것
mount() 시스템 콜을 사용하여 기존의 디렉터리 중 하나를 마운트 지점(mount point)으로 지정한다.
그리고 나서 마운트 지점에 생성된 파일 시스템을 "붙여 넣는다".
예)
/dev/sda1에 EXT3 파일 시스템 존재한다 가정 (아직 마운트 되지 않음)a와b 라는 두 개의 하위 디렉토리가 있고, 각 디렉터리에 foo 라는 파일이 들어있다./
├── a
│ └── foo
└── b
└── foo/home/users/ 위치에 마운트 하기 위해 다음 명령어를 실행한다.> mount −t ext3 /dev/sda1 /home/users> ls /home/users/
a b
> ls /home/users/a/
foo여러 개의 개별 적인 파일 시스템을 갖는 대신에 마운트는 모든 파일 시스템들을 하나의 트리 아래에 통합시킨다.
/
├── home
. ├── users
. . ├── a
. . │ └── foo
. └── b
└── foo