Files and Directories - 시스템프로그래밍

Files and Directories - 시스템프로그래밍

Tag
Computer Science Engineering
System Programming

Files and Directories

  • OS는 physical한 hard disk로 file systems을 관리한다.
    • 의미있는 정보를 파일 단위로 관리한다.

File System

  • 파일과 attributes ( 파일의 이름, 경로… ) 의 집합체이다.
  • 디스크 상에 저장되어있는 파일의 physical한 위치를 사용자가 직접 정하는 것이 아닌, filename과 offset ( inode 번호 ) 를 표현함으로써 파일의 위치를 지정한다.

Directory

  • 파일의 한 종류이며 다른 파일, 서브 디렉터리를 포함할 수 있는 기능을 가진다.
  • directory entries ( filename, offset pair들의 집합 ) 을 가진다.
* ls 옵션에 -i를 주게 되면 offset (inode 번호) 를 조회할 수 있다.
  • 대부분의 파일 시스템은 디렉터리를 트리 구조로 가지고 있다.
 

Tree Structure of a File System

notion image
 

Directory

  • Root directory
    • /
    • 파일 시스템 트리의 제일 상단
    • 다른 모든 것은 루트 디렉터리 하단에 존재한다.
  • Parent directory
    • ..
    • 현재 디렉터리 기준의 부모 디렉터리를 나타낸다.
  • Current working directory
    • .
  • Home directory
    • 로그인을 해서 터미널을 켜게 되면 default로 들어가게 되는 디렉터리
    • alias: ~
  • Sub-directory
    • 다른 디렉터리에 포함되어 있는 디렉터리
 

Pathname

  • 절대경로 ( Absolute path / fully qualified pathname )
    • 루트 디렉터리를 기준으로 한다.
    • ex) /dirA/dirB/my1.dat
  • 상대경로 ( Relative pathname )
    • 현재 디렉터리를 기준으로 한다. ( / 로 시작하지 않는다. )
    • ex) 위의 트리 구조 상에서 현재 작업 디렉터리가 dirA일 때, ../dirC/my3.dat
 

Pathname example

위의 트리구조를 예시로 하여, 현재 작업 directory가 /dirA/dirB 라 하자.
dirA 디렉터리에 있는 my1.dat를 나타내고 싶은 경우를 세 가지 방법으로 표현해라.
  1. /dirA/my1.dat
  1. ../my1.dat
  1. ./../my1.dat
 
경로를 표현할 수 있는 방법은 3가지가 아닌 무한대가 가능하다.
꼭 짧게 표현해야 하는게 아니니까 ../dirA/../dirA/ 와 같이 표현이 가능하다.
 

Change Directory

#include <unistd.h> int chdir(const char *path);
  • 현재 작업 디렉터리를 변경할 수 있다.
  • path
    • 이동하려고 하는 작업 디렉터리
  • Return 값
    • 성공했을 경우 0
    • 실패했을 경우 -1
  • chdir함수는 현재 프로세스의 working directory에만 영향을 끼친다.
 

Example - mychdir.c

#include <unistd.h> int main(void){ char *directory = "/tmp"; if(chdir(directory) == -1){ perror("Failed to change current working directory to /tmp"); } }
 

Get Current Directory

#include <unistd.h> char *getcwd(char *buf, size_t size);
  • getcwd 함수는 현재 작업 디렉터리의 pathname을 반환한다.
  • buf
    • output parameter 역할. 현재 작업 디렉터리를 받기 위한 역할
  • size
    • buf의 크기를 지정
  • Return 값
    • 성공했을 경우 buf의 포인터 값 ( 주솟값 )
    • 실패했을 경우 NULL
 

Buffer size

  • getcwd 함수를 호출할 때, 버퍼의 크기를 얼만큼 줘야 할까?
  • 시스템에서 반환할 수 있는 최대 경로의 길이를 알아야 정확한 버퍼의 크기를 설정할 수 있을 텐데, 그것을 알아낼 방법이 쉽지 않다.
  • PATH_MAX 라는 constant 값이 시스템 상에서 설정되어있는 경우가 있다.
    • 정의가 되어 있지 않은 경우도 있으니 확인이 필요하다.
 

Example

