1. 개요

 

    이 문서는 시리얼 모듈을 리눅스 C 공유 라이브러리로 제작하기 위해서
    필요한 메타 테이블의 __new_index 와 __index 에 대한 조사를
    기록한 문서이다.

 

2. 조사 이유
   
    이 강좌의 처음에 시리얼 모듈을 설계 할 때
    함수 호출 및 변수는 클래스 객체 개념을 비슷하게 구현을 가정하고 있다.
   
    다음이 그 예 이다.
   
    com  = serial_module.create();
   
    com.port = "/dev/ttyS1";
    com.baud = 115200;
    com.stop  = 1;
    com.data  = 8;
    com.parity = "none"
   
    com.open();
   
    여기서 보통 루아에서는 객체 함수는 ':' 를 사용해야 한다.
    즉 '.' 를 사용할 수 없다.
   
    하지만 이것을 나는 용납하기 싫었고
    부득히 '.' 로 호출하고 싶었다.
   
    그래서 이것이 가능한 것을 알아보고자 다음과 같은 메타 파일 시험을 했다.
   
    결론적으로 가능하지만 약간(?)의 처리 속도의 저하가 예상된다.
    하지만 대부분의 처리속도는 C 루틴안에서 발생할 것이므로 충분히 감수할 만한 지연으로 보인다.

 

2. 메타 테이블에서 __new_index 와 __index

 

    루아에서
   
        com[1] = "aaa";
   
    과 같은 처리는 테이블 할당 처리이다.
   
    즉 키가 1 이고 값은 "aaa" 라는 의미가 된다.
   
    이것은 다음과 동일한 것으로 봐도 된다.
   
        com["1"] = "aaa" ;
       
    또한 이것은 다음과 같은 표현이다.
   
    com.1 = "aaa";
   
    즉 다음은 동일한 표현이라는 말이다.
   
        com[1] = "aaa";
        com["1"] = "aaa" ;
        com.1 = "aaa";
       
    다른 예를 들면 다음 세가지는 같은 표현이다.
   
        com.baud = 115200;
        com["baud"] = 115200;
        com.baud = 115200;
       
    이런 표현에 대한 처리를 사용자 데이터에서 처리 하려면 메타 테이블에서 지정해야 한다.
    이에 해당하는 것이 __newindex 이다.    
   
    이와 반대로 값을 참조 하는 경우 예를 들면
   
        print com.baud;
       
    같은 경우인데 이에 해당되는 것은 __index 이다.
   
    즉 값 설정시 호출되는 것은 __newindex  이고
    값 참조시 호출되는 것은 __index 이다.
   
    다음과 같이 함수를 선언한다.
   
    static const luaL_reg serial_meta[] = {
        
      {"__newindex" , serial_newindex   },
      {"__index"    , serial_index      },
      {0, 0}
    };
    
    이 대한 처리 함수는 다음과 같이 호출된다.
       
    static int serial_newindex( lua_State *L )
    {
        return 0;
    }
       
    static int serial_index( lua_State *L )
    {
        return 0;
    }
       
    함수만으로는 특별한 것이 없다.
    이 뒤에 처리나 값을 한번 살표보도록 하자.
   

3. 스택 트레이스 함수 만들기

 

    위와 같은 처리가 어떤일이 일어나는지를 알고 싶을때
    그냥 이렇다고 머리로 아는 것보다는 실제로 데이터를
    살펴 보는 것이 좋다.
   
    루아는 모든 상호간에 인터페이스가 스택으로 이루어 지므로
    스택을 찍는 함수를 만들어서 호출 하는 것이 좋다.
   
    그래서 다음과 같은 호출 프로그램을 만들어 사용한다.
   
    static char *disp_lua_type( lua_State *L, int index)
    {
        switch( lua_type ( L, index) )
        {
        case LUA_TNONE          : return "TNONE         " ;
        case LUA_TNIL           : return "TNIL          " ;
        case LUA_TNUMBER        : return "TNUMBER       " ;
        case LUA_TBOOLEAN       : return "TBOOLEAN      " ;
        case LUA_TSTRING        : return "TSTRING       " ;
        case LUA_TTABLE         : return "TTABLE        " ;
        case LUA_TFUNCTION      : return "TFUNCTION     " ;
        case LUA_TUSERDATA      : return "TUSERDATA     " ;
        case LUA_TTHREAD        : return "TTHREAD       " ;
        case LUA_TLIGHTUSERDATA : return "TLIGHTUSERDATA" ;
        }
    }
   
    static void trace_stack( lua_State *L, int start, int end )
    {
        int lp;
       
        for( lp = start; lp <= end; lp++ )
        {
            printf( "stack %d = %s\n", lp, disp_lua_type( L, lp ) );
        }
    }
   
    사용예를 다음과 같다.
   
        trace_stack( L, 1, lua_gettop(L));
       
    이예는 스택 전체를 덤프해 보는 것이다.
   
