linux kernelでのFPU, MMX, SSEについて

linux kernelでのFPU, MMX, SSEについて

本記事では、linux kernel 2.6.11でのFPU(Float Point Unit)やMMX, SSEがどう設定、使用されているのかを確認する。FPU, MMX SSE命令を使用する際は、使用する際に意図的に#NM(Interrupt7: Device not available exception)を出し、各種フラグを切り替え(特にcr0.TS flagをOFFにして)、これらの命令を使えるようにしている。(後述の通り、Kernel Modeでは、kernel_fpu_begin, kernel_fpu_endにてフラグの切り替えをおこなっている1ため、この限りでない。)

プログラミング言語の例外処理はパフォーマンスを落とすものとして一般に嫌われているが、ハードウェア(この場合x86)のレイヤでは、例外をあえて意図的に出すことでパフォーマンス向上を狙っている点で、面白いなと思った2ので、少し詳細に調べて見ようと思った。

reference

  • Understanding the Linux Kernel ch.3とch.8のFPUの部分(本記事ではこの本に従って、version 2.6.11とする)

  • Intel SDM vol.1 ch.8

  • Indel SDM vol.2 fxsave

  • Intel SDM vol.3 cr0の部分、#NM exception

boot時:

arch/i386/boot/setup.S

最初の値がどうなっているのかは気になるので、protected modeに移行した直後の処理をみていく。

pagingとIDTの初期設定が終わった後は、checkCPUtypeに移る:

#define X86     new_cpu_data+CPUINFO_x86
#define X86_VENDOR new_cpu_data+CPUINFO_x86_vendor
#define X86_MODEL  new_cpu_data+CPUINFO_x86_model
#define X86_MASK   new_cpu_data+CPUINFO_x86_mask
#define X86_HARD_MATH  new_cpu_data+CPUINFO_hard_math
#define X86_CPUID  new_cpu_data+CPUINFO_cpuid_level
#define X86_CAPABILITY new_cpu_data+CPUINFO_x86_capability
#define X86_VENDOR_ID  new_cpu_data+CPUINFO_x86_vendor_id

// skip (Enable Paging)

checkCPUtype:
    movl $-1,X86_CPUID       #  -1 for no CPUID initially
    movb $3,X86      # at least 386
    pushfl           # push EFLAGS
    popl %eax     # get EFLAGS
    movl %eax,%ecx     # save original EFLAGS
    xorl $0x240000,%eax  # flip AC and ID bits in EFLAGS
    pushl %eax        # copy to EFLAGS
    popfl            # set EFLAGS
    pushfl           # get new EFLAGS
    popl %eax     # put it in eax
    xorl %ecx,%eax     # change in flags
    pushl %ecx        # restore original EFLAGS
    popfl
    testl $0x40000,%eax  # check if AC bit changed
    je is386

    movb $4,X86      # at least 486
    testl $0x200000,%eax # check if ID bit changed
    je is486

    /* get vendor info */
    xorl %eax,%eax         # call CPUID with 0 -> return vendor ID
    cpuid
    movl %eax,X86_CPUID        # save CPUID level
    movl %ebx,X86_VENDOR_ID        # lo 4 chars
    movl %edx,X86_VENDOR_ID+4 # next 4 chars
    movl %ecx,X86_VENDOR_ID+8 # last 4 chars

    orl %eax,%eax          # do we have processor info as well?
    je is486

    movl $1,%eax     # Use the CPUID instruction to get CPU type
    cpuid
    movb %al,%cl       # save reg for future use
    andb $0x0f,%ah       # mask processor family
    movb %ah,X86
    andb $0xf0,%al       # mask model
    shrb $4,%al
    movb %al,X86_MODEL
    andb $0x0f,%cl       # mask mask revision
    movb %cl,X86_MASK
    movl %edx,X86_CAPABILITY

is486:   movl $0x50022,%ecx   # set AM, WP, NE and MP
    jmp 2f

