3. 파일 쓰기 - write()
System/System Programming

3. 파일 쓰기 - write()



write()로 쓰기

  파일을 열고, 읽는 것까지 했다. 이번 포스팅에서는 write() 시스템 콜을 이용하여 파일에 사용자가 넣고 싶어하는 내용을 써보자. write() 시스템 콜은 파일에 데이터를 기록하기 위해 가장 기본적인 시스템 콜로, read()와 반대 개념이며 똑같이 POSIX.1에 정의되어 있다. read()의 기본 형태는 다음과 같다.

1
2
3
#include <stdio.h>
 
ssize_t write(int fd, const void *buf, size_t count);
cs

  코드를 보면 알 수 있듯이 반환값은 ssize_t 형이다. (이에 대해서는 이전 포스팅에서 이미 설명을 했었다.) write() 호출은 count 바이트만큼 파일 디스크립터(fd)가 참조하는 파일의 현재 파일 위치에 시작 지점이 buf인 내용을 기록한다. 파일 디스크립터가 표현하는 객체에 탐색하는 기능이 없다면, 쓰기 작업은 기본적으로 항상 파일의 처음 위치에서 시작한다.


  write() 호출에 성공하면 쓰기에 성공한 바이트 수(ssize_t)를 반환한다. 더불어 파일 오프셋도 반환한 바이트 수만큼 앞으로 나아간다. 이전 포스팅들에서 다루었던 다른 시스템 콜들과 마찬가지로 write() 시스템 콜 또한 에러가 발생하면 -1을 반환하고, errno를 상황에 따라 적절한 값으로 설정한다. 0을 호출할 경우에는 별 의미 없으며, 단순히 0바이트를 썼다는 의미이다. 여담으로 count를 0으로 둔 상태에서 write() 시스템 콜을 호출하면 호출 즉시 0을 반환한다.


  write()의 기본적인 사용법을 알아보자.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
 
int main() {
    int fd = open("TEXT", O_RDWR | O_CREAT, 0666);
    if(fd == -1) {
        fprintf(stderr, "Failed to open file.\n");
        exit(0);
    }
 
    const char *buf = "Welcome to VALLHALLA!\n";
 
    // writes buf --> fd
    ssize_t nr = write(fd, buf, strlen(buf));
    if(nr == -1) {
        fprintf(stderr, "Failed to write file.\n");
        exit(0);
    }
    else printf("Writing Success!\n");
 
    return 0;
}
cs


  TEXT라는 이름의 파일을 일단 열고,[각주:1][각주:2] buf에 저장되어 있는 문자열 "Welcome to VALLHALLA!\n"를 파일 디스크립터에 대해 쓰기 작업을 해서 TEXT 파일에 해당 내용을 작성하는 간단한 코드이다. 실행 결과도 같이 한 번 보자.




  성공적으로 파일이 생성되고, 거기에 우리가 원하는 문자열이 저장되는 것을 볼 수 있다.


  하지만 앞에서 제시한 코드는 언제까지나 write() 시스템 콜 사용법을 알기 위한 '기본적인' 코드인 거지, 효율성을 따진다면 그리 좋지 못하다. 예외가 아직 존재하기 때문이다. 예를 들면 부분 쓰기가 일어나는 상황을 대비하여 코드를 점검해야 할 필요성이 있다.


부분 쓰기


  write() 시스템 콜은 read() 시스템 콜에서 발생하는 부분 읽기와 비교해서 부분 쓰기를 일으킬 가능성이 훨씬 적다. 게다가 read()에서와는 달리 write()에는 EOF 조건도 없다. 그냥 추가해서 써버리면 되니까! 일반 파일에 쓰이는 write()는 에러가 발생하지 않는 한 웬만하면 쓰기 작업을 안전하게 보장받을 수 있다. 그렇기 때문에 일반 파일을 대상으로 write() 시스템 콜을 사용할 경우 굳이 루프를 사용해서 반복적으로 write()하지 않아도 된다.

  하지만 소켓 파일(s) 같은 특수한 파일 같은 것들은 상황에 따라서 루프가 필요할 수도 있다. 반복되는 write() 호출이 숨어 있던 에러 코드를 잡아낼 수 있기 때문이다. 앞에서 제시한 코드의 약간 수정해보자.

