OS를 만듭시다. 어때요~ 참 쉽죠? (17)

나빌레라의 이미지

OS. 영어로 풀어서 Operating System. 한자어로 번역해서 운영체제. 이것을 만든다고 하면 사람들은 굉장히 어렵게 생각한다. 리눅스나 윈도우같은 OS를 비교대상으로 본다면 OS를 만드는것은 정말 어렵고 힘들고, 개인이 만들기엔 어쩌면 불가능에 가까운 도전일지도 모른다. 하지만 OS의 기본 개념들은 대학교 학부과정에서 가르칠 정도로 이미 보편화 되어 있고 그 개념 자체들은 그다지 어렵지 않다. 개념 구현을 중심으로 동작하는 것 자체에 의미를 둔 OS를 만드는 것은 어쩌면 도전 해 볼만 한 가치가 있는 시도가 아닐까.

목차


1회
2회
3회
4회
5회
6회
7회
8회
9회
10회
11회
12회
13회
14회
15회
16회

17. 메모리 동적 할당

최초 나빌눅스를 설계 할때는 메모리동적할당 기능을 넣을지 안넣을지에 대해서 심히 고민을 많이 했다. 사실 임베디드OS에서 메모리 동적할당을 할일이 그다지 많지 않다. 설사 필요하다 하더라도 여러가지 다양한 포인터를 이용한 테크닉으로 유사하게 구현 할 수 있다. 실제로 메모리동적할당 자체를 지원하지 않는 임베디드OS도 많이 있다. 하지만 이왕 OS를 만들기로 작정 했고, 할 수 있는 한 기존 OS에서 지원하는 기능들은 살짝이나마 흉내라도 내보기 위한 목적으로 만드는 OS인 만큼 정확하고 명확하며 효율적으로 동작하진 않더라도 대충 메모리동적할당이 어떤식으로 동작하는 지에 대한 이해의 목적으로 기능을 포함 하기로 결정 하였다.

실제로 메모리동적할당에 관련해서는 많은 시스템콜이 존재 하는데, 나빌눅스는 가장 많이 사용하며 꼭 필요한 딱 두개의 함수. malloc() 과 free() 두개만 구현 하도록 하겠다. 나머지 다른 시스템콜들이 필요하다면 그것은 독자들의 몫으로 남겨 두 도록 하겠다.

앞서 나빌눅스의 메모리 맵을 설계 할 때, 이지보드가 제공하는 sdram 은 총 64메가 이고 이중 사용자테스크에게 할당하는 영역은 4메가 위치부터 64메가 위치까지 60메가로 결정한다고 하였다. 그리고 메모리관리자를 구현할때 테스크 한개당 할당하는 스택영역은 1메가로 하였고, 이로 인해 최대 60개의 테스크를 커널에 등록 시키는게 가능하게 구현 하였다. 그리고 테스크관리자를 구현할때 최대 태스크갯수를 40개로 정해서 sdram의 상위 20메가를 남겨 두었다. 이 상위 20메가를 이제 메모리 동적 할당을 위해 사용하게 될 것이다. 즉 메모리 동적할당에 사용되는 영역은 44메가 위치부터 64메가 위치까지 이다.

즉, 메모리 주소 기준으로 0xA2C00000 부터 0xA4000000 번지까지가 동적메모리 할당을 위해 예정된 공간이 된다. 동적메모리를 할당하여 변수의 주소값을 찍는 다면 저 두 주소값 범위안에서 값이 나와야 한다.

구현하게 될 동적메모리 할당을 위한 구현 코드는 효율성이나 단편화 등에 대한 고려는 전혀 하지 않고 오로지 이해를 쉽게 하기 위한 코드로 작성 할 것이다. 실제 구현된 코드는 내부단편화와 외부단편화가 모두 발생한다. 하지만 이를 처리 하는 코드는 추가 하지 않았다.

