포스트

시스템 프로그래밍 12. Standard IO

시스템 프로그래밍 12. Standard IO

건국대학교 시스템 프로그래밍 진현욱 교수님의 수업을 정리한 내용입니다.

Standard I/O

보안 이슈 때문에 Kernel 영역과 User 영역은 엄격하게 분리되어있고, System Call을 통해서만 Kernel에 작업을 요청할 수 있다. 따라서, Kernel 영역과 User 영역을 오가는데 오버헤드가 존재하고, 만약 System Call 호출이 잦을수록 더 많은 오버헤드가 발생한다. 그 이유로 System Call 호출을 여러번 하는것보다 한번만 하는게 더 좋으며, 이를 Stand I/O가 User Level Buffer를 구현함으로써 성능을 높힌다.

Kernel level buffer는 Kernel <-> Disk 사이의 I/O를 담당하는 Buffer이고, User level buffer는 Application <-> Kernel 사이의 I/O를 담당하는 Buffer이다. read, write를 사용하면 데이터가 바로 Kernel level buffer에 쓰여지고, 이 과정은 느리다. stand i/o 함수를 사용하여 데이터가 먼저 User level buffer에 쓰여지도록 한다. 적당한 때에 Kernel level buffer로 flash되며, 또 적당한 때에 최종적으로 데이터가 Disk에 Flash된다. 이렇게 구현하면 Application -> Kernel Bus의 연산을 최소한으로 줄일 수 있다.

하지만 상황에 따라서 User level buffer 사용이 불필요할 수 있다. 예를들어 큰 데이터를 한번에 write하는 경우 user buffer에서 나눠서 전송하지 말고 write를 사용해서 바로 Kernel buffer로 보내는게 나을 수도 있다. 따라서 무조건 standio가 항상 좋은 것은 아니며, 상황에 맞게 standio를 쓸건지 system call을 쓸건지 판단하면 되겠다.

File Open

  • FILE * fopen (const char *path, const char *mode)
  • FILE * fdopen (int fd, const char *mode)

FILE은 File Descriptor에 대응하는 값이다 File pointer라고 한다. fdopen을 사용하여 File Descriptor를 FIle pointer로 변환할 수 있다.

fopen이 성공하면 File Pointer 값을 반환하며, 실패하면 NULL 값을 반환한다.

Pasted image 20241128143628.png File Mode는 r, w, a, w+, r+, a+를 지원한다. a는 append의 약자이며, 파일을 열 때 기존의 내용을 clear하고 싶다면 w, 기존의 내용에서 추가하고 싶다면 a를 선택한다. Truncate가 True면 기존의 내용을 지운다는 의미다.

File Read

  • int fgetc (FILE *stream)
  • char * fgets (char *str, int size, FILE *stream)
  • size_t fread (void *buf, size_t size, size_t nr, FILE *stream)

각각 char, string, binary를 읽는다.

fgetc의 반환값은 성공시 char, 실패시 EOF를 반환한다. 반환값이 char가 아니라 int인 이유는, EOF와 Error Code를 구분하기 위함이다. 따라서 fgetc로 읽으면 int를 (unsigned char)로 castin하면 된다.

fgets를 사용할 때 주의할 점은 실제로 size - 1 Byte만큼 읽는다는 것이다. 그 이유는, string 끝에 Null 문자 \0을 고려한 결과다. 또, 줄바꿈 문자 \n을 따로 구분하지 않고 그냥 char로 읽어들인다. 성공시 char 포인터를, 실패시 NULL을 반환한다.

fread의 nr은 비슷한 format의 binary를 반복적으로 받기 위한 기능이다. 그러고 싶지 않다면 nr=1로 지정하면 되고, nr을 5로 지정하면 size만큼의 byte를 nr번 읽게된다. fread의 반환값은 읽은 요소의 수가 반환된다. 즉 nr=1로 지정했을 때 1이 반환되면 잘 읽었다는 뜻이다.

scanf(), getc(), getchar() 함수를 사용하는 것보다, fgetc()나 fgets()를 사용하는게 좋다. 앞의 세 함수는 담는 Buffer의 Size를 지정할 수 없어 Overflow에 취약하다. 더 견고한 코드 작성을 위해 fgetc(stdin), fgets(…, stdin) 함수 사용을 지향하자.

File Write

  • int fputc (int c, FILE *stream)
  • int fputs (const char *str, FILE *stream)
  • size_t fwrite (void *buf, size_t size, size_t nr, FILE *stream)

fputs를 성공하면 음수가 아닌 숫자가 반환되고, 실패하면 EOF가 반환된다. 나머지 fputc와 fwrite의 반환값은 read인 경우와 같다.

File Close

  • int fclose (FILE *stream)
  • int fcloseall (void)

fcloseall은 3개의 Default file인 stdin, stdout, and stderr까지 모두 Close한다. 두 close 함수 모두 성공하면 0을, 실패하면 EOF를 반환한다.

Util

  • int fflush (FILE *stream)
  • int fseek (FILE *stream, long offset, int whence)
  • long ftell (FILE *stream)

fflush는 User level buffer의 내용을 바로 Kernel level buffer로 Flash한다. 따라서, fflush를 사용한다고 해서 바로 Disk에 내용이 쓰여지는 것이 아니다. Disk Flash까지 보장하려면, fflush() 이후 fsync()까지 사용해야 한다.

fseek는 File pointer의 offset 값을 옮기는 함수다. offset만큼 이동시키며, whence 값에 SEEK_SET, SEEK_CUR, SEEK_END 셋중 하나를 넣으면 된다. 성공시 0을 반환하며, 실패시 -1를 반환한다.

fseek의 반환값이 0 또는 -1이므로, lseek처럼 현재 File Offset를 알아내는 함수로 사용할 수 없다. 따라서 현재 File Offset의 위치를 알아내는 함수가 fteel()로 따로 존재한다. fteel()을 실패하면, -1을 반환하고 성공하면 현재 File pointer의 offset을 반환한다.