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

나빌레라의 이미지

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

목차


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

15. 강제로 컨텍스트 스위칭 시키기 그리고 ITC (Inter Task Communication)

전통적인 유닉스 시스템에서는 IPC(Inter Process Communication) 라는 통칭으로 파이프와 FIFO, 메시지큐, 공유메모모리, 유닉스소켓 같은 프로세스간에 데이터를 주고 받을 수 있는 수단을 제공한다. 나빌눅스는 임베디드OS이고 프로세스가 아니라 테스크의 개념을 사용한다. 그러므로 테스크간 통신이라 하여 ITC(Inter Task Communication)을 구현 해 보도록 하겠다.

나빌눅스에서 지원하는 ITC는 가장 최소화 한 테스크간 통신의 기본 원리를 이해 할 수 있는 수준에서 구현 하도록 하겠다. 그래서 메시지큐나 파이프 처럼 다수의 바이트를 전송하는 복잡한 방식 대신 그냥 한개의 int 형 자료를 테스크에서 테스크로 전송하는 매커니즘을 구현 하겠다.

방법 자체는 복잡하지 않다. 커널 전역변수를 공유하면서 전역변수에 데이터가 있으면 가져오고 없으면 블럭걸리는 단순한 방법이다. 주의 할 것은 멀티테스크 운영체제이기 때문에 블럭 걸릴때 해당 테스크가 마냥 블럭 걸리는 것이 아니라, 바로 바로 스케줄러에게 cpu 사용권을 반납 해 주어야 한다. 그래서 따로 사용자 테스크에서 임의로 스케줄러를 호출 하는 방법 또한 구현 해야 한다.

사용자 테스크에서 임의로 컨택스트 스위칭과 스케줄러를 호출하는 것 역시 일종의 시스템 콜 이므로, 이전 강좌에서 구현 했던 시스템콜 레이어를 타도록 하자. 하지만 그 특성상 navilnux_syscallvec 커널 전역 배열에 함수를 등록하기엔 구현상의 무리가 좀 따른다. 그래서 별도로 entry.S 에 따로 컨텍스트 스위칭과 스케줄러만 곧바로 호출하는 어셈블리 레이블을 하나 만들도록 하자.

.global sys_scheduler:
sys_scheduler:
    ldr     sp, =navilnux_current
    ldr     sp,  
 
    add     sp, sp, #4
    stmia   sp!, {r0-r12}^
    stmia   sp!, {sp,lr}^
    stmia   sp!, {lr}
 
    sub     sp, sp, #68
    mrs     r1, spsr
    stmia   sp!, {r1}
 
    ldr     sp, =svc_stack
 
    bl      scheduler
 
    ldr     sp, =navilnux_next
    ldr     sp,  
 
    ldmia   sp!, {r1}
    msr     spsr_cxsf, r1
    ldmia   sp!, {r0-r12}^
    ldmia   sp!, {r13,r14}^
 
    ldmia   sp!, {pc}^

이름은 sys_scheduler 이다. 시스템콜 레이어를 따른다고 하였으므로 네이밍컨벤션도 나빌눅스의 시스템콜 네이밍을 따랐다. 그리고 우리가 구현하면서 정한 규칙대로 시스템콜 추가 절차를 밟도록 하자. syscalltbl.h 에 시스템콜 번호를 추가 하자.

#ifndef _NAVIL_SYS_TBL
#define _NAVIL_SYS_TBL
 
#define SYS_MYSYSCALL   0
#define SYS_MYSYSCALL4  1
 
#define SYS_CALLSCHED	SYS_CALLNUM   
 
#endif

navilnux_syscallvec 의 크기로 지정된 SYS_CALLNUM 을 sys_scheduler 의 시스템콜 번호로 할당 하였다. 즉, 나빌눅스에서 지원하는 시스템콜의 갯수를 증가 시키더라도 sys_scheduler 는 절대로 navilnux_syscallvec 의 범위안에 포함되지 않는다.

원래 우리가 만든 규칙 대로라면 다음으로는 navilnux_sys.c 에 실제 시스템콜의 본체를 구성하는 함수가 구현되어야 하지만 그 대신에 entry.S 에 sys_scheduler 레이블을 구현하였으므로 이를 건너 뛴다. 이어서 navilnux_lib.h 에 시스템콜 랩퍼 함수의 프로토타입을 선언한다.

