관리 메뉴

너와 나의 스토리

OS-[CH39_Files and Directories] 본문

Operating System

OS-[CH39_Files and Directories]

노는게제일좋아! 2019. 6. 2. 22:23
반응형

출처: http://pages.cs.wisc.edu/~remzi/OSTEP/file-intro.pdf

 

The Crux: How to manage a persistent device

39.1 Files And Directories

● File

file: 읽고 쓸 수 있는 linear한 byte 배열이다.

각각의 파일은 low-level name이 있는데, 보통 숫자이다. (inode number)

 

대부분의 시스템에서는 OS는 파일 구조를 많이 알고 있지 않다.

 

● Directory

directory: 디렉터리도 low-level name을 가짐. 하지만 list 쌍처럼 구체적인 것을 포함함.

ex) low-level name "10", 사용자가 읽을 수 있는 이름으로는 "foo"를 가지는 파일이 있다고 하자.

디렉터리의 항목은 ("foo","10")이 된다. (user-readable name, low-level name)으로 매핑됨

즉, 디렉터리는 <"human-readable name","low-level name"> 쌍의 집합이다

디렉터리의 각 항목은 파일 또는 다른 디렉터리를 참조한다.

다른 디렉터리에 디렉터리를 놓음으로써 사용자들은 directory tree를 만들 수 있다.

 

foo 디렉터리에 bar.txt 파일을 만들면 absolute pathname는 /foo/bar.txt가 된다.

 

디렉터리와 파일은 파일 시스템 트리의 다른 위치에 있는 한 동일한 이름을 가질 수 있다.

ex) /bar/foo/bar.txt

 

 

39.3 Creating Files

● open system call

open() 호출하고 O_CREAT flag를 넘김으로써, 파일은 새로운 파일을 만들 수 있다.

ex) 현재 작업 중인 디렉터리에 "foo"라는 파일을 만드는 코드

int fd=open("foo", O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);

 

open() 루틴은 서로 다른 여러개의 flag를 가진다. 

만약 파일이 존재하지 않으면 (O_CREAT)가 파일을 만든다.

(O_WRONLY)로 파일이 쓰여진다.

만약 파일이 이미 존재한다면, 이를 0 byte 크기로 절단하여 기존 내용 (O_TRUNC)을 제거한다.

(S_IWUSR)은 읽고 쓸 수 있는 파일인지 권한을 나타냄

 

open()에서 중요한 측면은 file descriptor이다. 

file descriptor은 프로세스 당 private 정수이며, UNIX 시스템에서 파일에 접근하는데 사용된다.

따라서, 일단 파일이 열리면, file descriptor을 이용해서 파일을 읽고 쓸 수 있다. (권한이 있을 때)

 

1. file descriptor은 capability이다.

2. file descriptior은 파일 타입의 객체를 가리키는 pointer이다.

   객체가 있다면 read(), write()를 통해 파일에 접근 가능

 

file descriptor은 OS에 의해 관리됨

struct proc {
  ...
  struct file *ofile[NOFILE]; // Open files
  ...
};

파일이 열린 것을 추적하는 간단한 배열이다.

각 배열의 엔트리는 파일이 읽거나 쓰여졌는지 정보를 추적하는데 사용되는 struct file의 포인터이다.

 

39.4 Reading And Writing Files

● 존재하는 파일 읽기

cat이라는 프로그램을 사용하여 파일 내용을 화면에 dump 할 수 있음

prompt> echo hello > foo
prompt> cat foo
hello
prompt>

cat이 foo 파일에 접근 가능한 이유 -> strace라는 Linux의 tool 때문

strace는 프로그램이 실행되는 동안 실행되는 모든 시스템 호출을 추적하고, 화면에 추적을 표시하여 볼 수 있게 함.

 

● cat이 하는 일

1. 읽기 전용으로 파일 열기

  O_RDONLY flag가 나타내는 것처럼 읽기 전용으로 파일이 열린다.

2. 64bit offset을 사용

3. open() 호출 성공하면 file descriptor 리턴 (value=3)

   open을 성공하면 cat은 read() 시스템 콜을 반복적으로 호출하여 파일을 몇 바이트씩 읽는다.

 

read()의 첫 매개변수는 file descriptor이여서 어떤 파일을 읽을지 파일 시스템한테 말한다. 

여러개의 파일을 동시에 읽을 수도 있다.

두 번째 매개변수는 read()의 결과가 놓일 버퍼를 가리킨다.

세 번째 인자는 버퍼의 사이즈이다.

 

read()가 성공적으로 끝나면 읽은 바이트 수를 리턴한다.

 