사용자테스크에서 malloc()으로 동적메모리 영역을 요청하면 커널은 메모리상에 해당 크기 만큼의 메모리공간을 사용자테스크를 위해 예약 해야 한다. 즉, 다른 테스크에서 해당영역을 할당받지 못하게 해야 한다. 그러기 위해서는 어느정도의 사이즈가 사용중인지를 표시하는 별도의 자료구조가 있어야 한다. 자유메모리블럭 처럼 별도의 공간에 정해진 갯수만큼의 관리 블럭을 만들어도 되지만 이렇게 된다면 동적메모리 할당의 원래 취지에는 맞지 않게 전체 OS에서 할당할 수 있는 총 동적메모리 블럭의 갯수가 한정되어 지게 된다.

예를 들어 커널안에 동적메모리관리를 위한 별도의 자유메모리블럭풀이 100개로 선언되었다면 실제 할당할 수 있는 메모리 공간은 20Mbyte 임에도 불구하고 1Kbyte 짜리를 100개 선언한다면 더이상 동적메모리를 할당 할 수 없게 되는 문제가 발생한다. 그래서 동적메모리블럭을 관리하는 관리자 자료구조는 할당되는 동적메모리블럭 앞에 그대로 헤더로 붙여서 관리하게 할 것이다. 실제로 리눅스에서도 이와같은 방법으로 동적메모리를 관리한다.

나빌눅스에서 동적메모리를 관리하기 위한 헤더블록의 내용은 별거 없다. 사용중인지를 표시하는 플래그와 해당블럭의 크기가 몇바이트인지를 표시하는 변수 이렇게 두개 이다. 아래와 같은 그림이 도움이 될 듯 하다.

+------------+----------------------------+
|   header   |                            |
+------+-----+    요청한메모리영역           |
|flag  |size |                            |
+------+-----+----------------------------+
             ↑
            반환주소

헤더블럭은 다른 데이터가 들어가면 안되므로 실제 사용자영역에 반환되는 주소는 헤더블럭이 끝나는 부분. 즉 요청한 실제 메모리블럭이 시작되는 곳의 주소이다. 헤더블럭에 들어가는 size 는 요청한메모리블럭의 크기와 헤더블럭의 크기(8byte) 를 더한 크기이다.

헤더블럭에 해당하는 자료구조를 프로그래밍 해 보자. 메모리관련이므로 당연히 메모리관리자를 수정한다. navilnux_memory.h 를 수정하자.

#ifndef _NAVIL_MEM
#define _NAVIL_MEM
 
#define MAXMEMBLK 60
 
 
typedef struct _navil_free_mem {
    unsigned int block_start_addr;
    unsigned int block_end_addr;
    int is_used;
} Navil_free_mem;
 
typedef struct _dy_mem_header {
    int used;
    int size;
} Dy_mem_header;
 
 
typedef struct _navil_mem_mng {
    Navil_free_mem free_mem_pool[MAXMEMBLK];
 
    unsigned int heap_start;
    unsigned int used_end;
 
    void (*init)(void);
    unsigned int (*alloc)(void);
 
    void* (*malloc)(int);
    int (*free)(void *);
} Navil_mem_mng;
 
void mem_init(void);
unsigned int mem_alloc(void);
 
void* mem_malloc(int);
int mem_free(void *);
 
#endif

Dy_mem_header 라는 구조체가 추가 되었다. 구조체의 멤버변수는 너무도 솔직하게도 위에서 그린 그림 그대로 이다. used 변수는 해당 메모리블럭이 사용중이면 1, 사용중이 아니면 0을 가지고 있다. size 변수는 말 그대로 헤더블럭뒤에 따라오는 동적메모리영역의 크기와 헤더블럭의 크기를 더한 값이 들어간다.

메모리관리자 구조체에도 두개의 함수포인터가 추가 되었다. malloc() 과 free() 이다. 표준라이브러리와 마찬가지로 malloc() 함수는 void 형 포인터를 반환하고, int 형으로 사이즈를 인자로 받는다. free() 함수는 int 형으로 성공혹은 에러값을 반환하고 인자로 void 형 포인터를 받아서 해당 포인터주소의 메모리 블럭 할당을 해제 한다. 함수포인터 외에 heap_start 와 used_end 라는 두개의 unsigned int 형 변수가 메모리관리자에 선언되어 있다. 이 두변수가 하는 일은 힙영역에서 동적메모리를 할당 할때 현재까지 사용중인 힙영역의 처음과 끝을 가르키는 역할로써 heap_start 주소번지 부터 used_end 주소 번지까지는 사용중이던 비어있던 간에 동적메모리 블럭이 할당되어 있다는 의미이다.

그리고 malloc() 과 free()를 구현할 실제 함수인 mem_malloc() 과 mem_free() 함수에 대한 프로토타입이 아랫쪽에 선언되어 있다. 메모리관리자 구조체가 수정되었으므로 mem_init() 함수도 수정되어야 한다. 수정된 소스는 아래와 같다.

#define STARTDYMEMADDR      0xA2C00000  /// 44M
 
void mem_init(void)
{
    unsigned int pt = STARTUSRSTACKADDR;
    int i;
 
    for(i = 0 ; i < MAXMEMBLK ; i++){
        memmng.free_mem_pool[i].block_start_addr = pt;
        memmng.free_mem_pool[i].block_end_addr = pt + USRSTACKSIZE -4;
        memmng.free_mem_pool[i].is_used = 0;
        pt += USRSTACKSIZE;
    }
 
    memmng.heap_start = STARTDYMEMADDR;
    memmng.used_end = memmng.heap_start;
 
    memmng.init = mem_init;
    memmng.alloc = mem_alloc;
 
    memmng.malloc = mem_malloc;
    memmng.free = mem_free;
}

heap_start 에는 위에 define 되어 있는 0xA2C00000 의 값을 할당한다. 0xA2C00000 은 이지보드의 sdram 이 시작하는 0xA0000000 부터 44Mbyte 되는 위치에 해당하는 주소값이다. 위에서 설명한 대로 동적메모리 할당을 위한 힙 영역은 상위 20메가에 들어가게 하기 위해서 전체 64메가중 44메가 위치에서 시작하게 된다. 그리고 메모리관리자를 초기화 하는 이 시점에서 힙 영역에는 할당된 동적메모리블럭이 없으므로 used_end 변수는 heap_start 변수와 같은 값을 가지게 된다.

mem_malloc() 과 mem_free() 함수를 어떻게 구현 할지 생각 해 보도록 하자. mem_malloc() 함수에서는 먼저 해야 할 일이 비어 있는 공간을 찾는 일이다. 사용중이지 않은 메모리 공간은 당연히 used_end 변수에 할당되어 있는 주소값 부터 이다. 사용되지 않는 메모리 공간이 나온다면 그곳에 먼저 동적메모리헤더블럭데이터를 써 넣고 used_end 를 요청된 크기만큼 위로 올린다. 그리고 그렇게 여러개의 메모리 블럭이 할당되다가 헤제가 된다면 동적메모리블럭이 연속되어 있다가 중간이 사용하지 않는 블럭이 된다. 사용하지 않는 공간 뿐만 아니라 사용하지 않는 블럭도 검사 해서 해당 블럭보다 요청한 메모리의 크기가 더 작다면 해당 블럭을 그대로 다시 사용한다. 그렇기 때문에 mem_free() 함수는 그 구현히 지극히 단순해 진다. 요청받은 메모리블럭의 헤더에 있는 used 플래그를 단순히 0으로 바꾸어 놓으면 끝이다.

그럼 일단 단순한 mem_free() 함수의 실제 구현을 보자.

int mem_free(void *addr)
{
    Dy_mem_header *dy_header;
 
    unsigned int add_header = (unsigned int)addr - sizeof(Dy_mem_header);
    dy_header = (Dy_mem_header *)add_header;
 
    if(dy_header->used != 1){
        return -1;
    }
 
    dy_header->used = 0;
 
    return 0;
}

사용자 영역에서 가지고 있는 동적메모리포인터의 주소값은 헤더의 끝 위치 이기 때문에 인자로 넘어온 주소에서 헤더의 크기만큼의 주소값을 빼야 한다. 헤더는 32비트 int 형 변수 두개이기 때문에 그 크기는 8byte 이다. 그러므로 add_header 변수에는 인자로 넘어온 addr - 8 의 값이 들어갈 것이다. 그리고 8바이트를 뺀 주소값을 헤더 구조체의 포인터에 할당한다. 해당 위치부터 8바이트는 동적메모리헤더블럭이다. 헤더블럭의 used 플래그는 반드시 1 이어야 한다. 1이 아니라면 잘못된 주소값을 넘겨준 것이거나, 이미 free() 수행한 포인터를 넘긴것이 되므로 -1을 반환한다. 실질 적으로 mem_free() 함수가 하는 일은 dy_header->used = 0 이게 전부 이다. 블럭의 사용중 플래그를 0으로 바꾸고는 끝이다.

다음으로는 mem_free() 보다는 조금 더 복잡한 mem_malloc() 함수를 보겠다.

void* mem_malloc(int size)
{
    int req_size = size + sizeof(Dy_mem_header);
    unsigned int cur_pos = memmng.heap_start;
    unsigned int ret_addr = 0;
 
    Dy_mem_header *dy_header;
 
    while(cur_pos != memmng.used_end){
        dy_header = (Dy_mem_header *)cur_pos;
        if(!dy_header->used){
            if(dy_header->size >= req_size){
                dy_header->used = 1;
                ret_addr = cur_pos;
                break;
            }
        }
        cur_pos += dy_header->size;
    }
 
    if(!ret_addr){
        ret_addr = memmng.used_end;
        memmng.used_end += req_size;
 
        if(memmng.used_end > ENDUSRSTACKADDR){
            return (void *)0;
        }
 
        dy_header = (Dy_mem_header *)ret_addr;
        dy_header->used = 1;
        dy_header->size = req_size;
    }
 
    ret_addr += sizeof(Dy_mem_header);
 
    return (void *)ret_addr;
}

mem_malloc() 함수의 기본 알고리즘은 위에 서술한 내용 그대로 이다. 먼저 req_size 는 요청받은 메모리공간영역에 동적메모리블럭헤더의 크기를 더한 사이즈이다. 사용자는 요청한 크기만 알고 있으면 되지만 커널은 헤더를 포함한 사이즈를 알아야 하기 때문이다. cur_pos 는 앞으로 동적메모리블록을 할당 해야 할 위치의 주소를 가지고 있는 변수이다. 일단 처음에는 힙영역의 시작값을 가지고 출발한다. ret_addr 은 mem_malloc() 에 의해 할당받아 사용자 영역으로 넘겨줄 주소값이다.

mem_malloc() 함수는 크게 두부분으로 나뉘어져 있다. 위쪽에 보이는 while() 문 루프이고, 아래쪽에 보이는 if()블락이다. while() 루프는 힙 시작영역서 부터 이미 할당 되어 있는 동적메모리 블럭을 하나씩 읽어 가면서 비어있는 (사용중이지 않은) 블럭을 찾아 그 블럭의 크기가 요청한 블럭사이즈보다 크다면 그대로 그 블럭을 다시 사용하는 코드이다. while() 루프를 끝까지 다 돌았는데도, ret_addr 에 값이 할당되지 않았으면 그것은 현재 할당되어 있는 동적메모리블럭이 아직 모두 사용중일 경우이다. 그때에 아래쪽에 있는 if()블럭에 진입하게 되는데, if() 블럭 안의 내용은 새로운 동적메모리 블럭을 하나 할당하고 그곳의 주소를 사용자 영역에 반환 해 주는 동작을 하는 것이다. 그리고 마지막으로 ret_addr 을 반환하기 전에 동적메모리블럭헤더만큼의 주소를 더한다. 그래야 실제 데이터영역의 시작 주소가 사용자 영역에 전달 된다.

이렇게 두개의 함수를 작성함으로써 실제로 메모리 동적할당의 구현은 끝났다. 그럼이어서 할일은 무엇인가. 바로 시스템콜 등록 절차이다. 시스템콜 구현 강좌이후 추가되는 기능이 모두 시스템콜이다 보니 글 쓰는 나도 이부분을 설명 할때가 되면 어떤식으로 써야 할지 난감해 진다. 매번 반복되는 내용이니 코드를 갖다 붙이기도 뭐하고.. 그래서 이번에도 그냥 시스템콜 관련 각 파일에 어떤 내용만 추가 되었는지를 간략히 서술하고 넘어 가겠다. 완전한 소스는 첨부 파일에서 살펴 봐 주시기 바란다.

syscalltbl.h

 #define SYS_MALLOC      8
 #define SYS_FREE        9


navilnux_sys.h
 void* sys_malloc(int);
 int sys_free(void *);

navilnux_sys.c

void *sys_malloc(int size)
{
    return memmng.malloc(size);
}
 
int sys_free(void *addr)
{
    return memmng.free(addr);
}
 
void syscall_init(void)
{
    navilnux_syscallvec[SYS_MYSYSCALL] = (unsigned int)sys_mysyscall; 
    navilnux_syscallvec[SYS_MYSYSCALL4] = (unsigned int)sys_mysyscall4; 
    navilnux_syscallvec[SYS_ITCSEND] = (unsigned int)sys_itcsend; 
    navilnux_syscallvec[SYS_ITCGET] = (unsigned int)sys_itcget; 
    navilnux_syscallvec[SYS_MUTEXTWAIT] = (unsigned int)sys_mutexwait;
    navilnux_syscallvec[SYS_MUTEXREL] = (unsigned int)sys_mutexrelease;
    navilnux_syscallvec[SYS_SEMP] = (unsigned int)sys_semp;
    navilnux_syscallvec[SYS_SEMV] = (unsigned int)sys_semv;
    navilnux_syscallvec[SYS_MALLOC] = (unsigned int)sys_malloc;
    navilnux_syscallvec[SYS_FREE] = (unsigned int)sys_free;
}

navilnux_lib.h

void* navilnux_malloc(int);
int navilnux_free(void *);

navilnux_lib.S

.global navilnux_malloc
navilnux_malloc:
    swi SYS_MALLOC
    mov pc, lr
 
.global navilnux_free
navilnux_free:
    swi SYS_FREE
    mov pc, lr

이상이다. 한가지 유념 할 점은 navilnux_clib.c 에 한번더 감싸는 코드가 없다는 점이다. navilnux_malloc() 과 navilnux_free() 는 시스템콜 단으로 넘어가기 전에 사용자 영역에서 추가로 처리를 해줄 것이 아무것도 없이 그냥 시스템콜 레이어를 따라서 메모리관리자 본체 코드로 갈때까지 계속해서 호출로만 이루워진다. 그렇기 때문에 레이어를 한단계라도 줄이려고 navilnux_clib.c 에는 코드를 추가 하지 않았다. 추가해도 상관은 없지만 별 의미가 없기 때문이다.

여기까지 해서 커널 단에서의 동적메모리 할당을 위한 준비는 다 끝났다. 그럼 이제 사용자테스크영역에서 동적메모리 할당을 사용해보고 테스트 해 볼 차례이다. 테스트 방법은 총 4개의 동적변수를 할당한다. 각각 2Kbyte, 3Kbyte, 4Kbyte 를 할당하고, 3Kbyte 짜리 변수를 할당해제 한다. 그리고 다시 1Kbyte 변수를 할당한다. 그렇다면 결과는 2Kbyte, 3Kbyte, 4Kbyte 변수가 연속해서 할당되고 그다음에 3Kbyte 가 해제 되므로 해당블럭의 used 플래그가 0 이 되었다가 다시 1Kbyte 가 할당되면 3Kbyte 보다 크기가 작으므로 전에 3Kbyte 블럭이 사용했던 블럭이 그대로 1Kbyte 짜리 변수에 할당된다.

    |<--2K-->|  |<----3K---->|  |<------4K------>|
 +--+--------+--+------------+--+----------------+------------------------
 |  |        |  |    :       |  |                |
 |hd|        |hd|    :       |hd|                |
 |  |        |  |    :       |  |                |
 +--+--------+--+------------+--+----------------+------------------------
                |<1K>|

위 그림과 같이 할당되어야 할 것이다. 실제 navilnux_user.c 의 사용자테스크 함수의 내용을 보자. 수정한 테스크는 TASK1 이다.

