Notice
Recent Posts
Recent Comments
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

topcue

[OS] Files and Directories 본문

Operating System

[OS] Files and Directories

topcue 2021. 9. 3. 23:44

Intro

block 안에 sector들이 있다. sector는 보통 512 바이트. block은 4KB(UNIX) or 8KB. 섹터는 atomic하게 처리 가능하다.

UNIX에서 metadatai-node라고 부른다. head가 여러 block을 가리키고 있다. 이 head에 metadata(i-node)가 들어있다.

file과 direcotry는 물리적으로 똑같이 생겼다(block).

프로세스의 fd가 커널에서 open file table로 볼 수 있고, 이 fd는 i-node table을 가리키고 있다.

device, pipe, socket은 실제 데이터 정보가 필요 없으므로 헤더만 있다.

  • file은 읽거나 쓸 수 있는 bytes들의 배열이다. 모든 파일은 i-node number라고 불리는 low-level name이 있다.
  • directory도 파일처럼 i-node number라는 low-level name이 있다. 디렉토리는 (user-readable name, low-level name) list를 포함한다. directory tree 형태로 계층 구조를 가질 수 있다.

File

user 입장에서는 데이터구조, 시스템의 system call 입장에서는 그냥 바이트 배열, OS 입장에서는 블럭 단위로 보인다.

Create file

open() syscall에 O_CREAT flag를 인자로 전달해 file을 생성할 수 있다.

int fd = open("foo", O_CREAT | O_WRONLY | O_TRUNC);

O_TRUN flag는 파일이 이미 존재할 경우 사이즈를 0으로 잘라서(truncate) 없애라는 의미이다.

open()은 file descriptor를 return 한다. 프로세스가 fd를 알아야 해당 파일에 접근해 읽고 쓸 수 있다.

Read file

다음은 strace로 cat의 system call을 추적한 결과이다.

$ strace cat foo
...
open("foo", O_RDONLY|O_LARGEFILE) = 3
read(3, "hello\n", 4096)          = 6
write(1, "hello\n", 6)            = 6
hello
read(3, "", 4096)                 = 0
close(3)                          = 0
...

읽기만 가능하도록 O_RDONLY로 열었고 fd 3을 return 했다.

read()를 이용해 4KB 단위로 파일을 읽으려 했으며 처음에 6바이트를 읽었다. 이후 EOF에 도달해 read() 를 멈추었다. read()의 return 값은 실제로 읽은 버퍼의 크기인 6이다.

Read and Write file (not sequentially)

lseek()으로 random offset에 접근할 수 있다.

off_t lseek(int fildes, off_t offset, int whence);
# arg1: fd
# arg2: 읽기 시작할 offset
# arg3: SEEK_SET, SEEK_CUR, SEEK_END

lseek()은 file pointer를 옮기는 함수이다. fd에서 whence를 기준으로 file pointer를 옮긴다.

파일을 N bytes 읽으면 N bytes 만큼 current offset이 업데이트된다.

이렇게 순차적으로 읽어나가는 방법이 있고, lseek()을 이용해 지정한 offset부터 읽을 수도 있다.

Write file immediately - fsync()

storage에 wrtie()하면 효율성 때문에 buffer 형태로 5초나 30초마다 실제 데이터를 쓴다.

buffer는 page cache라고도 불리며 DRAM에 있고 disk에 비해 속도가 훨씬 빠르다.

UNIX에서는 buffer를 즉시 쓰기 위해 API로 fsync(int fd)를 제공한다. fsync()를 호출하면 fd가 아직은 쓰이지 않은 dirty data를 disk에 즉시 쓴다.

fsync는 파일뿐만 아니라 디렉터리 정보도 즉시 써준다.

Rename file

파일명을 바꿀 때 rename() syscall을 사용한다.: rename(char *old, char *new)

mv 명령어로 파일명을 바꾸는 과정을 strace 하면 다음과 같다.

int fd = open("foo.txt.tmp", O_WRONLY|O_CREAT|O_TRUNC);
write(fd, buffer, size); // write out new version of file
fsync(fd);
close(fd);

rename("foo.txt.tmp", "foo.txt");

rename은 atomic 해야 한다.

File information

file system은 저장된 데이터의 metadata를 가지고 있다. metadata는 inode와 superblock에 들어있다.

metadata를 보기 위해 stat()이나 fstat() syscall을 사용할 수 있다.

state(file_name, struct stat): file의 정보를 구조체 stat에 담는다.

fstate(fd, struct stat): fd의 정보를 구조체 stat에 담는다.

struct stat {
	dev_t st_dev;         /* ID of device containing file */
	ino_t st_ino;         /* inode number */
	mode_t st_mode;       /* protection */
	nlink_t st_nlink;     /* number of hard links */
	uid_t st_uid;         /* user ID of owner */
	gid_t st_gid;         /* group ID of owner */
	dev_t st_rdev;        /* device ID (if special file) */
	off_t st_size;        /* total size, in bytes */
	blksize_t st_blksize; /* blocksize for filesystem I/O */
	blkcnt_t st_blocks;   /* number of blocks allocated */
	time_t st_atime;      /* time of last access */
	time_t st_mtime;      /* time of last modification */
	time_t st_ctime;      /* time of last status change */
};

Remove file

file은 unlink() system call로 삭제한다.

unlink는 link에 대해 다룰 때 같이 다룬다.


Directories

create directory

mkdir() system call로 directory를 만들 수 있다. : 예) mkdir("foo", 0777)

Read directory

ls로 directory를 탐색하면 opendir(), readdir(), closedir() 세 가지 system call을 사용해 directory entry 단위로 읽는다.

opendir(): 현재 디렉토리 들어가기

readdir(): 디렉토리에서 파일/디렉토리 읽기

int main(int argc, char *argv[]) {
    DIR *dp = opendir(".");
    assert(dp != NULL);
    struct dirent *d;
    while ((d = readdir(dp)) != NULL) {
        printf("%d %s\n", (int) d->d_ino, d->d_name);
    }
    closedir(dp);
    return 0;
}

struct dirent {
	char d_name[256];        /* filename */
	ino_t d_ino;             /* inode number */
	off_t d_off;             /* offset to the next dirent */
	unsigned short d_reclen; /* length of this record */
	unsigned char d_type;    /* type of file */
};

Delete directory

rmdir() system call로 directory를 지울 수 있다.

이때 디렉토리가 비어있는지(현재 디렉토리 "."과 상위 디렉토리 ".."만 있는지) 확인한다.


Hard Links

link() system call로 hard link를 걸 수 있다. 이렇게 link된 두 파일의 i-node number는 같다.

$ ls -i file file2
67158084 file
67158084 file2

이때 한 i-node가 두 file을 가리키고 있고 이 i-node의 reference count(link count)는 2다.

unlink()로 파일을 삭제하면 link를 끊고 reference count를 하나 감소시킨다.

link로 new file을 생성하고 old filed을 삭제해도 new file이 남아있다.

대신 hard link는 두 파일이 같은 파티션에 있어야 한다. 파티션이 다르면 다른 i-node table을 가지고 있기 때문에 구분할 수 없다.

Symbolic Links

ln -s 옵션으로 symbolic link(soft link)를 만들 수 있다.

symbolic link는 new file을 생성하고, 이 new file이 file pointer로 old file을 가리키고 있다.

symbolic link는 다른 파티션을 가리킬 수 있다.

symlink가 걸린 old file을 삭제하면 new file은 dangling reference(엉뚱한 곳을 참조)하게 된다.

Making and Mounting a File System

mkfs를 이용해 새로운 file system tree를 만들 수 있다.

이렇게 생성된 fs를 특정 디렉토리에 mount 할 수 있다.


'Operating System' 카테고리의 다른 글

[OS] File System Implementation  (0) 2021.09.03
[OS] Hard Disk Drives  (0) 2021.09.03
[OS] I/O Devices  (1) 2021.09.03
[OS] DeadLock  (0) 2021.09.03
[OS] Semaphores  (0) 2021.09.03
Comments