📒 학교 강의를 바탕으로 개인적인 공부를 위해 정리한 글입니다.
디렉토리란 파일의 목록을 갖고 있는 또 다른 형태의 파일.
디렉토리 밑에는 디렉토리와 파일이 있을 수 있음.
디렉토리 안에 있는 파일과 디렉토리의 목록을 'directory entry'라고 함.
파일에 관한 시스템 콜 대부분을 디렉토리에도 쓸 수 있지만, 몇가지 차이점이 있음.
예를 들자면
디렉토리는 open이나 creat 시스템 콜로 생성할 수 없음.
디렉토리를 open할 수 없음.
→ open해서 내용을 볼 수 없음.
→ 디렉토리에 대해 open을 호출하면 errno = EISDIR이라는 에러가 남. "이것은 디렉토리야".
디렉토리는 write로 업데이트 할 수 없음.
→ 디렉토리는 일반 파일이 아니라 특별한 포맷이 정해져 있음.
→ 파일 명과 i-node 엔트리.
→ 커널만이 디렉토리에 write 할 수 있음.

디렉토리는 디렉토리 엔트리들의 연속으로 구성됨.
디렉토리 엔트리에는 디렉토리 밑에 있는 파일과 디렉토리가 하나씩 있음.
디렉토리 엔트리는 위와 같이 i-node 번호와 파일명 또는 디렉토리 명을 담은 character field로 구성됨.
✔ link : hard link를 해주는 명령.

위와 같이 link 시스템 콜을 호출하면
1) xyz라는 새로운 파일의 엔트리를 디렉토리에 새로 추가함. 이때, xyz의 i-node 넘버는 abc와 같음.
2) 235번 i-node가 가리키는 파일의 link count는 2가 됨.
✔ unlink : hard link를 지우는 명령.
link를 지울 때 사용함.
unlink를 하다보면 i-node에 대한 파일 이름이 하나도 남김없이 없어지는 경우가 있음.
→ link count가 0이 되는 경우.
그 파일을 link하는 마지막 link가 삭제되면 그 i-node가 가리키는 파일은 아무 의미가 없음.
그 i-node와 i-node가 가리키는 데이터 블록에 있는 파일이 clear됨.
디렉토리를 생성하면 항상 특별한 엔트리가 만들어짐.
'.' : 현재 디렉토리를 나타냄.
'..' : 현재 디렉토리 위에 있는 부모 디렉토리를 나타냄.

ben의 '.'에 해당하는 i-node 넘버와
book의 '..'에 해당하는 i-node 넘버
memos의 '..'에 해당하는 i-node 넘버가 같음.

2549번 i-node가 가리키는 데이터 블록의 내용을 보면 '.'과 '..'이 있음.
'.'에 해당하는 2549는 자기 자신.
'..'에 해당하는 1267번이 2549번의 부모.
1267번 i-node가 가리키는 데이터 블록의 내용을 보면 '.'과 '..', 'testdir'이 있음.
→ 2549번의 디렉토리 명이 testdir임을 알 수 있음.

cat > fout
왼쪽에 파일 이름이 없는 경우, “키보드”를 의미함.
키보드에서 입력 받은 것을 fout 파일로 write.
^D : end of file
cat > test1.txt
xxxxxx ^D
위의 명령이 실행되면 아래 그림과 같이 변함.

test1.txt가 만들어지면서 디렉토리에 엔트리가 들어감.
엔트리에 들어가면서 i-node가 하나 할당됨.
mkdir tempdir
위의 명령이 실행되면 아래 그림과 같이 변함.

