免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5589 | 回复: 5
打印 上一主题 下一主题

Linux进程:Linux切换机制主流程 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-05-22 16:30 |只看该作者 |倒序浏览
Linux切换并没有使用X86CPU的切换方法,Linux切换的实质就是cr3切换(内存空间切换,在switch_mm函数中)+ 寄存器切换(包括EIP,ESP等,均在switch_to函数中)。这里我们讲述下switch_to主流程:
  • 在switch_mm函数中将new_task->pgd设置到cr3寄存器中,实现页表切换,由于每个进程3-4G的页表映射机制完全一样(从内核页表中直接复制过来的),故这里虽然切换了pgd,但是并无影响,只是在任务回到用户空 间中时,才会发生变化,因为每个任务在0-3G中的页表映射都是各自独立的;
  • 压入esi edi ebp到cur_task堆栈中;
  • 将esp寄存器中的值保存到cur_task.task_struct.thread.esp中,也就是将cur_task切换时的堆栈指针保存起来;
  • 将new_task.task_struct.thread.esp中的值设置到esp寄存器中,这里的new_task.task_struct.thread.esp中的值就是new_task上一次被换出时的堆栈指针,现在被恢复了,2和3结合实现了从cur_task到new_task的堆栈切换;
  • 将1f地址设置到cur_task.task_struct.thread.eip中,当下次cur_task恢复运行时,将会从1f处开始运行,下面阐述了这种原理;
  • 将new_task.task_struct.thread.eip压入到new_task的堆栈中,这里new_task.task_struct.thread.eip的值就是1f,因为从4中可知,new_task上一次被换出时,其也是和现在的cur_task类似,1f地址被设置到new_task.task_struct.thread.eip中;
  • 随后CPU跳转到__switch_to函数(注意,在__switch_to中,做了一件非常重要的事情就是让init_tss.esp0=new_task.task_struct.esp0,原因见【C.Linux进程:Linux进程管理和X86进程管理的结合】一文)中开始执行,注意这里使用的是jmp,不是call,call会pusheip,而jmp不会,由于__switch_to是函数,当CPU执行完该函数后,最后一条指令必然为iret,该指令会popeip,从5中可以知道,此时new_task堆栈中的镜像为[......., esi,edi,ebp,eip(&1f)],故popeip将值eip(&1f)设置到eip寄存器中,这样当iret执行完毕后,CPU将从eip处继续执行,也就是从1f处继续执行;;
  • 此时已经在new_task的执行环境中了,pop ebp, pop edi, popesi,回到schedule函数中,当返回用户空间中时,由于new_task用户空间的eip,ss,esp等均被从new_task的堆栈中弹出到对应寄存器中,从而new_task得以顺利执行;