1
2
3
4
5
6
7
8
9
10
ssize_t ret, nr;
while(len != 0 && (ret = write(fd, buf, len)) != 0) {
    if(ret == -1) {
        if(errno == EINTR) continue;
        perror("write");
        break;
    }
    len -= ret;
    buf += ret;
}
cs


덧붙이기 모드



Icons made by Smashicons from www.flaticon.com is licensed by CC 3.0 BY


  지금까지 write() 시스템 콜의 기본적인 사용법에 대해 알아봤는데, 지금까지의 실습은 원래 파일에 저장되어 있던 내용을 무시하고 다시 처음부터 새로 내용을 쓰는 방식이었다. 이는 기본적으로 write() 시스템 콜이 파일 디스크립터의 현재 파일 오프셋(파일의 처음)에서부터 쓰기 작업을 하게 하기 때문이다. 여기서 O_APPEND 옵션을 사용해서 파일 디스크립터를 덧붙이기 모드로 열 수 있는데, 이 경우 파일 끝에서부터 쓰기 연산을 할 수 있다.

  앞에서 write() 시스템 콜 예제로 썼던 코드의 10번째 행을 다음과 같이 수정하고, 컴파일한 후 실행해서 TEXT 파일 내용의 변화를 확인해보자.

1
int fd = open("TEXT", O_RDWR | O_CREAT | O_APPEND, 0666);
cs



  덧붙이기 모드는 파일 오프셋이 항상 파일 끝에 위치하도록 설정하기 때문에 다중 쓰기 작업 시에도 항성 덧붙이기로 파일에 내용을 작성할 수 있다. 로그 파일 갱신 등에서 이런 뛰어난 효과를 발휘한다.

논블록 쓰기


  O_NONBLOCK 옵션은 파일 디스크립터가 논블록 모드로 열린 상태에서 쓰기 작업이 블록될 경우, write() 시스템 콜은 -1을 반환한다. 이때 반환되는 errno는 EAGAIN으로 설정된다. 이 경우 나중에 쓰기 요청을 다시 해야 한다. 다행히 일반 파일에서는 대체로 일어나지 않는 상황이다.

그 외 에러 코드


  write() 시스템 콜에서 쓸만한 errno 값들은 다음과 같다.

에러 코드

설명

EBADF

 파일 디스크립터가 유효하지 않거나 쓰기 모드가 아니다.

EFAULT

 buf의 포인터가 호출하는 프로세스 주소 공간 안에 있지 않다.


EFBIG

 쓰기 작업이 프로세스 단위로 걸려 있는

 최대 파일 제약이나 내부 구현 제약보다

 더 큰 파일을 만들었다.

EINVAL

 파일 디스크립터가 쓰기에 적합하지 않은 객체에 매핑되어 있다.

EIO

 저수준 입출력 에러

ENOSPC

 파일 디스크립터가 들어 있는 파일시스템에 공간 부족


EPIPE

 파일 디스크립터가 파이프와 소켓에 연결되어 있지만,

 반대쪽 읽기 단이 닫혀버렸다.

 또한, 프로세스는 SIGPIPE 시그널을 받는다.[각주:3]



  1. 이 이름의 파일이 없다면 O_CREAT로 빈 파일을 새로 생성한다. [본문으로]
  2. 이때 생성되는 TEXT 파일의 권한은 rw-r--r--. [본문으로]
  3. SIGPIPE 시그널의 기본 동작은 수신 프로세스 종료이므로 명시적으로 이 시그널을 무시하거나 블록하거나 처리하도록 요청할 경우에만 프로세스 쪽으로 이 errno 값이 넘어온다. [본문으로]

'System > System Programming' 카테고리의 다른 글

6. 파일 닫기 - close()  (0) 2019.04.24
5. 직접 입출력  (0) 2019.04.24
4. 동기식 입출력 - fsync(), fdatasync()  (2) 2019.04.22
2. 파일 읽기 - read()  (0) 2019.04.15
1. 파일 열기 - open(), creat()  (0) 2019.04.11