読者です 読者をやめる 読者になる 読者になる

自作カーネルでコンテキストスイッチを実装したいので,Minix3.1.0のやり方を調べる.

minix

Minixコンテキストスイッチする部分は,処理内容的にkernel/mpx386.sのrestart()っぽいのでこれを見ます.
実装はアセンブラだけど,cからも呼べるようにkernel/proto.hにプロトタイプ宣言があります.

_PROTOTYPE( void restart, (void)                                        );

といっても,実際にcのファイルからrestart()を呼ぶのは,kernel/main.cのmain()の最後のところだけです.

!*===========================================================================*
!*                              restart                                      *
!*===========================================================================*
_restart:

! Restart the current process or the next process if it is set. 

        cmp     (_next_ptr), 0          ! see if another process is scheduled
        jz      0f

まず_next_ptrは何かと言うと,kernel/glo.hで宣言されてます.

EXTERN struct proc *next_ptr;   /* next process to run after restart() */

restart()の後に動くプロセス(コンテキストスイッチの結果,処理が始まる/再開)ですね.

最初に次のプロセスがあるかをチェックします._next_ptrがNULLでなければ,次のプロセスがあるという感じで.
無ければ,ラベル0に飛びます.
cmp命令で_next_ptrと0を比較して,_next_ptrが0なら,ゼロフラグが1になるので,次のjz命令でラベル0に飛びます.

もし_next_ptrが0でなければ,下の処理を実行します.
_proc_ptrもglo.hにてextern付きで宣言されてます.

EXTERN struct proc *proc_ptr;   /* pointer to currently running process */

proc_ptrはカレントプロセスを示すので,_next_ptrで示すプロセスがカレントプロセスになるようにしてます.

        mov     eax, (_next_ptr)
        mov     (_proc_ptr), eax        ! schedule new process 
        mov     (_next_ptr), 0

next_ptrをproc_ptrにセットしたら,next_ptrはNULLにされます.

0:      mov     esp, (_proc_ptr)        ! will assume P_STACKBASE == 0
        lldt    P_LDT_SEL(esp)          ! enable process' segment descriptors

P_LDT_SELはマクロで,scont.hに定義があります.
これによるとstruct procの先頭からのオフセットを定義しているようです.
proc_ptrは当然struct proc型の構造体なので,これの先頭からのオフセットから,ldtセグメントディスクリプタの場所を示しているはずです.というか,そうじゃないとlldtでエラーになるので.
lldtで使用している変数は,proc構造体のp_ldt_selですね.

struct proc {
  struct stackframe_s p_reg;    /* process' registers saved in stack frame */

#if (CHIP == INTEL)
  reg_t p_ldt_sel;              /* selector in gdt with ldt base and limit */
  struct segdesc_s p_ldt[2+NR_REMOTE_SEGS]; /* CS, DS and remote segments */
#endif 

次は,lltdの時と同様に,proc構造体のP_STACKTOPで示す変数にアクセスして,そのアドレスをeaxに入れておいて,
それをtss構造体のメンバ変数sp0に代入します.

        lea     eax, P_STACKTOP(esp)    ! arrange for next interrupt
        mov     (_tss+TSS3_S_SP0), eax  ! to save state in process table

tss構造体は,protect.cで定義があります.TSS3_S_SP0は以下のように4となってます.
reg_tはunsigned intなので,x86なら4バイトってことで,tss構造体の先頭アドレス+4でsp0を指すようになってます.

struct tss_s {
  reg_t backlink;
  reg_t sp0;                    /* stack pointer to use during interrupt */
  reg_t ss0;                    /*   "   segment  "  "    "        "     */
  reg_t sp1;
#define TSS3_S_SP0	4

最後に,各種レジスタをpopしてから,iret命令を使うことでrestart()を抜けるとproc_ptrで示されるプロセスに処理が遷移します.

restart1:
        decb    (_k_reenter)
    o16 pop     gs
    o16 pop     fs
    o16 pop     es
    o16 pop     ds
        popad
        add     esp, 4          ! skip return adr
        iretd                   ! continue process