cat 프로그램은 파일을 더 읽으려고 하는데 파일에 남은 바이트가 없으면 read()는 0을 리턴해서 프로그램에게 파일을 이미 다 읽었다고 알려준다. 따라서, 프로그램은 close()를 호출해서 해당되는 file descriptor을 전달하면서 "foo" 파일이 끝났다고 알린다.

 

* read(), write()를 호출하면 current offset이 업데이트된다.

그렇지 않으면 프로세스는 lseek()를 사용해서 이 값을 바꾸어 파일의 다른 부분을 랜덤으로 접근 가능하다

 

●file에 write하는 단계

1. file을 쓰기 전용으로 연다

2. write() 호출

3. close()

4. 파일에 적은 것을 추적하기 위해 strace 사용, 또는 dd 유틸리티 추적

 

 

39.4 Reading and writing, but not sequentially

파일에 랜덤하게 접근해서 원하는 부분 찾기 -> lseek() 

off_t lseek(int fildes, off_t offset, int whence);

첫 번째 인자: file descriptor

두 번째 인자: 파일의 특정 위치의 file offset 위치

세 번째 인자: 어떻게 탐색할 것인가 결정

 

● Open file table

1. current offset은 파일이 열리면 0으로 초기화 된다.

2. 프로세스에 의해 read()가 증가한다. 

    다음은 지금 offset에 100 더한 곳

    -> 프로세스가 read()를 호출해서 파일의 다음 부분을 얻기 쉽게 한다.

3. 파일 다 읽었는데 read() 시도하면 0 리턴함

 

39.6 Shared file table entries: frok() and dup()

file descriptor이랑 열린 파일 테이블의 엔트리와의 매핑은 일대일 매핑이다.

ex) 프로세스가 실행되면 하나의 파일이 열리는게 결정되고 이것을 읽고 닫는다.

이 예에서는 파일은 open file table에서 유니크하다.

다른 프로세스가 동시에 같은 파일을 읽으면 open file table에서 자신의 엔트리를 가지게 된다.

// Shared Parent/Child File Table Entries
int main(int argc, char *argv[]) {
    int fd = open("file.txt", O_RDONLY);
    assert(fd >= 0);
    int rc = fork();
    if (rc == 0) {
      rc = lseek(fd, 10, SEEK_SET);
      printf("child: offset %d\n", rc);
    } else if (rc > 0) {
      (void) wait(NULL);
      printf("parent: offset %d\n", (int) lseek(fd, 0, SEEK_CUR));
    }
    return 0;
}

파일을 읽고 쓰는 작업이 독립적이고, 각각 다른 current offset을 갖는다.

 

● fork로 공유

위 코드에서 parent 프로세스가 fork()로 child 프로세스를 만들고 wait()을 통해 자식을 기다린다.

자식은 lseek()를 통해 current offset을 조정하고 나간다. 마지막으로 부모는 current offset을 체크하고 그 값을 프린트한다. 이 프로그램을 돌려보면 다음과 같은 결과를 얻는다.

prompt> ./fork-seek
child: offset 10
parent: offset 10
prompt>

file table entry가 공유될 때, reference count는 증가된다. 두 프로세스가 파일을 닫거나 종료할 때만 엔트리가 제거된다.

 

parent와 child 사이에서 open file table entries를 공유하는 것은 때때로 유용하다.

-> 각자 같은 write할 수 있음

 

● dup()으로 공유

dup(): 프로세스가 기존의 descriptor와 동일한 open file을 참조하는 새로운 file descriptor 만드는 것을 허락

// Shared File Table Entry With dup()
int main(int argc, char *argv[]) {
  int fd = open("README", O_RDONLY);
  assert(fd >= 0);
  int fd2 = dup(fd);
  // now fd and fd2 can be used interchangeably
  return 0;
}

 

39.7 Writing Immediately with fsync()

특정 file descriptor에 대해 프로세스가 fsync()을 호출하면 파일 시스템은 지정된 file descriptor가 참조하는 파일에 대해 디스크에 dirty(아직 쓰지 않은) 모든 데이터를 강제로 응답한다.

fsync() 루틴은 이러한 모든 쓰기가 완료되면 리턴한다.

fsync()가 반환되면, 어플리케이션은 데이터가 지속되었다는 것을 알면서 안전하게 이동

 

39.8 Renaming Files

rename("foo.txt.tmp", "foo.txt");  

 

39.9 Getting information about files

특정 파일의 metadata를 보기 위해서 stat() 또는 fstat() system call을 사용한다.

이러한 호출은 pathname을 파일로 가져와 stat 구조체를 채운다.

각 파일 시스템은 일반적으로 이러한 유형의 정보를 inode라는 구조체에 보관한다.

 

39.10 Removing Files

● rm 프로그램 작동시키기

prompt> strace rm foo

... unlink("foo")                                        = 0

unlink()로 지울 파일 이름을 가지고 온다. 성공하면 0을 리턴

 