#ifndef _NAVIL_LIB
#define _NAVIL_LIB
 
extern int mysyscall(int, int, int);
extern int mysyscall4(int, int, int, int);
extern void call_scheduler(void);
 
#endif

인자를 넘길것도 리턴값을 받을 것도 없으므로 모두 void 형으로 선언하였다. 랩퍼함수의 이름은 call_scheduler() 이다. 이어서 랩퍼 함수를 작성한다. 랩퍼함수는 역시 형태를 그대로 유지 하는 아무것도 안하는 함수 이다.

#include <syscalltbl.h>
 
.global mysyscall
mysyscall:
    swi SYS_MYSYSCALL 
    mov pc, lr
 
.global mysyscall4
mysyscall4:
    swi SYS_MYSYSCALL4
    mov pc, lr
 
.global call_scheduler
call_scheduler:
    swi SYS_CALLSCHED
    mov pc, lr

랩퍼함수가 이렇게 되어 있으면, 일단 커널의 흐름은 Core_swiHandler 로 진입하게 된다. Core_swiHandler 는 다른 시스템콜의 리턴값과 매개변수를 처리하기 위하여 r0 를 백업안하는 등의 코드가 작성되어 있다. 하지만 sys_scheduler 는 컨텍스트 스위칭과 스케줄러가 동작해야 하기 때문에 다른 시스템콜 들과는 별도의 흐름을 타야 한다. 그래서 Core_swiHandler 를 아래 코드와 같이 수정하고, entry.S 에 syscalltbl.h 를 include 한다. entry.S 의 전체 코드를 보면 아래와 같다.

#include <syscalltbl.h>
 
.globl _ram_entry
_ram_entry:
    b   kernel_init 
	b	_ram_entry
    b   Core_swiHandler
    b   Core_irqHandler
 
 
#define svc_stack   0xa0300000
#define irq_stack   0xa0380000
#define sys_stack   0xa0400000
 
.global kernel_init
kernel_init:
    msr     cpsr_c,#0xc0|0x13    //SVC mode
    ldr     r0,=svc_stack
    sub     sp,r0,#4
 
    msr     cpsr_c,#0xc0|0x12    //IRQ mode
    ldr     r0,=irq_stack
    sub     sp,r0,#4
 
    msr     cpsr_c,#0xc0|0x1f    //SYSTEM mode
    ldr     r0,=sys_stack
    sub     sp,r0,#4
 
    msr     cpsr_c,#0xc0|0x13
 
    bl      main
    b       _ram_entry
 
