2008년 3월 28일 금요일

리눅스 커널 2.4 - scheduling 과정 중 context switching



/usr/src/linux-2.4.20-8/kernel/sched.c

 

/*

* context_switch switch to the new MM and the new

* threads register state.

*/

static inline task_t * context_switch(runqueue_t *rq, task_t *prev, task_t *next)

/*

* rq(runqueue_t)

* prev(task_t): 현재 실행되고 있는 process

* next(task_t): scheduling받아 이제 실행 될 process

*/

{

           struct mm_struct *mm = next->mm;

           struct mm_struct *oldmm = prev->active_mm;

 

           if(unlikely(!mm)) //이전 process(prev)와 주소공간 공유

           {

                     next->active_mm = oldmm;

 //이전 process(prev) active_mm을 갖는다

                     atomic_inc(&oldmm->mm_count);

//oldmm reference count 증가

                     enter_lazy_tlb(oldmm, next, smp_processor_id()); //TLB설정

           }

           else //다른 주소공간을 가진 process일 경우

                     switch_mm(oldmm, mm, next);

//switch_mm을 호출하여 주소공간switch

          

if(unlikely(!prev->mm)) //현재process(prev) mm포인터가 null이라면

           {

                     prev->active_mm = NULL;

//prev active_mm null로 만들어준다.

                     rq->prev_mm = oldmm;

           }

 

           /* Here we just switch the register state and the stack. */

           switch_to(prev, next, prev); // CPU dependent한 매크로

 

           return prev;

}

 

   이 함수는 schedule() 함수에서 호출된다. scheduler가 다음 수행 될 process를 고르고 나서 호출하는 함수가 바로 이 context_switch함수이다. 현재 수행되고 있는 process의 정보와 다음 수행 될 process의 정보를 argument로 넘겨받는 context_switch함수는 가장먼저 process를 메모리 상에 올리는 일을 수행 한다.  scheduling에 의해 선택되어 다음 번에 실행 될 processmemory management(task_struct안의 mm_struct)  NULL일 경우에는 수행되던 process의 주소공간을 공유하는 것이므로 이전의 process가 가진 mm_struct를 가지게 되고 mm_strcut의 구조체 포인터를 하나 증가시켜준다. NULL이 아닐 경우에는 수행 되던 process와 주소공간이 다른 것이므로 이 process가 가지고 있던 switch_mm이라는 함수를 호출한다. 여기서 switch_mm이 하는 일은 process의 주소 공간을 교체하는 것이다. 만약 수행중인 process(prev) mm NULL이라면 다른 누군가와 주소공간을 공유하고 있다는 뜻이다. 주소공간을 공유하더라도 이제 실행되지 않으면 active_mm NULL값을 주어 실행되지 않는 process임을 나타낸다. 그리고 runqueue prev_mm에 수행되던 process active_mm을 저장해 두었던 oldmm값을 넣는다. 이러한 주소공간 switching이 모두 일어난 후에 switch_to라는 매크로를 호출하여 레지스터의 저장과 저장되어있던 레지스터를 불러오는 일을 한다.

 

/usr/src/linux-2.4.20-8/include/asm-i386/system.h

 

#define switch_to(prev, next, last) do{

           asm volatile(“pushl %%esi\n\t”

“pushl %%edi\n\t”

“pushl %%ebp\n\t”  //레지스터 값 저장

“movl %%esp,%0\n\t”       /* save ESP */

                                “movl %2,%%esp\n\t”    /* restore ESP */

                                “movl $1f,%1\n\t”           /* save EIP */

                                “pushl %3\n\t”                /* restore EIP */

                                “jmp __switch_to\n”          //__switch_to로 점프

                                “1:\t”

                                “popl %%ebp\n\t”

                              “popl %%edi\n\t”

                                “popl %%esi\n\t”  //레지스터 값 로딩

                                :”=m” (prev->thread.esp), “=m” (prev->thread.eip)//출력

                                :”m” (next->thread.exp),”m”(next->thread.eip),                                                “a”(prev), “d” (next)); //입력

}while(0)

 

 이 부분은 CPU dependent한 부분이므로 CPU에 따라 달라질 수 있다. 이 매크로가 하는 일은 현재 실행중인 프로세서의 레지스터들을 저장하고 스케줄링 되어 실행 될 프로세서의 레지스터들을 가지고 오는 것이다. 먼저 실행중인 프로세서의 레지스터들을 stack에 저장하는데 그것이 esi, ebp, esp이다. thread_struct라는 struct 0번째는 esp0가 있는데 이곳에 esp를 저장하고 2번째에 있는 esp값을 esp에 가져다 놓는다. 그리고 1번째에는 eip가 있는데 이 곳에 1f를 저장하여 다음번에 1f라는 곳에서부터 수행할 것을 알려준다. 3번째 값은 fs인데 이곳에 eip를 저장한다. 그리고 나서 __switch_to로 점프뛰는데 이 함수가 실행되고 난 후에는 바뀐 프로세서가 수행될 것이다. context switch가 완전히 다 일어나고 난 후에는 1: 이라고 되있는 leble부터 스케줄링 된 프로세스가 수행된다. 그래서 ebp, edi, esi가 차례대로 복구된다. m은 메모리를 의미하는데 “=”이 출력을, a 또는 d eax, edx레지스터를 뜻한다. 그래서 밑의 3줄 소스를 보면 prev(현재 실행중인 프로세스) eax레지스터에 저장되고 next(스케줄링 받은 프로세스) edx에 저장이 된다.

 