4. __newindex      

 

    이것은 유저 데이터에 값을 대입하는 것이다.
   
    예)
   
        com.size = 4;
       
    스택에는 다음과 같이 형태로 쌓여 있게 된다.

        |                          |
        +--------------------------+
        |  4                       | 3
        +--------------------------+
        |  "size"                  | 2
        +--------------------------+
        |  USERDATA                | 1
        +--------------------------+

    이런 스택 데이터를 만든후 루아는 메타 테이블에 __newindex 로 정의된 함수를 호출한다.   
   
5. __index

 

    이것은 값을 참조 하거나 함수를 호출 할때 호출 되게 된다.
   
    예)
   
        com.open(3,"4");
        a = com.size;

 

    이것 역시 스택에 데이터를 쌓은 후 __index 로 정의된 함수를 호출한다.   
   
    위 예의 스택은 각각 다음과 같은 형태를 갖는다.
   
    com.open(3,"4");
   
        |                          |
        +--------------------------+
        |  "open"                  | 2
        +--------------------------+
        |  USERDATA                | 1
        +--------------------------+
   
    a = com.size;
   
        |                          |
        +--------------------------+
        |  "size"                  | 2
        +--------------------------+
        |  USERDATA                | 1
        +--------------------------+

    이 함수가 호출될때는 기본적으로 다음과 같은 형태로 처리 되어야 한다.
   
    if( lua_type(L, 2) == LUA_TSTRING )
    {
        lua_getmetatable(L, 1);
        lua_replace(L, 1);
        lua_rawget(L, 1);
        return 1;
    }

    여기서는 문자열일 경우만 처리되도록 하고 디폴트로 처리하라는 의미가 된다. 
  
6. 시험용 루아 소스

 

    #!/mnt/nfs/lua/lua
    print "START TEST LUA"
   
    m = require "serial";
    m.port_list();
   
    com =  m.create();
   
    print '-[print( com )]------'
    print( com );
   
    print '-[com.open(3,"4")]------'
    com.open(3,"4");
    print '-[com.size = 4]------'
    com.size = 4;
    print '-[a = com.size]------'
    a = com.size;
    print '-[m = nil]------'
    m = nil;

 

    이 소스는 C 모듈 함수를 호출 하면서
   

7. 시험용 C 모듈 소스

 

    #include <stdarg.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
   
    #define LUA_LIB
   
    #include <lua.h>
    #include <lauxlib.h>
   
    #define SERIAL      "SERIAL"
   
    typedef struct {
      char device_name[128];
    } serial_t ;
   
    static char *disp_lua_type( lua_State *L, int index)
    {
        switch( lua_type ( L, index) )
        {
        case LUA_TNONE          : return "TNONE         " ;
        case LUA_TNIL           : return "TNIL          " ;
        case LUA_TNUMBER        : return "TNUMBER       " ;
        case LUA_TBOOLEAN       : return "TBOOLEAN      " ;
        case LUA_TSTRING        : return "TSTRING       " ;
        case LUA_TTABLE         : return "TTABLE        " ;
        case LUA_TFUNCTION      : return "TFUNCTION     " ;
        case LUA_TUSERDATA      : return "TUSERDATA     " ;
        case LUA_TTHREAD        : return "TTHREAD       " ;
        case LUA_TLIGHTUSERDATA : return "TLIGHTUSERDATA" ;
        }
    }
   
    static void trace_stack( lua_State *L, int start, int end )
    {
        int lp;
       
        for( lp = start; lp <= end; lp++ )
        {
            printf( "stack %d = %s\n", lp, disp_lua_type( L, lp ) );
        }
    }
   
    static int port_list( lua_State *L )
    {
        return 0;
    }
   
    static serial_t *serial_new_userdata(lua_State *L)
    {
      serial_t *com = (serial_t *)lua_newuserdata(L, sizeof(serial_t));
      luaL_getmetatable(L, SERIAL);
      lua_setmetatable(L, -2);
      return com;
    }
   
    static serial_t * serial_get_data(lua_State *L, int index)
    {
        serial_t *com = (serial_t *)lua_touserdata(L, index);
        if (com == NULL) luaL_typerror(L, index, SERIAL);
        return com;
    }
   
    static int serial_create( lua_State *L )
    {
        serial_t *com;
   
        printf( "OK CALL MODULE create()\n" );
      
        com = serial_new_userdata(L);
        sprintf( com->device_name , "DEVICE NAME" );
      
        return 1;
    }
   
    static int serial_newindex( lua_State *L )
    {
        printf( "CALL NEW INDEX\n" );
        if( lua_type(L, 2) == LUA_TSTRING )
        {
            printf( "INDEX IS STRING [%s]\n",lua_tostring(L,2) );
            trace_stack( L, 1, lua_gettop(L));
        }  
        return 0;
    }
   
    static int serial_index( lua_State *L )
    {
        serial_t *com;
       
        printf( "CALL INDEX\n" );
       
        if( lua_type(L, 2) == LUA_TSTRING )
        {
            printf( "INDEX IS STRING [%s]\n",lua_tostring(L,2) );
            trace_stack( L, 1, lua_gettop(L));
            lua_getmetatable(L, 1);
            lua_replace(L, 1);
            lua_rawget(L, 1);
            return 1;
        }
        return 0;
    }
   
   
    static int serial_tostring (lua_State *L)
    {
        serial_t *data;
      
        printf( "CALL TO STRING " );
    
        data = serial_get_data( L, 1 );
        lua_pushfstring(L, "[%s]", data->device_name);
        return 1;
    }
   
    static int serial_gc (lua_State *L)
    {
        serial_t *data;
        data = serial_get_data( L, 1 );
   
        printf( "CALL GC [%s]\n", data->device_name );
        return 0;
    }
   
    static int serial_open(lua_State *L)
    {
        printf( "CALL OPEN\n" );
        trace_stack( L, 1, lua_gettop(L));
        return 0;  
    }
   
    static const struct luaL_Reg sm[] =
    {
        { "port_list"   , port_list     },
        { "create"      , serial_create },
      
        { NULL, NULL }
    };
   
    static const luaL_reg serial_meta[] = {
        
      {"__newindex" , serial_newindex   },
      {"__index"    , serial_index      },
      {"__tostring" , serial_tostring   },
      {"__gc"       , serial_gc         },
      {"open"       , serial_open       },
      {0, 0}
    };
   
    LUALIB_API int luaopen_serial( lua_State *L )
    {
        int top;
      
        luaL_register(L, "serial", sm );
      
        top = lua_gettop(L);  
   
        luaL_newmetatable(L, SERIAL);         
   
        lua_pushvalue(L, -1);             
        lua_setfield(L, -2, "__index");
   
        luaL_register(L, NULL, serial_meta );
   
        lua_settop(L, top);
      
        return 1;
    }

 