.global Core_swiHandler
Core_swiHandler:
    ldr     sp, =svc_stack
 
    stmfd   sp!, {lr}
    stmfd   sp!, {r1-r14}^
    mrs     r10, spsr
    stmfd   sp!, {r10}
 
    ldr     r10, [lr,#-4]
    bic     r10, r10, #0xff000000
    cmp     r10, #SYS_CALLSCHED
    beq     sys_scheduler
 
    mov     r11, #4
    mul     r10, r10, r11
 
    ldr     r11, =navilnux_syscallvec
    add     r11, r11, r10
    ldr     r11, [r11]
    mov     lr, pc
    mov     pc, r11
 
    ldmfd   sp!, {r1}
    msr     spsr_cxsf, r1
    ldmfd   sp!, {r1-r14}^
    ldmfd   sp!, {pc}^
 
 
.global Core_irqHandler
Core_irqHandler:    
    mrs     r13, cpsr
    orr     r13, r13, #0x80
    msr     cpsr, r13
 
    ldr     sp, =navilnux_current
    ldr     sp,  
 
    sub     lr, lr, #4
    add     sp, sp, #4
    stmia   sp!, {r0-r12}^
    stmia   sp!, {sp,lr}^
    stmia   sp!, {lr}
 
    sub     sp, sp, #68
    mrs     r1, spsr
    stmia   sp!, {r1}
 
    ldr     sp, =irq_stack
 
    bl      irqHandler
 
    ldr     sp, =navilnux_next
    ldr     sp,  
 
    ldmia   sp!, {r1}
    msr     spsr_cxsf, r1
    ldmia   sp!, {r0-r12}^
    ldmia   sp!, {r13,r14}^
 
    mrs     r14, cpsr
    and     r14, r14, #0x7f
    msr     cpsr, r14
 
    ldmia   sp!, {pc}^
 
.global sys_scheduler:
sys_scheduler:
    ldr     sp, =navilnux_current
    ldr     sp,  
 
    add     sp, sp, #4
    stmia   sp!, {r0-r12}^
    stmia   sp!, {sp,lr}^
    stmia   sp!, {lr}
 
    sub     sp, sp, #68
    mrs     r1, spsr
    stmia   sp!, {r1}
 
    ldr     sp, =svc_stack
 
    bl      scheduler
 
    ldr     sp, =navilnux_next
    ldr     sp,  

맨 아래에 있는 sys_scheduler 레이블은 위에서 한번 나왔던 것이고, 코드 자체도 여러번 설명 했던 것이므로 이해에 별 다른 어려움이 없을 것이라 사료 된다. 이번 수정된 코드에서 일단 중요하게 봐야 할 것은 맨 위에서 syscalltbl.h 를 include 하였다는 것이고, Core_swiHandler 에서 바뀐 부분이 있다. 바뀐 부분을 다시 자세히 보면,

    ldr     r10, [lr,#-4]
    bic     r10, r10, #0xff000000
    cmp     r10, #SYS_CALLSCHED
    beq     sys_scheduler

위 코드에서 cmp 문과 beq 문 두줄이다. ldr 문과 bic 문을 거치면서 r10 에는 시스템콜 번호가 들어가게 된다. 이 시스템콜 번호를 SYS_CALLSCHED 와 비교해서 같은 값이라면, 즉 위 코드대로라면 255 라면 별도로 밑에 만들어 놓은 sys_scheduler 레이블로 점프 하라는 것이다. sys_scheduler 로 들어가게 되면 기존에 스택에 백업했던 내용은들은 전부 무시되고 다시 navilnux_current 로 부터 값을 받아서 메모리에 컨택스트를 백업하고, scheduler 를 호출해서 navilnux_next 를 받은 다음, 컨택스트를 복구해서 이어받은 테스크로 실행 흐름이 넘어가게 된다. 결론적으로 OS timer 에 의해서가 아닌 사용자 테스크의 요청에 의해서 컨택스트 스위칭이 발생하게 되는 것이다.

일단 여기까지를 테스트 해 보도록 하자. navilnux_user.c 를 아래와 같이 수정한다.

#include <navilnux.h>
 
extern Navil_task_mng taskmng;
 
void user_task_1(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK1 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        printf("before call Scheduler\n");
        call_scheduler();
        printf("after call Scheduler\n");
        msleep(1000);
    }
}
 
void user_task_2(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK2 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        c = mysyscall4(4,5,6,7);
        printf("Syscall4 return value is %d\n", c);
        msleep(1000);
    }
}
 
void user_task_3(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK3 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        c = mysyscall(1,2,3);
        printf("Syscall return value is %d\n", c);
        msleep(1000);
    }
}
 
void navilnux_user(void)
{
    taskmng.create(user_task_1);
    taskmng.create(user_task_2);
    taskmng.create(user_task_3);
}

다른 곳은 변한곳이 없고, user_task_1() 함수에서

        printf("before call Scheduler\n");
        call_scheduler();
        printf("after call Scheduler\n");

이 세 줄이 추가 되었다. 실행결과를 예측해 본다면 먼저 TASK1 에 대한 내부 변수의 주소 값이 출력된다음, "before call Scheduler" 메시지가 출력될 것이다. 그리고 사용자 테스크에서 스케줄러 호출을 요청했으므로 바로 이어서 TASK2 에 대한 출력이 나오고, TASK2 에 대한 출력이 끝나고 나면 TASK3 에 대한 출력이 나올 것이다. 그리고 다시 TASK1 이 스케줄링을 받는 다면 Software Interrupt 를 빠져 나오는 부분부터 받을 것이므로 Software Interrupt 를 빠져 나와서 "after call Scheduler" 를 출력하고 1초를 sleep 하므로 다시 TASK2 로 흐름이 넘어갈 것이다.

추가된 파일은 없으므로 가벼운 마음으로 make 를 쳐서 빌드를 시킨 다음 빌드된 이미지를 이지보드에 다운로드 한다음 부팅 해 보자. 결과값은 아래와 같다.