/usr/src/linux-2.4.20-8/arch/i386/kernel/process.c

 

void __switch_to(struct task_struct *prev_p, struct task_struct *next_p)

{

         struct thread_struct *prev = &prev_p->thread,

*next = &next_p->thread;

           int cpu = smp_processor_id();

           struct tss_struct *tss = init_tss + cpu;

           //현재 실행중인 프로세스 prev FPU상태를 저장

 

           /* never put a printk in __switch_to printk() calls wake_up*() indire ctly*/

 

           unlazy_fpu(prev_p);

 

           /* Reload esp0, LDT and the page table pointer: */

           tss->esp0 = next0->esp0;

           //스케줄링 받은 프로세스next esp0(커널 스택 주소)를 저장

 

           /* Load the per-thread Thread-Local Storage descriptor. */

           load_TLS(next, cpu);

 

           /* Save away %fs and %gs. No need to save %es and %ds, as

         those are always kernel segments while inside the kernel. */

           asm volatile(“movl %%fs, %0”: “=m” (*(int *)&prev->fs));

           asm volatile(“movl %%gs, %0”: “=m” (*(int *)&prev->gs));

           //prev 프로세스의 fs gs레지스터 저장

 

           /* Restore %fs and %gs if needed. */

           if(unlikely(prev->fs | prev->gs | next->fs | next->gs)){

                     loadsegment(fs, next->fs);

                     loadsegment(gs, next->gs);

           } //next 프로세스의 fs gs 레지스터 저장

 

           /* Now maybe reload the debug registers */

           if(unlikely(next->debugreg[7])) {

                     loaddebug(next, 0);

                     loaddebug(next, 1);

                     loaddebug(next, 2);

                     loaddebug(next, 3);

                     /* no 4 and 5 */

                     loaddebug(next, 6);

                     loaddebug(next, 7);

           } //디버그 레지스터 로딩

 

           if (unlikely(prev->ioperm || next->ioperm))

           //IOperm시스템 콜일 경우에만 해당.

{

                     if(next->ioperm) {

                                memcpy(tss->io_bitmap, next->io_bitmap,

IO_BITMAP_SIZE*sizeof(unsigned long));

                                tss->bitmap = IO_BITMAP_OFFSET;

                     } else

                                tss->bitmap = INVALID_IO_BITMAP_OFFSET;

           }

}

 

   이 함수가 처음 하는 일은 현재 실행중인 프로세스의 상태를 저장하는 것이다. 그리고 스케줄링 받은 프로세서의 커널 스택 주소를 저장하는데 이 주소는 tss_struct라는 구조체의 esp0필드에 저장되어야 한다. 이 곳에서 CPU가 사용하는 커널 스택 주소가 결정되기 때문이다. 그 후에는 현재 수행중인 프로세스의 fs, gs 레지스터를 저장한다. fs gs는 데이터 이동시 사용자 세그먼트와 커널 세그먼트를 전환하는데 사용한다. 현재 수행중인 프로세스의 레지스터를 저장한 후에 할일은 당연히 다음 수행 될 프로세스의 레지스터를 로딩하는 것이다. 그리하여 fs, gs 레지스터를 로딩시키고 마지막으로 디버그 레지스터들을 로딩시킨다. 만약 프로세스가 IOperm시스템콜을 호출하였다면 그 프로세스가 I/O bitmap을 가지고 있는지 아닌지를 검사한다. 다음 수행될 프로세스가 I/O bitmap을 가지고 있다면 tss unsigned long크기만큼의 byte를 복사하고 그렇지 않다면 port 제어 명령어를 사용할 수 없다는 플래그를 설정한다.

댓글 없음:

댓글 쓰기