전공/시스템 프로그래밍(운영체제)

7. Buffered IO

문정훈 2021. 10. 17. 03:31

1-1. 도입: HDD는 왜 느린가? (하드웨어적 관점)

HDD는 왜 느린가? RAM을 왜 사용하는가?

HDD는 굉장히 느리다. 왜 느리냐면

HDD의 구조를 뜯어보면 disk 라는 동그란 CD 같은 원판이 있다. 이곳에 데이터가 기록된다.

CD 원판은 회전을 하는데 reder라고 하는 일종의 핀이 있다.

이 핀이 읽고 쓰는 역할을 담당하는 막대기같은 것이다. 예를 들어

 

디스크로 read 명령이 주소 단위로들어온다. 그럼 디스크는 해당 주소의 위치에 있는 데이터를 읽어야한다. 디스크 원판은 회전을 하는데 위 read 명령에 의해 디스크에서 접근해야하는 주소가 방금 회전을 해서 지나갔다? => 그럼 한바퀴 돌아 해당 주소가 핀에 도달할 때까지 다시 기다려야한다. 이는 아무리 짧다해도 물리적인 시간이 소요된다.

 

보통 하드디스크에 있는 디스크에는 데이터는 한 곳에 모여 있을 수 있지만 모여있지 않은 경우가 대부분이며

read의 명령으로 여러 주소의 request를 보내면 하드디스크는 디스크에서 서로 떨어져 있는 주소의 값을 회전을 하여 핀에 도달했을 때 읽고 조합해서 사용자에게 보내야하는데 이 읽는 과정이 디스크의 회전으로 핀에 도달이라는 원리 이기 때문에 시간이 오래 걸리는 것이다.

 

보통 read, write 할 때 단위는 byte 단위이다.

 

 

1-2. 커널에서 block 단위로 IO가 일어난다.

OSHDD에서 RAM으로 block 단위로 IO가 일으킨다.

HDD와 프로세스 간의 최소 한의 이동 단위가 block이다.

커널에서 block 단위보다 작은 단위는 존재하지 않는다. !! HDD와 RAM사이의 교환은 block 단위로 일어난다.

사용자가 프로그래밍을 통해 1byte, 2byte 등의 단위로 읽기 쓰기를 요청한다고 가정할 때 실제 OS는 이런 단위로 IO를 하지 않는다. block 단위로 IO가 일어난다.

 

 

1-3. block의 뜻을 정리하기 위해 OS의 기본적인 개념 정리

아래에서 설명하는 동작 원리는 실제론 더 복잡한 작용을 하지만 OS 수업이 아니기 때문에 전체적은 큰 흐름과 그림만 본다.

buffered IO의 개념을 아는 것이 이 포스팅의 목적이다. buffered IO의 개념을 알기 위해 비유적으로 RAM의 동작 원리를 알면 차후 buffered IO의 개념을 이해하는데 도움이 되므로 아래 내용부터 보자.

 

우선 block 이란

파일 시스템에서 저장소의 가장 작은 unit을 대표하는 추상적 개념이다.

커널에서 모든 파일 시스템의 동작은 block 단위로 동작한다.

 

1) 프로그램이 HDD->RAM->cache->CPU 순서로 실행하는 원리 설명

예를 들어 우리가 짠 프로그램을 다시 생각해보자

HDD에 있는 우리가 짠 프로그램을 실행하면 RAM으로 load가 되고 이를 프로세스라고 부른다. 이 프로세스의 명령어가 CPU 내부의 캐시로 load되고 CPU는 이 캐시에 있는 명령어를 실행한다.

HDD에서 바로 CPUload 하면안되나?=> HDD는 느리기 때문에 이러면 CPU가 노는 시간이 크다. 그래서 생각해낸 것이 RAM이고 RAM도 큰 관점에선 일종의 buffer이다.

 

우리의 프로그램은 메인 메모리보다 훨씬 큰 경우가 많은데 이때 어떻게 하냐면 HDD 에 있는 명령어들을 쪼갠다. 쪼갤 때 page 단위로 쪼개며 이 page 단위의 실행 파일을 RAM에 적재가 되고 이 page1에 있는 명령어들이 CPU로 전달된다.