switch_to中cur_task寄存器保存、new_task寄存器恢复的几个问题:
  • GCC在编译该段代码时,会注意到EAX,EBX,EDX在这里被使用,因此此处无需要显示的用汇编语句来保存这三个寄存器,GCC在编译时会自动考虑添加对这些寄存器的保护;
  • 由于在内核中所有的内核段寄存器均为统一的,因此这里无需保存ES,CS,SS,DS,FS,GS;
  • CR3(Linux中没有使用LDT)已经在前面的switch_mm处理了;
  • 由于Linux没使用TSS-previous task link field,其切换完全采用软件处理切换,故这里无需考虑TSS-previous task link field;
  • 指令EIP会保存在task.thread.eip中,ESP会保存在task.thread.esp中,EBP,ESI,EDI会用显示指令入栈保存;
  • 对于Linux而言,其仅仅使用到了CPU的4级机制中的0和3两级,使用方法如下:
    当进程正运行在用户空间时,如果此时来了个中断,CPU将会执行:从TR->GDTR[i ]->TSS中取出当前的SS0:ESP0,从IDTR->IDT[i ]中取出执行代码CS:IP,将当前所有寄存器压到SS0:ESP0堆栈中,包括进程的SS3:ESP3,随后从CS:IP处开始执行代码。当中断代码执行完毕后,内核将会从进程堆栈中,将SS3:ESP3、CS:IP弹出,从而回到用户空间重新开始执行,此时并不需要CPU主动来切换级别了;从这里可知,CPU是需要TSS中的SS0和ESP0来进行高->低级别切换的,因此进程在切换时,必须要将自己的SS0和ESP0保存到TR->GDTR[i ]->TSS的SS0和ESP0字段中去,其实,在Linux中,对于同一个CPU,所有的进程都使用一个TSS,只是在进程切换时,被切换到的进程将会把自己的ESP0保存到TSS.ESP0中去(在函数__switch_to中),那为什么不把自己的SS0也保存到TSS.SS0中呢,这是因为所有进程的SS0都是统一的,为内核的SS,而内核在初始化的时候,已经将该TSS.SS0设置为自己的SS,因此无需继续设置SS0;
  • 至于EFLAGS为什么没有保存,这点在2.6中已经纠正,即执行了pushf和popf;
  • ECX为什么没有保存,则涉及到了如下的理由:i386 ABI / function calling sequence
    Allregisters on the Intel386 are global and thus visible to both a callingand a called function. Registers %ebp, %ebx, %edi,%esi, and %esp'belong' to the calling function. In other words, a called functionmust preserve these registers' values for its caller. Remainingregisters 'belong' to the called function. If a calling function wantsto preserve such a register value across a function call, it must savethe value in its local stack frame.Some registers have assigned rolesin the standard calling sequence:
  • %esp:The stack pointerholds the limit of the current stack frame, which is the address of thestack’s bottom-most, valid word. At all times, the stack pointer shouldpoint to a word-aligned area.
  • ......
  • %ecx and%edx:Scratch registers have no specified role in the standard callingsequence. Functions do not have to preserve their values for the caller.
  • ......

Linux进程管理和X86进程管理的结合
  • 从上面描述中,我们知道X86要求每个进程都必须有自己的TSS,在每次进程切换的时候,通过对应的TR[i ]+GDTRbase找到该进程的TSS,然后保存前一个任务的所有寄存器,同时将找到的TSS中所有的寄存器值恢复到系统对应的寄存器中,从而实现进程切换。
  • 由于Linux实现进程切换的时候,并不采用该机制,但是却避不过X86这个机制,因此Linux内核设置了init_tss全局变量,在start_kernel->trap_init->cpu_init初始化时,设置了对应的GDT[i ]指向该init_tss以及TR.index=i;此后,在Linux系统运行过程中,就再也不会改变TR以及该GDT[i ],对应X86来讲,就好像永远运行着1个进程;Linux使用自身的切换机制来实现了进程的切换,这些在上面的文章中已经说明,这里不在多说。
  • 另一重要问题Linux必须面对:当从高级别切换到低级别时,会引起CPU运行级别的变化,例如从级别3到级别0,此时CPU需要获取0级别的SS0以及ESP0(例如前面描述的当进程正运行在用户空间时来了个中断范例)来恢复SS:ESP,其设计方法就是从当前的TSS->SS0:ESP0中获取。为了适应CPU的这种设计,Linux内核在每次switch_to切换进程时,都将被切换来的进程的ESP0保存到init_tss.esp0中;另外由于Linux内核的SS0始终为KERNEL_SS保持不变,故无需每次切换都将其保存到init_tss.ss0中,只需要在Linux内核初始化时将init_tss.ss0设置为KERNEL_SS就可以了;


[ 本帖最后由 frank_seng 于 2008-5-22 16:33 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2008-05-23 09:50 |只看该作者
沈兄最近写了不少东西啊

又坐个沙发.

论坛徽章:
0
3 [报告]
发表于 2008-05-23 10:31 |只看该作者
原帖由 xpl 于 2008-5-23 09:50 发表
沈兄最近写了不少东西啊

又坐个沙发.

老弟,如何知道我姓沈?

论坛徽章:
0
4 [报告]
发表于 2008-05-23 12:52 |只看该作者
你的博客上写的啊.

还有靓照

看样子,应该也是事业有成了.

论坛徽章:
0
5 [报告]
发表于 2010-10-22 14:40 |只看该作者
不错。@真棒@!

论坛徽章:
0
6 [报告]
发表于 2010-10-22 15:02 |只看该作者
特来拜读
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP