-
System-Level I/O대학/시스템소프트웨어 2022. 10. 22. 18:24
- Disk Sector
데이터를 저장하는 기본 단위로 512bytes 당 1섹터를 구성한다. 단 1byte를 읽더라도 이 섹터 전체의 데이터를 읽어와야 한다.
이렇게 블록 단위로 데이터에 접근할 수 있는 장치를 Block device 라고 부른다. (HDD, SSD...)
반대로 1byte 단위로 데이터에 접근할 수 있는 장치를 Character device 라고 부른다. (키보드, 터미널...)
- Unix files
유닉스 계열 운영체제의 파일은 m bytes의 배열 형태로 관리된다.
예로 들어 /dev/sda2 같은 파일은 /usr의 디스크 파티션을 관리하고, /dev/tty2 는 터미널 파일이다.
유닉스 파일의 종류는 다음과 같다.
- Regular file
- Directory file
- Device file
Character device(special) file, Block device(special) file이 이에 해당한다. - FIFO (named pipe)
- Socket
- System call
open(), close(), lseek(), read(), write()와 같은 함수를 시스템 콜 함수라 부르는데,
이 함수들은 커널 수준에서 파일 시스템에 접근하여 유저 프로세스와 파일들이 통신하도록 돕는 역할을 한다.
- File descriptor
커널에서는 파일별로 inode(id)를 부여하여 파일을 관리하는데, 프로세스에서 이런 파일들에 접근하기 위해서는
파일위치, 크기, 권한 등에 대한 정보가 필요하다. 이런 정보들의 집합을 파일 지시자 라고하며,
이 파일 지시자가 있어야 프로세스가 파일에 접근할 수 있다.
open() 시스템 콜 함수로 이 파일 지시자를 받아올 수 있는데, 함수 호출에 성공하면 3번 부터 파일 지시자로 부여된다.
0: standard input
1: standard output
2: standard error
위 세개의 파일 지시자는 프로세스 생성과 동시에 할당이 되기 때문에 위 번호의 파일 지시자는 부여되지 않는다.
프로세스는 약 200개의 파일을 동시에 open할 수 있다.
즉, 파일을 닫지 않으면 다른 파일을 열지 못하는 상황이 발생할 수 있다.
close()라는 시스템 콜 함수가 파일 지시자를 인자로 받아 해당 파일을 닫아주는 역할을 수행한다.
- File offset
파일을 read(), write()할 때, current file offset부터 파일을 읽고 쓰게 된다.
char buf[4]; fd = open("foo.txt", O_CREAT ...); read(fd, buf, sizeof(buf)); // 1 write(fd, buf, 4); // 2
이 코드의 경우 foo.txt 파일을 열고 buf에 저장된 데이터를 읽고 쓰게 되는데,
1번 read()의 실행 후, foo.txt에 0~3byte의 데이터를 읽어오고, file offset이 0 에서 4로 바뀌게 된다.
2번 write()의 실행 후, foo.txt에 4~7byte가 채워지고, file offset이 4 에서 8로 바뀌게 된다.
참고로 buf에 저장된 데이터의 크기가 반드시 sizeof(buf)의 크기보다 작거나 같아야 에러가 발생하지 않는다.
파일을 쓸 때, 기존에 저장된 데이터 뒤 부터 이어서 쓰고 싶다면, 파일을 열 때, O_APPEND 플래그를 사용해서 열어야 했다.
하지만, lseek을 이용해서 하는 방법도 있다.
fd = open("foo.txt", O_APPEND); write(fd, buf, 4); 대신 fd = open("foo.txt", ...); lseek(fd, 0, SEEK_END); write(fd, buf, 4);
- File metadata
파일에 대한 보이지 않는 정보들을 파일 메타데이터 라고 하는데, 이는 c에서 stat구조체 안에 정의되어 있다.
device, 파티션을 구분한 정보를 갖는 st_dev,
OS가 파일을 식별하기 위한 번호를 갖는 st_ino,
inode device가 block인지 character인지 구분하는 st_rdev,
현재 파티션 파일 시스템의 블록 사이즈를 갖는 st_blksize 등등... 이 정의되어있다.
참고로, 디스크 섹터는 512bytes (1블록) 단위로 구성되어 있는데, 이 블록들의 묶음을 블록 사이즈라 한다.
파티션 별로 이 블록 사이즈를 다르게 설정하여 파일 시스템을 구축할 수 있다.
- Descriptor table
Descriptor table: 파일 지시자에 대한 정보가 담기며, 각 파일 지시자는 file table을 포인팅한다.
0~2번은 기본적으로 할당되어 있고, 새로운 파일 지시자를 할당할 때, descriptor table을 위에서 부터 아래로 순차 검색하면서 비어있는 번호부터 파일 지시자로 할당하게 된다.
File table: current file offset(File pos), read_option, 현재 이 파일을 참조하는 프로세스의 수(refcnt) 등에 대한 정보가 담기며, 모든 프로세스에서 접근하여 사용할 수 있다. 첫번째 인덱스는 v-node table을 포인팅한다.
V-node table: 시스템 글로벌로 관리되는 테이블로 stat 구조체에 저장된 정보는 이 테이블에서 가져오게 된다.
모든 프로세스에서 접근할 수 있지만, 시스템 당 1개만 존재한다.
참고로, 같은 파일을 open해도 새로운 file table을 할당받는다.
단, 기존 프로세스가 파일을 open한 상태에서 fork()을 이용해 자식 프로세스를 만든다면, 두 프로세스가 같은 file table을 가리키게 된다.
이 경우에는 refcnt가 1 증가하게 된다.
- I/O Redirection
dup2() 시스템 콜 함수를 이용해서 파일 지시자를 복사할 수 있다.
이 경우에는 터미널의 연결된 파일 지시자를 foo.txt에 연결된 파일 지시자로 덮어 썼는데,
따라서, ls 명령어를 치면 터미널에 나오던게 too.txt에 저장되게 된다.
- Directory
파일의 정보를 담고 있는 node의 집합으로 정의된다.
그래서 때문인지, directory 파일만을 위한 함수가 따로 정의되어 있다.
Struct dirent *dentry; DIR *dirp; dirp = opendir("/home/woong"); while ((dentry = readdir(dirp)) != NULL) {...} closedir(dirp);
opendir, closedir로 디렉토리를 열고 닫을 수 있다.
그리고 readdir로 디렉토리의 디렉토리 엔트리(파일)을 읽어올 수 있다.
디렉토리 엔트리를 읽을 때, 엔트리 집합 중간이 비어있더라도 EOF을 만날 때 까지 계속 읽는 것이 특징이다.
여기서 눈 여겨 봐야 할 점은 dentry가 포인터 타입이라는 것과, writedir이 없다는 것이다.
readdir()함수는 메모리공간을 만드는 것이 아닌, 실제 파일의 포인터를 리턴한다.
또한, writedir이 없으므로, 디렉토리 엔트리의 정보를 바꿀 수는 없다. (단, rename만 허용된다)
그리고, mkdir(), rmdir() 함수도 있는데, 각각 디렉토리를 만들고 삭제하는 기능을 수행한다.
단, rmdir()의 경우 디렉토리 내부가 완전히 비어있어야 에러없이 디렉토리를 삭제한다.
- Buffer
메모리의 있는 데이터를 파일에 쓰는 동작은 즉시 일어나지 않는다.
보통은 버퍼 캐시가 가득 차야 Disk device에서 I/O를 수행하게 된다.
이렇게 동작하는 이유는 I/O가 발생 횟수가 많으면 시스템 성능이 저하되기 때문에 모았다가 한번에 처리하는 방식으로
최적화를 하는 것이다.
다만, 버퍼가 꽉 차지 않은 상황에서 컴퓨터 파워가 나가게 되면 데이터 손실이 발생할 수 있다.
따라서 다음의 특정 조건에선 버퍼가 꽉 차지 않아도 강제로 I/O를 발생시킨다.
- fsync(fd): fd에 해당하는 변경된 데이터(Buffer cache에 있는 데이터)를 디스크에 저장한다.
- sync(): 변경된 모든 데이터를 디스크에 저장한다. (fflush와 비슷)
- flush deamon: 주기적으로 버퍼의 데이터를 디스크로 보낸다.
'대학 > 시스템소프트웨어' 카테고리의 다른 글
Threads (0) 2022.12.11 Inter-Process Communication (2) 2022.10.23 Timer (0) 2022.10.22 Exceptional Control Flow 2 - Signal (0) 2022.10.22 Exceptional Control Flow 1 - Process (0) 2022.10.22