더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.
https://reakwon.tistory.com/233
파일입출력
리눅스 상에서 파일에 대한 입출력을 소개합니다. c언어에서의 라이브러리가 아닌 시스템 콜에는 open, read, write가 있는데요. 이것을 잘 포장하여 만든것이 fopen, fread, fwrite라는 함수랍니다. 이제 우리는 원시적인 시스템 콜을 가지고 파일에 대한 입출력을 하려고 합니다. 한가지 알아두셔야할것은 read, write는 버퍼링 없는 입출력 시스템 콜이라는 것입니다. 내부적으로 버퍼링을 하지 않기 때문에 read나 write시에 매번 커널 안의 시스템 호출이 실행이 됩니다. 반면 printf와 같은 라이브러리 함수는 버퍼링을 사용한 출력 함수입니다.
1. open
int open(const char *pathname, int flags, ... /* mode_t mode */ );
int openat(int fd, const char*pathname, int oflag, ... /* mode_t mode */ );
파일은 열 수 있는 두개의 시스템 콜이 있습니다. open과 openat이 그것들입니다. 여기서는 open 함수만을 설명하도록 하겠습니다. 가장 오른쪽 인자 mode는 주석이 처리되어있고 주석 시작 앞에 ...을 볼 수 있는데요. ...은 가변인자를 의미하고 있을 수도 있고, 없을 수도 있다는 것을 의미합니다. 있다면 mode에 인수를 지정해야합니다.
pathname : 파일의 경로와 이름입니다. 절대경로의 파일명을 주어도 되고 상대경로의 파일명을 주어도 됩니다.
flags : 파일을 어떻게 열지를 결정합니다. 읽기 전용으로 열때는 O_RDONLY, 쓰기 전용으로 열때는 O_WRONLY, 읽기 쓰기로 열고 싶을때는 O_RDWR을 사용합니다. 이 밖에도 O_EXEC, O_SEARCH 등이 있습니다. 이 다섯개 중 하나는 반드시 지정돼야 있어야합니다. 아래의 표로 다시 정리해드리죠.
flag | 설명 |
O_RDONLY | 파일을 읽기 전용으로 엽니다. |
O_WRONLY | 파일을 쓰기 전용으로 엽니다. |
O_RDWR | 파일을 읽기, 쓰기 전용으로 엽니다. |
O_EXEC | 파일을 실행 전용으로 엽니다. |
O_SEARCH | 파일을 검색 전용으로 엽니다. 디렉토리만 해당합니다. |
이 밖에도 자주쓰이는 flag들에 대해서는 아래를 참고하세요.
flag | 설명 |
O_CREAT | 파일이 없으면 생성합니다. 이 옵션을 사용할때는 mode 인자를 추가로 지정해야합니다. 파일의 권한을 설정해야하기 때문이죠. |
O_EXCL | O_CREAT과 같이 사용되며 파일이 이미 존재하면 에러나 파일을 여는데에 실패합니다. |
O_TRUNC | 파일이 이미 존재한다면 이미 존재하는 파일의 내용을 무시하여 엽니다. 파일을 처음부터 쓴다는 flag입니다. |
O_APPEND | 파일에 내용을 추가할 수 있는 옵션입니다. |
O_NONBLOCK O_NDELAY |
파일을 블록모드가 아닌 비블록모드로 엽니다. 기본적으로 파일은 블록모드입니다. 입력이 없을때는 입력할때까지 기다린다고 이해하시면 됩니다. |
O_DIRECTORY | pathname이 디렉토리가 아니면 오류를 발생시킵니다. |
O_CLOEXEC | FD_CLOEXEC 파일 서술자 flag를 설정합니다. exec류 함수 호출시에 파일을 close한다는 flag입니다. |
O_NOFOLLOW | pathname이 기호링크(symbolic link) 가리키면 오류를 발생시킵니다. |
이 밖에도 여러 flag들이 쓰이는데, 여기서는 이정도만 설명하고 넘어가도록 하겠습니다. 그리고 여러개의 flag들을 사용하고 싶을때는 | (OR) 연산을 사용합니다. 예를 들어 읽기 쓰기 모드로 열고 싶은데 파일이 없으면 생성하는 flag는 O_RDWR| O_CREAT로 사용하면 됩니다.
mode : 파일을 생성할때의 권한을 주는 옵션입니다. O_CREAT과 같이 파일을 생성할때 사용합니다.
만약 0666을 주게 된다면 모두 읽기, 쓰기 권한을 가진 파일을 만들게 되는 것이죠. 현재 umask에 따라 생성 권한이 다르게 나타나겠지만, 정확히 확인하기 위해서는 umask(0)으로 umask를 해제한 이후에 O_CREAT을 통해 실제 생성한 파일의 권한을 확인할 수 있습니다. umask를 해제하면 file은 기본적으로 0666 권한으로 생성이 됩니다.
또한 가독성을 높이기 위해 심볼릭 상수도 제공하고 있습니다. 심볼릭 상수를 사용할때 역시 | 연산으로 권한을 줄 수 있습니다.
심볼릭 상수는 <fcntl.h> 헤더파일에 존재하고 아래와 같습니다.
상수 | 설명 |
S_IRUSR | 소유자 읽기 권한 설정 |
S_IWUSR | 소유자 쓰기 권한 설정 |
S_IRGRP | 소유자 그룹 읽기 권한 설정 |
S_IWGRP | 소유자 그룹 쓰기 권한 설정 |
S_IROTH | 일반 유저의 읽기 권한 설정 |
S_IWOTH | 일반 유저의 쓰기 권한 설정 |
반환값 : 성공적으로 파일을 열게되면 파일 디스크립터를 반환합니다. 그렇지 않으면 음수를 반환합니다.
2. read
ssize_t read(int fd, void* buf, size_t len);
fd : 파일 디스크립터입니다. open은 정상적으로 파일을 열면 그 파일에 파일디스크립터를 반환하죠? 그 파일디스크립터를 써주면 됩니다. 하지만 표준 입력과 표준 출력, 표준 에러는 각각 순서대로 0, 1, 2가 되므로 표준 입력으로 읽어들일때는 0을 써주면 됩니다.
buf : 파일에서 읽어들일 버퍼를 말하고 있습니다. 어떤 자료형으로 읽어올지 모르므로 void*로 매개변수로 받습니다.
len : 얼마만큼 읽어올지를 결정합니다.
반환값 : 정상적으로 파일에 대한 내용을 읽어온다면 읽은 바이트수를 반홥합니다. 즉 len의 값을 반환하죠. 그렇지 않다면 0을 반환합니다.
3. write
파일에 내용을 기록합니다. 사용방법은 read와 거의 비슷합니다.
size_t write(int fd, const void *buf, size_t nbytes);
fd : 파일 디스크립터랍니다. read의 파일디스크립터를 받는 것처럼 사용합니다.
buf : 버퍼에 쓸 내용을 말합니다.
nbytes : 실제 버퍼에서 얼마만큼의 길이를 파일에 쓸것인지를 결정합니다.
반환값 : 올바로 write에 성공했다면 쓰여진 bytes수, 그렇지 않다면 -1을 반환합니다.
파일 쓰기, 읽기
이제 이것을 바탕으로 표준 입력으로 내용을 입력받아 파일에 내용을 기록할 겁니다. 그리고 난 후에는 쓴 파일에서 그 내용을 읽어 표준출력으로 출력할 겁니다.
여기에 그 코드가 있습니다.
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#define BUF_SIZE 1024
int main(){
int fd,n;
char buf[BUF_SIZE];
umask(0); //0666의 권한을 확인하기 위해 umask해제
//file.txt를 읽기,쓰기로 생성하고(기존에 있다면 내용무시), 0666 권한으로 엶
fd=open("file.txt",O_CREAT|O_RDWR|O_TRUNC, 0666);
if(fd<0){
printf("file open error\n");
exit(1);
}
n=read(0,buf,BUF_SIZE);
n=write(fd,buf,n);
if(n<0){
printf("file write error\n");
exit(1);
}
lseek(fd,0,SEEK_SET);
n=read(fd,buf,BUF_SIZE);
if(n==0){
printf("this file is empty\n");
exit(1);
}
n=write(1,buf,n);
if(n<0){
printf("file write error\n");
exit(1);
}
close(fd);
}
fd=open("file.txt",O_CREAT|O_RDWR|O_TRUNC, 0666);
우선 파일을 열어야겠죠? open 시스템콜을 이용합니다. 파일명은 file.txt이고, 파일이 없으면 만들고 읽기쓰기 전용으로 엽니다. 그리고 파일의 내용이 있다면 그 내용을 잘라내고 씁니다.
n=read(0,buf,BUF_SIZE); n=write(fd,buf,n);
파일을 여는데에 성공했다면 표준입력(키보드)로 데이터를 읽어옵니다.
그 이후 file.txt에 그 내용을 기록합니다.
lseek(fd,0,SEEK_SET);
기록을 했다면 파일 포인터는 파일의 맨끝을 가리키고 있겠네요. 그러니 lseek이란 함수를 통해서 파일 포인터의 위치를 가장 처음으로 위치시킵니다.
lseek에 대해서 간단히 설명하자면 파일이 어디부터 읽고 쓰는지에 대한 위치를 변경하는 함수라고 기억하시면 됩니다.
파일 디스크립터 fd를 갖고 처음(SEEK_SET), 현재 위치(SEEK_CUR), 끝 (SEEK_END)을 일단 정하고 난 후에 상대적인 값을 통해 파일 포인터의 위치를 결정합니다.
n = read(fd, buf, BUF_SIZE);
if (n == 0) {
printf("this file is empty\n");
exit(1);
}
n = write(1, buf, n);
이제 read를 통해서 file.txt에서 내용을 읽고 표준 출력으로 출력하는 프로그램입니다.
어때요? 이해 되셨나요?
이제 컴파일하고 실행시킵시다. 그리고 file.txt라는 파일에 기록이 됐는지 확인해봅시다.
[root@localhost ~] # gcc file.c
[root@localhost ~] # ./a.out
Hello, world!
Hello, world!
[root@localhost ~] # cat file.txt
Hello, world!
파일에도 기록이 잘 되어있고 출력도 잘 된것을 확인할 수 있습니다. 그리고 파일의 권한도 확인해볼까요?
# ls -al file.txt
-rw-rw-rw- 1 root root 12 4월 1 15:59 file.txt
저희가 생성했던 0666(-rw-rw-rw-) 권한으로 생성되었음을 확인할 수 있네요.
파일 다루는 방법 어렵지 않죠!?
'컴퓨터 > 운영체제(주로 리눅스)' 카테고리의 다른 글
[리눅스] 시그널(Signal) 3 sigpending, sigismember, sigsuspend (0) | 2018.12.31 |
---|---|
[리눅스] 시그널 (SIGNAL) 2 시그널 함수 sigprocmask, sigfillset, sigemptyset, sigaddset, sigdelset (11) | 2018.12.31 |
[리눅스] 시그널1 신호(SIGNAL)의 개념과 자세히 들여다 보기 - 시그널 핸들러 (0) | 2018.12.10 |
[리눅스] 프로세스 생성과 특징, 종료 (fork, wait), 예제 코드 (0) | 2018.12.10 |
[리눅스] 파일 정보 얻어오기(lstat), 사용 예제 (0) | 2018.11.19 |