/usr/src/linux-2.4.20-8/kernel/sched.c
/*
* context_switch – switch to the new MM and the new
* thread’s 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에 의해 선택되어 다음 번에 실행 될 process의 memory 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 제어 명령어를 사용할 수 없다는 플래그를 설정한다.
 
댓글 없음:
댓글 쓰기