linux 网卡 switch hardware,进程切换,新老Linux版本的对比

看Linux

Cod会比较难以理解一些机制,其实是因为很多行为CPU自动做掉了,这里列举一下相关的行为,目前以X86为例:

(一) 老版本的Linux的进程切换

X86的任务切换是通过Jmp到一个任务门描述符(TSS)去完成的。其中任务门描述符中的基地址,指向了一个tss的数据结构,其中存放了所有寄存器的值,以及内核态,用户态栈的信息,页目录表的信息(CR3),以及位图信息,在任务切换时,CPU会自动SAVE当前进程的所有信息至TSS,同样也会加载新进程的TSS信息,所以,重新加载页表,以及内核态,用户态堆栈信息和位图信息,都是CPU完成的。

在早期版本的Linux中,每个任务有自己的TSS, 所以进程上下文会保存在TSS里,但这样严重影响了进程的数目。

(二)新版本的Linux的进程切换(V2.4以后)

进程切换(process

switch),作为抢占式多任务OS中重要的一个功能,其实质就是OS内核挂起正在运行的进程A,然后将先前被挂起的另一个进程B恢复运行。

硬件上下文

每个进程都有自己的地址空间,但是所有进程在物理上共享着CPU的寄存器,因此,当恢复一个进程执行前,OS内核必须要将挂起该进程时寄存器的值装入CPU寄存器。进程恢复执行前必须装入寄存器的一组数据就叫做“硬件上下文”(hardware

context),它是进程执行上下文的子集,后者是进程执行时需要的所有信息(如地址空间中的数据等)。

Linux中,TSS保存着部分的进程的硬件上下文(如ss、esp等寄存器的值),剩余部分保存在内核堆栈中(如eax、ebx等通用数据寄存器的值)。

进程切换只发生在内核态,在进程切换之前,用户态使用的寄存器内容都已保存在内核堆栈上,如ss、esp等。

任务状态段(TSS)

80x86体系结构中有个特殊的段——TSS,用来存放硬件上下文。Linux为每个CPU分配一个TSS。这样,当一个CPU从用户态切换到内核态时,就从TSS中得到内核态的堆栈地址,如果用户态程序试图用in或out指令访问I/O设备时,CPU就可以访问在TSS中的I/O许可位图(I/O

Permission Bitmap)来检查该操作是否合法。

tss_struct结构描述TSS的格式。系统中有一个全局数组——init_tss,里面保存着每个不同CPU的TSS(n个CPU就有n个TSS)。由此可见,TSS表示了CPU上当前进程的信息,没有必要为每个进程都分配TSS。

Linux创建的TSSD(任务状态段描述符)存放在GDT中,GDT的基地址保存在每个CPU的gdtr寄存器里。每个CPU的tr寄存器里有相应的TSS的TSSD的选择子,这可以用来在GDT中定位TSSD,从而得到TSS。CPU中有两个不可编程的寄存器,存放TSSD的Base字段和Limit字段,这样CPU可以快速地对TSS寻址,而不需经过GDT。

因为Linux为每个CPU分配TSS,而不是每个进程分配TSS,因此,被替换的进程的硬件上下文必须保存在别处,不能存在TSS中。每个进程描述符中有一个字段thread——一个thread_struct类型的字段,使用它可以保存部分硬件上下文。该结构中包含了大部分的CPU寄存器(如esp、eip等),但不包含eax、ebx之类的通用寄存器,因为它们保存在进程内核堆栈中。

执行进程切换

进程切换发生在schedule()函数中。进程切换分为两个步骤:

切换页全局目录(Page Global

Directory)来加载一个新的地址空间,实际上就是加载新进程的cr3寄存器值。

切换内核堆栈和硬件上下文,这些包含了内核执行一个新进程的所有信息,包含了CPU寄存器。

现在假设prev表示即将被替换的进程的描述符,next表示即将执行的进程的描述符。其实,prev和next都是schedule()函数的局部变量。

switch_to宏

这里讨论进程切换的第2个步骤,该步骤通过switch_to宏来实现。

switch_to宏它有三个参数:prev、next、last。prev和next不需要解释,last参数是干什么的呢?实际上,任何进程切换涉及3个进程,不仅仅是2个。

假设内核决定将进程A挂起,执行进程B,那么在schedule()函数中,prev就是进程A的描述符地址,next就是进程B的描述符地址,一旦switch_to挂起A,那么进程A就冻结了。后来,当内核想重新执行进程A,它必须通过switch_to宏来挂起进程C(通常不是进程B),此时prev代表C、next代表A。当A恢复执行,它得到它原来的内核堆栈,在这个原来的内核堆栈里,prev代表A,next代表B。此时,代表进程A的内核代码失去了对进程C的引用,就找不到进程C了。事实证明,这个引用对于完成进程切换是有用的。

