linux进程的状态转换过程是,Linux进程切换

从用户的角度来理解,进程就是程序的执行过程,例如我们在shell下敲入一个nonbuild-in的命令,我们的shell就会fork出一个进程来执行这个任务。

(1) 什么是多任务

地球人都知道,我们现在使用的Linux都是多任务的操作系统。下面就唠一唠多任务的概念。

什么是多任务?执行完一个任务再接着执行另一个任务(传说中石器时代,计算机还真是这样处理任务的)就是多任务? NO,必须多个任务“同时”执行才能算是多任务操作系统。此处的“同时”只是指在用户角度感觉是同时执行。如果只有一个CPU,显然它是没有办法同时执行两个任务的。因此,让用户感到是多个任务在同时执行才是多任务操作系统的目的。

(2)从kernel看进程

如何才能够在单个CPU上运行多个进程呢?很自然的想法就是保存老进程的状态,然后加载新进程的状态。对,Linux就是这样子实现的。那么老进程需要保存那些信息呢?让我们来看看Linux是如何完成的。

在Linux下,进程切换通常存在以下情况:

(1)进程主动放弃继续执行,例如yield系统调用和一些阻塞型的系统调用会引发进程切换,某些异常的发生也会导致进程切换的发生。

(2)由内核主动暂停进程的执行,转而执行别的进程或中断。例如中断的发生和更高优先级进程的唤醒,或者进程消耗完时间片等等因素,都会导致内核暂停动迁进程的执行。

Linux 2.4(i386平台)使如下代码进行进程切换:

//sched.c schedule()函数的部分代码

{prepare_to_switch();   //在i386体系下该函数什么也不执行{   struct mm_struct *mm = next->mm;   struct mm_struct *oldmm = prev->active_mm;   if (!mm) {    BUG_ON(next->active_mm);    next->active_mm = oldmm;    atomic_inc(&oldmm->mm_count);    enter_lazy_tlb(oldmm, next, this_cpu);   } else {    BUG_ON(next->active_mm != mm);    switch_mm(oldmm, mm, next, this_cpu);   }

if (!prev->mm) {    prev->active_mm = NULL;    mmdrop(oldmm);   }}switch_to(prev, next, prev);__schedule_tail(prev);}

此处应注意对内核线程的处理方法。判断线程是不是内核线程的有效方法就是判断PCB中的内存描述符是否为空。如果内存描述符为空,则就是内核线程;否则就是普通线程。下面函数为页表切换的程序:

static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk, unsigned cpu){if (prev != next) {   /* stop flush ipis for the previous mm */   clear_bit(cpu, &prev->cpu_vm_mask);#ifdef CONFIG_SMP   cpu_tlbstate[cpu].state = TLBSTATE_OK;   cpu_tlbstate[cpu].active_mm = next;#endif   set_bit(cpu, &next->cpu_vm_mask);   /* Re-load page tables */   load_cr3(next->pgd);/* load_LDT, if either the previous or next thread   * has a non-default LDT.   */   if (next->context.size+prev->context.size)    load_LDT(&next->context);}#ifdef CONFIG_SMPelse {   cpu_tlbstate[cpu].state = TLBSTATE_OK;   if(cpu_tlbstate[cpu].active_mm != next)    out_of_line_bug();   if(!test_and_set_bit(cpu, &next->cpu_vm_mask)) {    /* We were in lazy tlb mode and leave_mm disabled     * tlb flush IPI delivery. We must reload %cr3.    */    load_cr3(next->pgd);    load_LDT(&next->context);   }}#endif}

以上应注意对于具有相同内存描述符的管理措施,具有相同描述符只有两种情况:1. 至少其中之一是内核线程;2. 或者两个共享相同页表的普通进程。如果内存描述符相同,则页表切换不会发生。页表切换的代价是相当巨大的,TLB中的内容全部失效,这意味着更多次的访问内存甚至硬盘。此处也给我们较好的提示:在应用程序开发中,能用线程则尽量用线程来解决,这样就能将不同执行流程之间的切换代价降至最小。

以下代码为386平台下的切换代码:

#define switch_to(prev,next,last) do {      \asm volatile("pushl %%esi\n\t"      \       "pushl %%edi\n\t"      \       "pushl %%ebp\n\t"      \       "movl %%esp,%0\n\t" /* save ESP */   \       "movl %3,%%esp\n\t" /* restore ESP */ \       "movl $1f,%1\n\t"   /* save EIP */   \       "pushl %4\n\t"   /* restore EIP */ \         //将返回地址压栈,__switch_to函数返回会自动执行1后的代码       "jmp __switch_to\n"     \       "1:\t"       \            "popl %%ebp\n\t"      \       "popl %%edi\n\t"      \       "popl %%esi\n\t"      \       :"=m" (prev->thread.esp),"=m" (prev->thread.eip), \        "=b" (last)      \       :"m" (next->thread.esp),"m" (next->thread.eip), \        "a" (prev), "d" (next),     \        "b" (prev));      \} while (0)

// 进程切换,在Linux2.4中每个CPU只使用了一个TSS,因此当进程切换时,需要频繁的更改TSS。void fastcall __switch_to(struct task_struct *prev_p, struct task_struct *next_p){struct thread_struct *prev = &prev_p->thread,     *next = &next_p->thread;struct tss_struct *tss = init_tss + smp_processor_id();

unlazy_fpu(prev_p); //保存FPU,MMX,XMM寄存器

/** Reload esp0, LDT and the page table pointer:*/tss->esp0 = next->esp0;

/** Save away %fs and %gs. No need to save %es and %ds, as* those are always kernel segments while inside the kernel.*/asm volatile("mov %%fs,%0":"=m" (prev->fs));asm volatile("mov %%gs,%0":"=m" (prev->gs));

/** Restore %fs and %gs.*/loadsegment(fs, next->fs);loadsegment(gs, next->gs);

/** Now maybe reload the debug registers*/if (next->debugreg[7]){   loaddebug(next, 0);   loaddebug(next, 1);   loaddebug(next, 2);   loaddebug(next, 3);   /* no 4 and 5 */   loaddebug(next, 6);   loaddebug(next, 7);}

if (prev->ioperm || next->ioperm) {   if (next->ioperm) {    /*    * 4 cachelines copy ... not good, but not that    * bad either. Anyone got something better?    * This only affects processes which use ioperm().    * [Putting the TSSs into 4k-tlb mapped regions    * and playing VM tricks to switch the IO bitmap    * is not really acceptable.]    */    memcpy(tss->io_bitmap, next->io_bitmap,     IO_BITMAP_BYTES);    tss->bitmap = IO_BITMAP_OFFSET;   } else    /*    * a bitmap offset pointing outside of the TSS limit    * causes a nicely controllable SIGSEGV if a process    * tries to use a port IO instruction. The first    * sys_ioperm() call sets up the bitmap properly.    */    tss->bitmap = INVALID_IO_BITMAP_OFFSET;}}此处会保存FPU,MMX,XMM等寄存器,内核堆栈切换终于发生。状态段加载IO允许位图。

参考文献:1. 《Understanding Linux Kernel》2. Linux 2.4 部分源码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值