2007년 3월 28일 수요일

shm_open에서 path는 실제 파일이 아니다.

당연한 이치겠지만 shm_open에서 첫번째 인자로 받는 path는 실제 파일이 아니라 OS에서 shared memory object를 구분하기 위한 이름일 뿐이다. Linux 2.6 커널에서 /dev/shm를 마운트 하는데, 이것은 shm_open으로 연 객체를 사용자가 파일형태(실제 파일은 아니고 파일 흉내)로 shared memory object에 접근하도록 만들어놓은 interface일 뿐이다. IBM개발문서를 보면 /dev/shm이 tmpfs라는 file system이라는 것과 reboot하면 깔끔하게 내용이 날아가버린다는 사실도 확인할 수 있다.

아래는 간략한 shm_open을 이용한 테스트 프로그램이다. 이것을 위해서는 df -h 해서 /dev/shm 용량이 2기가 정도는 있어줘야 문제 없이 일을 수행할 수 있을 것이다. 이는 fstab을 고치거나 'mount -oremount,size=2g /dev/shm' 하면 대충 넘어갈 것이다. (정확한 사용량은 1,749,470,000 bytes로 약 1.6 giga bytes에 이른다. 쒧!)

// 헤더 겁내 많네.
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

// 아름다운 STL
#include <map>
#include <iostream>
#include <string>
using namespace std;

const size_t g_total_count(170000);     //!< 객체 개수
const string g_shm_id("/shmobj");       //!< shared memory object id

typedef int64_t ts_t;   //!< Timestamp type

//! @breif 사용자데이터타입
typedef struct _user_t
{
        char id[50+1];
        char data[1024*10];
} user_t;

//! @brief 사용자데이터타입이 g_total_count만큼 있을 때
//! 필요한 용량.
const size_t g_total_size(g_total_count*sizeof(user_t));

user_t*         g_users;        //!< 맵핑할 주소를 저장할 포인터

//! @beief 현재 시간을 1/1,000,000초 단위로 알려준다.
ts_t
getTimestamp(void)
{
        static struct timeval tv;
        gettimeofday(&tv,NULL);
        return (tv.tv_sec*1000000LL)+tv.tv_usec;
}

//! @brief 오류 메시지를 뿜고 프로세스를 죽여준다.
void
exitError(void)
{
        cerr << strerror(errno) << endl;
        exit( errno );
}

//! @brief 시그널 처리
void
sigFunc(int sig)
{
        switch(sig)
        {
        case SIGBUS:
        {
                cerr << "SIGBUS" << endl;
                exit(0);
        }
        }

        cerr << "signal: " << sig << endl;
}

int
main(int,char**)
{
        // mmap에 연결된 파일을 넘어선 곳을 접근하면 SIGBUS 발생
        // 대충 에러 뿌려주고 잘 죽으라고 signal 처리.
        signal(SIGBUS, sigFunc);

        int fd, res;
        ts_t pos;

        // /hbd 라는 이름을 가지는 shared memory object를 만들라는 것.
        // 이미 있으면 그냥 그거 열어서 쓴다.
        fd = shm_open(g_shm_id.c_str(), O_RDWR|O_CREAT, 0666);
        if ( -1 == fd )
        {
                exitError();
        }

        // 나중에 죽었을 때 코어 파일 떨구지 말라고 삽질해놓은 것.
        // 실제로 쓰면 매우 위험하니 이런 짓 하지 말자.
        struct rlimit rl;
        memset(&rl, 0x00, sizeof(rl));
        res = setrlimit(RLIMIT_CORE, &rl);
        if ( -1 == res )
        {
                exitError();
        }

        cerr << "user_t size: " << sizeof(user_t) << endl;
        cerr << "total count: " << g_total_count << endl;
        cerr << "total size: "
                << (double)g_total_size/1024.0/1024.0
                << " mega bytes" << endl;

        cerr << "initializing..." << endl;

        // 자... 시간을 재자.
        pos = getTimestamp();

        // 초기화 시~작!
        for (size_t i=0; i<g_total_count; i++)
        {
                user_t tmp;
                res = write(fd, &tmp, sizeof(tmp));
                if ( -1 == res )
                {
                        cerr << "init count: " << i << endl;
                        exitError();
                }
        }

        // 헉! 이만큼이나 걸렸어?
        cerr << "initialization time: "
                << getTimestamp() - pos << "us" << endl;

        // 받은 공유메모리를 실제 프로세스 메모리맵에 넣는다.
        g_users = (user_t*)mmap(NULL, g_total_size,
                PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

        // 실패면 죽어라.
        if ( MAP_FAILED == g_users )
        {
                exitError();
        }

        cerr << "read & write..." << endl;

        pos = getTimestamp();
        for ( size_t i = 0; i < g_total_count; i++ )
        {
                memset(g_users, 0x00, sizeof(user_t));
        }
        cerr << "read & write time: "
                << getTimestamp() - pos << "us" << endl;

        cerr << "done." << endl;

        return 0;
}
리눅스에서 컴파일할 때 옵션이 몇개 더 필요하므로 아래와 같은 Makefile을 예쁘게 만들어주자.
LDFLAGS=-lrt

CXXFLAGS=-g -O -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64

all:shmobj
자, 이제 실행이다! 두근두근!
$ ./shmobj
user_t size: 10291
total count: 170000
total size: 1668.42 mega bytes
initializing...
initialization time: 4422271us
read & write...
read & write time: 138441us
done.

$ ls -al /dev/shm
total 1710144
drwxrwxrwt  2 root     root             60 Mar 28 14:56 .
drwxr-xr-x  7 root     root           5260 Nov  1 18:11 ..
-rw-rw-r--  1 purewell purewell 1749470000 Mar 28 14:56 shmobj


오~ 끝내주는데? 하악하악 초기화는 좀 뻘짓을 해서 원래 저렇게 오래걸린 거고, 읽고 쓰는 시간이 1.6기가에 무려 0.1초! 아름다운 성능일쎄.

주의! 저 shmobj 파일을 수단과 방법을 가리지 않고 지우지 않을 경우 고스란히 메모리에 남아 있을 것이다. shm_unlink든 unlink든 뭐든 후딱 지우자.