ウォンツテック

そでやまのーと

Linuxのpriviledge levelを切り替える辺りのコードを見てみる。

Linux 2.6.18

fs/binfmt_elf.c 1017行目あたり
    start_thread(regs, elf_entry, bprm->p);                                     
    if (unlikely(current->ptrace & PT_PTRACED)) {                               
        if (current->ptrace & PT_TRACE_EXEC)                                    
            ptrace_notify ((PTRACE_EVENT_EXEC << 8) | SIGTRAP);                 
        else                                                                    
            send_sig(SIGTRAP, current, 0);                                      
    }                                                                           
    retval = 0;  
include/asm-i386/processor.h
#define start_thread(regs, new_eip, new_esp) do {       \                       
    __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));   \                       
    set_fs(USER_DS);                    \                                       
    regs->xds = __USER_DS;                  \                                   
    regs->xes = __USER_DS;                  \                                   
    regs->xss = __USER_DS;                  \                                   
    regs->xcs = __USER_CS;                  \                                   
    regs->eip = new_eip;                    \                                   
    regs->esp = new_esp;                    \                                   
} while (0) 

これはexecveシステムコール時にelf形式の実行ファイルを読み込むときに呼ばれる関数で、ここでstart_threadにより各セグメントレジスタにユーザプロセス用のレジスタ(__USER_CS = 0x23, __USER_DS = 0x2B)をセットしており、以後このプロセスはこのセグメント(Priviledge Level)で動作する。

以下のコードは実際にスケジューリングされている各プロセスが割り込みハンドラによりスケジューラが呼ばれ、そのスケジューラ処理が終了した時のコードで、iretする前にユーザプロセス用のcs, eip, eflags, ds, ssなどがセットされ割り込みハンドラを呼出す側には戻らずにスケジューリングされたプロセスの(cs, eip)に戻るように加工されている。

arch/i386/kernel/entry.S
ENTRY(interrupt)                                                                
.text                                                                           
                                                                                
vector=0                                                                        
ENTRY(irq_entries_start)                                                        
    RING0_INT_FRAME                                                             
.rept NR_IRQS                                                                   
    ALIGN                                                                       
 .if vector                                                                     
    CFI_ADJUST_CFA_OFFSET -4                                                    
 .endif                                                                         
1:  pushl $~(vector)                                                            
    CFI_ADJUST_CFA_OFFSET 4                                                     
    jmp common_interrupt                                                        
.data                                                                           
    .long 1b                                                                    
.text                                                                           
vector=vector+1                                                                 
.endr                                                                           
                                                                                
/*                                                                              
 * the CPU automatically disables interrupts when executing an IRQ vector,      
 * so IRQ-flags tracing has to follow that:                                     
 */                                                                             
    ALIGN                                                                       
common_interrupt:                                                               
    SAVE_ALL                                                                    
    TRACE_IRQS_OFF                                                              
    movl %esp,%eax                                                              
    call do_IRQ                                                                 
    jmp ret_from_intr                                                           
    CFI_ENDPROC     

do_IRQで各割り込みハンドラを呼び出している。(この中にはスケジュール処理やシステムコールによる処理も含まれる)
そのハンドラ処理が終わるとret_from_intrへjmp。

/*                                                                              
 * Return to user mode is not as complex as all this looks,                     
 * but we want the default path for a system call return to                     
 * go as quickly as possible which is why some of this is                       
 * less clear than it otherwise should be.                                      
 */                                                                             
                                                                                
    # userspace resumption stub bypassing syscall exit tracing                  
    ALIGN                                                                       
    RING0_PTREGS_FRAME                                                          
ret_from_exception:                                                             
    preempt_stop                                                                
ret_from_intr:                                                                  
    GET_THREAD_INFO(%ebp)                                                       
check_userspace:                                                                
    movl EFLAGS(%esp), %eax     # mix EFLAGS and CS                             
    movb CS(%esp), %al                                                          
    testl $(VM_MASK | 3), %eax                                                  
    jz resume_kernel                                                            
ENTRY(resume_userspace)                                                         
    cli             # make sure we don't miss an interrupt                      
                    # setting need_resched or sigpending                        
                    # between sampling and the iret                             
    movl TI_flags(%ebp), %ecx                                                   
    andl $_TIF_WORK_MASK, %ecx  # is there any work to be done on               
                    # int/exception return?                                     
    jne work_pending                                                            
    jmp restore_all 
restore_all:                                                                    
    movl EFLAGS(%esp), %eax     # mix EFLAGS, SS and CS                         
    # Warning: OLDSS(%esp) contains the wrong/random values if we               
    # are returning to the kernel.                                              
    # See comments in process.c:copy_thread() for details.                      
    movb OLDSS(%esp), %ah                                                       
    movb CS(%esp), %al                                                          
    andl $(VM_MASK | (4 << 8) | 3), %eax                                        
    cmpl $((4 << 8) | 3), %eax                                                  
    CFI_REMEMBER_STATE                                                          
    je ldt_ss           # returning to user-space with LDT SS                   
restore_nocheck:                                                                
    TRACE_IRQS_IRET                                                             
restore_nocheck_notrace:                                                        
    RESTORE_REGS                                                                
    addl $4, %esp                                                               
    CFI_ADJUST_CFA_OFFSET -4                                                    
1:  iret

この時点でスタックの内容は書き換わっており、その値をチェックしているだけのようです。
最後にiretをし、仕込まれたss, esp, cs, eip, eflagsがセットされ、指定したプロセスのeip部分から実行が再開される。
CPLが変化するiret時はcs, eip, eflagsだけでなく、ss, espも復帰される。(CPUが自動で行う)


自作OS界隈ですと、Mona OSは大体これと同じ処理をしており、AntosはTSSを使っているようでした。
sodexでは、プロセスの切り替えに現時点ではCPLの変化が発生しないのでjmpのみで行ってます。csを変える方法にはセグメント間ジャンプ、割り込み復帰(iret)、コールゲートを使う方法があり、最初はコールゲートを使ってみようかと思いましたが、コールゲートでcsを変えると、eipを変える箇所が別々になり、カーネルコード中にUSER_CSで動作させる箇所が出てきてしまい、整合性が取れなくなる可能性が考えられるのでLinuxと同じ方式でやってみようと思います。