#include <limits.h> #include <stdio.h> // 시스템상에서 PATH_MAX 값이 정의되어 있지 않다면, PATH_MAX 값을 255로 설정한다. #ifndef PATH_MAX #define PATH_MAX 255 #endif int main(void){ char mycwd[PATH_MAX]; if(getcwd(mycwd, PATH_MAX) == NULL){ perror("Failed to get current working directory"); return 1; } printf("Current working directory: %s\n", mycwd); return 0; }
 

Search paths

  • 시스템 안에 있는 임의의 파일을 액세스하고 싶은 경우, 타겟 파일의 경로를 OS에게 알려주어야 한다.
    • 하지만 linux에서 ls만 실행하여도 작동이 제대로 되는 경우가 있다.
  • system 환경 변수 중 PATH에 등록된 디렉터리 들을 순서대로 확인한다.
    • .profile / .bashrc / .zshrc 등..
  • which 명령어를 사용하게 되면 실행 파일의 절대경로를 알 수 있다.
    • ex) which ls
  • PATH 환경변수에 . 을 맨 처음에 추가하는 경우 보안문제 상 위험할 수 있다.
    • ex) 해커에 의해 홈 디렉터리를 해킹당했을 때, 홈 디렉터리에 ls 파일을 심어놓았다고 가정하자. ls를 실행할 때, ls 파일을 찾기 위해 PATH 환경 변수를 확인할 것이다. 그 때 처음으로 설정되어있는 것이 현재 작업 디렉터리이기 때문에 해커가 심어놓은 파일이 돌아갈 것이다.
 

Directory Access

  • opendir
  • closedir
  • readdir
 

Directory open

#include <sys/types.h> #include <dirent.h> DIR *opendir(const char *dirname);
  • dirname
    • 열고 싶은 directory의 이름
  • Return 값
    • 성공했을 시 오픈된 디렉터리 정보 목록을 가지고 있는 data-structure의 포인터 값. 즉, DIR 포인터 값
    • 실패했을 시 NULL
      • 권한이 없거나, 디렉터리가 존재하지 않았을 때
  • DIR type
    • dirent.h 에 정의되어 있다.
    • directory stream을 표현한다.
  • Directory stream
    • 타겟 디렉터리 안에 들어있는 모든 directory entry 의 나열된 정보
    •  
즉, Directory entry는 파일의 이름, inode 값의 쌍. Directory stream은 directory entry들의 sequence 정보. DIR은 directory stream을 나타내는 data-structure.
 

Directory read

#include <sys/types.h> #include <dirent.h> struct dirent *readdir(DIR *dirptr); // example while(readdir(...) != NULL){ }
  • Return 값
    • 성공했을 시 호출 시마다 struct dirent pointer를 계속해서 하나 씩 반환한다
      • 반환하고 하나씩 offset이 이동
      • 반복적으로 호출하여 탐색하는 방식으로 사용한다.
    • 실패했을 시 NULL
  • struct dirent
    • 여러개의 정보를 담고 있는 객체 ( 파일 이름, inode 번호 등을 담기 위해서 구조체를 사용 )
 
struct dirent{ ino_t d_ino; /* inode number */ char d_name[256]; /* Null-terminated filename */ off_t d_off /* Not an offset */ unsigned short d_reclen; /* Length of this record */ unsigned char d_type; /* Type of file; not supported by all filesystem types */ }

Directory close and rewind

  • closedir system call
    • #include <dirent.h> int closedir(DIR *dirptr);
    • Return 값
      • 성공했을 시 0
      • 실패했을 시 -1
      •  
  • rewinddir system call
    • #include <sys/types.h> #include <dirent.h> void *rewinddir(DIR *dirptr);
    • readdir의 경우 반복적으로 호출하면서 directory stream 내부에서 offset이 하나씩 옮겨진다고 이야기했었다.
    • rewind는 이 offset, 즉 내부 포인터 를 처음으로 되돌려주는 역할을 하게 된다.
      • notion image
 

Example - showname.c

해당 예시는 ls의 역할을 해주는 코드이다.
#include <dirent.h> #include <errno.h> #include <stdio.h> int main(int argc, char *argv[]) { struct dirent *direntp; DIR *dirp; if (argc != 2) { fprintf(stderr, "Usage: %s directory_name\n", argv[0]); return 1; } if ((dirp = opendir(argv[1])) == NULL) { perror ("Failed to open directory"); return 1; } while ((direntp = readdir(dirp)) != NULL) printf("%s\n", direntp->d_name); while ((closedir(dirp) == -1) && (errno == EINTR)); return 0; }
 

