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

나빌레라의 이미지

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

목차


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

18. 디바이스드라이버

임베디드OS의 존재 목적은 임베디드장비를 구동하기 위함이다. 임베디드 장비를 구동한다는 것은 임베디드 장비에 붙어 있는 여러 디바이스들을 원할하게 동작시키는 것이고 OS 에서 주변장치 제어를 책임지는 부분은 디바이스드라이버이다. 어차피 임베디드 OS 이므로 각각의 사용자테스크가 디바이스드라이버의 역할을 할 수도 있지만 엄밀한 의미에서 디바이스드라이버 역시 시스템의 자원을 이용하는 것이고, 시스템자원을 이용하기 위해서는 시스템콜을 사용해야 하고 그러면서도 디바이스 드라이버는 일반적인 시스템콜과 다르게 처리되어야 할 것이기 때문에 따로 디바이스드라이버 계층을 만들어야 한다.

이번 회차 강좌에서 구현하게 될 디바이스드라이버 계층은 기본적으로 리눅스의 캐릭터 디바이스 드라이버 계층의 개념을 차용 했다. 그래 봤자 open(), read(), write(), close() 네개의 함수를 구현한 것이고, 그 네개의 함수를 핸들링 하기 위해서 리눅스의 file_operation 구조체의 형식을 간략화 해서 구현 할 생각이다.

리눅스에서는 캐릭터 디바이스 드라이버를 핸들링 하기 위해 mkmod 로 생성하는 장치파일을 이용한다. 장치파일의 major 번호와 minor 번호가 디바이스드라이버를 등록할때 입력하는 major, minor 번호와 매칭하여 실제 디바이스 드라이버의 구현체 함수와 연결한다. 나빌눅스는 파일시스템이 없기 때문에 장치파일을 사용하지 않는다. 하지만 실제 디바이스와 open(), read(), write(), close() 시스템콜을 연결해야 하기 때문에, 방법을 생각 해야 한다. 나빌눅스에서는 이 방법으로 하나의 정수형 변수를 사용할 생각이다.

즉, 전체 디바이스드라이버 레이어의 기본 메카니즘은 이렇게 될 것이다. 나빌눅스의 file_operation 구조체는 배열로 나빌눅스 커널에 선언되고, open(), read(), write(), close()의 인자로 파일경로 대신, 나빌눅스 file_operations 구조체의 인덱스가 넘어간다. 시스템콜에서는 인덱스를 받아서 file_operation 구조체의 포인터를 받아 디바이스 드라이버에 구현된 함수 본체와 연결 되게 된다.

디바이스 드라이버는 나빌눅스에 또 다른 하나의 레이어 이기 때문에 디바이스드라이버 관리자 계층을 설계한다. navilnux_drv.h 파일을 새로 만들고, file_operation 구조체와 디바이스드라이버 관리자 자료구조를 작성 해 보자. 소스코드는 아래와 같다.

#ifndef _NAVIL_DRV
#define _NAVIL_DRV
 
#define DRVLIMIT 100
 
#define O_RDONLY    0
#define O_WRONLY    1
#define O_RDWR      2
 
typedef struct _fops{
    int (*open)(int drvnum, int mode);
    int (*read)(int drvnum, void *buf, int size);
    int (*write)(int drvnum, void *buf, int size);
    int (*close)(int drvnum);
} fops;
 
typedef struct _navil_free_drv {
    fops *navil_fops;
    int usecount;
    const char *drvname;
} Navil_free_drv;
 
typedef struct _navil_drv_mng {
    Navil_free_drv free_drv_pool[DRVLIMIT];
 
    void (*init)(void);
    int (*register_drv)(int, const char*, fops*);
} Navil_drv_mng;
 
void drv_init(void);
int drv_register_drv(int, const char*, fops*);
#endif

먼저 file_operation 을 나빌눅스에 맞춰 설계한 fops 구조체이다. 리눅스의 file_operation 구조체에는 open(), read(), write(), close() 외에도 ioctl(), lseek() 등 더 많은 함수포인터들이 정의되어 있지만 나빌눅스에서는 그 많은 함수들이 필요하지 않기 때문에 가장 많이 쓰고 필수적인 open, read, write, close 만 fops 구조체에 정의 하였다.

이어서 자유테스크블럭이나 자유메모리블럭, 자유메시지블럭과 같이 자유드라이버블록이라 명명한 Navil_free_drv 구조체 이다. 직전에 정의한 fops 포인터를 가지고 있고 디바이스 드라이버의 사용횟수를 표시하는 usecount 변수. 그리고 디바이스드라이버의 이름을 지정하는 drvname 변수가 있다. 이 구조체가 하나의 디바이스 드라이버를 추상화 하게 된다. 이어서 디바이스드라이버 관리자 구조체를 구현한다. 디바이스드라이버 관리자는 생각보다 훨씬 단순하다. 디바이스드라이버 관리자를 초기화 하는 init 함수포인터와 디바이스드라이버의 fops 를 커널에 등록하는 register_drv 함수포인터가 있다.

저 두개의 함수 본체는 navilnux_drv.c 이다. 물론 방금 추가한 navilnux_drv.h 는 navilnux.h 에 미리 추가 해 놓는다.

#include <navilnux.h>
 
Navil_drv_mng drvmng;
 
int drv_register_drv(int drvnum, const char *name, fops *drvfops)
{
    if(drvnum > DRVLIMIT || drvnum < 0){
        return -1;
    }
 
    if(drvmng.free_drv_pool[drvnum].usecount >= 0){
        return -1;
    }
 
    drvmng.free_drv_pool[drvnum].navil_fops = drvfops;
    drvmng.free_drv_pool[drvnum].drvname = name;
    drvmng.free_drv_pool[drvnum].usecount = 0;
 
    return 0;
}
 
