在进程进行切换时,需要进行上下文切换,也就是寄存器的值的保存。
对于上下文的切换,有两种情况:硬件自动(隐式)完成、操作系统显式保存。
下面分析两段xv6的源代码,都是上下文切换部分,版本有所不同:
注:每个进程都有一个用来维护过程调用的栈,有的书把这个栈叫做内核栈;在编译原理中,这个栈的术语叫活动记录,在下文中,提到的栈都是说的这个保存活动记录的栈。每个进程都有自己的活动记录(当然,如果是多CPU的情况下,就会引入线程的概念,则每个线程有一个活动记录)
较老的版本
# void swtch(struct context *old, struct context *new);
# Save current register context in old
# and then load register context from new.
.globl swtch
swtch:
# Save old registers
movl 4(%esp), %eax
popl 0(%eax) # %eip
movl %esp, 4(%eax)
movl %ebx, 8(%eax)
movl %ecx, 12(%eax)
movl %edx, 16(%eax)
movl %esi, 20(%eax)
movl %edi, 24(%eax)
movl %ebp, 28(%eax)
# Load new registers
movl 4(%esp), %eax # not 8(%esp) - popped return address above
movl 28(%eax), %ebp
movl 24(%eax), %edi
movl 20(%eax), %esi
movl 16(%eax), %edx
movl 12(%eax), %ecx
movl 8(%eax), %ebx
movl 4(%eax), %esp
pushl 0(%eax) # %eip
这个版本的上下文结构struct context的内容如下:
struct context {
int eip; //程序计数器
int esp; //栈指针
int ebp;
int ecx;
int edx;
int esi;
int edi;
int ebp;
分析一下上下文切换的源代码:
源代码的上半段是将“老”的进程的上下文保存,下半段将“新”的进程(要切换到的)的上下文恢复。
执行swtch的指令时,栈的状态:
movl 4(%esp), %eax
将old(参数)的值放入寄存器%eax。
popl 0(%eax)
然后将栈顶的值保存到0(%eax)的位置。也就是将返回地址(%eip)的值保存到old指向的context空间中
之后,就是将其他的寄存器放入old指向的context空间中。
通过上面的源码,可以得到context在内存中分配空间的方式:
下面的代码就是从new指向的context空间中,将各个寄存器的值恢复,就不详细说明了。
较新的版本:就像一开始提到的,硬件会自动保存一部分
硬件会保存一部分的寄存器,还有剩余的需要进行手动保存。
# Context switch
# void swtch(struct context **old, struct context *new);
# Save the current registers on the stack, creating
# a struct context, and save its address in *old.
# Switch stacks to new and pop previously-saved registers.
.globl swtch
swtch:
movl 4(%esp), %eax
movl 8(%esp), %edx
# Save old callee-saved registers
pushl %ebp
pushl %ebx
pushl %esi
pushl %edi
# Switch stacks
movl %esp, (%eax)
movl %edx, %esp
# Load new callee-saved registers
popl %edi
popl %esi
popl %ebx
popl %ebp
开始的两条代码,将old与new放入%eax和%edx,方便后面通过指针的方式访问内存。
接下来的四条压栈指令,直接将要手动保存的寄存器压入栈中。
执行到这里,“老”进程和“新”进程对应的栈的示意图如下:
与之前旧版本的有些许不同,新版本的直接将上下文保存在进程自己的栈中;旧版本的保存在了其他的地方(暂时就理解到这个程度,由于没有分析过所有的源码,这里的分析可能比较片面,但这是我整个的分析过程,如有有大佬看出哪里不会,请指正)
进程切换过程之一:上下文切换注:下面给出的源码均出自https://github.com/mit-pdos/xv6-public在进程进行切换时,需要进行上下文切换,也就是寄存器的值的保存。对于上下文的切换,有两种情况:硬件自动(隐式)完成、操作系统显式保存。下面分析两段xv6的源代码,都是上下文切换部分,版本有所不同:注:每个进程都有一个用来维护过程调用的栈,有...
1. CPU上下文切换到底是个什么东西
文章目录1. CPU上下文切换到底是个什么东西1.1. CPU上下文1.2. CPU上下文切换1.2.1. 进程上下文切换1.2.2. 线程上下文切换1.2.3. 中断上下文切换
第一节,我们了解到了平均负载是个什么东西,并且通过三个案例展示了不同场景下(cpu密集型场景,io密集型场景,大量进程场景)的平均负载升高的分析方法其中,多个进程竞争cpu的问题经常会被我们忽略;多个进程竞争cpu的时候,会发生频繁的cpu上下文切换
1.1. CPU上下文
CPU寄存器,是cpu内置的容量小,但是速度极快的存储器件.而程序计数器,是用来存储CPU正在执行的执行
vmstat:是查看系统的整体上下文切换情况,想看具体的每一个进程的情况,需要pidstat工具。
如果不知道参数指令的意思,可以通过 man vmstat 查看具体的指令分析。
自愿上下文切换:是指由于系统资源不足,导致的上下文切花。这导致进程自动挂起,然后由系统进行管理进程的正常调度和运行。
非自愿上下文切换:是指由于时间片的时间到了,然后被系统强制调用,进而发生上下文切换。
pidstat -w -u 5
-w:进程上下文切换情况。
-u :进程cpu使用情况
5:是每五秒输出一组数据。
pidstat -wt 1
-wt :输出线程的上线文切换情况。
1:每1秒输出一组数据。
type.h:用于声明一些数据类型的简化名称,和声明页表指针的数据类型。
param.h:主要用于声明基本的一些常量,包括内核栈大小等。
memlayout.h:主要用于声明一些和内存与地址相关的常量与方法,包括虚拟内存转物理内存的方法以及物理内存转虚拟内存的方法等。
defs.h:声明在之后文件中要用到的函数。
x86.h:让c代码使用特殊的x86汇编的一些函数包括outb等,并声...
支持多任务处理是CPU设计史上最大的跨越之一。在计算机中,多任务处理是指同时运行两个或多个程序。从使用者的角度来看,这看起来并不复杂或者难以实现,但是它确实是计算机设计史上一次大的飞跃。在多任务处理系统中,CPU需要处理所有程序的操作,当用户来回切换它们时,需要记录这些程序执行到哪里。上下文切换就是这样一个过程,他允许CPU记录并恢复各种正在运行程序的状态,使它能够完成切换操作。
在上下文切
我都知道操作系统的一个重要功能就是进行进程管理,而进程管理就是在合适的时机选择合适的进程来执行,在单个cpu运行队列上各个进程宏观并行微观串行执行,多个cpu运行队列上的各个进程之间完全的并行执行。进程管理是个复杂的过程,例如进程的描述、创建和销毁、生命周期管理、进程切换、进程抢占、调度策略、负载均衡等等。本文主要关注进程管理的一个切入点,那就是进程的上下文切换,来理解linux内核是如何进程进程上下文切换的,从而揭开上下文切换的神秘面纱。
(注意:本文以linux-5.0内核源码讲解,采用arm64架构)
检查need_resched标志位,若有效则调用schedule()函数完成进程调度,schedule()会执行以下步骤:
调用pick_next_task()根据相关调度算法得到下一个待运行的进程。
调用context_switch()执行以下步骤:
调用switch_mm()将虚拟内存地址映射到待运行进程,恢复内
对于服务器的优化,很多人都有自己的经验和见解,但就我观察,有两点常常会被人忽视 – 上下文切换 和 Cache Line同步 问题,人们往往都会习惯性地把视线集中在尽力减少内存拷贝,减少IO次数这样的问题上,不可否认它们一样重要,但一个高性能服务器需要更细致地去考察这些问题,这个问题我将分成两篇文章来写:
1)从一些我们常用的用户空间函数,到linux内核代码的跟踪,来看一个上下文切换是如何产生...
上下文切换过程
上图展示了进程切换时的上下文切换的过程,首先一个用户进程通过中断或者系统调用陷入内核,此时发生用户态和内核态的切换,进程切换到自己的内核栈,再接着切换到上下文切换到调度器专属的内核栈,最后从调度器的内核栈切换到另一个用户进程的内核栈,最后从该内核栈返回用户态,即完成切换到了另一个用户进程。
在如下的sched函数中,就发生了上述中的过程,在sched中调用了swtch函数,可以看到传入s
1.抢占和上下文切换
上下文切换(也就是切换进程),在schedul()函数中通过context_switch()函数处理进行两个基本工作。context_switch()函数就是执行下一个进程,并返回指向前一个进程的进程结构的指针。context_switch()中的两个主要函数一个是切换虚拟内存映射,另外一个是切换进程/线程的结构。
第一个是由函数switch_mm()完成,该函数使用了许...