파일에 permission이 있듯이 디렉토리에도 permission이 있음.
똑같이 rwx.
그런데 디렉토리에서의 permission은 파일에서의 permission과 의미가 다름.
✔ read permission
읽을 수 있는 권한이 있다는 것은 똑같음.
디렉토리를 읽을 수 있다는 것은 디렉토리 밑에 있는 파일 이름이나 디렉토리 이름의 목록을 볼 수 있다는 것.
✔ write permission
디렉토리에 write할 수 있는 권한이 있다는 것은
디렉토리 안에 있는 파일이나 디렉토리의 이름을 바꾸거나, 지우거나, 새로 만들 수 있는 권한이 있다는 것.
create, remove, rename.
✔ execute permission(search permission)
파일에서의 execute permission은 실행할 수 있는 권한.
But 디렉토리는 실행 파일이 아님.
디렉토리에서의 execute 권한은 그 디렉토리로 change directory 할 수 있는 권한을 의미함.
디렉토리로 들어가서 그 디렉토리 안에 있는 목록을 볼 수 있는 권한.
execute permission이 없으면 그 디렉토리를 current directory로 잡지 못하고, 그 디렉토리의 목록을 열거하지 못함.
→ 그 디렉토리를 current working directory로 세팅하지 못함.
예를들어, /usr/include/stdio.h 파일을 open하려면 stdio.h 파일까지 가야함.
따라서 stdio.h 위에 있는 /, /usr, /usr/include 디렉토리의 x permission이 전부 다 있고, stdio.h의 r permission이 있어야 저 파일을 open할 수 있음.
앞의 디렉토리들 중에 하나라도 x permission이 없으면 stdio.h를 open할 수 없음.
✔ 디렉토리에 r permission이 있다는 것과 x permission이 있다는 것은 의미가 다름.
💡read permission
그 디렉토리를 읽을 수 있는 permission.
그 디렉토리 밑에 있는 파일들의 목록을 볼 수 있는 permission.
💡execute permission
그 디렉토리로 갈 수 있느냐에 대한 permission.
파일 명을 찾아서 open 하려면 디렉토리로 찾아 가야함.
디렉토리의 execute permission을 search permission이라고도 함.
execute permission bit를 search bit라고도 함.
✔ sticky bit

위의 그림에서 't'에 해당하는 것이 stikcy bit
sticky bit는 파일에서는 특별한 의미가 없었음.
디렉토리에서는 이 sticky bit가 1로 세팅되면 특별한 의미를 가짐.
디렉토리 안에 있는 파일을 삭제하거나 이름을 바꾸는 것은 그 디렉토리에 대해 write permission이 있어야 함.
그런데 sticky bit가 1로 세팅되어 있으면 거기에 조건이 더 붙음.
유저가 write permission 뿐만 아니라 다음중에 하나가 더 있어야 함.
1) 파일의 소유자이거나
2) 디렉토리의 소유자이거나
3) 슈퍼 유저이거나
위의 추가 조건중 하나를 만족해야지만 파일을 삭제하거나 이름을 바꿀 수 있음.
단순히 어떤 디렉토리에 대해 write permission이 있다고 해서 다른 사람이 소유한 디렉토리에 들어와서 마음대로 삭제하거나 이름을 바꿀 수 없음.
→ sticky bit가 세팅되어 있으면 마음대로 할 수 없음.

sticky bit가 세팅되었으면 위의 그림의 위치에 't'가 표시됨.

디렉토리 엔트리는 파일 이름과 i-node 번호로 구성되어 있음.
그것에 관한 struct 구조가 dirent(directory entry).
char d_name[NAME_MAX+1] : 파일 이름은 문자열. 맨 끝에 null character를 위해 +1
d_ino 값에는 음수가 없음.
d_ino 값이 0인 경우는 대응되는 i_node가 없음.
→ empty slot을 의미함.

디렉토리를 만드는 시스템 콜.
성공하면 0, 실패하면 -1을 리턴.
mode_t mode : permission.
mode 값을 777로 준다고 하더라도 mask가 022로 세팅되어 있으면 그룹과 others의 write가 클리어되어 777 → 755로 바뀜.
디렉토리를 생성하면 그 안에 '.'과 '..'은 디폴트로 들어감.
'.'은 자기 자신, '..'은 만들어진 디렉토리의 부모 디렉토리.
💡디렉토리에 permission을 줄 때 주의할 점
그 디렉토리로 change directory 하려면 반드시 x permission이 있어야 함.
따라서 디렉토리에 대해서는 x를 반드시 줘야함.
그래야 change directory 해서 그 안에 있는 목록을 작업할 수 있음.
따라서 디렉토리는 적어도 하나의 x permission이 있어야 함.
💡 예시

