Linux进程上下文切换

转载请注明:【转载自博客xelatex KVM】,并附本文链接。谢谢。


Process Context Switch in Linux Kernel
 
Basic call path of process scheduling in Linux  (start from kernel/sched.c):
    schedule()->context_switch()->switch_to()->__switch_to()
 
When switching process context, two main parts should be taken into consideration:
  1. switch global page table 
  2. switch kernel stack and hardware context.
These steps are done in context_switch(), switch_to() and __switch_to(). switch_to() switches kernel stack and __switch_to() handles hardware context switch.
 
Here is the source code of switch_to():

/*
* Saving eflags is important. It switches not only IOPL between tasks,
* it also protects other tasks from NT leaking through sysenter etc.
*/
#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   */    \
             __switch_canary                    \
             "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),        \
               [prev_ip] "=m" (prev->thread.ip),        \
               "=a" (last),                    \
                                    \
               /* clobbered output registers: */        \
               "=b" (ebx), "=c" (ecx), "=d" (edx),        \
               "=S" (esi), "=D" (edi)                \
                                           \
               __switch_canary_oparam                \
                                    \
               /* input parameters: */                \
             : [next_sp]  "m" (next->thread.sp),        \
               [next_ip]  "m" (next->thread.ip),        \
                                           \
               /* regparm parameters for __switch_to(): */    \
               [prev]     "a" (prev),                \
               [next]     "d" (next)                \
                                    \
               __switch_canary_iparam                \
                                    \
             : /* reloaded segment registers */            \
            "memory");                    \
} while (0)

We can see 3 things are done in function switch_to(): 
  1. switch %esp
  2. hardware context switch (__switch_to())
  3. stack switch. 
This piece of code is so excellent and well designed, and the functionalities are clearly described by comments. We should notice that when calling __switch_to(), %[next_ip] is pushed into stack so the %eip of next process is used as __switch_to()'s param. Then we can see the next line ("1:\t") is a symbol, which is used as %[prev_ip] (in line "movl %1f, %[prev_ip]\n\t"). After switching to the new context, %eip is stored here. When new process starts, %ebp is poped and flags are restored. If the process we switch to is a new process, %[next_ip] will be the address of ENTRY(ret_from_fork) (in file arch/x86/kernel/entry_32.S), so we cannot use "call __switch_to" instead of "jmp __switch_to" because "call" will push the address of the following code into stack and we can't get the address of ret_from_fork in %[next_ip]. Here will do "pushl" operation manually.

### Linux 进程上下文切换原理 在 Linux 系统中,进程上下文切换是指当 CPU 需要从一个进程切换到另一个进程时所执行的一系列操作。这一过程涉及保存当前运行进程的状态以及恢复下一个待运行进程的状态[^1]。 具体来说,上下文切换的过程包括以下几个方面: - **寄存器状态保存**:为了确保被暂停的进程能够在其下次调度时继续执行,操作系统需要保存该进程的所有寄存器状态。 - **程序计数器更新**:程序计数器指向即将执行的指令地址,在上下文切换过程中也需要进行相应的调整。 - **内存管理单元 (MMU)**:如果涉及到不同进程之间的切换,则还需要刷新 MMU 的页表映射关系,以便新进程访问其对应的虚拟地址空间[^2]。 这些复杂的操作使得频繁发生上下文切换成为影响系统整体性能的重要因素之一。 ### 解决与优化方法 针对因高频率上下文切换而导致的性能瓶颈问题,可以从多个角度出发来改善情况: #### 减少不必要的线程/进程创建 过多活跃的同时存在的轻量级单位如线程或重载型实体像传统意义上的独立流程都会增加竞争条件下的切换开销。因此合理规划应用架构设计减少此类对象实例数量至关重要。 #### 使用无锁数据结构 对于多线程环境中的共享资源访问控制机制而言,默认采用互斥锁可能会引发额外等待从而加剧上下文转换现象;改用基于原子操作构建而成无需依赖显式锁定逻辑的数据容器则可以在一定程度上缓解上述矛盾局面。 #### 调整工作负载分配策略 通过分析实际业务场景需求重新审视现有任务分发模式是否存在不合理之处进而作出相应修正措施比如适当降低并发度水平等手段均有助于削减总体上的切换成本支出比例。 ```bash # 查看系统的上下文切换次数 vmstat 1 5 | awk 'NR>2 {print $9}' ``` 以上命令可以帮助监控一段时间内的平均每秒上下文切换次数,便于后续针对性地实施改进方案。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值