前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >操作系统如何在多处理器间切换应用程序的执行

操作系统如何在多处理器间切换应用程序的执行

原创
作者头像
潋湄
修改2024-10-21 15:59:47
890
修改2024-10-21 15:59:47
举报
文章被收录于专栏:操作系统

如果有写过多线程的小伙伴知道,如果两个程序同时向一片区域中写入数据,可能会导致写入数据交叉错乱的情况,这是因为操作系统在运行程序时,为了能够让每个进程资源都充分被调度,会定期实施切换进程操作,本文旨在从底层源码介绍操作系统如何在内核态中切换应用程序

前置知识:小伙伴们可以阅读我这两篇文章:https://cloud.tencent.com/developer/article/2459968(介绍中断),https://cloud.tencent.com/developer/article/2457403(介绍用户态与内核态的切换)

schedular调度器介绍

我们首先需要了解一下scheduler函数,操作系统在底层进行进程资源调度时,会通过定时调度函数schedular进行:

scheduler调度函数
scheduler调度函数

这个函数会在每个CPU底层定时运行,主要工作有两个:

1)更改运行状态:它会遍历当前进程的所有子进程,判断运行状态是否为就绪态(RUNNABLE),如果是,操作系统会将它的状态更改为运行态(RUNNING)

2)移交运行权:通过swtch函数更改上下文信息,这个函数后面介绍流程会具体介绍,它的主要作用是恢复上下文信息,并移交运行权给当前线程,完成进程的切换

进程切换流程——当前进程的资源调度

如果你已经看了我的用户态与内核态切换文章(https://cloud.tencent.com/developer/article/2457403),我们知道,当操作系统从用户态切换到内核态时,会在trap.c中的usertrap()函数中,调用syscall()执行程序:

usertrap处理程序
usertrap处理程序

这里面有很关键的一个点,就是在执行syscall()命令之前,操作系统会通过intr_on()开启中断,在开启中断后,当前运行的应用进程允许被CPU抢占进行资源调度,因此在操作系统调度程序时,必须开启中断,让CPU有能够进行调度的机会,而在下一个else if判断中,会判断返回的dev中断信息是否为机器内部发送的中断,在devintr()函数中,定义了三个返回数字:

devintr函数
devintr函数

返回2说明是计时器定期发送的中断,返回1说明是其他设备的中断,0说明还没有定义,而在usertrap函数的下面会判断which_dev是否为2,如果为2会进入yield函数:

判断中断类型是否为定时器中断
判断中断类型是否为定时器中断

在CPU进行资源调度时,会通过计时器发送中断,使得运行进程进入yield函数:

yield函数
yield函数

在yield函数中,会获取当前运行进程,获得锁,防止其他进程对当前资源进行修改操作,之后会将对应的状态state从RUNNING切换为RUNNABLE,之后进入sched函数:

sched函数
sched函数

在这个函数中,会判断当前进程是否持有锁、是否为运行态、是否得到了计时器中断等一系列操作,这些判断的作用就是确保是由于计时器中断进入的该程序,判断成功会调用swtch函数交换上下文信息:

swtch汇编代码
swtch汇编代码
context上下文信息结构体
context上下文信息结构体

这是一段汇编指令,一共有两个存储模块,分别有14条指令,其中的a0寄存器存储的是当前进程的上下文信息context指针,a1寄存器存储的是要切换的下一个进程的上下文信息context指针,这段代码的作用就是交换了两个进程的上下文信息,而这里我们不由得会思考:

既然CPU会切换进程的上下文信息状态,那么切换的下一个进程是什么?

关于这个问题,需要我们知道mycpu()函数指向的初始进程地址是什么,mycpu()在初始化时,其中会保存0号地址,也就是调度函数scheduler()对应的地址,因此,在切换上下文信息后,下一个运行的函数就是scheduler调度函数

同时在这里只存储了14个寄存器的上下文信息,并没有存储全部,其实原因很简单,由于当前进程的切换是在内核态中完成的,不需要知道用户态切换的其他信息,因此操作系统为了提高效率,只会保存接下来会用到的指令信息,因此只会存储必须用到的14个寄存器信息

这里最后要调用ret函数,这里值得注意的是,这里并不会返回到先前调用swtch函数的下一条地址,因为由于发生了定时器中断,是定时器中断导致的内核切换、保存上下文,所以获取的p->lock最后会在scheduler调度函数中被释放,即:

调用scheduler函数返回
调用scheduler函数返回

在swtch汇编返回时,会返回到对应于scheduler的swtch指令执行,而当前的swtch指令会交换上下文信息,将当前运行的进程信息更改为刚刚设置为运行态的对应进程,先前在yield函数中获取的锁也会在这里释放,因此进程的切换就在定时器中断与scheduler函数中完成了,这里附上一张图方便小伙伴们理解:

应用进程在不同内核之间的切换
应用进程在不同内核之间的切换

进程切换流程——scheduler函数调度下一个进程

到了这里我们再看回scheduler函数:

scheduler函数
scheduler函数

在多处理器执行时,另一个内核会执行这个函数中,开启中断,获取锁,之后会遍历当前进程的所有子进程,找到处于就绪态的函数,将它的运行状态从就绪态转变为运行态,之后再次通过swtch函数切换进程上下文信息,将当前进程指向这个即将运行的进程,最后释放锁,也就完成了进程信息的设置

而当操作系统发出定时器中断后,内核中又会发生上述的当前进程的资源调度,再次返回到当前的swtch函数,最后把进程资源调度处获得的锁释放

这里值得注意的是,在遍历进程之前,会再次开启中断,使得当前进程又能够被CPU进行调度,因此,有时我们看到的进程运行次序实际上是这样的:

运行程序之间的中断与调度
运行程序之间的中断与调度

也就是说,有可能你当前运行的程序,可能不知道经过了多少次中断调度才得以运行,因此,操作系统在内核态中的调度是很频繁的,而这也保证了计算机能够通过仅仅几个CPU就能运行数量如此庞大的程序

至此,有关于操作系统在内核中切换应用程序的介绍就结束了,希望对你有所帮助,祝好!!!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • schedular调度器介绍
  • 进程切换流程——当前进程的资源调度
  • 进程切换流程——scheduler函数调度下一个进程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档