그럼 CPU는 명령을 실행하다가 만약에 RMA 에 있는 page의 명령어가 끝날 때 쯤 HDD에 있는 다음 page2 실행 파일을 RAM으로 로드하고 기존의 page1RAM에서 지운다.
이러면 CPU 입장에서는 컨베이어 벨트처럼 명령어가 계속 들어오게 될 것이다.

 

그리고 page가 크다면 page 내부에서도 block 단위로 나뉠 수 있다.

이말은 즉 page는 적어도 1개의 block 사이즈보단 크다. 그리고 1개의 page는 여러개의 block을 가질 수 있다.

 

2) CPU 캐시(cache)

CPU 입장에서는 RAM역시 느리다.

우리가 짠 코드는 지역성을 띈다. whilefor문과 같이 지역성으로 비슷한 명령을 주로 많이 호출한다. 이런 비슷한 명령은 CPU의 캐시에 자주 실행되던 명령어를 저장하고 캐시에서 바로 불러와 사용하는 것이다. 이러면 자주 사용되는 명령어를 RAM에서 계속 불러와 사용하는 것보다 더 빠르다.

 

 

1-4. block 정리

OSHDD에서 RAM으로 block 단위로 IO가 일으킨다.

HDD와 프로세스 간의 최소 한의 이동 단위가 block이다.

보통 블록 크기는 1024byte라고 일단 지금은 이렇게 정리하자.

 

그럼 IO 작업(RAM에 적재된 파일에 read, write) 은 block 단위로 일어나면 되는거 아닌가? => 그렇다면 베스트다. 하지만 파일의 수정이 block 단위로 이루어지나? 그것은 아니다.

예를 들어 설명하면, 학생 정보의 데이터가 저장된 시스템(파일)이 있다고 가정해보자

특정 학생의 정보의 이름을 수정한다고 해보자. 한글 이름 3글자는 기껏해야 6byte이며 이는 1024byte크기나 되는 block 단위보단 턱없이 작다. 이처럼 파일의 수정은 block 단위로 이루어지지 않고 그보다 더 작은 크기로 이루어질 수 있다.

이런 간단한 예제를 통해서만 봐도 block 단위의 read, write는 절대 일어나지 않는다는 것을 알 수 있다. 이런 갭을 줄이기 위한 개념이 드디어 나오는 buffered IO 개념이다.

 

 

1-5. buffered IO 간단 소개

위 정리를 통해 block 단위의 read, write는 일어나지 않는다는 것을 알 수 있었다.

사용자의 operationOS의 블록 단위의 operatino의 차이가 크다 즉

사용자 프로그램에서 수행하는 IO 작업과 OS에서 수행하는 IO 작업의 차이가 크단 소리다.

사용자 프로그램에서 수행하는 IO 작업은 6byte의 파일의 학생의 이름을 수정하는 것다.

OS에서 수행하는 IO 작업은 dirty buffer의 내용을 sync 하는 작업이다.

무슨 말이냐?

사용자가 위 예제에서 write를 하면 6byte가 바뀌며, OS에서 이 변경된 파일의 내용을 sync할 때는 block 단위로 IO 작업이 이루어진다고 하였는데

사용자의 변화는 6byte이지만 OS에서는 이를 sync 하기 위한 IO 작업은 1024byte 단위로 수행해야한다. 이런 갭이 크단 소리다. 이 갭을 줄이기 위해 나온 개념이 그래서 나온 개념이 buffered IO 개념이다.

buffered IO의 개념은 1-3.의 1)에서 정리한 RAM이 동작하는 모습과 거의 흡사하다.

 

사용자 프로그램에서 수행하는 IO 작업은 기껏해야 몇 byte (주로 32byte 내외)이다.

OS 수준의 IO 작업은 block 단위인 1024byte 단위이다.

이 사이에 bufferd IO가 존재하여 사용자 프로그램에서 수행하는 IO가 여러번 일어날 때마다

bufferd IO에 저장하며 일정 용량이 되었을 때 buffered IO에서 OSIO 작업을 수행한다.

