1进入内核的手段:系统调用
为什么不能直接进入内核:不安全。例如用户名密码都在内核,如果随意进入,不安全
如何保证不能强制jump:硬件设计,DPL寄存器表示目标内存段的特权级,系统初始化时,DPL初始化为0.CPL表示当前特权级CPL<=DPL时,才能执行当前指令.系统调用通过中断处理,CPL在切换段时会随之修改。
系统调用的过程
int0x80通过不同系统调用号执行不同系统调用
将系统调用号置给eax,然后调用int0x80 30:00
int0x80会查idt表(中段描述表),在系统初始化的过程中将DPL置为3,设置中断处理程序入口地址,设置idt表中的段基址。通过查表,DPL被设为3,而CPL是cs的最后两位,自然是0,然后通过中断处理函数入口进入systemcall程序。
接下来systemcall查system_CALL_TABLE根据exa中的系统调用号*函数指针大小+基址调用对应的系统调用。
2.多进程图像
cpu工作流程:取址执行,更新pc
每个进程有一个保存进程信息的数据结构:PCB
main中的fork()开创了第一个进程
子进程fork()= 0
多进程如何组织
多个PCB分为不同状态,分别组织在就绪/等待队列
多进程如何交替?
队列操作+调度+切换
通过schedule完成进程的切换
进程与线程的区别:
进程是CPU资源分配的最小单位,线程是CPU调度的最小单位。不同进程拥有不同的地址空间,而同一个进程中不同的线程共用一套地址空间,共用一套虚拟内存。
进程切换与线程切换的区别:
进程切换只需要切换指令序列,线程切换需要切换指令序列和地址空间
用户级线程与核心级线程的区别?
核心级线程切换需要进入内核,通过schedule函数调度。用户级线程的切换不需要进入内核,通过yield函数调度。
多线程的实现:
如何切换用户级线程?(L10 23:00)
从目标tcb取出目标esp,通过yield切换栈(每个线程独占一个栈)指针(esp寄存器),原线程TCB修改栈指针记录现场
用户栈与内核栈的关联?11:00
通过中断进入内核时,将用户栈的ss,sp压入内核栈,将cs,ip压入内核栈,保存现场
通过iret返回用户栈时,弹栈从而恢复用户栈的ss,sp,cs,ip
如何切换核心级线程?
1中断引发schedule
2通过next找到目标线程的tcb。
3通过schedule切换内核栈指针(esp寄存器)(注意:用户栈的ss,sp,cs,ip已经被封装在了内核栈,内核栈的cs,ip也被封装在了内核栈)
4修改原线程tcb中的内核栈指针记录现场
为什么线程切换不用切pc?
pc已经被压在了栈里,通过切换栈已经完成等了pc的切换。
如何创建线程?
1如何创建用户级线程
1申请两块内存存放tcb和栈
2设置栈起始地址
3将tcb和栈关联
2如何创建核心级线程?
1申请两块内存存放tcb和内核栈
2初始化内核栈
3tcb关联内核栈
多核与多cpu的区别?
多处理器:每个cpu有独立的一套高速缓存和mmu,不同cpu内存映射相互独立
多核:多个cpu共用一套高速缓存和mmu,用一套内存映射
fork
fork的过程:查idt表,修改CPL和DPL,进入内核,根据系统调用号查系统调用表,调用system_fork,调用copy_process,传参父进程的内核栈内容,接下来:
1为pcb申请一页物理内存(getfreepage)
2创建内核栈(参数根据申请到的物理内存基址和偏移量设定),创建用户栈(参数来自父进程)
3将eax置为0
4创建虚拟地址空间ldt
CPU调度策略
评价CPU调度策略的标准
1周转时间
2响应时间
3吞吐量
调度算法
1先入先出,特点:响应时间短,但周转时间大
2短作业优先,特点:周转时间短,但是长作业响应时间长
3时间片轮转调度:特点:大幅缩短响应时间,但是增加了调度次数,吞吐量低,周转时间长
除此之外,根据任务特点设置不同任务队列(前台/后台),分别使用不同的调度算法。同时,为了提高响应时间,所有任务队列都要引入时间片。
Linux0.11的调度算法
L15
过程:将轮询指针置于数组末尾,轮询数组,next即为counter(优先级)最大的就绪态进程。
如果没有就绪态进程有时间片,那么设所有进程的时间片=时间片/2+priority
特点:counter既表示优先级又表示时间片
设置时钟中断,每次counter--。当counter=0时,schedule被调用。
这种算法的优点?
保证了阻塞态进程优先级较高,一旦其进入就绪态,可以较快被调度。
3进程同步与信号量
1为什么是信号量而不是信号?
L16 16:00
当有多个生产者睡眠时,消费者只能唤醒一个生产者(一个信号只能记录缓冲区大小,无法记录多个睡眠中的生产者)
信号量
信号量>0,表示还有多少空闲缓冲区。信号量<0,表示有多少进程被阻塞
信号量的数据结构包含一个阻塞队列和一个value。
P系统调用:如果value<0,表示--之前的value<=0.阻塞。
V系统调用:如果value<=0,表示++之前value<0.唤醒。
40:00
其中,empty表示空闲缓冲区大小,full表示生产出的产品数量
对于生产者,当缓冲区满的时候(空闲缓冲区大小empty=0时),生产者阻塞。
当消费者消费了一次之后(V(empty)),空闲缓冲区增加,生产者wake。
对于消费者,当缓冲区空的时候(生产出的产品数量full=0时),消费者阻塞。
当生产者生产了一件产品后(V(full)),产品数量增加,消费者wake
除此之外,互斥信号量mutex保证了文件同时只有一个进程能访问
为什么要用互斥信号量保护信号量?
不合适的cpu调度会导致指令执行序列不合适,从而导致共享数据(比如信号量)错误
临界区的设计准则?
1基本原则:互斥性
2有空让进:如果没有进程进入临界区,应尽快安排等待进入临界区的进程进入
3有限等待:不能让等待进入临界区的进程无限等待
进入临界区的尝试
1轮换法 缺点:不满足有空让进
2标记法 缺点:可能造成两个进程相互等待,出现死锁场景
3非对称标记(标记+轮转) 缺点:只支持两个进程
4面包店算法
choosing表示有进程正在取号
5硬件手段阻止调度
缺点:多核情况下影响其它cpu
6硬件原子指令法
testandset是原子的,中途不会检查中断寄存器,不会在执行过程中切出去
缺点:多cpu情况下影响其它cpu
信号量的代码实现(linux0.11)
死锁
死锁产生的原因
1互斥性 2占有且等待 3不可抢占 4循环等待
死锁的处理
1死锁预防:破坏死锁出现的条件
手段:1资源按顺序申请(破坏了循环等待的条件,从而避免死锁)
2一次性申请所有资源(破坏了占有且等待的条件,从而避免死锁)
缺点:造成了资源浪费
2死锁避免:检测每个资源请求,如果造成死锁就拒绝
手段:银行家算法
缺点:时间复杂度高
3死锁检测+恢复:回滚部分进程(优先级低的,占用资源多的),然后调用银行家算法
缺点:回滚比较麻烦
4死锁忽略
最常用