파일이 작성된 후 언제 실제 크기를 반영합니까?

파일이 작성된 후 언제 실제 크기를 반영합니까?

(아마도) libparquet/libarrow의 StreamWriter를 사용하여 주기적으로 파일에 쓰는 프로세스가 있습니다(~65MB마다 써야 함). 예를 들어 쉘을 통해 파일을 쿼리할 때 watch du -sh {file_name}크기가 증가하지 않는 것 같습니다. 그러나 프로세스가 완료되면 파일이 완전히 작성된 것처럼 보입니다.

여기서 무슨 일이 일어나고 있는 걸까요? 프로세스가 파일을 열고 끝날 때까지 계속해서 쓴 것으로 추측하는데, 파일의 실제 크기는 언제 사용자(또는 OS)에 반영됩니까? 파일이 최종적으로 닫힌 이후인가요?

예를 들어 Python에서 파일에 반복적으로 쓸 때 셸 with open('file.txt','w') as fp을 통해 또는 셸에서 파일을 쿼리하면 파일의 실제 크기가 반영됩니다. 이 Python은 단지 배후에 추가 단계를 추가하는 것입니까?lsdu

답변1

프로그램에서 어떤 방법을 사용하는지 지정하지 않으면 I/O를 수행하는 방법이 많고 그 과정에서 많은 문제가 발생할 수 있으므로 대답은 "매우 빠르거나 전혀 그렇지 않음"입니다. 또한 다양한 운영 체제나 파일 시스템으로 인해 상황이 복잡해질 수 있으므로 여기서는 논의하지 않습니다.

일부 시스템에서는 아래에 표시된 테스트 프로그램을 컴파일하기 위해 임의의 매직 문자열을 정의해야 할 수도 있습니다. 예를 들어 feature_test_macros(7)일부 Linux 버전을 참조하세요. 현재 많은(모든 사람은 아니지만...) 제공하는 C99 컴파일러가 필요할 수도 있습니다.

쓰다

write(2)매우 간단합니다. 버퍼링이 부족하고 이 코드에서 다른 일이 발생하지 않기 때문에 이 호출의 출력은 스펙트럼의 더 빠른 끝 부분에 표시되어야 합니다(모든 것이 잘 진행되는 경우). 이러한 스크립트를 ktrace다른 strace유사한 시스템 호출 추적 유틸리티 에서 실행하는 것이 교육적일 수 있습니다.

// write.c -- `rm out; make write && ./write > out`
#include <stdio.h>
#include <unistd.h>
int main(void) {
    char buf[] = "this is some output\n";
    //while(1) {
    write(STDOUT_FILENO, buf, sizeof(buf) / sizeof(char));
    //}
    return 0;
}

더 많은 출력을 위해 무한 루프의 주석 처리를 제거하고 sleep(3)"파일 시스템이 무엇을 표시합니까?" 테스트에 도움이 되도록 속도를 늦추는 호출을 추가합니다.

버퍼링...버퍼링...버퍼링...

실제 프로그램(TM)은 버퍼링된 I/O를 사용할 수 있으며 다른 일(부풀림)이 발생할 수 있습니다. 여기서는 버퍼링에 대해서만 설명하겠습니다. 첫째, 출력을 전혀 내보내지 않는 프로그램:

// never.c -- `rm out; make never && ./never > out`
#include <stdio.h>
#include <unistd.h>
int main(void) {
    setvbuf(stdout, 0, _IOFBF, 0);
    fputs("x", stdout);
    _exit(0);
}

이는 전체 버퍼링을 선택하지만 아무것도 플러시하지 않고 종료됩니다. 이를 달성하는 데는 다른 많은 방법이 있습니다. 예를 들어 사람들은 kill -9segfaults 등에 관심이 있습니다. 듀얼 프로세서 Linux와 같이 시스템이 매우, 매우, 매우 바쁜 경우 시스템의 CPU 로드가 약 5,000이면 일반적으로 빠른 출력이 "거의 없음"으로 바뀔 수 있습니다.