is386:   movl $2,%ecx     # set MP
2: movl %cr0,%eax
    andl $0x80000011,%eax    # Save PG,PE,ET
  # append MP: Monitor Coprocessor[1]
    orl %ecx,%eax
    movl %eax,%cr0
    call check_x87
# skip (After that load IDT, GDT and call start_kernel)
check_x87:
    movb $0,X86_HARD_MATH
    clts # clear task switch flag
    fninit # initialize FPU
    fstsw %ax # store x87 FPU status register to %ax
    cmpb $0,%al
    je 1f
    movl %cr0,%eax     /* no coprocessor: have to set bits */
    xorl $4,%eax     /* set EM */
    movl %eax,%cr0
    ret
    ALIGN
1: movb $1,X86_HARD_MATH
    .byte 0xDB,0xE4     /* fsetpm for 287, ignored by 387 */
    ret

ここでは、まず、cr0のフラグについてとcheck_x87の動作について説明する。

checkCPUtype ~ i386まで

je is386やらje is486はすべてjumpせず、cpuid命令3の実行により、new_cpu_dataに値が入り、%cr0 registerに各種flagが設定される:(FPUに関係のあるのだけ4)

  • ET(Extension Type[4])=1 : Reserved in the Pentium 4, Intel Xeon, P6 family, and Pentium processors. In the Pentium 4, Intel Xeon, and P6 family processors, this flag is hardcoded to 1.
  • TS(Task Switched[3])=0
  • EM(Emulation[2])=0 : indicates an x87 FPU is present when clear.
  • MP(Monitor Coprocessor[1]) =1 : If the MP flag is set, a WAIT instruction generates a device-not-available exception (#NM) if the TS flag is also set.

ET, EM, MPは常にこの値で、TSが特定のタイミングで切り替わる。TS flagについて、役割の概要を述べる。(実際のコードは後述とする)

cr0.TSはIntel SDM ch.2.5によれば、以下のような仕様である。

  • If the TS flag is set and the EM flag (bit 2 of CR0) is clear, a device-not-available exception (#NM) is raised prior to the execution of any x87 FPU/MMX/SSE/SSE2/SSE3/SSSE3/SSE4 instruction; with the exception of PAUSE, PREFETCHh, SFENCE, LFENCE, MFENCE, MOVNTI, CLFLUSH, CRC32, and POPCNT. See the paragraph below for the special case of the WAIT/FWAIT instructions.

  • If the TS flag is set and the MP flag (bit 1 of CR0) and EM flag are clear, an #NM exception is not raised prior to the execution of an x87 FPU WAIT/FWAIT instruction.

また、FPU, MMX, SSEにおける各種flagのtableを見ると、次のようになっている:

FPU: 3,4行目 f:id:knknkn11626:20190414132914p:plain

MMX: f:id:knknkn11626:20190414132806p:plain

SSE: Intel SDM vol.3 Table 13.1 [4, 6行目](Xはdon't care) f:id:knknkn11626:20190414132619p:plain cr4.OSFXSR(bit 9), cr4.OSXMMEXCPT(bit 10)

MXCSRはSSE命令に付随するregister5

CPUIDの項目はsse系がハードウェアでサポートされているか?

SSEのcr4.OSFXSRとcr4.OSXMMEXCPTはstart_kernel -> check_bugs -> check_fpuにて、ハードウェアがサポートされている限りONになる。

これら3つの表を見ると、

  • TS=0の時、FPU, MMX, SSEの命令時にExceptionを送出せずに実行する
  • TS=1の時、FPU, MMX, SSEの命令時にException(#NM)を送出する

となる。これを利用して、

context switchとdo_forkの際に6unlazy_fpu(または、__unlazy_fpu)にてTS=1`と設定して、使われる段になったら、#NMのException handler(device_not_available_emulate -> math_state_restore)内にてTS=0として、2回目以降はこれらの命令を使えるようにしている。

FPUやMMXに関連するregisterは大きいため、context switchのたびにこれらのregisterをsave and storeするのはパフォーマンスが良くない。また、user側での使用頻度は多くないため、必要になったら、registerをsave, storeするような方式7をとっている。「必要になったよ」というトリガーがException handlerというわけだ。

なお、TSのON, OFFは以下のマクロor命令で表現されている:

// include/asm-i386/system.h
#define clts() __asm__ __volatile__ ("clts")
#define write_cr0(x) \
   __asm__("movl %0,%%cr0": :"r" (x));
#define stts() write_cr0(8 | read_cr0()) // 8=0b1000

check_x87

check_x87 labelでは、je 1f1にjumpして、movb $1,X86_HARD_MATHとして、終了する。fstswx87 FPU status registerをmemoryにstoreする命令で、下位8bitがすべて0であるかをチェックする(勿論、真なので8jumpする)

f:id:knknkn11626:20190413140800p:plain Intel SDM vol.1より

Note) X86_HARD_MATHはmathemetical coprocessor(IRQ 13)と関連する。結果的にはIRQ13はdisableのままである。FPU以前のlegacyな計算機の外部deviceとして備え付けられていたものらしい9。その設定については、細かい(&かなりややこしい)ので補足に回す。

cpu_init

head.Sではcr0.TSを0にしていなかったので、どこかで1にしているはずだ。この設定は、start_kernel -> trap_init -> cpu_initにておこなっているので、実際に見てみよう:

// cpu_init
current_thread_info()->status = 0; // disable TS_USEDFPU
// { current->flags &= ~PF_USED_MATH; }
clear_used_math();
// sttsマクロ
mxcsr_feature_mask_init();

一番目の行は、TS_USEDFPU(0x0001) flagをOFFにする狙いがある。2番目の行は、PF_USED_MATHをOFFにしている。3番目は、sttsマクロ(前述のとおり)を用いて、cr0.TS flagをONにしている。

TS_USEDFPUは、stack(struct thread_info)に結びついており、PF_USED_MATHについてはprocess(struct task_struct)に結びついている。両者は意味は同じだが、Kernel ModeにてFPUを使用する際に、(process単位でなく)thread単位でcr0.TSの管理をしたいという動機でTS_USEDFPUが使用される。


TS.flagとその周辺の前提知識がわかったところで、forkの部分とcontext switchの部分をみてみる。

do_fork

do_forkはfork, clone, kernel_threadのコアの部分の関数である。:

when sys_fork .. do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
when sys_clone .. do_fork(clone_flags, newsp, &regs, 0, parent_tidptr, child_tidptr);
when kernel_thread .. do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);

FPUに関連するところは、do_fork -> copy_process -> prepare_to_copy -> unlazy_fpuの部分:

// prempt_disable: thread_infoのpreempt_countを1プラスする
// preempt_enable:     preempt_enable_no_resched(); preempt_check_resched();

#define unlazy_fpu( tsk ) do {   \
   preempt_disable();  \
   __unlazy_fpu(tsk);  \
   preempt_enable();   \
} while (0)

#define __unlazy_fpu( tsk ) do { \
   if ((tsk)->thread_info->status & TS_USEDFPU) \
       save_init_fpu( tsk ); \
} while (0)

static inline void save_init_fpu( struct task_struct *tsk )
{
    preempt_disable();
    __save_init_fpu(tsk);
    stts(); // #define stts() write_cr0(8 | read_cr0())
    preempt_enable();
}

static inline void __save_init_fpu( struct task_struct *tsk )
{
    if ( cpu_has_fxsr ) {
        asm volatile( "fxsave %0 ; fnclex"
                  : "=m" (tsk->thread.i387.fxsave) ); // thread_struct.i387(i387_union) 
    } else {
        asm volatile( "fnsave %0 ; fwait"
                  : "=m" (tsk->thread.i387.fsave) );
    }
    tsk->thread_info->status &= ~TS_USEDFPU;
}

TS_USEDFPUはFPU命令がすでに使用されていることを表すフラグ。もし、使用されていれば、__save_init_fpuを実行する。cpu_has_fxsr#define cpu_has_fxsr boot_cpu_has(X86_FEATURE_FXSR)で真なので、fxsave命令が実行され、FPUやSSE関連のregisterが全てtsk->thread.i387.fxsaveに保存される。fxsaveの型はunion i387_unionで、以下のようになっている:

union i387_union {
    struct i387_fsave_struct   fsave;
    struct i387_fxsave_struct  fxsave; // SSE and SSE2 extensions.
    struct i387_soft_struct soft;
};

struct i387_fxsave_struct {
    unsigned short    cwd;
    unsigned short    swd;
    unsigned short    twd;
    unsigned short    fop;
    long   fip;
    long   fcs;
    long   foo;
    long   fos;
    long   mxcsr;
    long   mxcsr_mask;
    long   st_space[32];  /* 8*16 bytes for each FP-reg = 128 bytes */
    long   xmm_space[32]; /* 8*16 bytes for each XMM-reg = 128 bytes */
    long   padding[56];
} __attribute__ ((aligned (16))); // must be aligned on a 16-byte boundary.

fsaveとfxsaveの違いは、 SSE命令をサポートしているか否かの違い10。これらの(おどろおどろしい)構造体の正体は、Intel SDM vol.2のfxsave命令を見れば、以下のような形でsaveしていると書かれている:

f:id:knknkn11626:20190414161350p:plain
fxsave memory region

また、it must be aligned on a 16-byte boundary.とfxsaveにかかれているため、__attribute__ ((aligned (16)));となっている。これら一つ一つの構造体の意味については、深入りしない(し、linux kernelの範囲では深入りする必要もない)

Note) unlazy_fpuはfork元の(親となる)processに対して実行しているが、dup_task_structではtask_structの内容をcopyするため、子のprocessに対しても同様にTS_USEDFPUが0となっている。

context switch

context switchのentry pointはscheduler関数であったから、scheduler -> context_switch -> switch_toとたどっていき、__switch_to11内の__unlazy_fpuがFPU関連のコードである:

struct task_struct fastcall * __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 = &per_cpu(init_tss, cpu);
    __unlazy_fpu(prev_p);
    load_esp0(tss, next);
    load_TLS(next, cpu);
// skip

__unlazy_fpuについては、do_forkの章でやったとおりなので、省略する。

Note) next_p(process P_Bとよぶ)は前の(P_Bがprev_pであった頃の)context switchにて__switch_to(P_B, n_p) -> __unlazy_fpu(P_B)と実行されているので、TS_USEDFPUが0となっていることが保証される。

Exception

ここからは、context switch後に一度も使用されていない(cr0.TS=1)場合に、FPU関連の命令が使用されると、#NM exceptionのhandlerが発生し、cr0.TS=0となり、以降、exceptionが発生せずにFPU関連の命令が使用できる部分のコードをみていきたい。boot時の章のcr0.TSに述べたとおり、

  • TS=0の時、FPU, MMX, SSEの命令時にExceptionを送出せずに実行する
  • TS=1の時、FPU, MMX, SSEの命令時にException(#NM)を送出する

ということを念頭に置く。

handlerの登録

まず、Interrupt 7 Device Not Available Exception (#NM)で、trap_init にて、set_trap_gate(7,&device_not_available);とhandlerが登録される。trap gateなので、device_not_available開始時にはeflags.IFが自動的に0にならない。

device_not_available

linuxにおけるException全般の取扱は、http://cstmize.hatenablog.jp/entry/2019/03/28/linux2_6%E3%81%AE%E5%A0%B4%E5%90%88%E3%81%AEException%E3%81%A8%60int_%240x80%60%28system_call%29%E3%81%AE%E6%8C%99%E5%8B%95 に以前書いた。今回は、device_not_availableに限った話をする。

# arch/i386/kernel/entry.S
ENTRY(device_not_available)
    pushl $-1           # mark this as an int
    SAVE_ALL
    movl %cr0, %eax
    testl $0x4, %eax     # EM (math emulation bit): usually set 0
    jne device_not_available_emulate
     # we take cr0.EM=0
    preempt_stop # cli
    call math_state_restore
    jmp ret_from_exception
device_not_available_emulate:
    pushl $0        # temporary storage for ORIG_EIP
    call math_emulate # force_sig(SIGFPE,current); and schedule()
    addl $4, %esp
    jmp ret_from_exception

cr0.EMは常時0だったから、jne(ZF=0, 結果が0でない場合jumpする)は素通りして、math_state_restoreをcallする:

void init_fpu(struct task_struct *tsk)
{
    if (cpu_has_fxsr) {
        memset(&tsk->thread.i387.fxsave, 0, sizeof(struct i387_fxsave_struct));
        tsk->thread.i387.fxsave.cwd = 0x37f; // 0b0000_0011_0111_1111
        if (cpu_has_xmm)
            tsk->thread.i387.fxsave.mxcsr = 0x1f80;
    } else {
        memset(&tsk->thread.i387.fsave, 0, sizeof(struct i387_fsave_struct));
        tsk->thread.i387.fsave.cwd = 0xffff037fu;
        tsk->thread.i387.fsave.swd = 0xffff0000u;
        tsk->thread.i387.fsave.twd = 0xffffffffu;
        tsk->thread.i387.fsave.fos = 0xffff0000u;
    }
    /* only the device not available exception or ptrace can call init_fpu */
    set_stopped_child_used_math(tsk); // tsk->flags |= PF_USED_MATH;
}


asmlinkage void math_state_restore(struct pt_regs regs)
{
    struct thread_info *thread = current_thread_info();
    struct task_struct *tsk = thread->task;

    clts(); // clear cr0.TS
    if (!tsk_used_math(tsk)) // ((p)->flags & PF_USED_MATH)
        init_fpu(tsk);
    restore_fpu(tsk);
    thread->status |= TS_USEDFPU;    /* So we fnsave on switch_to() */
}

次からFPU, MMX, SSEの命令時にExceptionを送出せずに実行したいので、cr0.TSをoffにする。PF_USED_MATHがOFFなら、init_fpuでcurrent->thread.i387.fxsaveが初期化され12PF_USED_MATHがONになる。この初期化したfxsaveがrestore_fpuによって、registerに反映される(fxrstor: restore命令)

その後、TS_USEDFPUもONする。

ということで、cr0.TSをoffにしていることが確認できた。以降、context switchされるまではFPU関連の命令を使ってもdevice_not_availableで補足されない。

kernel_start_fpu, kernel_end_fpu

上記の話は、user landでFPUが使われたときの挙動だったが、kernel thread(Kernel land)でFPUを使用する場合は、kernel_start_fpukernel_end_fpuの間でFPU, MMX命令を使うようにする。というのも、Exceptionはuser land(か、interrupt handlerの最中)でしか起こらないので、Kernel landでは上のようなことができないからである。

一例として、get_zeroed_pageという関数は、最終的にmmx_clear_pageで4K pageの中身を0埋めしている。get_zeroed_pageは例えば、start_kernel -> pidmap_initにて、pidmap_array->page = (void *)get_zeroed_page(GFP_KERNEL);という形で使われている。

static pidmap_t pidmap_arrayはpidが使用されているか否かを1(used) or 0(free)のbitで表現するための構造体であり、pidmap_array->pageは、pidの最大値32768bit=4KBの大きさのbitmapである。初期値は、process 0(kernel process, swapper processともいう)を除いて13すべて使用可能なので、0である必要がある。ということで、0埋めするというモチベーションが起こる。

get_zeroed_pageをたどっていくと、get_zeroed_page -> alloc_pages(gfp_mask | __GFP_ZERO, 0); -> alloc_pages_node(0, gfp_mask, order=0) -> __alloc_pages(gfp_mask, order=0, node_data[0]->node_zonelists[(gfp_mask & GFP_ZONEMASK)] ) -> buffered_rmqueue -> prep_zero_page(page, order=0, gfp_flags);(__GFP_ZEROのため) -> clear_highpage -> clear_page(kaddr);-> mmx_clear_page -> fast_clear_page(page);という感じでようやく所要の関数にたどり着く。

fast_clear_pageは

// arch/i386/lib/mmx.c
void kernel_fpu_begin(void)
{
    struct thread_info *thread = current_thread_info();

    preempt_disable(); // inc_preempt_count(); barrier();
    if (thread->status & TS_USEDFPU) {
          // fxsave %0 ; fnclex
          // tsk->thread_info->status &= ~TS_USEDFPU;
        __save_init_fpu(thread->task);
        return;
    }
    clts();
}
#define kernel_fpu_end() do { stts(); preempt_enable(); } while(0)


static void fast_clear_page(void *page)
{
    int i;

    kernel_fpu_begin(); 
    __asm__ __volatile__ (
        "  pxor %%mm0, %%mm0\n" : :
    );

    for(i=0;i<4096/64;i++)
    {
        __asm__ __volatile__ (
        "  movntq %%mm0, (%0)\n"
        "  movntq %%mm0, 8(%0)\n"
        "  movntq %%mm0, 16(%0)\n"
        "  movntq %%mm0, 24(%0)\n"
        "  movntq %%mm0, 32(%0)\n"
        "  movntq %%mm0, 40(%0)\n"
        "  movntq %%mm0, 48(%0)\n"
        "  movntq %%mm0, 56(%0)\n"
        : : "r" (page) : "memory");
        page+=64;
    }
    /* since movntq is weakly-ordered, a "sfence" is needed to become
    * ordered again.
    */
    __asm__ __volatile__ (
        "  sfence \n" : :
    );
    kernel_fpu_end();
}

という感じになっており、kernel_fpu_beginでcr0.TS=0が保証され、kernel_fpu_endでcr0.TS=1となる。つまり、両者の内部では、(cr0.TS=0なので)mmx命令がexceptionを送出せず使える。

補足

mathematical coprocessorについて

X86_HARD_MATHの値については、legacyのIRQ13(mathematical coprocessor)の設定のときに用いられているようだ。結果的にdisableになっている。

start_kernel -> init_IRQ

 if (boot_cpu_data.hard_math && !cpu_has_fpu)
        setup_irq(FPU_IRQ, &fpu_irq); // FPU_IRQ=13

にてIRQ13がsetupされるかどうか決まる。

boot_cpu_data.hard_mathについては、movb $1,X86_HARD_MATHの影響で、1になる。#define X86_HARD_MATH new_cpu_data+CPUINFO_hard_mathと定義されていて、start_kernel -> setup_arch -> memcpy(&boot_cpu_data, &new_cpu_data, sizeof(new_cpu_data));boot_cpu_dataに値がコピーされる。なので、boot_cpu_data.hard_math=1で良さそう。

cpu_has_fpuについても1になる。これは、movl %edx,X86_CAPABILITYと関係がある。

// include/asm-i386/cpufeature.h
#define boot_cpu_has(bit) test_bit(bit, boot_cpu_data.x86_capability)
#define cpu_has_fpu boot_cpu_has(X86_FEATURE_FPU) // #define X86_FEATURE_FPU (0*32+ 0)

で、#define X86_CAPABILITY new_cpu_data+CPUINFO_x86_capabilityと合わせてみると、cpuidの結果FPUはサポートされているので、1が入るはずで、boot_cpu_has(X86_FEATURE_FPU)は1となり、cpu_has_fpu=1となる

ということで、IRQ13は使用されない14


  1. Kernel Modeでは、4K-byte pageを0 clearしたり、pageのcopyをする際にmmx命令を使っている。

  2. 勿論、HWのexceptionの機構とsoftwareの分野のそれとは、異なるものであるが、少なくとも言葉としては同じ"exception"なので。

  3. cpuid命令の詳細については、https://www.scss.tcd.ie/Jeremy.Jones/CS4021/processor-identification-cpuid-instruction-note.pdf のch. 5.1.2.4とTable5.6(edx registerの方)を見れば良いとおもう。

  4. この結果は、Intel SDM vol.3のTable 9-3. Recommended Settings of EM and MP Flags on IA-32 Processorsにも合致しているので、大丈夫そう。

  5. Intel SDM vol.1 ch.11.5.1を見ると、Each of the six exception conditions has a corresponding flag (IE, DE, ZE, OE, UE, and PE) and mask bit (IM, DM, ZM, OM, UM, and PM) in the MXCSR registerとある。そこでMXCSRがどう設定されてるのかを確認する。Intel SDM vol.3のTable 9-1を見ると、 Power-upのときに、MXCSRが0x1F80にリセットされる。それと、Intel SDM vol.1 のtable10.3とを合わせると、初期状態はexception flagが全てmaskされていることがわかるので、#UDや#XM Exceptionは発生しないと思われる。(start_kernel -> trap_init -> cpu_init -> mxcsr_feature_mask_initの部分では、MXCSRの初期化はなされてなさそう)

  6. また、do_notify_resume内でもdo_notify_resume -> handle_signal -> setup_frame -> setup_sigcontext -> save_i387 -> save_i387_fxsave -> unlazy_fpu で必要であれば、TS=1と初期化されているようだ。

  7. これは、linux OSが特別にこの性質を利用しているというわけではなく、x86側の機能としてそもそも搭載されているものだ。Intel SDM ch.2.5のThe processor does not automatically save the context of the x87 FPU, XMM, and MXCSR registers on a task switch. Instead, it sets the TS flag, which causes the processor to raise an #NM exception whenever it encounters an x87 FPU/MMX/SSE/SSE2/SSE3/SSSE3/SSE4 instruction in the instruction stream for the new task (with the exception of the instructions listed above).でそう書いてある。

  8. やや細かいが、Intel SDM vol.3のTable 9.1によれば、x87 FPU Status Word(regiterの中身)の初期値は0x0000である。

  9. Understanding the Linux kernel ch.3に、"The name mathematical coprocessor continues to be used in memory of the days when floating-point computations were executed by an expensive special-purpose chip.“とある。

  10. この点をもう少し詳しく述べると、FPU, MMXとSSE系の命令は独立に動作する(Intel SDM vol.1 ch.8.1.The x87 FPU executes instructions from the processor’s normal instruction stream. The state of the x87 FPU is inde- pendent from the state of the basic execution environment and from the state of SSE/SSE2/SSE3 extensions.より) 一方、FPUとMMXは同じregisterを用いる(However, the x87 FPU and Intel MMX technology share state because the MMX registers are aliased to the x87 FPU data registers. Therefore, when writing code that uses x87 FPU and MMX instructions, the programmer must explicitly manage the x87 FPU and MMX state)。ということで、FPU, MMX, SSE, SSE2..と種類があるが、SSE命令をサポートしているか、で区切られているのはこういった背景があるためである。

  11. inline assemblyで難しいですが、 switch_toに関しては、以前、http://cstmize.hatenablog.jp/entry/2019/03/16/%60switch_to%60%E3%82%92%E8%AA%AD%E3%82%93%E3%81%A7%E3%81%BF%E3%82%8B でまとめましたので、参照してください。

  12. init_fpuのcwd = 0x37fの左辺はFPU Control Word(16bitのFPU Control Register)でIntel SDM vol.3 Table 9-1によれば、INITの際、0x37Fに初期化するので、それを踏襲している。mxcsrの0x1F80もPower upの際の設定値と同じ。

  13. swapper processに関しては、set_bit(0, pidmap_array->page);でbit0番に1を埋めてる

  14. commentにも、Careful.. There are problems with IBM-designed IRQ13 behaviour. Don't touch unless you *really* know how it works. とか、IRQ13は危険!みたいな文言がいくつかあるので、disableであってると思う。