File status information

  • lstat, stat 함수가 제공된다.
    • 이름을 통해 특정 파일을 access할 수 있다. ( status 정보를 알기 위함 )
#include <sys/stat.h> int lstat(const char *restrict path, struct stat *restrict buf); int stat(const char *restrict path, struct stat *restrict buf);
  • buf
    • output 파라미터
  • Link
    • 바로가기 아이콘 이라고 생각하면 된다.
 
  • 함수를 사용할 때, path에 일반 파일 경로를 주게 되면, 그 파일의 status information을 buf에 넣어 준다.
  • path에 링크 ( symbolic link ) 를 주게 되면?
    • lstat: 링크 파일의 status information을 반환
    • stat: 원본 파일의 status information을 반환
 

struct stat structure

inode 안에 정보들이 들어가있을 것이다.
자세하게 알고 싶다면 man inode 명령어를 통해 알아보도록..
struct stat { // Device ID dev_t st_dev; // inode 번호 ino_t st_ino; // 파일의 모드. 파일의 권한 정보, 타입 (regular, directory, special) 등등.. mode_t st_mode; // count 값. hard link의 개수 nlink_t st_nlink; // 파일의 유저 아이디 uid_t st_uid; // 파일의 그룹 아이디 gid_t st_gid; // 파일의 크기 정보가 바이트 단위로 담겨있음 off_t st_size; // 마지막 access 시간 // access => 읽는 것, 혹은 system call을 통해서 파일을 access하는 경우 // time type은 long 타입. 추후에 더 알아볼 것 time_t st_atime; // 마지막 수정 시간 time_t st_mtime; // 마지막 status information이 변경된 시간 time_t st_ctime; }
 

Example - printaccess.c

#include <stdio.h> #include <time.h> #include <sys/stat.h> void printaccess(char *path) { struct stat statbuf; if (stat(path, &statbuf) == -1) perror("Failed to get file status"); else printf("%s last accessed at %s", path, ctime(&statbuf.st_atime)); }
 

Determining the type of a file

  • st_mode 필드는 파일의 권한 정보, 파일 타입 등을 포함하고 있다고 설명하였다.
  • 아래의 예시는 해당 필드를 사용해서 타겟 파일이 디렉터리인지 아닌지를 판별하는 예시이다.
#include <stdio.h> #include <time.h> #include <sys/stat.h> int isdirectory(char *path) { struct stat statbuf; if (stat(path, &statbuf) == -1) return 0; else // mode 타입도 복잡하기 때문에 매크로를 제공해준다. // S_ISDIR 매크로는 st_mode를 통해 해당 파일이 디렉터리 인지를 확인해준다. return S_ISDIR(statbuf.st_mode); }
 

UNIX File Implementation

  • UNIX 시스템은 파일의 타입 ( ex. 확장자 ) 을 크게 구분하지 않는다.
    • OS가 파일의 타입을 확인하기 보단, 사용자가 파일의 타입을 확인하기 위함.
  • 특별한 실행 파일이 아니라면, 따로 규정하는 것은 있지 않다.
  • 하지만 UNIX 파일 시스템은 modified tree structure 상에서 구현이 된다.
  • Directory entries에는 filename, inode가 포함되어 있다. ( 상단에서 자세히 봐 보도록 하자. )
 

inode