void drv_init()
{
    int i;
    for(i = 0 ; i < DRVLIMIT ; i++){
        drvmng.free_drv_pool[i].navil_fops = (fops *)0;
        drvmng.free_drv_pool[i].usecount = -1;
        drvmng.free_drv_pool[i].drvname = (const char *)0;
    }
 
    drvmng.init = drv_init;
    drvmng.register_drv = drv_register_drv;
}

디바이스드라이버 매니저 커널 전역변수인 drvmng 를 선언해 놓는다. 다른 소스파일에서는 extern 으로 호출해 사용한다. drv_register_drv 함수 구현을 보기 전에 drv_init() 함수를 먼저 보도록 하자. 테스크관리자나 메모리 관리자들과 거의 같은 패턴의 코드이다. 자유디바이스드라이버블럭의 크기만큼 for 루프를 돌면서 navil_fops 포인터는 null 로 초기화 하고 usecount 는 -1로 초기화 한다. 0이 아니라 -1로 초기화 한 이유는 초기화 값은 -1 이고 디바이스 드라이버가 자유디바이스드라이버블럭에 할당이 되면 그때 0 이 되고 실제 디바이스드라이버가 open() 이 되면 1 씩 올라가는 방식을 택했기 때문이다. 디바이스드라이버의 이름을 표시하는 drvname 역시 아무것도 없이 null 로 초기화 한다.

drv_register_drv() 함수는 디바이스드라이버 매니저의 register_drv 함수포인터에 연결되는 함수이다. 인자로 넘어오는 drvnum 의 경계값 체크를 해서 경계값을 벗어나는 값이 인자로 넘어올 경우 -1 을 리턴한다. 이어서 usecount 를 체크한다. usecount 가 0보다 크거나 같으면 이전에 한번 할당된 자유디바이스드라이버블럭이기 때문에 역시 -1 로 에러를 리턴한다. 초기값이 -1 이기 때문에 한번도 할당되지 않은 자유디바이스드라이버블록이라면 이 에러체크를 통과한다. 모든 에러체크를 다 통과하고 나면 인자로 넘어온 fops 와 name 을 할당하고 usecount 를 0 으로 한다.

이렇게 해서 디바이스드라이버관리자는 모두 구현했다. 하지만 진짜는 지금 부터 이다. 동일한 open(), read(), write(), close() 시스템콜을 이용해서 인자로 넘어오는 drvnum 으로 서로 다른 open, read, write, close 가 실행 되어야 하므로 시스템콜로써 이를 구현 해야 한다.

open(), read(), write(), close() 에 대해서 시스템콜번호를 부여 하자. syscalltbl.h 함수이다.

#define SYS_OPEN        10
#define SYS_CLOSE       11
#define SYS_READ        12
#define SYS_WRITE       13

각각 10, 11, 12, 13번을 부여 받았다. 어느덧 나빌눅스에도 시스템콜이 무려 13개나 추가되었다는 말이다. 꽤나 번듯한 OS가되어 가고 있는 나빌눅스이다. :) 시스템 콜 번호가 할당 되었으니, 시스템콜 본체를 구현 해 보자. 프로토 타입은 navilnux_sys.h 에 선언되고 구현은 navilnux_sys.c 에 구현 된다.

int sys_open(int, int);
int sys_close(int);
int sys_read(int, void*, int);
int sys_write(int, void*, int);

fops 구조체에 선언된 open, read, write, close 함수포인터의 매개변수 타입과 일치하게 각 함수의 프로토타입을 선언한다. 당연히 해당 시스템콜 본체에서는 디바이스드라이버 관리자 계층을 통해서 자유디바이스드라이버블럭에 할당된 navil_fops 포인터를 통해서 실제 open, read, write, close 함수를 선언할 것이기 때문에 매개변수 갯수와 타입을 맞춰 주어야 한다.

int sys_open(int drvnum, int mode)
{
    if(drvnum > DRVLIMIT || drvnum < 0){
        return -1;
    }
 
    if(drvmng.free_drv_pool[drvnum].usecount < 0){
        return -1;
    }
 
    drvmng.free_drv_pool[drvnum].usecount++;
 
    return drvmng.free_drv_pool[drvnum].navil_fops->open(drvnum, mode); 
}
 
int sys_close(int drvnum)
{
    if(drvnum > DRVLIMIT || drvnum < 0){
        return -1;
    }
 
    drvmng.free_drv_pool[drvnum].usecount--;
 
    return drvmng.free_drv_pool[drvnum].navil_fops->close(drvnum);
}
 
int sys_read(int drvnum, void *buf, int size)
{
    if(drvnum > DRVLIMIT || drvnum < 0){
        return -1;
    }
 
    return drvmng.free_drv_pool[drvnum].navil_fops->read(drvnum, buf, size);
}
 
int sys_write(int drvnum, void *buf, int size)
{
     if(drvnum > DRVLIMIT || drvnum < 0){
        return -1;
    }
 
    return drvmng.free_drv_pool[drvnum].navil_fops->write(drvnum, buf, size);
}
 
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_syscallvec[SYS_OPEN] = (unsigned int)sys_open;
    navilnux_syscallvec[SYS_CLOSE] = (unsigned int)sys_close;
    navilnux_syscallvec[SYS_READ] = (unsigned int)sys_read;
    navilnux_syscallvec[SYS_WRITE] = (unsigned int)sys_write;
}