8. Makefile

 

    CROSS_PREFIX = arm-linux
   
    LUA_MODULE = serial.so
   
    TARGET     = $(LUA_MODULE)
    TARGET_NFS = /nfs/lua
   
    LIB_LUA_A  = liblua.a
   
    C_SRCS =
    LUA_SRCS =
    LUA_LIBS_SRCS =
   
    C_SRCS      += serial.c
    LUA_SRCS    += run.lua
   
    # LUA Support  -- CORE
    LUA_LIBS_SRCS =
    LUA_LIBS_SRCS += ../../lua_lib/lapi.c
    LUA_LIBS_SRCS += ../../lua_lib/lcode.c
    LUA_LIBS_SRCS += ../../lua_lib/ldebug.c
    LUA_LIBS_SRCS += ../../lua_lib/ldo.c
    LUA_LIBS_SRCS += ../../lua_lib/ldump.c
    LUA_LIBS_SRCS += ../../lua_lib/lfunc.c
    LUA_LIBS_SRCS += ../../lua_lib/lgc.c
    LUA_LIBS_SRCS += ../../lua_lib/llex.c
    LUA_LIBS_SRCS += ../../lua_lib/lmem.c
    LUA_LIBS_SRCS += ../../lua_lib/lobject.c
    LUA_LIBS_SRCS += ../../lua_lib/lopcodes.c
    LUA_LIBS_SRCS += ../../lua_lib/lparser.c
    LUA_LIBS_SRCS += ../../lua_lib/lstate.c
    LUA_LIBS_SRCS += ../../lua_lib/lstring.c
    LUA_LIBS_SRCS += ../../lua_lib/ltable.c
    LUA_LIBS_SRCS += ../../lua_lib/ltm.c
    LUA_LIBS_SRCS += ../../lua_lib/lundump.c
    LUA_LIBS_SRCS += ../../lua_lib/lvm.c
    LUA_LIBS_SRCS += ../../lua_lib/lzio.c
   
    # LUA Support  -- LIB
   
    LUA_LIBS_SRCS += ../../lua_lib/lauxlib.c
    LUA_LIBS_SRCS += ../../lua_lib/lbaselib.c
    LUA_LIBS_SRCS += ../../lua_lib/ldblib.c
    LUA_LIBS_SRCS += ../../lua_lib/liolib.c
    LUA_LIBS_SRCS += ../../lua_lib/lmathlib.c
    LUA_LIBS_SRCS += ../../lua_lib/loslib.c
    LUA_LIBS_SRCS += ../../lua_lib/ltablib.c
    LUA_LIBS_SRCS += ../../lua_lib/lstrlib.c
    LUA_LIBS_SRCS += ../../lua_lib/loadlib.c
    LUA_LIBS_SRCS += ../../lua_lib/linit.c
   
    INCLUDES += -I.
    INCLUDES += -I/usr/local/include
    INCLUDES += -I/usr/$(CROSS_PREFIX)/include
    INCLUDES += -I../include
   
    INCLUDES += -I../../lua_include
   
    LDFLAGS += -L./
    LDFLAGS += -L../../lua_lib/
    LDFLAGS += -L/usr/$(CROSS_PREFIX)/lib
   
    LIBS  = -lm -ldl -llua
   
    CFLAGS   += $(INCLUDES)
    CFLAGS   += -Wall -O2 -g -DLUA_USE_LINUX
    CPPFLAGS += $(DEFINES)
   
    ARFLAGS = rs
   
    #---------------------------------------------------------------------
    CC           = $(CROSS_PREFIX)-gcc
    CXX          = $(CROSS_PREFIX)-g++
    AR           = $(CROSS_PREFIX)-ar rcu
    AR2          = $(CROSS_PREFIX)-ranlib
    RANLIB       = $(CROSS_PREFIX)-ranlib
    LD           = $(CROSS_PREFIX)-ld
    NM           = $(CROSS_PREFIX)-nm
    STRIP        = $(CROSS_PREFIX)-strip
    OBJCOPY      = $(CROSS_PREFIX)-objcopy
    CP = cp
    MV = mv
    #--------------------------------------------------------------------
   
    C_OBJS          = $(C_SRCS:%.c=%.o)
    LUA_LIBS_OBJS   = $(LUA_LIBS_SRCS:%.c=%.o)
   
    #
    # Compilation target for C files
    #
    %.o:%.c
        @echo "Compiling $< ..."
        $(CC) -c $(CFLAGS) -o $@ $<
   
    %.o:%.cc
        @echo "Compiling $< ..."
        $(CXX) -c $(CPPFLAGS) $(CFLAGS) -o $@ $<
   
    #
    # Compilation target for C++ files
    #
    %.o:%.cc
        @echo "C++ compiling $< ..."
        $(CXX) -c $(CFLAGS) $(CXXFLAGS) -o $@ $<
   
    all : $(LIB_LUA_A) $(LIB_GXLIB_A) $(TARGET)
        cp -a $(LUA_MODULE) $(TARGET_NFS)
        cp $(LUA_SRCS) $(TARGET_NFS)
   
    $(LIB_LUA_A) : $(LUA_LIBS_OBJS)
        $(AR) $@ $?
        $(RANLIB) $@
   
    $(TARGET) : $(C_OBJS)
        $(CC) $(LDFLAGS)  -shared -Wl,-soname,$(TARGET) $(C_OBJS) -o $@ $(LIBS)
   
    dep :
        $(CC) -M $(INCLUDES) $(C_SRCS) > .depend
   
    clean:
        rm -f *.bak
        rm -f *.map
        rm -f *.o
        rm -f *.so
        rm -f $(C_OBJS)
        rm -f $(LUA_LIBS_OBJS)
        rm -f $(LIB_LUA_A)
        rm -f $(TARGET) core
   
    distclean: clean
        rm -rf .depend
   
    ifeq (.depend,$(wildcard .depend))
    include .depend
    endif

 

