Fascination
article thumbnail

Chapter 06. 파일 시스템

리눅스 프로그래밍 원리와 실제 - 창병모 교수님


6.1 파일 시스템

1) 파일 시스템 구조

- 부트 블록(Boot block)

  • 파일 시스템 시작부 위치, 첫 번째 섹터 차지
  • 유닉스/리눅스가 처음 시작될 때 사용되는 부트스트랩 코드가 저장되는 블록

- 슈퍼 블록(Super block)

  • 전체 파일 시스템에 대한 정보 저장
  • 파일 시스템 내의 총 블록 수, 사용 가능한 i-노드 개수, 사용 가능한 블록들을 나타내는 비트 맵, 블록의 크기, 사용 중인 블록 수, 사용 가능한 블록 수

- i-리스트(i-list)

  • 각각의 파일을 나타내는 i-노드들의 리스트
  • 한 블록은 약 40개 정도의 i-노드 포함

- 데이터 블록(Data block)

  • 파일의 내용(데이터)을 저장하기 위한 블록

파일 시스템 구조

 

2) i-노드와 블록 포인터

- i-노드

  • 한 파일은 하나의 i-노드를 가짐
  • 파일에 대한 모든 정보를 가지고 있음(파일 타입, 파일 크기, 접근 권한, 파일 소유자 및 그룹, 접근 및 갱신 시간, 데이터 블록에 대한 포인터(주소) 등)

- 데이터 블록에 대한 포인터: 파일의 내용을 저장하기 위해 할당된 데이터 블록의 주소

- 파일의 내용을 저장하기 위해 직접 블록 포인터, 간접 블록 포인터, 이중 간접 블록 포인터, 삼중 간접 블록 포인터 존재

- 직접 블록 포인터(direct block pointer): 파일 데이터에 저장될 데이터 블록 주소(12개)

- 간접 블록 포인터(indirect block pointer): 직접 블록 포인터 저장(1개)

- 이중 간접 블록 포인터(double indirect block pointer): 간접 블록 포인터 개념 확장(1개)

  • 하나의 블록 포인터(주소) 크기가 4byte이고 한 블록의 크기가 4096바이트라면 간접 블록 포인터가 가리키는 하나의 데이터 블록 내에는 1024개의 직접 블록 포인터 저장 가능

- 삼중 간접 블록 포인터(triple indirect block pointer): 이중 간접 블록 포인터 한 단계 더 확장(1개)

 

 

6.2 파일 입출력 구현

1) 파일 열기 및 파일 입출력 구현

- 파일 디스크립터 배열(file descriptor array)

  • 프로세스마다 파일 디스크립터 배열 하나씩 가짐
  • 열린 파일의 파일 디스크립터를 저장하기 위한 자료구조

- 파일 테이블(file table)

  • 커널 자료구조로 알려진 모든 파일 목록을 저장하기 위한 자료구조
  • 파일 테이블 엔트리로 구성, 파일을 열 때마다 파일 테이블 엔트리가 만들어짐

- 동적 i-노드 테이블(active i-node table)

  • 커널 내의 자료구조로 열린 파일의 i-노드를 저장하기 위한 테이블
  • 파일을 열면 파일 시스템 내에서 그 파일의 i-노드 내용을 가져와 이 테이블에 엔트리를 만듦
fd = open("file",O_RDONLY);

  1. 가장 먼저 파일 시스템 내에서 그 파일의 i-node를 찾음, 디렉터리에 저장되어 있고 이 정보를 이용해야 찾을 수 있음
  2. i-노드를 찾으면 i-노드 내의 모든 정보를 커널 내의 자료구조인 동적 i-노드 테이블로 가져와 테이블 내에 하나의 엔트리를 만듦
  3. 커널 내의 자료구조인 파일 테이블에도 하나의 엔트리를 만들고, 이 엔트리 내에 현재 파일 위치나 파일 상태 플래그 저장
  4. 프로세스 내의 자료구조인 파일 디스크립터 배열에 하나의 엔트리를 만들고 그 인덱스 반환 →인덱스가 open() 시스템 호출이 반환하는 파일 디스크립터

Q. 사용자가 열린 파일에 대해 읽기나 쓰기 입출력을 요청하면 어떻게 해당 데이터 블록을 찾을까?

A. 커널 코드는 파일 테이블 엔트리 내의 현재 파일 위치 정보와 동적 i-노드 내의 블록 포인터 정보를 이용해 해당 데이터 블록의 위치를 계산하여 블록의 위치를 결정한 다음 해당 블록에서 데이터를 읽거나 데이터를 씀

Q. i-node 테이블을 사용하는 이유

A. 파일 시스템에서 i-node를 읽는 것은 디스크로부터 읽는 과정이기 때문에 느리고 비효율적이므로 파일을 open할 때 커널에 i-node 정보를 저장하는 것. 커널은 메인 메모리 위에 있으므로 빠르게 접근이 가능함

 

2) 한 파일을 두 번 열기 구현

fd1 = open("file", O_RDONLY);

- 이미 열였던 파일의 i-노드 내용이 동적 i-노드 테이블에 존재할 것이므로 동적 i-노드 테이블 내에서 새로운 엔트리를 만들 필요 X

- 새로 파일을 열게 되면 거기에 따라 현재 파일 위치, 파일 플래그 등을 새로 설정해야하기 때문에 열린 파일 테이블 내에는 새로운 엔트리를 만들어야 함

- fd 테이블에도 새로운 엔트리를 하나 만들어 새로운 파일 디스크립터를 반환함

한 파일을 두 번 열기

 

3) dup() 시스템 호출 구현

fd2 = dup(3);
or
fd2 = dup(3,4);

- dup() 혹은 dup2() 시스템 호출을 하는 경우

- 기존에 열려있는 파일을 공유하는 새로운 파일 디스크립터 반환

- i-노드 테이블이나 열린 파일 테이블 내에 새로운 엔트리를 만들 필요 없음

- fd 테이블 내에만 새로운 엔트리(파일 디스크립터)를 하나 만들어 열린 파일 테이블 내의 기존에 열려 있는 파일의 엔트리를 가리키도록 하고 새로운 파일 디스크립터를 반환

- 해당 엔트리 refcnt(reference count)는 2가 됨

- 기존 파일을 공유해 사용함

dup() 시스템 호출 구현

 

 

6.3 파일 상태 정보

1) 파일 상태와 stat()

- 파일 상태

  • 파일에 대한 모든 정보
  • 블록 수, 파일 타입, 사용 권한, 링크 수, 파일 소유자의 사용자 ID, 그룹 ID, 파일 크기, 최종 수정 시간 등

- 파일 하나 당 하나의 i-노드가 있으며 i-노드 내에 파일에 대한 모든 상태 정보 저장

- 파일 상태 정보를 가져와서 stat 구조체 buf에 저장. 성공하면 0 실패하면 -1을 리턴

int stat(const char *filename, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *filename, strcut stat *buf);

// stat 구조체
struct stat{
	mode_t st_mode; // 파일 타입과 접근 권한
    ino_t st_ino; // i-node 번호
    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; // 파일의 블록 수
};

 

- lstat()은 stat()과 같은데 대상 파일이 심볼릭 링크 일 때 링크 자체에 대한 상태 정보를 가져옴(링크를 가리키는 파일이 아님)

 

2) 파일 타입

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

- 파일 타입을 검사하기 위해서는 stat() 시스템 호출의 결과로 받은 stat 구조체의 st_mode 필드 내부를 조사해야 하는데 복잡하기 때문에 매크로 함수를 제공. 위 매크로 함수는 해당 파일이면 1을 반환하고 아니면 0을 반환함

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

 

3) 접근권한 변경 chmod()

- 각 파일에 대한 권한 관리: 각 파일마다 접근 권한이 있고, 소유자(owner)/그룹(group)/기타(others)로 구분해서 관리함

- 파일에 대한 권한: 읽기(r), 쓰기(w), 실행(x)

int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);

- mode: 8진수 접근 권한

 

4) 접근 및 수정 시간 변경 utime

int utime(const char *filename, const struct utimbuf *times);

// utimbuf 구조체 
struct utimbuf{
	time_t actime; // access time
    time_t modtime; // modification time
}

- 파일의 최종 접근 시간과 최종 변경 시간을 조정

- times가 NULL이면, 현재 시간으로 설정된다

- 성공하면 0, 실패하면 -1을 반환

 

5) 파일 소유자 변경 chown()

int chown(const char *path, uid_t owner, gid_t group);
int fchown(int filedes, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);