sys_open, sys_read, sys_write, sys_close() 함수 모두 drvnum 의 경계값 체크는 동일한 코드로 수행한다. 따로 설명하지 않겠다. 그리고 sys_open() 은 usecount 가 음수일경우에 대해 따로 에러체크를 한번 더 한다. usecount 가 음수인 상태에서 open() 시스템콜을 사용했다는 것은 register_drv() 도 하지 않은 채로 open() 으로 장치에 대한 열기를 시도했다는 것이므로 에러를 반환 해 주어야 한다. sys_open() 에서는 usecount 를 하나 증가 시키고 sys_close() 에서는 usecount를 하나 감소 시킨다. 어떤 프로그램을 구현 할 때 필요할 지는 모르겠지만 usecount변수의 값을 알면 해당 장치가 몇개의 테스크에서 open 되었는지를 알 수 있다.

에러체크를 다 하고 나면 open, read, write, close 모두 인자로 넘어온 drvnum 을 인덱스로 하여 디바이스 드라이버 매니저의 자유디바이스드라이버블럭에 접근하여 navil_fops 에 포인터로 할당되어 있는 각 디바이스드라이버의 fops 에 접근해서 실제 구현된 디바이스드라이버의 open, read, write, close 함수를 실행 하게 된다. 이런방식을 사용함으로써 하나의 open(), read(), write(), close() 시스템콜 이름을 사용해서 각 장치마다 그 장치에 적합한 open, read, write, close 함수를 호출 할 수 있게 되는 것이다.

이제 시스템콜랩퍼 라이브러리를 작성해 보자. 특히 read() 시스템콜의 경우 읽어올 데이터가 장치에 아직 준비되어 있지 않는다면 데이터가 들어 올때까지 사용자 영역에서 blocking 걸려야 한다. 그러므로 사용자영역에서 강제로 스케줄링을 돌리는 2차 랩퍼 함수가 필요하다. navilnux_lib.h 에 두 단계의 랩퍼함수 프로토타입을 선언한다.

extern int open(int, int);
extern int close(int);
extern int read(int, void*, int);
extern int write(int, void*, int);
          :
          :
int navilnux_open(int, int);
int navilnux_close(int);
int navilnux_read(int, void*, int);
int navilnux_write(int, void*, int);

open, read, write, close 함수는 어셈블리로 작성된 swi 로 직접 시스템콜을 호출하는 함수이고, navilnux_open, navilnux_read, navilnux_write, navilnux_close 함수는 직접 사용자 테스크로 제공되는 랩퍼 함수이다. navilnux_lib.S 파일의 내용을 보자.

.global open
open:
    swi SYS_OPEN
    mov pc, lr
 
.global close
close:
    swi SYS_CLOSE
    mov pc, lr
 
.global read
read:
    swi SYS_READ
    mov pc, lr
 
.global write
write:
    swi SYS_WRITE
    mov pc, lr

별다를 것 없는 똑 같은 내용이다. 이어서 navilnux_clib.c 파일의 내용을 보자.

int navilnux_open(int drvnum, int mode)
{
    return open(drvnum, mode);
}
 
int navilnux_close(int drvnum)
{
    return close(drvnum);
}
 
int navilnux_read(int drvnum, void *buf, int size)
{
    int ret_value = 0;
    while(1){
        ret_value = read(drvnum, buf, size);
        if(ret_value >= 0){
            return ret_value;
        }else if(ret_value == -1){
            return -1;
        }else{
            call_scheduler();
        }
    }
}
 
int navilnux_write(int drvnum, void *buf, int size)
{
    return write(drvnum, buf, size);
}

open, read, write 함수는 그냥 계층을 유지하기 위해 감싸주는 역할 만 할 뿐 아무런 일도 하지 않는다. read 함수만이 반환값이 0보다 클 경우에는 반환값 자체를 리턴하고 -1 일때는 에러, 그외의 경우에는 강제로 스케줄링을 해서 blocking 한다. read 함수의 반환값은 읽은 데이터의 사이즈이고, 0일때는 read 함수가 더이상 읽을 데이터가 없을 때 반환하는 것이므로 0을 포함한 값에 대해서 정상 종료 처리를 해 준다.

이상으로 시스템콜 계층에 navilnux_open(), navilnux_read(), navilnux_write(), navilnux_close() 함수를 추가 함으로써 디바이스드라이버의 전체 구조는 모두 완성 하였다. 그럼 이제 디바이스드라이버를 하나 만들어서 넣어보자.

이지보드에는 활용할 수 있는 주변장치들이 꽤 많이 달려 있는 편이다. 그중에서 가장 손쉽고 간단하게 제어 할 수 있는 것이다. 바로 LED 이다. LED 를 점멸하는 것은 본 강좌가 시작할때 쯤인 2회차 강좌에서 이지부트의 함수를 그대로 차용해서 동작 시켜본 경험이 있다. 바로 이 동작을 이제는 어엿한 OS 가 된 나빌눅스의 디바이스 드라이버 계층을 통해서 제어 해 보도록 하겠다. LED 를 켜고 끄는 동작은 사용자 테스크가 OS 에 어떤 신호를 보내서 동작시키는 것이다. 즉 값을 전달 하는 것이다. 그러므로 navilnux_write() 를 사용한다. 그럼 navilnux_read() 를 써먹을려면 어떤 장치를 이용해야 좋을까. 적당한 것이 있다. 바로 GPIO를 이용한 IRQ 핸들러를 작성할 때 보드에 붙여 놓았던 스위치 이다. IRQ 핸들러 계층도 디바이스드라이버 계층에 포함시켜 보도록 하자.

전 강좌에서 GPIO 를 제어 하기 위해서 데이터시트를 열심히 보고 세팅해서 커널에 있는 irq_Handler 에 직접 printf 를 넣어서 스위치가 눌릴 때마다 메시지가 시리얼을 통해서 터미널 프로그램에 찍히는 것을 확인 하였다. 하지만 그렇게 한다면 다른 IRQ 를 세팅하고 추가 할때 마다 irq_Handler 함수에 계속 내용이 추가 되어야 한다. 물론 그렇게 하는 것이 당연하지만 필자는 irq_Handler 함수 자체는 최대한 깔끔하고 반복적으로 작업하면 되게끔 하고 싶다.

그래서 결정했다. 커널에 IRQ Handler Vector Table 을 만드는 것이다. 그렇게 한다음 irq_Handler 함수는 해당 IRQ 번호에 해당하는 IRQ 가 들어올경우 역시 해당번호의 IRQ Handler Vector Table 에 할당된 함수를 실행하기만 하면 되는 것이다.

IRQ 핸들러 처리는 디바이스드라이버와는 약간 다르게 생각해야 할 개념이므로 이를 디바이스드라이버매니저 계층에 포함하기엔 개념상 약간 어울리지 않는 듯 하여 그냥 따로 커널에 존재 시키기로 결정 하였다. navilnux.c 파일의 커널 전역변수 선언하는 부분을 수정한다.

#include <navilnux.h>
 
extern Navil_task_mng taskmng;
 
Navil_free_task *navilnux_current;
Navil_free_task *navilnux_next;
Navil_free_task dummyTCB;
int navilnux_current_index;
 
unsigned int navilnux_time_tick;
 
int (*navilnux_irq_vector[IRQNUM])(void);
              :
              :

제일 아래에 navilnux_irq_vector 라는 함수포인터 배열이 선언되었다. IRQNUM 갯수만큼의 배열이다. 관련 define 문은 당연히 navilnux.h 에 선언되어 있다.

 
             :
             :
#define IRQNUM  64
#define IRQ0    0
#define IRQ1    1
#define IRQ2    2
#define IRQ3    3
#define IRQ4    4
#define IRQ5    5
#define IRQ6    6
#define IRQ7    7
#define IRQ8    8
// 여기에 IRQ핸들러 등록번호를 추가 해 주세요
#define IRQ63   63
 
#endif

IRQNUM 은 64개이고 커널에는 각 IRQ 번호가 미리 정의되어 있어야 한다. 0부터 63번까지 다 써 놓으려니 파일길이가 너무 길어져서 GPIO0 번의 인터럽트가 할당된 ICIP 의 비트번호인 8번까지만 순차적으로 define 해놓고 마지막값인 63번을 define 해 놓았다. 이후 개발자들이 필요하다면 이곳에 추가로 define 문을 써 주기 바란다. 그리고 irq_Handler 의 내용을 수정한다.

void irqHandler(void)
{
    if( (ICIP&(1<<27)) != 0 ){
        OSSR = OSSR_M1;
        OSMR1 = OSCR + 3686400;
 
        navilnux_time_tick++;
 
        scheduler();
    } 
 
    if ( (ICIP&(1<<8)) != 0 ){
        GEDR0 = 1;
        if(navilnux_irq_vector[IRQ8] != NULL) navilnux_irq_vector[IRQ8]();
    }
}

OS timer 관련 IRQ 처리는 그대로 두고, GPIO0번에 대한 코드만 수정 하였다. ICIP의 8번 비트에 대해 처리한다. 그래서 별다른 이유없이 IRQ8 번 벡터에 실제 핸들러 함수를 할당 하였다. 혹시나 핸들러가 할당되지 않은 인터럽트가 발생할 수도 있으므로 NULL 이 아닌지를 검사한다음 NULL 이 아니라면 핸들러 함수가 할당된것이므로 핸들러 함수를 실행 한다.

이제 준비는 모두 끝났다. read() 에서 스위치의 입력을 대기 하고 write() 에서 LED 를 제어하는 디바이스드라이버를 만들어 보도록 하자. 디바이스드라이버 이름은 mydrv 로 하자. 새로운 디바이스드라이버는 새로운 파일로 만들어 진다. my_drv.c 로 하자.

#include <navilnux.h>
 
extern Navil_drv_mng drvmng;
extern int (*navilnux_irq_vector[64])(void);
 
int switch_pushed;
 
int gpio0_irq_handler(void)
{
    if(switch_pushed) return -1;
 
    printf("Switch Push!! in Device Driver Layer\n");
    switch_pushed = 1;
    return 0;
}
 
int mydrv_open(int drvnum, int mode)
{
    navilnux_irq_vector[IRQ8] = gpio0_irq_handler;
    switch_pushed = 0;
 
    return 0;
}
 
int mydrv_close(int drvnum)
{
    return 0;
}
 
int mydrv_read(int drvnum, void *buf, int size)
{
    int *b = (int *)buf;
    if(switch_pushed == 1){
        *b = switch_pushed;
        switch_pushed = 0;
        return 4;
    }else{
        return -2;
    }
}
 
int mydrv_write(int drvnum, void *buf, int size)
{
    int *b = (int *)buf;
    int n = (int)b[0];
    int s = (int)b[1];
    GPIO_SetLED(n,s);
 
    return 0;
}
 
fops mydrv_fops = 
{
    open : mydrv_open,
    read : mydrv_read,
    write : mydrv_write,
    close : mydrv_close,
};
 
