您好,欢迎访问三七文档
当前位置:首页 > 行业资料 > 其它行业文档 > Linux进程调度切换和虚拟空间管理深入分析
一、Linux进程切换深入分析#defineCLONE_KERNEL(CLONE_FS|CLONE_FILES|CLONE_SIGHAND)创建内核线程时使用的CLONE标志。1.#defineunlikely(x)__builtin_expect(!!(x),0)编译器优化,实际返回值x是整型表达式,0表示并不预期该事件发生,也就是说x为0的可能性很小,这是为了让编译器对下面得语句进行优化。2.进程内核态堆栈结构:进程是动态实体,进程描述符是存放在动态内存中的。在一块进程内存区上,Linux存放了两个数据结构:指向task_struct得thread_info和内核态的进程栈。大小一般2页8K,这要求页面帧对齐2的13次幂,在X86上编译时可以配置大小为4K。thread_info在内存区开始处,内核栈从内存尾向下增长。在C语言中可以用union结构表示:图1.8K内核栈和进程描述符task_struct及thread_info的相互关系unionthread_union{structthread_infothread_info;unsignedlongstack[2048];/*1024for4KBstacks*/};CPU的esp寄存器用于执行堆栈的顶部指针,当从用户态转向内核态时,进程内核栈总是空的,所以esp就会执行堆栈底部。使用alloc_thread_info和free_thread_info用于分配和释放一个存放thread_info结构和内核堆栈的内存区。内核通过当前esp指针可以很方便的得到thread_info结构的地址。current_thread_info(void)的原理即如下:movl$0xffff2000,%ecx/*or0xfffff000for4KBstacks*/andl%esp,%ecxmovl%ecx,pthread_info中task指针是第一个,所以current宏相当于current_thread_info()-task,从而也就得到task指针。每个进程有自己独立得进程空间,所有进程共享CPU寄存器。进程继续执行时必须装入寄存器恢复得数据集称为硬件上下文环境。在Linux中部分硬件上下文存放在进程描述符中,部分存放到内核态堆栈里。3.进程切换堆栈原理:每个进程有自己独立得进程空间,所有进程共享CPU寄存器。进程继续执行时必须装入寄存器恢复得数据集称为硬件上下文环境。在Linux中部分硬件上下文存放在进程描述符中,部分存放到内核态堆栈里。80x86体系支持在进程TSS段跳转时自动执行进程硬件上下文切换。Linux使用软件方法实现。软件方式效率差不多,当更灵活,可以控制流程,留下优化空间。80x86用TSS段保存硬件上下文内容,每个CPU有一个TSS段。从用户态到内核态切换时,从TSS中取出内核栈地址。用户态进程访问I/O端口时,TSS中的I/O访问位图可以验证权限。tss_struct描述了TSS格式,init_tss存放初始TSS内容,每次进程切换,内核更新TSS中的某些字段,以反映当前运行进程的权限等级。每个进程有个反映任务CPU状态的thread_struct结构变量thread,除eax、ecx等通用寄存器内容保存在内核态堆栈中,其他大部分寄存器都保存在次结构中。该结构一部分对应于tss_struct中的内容,进程切换时把thread中某些内容更新到tss_struct中就可以反映当前任务的运行CPU环境。structtss_struct{unsignedshortback_link,__blh;unsignedlongesp0;unsignedshortss0,__ss0h;unsignedlongesp1;unsignedshortss1,__ss1h;/*ss1isusedtocacheMSR_IA32_SYSENTER_CS*/unsignedlongesp2;unsignedshortss2,__ss2h;unsignedlong__cr3;unsignedlongeip;unsignedlongeflags;unsignedlongeax,ecx,edx,ebx;unsignedlongesp;unsignedlongebp;unsignedlongesi;unsignedlongedi;unsignedshortes,__esh;unsignedshortcs,__csh;unsignedshortss,__ssh;unsignedshortds,__dsh;unsignedshortfs,__fsh;unsignedshortgs,__gsh;unsignedshortldt,__ldth;unsignedshorttrace,io_bitmap_base;/**Theextra1istherebecausetheCPUwillaccessan*additionalbytebeyondtheendoftheIOpermission*bitmap.Theextrabytemustbeall1bits,andmust*bewithinthelimit.*/unsignedlongio_bitmap[IO_BITMAP_LONGS+1];/**Cachethecurrentmaximumandthelasttaskthatusedthebitmap:*/unsignedlongio_bitmap_max;structthread_struct*io_bitmap_owner;/**padstheTSStobecacheline-aligned(sizeis0x100)*/unsignedlong__cacheline_filler[35];/**..andthenanother0x100bytesforemergencykernelstack*/unsignedlongstack[64];}__attribute__((packed));structthread_struct{/*cachedTLSdescriptors.*/structdesc_structtls_array[GDT_ENTRY_TLS_ENTRIES];unsignedlongesp0;unsignedlongsysenter_cs;unsignedlongeip;unsignedlongesp;unsignedlongfs;unsignedlonggs;/*Hardwaredebuggingregisters*/unsignedlongdebugreg[8];/*%%db0-7debugregisters*//*faultinfo*/unsignedlongcr2,trap_no,error_code;/*floatingpointinfo*/unioni387_unioni387;/*virtual86modeinfo*/structvm86_struct__user*vm86_info;unsignedlongscreen_bitmap;unsignedlongv86flags,v86mask,saved_esp0;unsignedintsaved_fs,saved_gs;/*IOpermissions*/unsignedlong*io_bitmap_ptr;unsignedlongiopl;/*maxallowedportinthebitmap,inbytes:*/unsignedlongio_bitmap_max;};4.进程切换流程解析switch_to进程切换本质上两步:1)进程页表PGD切换;2)内核态堆栈和硬件上下文切换(包括CPU寄存器);上面两步通过context_switch()实现,它通过调用switch_mm()切换进程空间,switch_to切换内核上下文环境。首先看看context_switch()做了些什么:1)进程描述符中active_mm执行进程使用的地址空间,mm执行进程拥有的地址空间,对于普通进程它们相同。对于内核线程,它的mm总为NULL。所以context_switch()首先判断if(!next-mm)即next为内核线程,则使用prev的进程地址空间:if(!next-mm){next-active_mm=prev-active_mm;atomic_inc(&prev-active_mm-mm_count);enter_lazy_tlb(prev-active_mm,next);}2)否则,如果next是普通进程,则用next进程空间替换prev的地址空间:switch_mm(oldmm,mm,next);3)如果prev是内核线程或者正在退出,则设置prev-active_mm和runqueue的prev_mm为NULL:if(!prev-mm){prev-active_mm=NULL;WARN_ON(rq-prev_mm);rq-prev_mm=oldmm;}下面看看switch_mm()如何切换进程空间:1)获取cpu逻辑号。2)cpu_clear(cpu,prev-cpu_vm_mask)清除cpu_vm_mask位标志。3)per_cpu(cpu_tlbstate,cpu).state=TLBSTATE_OK设置cpu_tlbstate状态。4)per_cpu(cpu_tlbstate,cpu).active_mm=next设置cpu_tlbstate的active_mm为next。5)cpu_set(cpu,next-cpu_vm_mask)设置next的cpu_vm_mask标志。6)load_cr3(next-pgd)装载next的pgd页表到cr3寄存器。7)如果next的LDT描述符改变,则加载next的LDT描述符。if(unlikely(prev-context.ldt!=next-context.ldt))load_LDT_nolock(&next-context);最后,switch_to进行内核堆栈和CPU环境切换操作:#defineswitch_to(prev,next,last)do{\unsignedlongesi,edi;\asmvolatile(pushfl\n\t/*Saveflags*/\pushl%%ebp\n\t\movl%%esp,%0\n\t/*saveESP*/\movl%5,%%esp\n\t/*restoreESP*/\movl$1f,%1\n\t/*saveEIP*/\pushl%6\n\t/*restoreEIP*/\jmp__switch_to\n\1:\t\popl%%ebp\n\t\popfl\:=m(prev-thread.esp),=m(prev-thread.eip),\=a(last),=S(esi),=D(edi)\:m(next-thread.esp),m(next-thread.eip),\2(prev),d(next));\}while(0)流程描述,prev是进程A的task结构,next是进程B的task结构,last是进程C的结构:1)保存prev和next指针的值到eax和edx:movlprev,%eaxmovlnext,%edx2)保存eflags和ebp寄存器内容到prev内核态堆栈中:pushflpushl%ebp3)将esp内容保存到prev-thread.esp中,该字段执行prev内核堆栈的top地址。movl%esp,484(%eax)4)将next-thread.esp加载到esp中,现在开始,esp执行next的内核堆栈,进程切换完成。movl484(%edx),%esp5)保存下面Label1到prev-thread.eip指针中,当prev进程恢复运行时,从该位置开始运行。movl$1f,480(%eax)6)将next-thread.eip的指针内容压到n
本文标题:Linux进程调度切换和虚拟空间管理深入分析
链接地址:https://www.777doc.com/doc-24317 .html