snail_314 发表于 2010-11-08 12:31

回复 10# zylthinking


    没说这条跳转

我的意思是,在movl %eax,%cr0    这条指令执行时,因为pipeline的原因,有可能后面x条(x要看pipeline的深度)的操作数已经被取了,但是因为movl %eax,%cr0    还没完成,所以它们的操作数还在实模式下面取得的,自然是不对的(因为后面的指令都希望在保护模式下取数据和写数据).所以这条jmp是clear pipeline,保证后面的x条指令都在保护模式下从取指->解码->取操作数->执行的过程重做一遍

zylthinking 发表于 2010-11-08 12:45

回复zylthinking


    没说这条跳转

我的意思是,在movl %eax,%cr0    这条指令执行时,因为pipeli ...
snail_314 发表于 2010-11-08 12:31 http://linux.chinaunix.net/bbs/images/common/back.gif

你说的是general 的情况, 看实际代码可以看到, movl $1f, %cr0 后若干指令为 jump 1f, movl $1f, %eax, jmp *%eax, 在开启保护模式后, eip 在 jmp *%eax 包括 执行jmp *%eax时是没变的, 还是物理地址取指令; 而涉及到数据段的只有 movl $1f, %eax, 而这个如果 $1f 本身被编译后赋值应该本身就是虚拟地址, 那么即便之前是实模式下缓存的, 反正被缓存的$1f 是虚拟地址, 又有什么关系呢?

snail_314 发表于 2010-11-08 12:52

回复 12# zylthinking


    恩.
并且我刚才说错了,上面代码只涉及了未分页和分页两种情况,和保护模式和实模式无关哈.纠正一下

snail_314 发表于 2010-11-08 13:24

本帖最后由 snail_314 于 2010-11-08 13:46 编辑

回复 12# zylthinking


    我想到一个解释,不知说不说得通

jmp *%eax这条指令在未分页和分页的情况下执行上有区别

pipeline中都是放下面可能会执行的指令的.
未分页情况下cpu会从%eax求值出来的物理地址取指令放到pipeline中(肯定取出来的都是垃圾数据了,毕竟这时%eax大约是0xCxxxxxxx之类的数,这个物理地址下不知道会是些什么咚咚);
分页情况下cpu会从%eax求值出来的数经过mmu再到物理地址取指令放到pipeline中,这才是正确的下一条该执行的指令

依赖cpu的设计,如果pipeline设计得够深,加上什么prefetch等,那么第一种情况有可能会出现,应该是种隐患.
所以,第一条jmp是为了让第一种情况的隐患消失

zylthinking 发表于 2010-11-08 13:38

回复zylthinking


    我想到一个解释,不知说不说得通

jmp *%eax这条指令在未分页和分页的情况下 ...
snail_314 发表于 2010-11-08 13:24 http://linux.chinaunix.net/bbs/images/common/back.gif

这个不能靠猜了, 只能找资料或者找高人指点了 呵呵

奇门遁甲-lu 发表于 2010-11-08 20:38

jmp 1f                        /* flush the prefetch-queue */
这句是多余的。
有jmp *%eax 就够了。
实际中把jmp 1f去掉, 是没有问题的, 实践过。
以前在一个mailist看到过关于arm 也有类似讨论:
是开启mmu 后,因为流水线的关系,要用几个nop来填充。
后来有牛人现身,现代的cpu,已经完全解决了这个问题了,
开启mmu 后立即跳转是没有问题的。

snail_314 发表于 2010-11-08 21:43

那以前的cpu为什么又不行?

剑魂箫心 发表于 2016-03-06 13:06

本帖最后由 剑魂箫心 于 2016-03-06 18:56 编辑

回复 16# 奇门遁甲-lu


    回答很正确,我看了2.6.8的代码,这个地方已经改成了192         movl %eax,%cr0          /* ..and set paging (PG) bit */
193         ljmp $__BOOT_CS,$1f   /* Clear prefetch and normalize %eip */
194 1:
195         /* Set up the stack pointer */
196         lss stack_start,%esp其中$__BOOT_CS是数字0x10,如果按照selector的格式来看,对应是GDT中index=2的descriptor。
内核编译链接后,标号1处的地址应该是3G+xx。为了方便我们假设它是0xc0100042
这样来说,L193就可以翻译成ljmp $0x10,$0xc0100042意思就是使用长跳转指令,直接把CS刷成0x10,把eip刷成0xc0100042,这样就完成了把寄存器中的物理地址全改为虚拟地址的过程。在这之后分页机制才会起作用。

总结一下,2.4内核中这个地方写的太罗嗦太晦涩,我做了下实验,将         jmp 1f /* flush the prefetch-queue */
1:直接删掉,编译后照样没问题。或者我们借鉴下2.6内核的做法,

         movl %eax,%cr0 /* ..and set paging (PG) bit */
         jmp 1f /* flush the prefetch-queue */
1:
         movl $1f,%eax
         jmp *%eax /* make sure eip is relocated */
1:直接改为         movl %eax,%cr0          /* ..and set paging (PG) bit */
         ljmp $__KERNEL_CS,$1f
1:
         /* Set up the stack pointer */这样就非常清晰了,我们也用长跳转把CS和EIP一块刷新,经测试,系统编译后可以正常启动。
页: 1 [2]
查看完整版本: head.s 汇编几个迷惑