본문 바로가기

not기초/시스템프로그래밍

[시스템프로그래밍]파일 시스템

파일

연속된 바이트의 나열로 특별히 다른 포맷을 정하지 않는다
파일을 사용하는 방식으로 외부 장치도 사용 가능하다

파일 디스크립터

  • 열린 파일을 나타내는 번호로 향후 파일에 대한 입출력 연산에서 열린 파일을 나타낸다
  • 0은 표준 입력, 1은 표준 출력, 2는 표준 에러에 해당하는 파일의 번호를 의미한다
  • C에서 파일을 fopen을 이용하여 열 때 리턴 받는 값인 파일 포인터와 대비된다


파일 시스템 구조

파일이 저장되어 있는 구조이다.

파일 시스템 구조
  1. 부트 블록(boot block)
    • 보통 파일 시스템의 시작부에 위치한다
    • 부트스트랩 코드가 저장된다
  2. 수퍼 블록(super block)
    • 전체 파일 시스템에 대한 정보를 저장한다
    • 총 블록 수, 사용 가능한 i-노드 개수, 사용 가능한 블록 비트 맵, 블록의 크기, 사용 중인 블록 수, 사용 가능한 블록 수 등을 저장한다
  3. i-리스트
    • 모든 i-노드들의 리스트
    • 한 블록 당 약 40개의 i-노드를 포함한다
  4. 데이터 블록(data block)
    • 파일의 내용(데이터)를 저장하기 위한 블록

i-노드(i-node)

파일에 대한 모든 정보를 가지고 있는 자료구조로 한 파일 당 하나의 i-노드가 존재한다
파일 타입, 파일 크기, 사용권한, 파일 소유자 및 그룹, 접근 및 갱신 시간, 데이터 블록에 대한 포인터(블록 포인터) 등을 가지고 있다

블록 포인터

  • 파일의 내용을 저장하기 위해 할당한 데이터 블록의 주소로 직접 블록 포인터, 간접 블록 포인터, 이중 간접 블록 포인터 등이 있다
  • 하나의 i-노드 내의 블록 포인터에는 직접 블록 포인터 10개, 간접 블록 포인터 1개, 이중 간접 블록 포인터 1개가 존재한다
  • 간접 블록 포인터는 직접 블록 포인터의 주소를 저장하며, 이중 간접 블록 포인터는 간접 블록 포인터의 주소를 저장한다.
i-node


파일 입출력 구현을 위한 커널 내 자료구조


  1. 파일 디스크립터 배열(fd array)
    • 프로세스 내에서 열린 파일의 파일 디스크립터를 저장하기 위한 자료구조
    • 프로세스 당 하나씩 갖는다
    • 파일 디스크립터 배열의 값은 열린 파일 테이블의 엔트리를 가리킨다
    • 파일 디스크립터 배열의 인덱스는 열린 파일의 파일 디스크립터이다
  2. 열린 파일 테이블(open file table)
    • 열려진 모든 파일 목록을 저장하기 위한 자료구조
    • 파일을 열 때마다 파일 테이블 엔트리를 생성한다
    • 파일 테이블 엔트리에는 파일 상태 플래그, 현재 파일 위치(current file offset), i-node에 대한 포인터 등이 저장되어 있다
  3. 동적 i-노드 테이블
    • 열린 파일의 i-노드를 저장하는 테이블
    • 파일을 열면 파일 시스템 내에서 그 파일의 i-노드의 모든 내용을 가져와 동적 i-노드 테이블에 엔트리를 만든다.
커널 자료구조

파일 열기 예시

open() 시스템 호출을 이용해서 파일을 열면 가장 먼저 파일 시스템 내에서 디렉토리 내의 정보를 이용하여 그 파일의 i-노드를 찾는다. i-노드를 찾게 되면 i-노드 내의 모든 정보를 동적 i-노드 테이블로 가져와 테이블 내에 하나의 엔트리를 만든다. 이어 파일 테이블에도 하나의 엔트리를 만드록 그 엔트리 내에 현재 파일 위치 등을 저장한다. 이후 파일 디스크립터 배열에 하나의 엔트리를 만들고 그 인덱스를 반환한다. 이 때, 인덱스가 open() 시스템 호출이 반환하는 파일 디스크립터이다.

하나의 파일을 두 번 열었을 경우

이미 그 파일의 i-노드 내용이 동적 i-노드 테이블에 존재하므로 fd 배열과 열린 파일 테이블에 새로운 엔트리를 생성한다