TCB : TASK1 - init PC(a000bdd4)          init SP(a04ffffc)                      
TCB : TASK2 - init PC(a000be4c)          init SP(a05ffffc)                      
TCB : TASK3 - init PC(a000bed4)          init SP(a06ffffc)                      
REAL func TASK1 : a000bdd4                                                      
REAL func TASK2 : a000be4c                                                      
REAL func TASK3 : a000bed4                                                      
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
before call Scheduler                                                           
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
My Systemcall4 - 4 , 5 , 6 , 7                                                  
Syscall4 return value is 3413                                                   
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
My Systemcall - 1 , 2 , 3                                                       
Syscall return value is 333                                                     
after call Scheduler                                                            
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
My Systemcall4 - 4 , 5 , 6 , 7                                                  
Syscall4 return value is 3413                                                   
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
My Systemcall - 1 , 2 , 3                                                       
Syscall return value is 333                                                     
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
before call Scheduler                                                           
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
My Systemcall4 - 4 , 5 , 6 , 7                                                  
Syscall4 return value is 3413                                                   
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
My Systemcall - 1 , 2 , 3                                                       
Syscall return value is 333                                                     
after call Scheduler                                                            
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
My Systemcall4 - 4 , 5 , 6 , 7                                                  
Syscall4 return value is 3413                                                   
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0                              
My Systemcall - 1 , 2 , 3                                                       
Syscall return value is 333              

예상과 정확하게 맞아 떨어지는 결과 값이 나왔다. 유심히 봐야 할 부분은 TASK3 에 해당하는 호출내용이 다 끝난 다음에 한번은 after call Scheduler 메시지가 나와야 하고 한번은 안나오고 그다음번엔 나오고, 다음엔 안나오고 이런식으로 한번씩 건너 뛰어 가면서 after call Scheduler 메시지가 출력되어야 한다는 것이다.

일단 이렇게 해서 사용자요청에 의한 임의로의 컨텍스트 스위칭을 구현 했다. 앞으로 시스템 블럭킹등을 구현할때 유용하게 사용 할 것이다. 그리고 이번 회차에서 구현하는 ITC 에서 블렁킹을 구현 할 때에도 사용 할 것이다. 그래서 이렇게 강좌 초반에 장황하게 설명을 하는 것이다.

사실 정작 ITC 구현 자체는 별 내용이 없다. 일단 ITC 는 일종의 커널 메시지 서비스의 일종이므로 navilnux_msg.h 파일을 새로 만들고 거기에 관련 자료구조를 설계해 보자.

#ifndef _NAVIL_MSG
#define _NAVIL_MSG
 
#define MAXMSG 255
 
typedef struct _navil_free_msg {
    int data;
    int flag;
} Navil_free_msg;
 
typedef struct _navil_msg_mng {
    Navil_free_msg free_msg_pool[MAXMSG];
 
    void (*init)(void);
    int (*itc_send)(int, int);
    int (*itc_get)(int);
} Navil_msg_mng;
 
void msg_init(void);
int msg_itc_send(int, int);
int msg_itc_get(int);
 
 
#endif

기본 골격은 테스크관리자나 메모리관리자와 크게 다르지 않다. 자유메시지 블럭은 데이터를 전달하는 것이 주 목적인 만큼 데이터가 들어갈 자리와 해당 블럭이 사용중인지를 표시하는 플래그가 있다. 지금은 그냥 int 형 변수 하나만 데이터로 사용하였는데 메시지큐로 사용하던지 하면 몇개의 변수가 더 추가되고 제어함수를 조금 수정하면 될것이다. 그 부분은 독자의 몫으로 남기겠다. 그럼 이제 부터 이 자료구조는 메시지관리자 라고 부르겠다.

물 흐르는 대로, 바람 부는 대로, 손가락 움직이는 대로 개발 해 오다 보니 벌써 테스크관리자, 메모리관리자, 메시지관리자 까지 관리자가 벌써 셋이나 생겼다. 이렇게 조금씩 조금씩 보태어지고 버려지면서 깔끔하게 기능이 늘어가는 프로그램을 보고 있으면 마치 성장하는 생명체 같다는 느낌이 들 때가 있다. 물론 대부분의 경우 나중에 가서는 수습곤란 처치곤란한 괴물을 만들어 버리고 마는 경우가 많긴 하지만, 나빌눅스는 제발 말쑥하게 잘 빠진 모습으로 완성되길 바란다.

