免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 9112 | 回复: 6

linux2.6.29 swtich_to 详细分析(一) [复制链接]

论坛徽章:
2
2015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:57:09
发表于 2009-05-05 08:52 |显示全部楼层
linux内核进程切换最重要的一个部分就是宏定义switch_to,下面从几个方面来详细讲解一下:
(1)内嵌汇编
(2)memory 破坏描述符(编译器优化)
(3)进程切换的标志是什么?
(4)堆栈切换的标志是什么?
(5)为什么switch_to 提供了三个参数?
(6)汇编参数的传递?

带着这几个问题,先来大体浏览一下代码

#define switch_to(prev, next, last)     \
do {         \
/*        \
  * Context-switching clobbers(彻底击败) all registers, so we clobber \
  * them explicitly, via unused output variables.  \
  * (EAX and EBP is not listed because EBP is saved/restored \
  * explicitly for wchan access and EAX is the return value of \
  * __switch_to())      \
  */        \
unsigned long ebx, ecx, edx, esi, edi;    \
         \
asm volatile("pushfl\n\t"  /* save    flags */ \
       "pushl %%ebp\n\t"  /* save    EBP   */ \
       "movl %%esp,%[prev_sp]\n\t" /* save    ESP   */ \
       "movl %[next_sp],%%esp\n\t" /* restore ESP   */ \
       "movl $1f,%[prev_ip]\n\t" /* save    EIP   */ \
       "pushl %[next_ip]\n\t" /* restore EIP   */ \
       "jmp __switch_to\n" /* regparm call  */ \
       "1:\t"      \
       "popl %%ebp\n\t"  /* restore EBP   */ \
       "popfl\n"   /* restore flags */ \
         \
       /* output parameters */                       \
       : [prev_sp] "=m" (prev->thread.sp),  \
       /*m表示把变量放入内存,即把[prev_sp]存储的变量放入内存,最后再写入prev->thread.sp*/\
         [prev_ip] "=m" (prev->thread.ip),  \
         "=a" (last),                                           \
         /*=表示输出,a表示把变量last放入ax,eax = last*/         \
         \
         /* clobbered output registers: */  \
         "=b" (ebx), "=c" (ecx), "=d" (edx),  \
         /*b 变量放入ebx,c表示放入ecx,d放入edx,S放入si,D放入edi*/\
         "=S" (esi), "=D" (edi)    \
                \
         /* input parameters: */    \
       : [next_sp]  "m" (next->thread.sp),  \
       /*next->thread.sp 放入内存中的[next_sp]*/\
         [next_ip]  "m" (next->thread.ip),  \
                \
         /* regparm parameters for __switch_to(): */ \
         [prev]     "a" (prev),    \
         /*eax = prev  edx = next*/\
         [next]     "d" (next)    \
         \
       : /* reloaded segment registers */   \
   "memory");     \
} while (0)

以上代码,主要是内嵌汇编,这里先简单介绍一下:
     1 内嵌汇编语法
__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));

__asm__ __violate__("指令模板" : 输出部 : 输入部);

“movl %1,%0”是指令模板;“%0”和“%1”代表指令的操作数,称为占位符,内嵌汇
编靠它们将C 语言表达式与指令操作数相对应。指令模板后面用小括号括起来的是C语言表
达式,本例中只有两个:“result”和“input”,他们按照出现的顺序分别与指令操作数“%0”,
“%1”对应;注意对应顺序:第一个C 表达式对应“%0”;第二个表达式对应“%1”,依次类
推,操作数至多有10 个,分别用“%0”,“%1”….“%9”表示
。在每个操作数前面有一个
用引号括起来的字符串,字符串的内容是对该操作数的限制或者说要求。“result”前面的
限制字符串是“=r”,其中“=”表示“result”是输出操作数,“r”表示需要将“result”
与某个通用寄存器相关联,先将操作数的值读入寄存器,然后在指令中使用相应寄存器,而
不是“result”本身,当然指令执行完后需要将寄存器中的值存入变量“result”,从表面
上看好像是指令直接对“result”进行操作,实际上GCC做了隐式处理,这样我们可以少写
一些指令。“input”前面的“r”表示该表达式需要先放入某个寄存器,然后在指令中使用
该寄存器参加运算。
(2)memory 破坏描述符(编译器优化)
内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存
Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没
有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度。以上是硬件
级别的优化。再看软件一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器
进行优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用
CPU指令流水线,常见的是重新排序读写指令。
对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬
件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序
执行的操作之间设置内存屏障(memory barrier),linux 提供了一个宏解决编译器的执行
顺序问题。
void Barrier(void)
这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器
中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
Memory描述符告知GCC:
l  1)不要将该段内嵌汇编指令与前面的指令重新排序;也就是在执行内嵌汇编代码
之前,它前面的指令都执行完毕
l  2)不要将变量缓存到寄存器,因为这段代码可能会用到内存变量,而这些内存变
量会以不可预知的方式发生改变,因此GCC插入必要的代码先将缓存到寄存器的变
量值写回内存,如果后面又访问这些变量,需要重新访问内存。
如果汇编指令修改了内存,但是GCC 本身却察觉不到,因为在输出部分没有描述,此时
就需要在修改描述部分增加“memory”,告诉GCC 内存已经被修改,GCC 得知这个信息后,
就会在这段指令之前,插入必要的指令将前面因为优化Cache 到寄存器中的变量值先写回内
存,如果以后又要使用这些变量再重新读取。
(3)进程切换的标志-----sp指针的切换
    因为进程切换也就是进程描述符的切换,现在让我们来想一下我们是如何定位某个进程描述符的地址的,看下面的汇编代码:
   mov $0xffffe000,%ecx
   andl %esp,%ecx
   movl %ecx,p

