0 回顾
多线程操作是进程切换的核心
1 CPU
1.1 运转CPU
- 怎么使用CPU?不断的取址执行就是使用CPU
- 单道程序,CPU的利用率很低,所以提出多道程序的概念,程序之间的切换可以用栈来做
- 单道程序设计效率不行
- 转入多道程序设计
- 开始切换
1.2 利用栈进行程序切换
- 利用栈来进行程序之间的切换
- Yield就是一种跳转
- Yield()找到下一个TcB→找到新的栈→切到新的栈
- 因为不能一直执行用户态,这样内核进程就切不回去了
- 所以引出了内核态
1.3 引入内核态
- 用户级线程存在缺点,操作系统内核调用阻塞时无法感知用户级线程
-
- 既然指令的切换就是栈的切换实现,那么到了内核是不是也有栈?
- 从用户栈到内核栈,内核栈到TCB,TCB直接进行切换,TCB切换完了以后内核栈切换,切换完了跟着用户栈切换,这就是两套栈的切换,这是思维递进的结果
1.4 idea的实现
- 提出一个简单、清晰、明确的目标:在屏幕上交替输出A和B
- 从用户代码开始,注意AB指的是主进程
int 0x80 -> sys_fork -> copy_process
jne
就是根据返回值进行操作判断- 上方是进行
cmp
比较,看res
和0是否相等,等于就执行子进程;不等于就跳转到208接着运行父进程
int 0x80 -> sys_fork -> copy_process
- 以下是
int 0x80
之后进入内核的过程 - 进入内核后就有用户栈和内核栈
- 进入到内核当中,
int 0x80
就是要执行system_call
- 执行完
system_call
就要执行sys_fork
- 执行完
sys_fork
就要跳转到copy_process
- 其中
copy_process
就是在内核中做出新的一套PCB来,然后做出一个新的栈
- 然后把PCB里面的tss都写好,eip都置好
- eip就是父进程
int 0x80
的代码 - 再把eax置成0
- 内核中的子进程做成这个样子
tss->eip
就是地址为100的那条指令
- 现在要想在屏幕上打印A,eip&esp这些栈的指针以及执行的地址都写到子进程里面,当子进程再开始调度的时候,屏幕上就会出现A了
- 所以如果在程序内核当中做出这种结构体,栈也有空间存放,eip指向的就是print(A)的代码,这样就不愁屏幕上打印不出A了
- 所以写代码的目的就是如此
1.5 返回
- 执行完
copy_process
,父进程就要往回返了,父进程退回,进行调度,因为调度才能让屏幕打印出A来(就是让那个打印A的语句去执行) system_call
中,从call_sys_call_table
返回后,会判断当前进程/线程是否阻塞,如果阻塞,就要调用reschedule
- 之前还提到过,需要判断是否时间片已经用完,下面的代码中没有写
- 现在是打印指令可以执行,但是不能执行,真正执行还得是在适当地方加上
schedule
- 此处返回是哪里做的?是在中断返回的时候加上
schedule
- 现在
fork()
并不调用schedule
,因为时间片没有用完
1.6 再次调用fork()
- 因为上文只创建好了打印A的进程,所以还得创建打印B的进程
- 再一次fork(),创建输出B的进程,下图中,主进程AB、子进程A、子进程B形成了一个就绪队列
- 所以要想在屏幕上打印出AB两个字母,就必须在内核当中做出两套结构
- 又一次会产生PCB和栈
- 两个结果非常相似
- 创建好两个打印进程之后,就该等待了
wait()
,所以父进程调用wait()
- 这个系统调用就是把自己的状态变为阻塞,然后调用
schedule()
- 现在
schedule
可以做最简单的实现:选择队首的就绪线程/进程 - 由一个进程产生出打印A和打印B的两个子进程,子进程PCB分别贴好,贴的对应的就是打印A,B的函数,然后父进程阻塞调用
schedule()
,然后选择其中一个进程,选择第一个就行(打印A),就完成了切换 - 打印A后要打印B就要进行切换
1.7 switch_to切换
- 如图,
eip = 100
就继续往下执行,res返回的是0,所以不断的printf("A")
- A不会进入阻塞,也没有主动释放CPU控制权,那怎么样才可以调度切换为B呢?
- 什么时候调用
schedule()
呢? - 出时钟中断的慨念
1.8 调用schedule()
- 需要调度,才能打出B
- 调度点在哪里?
current
是当前进程的PCB,current->counter
是当前进程的剩余时间片- 每次时钟中断,都上当前进程的
current->counter
,如果减到了0,那么就调用schedule()
疑问:一直学下来,只有在这个时钟中断的处理函数中,才会对counter–,并目立即判断了是否等于0,那么上一节视频中提到了系统调用的最后也要判断时间片是否多余?
- 调度必须调用
schedule()
- 调用
schedule()
在哪里?在内核,所以必须进入到内核 - 怎么进入内核?中断
- 什么中断?
- 时钟中断
- 时钟中断,使得当前进程的剩余时间片为0,那么就从A进程切换到B进程,B进程开始不断的输出B
- 打一会儿A再打一会儿B,就能交替打印AB
- 这里是把B的PCB的tss,esp赋值成300,就对应了打印B的指令
- 打完了B后,就完成了切换
- 接下来不断的时钟中断,当进程B的剩余时间片为O时,就再切换为进程A(根据调度算法的不同,当然有可能还是B本身),以此类推,不断循环,那么屏幕上就交替的输出A和B
2 总结
自己头脑写出一个代码,就完成了自己操作系统0.01