라고 우선은 간단히 정리하였다. 아래에서 자세히 설명하겠음.


 

 

2. buffered IO

1) Buffered 설명

프로세스는 CPU 자원이 할당된 프로그램을 의미한다.

프로그램이 프로세스 형태로 메인 메모리에 적재되었다고 다 실행중인 것은 아니다.

프로세스는 CPU의 자원을 할당 받아야 진짜 프로세스가 되는 것이다. 따라서 프로세스의 정의는 CPU자원이 할당된 프로그램을 의미한다.

예를 들어 메인 메모리에 프로세스A, 프로세스B, 프로세스C 이렇게 올라와있다.

그 중에서 CPU자원이 할당된 것만이 현재 실제 실행중인 프로세스인 것이다.

 

먄약 프로세스 가 CPU 자원을 할당 받고 실행하고 있었다.

이때 프로세스 A가 파일을 load(HDD->RAM)을 한다고 해보자.

load 작업을 1개 하는 시간동안 CPU100개 이상의 명령어를 실행할 수 있는 시간인데 그럼 프로세스 A가 파일을load(HDD->RAM)을 하면 CPU는 그냥 쉬게 냅두나? => 아니다

이때 잠시 CPU의 자원이 다른 프로스세로 이동하면 된다.

그리고 프로세스 Aload(HDD->RAM)이 끝날 때 까지 기다리게 된다.

이것을 sleep 한다라고 한다. (CPU 자원이 다른곳으로 갔기 때문에 상관 없음)

 

, 이제 프로세스A에서 파일의 load가 끝났다. (필요한 모든 파일의 block HDD->RAM으로 load 되었다.) 그럼 CPU 자원이 다시 프로세스 A에게 할당되어 프로세스 A가 실행된다.

지금까지 가정으로는 이런 context switching이 너무 자주 일어날 것이다.

context switching할 때마다 즉 프로세스 A에서 B로 넘어갈 때 CPU 자원이 교대되는 순간에는 CPU는 아무것도 안하는 것이다. (비유를 하면 수학공부를 하다 영어공부를 하기 위해 수학 책을 덮고 영어책을 피는 과정에는 공부를 했다고 할 수 없다.)

context switching이 자주 일어면 CPU가 아무것도 안하는 시간이 늘어나게 되고 이는 CPU 성능의 감소를 의미한다. 따라서 우리는 CPU성능을 개선하는 방법을 생각해야한다.

IO 작업(RAM에서 데이터를 읽고 쓰는)시간이 좀 줄어들면 CPU 성능을 개선할 수 있지 않나? => IO시간 자체는 줄일 수 없다.

프로세스 AHDD에서 RAM으로 필요한 파일의 block을 load할 동안 기다리는 것이 아닌 load되기 전부터 버퍼의 내용을 사용자는 접근하는 것이다.

이것을 가능하게 해주는 이유가 buffer를 사용하기 때문이다.

 

 

여기까지 정리

HDD에 있는 파일에 있는 내용 중 1byte만 읽고 싶은 경우를 생각해보면 HDD에서 RAM으로 카피를 하는데, 메인 메모리에 있는 프로세스가 접근할 수 있는 메모리 위치에 copy가 된다. 하지만 실제로 1byteRMA으로 적재될 수는 없다.

OSblock 단위로 수행한다.

따라서 1byte에 딸려있는 하나의 block이 통체로 HDD에서 RAM으로 옮겨진다.

 

프로세스 Aload(HDD->RAM)을 하는동안 프로세스 A는 무엇을 하냐? 노냐? => 안논다.

load가 일어나는 동안 CPU는 다른 자원에게 할당되고 프로세스Asleep모드로 간다.

그리고 HDD에서 필요한 데이터가 block 단위로 RAM으로 load 된다. (OS의 역할)

이때 프로세스 A는 언제까지 sleep을 하냐?

HDD에서 RAM으로 block단위 load가 다 끝나고나서 sleep을 깨고 RAM에 접근하는건가?

=>아니다.

이러면 sleep 기간이 너무 길어져 CPU 성능에 좋지 못하기 때문에 buffer의 개념이 도입되어 필요한 데이터의 block 단위 load가 끝나기 전부터 미리 프로세스 A는 해당 데이터에 접근할 수 있다. (sleep 기간을 최대한 줄이고하 함.)

