[OS] unix I/O와 standard I/O 함수
목차
- 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/O는 unix 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() 등이 있다.