void user_task_1(void)
{
    int a, b, c;
 
    int *ptr_a, *ptr_b, *ptr_c, *ptr_d;
 
    a = 1;
    b = 2;
    c = a + b;
 
    ptr_a = (int *)navilnux_malloc(2048);
    printf("Dynamic mem alloc 2K - %p\n", ptr_a);
 
    ptr_b = (int *)navilnux_malloc(3072);
    printf("Dynamic mem alloc 3K - %p\n", ptr_b);
 
    navilnux_free(ptr_b);
 
    ptr_c = (int *)navilnux_malloc(4096);
    printf("Dynamic mem alloc 4K - %p\n", ptr_c);
 
    ptr_d = (int *)navilnux_malloc(1024);
    printf("Dynamic mem alloc 1K - %p\n", ptr_d);
 
    while(1){
        printf("TASK1 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        navilnux_sleep(1);
    }
}

위와 같이 사용자 테스크를 수정하고 가벼운 마음으로 make 를 친 다음 보드에 이미지를 다운로드 하고 부팅 해서 출력값을 확인 해 보자. 아래와 같이 출력 될 것이다.

TCB : TASK1 - init PC(a000c9ec)          init SP(a04ffffc)                      
TCB : TASK2 - init PC(a000cac0)          init SP(a05ffffc)                      
TCB : TASK3 - init PC(a000cb68)          init SP(a06ffffc)                      
TCB : TASK4 - init PC(a000cbe4)          init SP(a07ffffc)                      
TCB : TASK5 - init PC(a000cc70)          init SP(a08ffffc)                      
TCB : TASK6 - init PC(a000ccfc)          init SP(a09ffffc)                      
TCB : TASK7 - init PC(a000cd88)          init SP(a0affffc)                      
TCB : TASK8 - init PC(a000ce14)          init SP(a0bffffc)                      
Dynamic mem alloc 2K - a2c00008                                                 
Dynamic mem alloc 3K - a2c00810                                                 
Dynamic mem alloc 4K - a2c01418                                                 
Dynamic mem alloc 1K - a2c00810

커널 힙 영역은 0xA2C00000 부터이다. 그리고 32비트 ARM 에서 동적메모리헤더의 크기는 8바이트 이다. 그러므로 첫번째로 할당되는 ptr_a 의 동적메모리의 반환값은 0xA2C00008 이다. 2Kbyte 만큼의 크기는 16진수로 0x800 이다. 그러므로 동적메모리 데이터 영역은 0xA2C00808 번지에서 끝난다. 동적메모리헤더는 8바이트이므로 두번째로 할당되는 ptr_b 의 동적메모리반환값은 0xA2C00810 이다. 마찬가지로 3Kbyte 는 16진수로 0xC00 이다. ptr_b 는 0xA2C01410 번지에서 끝나고 헤더를 건너뛰어 반환되는 ptr_c 의 값은 0xA2C01418 번지가 된다. 명확하게 나온다. 이어서 ptr_b 를 해제하고, 1Kbyte 짜리 ptr_d 를 할당한다. ptr_d 는 ptr_c 가 끝난 다음 할당되는 것이 아니라 해제되었던 ptr_b 가 사용하던 블럭을 그대로 다시 사용 하므로 ptr_b 가 받았던 주소값을 그대로 받는다.

위의 그림에 주소값을 넣어본다면 아래와 같다.

0xA2C00000  0xA2C00808      0xA2C01410
 |           |               |
 V           V               V
    |<--2K-->|  |<----3K---->|  |<------4K------>|
 +--+--------+--+------------+--+----------------+------------------------
 |  |        |  |    :       |  |                |
 |hd|        |hd|    :       |hd|                |
 |  |        |  | 1K :       |  |                |
 +--+--------+--+------------+--+----------------+------------------------
    ^           ^               ^
    |           |               |
0xA2C00008  0xA2C00810      0xA2C01418

아무것도 아닌 hello world 를 찍던 우리의 나빌눅스가 이제는 메모리 동적할당도 지원하는 어엿한 운영체제가 되어가고 있다. 이제 최초 설계했던 나빌눅스의 구현은 사실상 거의다 구현 하였다. 앞으로 남은 기능은 디바이스드라이버 계층 하나이다. 나빌눅스를 끝까지 완성 할 때 까지 같이 달려 주기 바란다. 역시 이번 회차 수정분이 반영된 소스를 첨부 하오니 즐겁게 공부 하시길 바란다.

이 글은 http://raonlife.com/navilera/blog/view/91/에 동시 연재 됩니다.

File attachments: 
첨부파일 크기
파일 navilnux_chap17.tgz29.08 KB

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.