이어서 메시지 관리자에 종속되는 세개의 함수에 대한 프로토타입이 선언되어 있다. 메시지관리자 자체를 초기화 작업해주는 msg_init() 와 이번 강좌에서 작성하는 ITC 관련한 두개의 함수 msg_itc_send() 와 msg_itc_get() 함수 이다. 이름에서 보면 알 수 있듯이 msg_itc_send() 함수는 사용자 테스크로부터 커널로 데이터를 보내는 함수이고, msg_itc_get() 은 커널에 있는 메시지를 받아오는 함수 이다.

msg_init() 함수는 navilnux_init() 에서 호출될 것이고, msg_itc_send() 와 msg_itc_get() 은 시스템콜 레이어를 거쳐서 구현될 것이다. ITC 역시 커널의 자원을 사용하는 것이기 때문에 시스템콜계층을 타고 구현되는 것이 이치에 맞다. 하지만 실제 코드 본체는 메시지관리자 소스파일에 존재 할 것이고 시스템콜레이어는 말 그대로 계층구분의 역할만 하게 될 것이다. 일단 ITC를 동작 하는 함수 본체를 작성하자. 파일명은 navilnux_msg.c 이다.

#include <navilnux.h>
 
Navil_msg_mng msgmng;
 
int msg_itc_send(int itcnum, int data)
{
    if(itcnum >= MAXMSG || itcnum < 0){
        return -1;
    }
    msgmng.free_msg_pool[itcnum].data = data;
    msgmng.free_msg_pool[itcnum].flag = 1;
 
    return itcnum;
}
 
int msg_itc_get(int itcnum, int *data)
{
    if(itcnum >= MAXMSG || itcnum < 0){
        return -1;
    }
 
    if(msgmng.free_msg_pool[itcnum].flag == 0){
        return -2;
    }
 
    *data = msgmng.free_msg_pool[itcnum].data;
    msgmng.free_msg_pool[itcnum].flag = 0;
    msgmng.free_msg_pool[itcnum].data = 0;
 
    return 0;
}
 
void msg_init(void)
{
    int i;
    for (i = 0 ; i < MAXMSG ; i++){
        msgmng.free_msg_pool[i].data = 0;
        msgmng.free_msg_pool[i].flag = 0;
    }
 
    msgmng.init = msg_init;
    msgmng.itc_send = msg_itc_send;
    msgmng.itc_get = msg_itc_get;
}

msg_itc_send()함수는 매우 단순하다. c언어 문법만 알면 누구라도 이해 하는 수준의 코드이다. 나빌눅스는 시종일관 이런식의 코드로만 구성될 것이다. 쉽지않은가? OS만들기는 참 쉽다.:) 첫번째 인자로 사용자 테스크가 사용할 itc 번호를 받는데 이 번호가 그대로 자유메시지블럭의 배열인덱스로 들어가므로 그 수가 음수이거나 MAXMSG 이상이면 안된다. 그래서 함수의 시작부분에서 경계값 체크를 하고 에러면 음수를 리턴한다. 경계값 안에들어간다면 아주 단순하게 해당 자유메시지 블럭의 data 필드에 인자로 넘어온 값을 쓰고, 플래그를 1로 셋 한다.

msg_itc_get() 함수는 함수의 인자로 int 의 포인터 변수를 받는다. call by refference 로 자유메시지블럭에 들어 있는 data의 값을 사용자테스크로 전달 한다. 리턴값으로 ITC 데이터를 전달 할 수도 있지만, 사용자가 전달하기를 원하는 값이 에러코드(-1,-2) 이거나 정상종료(0) 의 값일 경우에는 이를 구분 할 수 없기 때문에 포인터를 이용해서 값을 전달하게끔 하였다. 사용자가 데이터를 가져오기를 원하는 자유메시지 블럭에 아직 값이 없다면, -2를 리턴한다.

msg_init() 함수는 굳이 설명하는게 민망할 정도로 단순하다. MAXMSG 만큼 루프를 돌면서 자유메시지블럭을 모조리 0으로 초기화 한다. 그리고 위에서 작성한 함수시작 주소를 메시지관리자의 함수포인터에 연결한다.

그리고 navilnux_init() 함수를 수정한다.

void navilnux_init(void)
{
    mem_init();
    task_init();
    msg_init();
    syscall_init();
 
    os_timer_init();
    gpio0_init();
 
    os_timer_start();
}

그냥 단순하게 msg_init() 를 추가하는 작업만 한다.

이번 강좌에서 타이틀로 달았던 ITC 의 실질적인 구현은 여기서 끝이다. 이제 부터 할일은 이렇게 구현한 ITC 를 시스템콜 레이어에 얹어 놓는 것이다. 역시 순서대로 syscalltbl.h 에 두개의 시스템콜에 해당하는 시스템콜 번호를 부여 한다.

#ifndef _NAVIL_SYS_TBL
#define _NAVIL_SYS_TBL
 
#define SYS_MYSYSCALL   0
#define SYS_MYSYSCALL4  1
#define SYS_ITCSEND     2
#define SYS_ITCGET      3
 
#define SYS_CALLSCHED   255
 
#endif

itc_send() 와 itc_get() 각각 2 번과 3번 시스템콜 번호를 할당했다. 이어서 navilnux_sys.h 에 시스템콜 본체 함수에 대한 프로토타입을 선언한다.

#ifndef _NAVIL_SYS
#define _NAVIL_SYS
 
#include <syscalltbl.h>
 
#define SYSCALLNUM 255
 
void syscall_init(void);
 
int sys_mysyscall(int, int, int);
int sys_mysyscall4(int, int, int, int);
 
int sys_itcsend(int, int);
int sys_itcget(int, int*);
 
extern void sys_scheduler(void);
 
#endif

이 시스템콜 함수가 하는 일은 메시지관리자에서 작성한 함수를 그대로 호출해 주는 것 뿐이기 때문에 프로토 타입의 반환값이나 매개변수 형식역시 메시지관리자에 정의한 그대로 작성한다. 다음은 navilnux_sys.c 에 본체를 작성한다. 그리고 syscall_init() 함수에서 navilnux_syscallvec 전역변수에 함수포인터의 주소를 등록해준다.

#include <navilnux.h>
 
extern Navil_msg_mng msgmng;
 
unsigned int navilnux_syscallvec[SYSCALLNUM];
 
int sys_mysyscall(int a, int b, int c)
{
    printf("My Systemcall - %d , %d , %d\n", a, b, c);
    return 333;
}
 
int sys_mysyscall4(int a, int b, int c, int d)
{
    printf("My Systemcall4 - %d , %d , %d , %d\n", a, b, c, d);
    return 3413;
}
 
int sys_itcsend(int itcnum, int data)
{
    return msgmng.itc_send(itcnum, data);
}
 
int sys_itcget(int itcnum, int *data)
{
    return msgmng.itc_get(itcnum, data);
}
 
 
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_lib.h 에 함수 프로토타입을 선언해 준다.

#ifndef _NAVIL_LIB
#define _NAVIL_LIB
 
extern int mysyscall(int, int, int);
extern int mysyscall4(int, int, int, int);
 
extern int itc_send(int, int);
extern int itc_get(int, int*);
 
extern void call_scheduler(void);
 
#endif

이어서 아무거도 안하는 Software Interrupt 호출자를 작성해준다.

#include <syscalltbl.h>
 
.global mysyscall
mysyscall:
    swi SYS_MYSYSCALL 
    mov pc, lr
 
.global mysyscall4
mysyscall4:
    swi SYS_MYSYSCALL4
    mov pc, lr
 
.global itc_send
itc_send:
    swi SYS_ITCSEND
    mov pc, lr
 
.global itc_get
itc_get:
    swi SYS_ITCGET
    mov pc, lr
 
 
.global call_scheduler
call_scheduler:
    swi SYS_CALLSCHED
    mov pc, lr

