如果有写过多线程的小伙伴知道,如果两个程序同时向一片区域中写入数据,可能会导致写入数据交叉错乱的情况,这是因为操作系统在运行程序时,为了能够让每个进程资源都充分被调度,会定期实施切换进程操作,本文旨在从底层源码介绍操作系统如何在内核态中切换应用程序
前置知识:小伙伴们可以阅读我这两篇文章:https://cloud.tencent.com/developer/article/2459968(介绍中断),https://cloud.tencent.com/developer/article/2457403(介绍用户态与内核态的切换)
我们首先需要了解一下scheduler函数,操作系统在底层进行进程资源调度时,会通过定时调度函数schedular进行:
这个函数会在每个CPU底层定时运行,主要工作有两个:
1)更改运行状态:它会遍历当前进程的所有子进程,判断运行状态是否为就绪态(RUNNABLE),如果是,操作系统会将它的状态更改为运行态(RUNNING)
2)移交运行权:通过swtch函数更改上下文信息,这个函数后面介绍流程会具体介绍,它的主要作用是恢复上下文信息,并移交运行权给当前线程,完成进程的切换
如果你已经看了我的用户态与内核态切换文章(https://cloud.tencent.com/developer/article/2457403),我们知道,当操作系统从用户态切换到内核态时,会在trap.c中的usertrap()函数中,调用syscall()执行程序:
这里面有很关键的一个点,就是在执行syscall()命令之前,操作系统会通过intr_on()开启中断,在开启中断后,当前运行的应用进程允许被CPU抢占进行资源调度,因此在操作系统调度程序时,必须开启中断,让CPU有能够进行调度的机会,而在下一个else if判断中,会判断返回的dev中断信息是否为机器内部发送的中断,在devintr()函数中,定义了三个返回数字:
返回2说明是计时器定期发送的中断,返回1说明是其他设备的中断,0说明还没有定义,而在usertrap函数的下面会判断which_dev是否为2,如果为2会进入yield函数:
在CPU进行资源调度时,会通过计时器发送中断,使得运行进程进入yield函数:
在yield函数中,会获取当前运行进程,获得锁,防止其他进程对当前资源进行修改操作,之后会将对应的状态state从RUNNING切换为RUNNABLE,之后进入sched函数:
在这个函数中,会判断当前进程是否持有锁、是否为运行态、是否得到了计时器中断等一系列操作,这些判断的作用就是确保是由于计时器中断进入的该程序,判断成功会调用swtch函数交换上下文信息:
这是一段汇编指令,一共有两个存储模块,分别有14条指令,其中的a0寄存器存储的是当前进程的上下文信息context指针,a1寄存器存储的是要切换的下一个进程的上下文信息context指针,这段代码的作用就是交换了两个进程的上下文信息,而这里我们不由得会思考:
既然CPU会切换进程的上下文信息状态,那么切换的下一个进程是什么?
关于这个问题,需要我们知道mycpu()函数指向的初始进程地址是什么,mycpu()在初始化时,其中会保存0号地址,也就是调度函数scheduler()对应的地址,因此,在切换上下文信息后,下一个运行的函数就是scheduler调度函数
同时在这里只存储了14个寄存器的上下文信息,并没有存储全部,其实原因很简单,由于当前进程的切换是在内核态中完成的,不需要知道用户态切换的其他信息,因此操作系统为了提高效率,只会保存接下来会用到的指令信息,因此只会存储必须用到的14个寄存器信息
这里最后要调用ret函数,这里值得注意的是,这里并不会返回到先前调用swtch函数的下一条地址,因为由于发生了定时器中断,是定时器中断导致的内核切换、保存上下文,所以获取的p->lock最后会在scheduler调度函数中被释放,即:
在swtch汇编返回时,会返回到对应于scheduler的swtch指令执行,而当前的swtch指令会交换上下文信息,将当前运行的进程信息更改为刚刚设置为运行态的对应进程,先前在yield函数中获取的锁也会在这里释放,因此进程的切换就在定时器中断与scheduler函数中完成了,这里附上一张图方便小伙伴们理解:
到了这里我们再看回scheduler函数:
在多处理器执行时,另一个内核会执行这个函数中,开启中断,获取锁,之后会遍历当前进程的所有子进程,找到处于就绪态的函数,将它的运行状态从就绪态转变为运行态,之后再次通过swtch函数切换进程上下文信息,将当前进程指向这个即将运行的进程,最后释放锁,也就完成了进程信息的设置
而当操作系统发出定时器中断后,内核中又会发生上述的当前进程的资源调度,再次返回到当前的swtch函数,最后把进程资源调度处获得的锁释放
这里值得注意的是,在遍历进程之前,会再次开启中断,使得当前进程又能够被CPU进行调度,因此,有时我们看到的进程运行次序实际上是这样的:
也就是说,有可能你当前运行的程序,可能不知道经过了多少次中断调度才得以运行,因此,操作系统在内核态中的调度是很频繁的,而这也保证了计算机能够通过仅仅几个CPU就能运行数量如此庞大的程序
至此,有关于操作系统在内核中切换应用程序的介绍就结束了,希望对你有所帮助,祝好!!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。