이것을 가능하게 해주는 것이 fopen, fread, fwrite API 메소드이다. 

 

 

2) fopenopen 비교

fopen

지금까지 위에서 설명한 스트림이 fopen함수를 통해 구현이 가능하다.

open을 사용하면 시스템에서 제공하는 버퍼만 이용하는 것이고 시스템의 버퍼만을 사용하면

block 단위의 load 작업이 끝날 때 까지 기다렸다가 프로세스가 IO 작업을 할 수 있다.

fopen은 이중 버퍼를 두어 시스템 버퍼의 내용을 fopen에서 제공하는 스트림을 버퍼를 통해 buffered IO 가 구현되는 것이다.

 

 

fopenopen의 차이점

open() 함수는 시스템 함수로 커널수준에서 동작합니다.

fopen() 함수는 라이브러리 함수로, 컴파일러에 의해 표준 라이브러리를 사용하여 동작하므로 어플리케이션 수준에서 동작합니다

 

이중 버퍼

fopen() 쪽의 함수는 C 표준 라이브러리에서 잡아주는 버퍼를 이용하며, open() 함수는 시스템에서 제공해 주는 버퍼를 사용한다.

C 표준 라이브러리인 fopen() 함수를 사용하게 되면 C 표준 라이브러리가 제공하는 버퍼와 함께 시스템에서 제공해 주는 버퍼를 사용하게 되어 실제 처리에서 이중 버퍼를 사용하는 것이다.

시스템 버퍼가 있음에도 다시 C 라이브러리에서 버퍼를 제공하는 이유는 효율성을 높이기 위해서 이다.

C 라이브러리에서는 버퍼를 하나 더 두고 시스템의 버퍼의 상태에 맞추어 입출력이 되도록 함으로서 프로그램 성능을 향상 시키고 안정성을 높여 주게 되는 것이다.

 

3) 정리

IO작업은 RAM 버퍼에 있는 파일 내용에 read, write를 하는 것이다.!

HDD에 파일이 있고 사용자는 read작업을 하고 싶다. 프로세스 명령 open이 있다. open을 하면 RAM에 있는 프로세스 버퍼 안에 파일 디스크립터를 만들고 HDDassociate를 즉 연관 관계를 만든다.

read, write는 실제로 해당 파일이 프로세스가 할당하는 RAM 버퍼에 load된다. 그리고 그 버퍼에 IO작업(read, write)를 하는 것이다.

그리고 RAM에서 HDD로 쓰는 것을 flush라 한다.

 

그럼 왜 스트림이라는 용어를 사용하나?

스트림이 없다면 시스템 버퍼(RAM의 버퍼)에 우리가 read하고자하는 데이터를 포함하는 1개의 block이 모두 RAM으로 올라오기 전까지 IO작업(RAM에서 읽고 쓰는) 작업을 할 수 없다.

하지만 스트림이라고 하는 buffered IO 방식을 통해 이를 극복할 수 있다.

스트림(데이터의 물줄기처럼 생각하면된다)

fopen으로는 스트림이 HDDRMA을 연결해주어 block 단위의 load가 다 끝나고 나서 읽는 것이 아닌 1byteHDD에서 RAM으로 load 될 때마다 바로 읽을 수가 있다. !!

 

 

4) buffered IO 메소드 정리(open, get)

fopen 메소드

#include <stdio.h>
FILE * fopen(const char *path, const char *mode);

 

 

r 읽기 전용 모드이다. 파일의 가장 시작점으로 파일 포인터가 간다. 
r+ 읽기 모드로 열긴하는데 추가로 write 작업도 한다는 의미이다. 파일 포인터는 시작점
w 쓰기 전용이다. 파일의 내용을 싹다 자운다. 만약 파일이 없으면 해당 파일을 생성한다. 
파일 포지션은 시작점이다. 
w+ 쓰기 모드로 열리지만 읽기를 추가작업도 한다는 것이다. 
a 파일의 젤 끝으로 파일 포인터가 간다. 
a+ 파일의 젤 끝으로 파일 포인터가 간다.  추가 작업으로 read도 한다. 

