좋은 시스템 프로그래밍 문서 소개

gurugio의 이미지

http://people.redhat.com/drepper/defprogramming.pdf

늘 헛소리만 쓰더가 첨으로 정보가 될만한 글을 써봅니다.

여러 프로세스가 동일한 파일 (로그 파일같은..)에 데이터를 쓰고 있었습니다.
그런데 한 놈이 파일을 지우거나 이름을 바꾸면 어떻게 될까요?
이런 의문에서 시작되서 위의 문서를 읽고 개과천선을 했습니다.

제 시나리오는 이렇습니다.

자식 프로세스가 파일을 열고 락을 걸고, 데이터를 쓰고, 파일의 이름을 바꿉니다.
부모 프로세스는 파일을 열고 락을 대기하지요.
부모 프로세스가 락을 얻은 다음에는, 파일의 크기가 0으로 바뀐 것을 알았으면 좋겠습니다.

아래 소스를 만들어서 실행해봤습니다.

#include <stdio.h>
#include <unistd.h>
 
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
 
#include <string.h>
#include <wait.h>
 
 
int file_lock(int fd)
{
    struct flock lock;
    int rc;
 
    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0;
    lock.l_pid    = 0;
    lock.l_type   = F_WRLCK;
 
    if ((rc = fcntl(fd, F_SETLKW, &lock)) != 0)
    {
        return errno;
    }
 
    return rc;
}
 
int file_unlock(int fd)
{
    struct flock lock;
    int rc;
 
    lock.l_whence = SEEK_SET;
    lock.l_start  = 0;
    lock.l_len    = 0;
    lock.l_pid    = 0;
    lock.l_type   = F_UNLCK;
 
    if ((rc = fcntl(fd, F_SETLKW, &lock)) != 0)
    {
        return errno;
    }
 
    return rc;
}   
 
#define ORIG_FILE_MSG "<CHILD>THIS IS ORIG FILE\n"
#define NEW_FILE_MSG "<CHILD>THIS IS NEW FILE\n"
 
int rename_process(void)
{
    int orig_fd;
 
    orig_fd = open("file.txt",O_WRONLY|O_CREAT, S_IRWXU);
    if (orig_fd < 0)
    {
        perror("OPEN FAIL");
        return orig_fd;
    }
    printf("CHILD OPEN file.txt\n");
 
    if (file_lock(orig_fd) < 0)
    {
        perror("LOCK FAIL");
        close(orig_fd);
    }
 
    printf("CHILD<%d> lock file\n", getpid());
 
    if (write(orig_fd, ORIG_FILE_MSG, strlen(ORIG_FILE_MSG)) <= 0)
    {
        perror("WRITE ORIG-MSG ERROR");
    }
 
    printf("CHILD RENAME FILE!!--->LOCK IS OK??\n");
 
    if (rename("file.txt", "file.bak") < 0)
    {
        perror("RENAME FAIL");
        close(orig_fd);
    }
 
    /* File descriptor is the same,
       and this message is written at file.bak */
 
    if (write(orig_fd, NEW_FILE_MSG, strlen(NEW_FILE_MSG)) <= 0)
    {
        perror("WRITE NEW-MSG ERROR");
    }
 
    /* make parent try lock file */
    sleep(10);
 
    printf("CHILD<%d> unlock file\n", getpid());
 
    if (file_unlock(orig_fd) < 0)
    {
        perror("UNLOCK FAIL");
        close(orig_fd);
    }
 
    /* wake other processes */
    sleep(3);
 
    close(orig_fd);
    return 0;
}
 
int main(void)
{
    pid_t childp;
 
    childp = fork();
    if (childp == 0)
    {
        printf("CHILD<%d> START\n", getpid());
        rename_process();
        printf("CHILD<%d> END\n", getpid());
    }
    else if (childp > 0)
    { 
        int orig_fd;
        char readbuf[128];
        int waitstat;
        struct stat orig_stat;
 
        printf("PARENT<%d> START\n", getpid());
 
        orig_fd = open("file.txt",O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU);
        if (orig_fd < 0)
        {
            perror("OPEN FAIL");
            return orig_fd;
        }
        printf("PARENT OPEN file.txt\n");
 
        /* child lock&rename file first! */
        sleep(3);
 
        if (file_lock(orig_fd) < 0)
        {
            perror("PARENT LOCK FAIL");
            close(orig_fd);
        }
        printf("PARENT<%d> lock file\n", getpid());
 
        if (stat("file.txt", &orig_stat) == 0)
        {
            printf("PARENT READ FILE SIZE of file.txt: %d\n",
                   orig_stat.st_size);
        }
        else
        {
            perror("STAT FAIL");
        }
 
        if (fstat(orig_fd, &orig_stat) == 0)
        {
            printf("PARENT READ FILE SIZE of file.txt: %d\n",
                   orig_stat.st_size);
        }
        else
        {
            perror("STAT FAIL");
        }
 
 
        if (read(orig_fd, readbuf, 127) > 0)
        {
            printf("PARENT READ <%s>\n", readbuf);
        }
        else
        {
            printf("PARENT CANNOT READ\n");
        }
 
 
 
        if (write(orig_fd, "HELLO", 5) <= 0)
        {
            perror("WRITE ORIG-MSG ERROR");
        }
 
        /* File descriptor is the same,
           and this message is written at file.bak */
 
        if (write(orig_fd, "BYE", 3) <= 0)
        {
            perror("WRITE NEW-MSG ERROR");
        }
 
 
        if (read(orig_fd, readbuf, 127) > 0)
        {
            printf("PARENT READ <%s>\n", readbuf);
        }
        else
        {
            printf("PARENT CANNOT READ\n");
        }
 
        printf("PARENT<%d> unlock file\n", getpid());
 
        if (file_unlock(orig_fd) < 0)
        {
            perror("UNLOCK FAIL");
            close(orig_fd);
        }
 
        close(orig_fd);
        printf("PARENT<%d> END\n", getpid());
 
        wait(&waitstat);
    }
    else
    {
        perror("WARNING! fork failed\n");
    }
 
    return 0;
}

파일 이름이 바뀌면 락도 따라 바뀌고 원래 파일의 락을 기다리던 프로세스들이 락을 얻게 됩니다.
이것은 어떻게보면 당연한것 같습니다.

그런데 부모 프로세스가 락에서 깨어난 후이상하게 행동합니다.
파일에 데이터를 쓰긴 하지만 읽지는 못하고, 파일에 대한 정보도 얻질 못합니다.

아래는 실행 결과를 스크립한 것입니다.

CHILD<20189> START
CHILD OPEN file.txt
CHILD<20189> lock file
CHILD RENAME FILE!!--->LOCK IS OK??   ===> 자식 프로세스가 파일 이름 변경
PARENT<20188> START
PARENT OPEN file.txt
PARENT<20188> lock file ==============> 자식 프로세스가 파일 락을 안풀었어도 이름이 바꼈으므로 락 가능
STAT FAIL: No such file or directory========> stat콜은 실패함
PARENT READ FILE SIZE of file.txt: 49 ======> fstat 콜은 성공??
PARENT CANNOT READ
PARENT CANNOT READ
PARENT<20188> unlock file
PARENT<20188> END
CHILD<20189> unlock file =============> 락 걸린 파일의 이름이 바뀌면 락이 깨짐!!
CHILD<20189> END
 
gio-desk:rename$ cat file.txt
HELLOBYE =========================> 부모 프로세스는 데이터를 정상적으로 쓰지만 읽지 못함
gio-desk:rename$ cat file.bak
<CHILD>THIS IS ORIG FILE =============> 자식 프로세스는 데이터를 정상적으로 씀
<CHILD>THIS IS NEW FILE
gio-desk:rename$ 

첨부한 문서에 따르면 파일의 이름을 바꾸거나 파일을 지웠다 하더라도 다른 프로세스가 동일한 파일을 열고 있었다면
해당 파일의 정보가 모두 사라지지 않는다고 합니다.
따라서 파일에 접근할 때, 내가 원했던 파일이 맞는지 - 누가 다른 파일로 바꿔치기를 했거나 등등.. - 을
확인해야한다고 하네요.

결론은

1. 프로세스간에 공유되는 파일은 지우거나 이름을 바꾸지 말자.
2. 파일같은 자원에 접근하는 프로세스는 하나만 만들어서 자원 접근이 시리얼하게 되도록 설계하자.
3. 좋은 문서를 많이 찾아보자.

댓글

jam02의 이미지

좋은 자료 감사 ^^

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.