执行上面代码后,p中即存储当前运行进程的thread_info结构的地址,但是我们最长用的是进程描述符的地址,因此内核设计了current宏来计算指向进程描述符的指针:
mov $0xfffe000,%ecx
andl %esp,%ecx
movl (%ecx),p
因为task字段在thread_info中的偏移量为0,所以执行上述三条指令后,p即是当前运行的进程的描述符指针。
      我们可以看到,只要知道esp,那么,进程就唯一确定了,所以说esp是进程切换的标志
(4)堆栈切换的标志 --- ebp (栈低指针)
    毋庸置疑,栈底指针肯定是堆栈切换的标志
(5)switch_to 三个参数
进程切换一般都涉及三个进程,如进程a切换成进程b,b开始执行,但是当a恢复执行的时候往往是通过一个进程c,而不是进程b。注意switch_to的调用: switch_to(prev,next,prev), 可以看到last就是prev 调用方法如下:进程A->进程B switch_to(A,B,A)主要有三个参数:
输入参数两个:prev:切换前的进程,next:切换后的进程,输出参数一个:last:切换前进程。  
注意这三个变量都是局部变量,在系统栈中,所以切换到另一进程后变量的值不会改变。
进程a切换b之前,eax的值为prev,也就是A;edx的值为next,也就是B,eax的值为last,也就是A
当不考虑第三个参数时,从C切换成A,内核栈切换成A的栈,这时A中的prev和nexxt分别指向A和B,进程C的引用丢失了。这时第三个参数就派上用场了。C切换进程A后,将C存入eax中,切换到A后,由于输出部"=a" (last)会将eax的值写入last中,也就是prev中,所以此时prev和next的值就是C和B了。
(6)汇编参数的传递?
汇编是通过寄存器传递参数的,这里用了eax和edx,这样jmp就和call差不多了,但是jmp和call的区别是,call会有硬件自动压栈一些寄存器的,比如cs:ip ,这里是通过手工压栈ip,模拟了call,在__switch_to中,用return 返回。我们又会想到为什么不用call呢?原因是进入__switch_to后,我们是为运行别的进程做准备,也就是说当返回的时候应该是运行next进程而不是当前进程。如果用call的话,压栈的是当前进程的ip,那么__switch_to返回后就运行当前进程了,这与我们想运行next的进程的想法是不一致的,因此,我们手工压栈的是next进程的ip,那么,当__switch_to返回后自然出栈的就是next的ip,也就是运行next进程了。

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
发表于 2009-05-05 09:25 |显示全部楼层
不错,呵呵

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
发表于 2009-05-05 10:57 |显示全部楼层
LZ这几天发表了不少好文章,多谢分享。

论坛徽章:
0
发表于 2011-05-06 16:18 |显示全部楼层
顶楼主

论坛徽章:
0
发表于 2011-05-06 16:31 |显示全部楼层

论坛徽章:
0
发表于 2011-05-06 16:37 |显示全部楼层
学习。。。。。。

论坛徽章:
0
发表于 2011-05-07 20:48 |显示全部楼层
很有启发的
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP