기본 콘텐츠로 건너뛰기

공유메모리에 40바이트 읽고 쓰는데 얼마나 충돌날까?

가끔씩 공유메모리에 있는 자료를 n개 프로세스/쓰레드가 읽고 쓰기 위해 접근할 때, 정말 충돌이 나긴할까? 물론 이론은 반드시 나긴 나는데, 이론은 이론이고... 임계영역이 I/O wait이 안 일어날 정도로 짧은 구간이더라도 충돌이 날까?

결론은 충돌난다. ㅡ_-) 덴장... 이래서 잠금장치가 여러개 있는 것이지...


아래 소스를 대충 컴파일해서 돌려보고 Ctrl+C를 눌러보면...

#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>

enum
{
    SHM_STR_LEN = 32
};

typedef struct str_t
{
    int len;
    char buf[SHM_STR_LEN+1];
} str_t;

volatile size_t g_total_count(0);
volatile size_t g_crash_count(0);

int g_shmfd(-1);
str_t* g_buf(NULL);

//        make random string...
void
makeRandStr(str_t& buf)
{
    memset(&buf, 0x00, sizeof(buf));

    int i, max(rand()%SHM_STR_LEN);
    for ( i = 0; i < max; i++ )
    {
        buf.buf[i] = (rand()%10) + '0';
    }

    buf.len = max;
    buf.buf[SHM_STR_LEN] = 0x00;
}

//        check str_t structure...
bool
chkBuffer(const str_t& buf)
{
    ++ g_total_count;

    int reallen(strlen(buf.buf));

    bool res(reallen == buf.len);
    if ( res )
    {
        //printf("success!\n");
        return true;
    }

    //printf("crash! len=%d real=%d\nbuf=%s\n",
    //    buf.len, reallen, buf.buf);
    ++ g_crash_count;

    return false;
}

//        signal procedure for productor.
void
productor_sigint(int)
{
    shmctl(g_shmfd, IPC_RMID, NULL);
    exit(0);
}

//        productor makes random-string and memcpy to shared-memory.
void
productor(void)
{
    signal(SIGINT, productor_sigint);
    str_t buf;
    while (true)
    {
        makeRandStr(buf);
        memcpy(g_buf, &buf, sizeof(buf));
        //printf("rand: len=%d\n", g_buf->len, g_buf->buf);
    }
}

//        signal procedure for consumer.
void
consumer_sigint(int)
{
    printf("\ntotal: %d\ncrash: %d\nfail-rate: %2.02f%%\n",
        g_total_count, g_crash_count,
        g_crash_count*100.0/g_total_count);
    shmctl(g_shmfd, IPC_RMID, NULL);
    exit(0);
}

//        consumer reads memcpy from shared-memory and check.
void
consumer(void)
{
    signal(SIGINT, consumer_sigint);
    fprintf(stderr, "Test size: %d\n", sizeof(str_t));
    fprintf(stderr, "Press Ctrl+C...");
    str_t buf;
    while (true)
    {
        memcpy(&buf, g_buf, sizeof(buf));
        chkBuffer(buf);
    }
}

//        main function. make shared-memory and fork!
int
main(int argc, char* argv[])
{
    key_t shmkey(ftok("/dev/null", 1));
    if ( -1 == shmkey )
    {
        printf("%d %s\n", __LINE__, strerror(errno));
        return 0;
    }

    g_shmfd = (shmget(shmkey, sizeof(str_t), 0700|IPC_CREAT));
    if ( -1 == g_shmfd )
    {
        printf("%d %s\n", __LINE__, strerror(errno));
        return 0;
    }

    g_buf = ((str_t*)shmat(g_shmfd, NULL, SHM_RND));
    memset(g_buf, 0x00, sizeof(str_t));

    if ( fork() )
    {
        productor();
    }
    else
    {
        consumer();
    }
}


$ g++ -o shmtest -O3 shmtest.cpp
아주 정확한 방법은 아니지만, 대충 충돌하는 것을 알 수 있을 것이다. 여러 시스템에서 돌렸을 때, 몇 퍼센트가 나올지 궁금하다. 누가 돌려주면 좋겠지만, 아쉽게도 아무도 안 돌릴 것 같다. orz OTL

참고로 아래는 CentOS 5.2에서 살포시 돌려본 결과이다.
$ ./shmtest
Test size: 40
Press Ctrl+C...
total: 186202017
crash: 985010
fail-rate: 0.53%



덧글: 혹시나 오해할까봐... MT/MP 환경에서 임계영역이 있다면 락을 걸어주는 것이 상식. 이건 그냥 가끔씩 이론은 이론이고 실제는 얼마나 충돌할까라는 맛뵈기 실험.

댓글

이 블로그의 인기 게시물

Bash Array, Map 정리

Bash에서 Array, Map에 대한 정리. (매번 찾기 귀찮) 찾아보진 않았지만, Bash에서 Array든 Map이든 동일하게 Map(C++에서 Unordered Map)으로 동작하는 것 같다. 왜냐하면, Array의 Index가 연속하지 않아도 동작한다. 그저 Key가 0 이상의 정수인 Map이랑 비슷하게 동작한다. 예) 1, 2, 3, 9, 10 Array # 생성 declare -a empty_array declare -a ar=(haha hoho baba "long string haha hoho") # 접근 echo "ar[0]=${ar[0]}" echo "all as array=${ar[@]}" # 큰따옴표 안에서 각 원소를 따로따로 전달한다. echo "all as one=${ar[*]}" # 큰따옴표 안에서 각 원소를 문자열 하나로 합쳐 전달한다. echo "indexes=${!ar[@]}" echo "indexes=${!ar[*]}" echo "length=${#ar[@]}" echo "length=${#ar[*]}" echo "last=${ar[-1]}" echo "last=${ar[@]: -1}" # 콜론 뒤에 빈 칸이 꼭 필요하다. 옛 방식 # 현재 상황 declare -p ar #(출력) declare -a ar=([0]="haha" [1]="hoho" [2]="baba" [3]="long string haha hoho") ar[100]=hello # 인덱스를 건너 뛰어도 동작한다. declare -p ar #(출력) declare -a ar=([0]="haha" [1]="hoho" [2]="baba" [3]=...

설치한 패키지에서 RPM 추출하기

오래된 패키지를 관리할 저장소가 없어졌고, 기존 패키지로 다른 서버를 세팅해야할 일이 생겼다면 RPM의 리패키지 기능을 이용해보자. $ rpm -e --repackage [PACKAGE_NAME] 위와 같이 리패키지하면, /var/spool/repackage/ 에 생성한 RPM파일이 있다. :-)