  • 파일이 하나 생성되면, 파일의 정보 ( meta information / status, contents )를 찾아갈 수 있는 객체가 함께 생성된다. 이것을 inode라 함
  • 각각의 파일은 inode를 가지고 있으며, unique한 id인 inode number를 가진다.
  • 파일의 inode에 저장하는 정보의 예시
    • user and group ownership
    • 권한 정보
    • 파일 타입
  • 시스템에서 만들 수 있는 inode의 개수는 한계가 있다.
    • 파일 시스템이 가질 수 있는 파일은 한계가 있다.
    • file의 크기가 어떻든, inode의 크기는 고정이 되어있다. ( 파일의 contents가 직접 들어가있는 것은 아니기 때문 )
 

Schematic structure of a traditional UNIX file

notion image
information, 파일의 contents block을 가리키는 pointer
  • direct pointer
    • 직접 파일 컨텐츠 블럭을 가리키며, 컨텐츠를 바로 볼 수 있다.
  • indirect pointer
    • 간접적으로 파일 컨텐츠 블럭을 가리킨다.
    • 존재의 이유?
      • 파일의 크기가 큰 경우 ( 많은 컨텐츠 ) 에는 direct pointer 만으로 표현하기에 부족하다.
    • 종류
      • single indirect pointer
        • 한 단계 거쳐서 파일 컨텐츠 블럭을 가리킴
        • single indirect pointer → direct pointer block → file block
      • double indirect pointer
        • 두 단계 거쳐서 파일 컨텐츠 블럭을 가리킴
        • double indirect pointer → single indirect pointer block → direct pointer block → file block
      • triple indirect pointer
        • 세 단계 거쳐서 파일 컨텐츠 블럭을 가리킴
        • triple indirect pointer → double indirect pointer block → single indirect pointer block → direct pointer block → file block

Directory implementation

  • 디렉터리는 파일의 종류이며, directory entry 정보를 가지고 있는 파일이라 할 수 있다.
    • 즉 file name / file location ( inode number ) 을 pair로 가지고 있다.
  • directory entry = inode number + file name
  • 사용자가 inode를 access하는 방법
    • pathname을 통해서 접근하고 싶은 파일을 지정한다.
    • OS가 file system tree를 찾아서 경로명의 끝에 찾아가게 되면, directory entry를 찾아갈 수 있는 것이고, 그 파일의 inode number를 알게 된다.
    • inode 객체를 찾아가서 status information, contents를 access할 수 있게 된다.
 

Advantages of inode+filename

  • 파일 명을 변경을 할 때에 간단하다. directory entry 안에 들어가 있기 때문
  • 파일이 속한 경로를 같은 파티션 내에서 변경해야할 경우 directory entry만 옮기면 된다.
    • 파일을 옮기는 것은 금방 끝나지만, 복사는 오래걸리는 이유
  • 디스크 상에 실제 파일 block은 하나일 지라도, 그 파일을 나타내는 이름은 여러개가 있을 수도 있다.
    • ⇒ Link의 구현 방식. directory entry의 inode number가 같게 만들면.
  • directory entry의 크기는 작다. 대부분의 정보는 inode 객체에 들어있기 때문이다.
 

Exercise ( inode pointers )

  • inode 객체의 전체 크기가 128 bytes라고 하자. 포인터는 크기가 4bytes이며 status information은 68 bytes를 차지한다.
  • 하나의 block의 크기는 8K bytes이며, block pointers의 크기는 32 bits ( 4 bytes ) 이다.
 
  1. 하나의 inode 객체 안에 direct pointers는 몇개가 있을 수 있는가?
    1. indirect pointer는 3개. 즉 12bytes를 차지한다.
    2. direct pointers가 차지할 수 있는 크기는 128 - 68 - 12 = 48 bytes
    3. 총 개수는 48 / 4 = 12
  1. direct pointer block을 통해서 얼마나 큰 파일 block을 가리킬 수 있는가?
    1. 블록 하나 당 8KB. 12 * 8KB = 96KB
  1. single indirect pointer는 어떠한가?
    1. single indirect pointer는 direct pointers를 가리킨다.
    2. 가리키는 direct pointer block 안에 direct pointer의 개수를 구해야 한다.
    3. 포인터는 4bytes. 8KB ( 블럭 사이즈 ) / 4bytes = 8 X 2^10 / 4 = 2048
    4. 즉 direct pointer 개수는 2048개 이다.
    5. 2048 ( direct pointer의 개수 ) * 8KB ( 블럭의 크기 ) = 16MB
 

Hard Links and Symbolic Links

link는 filename과 ( inode number가 아닌 ) inode 사이의 연관 관계이다.
  • UNIX 시스템에는 두 가지 종류의 Link가 있다.
    • hard
    • symbolic ( soft )
  • Directory entry는 사실 hard link를 표현한 것이다.
    • → Why? directory entry는 filename을 inode로 직접 link시켜주기 때문이다.
      ⇒ 그렇기 때문에 파일을 생성하면 hard link가 생성된다.
    • 파일을 추가하는 것 제외하고도 hard link를 또 만드는 방법
      • directory entry 하나를 추가하는 것
  • Symbolic (Soft) link
    • 원본 파일로 가는 경로명을 가지고 파일이다.
      • symbolic link는 inode가 별도로 있는데, 해당 Inode에는 원본 파일의 경로명이 들어 있다.
      • 원본 파일의 경로를 다시 접근하여 해당 파일의 inode로 접근
  • 각 inode는 본인을 가리키는 hard link의 개수가 존재한다. ( st_nlink )
  • 파일이 생성되면, 새로운 directory entry가 생성되고, inode가 assigned
 

Hard Link

