[ZLG-ARM] linux2.6.29 swtich_to 详细分析(一)

[复制链接]
 楼主| acertm 发表于 2009-5-16 13:06 | 显示全部楼层 |阅读模式
linux内核进程切换最重要的一个部分就是宏定义switch_to,下面从几个方面来详细讲解一下:<br />(1)内嵌汇编<br />(2)memory&nbsp;破坏描述符(编译器优化)<br />(3)进程切换的标志是什么?<br />(4)堆栈切换的标志是什么?<br />(5)为什么switch_to&nbsp;提供了三个参数?<br />(6)汇编参数的传递?<br /><br />带着这几个问题,先来大体浏览一下代码<br /><br />#define&nbsp;switch_to(prev,&nbsp;next,&nbsp;last)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />do&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />/*&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;*&nbsp;Context-switching&nbsp;clobbers(彻底击败)&nbsp;all&nbsp;registers,&nbsp;so&nbsp;we&nbsp;clobber&nbsp;<br />&nbsp;&nbsp;*&nbsp;them&nbsp;explicitly,&nbsp;via&nbsp;unused&nbsp;output&nbsp;variables.&nbsp;&nbsp;<br />&nbsp;&nbsp;*&nbsp;(EAX&nbsp;and&nbsp;EBP&nbsp;is&nbsp;not&nbsp;listed&nbsp;because&nbsp;EBP&nbsp;is&nbsp;saved/restored&nbsp;<br />&nbsp;&nbsp;*&nbsp;explicitly&nbsp;for&nbsp;wchan&nbsp;access&nbsp;and&nbsp;EAX&nbsp;is&nbsp;the&nbsp;return&nbsp;value&nbsp;of&nbsp;<br />&nbsp;&nbsp;*&nbsp;__switch_to())&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />unsigned&nbsp;long&nbsp;ebx,&nbsp;ecx,&nbsp;edx,&nbsp;esi,&nbsp;edi;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />asm&nbsp;volatile(&quot;pushfl
        &quot;&nbsp;&nbsp;/*&nbsp;save&nbsp;&nbsp;&nbsp;&nbsp;flags&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;pushl&nbsp;%%ebp
        &quot;&nbsp;&nbsp;/*&nbsp;save&nbsp;&nbsp;&nbsp;&nbsp;EBP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;movl&nbsp;%%esp,%[prev_sp]
        &quot;&nbsp;/*&nbsp;save&nbsp;&nbsp;&nbsp;&nbsp;ESP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;movl&nbsp;%[next_sp],%%esp
        &quot;&nbsp;/*&nbsp;restore&nbsp;ESP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;movl&nbsp;$1f,%[prev_ip]
        &quot;&nbsp;/*&nbsp;save&nbsp;&nbsp;&nbsp;&nbsp;EIP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;pushl&nbsp;%[next_ip]
        &quot;&nbsp;/*&nbsp;restore&nbsp;EIP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;jmp&nbsp;__switch_to
&quot;&nbsp;/*&nbsp;regparm&nbsp;call&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;1:        &quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;popl&nbsp;%%ebp
        &quot;&nbsp;&nbsp;/*&nbsp;restore&nbsp;EBP&nbsp;&nbsp;&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;popfl
&quot;&nbsp;&nbsp;&nbsp;/*&nbsp;restore&nbsp;flags&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;output&nbsp;parameters&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;[prev_sp]&nbsp;&quot;=m&quot;&nbsp;(prev-&gtthread.sp),&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*m表示把变量放入内存,即把[prev_sp]存储的变量放入内存,最后再写入prev-&gtthread.sp*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[prev_ip]&nbsp;&quot;=m&quot;&nbsp;(prev-&gtthread.ip),&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;=a&quot;&nbsp;(last),&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*=表示输出,a表示把变量last放入ax,eax&nbsp;=&nbsp;last*/&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;clobbered&nbsp;output&nbsp;registers:&nbsp;*/&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;=b&quot;&nbsp;(ebx),&nbsp;&quot;=c&quot;&nbsp;(ecx),&nbsp;&quot;=d&quot;&nbsp;(edx),&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*b&nbsp;变量放入ebx,c表示放入ecx,d放入edx,S放入si,D放入edi*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;=S&quot;&nbsp;(esi),&nbsp;&quot;=D&quot;&nbsp;(edi)&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;input&nbsp;parameters:&nbsp;*/&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;[next_sp]&nbsp;&nbsp;&quot;m&quot;&nbsp;(next-&gtthread.sp),&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*next-&gtthread.sp&nbsp;放入内存中的[next_sp]*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[next_ip]&nbsp;&nbsp;&quot;m&quot;&nbsp;(next-&gtthread.ip),&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*&nbsp;regparm&nbsp;parameters&nbsp;for&nbsp;__switch_to():&nbsp;*/&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[prev]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;a&quot;&nbsp;(prev),&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*eax&nbsp;=&nbsp;prev&nbsp;&nbsp;edx&nbsp;=&nbsp;next*/<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[next]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&quot;d&quot;&nbsp;(next)&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;/*&nbsp;reloaded&nbsp;segment&nbsp;registers&nbsp;*/&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&quot;memory&quot;);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />}&nbsp;while&nbsp;(0)<br /><br />以上代码,主要是内嵌汇编,这里先简单介绍一下:<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;内嵌汇编语法<br />__asm__&nbsp;__violate__&nbsp;(&quot;movl&nbsp;%1,%0&quot;&nbsp;:&nbsp;&quot;=r&quot;&nbsp;(result)&nbsp;:&nbsp;&quot;m&quot;&nbsp;(input));<br /><br />__asm__&nbsp;__violate__(&quot;指令模板&quot;&nbsp;:&nbsp;输出部&nbsp;:&nbsp;输入部);<br /><br />“movl&nbsp;%1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇<br />编靠它们将C&nbsp;语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表<br />达式,本例中只有两个:“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,<br />“%1”对应;注意对应顺序:第一个C&nbsp;表达式对应“%0”;第二个表达式对应“%1”,依次类<br />推,操作数至多有10&nbsp;个,分别用“%0”,“%1”….“%9”表示。在每个操作数前面有一个<br />用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。“result”前面的<br />限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”<br />与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而<br />不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面<br />上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写<br />一些指令。“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用<br />该寄存器参加运算。<br />(2)memory&nbsp;破坏描述符(编译器优化)<br />内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存<br />Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没<br />有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件<br />级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器<br />进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用<br />CPU指令流水线,常见的是重新排序读写指令。<br />对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬<br />件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序<br />执行的操作之间设置内存屏障(memory&nbsp;barrier),linux&nbsp;提供了一个宏解决编译器的执行<br />顺序问题。<br />void&nbsp;Barrier(void)<br />这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器<br />中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。<br />Memory描述符告知GCC:<br />l&nbsp;&nbsp;1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码<br />之前,它前面的指令都执行完毕<br />l&nbsp;&nbsp;2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变<br />量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变<br />量值写回内存,如果后面又访问这些变量,需要重新访问内存。<br />如果汇编指令修改了内存,但是GCC&nbsp;本身却察觉不到,因为在输出部分没有描述,此时<br />就需要在修改描述部分增加“memory”,告诉GCC&nbsp;内存已经被修改,GCC&nbsp;得知这个信息后,<br />就会在这段指令之前,插入必要的指令将前面因为优化Cache&nbsp;到寄存器中的变量值先写回内<br />存,如果以后又要使用这些变量再重新读取。<br />(3)进程切换的标志-----sp指针的切换<br />&nbsp;&nbsp;&nbsp;&nbsp;因为进程切换也就是进程描述符的切换,现在让我们来想一下我们是如何定位某个进程描述符的地址的,看下面的汇编代码:<br />&nbsp;&nbsp;&nbsp;mov&nbsp;$0xffffe000,%ecx<br />&nbsp;&nbsp;&nbsp;andl&nbsp;%esp,%ecx<br />&nbsp;&nbsp;&nbsp;movl&nbsp;%ecx,p<br /><br />执行上面代码后,p中即存储当前运行进程的thread_info结构的地址,但是我们最长用的是进程描述符的地址,因此内核设计了current宏来计算指向进程描述符的指针:<br />mov&nbsp;$0xfffe000,%ecx<br />andl&nbsp;%esp,%ecx<br />movl&nbsp;(%ecx),p<br />因为task字段在thread_info中的偏移量为0,所以执行上述三条指令后,p即是当前运行的进程的描述符指针。<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;我们可以看到,只要知道esp,那么,进程就唯一确定了,所以说esp是进程切换的标志<br />(4)堆栈切换的标志&nbsp;---&nbsp;ebp&nbsp;(栈低指针)<br />&nbsp;&nbsp;&nbsp;&nbsp;毋庸置疑,栈底指针肯定是堆栈切换的标志<br />(5)switch_to&nbsp;三个参数<br />进程切换一般都涉及三个进程,如进程a切换成进程b,b开始执行,但是当a恢复执行的时候往往是通过一个进程c,而不是进程b。注意switch_to的调用:&nbsp;switch_to(prev,next,prev),&nbsp;可以看到last就是prev&nbsp;调用方法如下:进程A-&gt进程B&nbsp;switch_to(A,B,A)主要有三个参数:<br />输入参数两个:prev:切换前的进程,next:切换后的进程,输出参数一个:last:切换前进程。&nbsp;&nbsp;<br />注意这三个变量都是局部变量,在系统栈中,所以切换到另一进程后变量的值不会改变。<br />进程a切换b之前,eax的值为prev,也就是A;edx的值为next,也就是B,eax的值为last,也就是A<br />当不考虑第三个参数时,从C切换成A,内核栈切换成A的栈,这时A中的prev和nexxt分别指向A和B,进程C的引用丢失了。这时第三个参数就派上用场了。C切换进程A后,将C存入eax中,切换到A后,由于输出部&quot;=a&quot;&nbsp;(last)会将eax的值写入last中,也就是prev中,所以此时prev和next的值就是C和B了。<br />(6)汇编参数的传递?<br />汇编是通过寄存器传递参数的,这里用了eax和edx,这样jmp就和call差不多了,但是jmp和call的区别是,call会有硬件自动压栈一些寄存器的,比如cs:ip&nbsp;,这里是通过手工压栈ip,模拟了call,在__switch_to中,用return&nbsp;返回。我们又会想到为什么不用call呢?原因是进入__switch_to后,我们是为运行别的进程做准备,也就是说当返回的时候应该是运行next进程而不是当前进程。如果用call的话,压栈的是当前进程的ip,那么__switch_to返回后就运行当前进程了,这与我们想运行next的进程的想法是不一致的,因此,我们手工压栈的是next进程的ip,那么,当__switch_to返回后自然出栈的就是next的ip,也就是运行next进程了。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

7

主题

94

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部

7

主题

94

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部