dup() 시스템 호출을 하는 경우

기존의 열려 있는 파일을 공유하므로 새로운 파일 디스크립터만 생성한다.



파일 상태(file status)

파일에 대한 모든 정보로 블록 수, 파일 타입, 접근 권한, 링크 수, 파일 소유자 및 그룹, 파일 크기, 최종 수정 시간 등을 포함한다
앞에서 살펴 보았던 i-노드 안에 상태 정보가 저장되어 있으므로 상태 정보를 얻기 위해서는 stat() 시스템 호출을 해야 한다

stat()

  1. 지정된 파일의 상태 정보를 가져와서 stat 구조체에 저장한다
  2. prototype
  3. #include <sys/types.h>
    #include <sys/stat.h>
    
    int stat(const char *filename, struct stat *buf);
    int fstat(int fd, struct stat *buf);
    int lstat(const char *filename, struct stat *buf);

    성공 시 0, 실패 시 -1를 리턴한다

  4. stat()은 대상이 되는 파일을 파일 이름으로 지정하고 fstat()은 파일 디스크립터로 지정한다
  5. lstat()은 대상 파일이 심볼릭 링크일 때 링크가 가리키는 파일이 아니라 링크 자체에 대한 상태 정보를 가져온다는 점을 제외하면 stat()과 동일하다

stat 구조체

struct stat {
	mode_t st_mode; // 파일 타입과 사용권한
	ino_t st_ino; // i-노드 번호
	dev_t st_dev; // 장치 번호
	dev_t st_rdev; // 특수 파일 장치 번호
	nlink_t st_nlink; // 링크 수
	uid_t st_uid; // 소유자의 사용자 ID
	gid_t st_gid; // 소유자의 그룹 ID
	off_t st_size; // 파일 크기
	time_t st_atime; // 최종 접근 시간
	time_t st_mtime; // 최종 수정 시간
	time_t st_ctime; // 최종 상태 변경 시간
	long st_blksize; // 최적 블록 크기
	long st_blocks; // 파일의 블록 수
};

파일 타입

파일 타입
일반 파일데이터를 갖고 있는 텍스트/이진 파일
디렉토리 파일파일의 이름들과 파일 정보에 대한 포인터를 포함하는 파일
문자 장치 파일문자 단위로 데이터를 전송하는 장치를 나타내는 파일
블록 장치 파일블록 단위로 데이터를 전송하는 장치를 나타내는 파일
FIFO 파일프로세스 간 통신에 사용되는 파일로 이름 있는 파이프
소켓네트워크를 통한 프로세스 간 통신에 사용되는 파일
심볼릭 링크다른 파일을 가리키는 포인터 역할을 하는 파일

파일 타입을 검사하기 위해서는 stat 구조체의 st_mode 필드 내부를 조사해야 하므로 복잡하다. 이를 위해서 매크로 함수를 제공한다
해당 파일일 경우 1, 아닐 경우 0을 리턴한다


파일 타입매크로 함수
일반 파일S_ISREG()
디렉토리 파일S_ISDIR()
문자 장치 파일S_ISCHR()
블록 장치 파일S_ISBLK()
FIFO 파일S_ISFIFO()
소켓S_ISSOCK()
심볼릭 링크S_ISLNK()

ftype.c

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
	int i;
	struct stat buf;
	for (i = 1; i < argc; i++) {
		printf("%s", argv[i]);
		if (lstat(argv[i], &buf) < 0) {
			perror("lstat()");
			continue;
		}

		if (S_ISREG(buf.st_mode)) printf("%s \n", "일반 파일");
		if (S_ISDIR(buf.st_mode)) printf("%s \n", "디렉터리");
		if (S_ISCHR(buf.st_mode)) printf("%s \n", "문자 장치 파일");
		if (S_ISBLK(buf.st_mode)) printf("%s \n", "블록 장치 파일");
		if (S_ISFIFO(buf.st_mode)) printf("%s \n", "FIFO 파일");
		if (S_ISLNK(buf.st_mode)) printf("%s \n", "심볼릭 링크");
		if (S_ISSOCK(buf.st_mode)) printf("%s \n", "소켓");
	}
	exit(0);
}


파일 사용권한(file permissions)