39.11 Making Directories

● mkdir()

prompt> strace mkdir foo
...
mkdir("foo", 0777) = 0
...
prompt>

하나의 엔트리는 자기 자신 참조, 다른 하나는 부모 참조

 

39.12 Reading Directories

● opendir() / readdir() / closedir()

int main(int argc, char *argv[]) {
  DIR *dp = opendir(".");
  assert(dp != NULL);
  struct dirent *d;
  while ((d = readdir(dp)) != NULL) {
    printf("%lu %s\n", (unsigned long) d->d_ino,
    d->d_name);
  }
  closedir(dp);
  return 0;
}

while문을 통해 동시에 하나의 디렉터리 항목 읽고 디렉터리에 있는 각 파일의 이름과 inode number 출력하기

 

 

39.13 Deleting Directories

● rmdir()

디렉터리 삭제 전에 비어 있어야 함

 

 

39.14 Hard Links

 unlink()를 통해 파일을 삭제하는 이유

link(): 두 개의 인자를 가짐, 예전 pathname과 새로운 것

link()가 작동하는 방식은 링크를 만들 디렉토리에 다른 이름을 간단히 만들고 원래 파일에 동일한 inode 번호를 참조하는 것.

새로운 파일 이름을 예전 것에 "link"할 때, 같은 파일을 참조하는 또다른 것을 만들어야만 한다.

밑은 command-line 프로그램에서 ln이 그 역할을 한다

prompt> echo hello > file
prompt> cat file
hello
prompt> ln file file2
prompt> cat file2
hello

"hello"라는 단어를 가진 파일을 만들고 file을 호출한다.

그 후,ln 프로그램을 사용해서 해당 파일에 대한 hard link를 만든다.

 

unlink() 호출하는 이유?

파일을 만들 때, 두가지 일을 해야한다.

1. 파일 크기, 블록이 디스크에 있는 위치 등 파일에 대한 모든 관련 정보를 추적할 구조(inode)를 만들기

2. 사람이 읽을 수 있는 이름을 해당 파일에 연결하고 해당 링크를 디렉터리에 넣는 것

 

파일의 hard link를 만든 후에는 원래 파일 이름(file)과 새롭게 만든 파일 이름(file2) 사이에 차이가 없다.

둘 다 파일에 대한 metadata에 링크되어 있다.

따라서 unlink()를 호출해 파일 시스템에서 파일을 삭제한다.

inode number 내에서 reference count는 특정 inode에 연결된 다른 파일 이름들이 몇 개인지 추적한다.

 

39.15 Symbolic Links

다른 링크 타입: symbolic link / soft link

● Hard link는 다소 제한적이다:

디렉터리에 하나를 생성 할 수 없다(디렉터리 트리에 싸이클을 생성할까봐)

inode번호는 특정 파일 시스템 내에서만 유일하고 파일 시스템에서는 고유하지 않기 때문에 다른 디스크 파티션의      파일에 hard link할 수 없다.

 

● symbolic links와 hard links 차이점

symbolic link는 파일 자기 자신과 타입만 다른 그 자체이다.

prompt> ls -al
drwxr-x--- 2 remzi remzi 29 May 3 19:10 ./
drwxr-x--- 27 remzi remzi 4096 May 3 15:14 ../
-rw-r----- 1 remzi remzi 6 May 3 19:10 file
lrwxrwxrwx 1 remzi remzi 4 May 3 19:10 file2 -> file

fie은 6byte인데 file2는 4byte인 이유? 

symbolic link 방식은 link file의 데이터로써 링크된 파일의 pathname을 가지기 때문

pathname이 길어지면 사이즈도 커짐

 

sysbolic link가 만들어지면, dangling reference 가능성 생김

 

* 파일 시스템에서 사람이 읽을 수 있는 여러개의 이름을 사용하려면 동일한 기본 파일을 참조하거나 hard links 또는 symbolic links를 사용해야한다.

 

39.16 Permission bit and access control lists

 -rw-r--r-- 

이 비트는 각 정규 파일, 디렉터리 및 기타 엔티티에 대해 접근할 수 있는 것과 방법을 결정한다.

권한은 세 가지 그룹으로 구성된다.

1. 파일의 소유자가 할 수 있는 일

2. 그룹의 누군가가 파일에 할 수 있는 작업

3. 다른 사람들이 할 수 있는 작업

 

39.7  Making and mounting a file system

어떻게 많은 기본 파일 시스템에서 전체 디렉터리 트리를 어셈블하는가

mount 프로그램을 통해할 수 있다.

mount가 하는 일: 기존의 디렉터리를 목표 mount point로 사용하고 기본적으로 새로운 파일 시스템을 그 시점의 디렉터리 트리에 붙여 넣는 것이다.

 

 

 

 

 

 

반응형
Comments