switch_to的last参数是一个输出参数,表示宏把进程C的描述符地址写在内存的某个地址(这是在A恢复执行后完成的)。在进程切换之前,switch_to把prev的值写入eax。在A恢复执行后,此时还是在switch_to宏代码中,A得到它原来的内核堆栈,prev是A的描述符地址,注意,因为CPU内eax寄存器的值不会因为切换而变化,因此,eax里存的是进程C的描述符地址,switch_to会将eax的值写入到last中,原来last指向进程A的prev就被C的描述符地址覆盖了。

关于switch_to宏的分析,请见下一篇。

__switch_to函数

switch_to宏里有一句“jmp

__switch_to”,即跳转到__switch_to函数开始执行。该函数完成进程切换第2步骤的大部分工作。该函数是FASTCALL调用方式(利用关键字__attribute__(regparm(3))声明),因此参数用通用数据寄存器传递——eax传递prev_p、edx传递next_p。

关于__switch_to函数的分析,请见下一篇。

保存和加载FPU、MMX和XMM寄存器

从Intel

80486DX开始,FPU(算术浮点单元)被集成到了CPU中,浮点算术功能用ESCAPE指令来执行,操纵CPU中的浮点寄存器集。显然,当一个进程正在使用ESCAPE指令,那么浮点寄存器的内容就属于它的硬件上下文。

为了加速多媒体程序的执行,Intel在微处理器中引入了新的指令集——MMX,MMX指令也作用于FPU的浮点寄存器。这样,MMX就不能和FPU指令混用,但是OS内核就可以忽略新的MMX指令集,因为保存浮点寄存器的功能代码也能够应用于MMX的状态。

MMX使用SIMD(单指令多数据)流水线,Pentium

III增强了这种SIMD能力,引入SSE(Streaming SIMD

Extensions)扩展。该功能增强了8个128位寄存器(XMM寄存器)的功能,这些寄存器不和FPU/MMX寄存器重叠,因此能够与FPU/MMX指令混用。

Pentium

IV还引入了SSE2扩展,支持高精度浮点值,SSE2和SSE使用同一个XMM寄存器组。

80x86微处理器不在TSS中保存FPU、MMX和XMM寄存器的值,不过还是提供了一些支持,能够在需要时保存它们。cr0寄存器有一个TS(Task-Switching)标志位,每当执行硬件上下文切换时,TS置位,每当TS被置位后进程执行ESCAPE、MMX、SSE或SSE2指令,控制器就产生一个“Device

not

available”异常。这样,TS标志位就能够让OS内核只有在真正需要时才保存或恢复FPU、MMX和XMM寄存器。

假设进程A使用了数学协处理器,那么当进程A被切换出去的时候,内核设置TS并将浮点寄存器的内容保存到进程A的TSS中(原著这么写,但是应该是保存到进程A描述符的一个字段中,TSS是与CPU关联的,进程没有TSS)。

如果新进程B不使用数学协处理器,那么内核就不需要恢复浮点寄存器的内容,但是,一旦进程B执行FPU、MMX等指令,CPU就产生一个“Device

not available”异常,相应的异常处理程序就会用保存在进程B中的相关值来恢复浮点寄存器。

处理FPU、MMX和XMM寄存器的数据结构存放在进程描述符的thread字段的i387子字段中(即thread.i387),由i387_union联合体描述,其格式如下:

union i387_union {structi387_fsave_struct fsave;structi387_fxsave_struct fxsave;structi387_soft_struct soft;};

此外,进程描述符中还包含了两个附加的标志:

thread_info结构中status字段的TS_USEDFPU标志,表示进程当前执行过程中是否使用过FPU、MMX和XMM寄存器。

task_struct结构的flags字段的PF_USED_MATH标志,表示thread.i387的内容是否有意义。

保存和加载FPU、MMX和XMM寄存器主要用到__unlazy_fpu宏,该宏在__switch_to函数中使用,下一篇会对其进行分析。

内核态使用FPU、MMX和XMM寄存器

OS内核也可以使用FPU、MMX和XMM寄存器,当然,这么做的时候应该避免干扰用户态进程。因此,Linux使用如下方法来解决:

在内核使用协处理器之前,如果用户态进程使用了FPU(TS_USEDFPU标志为1),内核就要调用kernel_fpu_begin()函数,该函数里又调用save_init_fpu()来保存寄存器内容,然后重新设置cr0寄存器的TS标志。

使用完协处理器之后,内核调用kernel_fpu_end宏设置cr0寄存器的TS标志。

当用户态进程恢复执行时,math_state_restore()函数将恢复FPU、MMX和XMM寄存器的内容。

需要注意的是,如果当前用户态进程有在用数学协处理器时,kernel_fpu_begin()函数的执行时间比较长,甚至无法通过FPU、MMX或XMM达到加速的目的。因此,内核只在有限的场合使用FPU、MMX或XMM指令,比如移动或清除大内存区字段、计算校验和等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值