운영체제

[OS] unix I/O와 standard I/O 함수

pobii 2025. 4. 12. 14:23

목차

  • unix I/O와 standard I/O
  • unix I/O 함수
    • open()
    • read()
    • write()
    • close()
    • perror()
  • standard I/O 함수
    • fopen()
    • fread() & fwrite()
    • feof() & ferror()
    • buffer와 fflush()
  • 정리

 


unix I/O와 standard I/O 

unix I/O는 저수준의 파일 처리 방식으로, 파일 디스크립터(fd)를 사용하여 파일을 읽고 쓰는 시스템콜이다.

반면 standard I/Ounix I/O를 사용해 만든 파일 입출력 라이브러리다. C 표준 라이브러리에서 제공한다.

unix I/O와 standard I/O의 함수들을 알아보자.

 


 

 

unix I/O 함수

아래 코드는 파일에 있는 문자열을 읽어 buf 배열에 입력하는 코드다.

코드에 사용된 함수들은 모두 unix I/O함수다. 하나하나 알아보자.

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {return -1;}
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {
    	if (write(1, buf, bytes) < 0) {return -1;}
    }
    close(fd);
    return bytes >= 0;
}

open()

파일을 열거나 생성하는 역할이다. 성공시 파일을 가리키는 번호를 리턴한다. 에러시 -1을 리턴한다.

이때 파일 번호는 파일 고유번호가 아니라 한 프로세스 내부에서만 유효한 일시적 번호다.

#include <fcntl.h>
int open(const char *path, int flags, mode_t mode);

 

 

아래는 open()의 매개변수 flags와 mode에 대한 설명이다.

 

- flags 종류

 

* O_CREAT | O_EXCL 조합하면?
락과 비슷한 기능을 한다. 누군가가 파일 이미 생성했는데 또 생성시도시 파일 생성 실패하도록 한다

 

- mode 표현법

mode는 해당 파일의 권한을 지정하는 역할이다.

rwxrwxrwx를 이진수로 나타내고 이를 8진수로 변경하여 표현한다.

flags에 O_CREAT가 없으면 생략 가능하다. 즉 파일 생성시에만 mode를 적어주면 된다.

 

아래는 mode 표현 예시다.



- open() 사용 예시

//해석: example 파일을, 생성해줘, write만 할 예정이야, 이 파일은 644권한을 가지게 해줘.
int fd = open("example.txt", O_CREAT|O_WRONLY, 0644);

 

 

처음 예시코드에서의 open 함수를 해석하면,

"argv로 받아온 파일을 읽기만 할 예정이다. 성공시 파일 번호를 리턴하고 에러시 -1를 리턴한다."

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {return -1;}	// 파일 열어 파일 번호 저장
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {
    	if (write(1, buf, bytes) < 0) {return -1;}
    }
    close(fd);
    return bytes >= 0;
}

 

 


 

Read()

파일을 읽는 역할이다. 성공시 읽은 byte 수를 리턴하고, 파일 끝에 도달하면 0을, 에러시 -1를 리턴한다.

#include <unistd.h>
int read(int fd, void *buffer, size_t bytes);

 

처음 예시코드에서의 read 함수를 해석하면,

"파일내용을 최대 1024byte만큼 읽어 buf에 저장한다. 성공시 읽은 byte수를 리턴한다."

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {return -1;}	// 파일 열어 파일 번호 저장
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {	// 최대 1024byte만큼 읽기
    	if (write(1, buf, bytes) < 0) {return -1;}
    }
    close(fd);
    return bytes >= 0;
}

 

 


write()

파일에 저장 or 출력 or 에러를 내보내는 역할이다. 도중 에러시 -1를 리턴한다.

#include <unistd.h>
int write (int fd, const void *buffer, size_t bytes);

 

- fd (file descripter)

파일을 가리키는 번호이며, 파일 고유번호가 아니라 한 프로세스 내부에서만 유효한 일시적 번호다.

0 : 입력 stdin

1 : 출력 stdout

2 : 에러 stderr

3 이상 : open() 으로 연 파일에 할당되는 번호

 

- 사용 예시

char buf[1024] = "hello";

write(1, buf, strlen(buf));	// "hello"를 출력한다
write(2, buf, strlen(buf));	// "hello"라는 에러문장를 출력한다
write(10, buf, strlen(buf));	// "hello"를 fd=10인 파일에 저장한다

 

 

처음 예시코드에서의 write 함수를 해석하면,

"읽어온 값을 출력한다. 에러시 -1을 리턴한다"

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {return -1;}		// 파일 열어 파일 정수값 저장
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {		// 최대 1024byte만큼 읽기
    	if (write(1, buf, bytes) < 0) {return -1;}		// 읽은 값 출력하기
    }
    close(fd);
    return bytes >= 0;
}

close()

열린 파일을 닫고 자원을 해제하는 역할이다. 자원을 반납하기 위해 fd 사용 후 반드시 호출해야 한다. 

#include <unistd.h>
int close(int fd);

 

처음 예시코드에서의 close의 역할은 다음과 같다.

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {return -1;}		// 파일 열어 파일 정수값 저장
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {		// 최대 1024byte만큼 읽기
    	if (write(1, buf, bytes) < 0) {return -1;}		// 읽은 값 출력하기
    }
    close(fd);							//파일 닫아 자원 해제하기
    return bytes >= 0;
}

 


 

perror()

에러의 원인을 리턴한다.

예시코드에 perror를 넣은 모습이다.

#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]){
    char buf[1024];
    int fd, bytes;
    if ((fd=open(argv[1], O_RDONLY)) < 0) {
    	perror("read");				// "read: Input/output error" 라고 출력됨
        exit(1);
    }
    while ((bytes = read(fd, buf, sizeof(buf))) > 0) {
    	if (write(1, buf, bytes) < 0) {
            perror("write");			// "write: Input/output error" 라고 출력됨
            exit(1);
        }
    }
    close(fd);
    return bytes >= 0;
}

 

 

 

 


standard I/O

unix I/O에서 사용했던 예시를 standard I/O 함수로 바꾼 버전이다.

#include <stdio.h>

int main(int argc, char *argv[]){
    char buf[1024];
    FILE *fp;
    int bytes;
    if ((fp = fopen(argv[1], "r")) == NULL) {return -1;}
    while (!feof(fp) && (bytes = fread(buf, 1, sizeof(buf), fp)) > 0) {
    	if (fwrite(buf, 1, byes, stdout) == 0) {return -1;}
    }
    return ferror(fp) || ferror(stdout);
}

 

fopen()

fopen은 unixI/O의 open()을 호출하는 함수다.

#include <stdio.h>

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

 

- mode

mode는 open()의 flag 매개변수와 비슷하다.

"r" : read only

"w" : write only, 기존 내용 다 지우고 새로쓰기

"a" : write only, 기존 내용 이어서쓰기

"r+" : read & write, 기존 내용 이어서 쓰기

"w+" : read & write, 기존 내용 다 지우고 새로쓰기


fread() & fwrite()

fread()는 read()를, fwrite()는 write()를 호출하는 함수다

unix I/O와 달리 file descripter 대신 file pointer를 사용한다.

#include <stdio.h>

size_t fread(void *dest, size_t size, size_t nmemb, FILE *fp);
size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *fp);

 


feof() & ferror()

파일의 끝 도달 여부, 에러 발생 여부를 반환하는 역할을 한다.

int feof (FILE *fp);	// 파일의 끝에 도달한 경우 0이 아닌 값 반환
int ferror(FILE *fp);	// 에러 발생한 경우 0이 아닌 값 반환

 


buffer와 fflush()

시스템콜을 실행하려면 유저모드에서 커널보드로 전환하는 과정때문에 시간이 소요된다.

따라서 standard I/O에서는 시스템콜을 최대한 적게 호출하기 위해 buffer를 사용한다.

 

- Write Buffering

write할때 write 시스템 콜을 적게 호출하여 실행 시간을 줄이기 위한 방법이다.

ex) "Hello"라는 데이터를 입력받을때 한글자 입력할때마다 write 호출하는 방식 대신 버퍼에 모든 글자 입력받길 기다렸다가 한번에 write한다.

 

- fflush()

buffer에 있는 모든 데이터를 강제로 파일에 기록하고 buffer를 비우는 함수다.

int fflush(FILE *fp);

 

write buffering의 문제점을 보완해주는 방법이다.

ex) 프로그램이 "Hello"를 저장하다가 "Hel"만 저장하고 종료되는 문제가 발생할 수 있다. 이때 fflush()를 통해 "Hello"를 모두 저장 후 종료한다.

 

사용 예시

#include <stdio.h>

int main() {
    FILE *fp = fopen("example.txt", "w");
    fprintf(fp, "Hello");	// "Hello"가 버퍼에 기록됨
    fflush(fp);		// "Hello"가 fp에 기록되고 버퍼 비워짐
    fclose(fp);
    return 0;
}

 

 


 

정리

unix I/O는 저수준 파일 처리 방식으로, 파일을 읽고 쓰는 시스템콜이다.

해당 함수들은 파일 디스크립터(fd)를 사용한다. 

관련 함수들로는 open(), read(), write(), close(), perror() 등이 있다.

 

standard I/O unix I/O를 사용해 만든 파일 입출력 라이브러리이며 C 표준 라이브러리에서 제공한다.

해당 함수들은 FILE * 타입을 사용하며, 버퍼를 통해 성능을 최적화한다.

관련 함수들로는 fopen(), fread(), fwrite(), feof(), ferror(), fflush() 등이 있다.