여기서 간단한 수정은 파일 시스템에 대한 지연된 출력을 시뮬레이션하기 _exit전에 호출 exit을 추가하도록 변경한 다음 버퍼 크기를 채우기 위해 출력을 지연시키기 위해 절전 모드가 포함된 무한 루프를 추가하는 것입니다. sleep그럼에도 불구하고 버퍼링을 통한 파일 시스템의 출력 크기는 프로그램 쓰기 크기보다 지연될 수 있습니다.

임시 파일 이름 바꾸기

또 다른 방법은 임시 파일에 쓴 다음 I/O가 완료되면 임시 파일의 이름을 실제 파일로 바꾸는 것입니다. 이렇게 하면 판독기가 절반만 작성된 출력 파일을 보는 문제를 피할 수 있습니다. 이 경우 출력 파일은 호출 이후에만 새 크기를 반영합니다 rename(2).

// rename.c -- `rm out*; make rename && ./rename & sleep 1; ls -l out*`
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
    char *tmpbuf  = strdup("out.XXXXXXXXXX");
    char *tmpfile = mktemp(tmpbuf);
    if (!tmpfile) err(1, "mktemp failed");
    FILE *fh = fopen(tmpfile, "w");
    if (!fh) err(1, "fopen failed");
    fputs("this is some renamed output\n", fh);
    fclose(fh);
    sleep(9);
    rename(tmpfile, "out");
    //free(tmpbuf);
    return 0;
}

여기서 out파일은 약 9초 후에 새 크기를 반영해야 합니다. 또는 시스템이 매우 바쁜 경우에는 아주 많은 초가 남습니다. 또는 이름 바꾸기가 완료되기 전에 어떤 일이 발생하지 않도록 하세요.

메모리 맵

I/O를 수행하는 또 다른 방법은 메모리 매핑을 이용하는 것입니다. 그러나 이 동작을 변경하는 플래그가 있을 수 있지만 버퍼링의 징후는 표시되지 않습니다.

// mmap.c -- `make mmap && ./mmap & sleep 1; od -bc out`
#include <sys/mman.h>
#include <err.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(void) {
    char buf[]    = "this is some mmap'd output\n";
    size_t buflen = sizeof(buf) / sizeof(char);
    int fd        = open("out", O_RDWR | O_CREAT, 0666);
    ftruncate(fd, buflen);
    if (fd < 0) err(1, "open failed");
    void *ptr = mmap(0, buflen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED) err(1, "mmap failed");
    sleep(9);
    memcpy(ptr, buf, buflen);
    munmap(ptr, buflen);
    close(fd);
    return 0;
}

I/O가 완료된 후 메모리 매핑된 파일의 이름을 임시 파일에서 예상 파일로 바꿀 수 있습니다(이전 팁 참조). 여기에는 타당한 이유가 있을 수 있습니다.

다른

위와 다른 시스템 호출을 사용하는 다른 형태의 I/O가 있을 수 있습니다. strace무엇을 찾아야 할지 알고 있다면 그 내용이 드러날 것입니다. 파일 시스템의 출력 표시를 지연시킬 수 있는 버퍼링이 있을 수 있습니다. 간단한 스크립트를 작성하면 호출이 어떻게 작동하는지 보여주는 데 도움이 되며, 이는 더 큰 프로그램에서 무엇을 찾아야 하는지 보여주는 데 도움이 됩니다 strace. 문서가 있기를 바랍니다.

더 높이, 더 깊게 쌓아올려

표준 라이브러리 위에 구축된 언어는 운영 체제에서 제공하는 것 외에도 자체 버퍼를 가질 수 있지만 일반적으로 비트를 디스크로 전송하려고 시도하는 호출이 하나 이상 있을 flush수 있습니다. 사용자가 할 수 있는 것처럼 기본 동작을 조정하는 방법입니다 . 아니면. 일부 언어나 프로그래밍 가이드에서는 운영 체제와 다른 기본값을 설정할 수 있습니다. 버퍼링된 출력은 파일 시스템에 도달하는 데 지연이 표시되거나 이름 바꾸기 트릭이 사용되고 있을 수 있습니다.(finish-output)syncsetvbuf(3)

관련 정보