각 파일에 대한 권한으로 소유자/그룹/기타로 구분하여 관리한다. 읽기 권한, 쓰기 권한, 실행 권한이 존재한다.
읽기 권한이 있어야 O_RDONLY, O_RDWR을 사용할 수 있고, 쓰기 권한이 있어야 O_WRONLY, O_RDWR, O_TRUNC를 사용할 수 있고, 디렉토리에 쓰기&실행 권한이 있어야 그 디렉토리 안에 파일을 생성/삭제할 수 있다

chmod()

  1. 지정된 파일의 사용권한을 mode로 변경한다
  2. prototype
  3. #include <sys/stat.h>
    #include <sys/types.h>
    
    int chmod(const char *path, mode_t mode);
    int fchmod(int fd, mode_t mode);

    성공 시 0, 실패 시 -1를 리턴한다

  4. chmod()는 첫 번째 인자로 파일 경로를 받고, fchmod()는 파일 경로 대신에 파일 디스크립터를 받는다
  5. fchmod.c
  6. #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
    	int newmode;
    	newmode = (int)strtol(argv[1], (char**)NULL, 8);
    	if (chmod(argv[2], newmode) == -1) {
    		perror(argv[2]);
    		exit(1);
    	}
    	exit(0);
    }

chown()

  1. path가 나타내는 파일의 사용자 ID와 그룹 ID를 변경한다
  2. prototype
  3. #include <sys/types.h>
    #include <unistd.h>
    
    int chown(const char *path, uid_t owner gid_t group);
    int fchown(int fd, uid_t owner, gid_t group);
    int lchown(const char *path, uid_t owner, gid_t group);

    성공 시 0, 실패 시 -1를 리턴한다

  4. f, l이 붙은 경우는 위의 stat() 시스템 호출과 동일한 경우이다

utime()

  1. 지정된 파일의 접근 및 수정 시간을 변경한다
  2. prototype
  3. #include <sys/types.h>
    #include <sys/utime.h>
    int utime(const char *filename, const struct utimebuf *times);

    성공 시 0, 실패 시 -1를 리턴한다

  4. utimebuf 구조체
  5. struct utimbuf {
    	time_t actime;
    	time_t modtime;
    };
  6. cptime.c
  7. #include <sys/types.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <utime.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
    	struct stat buf;
    	struct utimebuf time;
    
    	if (argc < 3) {
    		fprintf(stderr, "사용법: cptime file1 file2\n");
    		exit(1);
    	}
    	if (stat(argv[1], &buf) < 0) {
    		perror("stat()");
    		exit(2);
    	}
    	time.actime = buf.st_atime;
    	time.modtime = buf.st_mtime;
    	
    	if (utime(argv[2], &buf))
    		perror("utime");
    	else exit(0);
    }


디렉토리

디렉토리 내에는 하나의 파일을 나타내는 디렉토리 엔트리가 저장되어 있다. 디렉토리 엔트리는 파일의 이름과 i-노드 번호로 구성되어 있다.

#include <dirent.h>
struct dirent {
	ino_t ino; //i-노드 번호
	char d_name[MANE_MAX + 1]; //파일 이름
};
디렉토리 엔트리

