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行目
MMX:
SSE: Intel SDM vol.3 Table 13.1 [4, 6行目](Xはdon't care) 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つの表を見ると、
となる。これを利用して、
context switchとdo_forkの際に6、unlazy_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 1f
で1
にjumpして、movb $1,X86_HARD_MATH
として、終了する。fstsw
はx87 FPU status registerをmemoryにstoreする命令で、下位8bitがすべて0であるかをチェックする(勿論、真なので8jumpする)
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, ®s, 0, NULL, NULL); when sys_clone .. do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr); when kernel_thread .. do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 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していると書かれている:
また、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に述べたとおり、
ということを念頭に置く。
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
が初期化され12、PF_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_fpu
とkernel_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。
-
Kernel Modeでは、4K-byte pageを0 clearしたり、pageのcopyをする際にmmx命令を使っている。↩
-
勿論、HWのexceptionの機構とsoftwareの分野のそれとは、異なるものであるが、少なくとも言葉としては同じ"exception"なので。↩
-
cpuid命令の詳細については、https://www.scss.tcd.ie/Jeremy.Jones/CS4021/processor-identification-cpuid-instruction-note.pdf のch. 5.1.2.4とTable5.6(edx registerの方)を見れば良いとおもう。↩
-
この結果は、Intel SDM vol.3の
Table 9-3. Recommended Settings of EM and MP Flags on IA-32 Processors
にも合致しているので、大丈夫そう。↩ -
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の初期化はなされてなさそう)↩ -
また、
do_notify_resume
内でもdo_notify_resume -> handle_signal -> setup_frame -> setup_sigcontext -> save_i387 -> save_i387_fxsave -> unlazy_fpu で必要であれば、TS=1
と初期化されているようだ。↩ -
これは、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).
でそう書いてある。↩ -
やや細かいが、Intel SDM vol.3のTable 9.1によれば、x87 FPU Status Word(regiterの中身)の初期値は0x0000である。↩
-
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.“とある。↩
-
この点をもう少し詳しく述べると、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命令をサポートしているか、で区切られているのはこういった背景があるためである。↩ -
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 でまとめましたので、参照してください。↩
-
init_fpuの
cwd = 0x37f
の左辺はFPU Control Word(16bitのFPU Control Register)でIntel SDM vol.3 Table 9-1によれば、INITの際、0x37F
に初期化するので、それを踏襲している。mxcsrの0x1F80
もPower upの際の設定値と同じ。↩ -
swapper processに関しては、
set_bit(0, pidmap_array->page);
でbit0番に1を埋めてる↩ -
commentにも、
Careful.. There are problems with IBM-designed IRQ13 behaviour. Don't touch unless you *really* know how it works.
とか、IRQ13は危険!みたいな文言がいくつかあるので、disableであってると思う。↩