수업/System Programming

[System Programming] File Systems 공부

hw-ani 2022. 10. 31. 23:46

Linux File Systems는 files과 directories의 collection이다.

우선 directory든 file이든 모두 Hard Disk에 저장된다.

Hard disk는 Sector라는 기본 unit으로 나뉘어진다. 우리는 각 Sector에 번호를 부여함으로써 마치 하드디스크를 마치 큰 배열처럼 사용할 수 있고, 그런 점을 이용해 Linux의 파일 시스템은 아래와 같은 Layout을 띄게 된다.

 

1) The Supuer Block

: File system 자체의 구조에 대한 정보를 담고 있다. ex. 각 area의 크기, 사용되지않는 data blocks의 위치 등등

2) The Inode Table

: `struct inode`에 각 file의 정보(data 위치, size, UserID, ...)를 담아서 이 Inode table 배열에 들어간다. 즉, The Inode table은 그냥 배열이다. 모든 inode는 같은 size를 가진다.

3) The Data Area

: File들의 실제 contents가 들어가는 자리이다. inode를 보면 해당 file의 data가 이 data area의 어디에 있는지 나와있다. 모든 block의 size는 같다.

 

 

우리가 file을 만들때 일어나는 과정은 아래와 같다.

1. free inode를 할당받아 만들고자 하는 file의 properties를 저장한다. 이때 file name은 저장되지 않는다. ex) 47번 inode에 저장

2. free blocks를 할당받아 그곳에 file의 데이터를 저장한다. ex) 113, 547 영역

3. inode에 할당받은 block 영역을 기록한다.

4. Directory file에 Filename과 해당 inode를 추가한다.

 

 

즉, 특정 file을 지칭한다는 것은 그 file의 이름을 말하는 것이 아니라, 그 file의 inode 번호를 말하는 편이 더 정확하다.

Directory도 결국 file이다. 그리고 file name은 directory file 내에 inode와 연결돼 기록된 정보에 불과하다.

(directory도 결국 file이다. 그리고 이 file에는 이 directory가 가지는 file의 inode와 그 inode에 해당하는 filename이 기록돼있을 뿐이다. 이런 의미에서 directory file이라고 말한 것이다.)

(inode를 보고싶다면 `ls`에 `-i` 옵션을 주면 된다.)

(실제로 directory file을 vi 같은 걸로 열어보면 inode 번호는 안 보일 수도 있는데, directory file은 binary file이라 vi 같은 텍스트 에디터로 열어서 그런 것일 수 있다. debugfs 같은 툴을 써야 볼 수 있다고 하네.)

 

어떤 file이 어떤 directory 안에 있다는 것은, 그 directory 파일 내에 그 file의 inode와 지정된 file name이 저장돼있단거다.

실상은 파일이든 디렉터리든 위 그림대로 배열 하나가 전부인데, 우리가 그 안에 directory라는 파일을 만들어서 트리 구조가 되도록 재구성 한 것일 뿐이다.

 

참고로 file system은 어떤 프로그램이 아니라 개념적인 것이다. 즉, NTFS 같은 건 프로그램이 아니라 파일을 관리하고 조직하기 위한 시스템을 설명하는 규칙, 체계인 것이다. 뭐 하드디스크 연결되면 메모리 올라가서 실행되는 그런 일반적인 프로그램이 아님! 개념적인거임!

 

또 하나 더 참고로 vi 같은 거로 디렉터리인 "." 같은 걸 열어봐도 inode 번호같은 건 안 보인다. 참고의 글이나 댓글을 보면, 그게 커널에서 유저에게는 잘 정돈되게 보이게 하려고 그렇게 보인다는 것 같다. 실제 raw 한 데이터가 아니라 정돈된 데이터를 보여준다는 듯! 댓글에 보면 초기 Unix에선 디렉터리 파일의 raw한 데이터를 그대로 볼 수 있었지만, 이후 디렉터리를 직접 못 읽게 됐다고 함! 내용에도 보면 사용자 공간에는 정돈된 API를 제공해주고, vim도 그런 것을 통해 디렉터리를 읽어오려 하기 때문에 자세한 내용을 읽는 게 제한된다는 것! 그렇게 raw한 부분을 노출하지 않는 이유는 뭐 글에 이것저것 나와있는데 그게 중요한 게 아니라 왜 `vi .`을 하면 inode가 안 보이는지 궁금했던거라... 궁금증 해결!

 


mkdir 명령어 : mkdir() 함수를 사용해 구현 (아래는 mkdir() 함수 작동 과정)

1) directory의 inode를 만든다.

2) 이 directory의 내용을 위한 disk block을 할당받는다.

3) "."과 ".."에 대한 정보를 추가한다.

4) parent directory에 이 directory file의 link를 추가한다.

 

rmdir 명령어 : rmdir() 함수를 사용해 구현

1) directory는 비어있어야 한다.

2) directory tree에서 이 directory node를 삭제하고

3) inode와 data를 free.

 

cd 명령어 : chdir() 함수를 사용해 구현

 

rm 명령어 : unlink() 함수를 사용해 구현

1) directory file에서 해당 파일과 inode 연결 정보를 삭제한다.

2) 삭제할 file의 inode의 link counts를 줄인다.

3) 만약 link count가 0이라면 data 영역과 inode를 free.

 

ln 명령어 : link() 함수를 사용해 구현