여기 까지 했으면 이제 사용자 테스크에서 itc_send() 와 itc_get() 을 이용해서 데이터를 전달 해보고 출력결과를 보고 이번 강좌 끝. 해야 겠지만 하직 한가지 더 해야 할 일이 있다. 무엇이냐면 시스템콜이 blocking 걸리게 만들어 주어야 한다. ITC 를 구현하기로 하고 어떤식으로 구현할지를 생각 할때, itc_get() 에서는 커널 자유메모리블럭에 데이터가 없다면 데이터가 들어올때까지 블럭걸리게 구현 한다고 하였다. 그리고 그것을 구현하기 위해서 사용자영역에서 강제로 컨택스트 스위칭을 해주는 call_scheduler() 시스템콜을 구현 했다. 중요한것은 바로 이것이다. 사용자영역에서 컨택스트스위칭을 한다는 것이다. 그러므로 blocking 을 해주는 코드도 사용자영역에 존재 해야 한다. 즉, Software Interrupt 를 발생시키는 navilnux_lib.S 안에 있는 함수들을 한번 더 감싸주는 계층이 필요하다. 하지만 레이어 위계상 navilnux_lib.S 보다 상위레이어라고 볼 순없다. 동격이라고 보는것이 맞을 것이다. 그리고 c 로 구현 할 것이다. 그래서 새로 만들어지는 파일 이름은 navilnux_clib.c 로 한다. 헤더파일은 navilnux_lib.h 를 같이 사용한다.

navilnux_clib.c 에 새로 추가될 계층의 함수를 작성 한다.

#include <navilnux.h>
 
int navilnux_itc_send(int itcnum, int data)
{
    return itc_send(itcnum, data);
}
 
int navilnux_itc_get(int itcnum)
{
    int ret_value = 0;
    int data = 0;
    while(1){
        ret_value = itc_get(itcnum, &data);
        if(ret_value == 0){
            return data;
        }else if(ret_value == -1){
            return ret_value;
        }else{
            call_scheduler();
        }
    }
}

함수이름은 앞에 navilnux 를 붙여서 navilnux_itc_send(), navilnux_itc_get() 으로 하였다. 네이밍 컨벤션상 위 함수들이 최종적으로 사용자테스크 영역에 서비스되는 함수가 될 것이다. navilnux_itc_send() 함수는 별로 볼게 없다. 그냥 똑같다.

navilnux_get() 함수는 좀 봐야 한다. 우선 매개변수가 하나 뿐이다. 에러 처리를 랩퍼함수 내부에서 하고, 포인터로 전달되어 오는 자유메시지블럭안의 값은 리턴값으로 넘기기 때문이다. 그리고 자유메시지 블럭이 비어있을때에는 시스템 콜이 0을 리턴하는데, 0이 리턴되었을 때에는 call_scheduler() 를 호출해서 강제로 스케줄링을 다음 테스크로 넘긴다. 이와 같은 과정이 계속 반복되면 사용자 테스크 입장에서는 navilnux_itc_get() 함수에서 진행이 멈춘것 처럼 보인다. 즉, blocking 걸리는 것이다.

새롭게 추가한 두 함수는 navilnux_lib.h 에 프로토타입이 선언 된다.

#ifndef _NAVIL_LIB
#define _NAVIL_LIB
 
extern int mysyscall(int, int, int);
extern int mysyscall4(int, int, int, int);
 
extern int itc_send(int, int);
extern int itc_get(int, int*);
 
extern void call_scheduler(void);
 
 
int navilnux_itc_send(int, int);
int navilnux_itc_get(int);
 
#endif

이제 사용자 테스크 함수에서 navilnux_itc_get() 과 navilnux_itc_send() 함수를 사용해서 실제로 ITC 가 제대로 동작하는지를 확인 해 보자.

#include <navilnux.h>
 
extern Navil_task_mng taskmng;
 
void user_task_1(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK1 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        msleep(1000);
    }
}
 
void user_task_2(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK2 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
 
        printf("ITC Count is %d\n", a);
        if (a == 3){
            navilnux_itc_send(2, 342);
            a = 1;
            printf("ITC send!!!\n");
        }
        a++;
 
        msleep(1000);
    }
}
 
void user_task_3(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        c = navilnux_itc_get(2);
        printf("TASK3 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        printf("ITC get!!!! ---> %d\n", c);
 
        msleep(1000);
    }
}
 
void navilnux_user(void)
{
    taskmng.create(user_task_1);
    taskmng.create(user_task_2);
    taskmng.create(user_task_3);
}

TASK1 은 그대로 카운트를 세기 위해 아무것도 안하고, 1초에 한번씩 메시지를 출력하게 둔다. 그리고 TASK2 는 3초에 한번씩 ITC 2번에다 342 라는 값을 전달 한다. TASK3 은 스케줄링을 정상대로 돌되 루프의 시작위치에 navilnux_itc_get() 을 한다.

그렇다면 현 시점에서 출력값을 예측 해 보겠다. 먼저 TASK1 메시지가 출력될것이고 이어서 TASK2 메시지가 출력될 것이다. 그리고 ITC count 가 출력되고 TASK3 은 메시지를 출력하기 전에 navilnux_itc_get()에 의해 blocking 되기 때문에 TASK2의 a 값이 3이 되기 전에는 TASK3의 메시지가 출력되지 않을 것이다. 즉, TASK1 은 1초에 한번씩 계속 출력 되고, TASK2 는 카운트를 1, 2, 3 출력하고 3 이 출력될 때에는 ITC send!! 메시지가 출력된다. 그리고 그제서야 TASK3 메시지가 출력되고 전달 받은 값이 출력 될 것이다.

일단 파일이 추가 되었으니 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
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
 
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
	$(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
	$(OC) $(OCFLAGS) navilnux_elf navilnux_img
 
clean:
	rm *.o
	rm navilnux_elf
	rm navilnux_img

가벼운 마음으로 make 를 쳐서 빌드 하고, 이미지를 이지보드에 다운로드 해서 부팅 해 보자. 출력값은 아래와 같다.

TCB : TASK1 - init PC(a000c120)          init SP(a04ffffc)                      
TCB : TASK2 - init PC(a000c17c)          init SP(a05ffffc)                      
TCB : TASK3 - init PC(a000c224)          init SP(a06ffffc)                      
REAL func TASK1 : a000c120                                                      
REAL func TASK2 : a000c17c                                                      
REAL func TASK3 : a000c224                                                      
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 1                                                                  
TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
ITC Count is 2                                                                  
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                                                                  
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 은 순서대로 스케줄링을 잘 받아 메시지를 출력한다. TASK2 역시 순서대로 스케줄링을 받으면서 화면에 카운트를 출력한다. 카운트가 3이 되면서 ITC send 를 출력하면서 navilnux_itc_send() 를 수행한다. 그러면 커널 전역변수를 거쳐서 navilnux_itc_get() 이 blocking 에서 풀린다. TASK3 메시지가 출력되고, ITC를 통해 전달받은 342가 제대로 화면에 출력된다. 의도한 대로 ITC 가 잘 동작 하는 것을 확인 하였다.

이번회는 코드 자체보다는 나빌눅스의 여러 정책에 대한 설명 때문에 강좌가 길어 진 듯 하다. 정작 내용은 별로 어렵지 않으니 다소 이해가 안가 더라도 다시 한번 천천히 읽어 주시기 바란다.

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

File attachments: 
첨부파일 크기
파일 navilnux_chap15.tgz106.68 KB

댓글

paeksj98의 이미지

감사합니다. 잘보고 있습니다.

verimuch의 이미지

나빌레라님~ 글 항상 잘보고 있습니다.
여기 나오는 어셈블리어가 gnu as가 맞습니까?
혹시 gnu as에 대한 문법 자료 설명이 있는지 궁금합니다;;

차려놓은 밥상만 먹으려는게 아니라
종일 검색을 해도 못찾겠네요...

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

나빌레라의 이미지

gnu as 인지는 잘 모르겠습니다.

x86 에 대해서만 gnu as 와 nasm, masm 등이 문법이 다른걸로 알고 있는데...

저기 나오는 어셈블리어는 어찌되었던 gcc 를 이용해 컴파일이 가능하고

문법 자체는 ARM system Developer's Guide 에 나오는 문법과 동일 합니다.

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

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

verimuch의 이미지

...

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

crazyits의 이미지

어셈도 모르고 내공도 부족하여 잘은 모르지만

항상 좋은내용이라 잘 보고 있습니다.

JuEUS-U의 이미지

항상 잘 보고 있습니다 ^^
근데 요즘 갑자기 진도가 빨라진 것 같네요 ㅎㅎ;

나빌레라의 이미지

^^;

원고를 다 썼거든요...;;

이제 올리기만 하면 되어서 이틀이나 삼일에 하나꼴로 올려 20회에 강좌 마무리 하려 합니다.
----
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

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

verimuch의 이미지

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