- 파일의 user ID와 group ID를 변경

- 성공하면 0을 리턴, 실패하면 -1을 리턴함

- lchown()은 심볼릭 자체를 변경

- super-user만 변환 가능함

 

6.4 디렉터리

1) 디렉터리 내용

- 일련의 디렉터리 엔트리 저장, 각 디렉터리 엔트리는 디렉터리 내에 있는 하나의 파일을 나타냄

struct dirent{
	ino_t d_ino; // i-노드 번호
    char d_name[NAME_MAX+1]; // 파일 이름
};

- 디렉터리 엔트리는 파일 이름과 그 파일의 i-노드 번호로 구성

DIR *opendir(const char *path);

- path가 나타내는 디렉터리 오픈

- 성공하면 DIR 구조체 포인터 반환, 실패하면 NULL

struct dirent *readdir(DIR *dp);

- dp가 가리키는 디렉터리에서 한 번에 하나씩 디렉터리 엔트리 읽어서 반환

 

2) printStat() 함수

- 파일 상태 정보 프린트

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\n",file);
}

 

3) type() 함수

- 파일 타입 리턴

char type(mode_t mode){
	if(S_ISREG(mode))
    	return('-');
    if(S_ISDIR(mode))
    	return('d');
    if(S_ISCHR(mode))
    	return('c');
    if(S_ISBLK(mode))
    	return('b');
    if(S_ISLNK(mode))
    	return('l');
    if(S_ISFIFO(mode))
    	return('p');
    if(S_ISSOCK(mode))
    	return('s');
}

st_mode의 필드

 

4) perm() 함수

- 파일 접근권한 리턴

#define S_ISUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100

char* perm(mode_t mode){
	static char perm[10];
    strcpy(perms, "---------");
    
    for(int i=0; i<3;i++){
    	if(mode & (S_IRUSR >> i*3))
        	perms[i*3] = 'r';
        if(mode & (S_IWUSR >> i*3))
        	perms[i*3+1] = 'w';
        if(mode & (S_IXUSR >> i*3))
        	perms[i*3+2] = 'x';
    }
}

 

5) 디렉터리 조작

- 디렉터리 생성

int mkdir(const char *path, mode_t mode);
  • path가 나타내는 새로운 디렉터리를 만듦
  • "." 와 ".." 파일은 자동적으로 만들어짐

- 디렉터리 삭제

int rmdir(contst char *path);
  • path가 나타내는 디렉터리가 비어 있으면 삭제

 

6) 디렉터리 구현

- 디렉터리도 다른 파일처럼 i-노드로 표현

- 내용은 다른 파일과 달리 그 디렉터리 안에 있는 파일의 이름과 그 파일의 i-노드 번호로 구성된 디렉터리 엔트리

- 루트, bin, usr 디렉터리는 각각 2번, 3번, 4번 i-노드로 표현

- 디렉터리 구현

  • 디렉터리도 일종의 파일로 다른 파일처럼 구현
  • 디렉터리도 다른 파일처럼 하나의 i-노드로 표현

 

6.5 링크

1) 하드 링크

int link(char *existing, char *new);

- 기존 파일 이름 existing에 새로운 링크 new를 만듦

- 성공하면 0, 실패하면 -1 반환

int unlink(char *path);

- 링크 path 제거

- 성공 0, 실패 -1

하드 링크

 

2) 심볼릭 링크

int symlink(const char *actualpath, const char *sympath);

- 심볼릭 링크는 실제 파일의 경로명에 따라서 데이터 블록에 저장하고 있는 링크로서 파일에 대한 간접적인 포인터 역할

- 따라서 다른 파일 시스템에 있는 파일도 경로명을 이용해 링크 가능

- 실제 파일의 경로면 actualpath를 저장하고 있는 심볼릭 링크 sympath를 만듦

- 성공하면 0, 실패하면 -1 리턴

 

3) 심볼릭 링크 내용 확인

int readlink(const char *path, char *buf, size_t bufsize);

- 심볼릭 링크의 실제 내용을 읽어서 지정한 버퍼에 저장

- 끝에 널문자 추가해주지 않음

- path 심볼릭 링크의 실제 내용을 읽어서 buf에 저장

- 성공하면 buf에 저장한 바이트 수를 반환, 실패 -1

profile

Fascination

@euna-319

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!