opendir()/readdir()

  1. opendir() : path가 나타내는 디렉토리를 연다
    성공 시 DIR 포인터, 실패 시 NULL를 리턴한다
  2. readdir() : dp가 가리키는 디렉토리에서 한 번에 하나씩 디렉토리 엔트리를 읽어서 리턴한다
  3. #include <sys/types.h>
    #include <dirent.h>
    DIR *opendir(const char *path);
    struct dirent *readdir(DIR *dp);
  4. 디렉토리도 파일이므로 open, read, close 시스템 호출을 사용할 수 있으나 디렉토리를 위한 별도의 시스템 호출이 제공된다
  5. 디렉토리에 대한 쓰기 권한이 있더라도 write() 시스템 호출을 이용하여 직접 작성할 수 없다.
    디렉토리 구조의 정확성/일관성을 유지하기 위해서이다
  6. list.c
  7. #include <sys/types.h>
    #include <sys/stat.h>
    #include <dirent.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    void printStat(char *, char *, struct stat *)
    char type(mode_t);
    char * perm(mode_t);
    
    int main(int argc, char **argv) {
    	DIR *dp;
    	char *dir;
    	struct dirent *d;
    	struct stat st;
    	char path[BUFSIZE + 1];
    
    	if (argc == 1)
    		dir = ".";
    	else dir = argv[1];
    
    	if ((dp = open(dir)) == NULL)
    		perror(dir);
    	while ((d = readdir(dp)) != NULL){
    		sprintf(path, "%s/%s", dir->d_name);
    		if (lstat(path, &st) < 0) perror(path);
    		printStat(path, d->d_name, &st);
    		putchar('\n');
    	}
    	closedir(dp);
    	exit(0);
    }
    
    void printStat(char *pathname, char *file, struct stat *st) {
    	printf("%5d", st->st_blocks);
    	printf("%c%s", type(st->st_mode), perm(st->st_mode));
    	printf("%3d", st->st_nlink);
    	printf("%s %s", getpwuid(st->st_uid)->pw_name, getgrgid(st->st_gid)->gr_name);
    	printf("%9d", st->st_size);
    	printf("%.12s", ctime(&st->st_mtime) + 4);
    	printf("%s", file);
    }
    
    char type(mode_t mode) {
    	if (S_ISREG(mode)) return('-');
    	if (S_ISDIR(mode)) return('d');
    	if (s_ISCHR(mode)) return('c');
    	if (s_ISBNK(mode)) return('b');
    	if (S_ISLNK(mode)) return('l');
    	if (S_LSFIFO(mode)) return('p');
    	if (S_ISSOCK(mode)) return('s');
    }
    
    char *perm(mode_t mode) {
    	int i;
    	static char perms[10] = "----------";
    
    	for (i = 0; i < 3; i++) {
    		if (mode&(S_IREAD >> i * 3))
    			perms[i * 3] = 'r';
    		if (mode&(S_IWRITE >> i * 3))
    			perms[i * 3 + 1] = 'w';
    		if (mode&(S_IEXEC >> i * 3))
    			perms[i * 3 + 2];
    	}
    	return(perms);
    }

mkdir()/rmdir()

  1. mkdir() : mode 사용권한을 갖는 새로운 디렉토리 path를 만든다
    성공 시 0, 실패 시 -1를 리턴한다
  2. rmdir() : path 디렉토리가 비어 있으면 삭제한다
    성공 시 0, 실패 시 -1를 리턴한다
  3. #include <sys/types.h>
    #include <sys/stat.h>
    int mkdir(const char *path, mode_t mode);
    #include <unistd.h>
    int rmdir(const char *path);

디렉토리 구현

디렉토리 구현 디렉토리 구현


링크

기존 파일에 대한 또 다른 이름이다

  • 하드 링크 : 파일 시스탬 내의 i-노드를 가리키므로 같은 파일 시스템 내에서만 사용될 수 있다
  • 심볼릭 링크 : 실제 파일의 경로명을 저장하고 있는 링크로 다른 파일 시스템에 있는 파일도 링크할 수 있다

link()/unlink()/symlink()/readlink()

  • link() : 기존 파일 이름 existing에 대한 새로운 링크 new를 만든다
    성공 시 0, 실패 시 -1를 리턴한다
  • unlink() : 링크 path를 제거한다
    성공 시 0, 실패 시 -1를 리턴한다
  • symlink() : 실제 파일의 경로명 actualpath를 저장하고 있는 심볼릭 링크 sympath를 만든다
    성공 시 0, 실패 시 -1를 리턴한다
  • readlink() : path 심볼릭 링크의 실제 내용을 읽어서 buf에 저장한다
    성공 시 buf에 저장된 바이트 수, 실패 시 -1를 리턴한다
  • #include <iunistd.h>
    int link(char *existing, char *new);
    int unlink(char *path);
    int symlink(const char *actualpath, const char *sympath);
    int readlink(const char *path, char *buf, size_t bufsize)
  • link.c
  • #include <unistd.h>
    #include <stdlib.h>
    
    int main(argc, char *argv[]) {
    	if (link(argv[1], argv[2]) == -1) exit(1);
    	exit(0);
    }
  • unlink.c
  • #include <unistd.h>
    #include <stdlib.h>
    
    int main(int argc, char *argv[]) {
    	if (unlink(argv[1]) == -1) {
    		perror(argv[1]);
    		exit(1);
    	}
    	exit(0);
    }
  • rlink.c
  • #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int main(int argc, char *argv[]) {
    	char buffer[1024];
    	int nread;
    	nread = readlink(argv[1], buffer, 1024);
    	if (nread > 0) {
    		write(1, buffer, nread);
    		exit(0);
    	}
    	else {
    		fprintf(stderr, "오류 : 해당 링크 없음\n");
    		exit(1);
    	}
    }

출처 : 리눅스 프로그래밍, 창병모 저