  • inode를 찾아갈 수 있는 직접적인 포인터 정보를 가지고 있는 directory reference
  • 파일명은 label이다.
    • 같은 파일을 가리키는 하드 링크가 여러개일 수 있고, 이름이 여러개일 수 있다. ( 다른 디렉터리에 위치할 수도 있다. )
  • 하드 링크가 여러 개 일때 서로 다른 이름을 통해서 파일을 access 할 때, 변경된 사항은 똑같다.
    • 같은 파일을 가리키고 있기 때문이다.
  • 하드 링크는 같은 파일 시스템 안에 있는 데이터만 참조할 수 있다.
 
  • 새로운 하드 링크를 생성하게 되면, 새로운 directory entry가 추가되는 것이다.
    • shell command: ln
    • system call: link
    • hard link가 생성될 때마다 link count 값이 증가한다.
    • hard link를 삭제할 때에는
      • shell command: rm
      • system call: unlink
      • link count가 감소한다.
      • link count가 0이 되면 inode number가 가리키는 inode를 삭제한다.
 

Link Counter

  • 대부분의 파일 시스템은 hard link라는 기능을 support 하며, link counter 값을 inode에 갖고 있다.
    • Integer value
    • integer 값은 링크의 총 개수를 나타낸다
  • 만약 새로운 링크가 추가되면 1 상승
  • 만약 링크가 삭제되면 1 감소
  • link count가 0이 되면 inode를 삭제한다.
 

Hard Link APIs

#include <unistd.h> int link(const char *path1, const char *path2); int unlink(const char *path);
  • link
    • path1: 원본 파일의 경로명
    • path2: 새로 만들어질 하드 링크의 경로명
  • unlink
    • path: 삭제할 하드 링크의 경로명
 

Example

ln /dirA/name1 /dirB/name2
 
#include <stdio.h> #include <unistd.h> if (link(“/dirA/name1”, “/dirB/name2”) == -1) perror(“Failed to make a new link in /dirB”);
 
notion image
 

Symbolic Links

  • 파일 명과 inode 사이에 간접적인 연관을 갖는다.
  • special type인 파일이다.
    • 원본 파일의 경로명이 포함된 파일 / 폴더
  • symbolic link를 접근하면 OS가 symbolic link의 inode를 확인.
  • 원본 파일의 경로명이 있음을 통해 symbolic link임을 확인한 뒤, 원본 파일의 경로명을 통해 원본 파일의 inode까지 탐색을 하게 된다.
  • shell command: ln -s
  • 원본 파일의 link count에 영향을 주지 않는다.
 

Symbolic Link API

#include <unistd.h> int symlink(const char *path1, const char *path2);
  • path1: 원본 파일의 경로명
  • path2: 새롭게 만들어지는 symbolic link의 경로명
 

Example

ln -s /dirA/name1 /dirB/name2
 
#include <stdio.h> #include <unistd.h> if (symlink(“/dirA/name1”, “/dirB/name2”) == -1) perror(“Failed to create symbolic link in /dirB”);
 
notion image
 
원본 파일 (A) / inode number 100 , hard link 파일 (B) / inode number 100, symbolic link 파일 (C → A) / inode number 104
  • A를 수정하면 B, C에 적용됨
  • B, C를 수정해도 A에 적용 됨
  • A를 삭제하면 B는 그대로 남아있으나, C는 참조가 끊겨버림
  • A를 삭제하고 A의 파일명과 똑같은 이름의 파일 D를 생성하면, C는 D를 참조한다.