mkdir로 dir이라는 디렉토리를 생성함.
cat으로 키보드로부터 입력을 받아 파일을 생성함.
디렉토리의 permission을 666으로 설정함 → rw-rw-rw-
→ x permission이 단 하나도 없음.
→ dir 디렉토리로 change directory를 못함.

그러나 read permission은 있기 때문에 ls 명령을 통해 dir 디렉토리를 조회할 수 있음.
하지만 cat은 수행할 수 없음.
cat을 수행하려면 dir 밑에 있는 file.txt를 search 해야함.
그러려면 dir에 x permission이 있어야만 가능함.

dir의 permission을 111로 바꿈 → --x--x--x
read permission이 없기 때문에 dir을 ls 할 수 없음.
그런데 x permission이 있기 때문에 cat은 할 수 있음.

성공하면 0, 실패하면 -1을 리턴.
빈 디렉토리만 rmdir로 삭제할 수 있음.
'.'과 '..'만 있는 디렉토리가 빈 디렉토리.
디렉토리 안에 파일이 하나도 없어야 함.

디렉토리에 대해서는 open 시스템 콜을 호출할 수 없음.
errno = EISDIR. "이것은 디렉토리야"
opendir은 디렉토리를 open하기 위한 라이브러리.
리턴 타입이 디렉토리의 포인터.
→ FILE*와 비슷함.
opendir을 실패하면 리턴 값이 NULL.
→ NULL 값이 나오는지 체크해야함.

디렉토리를 close 할 때도 close 시스템 콜을 사용하지 못함.
closedir이라는 라이브러리가 존재함.
opendir의 리턴값인 DIR* 값을 인자로 넘겨주면 됨.
💡 사용 예시


디렉토리를 읽을 때도 그냥 read 시스템 콜을 사용할 수 없음.
readdir 라이브러리가 준비되어 있음.
opendir의 리턴 값인 DIR*를 인자로 넘겨주면 됨.
엔트리를 하나씩 읽어들임.
성공하면 struct dirent 값에 대한 포인터가 넘어오고, end of directory거나 실패하면 NULL이 리턴됨.
읽으면 엔트리 하나가 넘어옴.
file offset이 읽은 만큼 이동하는 것 처럼 읽을때마다 directory offset이 다음 엔트리를 읽을 수 있도록 이동함.

디렉토리 엔트리를 하나씩 읽다가 end of entry에 도달하면 더 이상 읽지 못함.
그때 rewinddir을 쓰면 포인터를 디렉토리 엔트리의 처음으로 리셋해줌.
그 다음에 readdir 하면 처음부터 다시 읽을 수 있음.

디렉토리 아래에 있는 파일에서 주어진 접미사로 끝나는 첫번째 파일을 찾는 프로그램.


💡static DIR* dp = NULL;
지역변수는 그 함수 내에서만 사용됨.
함수가 실행될 때 변수의 메모리가 할당되고 그 함수가 리턴되면 변수의 메모리가 해제됨.
지역변수에 static이 붙으면 지역변수라는 것에는 변화가 없는데 변수가 메모리를 함수에 들어와서 할당하는 것이 아님.
→ 함수를 실행하기 전에 미리 할당됨.
static이 없으면 함수 내에서 지역 변수가 어떤 값을 갖더라도 리턴할 때 메모리가 해제되기 때문에 다음에 들어오면 그 값이 없음.
static으로 선언하면 실행하고 나갔다가 다시 들어와도 그 값을 갖고 있음.