fopen은 파일 스트림을 가리키는 파일 포인터를 반환한다. 

#include <stdio.h>
FILE *stream;
stream = fopen("etc/manifest". "r");
if(!stream) 
	/*error*/

 

● fdopen 메소드

#include <stdio.h>
FILE *fdopen(int fd, const char *mode);

파일의 경로가 아닌 파일 디스크립터 즉 open으로 열려있는 파일 디스크립터를 받아 fopen이 가능하다. 

 

● fclose 메소드

#include <stdio.h>

int fclose(FILE *stream);

HDD의 파일의 내용이 스트림 형태로 버퍼에 load되는데 close하면 해당 스트림을 해제하는 것이다. 

즉 해당 파일 포인터가 가리키는 버퍼에 연계된 스트림을 close한다는 얘기이다. 

 

● fcloseall 메소드

int fcloseall(void)

 

이 명령을 실행시킨 프로세시의 모든 버퍼를 해제 시키는 것이다. 그리고 항상 return 값이 0이고, flush 작업을 한다. 

(writeback 작업을 한다.)

 

● fgetc, fgets메소드

int fgetc(FILE *stream);
char* fgets(char *str, int size, FILE *strema);

 

<fgetc 예시>

#include <stdio.h>
int c; 
c = fgetc(stream);

위예시는 버퍼 스트림에 load된 char 문자 하나를 읽는 것이다.  fgetc의 반환형이 int인 것은 1byte를 읽는 것이 맞지만 읽어들이기를 성공하면 읽은 문자를 리턴해야하고, 문제가 발생하면 문제에 대한 결과도 리턴해야하기 때문에 int 인 것이다. 

fgetc는 읽고나면 스트림에서 읽은 문자를 뺀다. 그럼 다음 문자가 스트림 처음으로 오게 된다. 

파일의 끝을 읽으면 fgetc는 EOF를 반환한다. 

 

<fgets 예시>

두 번째 방법의 fgets는 size-1만큼 stream으로부터 읽고 *str에 저장하는 것이다.

str 변수에 문자열을 넣고 끝에 null을 붙힌다. 

str 변수에 문자열을 넣고 끝에 null을 붙힌다. 

그리고 저장에 성공하면 주소 값을 반환하고 저장에 실패하면 null을 반환한다. 

예를 들어 arra[64] 이렇게 배열이 있다면 이 배열에는 63개의 char + null 이렇게 문자열이 구성된다.

fgets(array, 64, stream); 이렇게하면 63개의 문자를 stream에서 읽어서 array에 저장하는 것이 된다. 

 

만약 스트림에 4문자가 남았다. (4개의 글자+EOF) 그럼 4개의 글자만 읽고 str에 저장한다. 

(EOF는 파일의 끝을 알리는 것이다. 파일의 크기에 포함되지 않는다.)

만약 size가6이고 스트림에 4개의 문자 + \n이 있다면 4개를 일고 str에 저장하고 str 끝에 개행 문자(2byte를 넣는다.)

그리고 시스템 자체에서는 LINE MAS in <limit.h> 값 보다 큰 길이로 읽을 수는 없다. 

 

그리고 추가로 \n 줄바꿈 문자가 파일에 포함되어 있으면 gfetc는 줄 바꿈문자를 포함해서 줄바꿈까지만 읽고 읽은 수를 리턴함.

char *s;
int c = 0;
s = str;
while(--n > 0 && (c = fgetc(stream) != EOF &&(*s++ = c) !=d);
	if(c = d)
		*--s = '\0';
    else
    	*s = '\0';

위 예시 코드는 ABC$DEF$GHI 이런 파일에 문자열이 있다고 치자 $를 기준으로 whie문을 통해 문자열을 읽고싶다 하는 경우 delimiting($값)을 설정하고 fgetc로 while을 통해 읽어야함

 

변수 d의 값이 delimiting이다.  

 

 

● ungetc 메소드

int ungetc(int c, FILE *stream);

fgetc를하는데 읽은 문자를 빼지 않고 스트림으로 부터 1byte를 읽는다. 

 

 

● fread 메소드

size_t fread(void *buf, size_t size, sizt_t nr, FILE *stream);
char buf[64];
size_t nr; 

nr = fread(buf, sizsof(buf), 1, stream);
if(nr == 0)
	/*error*/

파일 스트림에서 size 단위로 nr횟수를 읽는다.  

따라서 위 예시는 스트림에서 64byte크기를 1번 읽으라는 것이다. 

 

 

리턴 값 경우1)

size 단위로 nr 회 읽기를 성공했다면 리턴 값은 nr이다.

 

리턴 값 경우2)

근데 만약  size값이 4byte씩 2번 총 8byte를 읽으라고 했으나 실제로 파일에 6byte가 있다면 6byte를 다 읽기는 하는데

4byte로 온전히 읽은 횟수는 1번이므로 리턴 값은 1이다.

 

리턴 값 경우3)

size가 2이고 nr이 100즉 2byte씩 100번 읽으라고 fread를 하였다. 파일에는 6byte가  있다 fread의 리턴 값은? => 3

 

 

fread는 줄 바꿈이 파일에 있어도 그냥 줄바꿈도 문자로 취급한다. \n역시 1byte로 걍 문자라고 생각하면 됨

<파일 내용>
abc
abc
fopen("./file.txt", "r");
int count = fread(str, 6, 1, in);
printf("%s\n", str);
printf("실제 읽은 수: %d\n", count);
<출력 결과>
abc
ab
실제 읽은 수 : 1

 

 

5) buffered IO 메소드 정리(put)

fputc

int fputc(int c, FILE *stream)

if(fputc ('p', stream) ==EOF)
/*error*/

스트림에 기록을 한다. 사용자가 램 버퍼에 IO 작업으로 write 작업을 하려고 한다. 일반 적인 write 시스템 콜을 사용하면 버퍼에 block 단위로 쓰기가 끝나면 HDD로 쓰여진다. 하지만 fputc 메소드를 사용하면 HDD와 버퍼를 스트림으로 연결하여 사용자가 fputc로 버퍼에 문자를 쓸 때마다 바로 HDD로 쓸 수 있게 된다. 

fputc멧든느 stream 변수로 연결된 파일에 문자 c를 쓰는 것인데 문자 쓰기에 성공했다면 쓴 해당 문자를 반환한다. 

 

만약 에러가 난 경우 errno의 값을 error number로 셋팅하고 EOF를 리턴한다. 

 

 

● fputs

#include <stdio.h>
int fputs(const char *str, FILE *stream);
FILE *stream;

stream = fopen("jornal.txt", "a"); //append 모드로 여는 것이다.
if(!stream) 
	/*error*/

if(fputs("The ship is made of wood. \n", stream) == EOF)
	/*error*/
    
if(fclose (stream) == EOF)
	/*error*/

const char* 변수에 저장된 문자열의 형태는 항상 null 값인 '\0'이 존재한다. 

따라서 문자열 끝에 null 문자를 가지는 char c[] 배열로 저장된 문자열이던 "string" 과 같은 일반 문자열이던

매개변수로 주게 되면 null 문자를 만나기 전까지 파일에 내용을 기록하게 된다. 

 

이 메소드 역시 읽기에 성공하면 쓰기에 성공한 문자열에서 문자의 개수를 반환한다.

만약 쓰기에 실패했다면 EOF를 반환하는데 errno의 값을 셋팅한다. 

 

참고로 아래와 같이 문자열에 \n문자가 있더라도 abc\nabc가 파일에 써짐

char str2[] ="abc\nabc";
out = fopen("./file.txt", "a");
fputs(str2, out);
<출력 결과>
abc
abc

 

● fwrite

#include <stdio.h>
size_t fwrite (void* buf, size_t size, size_t nr, FILE* stream);

buf 주소에 저장되 데이터를 size만큼 읽어서 stream에 nr번 기록해라라는 의미이다. 

 

buf : 쓰고 싶은 데이터

size : 쓰고 싶은 데이터의 단위 크기를 바이트 단위로 적는다. 

nr : 몇 개의 데이터를 저장할 것인지 개수를 적는다. 

stream : 데이터를 쓸 파일 포인터를 작성한다. 

 

<리턴 값 경우1>

stream 파일에 size*nr만큼 기록을 하고 만약 size의 값이 10이고 nr의 값이 5라고 할 때 파일에 데이터 쓰기를 10byte 씩 5번 쓰기를 성공했다면 5를 반환한다. 

 

<리턴 값 경우2>

스트림에 데이터가 20 byte 뿐이라면 10byte 를 온전히 쓴 횟수는 2번 이므로 2를 리턴한다. 

 

 

<예시>

//sizeof(temp)는 40byte일 때 쓰기 작업이 성공하면 1을 반환하고 쓰기 작업이 실패하면 0을 반환한다. 
fwrite(temp, sizeof(temp), 1, stream);

 

 

● fseek

int fseek(FILE* stream, long offset, int whence);

fopen을 다시 정리하면 fopen을 하게 되면 HDD의 실제 파일을 버퍼에 stream 형식으로 저장을 하는데 파일이 버퍼에 모두 저장되기 전에 read 또는 wriet를 할 수 있다. 

fseek이란 것은 whence위치에서 offset만큼 파일 포인터를 옮긴다. 

아래 표는 whence의 값을 정리함

SEEK_SET 파일의 처음부터 offset만큼 순방향(뒤로) 이동시킨다. 
SEEK_CUR 현재 파일 position위치에서 offset만큼 순방향 이동시킨다. 
SEEK_END 현재 파일 position을 제일 끝으로 이동시킨다. 이때는 offset값이 -3이라면 앞으로 이동시키는 것이다. 

그리고 이동에 성공하면 0을 반환하고 이동에 실패하면 EOF를 반환하고 errors의 값을 적당한 에러 코드로 설정한다. 

※ whence 값에 위 표 3가지 값 이외의 값이 오면 EINVAL errors를 발생시킨다. 

※ 스트림에 문제가 발생하면 EBADF errors를 발생시킨다. 

 

 

● rewind

void rewind(FILE* stream);

position을 스트림의 시작점으로 옮긴다.

따라서 위 값은 fseek(strea, 0 , SEEK_SET); 과 같은 결과임

 

 

● ftell

long ftell(FILE* stream)

파일 position의 위치를 반환한다.  에러시 -1을 반환한다. 

 

 

● fgetpos

int fgetpos(FILE* stream, fpos_t* pos);

파일 position의 위치를 반환하는데 pos 주소 값에 반환한다.  에러시 -1을 반환한다. 

 

 

● fflush

int fflush (FILE* stream);

스트림에 남아있는 값들을 RAM->HDD로 flush해준다.

 

 

● feof

int feof(FILE* stream);

현재 파일 position의 위치가 파일의 끝인지를 확인한다. "abcd(\0)" 이렇게 4byte 문자열과 끝에 eof가 있으면 offset은 a의 뒤 위치인 0부터 offset이 증가하는데 offset이 d와 \0사이에 위치하면 foef는 참을 반환함

아래와 같은 예제에서 많이 사용된다.

while(!feof(pFile)) {
  fgetc(pFile);
  n++;
}

 

 

● clearerr

void clearerr (FILE* stream)

error 코드를 clear 해준다. 

 

 

● ferror

int ferror(FILE *stream);

 파일 스트림에 에러가 발생했는지 테스트하는 함수이며, 파일 스트림에 에러 지시자가 설정 상태이면 0이 아닌 값을 가진다. 

 

 

● fileno

int fileno(FILE* stream);

 stdio.h에 선언이 되어 있는 함수다.

FILE 포인터를 넘겨주면, 그것과 연관된 파일 디스크립터를 돌려주는 역할을 합니다.


'전공 > 시스템 프로그래밍(운영체제)' 카테고리의 다른 글

9. Process Management  (0) 2021.11.11
8. Multy-Process와 Mutual Locking  (0) 2021.11.06
6. Blocking, non-Blocking  (0) 2021.10.17
5. 추가적인 System Call  (0) 2021.10.17
4. 시스템 콜: buffer 정리  (0) 2021.10.16