1) inode number와 file name을 추가할 directory file에 작성한다.

2) 같은 file name이 존재할 순 없다.

3) inode의 link count++도 해야될듯(자료에 없는 말이긴 함)

 

ln -s : 일반 ln 명령어는 hard link를 만들지만, `-s` 옵션을 주면 symbolic link를 만든다.

hard link는 inode를 그대로 베껴오므로 기존 파일이 삭제돼도 이용하는데 전혀 문제가 없지만,

symbolic link는 기존 파일을 가리키는 방식으로 연결 되기 때문에 기존 file이 삭제되면 이용할 수 없다. (file type이 'l'로 표시됨)

hard link 파일은 inode가 기존 파일과 같지만, symbolic link 파일은 inode가 기존 파일과 다르다.

 

mv 명령어 : rename() 함수를 사용해 구현

1) 기존 link의 inode와 새로운 name을 연결해 지정 directory file에 추가(작성)한다.

2) 기존 link 정보를 directory file에서 삭제한다.

(이렇게 구현돼서 다른 directory로 move도 되고, 같은 directory에서 rename도 됐던 것)

mv 명령어는 "file_name dir_name" 순이거나 "file_name1 file_name2" 순 밖에 안된다. 전자는 해당 file을 해당 directory로 옮기는 것이고, 후자는 두 파일 위치가 다를 경우 옮겨지지만 같을 경우 이름이 바뀐다.

 


Linux/Unix에선 주변 Device도 모두 File로 다룬다. (우리는 그렇게 쓰면 된다. 커널에서 파일처럼 쓸 수 있게 알아서 잘 처리해주겠지)

`/dev` 에 가면 devices를 나타내는 file들이 저장돼있다.

device에는 character deviceblock device라는 것이 있는데, 전자는 character 단위로 소통하는 device(ex. Terminals, serial ports, ...)를 말하고, 후자는 block 단위로 소통하는 device(ex. HDD, SSD, ...)을 말한다.

뭐 어쨌든 이런 device file을 열어서 `write` 같은 system call로 file에 작성하면 된다. 그럼 원하는 terminal에 글을 쓰거나 할 수도 있음.

 

 


Signal (간단하게 정리)

signal이란 process들 간의 통신 수단이다.

예전에 공부할땐 이게 오류/예외 처리 수단인 줄 알았는데, 오류를 signal로 보내는건 결국 통신 신호 중 하나였을 뿐이다.

 

`kill -n pid` 명령어 : 특정 pid를 가지고 실행 중인 process에 n번 signal을 보낸다. (signal을 보낼뿐 어떻게 처리할지는 process에 따라 다르다.)

`kill -l` 명령어 : signal 목록을 보여준다.

`ps -aux` 명령어 : 현재 실행중인 프로세스 목록을 보여준다. 여기서 pid 정보를 받을 수 있다.

 

`void (*signal(int signum, void (*handler)(int)))(int)` 함수 : 특정 signal을 어떻게 다룰지 나만의 handler 함수를 지정할 수 있다. 이전 signal handler 함수 포인터를 반환함.

`kill()` 함수 : kill 명령어와 비슷하게 특정 pid를 가지고 실행중인 process에 n번 signal을 보낸다. (명령어 버전과 인자 순서는 좀 다르니 주의)

`sleep()` 함수 : 지정된 n초 동안 프로세스 동작을 멈춘다.

`alarm()` 함수 : 지정된 n초 후에 SIGALRM signal이 발생하도록 세팅한다. sleep 처럼 n초간 멈추는게 아니라 n초 후에 발생하도록 지정해두고 아래 코드는 계속 실행된다.

`pause()` 함수 : 해당 지점에서 멈춘 후, signal이 발생할때까지 기다린다. signal 발생하면 다음을 실행한다.

 

 

signal handler 안에서 또 다시 시그널이 발생하면, 그 signal은 해당 handler가 종료될때까지 무시되다가 handler가 끝나고 해당 시그널의 핸들러가 호출된다. 즉, 계속 signal이 재귀호출되며 무한 loop를 돌 일은 없다.
또 signal 발생 유무는 비트로 판단하므로, 저렇게 signal handler에서 다시 handler가 호출된 경우 그 사이에 여러번 signal을 발생시켜도 하나만 인식된다.
즉, 한번 signal이 발생해서 handler로 들어갔는데, 그 handler에서 3번 시그널이 10번 발생하면 일단 무시하고 진행하다가, 1번 signal handler가 종료된 후 3번 시그널 핸들러는 한번만 호출된다.

 


메모)

Symbolic link는 Hard link와 달리 원본 파일과 다른 inode 번호를 가진다. 즉, 아예 새로운 파일이란 것이다.(Hard link는 이와 달리 아예 같은 inode 번호를 가지고 이름만 다른 거였다.)

그럼 그 새로운 파일에는 어떤 내용이 들어있을까? -> 보통 그냥 연결된 파일의 경로가 적혀있다고 한다. 참고1 참고2

(Hard link는 곧바로 연결돼있어서 오버헤드가 적고, Symbolic link는 오버헤드는 조금 있지만 뭐 예를들어 원본 파일이 변경되거나 했을 때 Symbolic link들도 따라서 변경된다는 장점이 있다고 한다. Hard link는 하나만 바꾸면 나머지 Hard link들은 계속 예전 파일을 가리키고 있게 된다고 한다!!)