从这篇文章开始算是正式进入math模块的学习,前期我花了大量的时间来储备必要的知识,这个工作早在今年年初就进行了,可是被各种原因耽搁,我也寄希望于接下来能顺利进行。
这里得说一下,我是一边学习一边做的笔记,因此非常大可能出现错误。如果在文章发表后发现错误,我会及时更新上去,希望尽可能不给大家的理解造成困扰。
一 CR0控制寄存器
- NE
浮点处理器操作模式,0为MS-DOS compatibility mode(Vector 45, IRQ13),1为Native mode(Vector 16, Coprocessor error)。
- ET (Extension Type)
协处理器类型,0表示80287协处理器或者没有协处理器,1表示80387协处理器。
- TS (Task Switched)
任务已切换标志,0表示任务没有被切换,1表示任务已切换成功。它可以延迟保存任务切换时协处理器的内容(系统通过触发一个DNA(Device Not Available, Vector 7, device not available)异常来让你保存任务切换时协处理器的内容),直到新任务开始执行协处理器指令。一个直观的事实是如果新任务一直没有执行协处理器指令,那么保存之前任务的协处理器内容的操作就会一直被延迟,试想,如果在切回原任务前都没有任务执行协处理器指令,那么当前的协处理器内容就是原任务协处理器的内容,省去了协处理器内容的互换操作。
- EM (EMulation)
仿真标志,0表示系统有协处理器,1表示系统没有协处理器。对于EM和TS,EM=1系统没有协处理器或者EM=0 & TS=1任务已切换,一旦遇到协处理器指令就会触发DNA异常。
- MP (Math Present)
协处理器监控标志,0表示不监控,1表示开始监控。对于MP和TS,MP=1 & TS=1任务已切换被监控到,一旦遇到WAIT or FWAIT指令就会触发DNA异常。
二 Code部分
- boot/head.s (check x87)
/*
* NOTE! 486 should set bit 16, to check for write-protect in supervisor
* mode. Then it would be unnecessary with the "verify_area()"-calls.
* 486 users probably want to set the NE (#5) bit also, so as to use
* int 16 for math errors.
*/
movl %cr0,%eax # check math chip
andl $0x80000011,%eax # Save PG,PE,ET, reset NE,TS,EM,MP
/* "orl $0x10020,%eax" here for 486 might be good */
orl $2,%eax # set MP
movl %eax,%cr0
call check_x87
jmp after_page_tables
/*
* We depend on ET to be correct. This checks for 287/387.
*/
check_x87:
fninit /* initialize swd,cwd,twd */
fstsw %ax /* store swd to ax register,
all is 0 except for that the condition code of swd is indeterminte */
cmpb $0,%al /* 如果al为0(al初始不为0),则表示协处理器有将swd初始化 */
je 1f /* no coprocessor: have to set bits */
movl %cr0,%eax
xorl $6,%eax /* reset MP, set EM , 此时EM=1,没有协处理器,MP=0,不会监控TS标志 */
movl %eax,%cr0
ret
.align 4
1: .byte 0xDB,0xE4 /* fnsetpm(not 'fsetpm') for 287, ignored by 387,对于80287需要设定为保护模式 */
ret
- kernel/asm.s (IRQ13)
irq13:
pushl %eax
xorb %al,%al
outb %al,$0xF0 /* 0xF0是协处理器端口,用于清忙锁存器 */
movb $0x20,%al
outb %al,$0x20 /* 向8259主中断控制器发送EOI(中断结束)信号 */
jmp 1f
1: jmp 1f
1: outb %al,$0xA0 /* 向8259从中断控制器发送EOI(中断结束)信号 */
popl %eax
jmp coprocessor_error /* 异常转移 */
- kernel/sys_call.s (Coprocessor error)
coprocessor_error: /* 目前没有任何功能 */
push %ds
push %es
push %fs
pushl $-1 # fill in -1 for orig_eax
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
pushl $ret_from_sys_call
jmp math_error
- kernel/sys_call.s (Device Not Available)
device_not_available: /* '大名鼎鼎'的DNA异常 */
push %ds
push %es
push %fs
pushl $-1 # fill in -1 for orig_eax
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
movl $0x17,%eax
mov %ax,%fs
pushl $ret_from_sys_call /* 以上为标准的system call writing */
clts /* clear TS so that we can use math, 注意TS只在EM=0时才有意义 */
movl %cr0,%eax
testl $0x4,%eax # EM (math emulation bit)
je math_state_restore /* EM=0表示系统有协处理器,那么按正常flow跑即可,去执行协处理器内容的互换操作 */
pushl %ebp
pushl %esi
pushl %edi
pushl $0 # temporary storage for ORIG_EIP
call math_emulate /* 看来系统没有协处理器,开始协处理器的模拟 */
addl $4,%esp
popl %edi
popl %esi
popl %ebp
ret
- include/linux/sched.h (Task Switched)
/*
* switch_to(n) should switch tasks to task nr n, first
* checking that n isn't the current task, in which case it does nothing.
* This also clears the TS-flag if the task we switched to has used
* tha math co-processor latest.
*/
#define switch_to(n) {\
struct {long a,b;} __tmp; \
__asm__("cmpl %%ecx,current\n\t" \
"je 1f\n\t" \
"movw %%dx,%1\n\t" \
"xchgl %%ecx,current\n\t" \
"ljmp *%0\n\t" \
"cmpl %%ecx,last_task_used_math\n\t" \
/* 如果说返回原任务前没有其他任务去执行协处理器指令,那么清掉TS标志,无需进行协处理器内容的互换操作 */
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}