s1 : filename, s2 : suffix
현재 작업하고 있는 디렉토리.
유닉스 프로세스는 자신의 current working directory가 세팅되어 있음.
current working directory는 명확하게 유저와 연관되어 있음.
유저가 커맨드를 입력하면 그것이 프로세스가 됨.
그 프로세스의 current working directory는 지금 작업하고 있는 디렉토리.
→ 쉘 프로세스가 유저의 커맨드를 해석하고 있는 디렉토리.
그게 그대로 생성된 프로세스의 current working directory가 됨.
초기에 어떤 프로세스의 current working directory는 그 프로세스가 시작된 쉘의 working directory로 세팅됨.

current working directory를 바꾸는 시스템 콜.
프로세스의 working directory를 바꿔줌.
명령어로는 cd, 시스템 콜은 chdir.
새롭게 바꿔줄 working directory를 인자로 넘겨줌.
프로세스의 working directory를 바꾼다고 해서 쉘이 working directory를 바꿀 필요는 없음.
이 변화는 chdir을 호출한 프로세스에만 적용됨.
쉘과 무관함.
💡chdir을 사용하는 것이 효율적인 예제

open 할 때, 긴 절대 경로 명을 줘야 하는데, 둘이 working directory가 같음.
current working directory를 공통된 부분으로 바꾼 다음에 상대 경로명을 주면 짧아짐.


current working directory를 리턴해주는 라이브러리.
성공하면 cwd가 인자로 넘어간 name에 세팅됨.
cwd를 확인하기 위한 커맨드 명령어는 pwd.
cwd의 경로명을 포인터로 리턴해줌.
cwd의 이름이 name이라는 변수가 가리키는 배열로 복사됨.
💡name allocation problem
구현이 시스템에 의존적임
→ 시스템마다 버전이 조금씩 다름.
💡사용 예시

프로그램이 실행되면 디스크에 업데이트가 됨.
라이브러리를 이용해서 I/O를 하다가 아직 디스크에 write 되지 않은 것들이 라이브러리의 메모리 버퍼에 남아있는데 전원이 나가면 한참 작업한게 날라감.
이러한 문제를 해결하기 위해 주기적으로 버퍼에 있는 내용을 갱신시킴.
"flush out"
버퍼에 있는 메모리를 디스크로 다 write 해버림.
이를 위한 함수가 sync와 fsync.
✔ sync
"파일 시스템 전체"에 대해 메인 메모리에 있지만 아직 디스크에 업데이트 되지 않은 것들을 write함.
✔ fsync
"특정한 파일 하나 하나"에 대해 버퍼에 있는 내용을 디스크에 다 write함.
이 두 함수를 통해 갑자기 전원이 나갔을 때 작업한 내용이 디스크에 업데이트 되지 않는 사고를 방지함.

sync는 파일 시스템 전체에 대한 것이기 때문에 인자가 없음.
fsync는 특정한 파일에 대한 것이기 때문에 file descriptor를 인자로 받음.
fsync는 지정된 파일의 모든 데이터가 디스크에 다 쓰일 때까지 리턴되지 않음.
sync는 시스템 전체에 대한 것. 모든 데이터의 writing이 스케쥴되면 완료되지 않았더라도 즉시 리턴됨.
유닉스 OS 차원에서 주기적으로 sync를 반복해서 호출함.
✔ 메이저 넘버와 마이너 넘버
유닉스에서 디바이스는 특정한 디바이스 넘버로써 접근됨.
이 디바이스 넘버는 '메이저 넘버'라고 함.
메이저 넘버는 디바이스의 타입에 따라 붙여짐.
같은 종류의 디바이스에 대해서는 같은 드라이버가 쓰임.
→ 똑같은 디바이스가 여러개 있더라도 디바이스 드라이버는 같음.
같은 종류의 디바이스들도 서로 구분할 수 있어야 함.
이를 위한 것이 '마이너 넘버'.
마이너 넘버는 특정 디바이스에 붙는 것.
어떤 디바이스를 지정하려면 메이저 넘버와 마이너 넘버가 모두 필요함.
메이저 넘버를 이용해 특정 장치의 디바이스 드라이브를 찾음.
마이너 넘버를 이용해 몇번째 장치인지 구분함.
✔ 디바이스 파일
/dev 밑에 있음.
디바이스 파일에 대한 i-node를 보면 st_dev와 st_rdev가 있음.
예를 들어, 프린터 디바이스 파일(/dev/printer)가 있음.
이 프린터의 메이저 넘버와 마이너 넘버는 st_rdev에 있음.
이 디바이스 파일은 파일 시스템에 저장되어 있음.
파일 시스템은 디스크 안에 있음.
디스크에 있는 어떤 파일 시스템에는 printer라는 디바이스 파일이 있음.
이 디바이스 파일이 있는 디스크 장치는 프린터와는 다른 장치임.
이 디스크 장치의 메이저 넘버, 마이너 넘버는 프린터의 메이저 넘버, 마이너 넘버와 다름.
디스크 장치의 메이저 넘버와 마이너 넘버는 st_dev에 있음.
💡정리
실제 디바이스 파일의 메이저 넘버와 마이너 넘버는 rdev에 있음.
디바이스 파일 시스템이 저장되는 디스크의 메이저 넘버와 마이너 넘버는 dev에 있음.
주변 장치(peripheral device)를 파일로 간주함.
디바이스 파일들은 /dev 밑에 다 모여있음.

cat를 통해 파일을 디바이스로 곧장 출력할 수 있음.
💡사용 예시

✔ block 디바이스 파일
디스크, 마그네틱 테이프 등.
I/O가 블록 단위로 이루어짐.
블록은 보통 4k
디스크는 랜덤 엑세스가 가능함.
테이프는 랜덤 엑세스가 불가능함. 시퀀셜 엑세스.
✔ character 디바이스 파일
터미널, 모뎀, 프린터, 키보드 등.
대게 랜덤 엑세스가 불가능함.
될 수도 있고 안될 수도 있음.

stat(pathname, struct stat* sb)
→ 이 파일에 대한 i-node의 정보를 가져옴.
✔ st_mode

permission 정보.
총 16 bit(2 byte)로 구성됨.
special bit와 permission bit는 비트 하나당 각각의 의미를 갖기 때문에 비트 패턴을 통해 어떤 값이 있는지를 알 수 있음.
그런데 파일의 종류는 7가지이지만 파일 타입은 4 bit임.

따라서 한 비트에 파일 타입이 하나씩 할당되지 않고, 4 bit의 비트 패턴으로 7개의 파일 타입을 표현함.
어떤 파일인지 알 수 있는 4 bit의 비트 패턴 매크로가 정의되어 있음.
💡사용 방법
if(S_ISREG(sb.st_mode))
sb.st_mode에 들어있는 파일 타입이 레귤러 파일이라면 위의 조건문이 true가 됨.
이런 식으로 특정 파일의 타입을 체크할 수 있음.

또는 아래와 같은 방법으로 파일의 타입을 체크할 수 있음.

stat -> st_mode & S_IFMT
st_mode에서 파일 타입 위치만 딱 잘라내는 것.

위의 'S_'로 시작하는 값들은 <stat.h>에 상수로 정의되어 있음.
✔ st_rdev
real 디바이스의 메이저 넘버와 마이너 넘버가 들어있음.
레귤러 파일은 디바이스가 아님.
디바이스 파일 = character special 파일 + block special 파일
따라서 오직 character special 파일과 block special 파일만 st_rdev 값을 가짐.
편리한 비트 조작을 위한 major, minor 매크로가 준비되어 있음.
이것들을 쓰려면 <sys/types.h>를 include 해줘야 함.
💡사용 예시

✔ st_dev
디바이스 파일이 저장된 디스크의 메이저 넘버와 마이너 넘버가 들어있음.