int mydrv_init()
{
    return drvmng.register_drv(MYDRV, "navilnux first drv", &mydrv_fops);
}

그냥 쭉 훑어 보기만 해도 각 함수들이 무슨 역할을 하는 것이고, 왜 저렇게 작성되었고, 앞으로 어떤식으로 코드가 진행될지 거의 예측이 될만한 아주 난이도 낮은 코드이다. 지금까지 나빌눅스에 나오는 코드들이 다 저렇다. 누누이 말하지만 나빌눅스 전체 코드에서 어렵게 작성되어 해석이 힘든 코드는 전혀 없다. OS 만들기는 쉽다.

시작 부분에 두개의 커널 전역변수를 extern 으로 불러온다. 디바이스드라이버이므로 당연히 drvmng 를 불러오고, read() 에서 스위치입력을 처리 할것이므로 IRQ 핸들러 벡터에 핸들러를 등록하기 위해 navilnux_irq_vector 배열 변수를 extern 으로 불러 왔다. 각 함수 별로 구현을 살펴 보도록 하자. 우선 open() 함수이다.

int gpio0_irq_handler(void)
{
    if(switch_pushed) return -1;
 
    printf("Switch Push!! in Device Driver Layer\n");
    switch_pushed = 1;
    return 0;
}
 
int mydrv_open(int drvnum, int mode)
{
    navilnux_irq_vector[IRQ8] = gpio0_irq_handler;
    switch_pushed = 0;
 
    return 0;
}

gpio0_irq_handler() 함수는 navilnux_irq_vector 에 등록될 핸들러 함수이다. 즉 GPIO0 에 들어오는 IRQ 의 최종 수행은 gpio0_irq_hanler() 함수라는 것이다. switch_pushed 라는 모듈전역변수가 0이 아니라면 핸들러 함수는 아무것도 하지 않는다. 이는 read() 가 처리 되기 전에 중첩해서 IRQ 가 발생했을 때 생기는 문제를 해결하기 위한 일종의 채터링방지 코드이다. 에러방지코드를 통과 하고 나면 스위치가 눌렸다는 것을 시리얼을 통해 출력하고 switch_pushed 전역변수를 1로 만든다. 즉 스위치가 눌렸다는 것을 표시한다.

mydrv_open() 함수는 지금 만들고 있는 디바이스드라이버의 open() 함수이다. 보통 장치초기화와 같은 역할을 하게 된다. mydrv 는 스위치와 LED를 사용하기 때문에 그다지 장치를 초기화 할게 없다. 다만 navilnux_irq_vector 에 gpio0_irq_handler() 함수를 연결시켜주는 것과 switch_pushed 전역변수를 0 으로 초기화 하는 역할을 한다.

이어서 장치의 사용이 다 끝난다음 해당 장치를 닫는 mydrv_close() 함수를 보자.

int mydrv_close(int drvnum)
{
    return 0;
}

아무것도 하는 일이 없다. 커널 자원을 사용하거나 무언가 할당받은 메모리라도 있으면 그것을 해제하는 코드라도 있었을 터이지만 그런것이 전혀 없다. 그러므로 하는 것이 아무것도 없다. 넘어가자.

이제 스위치 입력을 대기하다가 스위치 입력이 들어오면 이를 사용자영역에 전달 해 주는 mydrv_read() 함수를 보자.

int mydrv_read(int drvnum, void *buf, int size)
{
    int *b = (int *)buf;
    if(switch_pushed == 1){
        *b = switch_pushed;
        switch_pushed = 0;
        return 4;
    }else{
        return -2;
    }
}

void * 타입으로 넘어오는 매개변수에 int 형 값을 전달하기 위해 내부 포인터 변수로 buf 로 넘어온 주소값을 받아 놓는다. 그런다음 모듈전역변수인 switch_pushed 변수이 값을 본다. 이 변수의 값이 1이 되면 스위치가 눌린것이므로 그 값을 내부포인터변수 b에 값으로 전달한다. 결과적으로 매개변수 buf 에 값으로 전달되고 이것은 그대로 사용자테스크에 값으로 전달 된다. int 형 값을 전달 했으므로 size 에 관계 없이 4바이트를 리턴한다. 만약 switch_pushed 가 아직 1이 아니라면 스위치가 눌린것이 아니므로 -2를 리턴한다. 나빌눅스는 전통적으로 커널영역 시스템콜에서 -2를 리턴하면 사용자영역 랩퍼 함수에서 blocking 걸리게 된다.

다음으로 LED 를 제어 하는 mydrv_write() 함수를 보자. LED를 제어 할려면 스위치제어할때 처럼 gpio 관련 이것저것 많은 레지스터를 세팅 해 주어야 한다. 하지만 우리는 이지부트로 부터 나빌눅스의 초기코드를 상당부분 가져 왔고, LED를 제어하는 코드역시 마찬가지이다. 이를 이용해 간단하게 LED점멸을 구현했던 강좌가 2회 강좌 이다. 본 회차 에서도 역시 그때 그 함수를 그대로 사용 할 생각 이다. 다시한번 강조하지만 기존에 있는건 그대로 사용해 주는 것이 예의 이다.

int mydrv_write(int drvnum, void *buf, int size)
{
    int *b = (int *)buf;
    int n = (int)b[0];
    int s = (int)b[1];
    GPIO_SetLED(n,s);
 
    return 0;
}

mydrv_write() 함수는 매개변수 buf 가 int형 2개 짜리 배열이라고 가정한다. 첫번째 인덱스에는 LED의 번호가 두번째 인덱스에는 LED의 상태(켬/끔)이 값으로 넘어온다고 가정하고 GPIO_SetLED()함수를 사용하여 LED를 제어 한다. 이지부트에서 가져온 저 함수는 printf() 함수와 함께 정말 잘 써먹는 듯 하다. :)

필요한 네개의 함수를 다 구현했으니 이제 할일은 fops 에 해당 함수들을 등록하는 것이다. 역시나 리눅스에서 캐릭터 디바이스 드라이버를 구현할때와 정말 같은 프로세스로 진행되고 있다. fops 구조체에 함수를 등록하는 것 역시 마찬가지 이다.

fops mydrv_fops = 
{
    open : mydrv_open,
    read : mydrv_read,
    write : mydrv_write,
    close : mydrv_close,
};

mydrv_fops 라는 이름으로 fops 구조체변수를 선언하면서 위에서 구현한 네개의 함수를 바로 함수포인터에 연결시켜 초기화 하였다. mydrv_fops 변수는 모듈전역변수 이다. 마지막으로 지금까지 구현한 mydrv 모듈을 초기화 시켜주는 함수를 구현한다.

int mydrv_init()
{
    return drvmng.register_drv(MYDRV, "navilnux first drv", &mydrv_fops);
}

mydrv_init() 함수는 앞서 구현한 register_drv() 함수를 이용해 mydrv_fops 를 커널에 등록한다. 그래야만 open, read, write, close 시스템콜을 이용해 디바이스드라이버의 함수를 실행 할 수 있다. 그럴려면 mydrv_init() 함수는 커널에 의해 자동 호출 되어야 하는데 어디쯤 들어가야 좋을까 고민하다가 어차피 디바이스 드라이버인데 그냥 디바이스드라이버매니저를 초기화 하는 함수에 같이 포함시키기로 결정 했다. drv_init() 함수를 다음과 같이 수정한다.

void drv_init()
{
    int i;
    for(i = 0 ; i < DRVLIMIT ; i++){
        drvmng.free_drv_pool[i].navil_fops = (fops *)0;
        drvmng.free_drv_pool[i].usecount = -1;
        drvmng.free_drv_pool[i].drvname = (const char *)0;
    }
 
    drvmng.init = drv_init;
    drvmng.register_drv = drv_register_drv;
 
    // user device driver
    mydrv_init();
}

함수의 제일 아래에 mydrv_init() 를 호출하는 부분이 보일 것이다. 만약 추가로 디바이스드라이버가 더 추가된다면 mydrv_init() 를 호출하는 줄 밑으로 계속 추가되는 디바이스드라이버의 초기화 함수들이 들어가면 될 것이다. 서로 다른 파일에서 함수를 호출 했으므로 프로토타입을 헤더파일에 선언 해 주어야 한다. navilnux_drv.h 를 수정한다.

#ifndef _NAVIL_DRV
#define _NAVIL_DRV
 
#define DRVLIMIT 100
 
#define O_RDONLY    0
#define O_WRONLY    1
#define O_RDWR      2
 
typedef struct _fops{
    int (*open)(int drvnum, int mode);
    int (*read)(int drvnum, void *buf, int size);
    int (*write)(int drvnum, void *buf, int size);
    int (*close)(int drvnum);
} fops;
 
typedef struct _navil_free_drv {
    fops *navil_fops;
    int usecount;
    const char *drvname;
} Navil_free_drv;
 
typedef struct _navil_drv_mng {
    Navil_free_drv free_drv_pool[DRVLIMIT];
 
    void (*init)(void);
    int (*register_drv)(int, const char*, fops*);
} Navil_drv_mng;
 
void drv_init(void);
int drv_register_drv(int, const char*, fops*);
 
 
// user define device drivers init function
#define MYDRV 0
int mydrv_init(void);
 
#endif

마찬가지로 제일 아랫줄에 앞으로 mydrv 를 구분하게 될 이름인 MYDRV 를 define 하고 mydrv 의 초기화 함수에 대한 프로토타입이 선언되어 있다. 추가되는 디바이스드라이버가 있드면 이 아랫줄에 구분하게 될 이름을 define 하고 초기화 함수에 대한 프로토타입을 계속 추가해 나가면 될 것이다.

디바이스 드라이버를 작성해서 커널에 포함시키는 것 까지 완료 했으므로 사용자 영역에서 이를 사용해 보는 일 만 남았다. 전체적인 동작은 이렇게 하자. 사용자 테스크는 스위치의 입력을 계속 기다리가다 스위치입력이 들어오면 LED를 하나 켜는 것이다. 그리고 다시 또 들어오면 앞서 켜졌던 LED는 꺼지고 그 아래 LED가 켜진다. 이런식으로 스위치입력이 들어 올때마다 LED는 순서대로 점멸 하는 것이다. 물론 나빌눅스는 리얼타임OS가 아니기 때문에 스위치 입력이 있고나서 약간의 딜레가 온 다음 LED에 반응이 올 수도 있다.

void user_task_9(void)
{
    int a, b, c;
    int led[2] = {0};
 
    a = 0;
    b = 0;
    c = a + b;
 
    navilnux_open(MYDRV,O_RDWR);
 
    while(1){
        navilnux_read(MYDRV, &a, 4);    
        printf("TASK9 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        printf("Device Driver Returned %d\n", a);
 
        c = b - 1;
        if(b == 0) c = 3;
        led[0] = c;
        led[1] = LED_OFF;
        navilnux_write(MYDRV, led, 8);
 
        led[0] = b;
        led[1] = LED_ON;
        navilnux_write(MYDRV, led, 8);
        b++;
        if(b == 4) b = 0;
 
        navilnux_sleep(5);
    }
    navilnux_close(MYDRV);
}

직전 강좌 까지 사용자 테스크는 8개 였는데 이번 디바이스드라이버를 테스트 하기 위해 사용자테스크를 하나 더 추가해서 9개가 되었다. TASK9 는 무한루프에 들어가기 전에 navilnux_open() 으로 MYDRV 를 연다. 그럼 이때에 스위치입력에 대한 IRQ핸들러가 커널에 등록된다. 그리고 무한루프에 진입하자마자 스위치입력을 navilnux_read() 시스템콜로 대기 하게 된다. 디바이스드라이버로 부터의 읽어온 값은 a 변수에 저장한다. 계속 blocking 걸려 있다가 스위치 입력이 들어오면 TASK9 관련 출력을 하고 a 값을 출력한다. 이 값은 무조건 1 이 나와야 한다. 1이 아니라면 무언가 잘못 된 것이다. 그리고 LED를 제어하기 위한 코드가 들어간다. 변수 c 를 이용하는 navilnux_write() 가 있는 코드는 직전에 켜져 있던 LED를 끄기 위한 코드이고, 변수 b 를 이용하는 navilnux_write() 가 있는 코드는 이번에 새로 LED를 켜기 위한 코드이다. 루프마다 5초를 대기 한다.

이번 강좌에서는 몇개의 파일이 추가 되었다. Makefile 을 수정한다.

CC = arm-linux-gcc
LD = arm-linux-ld
OC = arm-linux-objcopy
 
CFLAGS    = -nostdinc -I. -I./include 
CFLAGS   += -Wall -Wstrict-prototypes -Wno-trigraphs -O0
CFLAGS   += -fno-strict-aliasing -fno-common -pipe -mapcs-32 
CFLAGS   += -mcpu=xscale -mshort-load-bytes -msoft-float -fno-builtin
 
LDFLAGS   = -static -nostdlib -nostartfiles -nodefaultlibs -p -X -T ./main-ld-script
 
OCFLAGS = -O binary -R .note -R .comment -S
 
CFILES = entry.S navilnux.c navilnux_memory.c navilnux_task.c navilnux_user.c navilnux_lib.S navilnux_sys.c navilnux_msg.c navilnux_drv.c my_drv.c
HFILES = include/navilnux.h include/navilnux_memory.h include/navilnux_task.h include/navilnux_user.h include/navilnux_lib.h include/navilnux_sys.h include/syscalltbl.h include/navilnux_msg.h include/navilnux_drv.h
 
all: $(CFILES) $(HFILES)
	$(CC) -c $(CFLAGS) -o entry.o entry.S
	$(CC) -c $(CFLAGS) -o gpio.o gpio.c
	$(CC) -c $(CFLAGS) -o time.o time.c
	$(CC) -c $(CFLAGS) -o vsprintf.o vsprintf.c
	$(CC) -c $(CFLAGS) -o printf.o printf.c
	$(CC) -c $(CFLAGS) -o string.o string.c
	$(CC) -c $(CFLAGS) -o serial.o serial.c
	$(CC) -c $(CFLAGS) -o lib1funcs.o lib1funcs.S
	$(CC) -c $(CFLAGS) -o navilnux.o navilnux.c
	$(CC) -c $(CFLAGS) -o navilnux_memory.o navilnux_memory.c
	$(CC) -c $(CFLAGS) -o navilnux_task.o navilnux_task.c
	$(CC) -c $(CFLAGS) -o navilnux_user.o navilnux_user.c
	$(CC) -c $(CFLAGS) -o navilnux_lib.o navilnux_lib.S 
	$(CC) -c $(CFLAGS) -o navilnux_clib.o navilnux_clib.c 
	$(CC) -c $(CFLAGS) -o navilnux_sys.o navilnux_sys.c
	$(CC) -c $(CFLAGS) -o navilnux_msg.o navilnux_msg.c
	$(CC) -c $(CFLAGS) -o navilnux_drv.o navilnux_drv.c
	$(CC) -c $(CFLAGS) -o drvs.o my_drv.c
	$(LD) $(LDFLAGS) -o navilnux_elf entry.o gpio.o time.o vsprintf.o printf.o string.o serial.o lib1funcs.o navilnux.o navilnux_memory.o navilnux_task.o navilnux_msg.o navilnux_sys.o navilnux_lib.o navilnux_clib.o navilnux_user.o navilnux_drv.o drvs.o
	$(OC) $(OCFLAGS) navilnux_elf navilnux_img
 
clean:
	rm *.o
	rm navilnux_elf
	rm navilnux_img

이제 가벼운 마음으로 make 를 쳐서 빌드 한다음 빌드된 이미지를 이지보드에 다운로드해서 부팅 해보자. 그리고 스위치를 눌러보자 LED 가 이동하는가! 필자는 LED에 켜진 불이 밑으로 이동하는것이 보였다. 그리고 화면에는 아래와 같이 메시지가 잘 출력 되었다.

TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 2                                                                  
Switch Push!! in Device Driver Layer                                            
TASK9 - a:a0cfffe8      b:a0cfffe4      c:a0cfffe0                              
Device Driver Returned 1                                                        
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 3                                                                  
ITC send!!!                                                                     
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
ITC get!!!! ---> 342                                                            
TASK5 out critical section                                                      
TASK6 enter critical section SEMAPHORE                                          
TASK6 - a:a09fffe8      b:a09fffe4      c:a09fffe0                              
TASK7 enter critical section SEMAPHORE                                          
TASK7 - a:a0afffe8      b:a0afffe4      c:a0afffe0                              
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 2                                                                  
TASK4 enter critical section                                                    
TASK4 - a:a07fffe8      b:a07fffe4      c:a07fffe0                              
TASK6 out critical section SEMAPHORE                                            
TASK7 out critical section SEMAPHORE                                            
TASK8 enter critical section SEMAPHORE                                          
TASK8 - a:a0bfffe8      b:a0bfffe4      c:a0bfffe0                              
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 3                                                                  
ITC send!!!                                                                     
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
ITC get!!!! ---> 342                                                            
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 2                                                                  
Switch Push!! in Device Driver Layer  

Switch Push!! in Device Driver Layer 메시지는 핸들러에서 뿌리는 메시지이고 약간 후에 나오는 Device Driver Returned 1 메시지는 사용자 테스크에서 출력하는 메시지이다. 라운드로빈 스캐줄링이기 때문에 핸들러가 동작하고 나서 이를 처리하는 사용자테스크가 컨택스트를 받기 까지 시간이 걸리기 때문이다. 본강좌에서는 잘 동작하는 것을 확인 하는 것이 중요하므로 큰 문제는 아니다. 출력도 잘되고 LED도 잘 움직이니 잘 동작하는 것이다.

본 강좌 까지 해서 실질적인 나빌눅스의 구현은 모두 끝났다. 계획상 앞으로 2회의 강좌가 더 남았는데, 직접 프로그래밍을 하는 것은 이번강좌가 마지막이다. 한번 만들어 볼까! 하고 시작했던 OS 만들기가 결국 이렇게 해서 나름대로 만족스럽고 성공적으로 완성되었다. 모두 모두 축하 한다. 이번강좌도 코드분량이 좀 많고 다소 길다. 이번 회차분이 포함된 소스코드를 첨부하오니 강좌와 같이 보면서 이해에 도움이 되길 바란다.

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

File attachments: 
첨부파일 크기
파일 navilnux_chap18.tgz30.6 KB

댓글

DebPolaris의 이미지

매번 글이 올라오는 것을 보면서 대단하시다고 생각하고 있습니다. 저는 지금 파이썬부터 공부하고 있어서
c언어는 아직 모르지만 배우고 나서 따라하면서 공부하고 싶습니다.

남이 가르쳐주는 것만 받아들이는 것이 아니라, 스스로 만들고, 고쳐가는 사람을 '해커'라고 부른다.
그리고 자신이 쌓아온 노하우를 거리낌없이 나눌 줄 아는 사람을 '진정한' 해커라고 한다.
-Rob Flickenger 'Linux server hacks'
http://heunoni.tistory.com/

-----------------------------------------------------
남이 가르쳐주는 것만 받아들이는 것이 아니라, 스스로 만들고, 고쳐가는 사람을 '해커'라고 부른다.
그리고 자신이 쌓아온 노하우를 거리낌없이 나눌 줄 아는 사람을 '진정한' 해커라고 한다.
-Rob Flickenger 'Linux server hacks'

DEBIAN TESTING, KDE...
debpolaris.blogspot.kr

verimuch의 이미지

저는 왜 스위치를 on 시키면 보드가 reset이 될까요?;;

지난번 외부인터럽트 핸들링때 했던 스위치 구성맞죠?
그때도 안되서 넘어가긴했는데 마지막까지 이러면...

혹시 저항을 안달았는데 그것때문에 그런걸까요?
저항이 필요하다면 몇옴짜리가 필요한거죠?
full-up저항을 찾아봤는데 애매하더라구요;;

다른분들은 혹시 되는가요?
---------------------------------
Life is not fair, Get used to it.

---------------------------------
Life is not fair, Get used to it.

나빌레라의 이미지

흠..

저도 그러네요..

근데 또 어떨때는 잘 되고..

출판본 원고 작업 도중에 발견되어서 아직 원인 파악이 잘 되고 있는데

왠지 컨택스트 스위칭 부분에서 프로세서 모드에 따라

레지스터 백업/복구 쪽에서 뭔가 꼬이거나

아니면 irqhandlervector 쪽 문제 같습니다.

일단 ARM core에서 Exception이 발생 한 것이면 연결의 문제는 아마 없을 겁니다.

근데 풀업 연결에서 저항을 안달으셨다면 회로가 조금 불안 해 질 지도 모르겠군요.(내부 풀업이 있다면 별 문제 없을 수도 있겠네요)

아무래도 출판본 작업 하면서 그 부분을 다시 고민 해 봐야 겠네요.

감사합니다.

-----
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

나빌레라의 이미지

인터럽트 중첩을 허용하지 않게 설계한 나빌눅스에서
인터럽트 중첩이 발생 하여 생긴 문제같습니다.

출판본에 반영되는 소스코드에는 verimuch님께 발생하는 문제가 해결된 코드가 들어갑니다.
(이렇게 라도 차별화 해야 책이 팔리겠죠..^^)

----------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

verimuch의 이미지

그럼 제대로 중첩이 허용되지 않도록 설계하신건가요?
그렇다면 기존의 IRQ 활성화/비활성화 부분이 먼가 오류가 있었다는 건가요?ㅎ

혹시나해서 ARM System Developer's Guide에 나온 IRQ 활성화/비활성화 코드를 넣었는데 안되더군요;;

::활성화::
mrs r1, cpsr
bic r1, r1, #0x80
msr cpsr_c, r1

::비활성화::
mrs r1, cpsr
orr r1, r1, #0x80
msr cpsr_c, r1

아님 중첩허용?
---------------------------------
Life is not fair, Get used to it.

---------------------------------
Life is not fair, Get used to it.

댓글 달기

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