Music for reading(spotify)

write 시스템 콜에 대해

write는 기본적으로 ‘파일을 쓰는’ 시스템 콜이다. 해당 메모리에 존재하는 값을 일정 바이트 단위로 끊어서 저장한다. 사실 뭐 설명하려면 ‘부분 쓰기’나 ‘덧붙이기 모드’ 등 설명할 것은 많으나 이 정도로 끝내고, write가 어째서 그렇게 빠른 시간 안에 리턴이 가능한 지에 대해 이야기해보고자 한다.

한 번쯤은 생각해 봤을 이야기

write 시스템 콜은 빠르다. 심지어 자신의 저장 장치가 하드디스크임에도 엄청나게 빠르다. 한 번쯤은 생각해 봤겠지만, write는 분명히 데이터를 자신의 외부 저장소에 저장하는 작업임에도 불구하고 외부 저장소의 저장 속도보다 뛰어난 속도를 보여준다. 이상하지 않은가?

어째서 이런 일이 가능한가

write 시스템 콜은 사용자가 쓰기로 한 데이터를 가지고 있는 버퍼를 요구한다. 이 버퍼를 받아 버퍼 안의 데이터를 커널 버퍼로 복사하는데, 이 때 바로 버스를 통해 드라이브에 저장 요청을 하는 것이 아니라 커널 버퍼에 데이터를 복사하기만 하고 write 시스템 콜을 리턴한다. 따라서 실제 드라이브에 저장하는 것에 비해 압도적으로 속도가 빠르다.

그러면 이 정보에 대해 고려해서 사용해야 하는가

그것은 아니다. 내부적으로 write 콜을 사용하여 쓰고 다른 프로세스에서 이 데이터를 읽기 원한다면, 커널은 하드에서 읽어오는 것이 아니라 버퍼에서 이 새로 저장된 ‘따끈따끈한’ 데이터를 리턴해준다. 이런 작동 방식은 실제로 성능 향상을 이끌어낸다

그럼 뭐가 문제인가

애플리케이션에서 ‘실제로 하드디스크에 저장이 되었는지’ 확인 할 수가 없어 쓰기 순서가 강제되지 않는다. 커널은 이 버퍼를 순서 없이 저장해 뒀다가 배치로 수행하게 되는데, 이러면 프로세스가 비정상 종료될 때 실행하는 메서드가 만약 ‘마지막으로 하드디스크에 저장이 된 파일을 이용’하는 것이라면, 실제로는 데이터가 저장되어 있지 않기 때문에 문제가 된다.

하지만 가장 큰 문제는 ‘무언가 문제가 생겨 드라이브에 쓰기에 실패할 경우, 시스템 콜을 요청한 프로세스에게 결과 보고가 불가능’하다는 점이다. 버퍼에 포함된 데이터를 다중 프로세스가 변경하였을 수도 있고, 프로세스가 버퍼에 데이터를 쓰고 난 다음에 커널이 실제로 저장하기 전에 프로세스가 종료되어 버렸을 가능성도 있다.

그럼 비동기가 안 좋은 건가?

아니다. 비동기가 기본인 이유가 있다. 프로세스는 딱히 실제로 파일이 저장이 되었는지 확인할 필요가 크게 없고, 커널이 알아서 잘 해주기 때문이며, 버퍼에 담았다가 배치로 처리하는 것이 성능면에서 월등하기 때문이다. 그럼 비동기와 동기간의 성능 차이를 알아보자.

코드

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

int main(){
    int fd1, fd2, ret;
    clock_t start, end;
    double cpu_time_used_fd1, cpu_time_used_fd2;
    const char* str = "대충 로렌 입숨 데이터";
    ssize_t nr;
     
    fd1 = open("대충 파일 저장 위치 1", O_WRONLY);
    fd2 = open("대충 파일 저장 위치 2", O_WRONLY);

    start = clock();
    nr = write(fd1, str, strlen(str));
    end = clock();

    cpu_time_used_fd1 = ((double)(end-start)) / CLOCKS_PER_SEC;

    start = clock();
    nr = write(fd2, str, strlen(str));
    ret = fsync(fd2);
    end = clock();

    cpu_time_used_fd2 = ((double)(end-start)) / CLOCKS_PER_SEC;

    printf("====================================================");
    printf("Asynchronus file write time measure: %lf", cpu_time_used_fd1);
    printf("====================================================");

    printf("\n");

    printf("====================================================");
    printf("Synchronus file write time measure: %lf", cpu_time_used_fd2);
    printf("====================================================");

    return 0;
}

결과

Return result

결과를 보면 Synchronus가 훨씬 느린 것을 볼 수 있다. 아마 계산해 보면 ssd의 쓰기 속도가 나오지 않을까 싶다.

마치며

사실 딱히 신경쓸 필요는 없다. 알고 있으면 나중에 문제가 생겼을 때 찾기 편하다 수준?