9. 수행

 

    수행은 nfs 를 마운트 해서 보드에서 하는데 다음과 같은 파일이 준비도 있어야 한다.
   
    [root@falinux lua]$ ls        
    lua        run.lua    serial.so
    [root@falinux lua]$           
   
    수행은 다음과 같이 하면 된다.

    [root@falinux lua]$ ./run.lua
    START TEST LUA
    OK CALL MODULE create()
    -[print( com )]------
    CALL TO STRING [DEVICE NAME]
    -[com.open(3,"4")]------
    CALL INDEX
    INDEX IS STRING [open]
    stack 1 = TUSERDATA    
    stack 2 = TSTRING      
    CALL OPEN
    stack 1 = TNUMBER      
    stack 2 = TSTRING      
    -[com.size = 4]------
    CALL NEW INDEX
    INDEX IS STRING [size]
    stack 1 = TUSERDATA    
    stack 2 = TSTRING      
    stack 3 = TNUMBER      
    -[a = com.size]------
    CALL INDEX
    INDEX IS STRING [size]
    stack 1 = TUSERDATA    
    stack 2 = TSTRING      
    -[m = nil]------
    CALL GC [DEVICE NAME]
    [root@falinux lua]$

 

    사용자 테이블에 메타 테이블 함수들이 호출되는 것을 확인 할 수 있다.
    C000_lua.gif