操作系统
进程和线程
进程
-
进程模型,单核
-
特点:任何一个给定瞬间仅有一个进程在运行
-
多道程序设计:CPU在多道程序间快速切换
-
与程序的不同
两个进程可能恰好运行同一程序,一个进程是某种类型的一个活动,进程在内存中执行,程序在磁盘中。
例子:
一个人是一段程序,那他看书和开门是两个进程
-
-
进程创建
-
fork
-
父子进程有不同的地址空间
-
父子进程有相同的内存映像,环境字符串,打开文件
-
UNIX中不可写的内存区共享,可写不共享
-
形式
-
写时拷贝
- 虚拟内存独立,物理页表要修改再独立
-
非写时拷贝
- 虚拟内存和物理页表都独立
-
vfork()
- 虚拟内存和物理页表都不独立
-
-
-
execve
- 子进程执行新程序
-
-
进程状态
- 运行
- 阻塞
- 就绪
-
进程实现
-
栈段
- 函数参数,局部变量
-
堆段
- 由程序员分配和释放
-
BSS
- 存放未初始化的全局变量和静态变量
-
数据段
- 存放初始化的全局变量和静态变量
-
代码段
- 代码主体,二进制格式
-
-
POSIX和SYSTEM V的区别
-
POSIX
- 在磁盘创建文件
-
SYSTEM V
- 在内存中,断电就不可用,速度快
-
线程
-
线程使用原因
- 应用中同时发生多种活动
- 线程轻量,易创建和销毁
- 线程有助于应用的性能提升
-
线程特点
- 彼此之间无保护
- 密切配合合作,且平等
- 一个进程中的线程有完全一样的地址空间
- 各自栈,寄存器,PC独立,数据段,代码段和堆段共享
-
POSIX线程
-
pthread_create
- 创建一个线程
-
pthread_exit
- 结束调用的线程
-
pthread_join
- 等待特定线程退出
-
pthread_yield
- 释放CPU来运行另一个线程
-
pthread_attr_init
-
创建并初始化一个线程的属性结构
- 分离状态
- 调度策略
- 继承性
- 作用域
- 栈的位置
- 栈的大小
- 栈末尾的警戒缓冲区大小
-
-
pthread_attr_destroy
- 删除一个线程的属性结构
-
-
线程实现方式
-
用户
-
利
- 不需要陷入内核,不需要切换上下文,调度快捷
- 定制调度算法
-
弊
-
线程进行系统调用,阻塞整个进程
由于内核不知道线程的存在,所以当用户线程引起阻塞时,通常会把整个进程阻塞直到磁盘I/O设备完成为止,尽管其他线程是可以运行的。
-
一个线程开始运行,其他线程就不能运行,除非自动放弃
-
-
-
内核
-
利
- 内核中有线程表,创建或销毁都是针对线程表操作,线程引起的阻塞,内核可以很方便的调度
-
弊
- 直接针对线程表操作,开销大
-
-
混合
-
-
杂例
-
弹出式线程
- 一个消息到达导致系统创建一个处理该消息的线程
-
单线程多线程化(通过模拟实现),看不太懂
-
进程通信
-
竞争条件
- 取决于进程运行时的精准时序
-
临界区
- 对共享资源进行访问的程序片段
-
互斥
-
软件算法
-
锁变量
-
严格轮转法
-
忙等待
- 等待一个变量直到出现某个值为止,Peterson的while();便是忙等待
-
自旋锁
- 用于忙等待的锁
-
-
Peterso解法
int turn;
int interested[N];
void enter_region(int process)//进程0|1
{
int other;
interested[process]=true;
turn=process;
while(turn==process&&interested[other]==true);
}
//临界区代码
void leave_region(int process)//进程离开
{
interested[process]=false;
}
-
-
硬件
-
屏蔽中断
进程的切换势必会引起中断,只要在进程执行时屏蔽中断,就可以达到互斥效果,但缺陷也很明显,不是一种合适的通用互斥机制。
-
TSL指令(仅作了解,硬件实现)
-
-
-
同步
-
生产者-消费者问题(有界缓冲区问题)
-
信号量S,PV操作
-
S:当信号量S小于0时,其绝对值表示系统中因请求该类资源而被阻塞的进程数目。S大于0时表示可用的临界资源数。注意在不同情况下所表达的含义不一样。当等于0时,表示刚好用完。
-
P:申请资源,S-=1,S<0,进程阻塞
P(S)
{
value–;
if(value<0)
{
//add this process to waiting queue
block();//阻塞
}
} -
V:释放资源,S+=1,S<=0,唤醒进程
V(S)
{
value++;
if(value<=0)
{
//remove a process from the waiting queue
wakeup§;
}
} -
注意点:PV配套出现,否则会出现死锁
-
经典案例
-
读者-写者问题
-
写者优先
int read_count = 0;
semaphore read_count_lock = 1;
semaphore wsem = 1;
reader(){
wait(read_count_lock);
read_count++;
if(read_count==1) wait(wsem);
signal(read_count_lock);READ_PROCESS(); wait(read_count_lock); read_count--; if(read_count==0) signal(wsem); signal(read_count_lock);
}
writter(){
wait(wsem);
WRITE_PROCESS();
signal(wsem);
}
多名读者可以同时读体现在READ_PROCESS()前后没有信号量抢锁语句
因为写者需要wait(wsem)才能进入写过程,而只要储存区还有一名读者,wsem就被读者持有,故写者无法进行写
当储存区没有读者时,wsem值为1,读者和写者公平地竞争wsem -
读者优先
int write_count = 0,read_count = 0;
semaphore write_count_lock = 1,read_count_lock = 1;
semaphore wsem = 1,rsem = 1;
semaphore readQueue = 1;reader(){
wait(readQueue);
wait(rsem);
wait(read_count_lock);
read_count++;
if(read_count==1) wait(wsem);
signal(read_count_lock);
signal(rsem);
signal(readQueue);READ_PROCESS(); wait(read_count_lock); read_count--; if(read_count==0) signal(wsem); signal(read_count_lock);
}
writer(){
wait(write_count_lock);
write_count++;
if(write_count==1) wait(rsem);
signal(write_count_lock);wait(wsem); WRITE_PROCESS(); signal(wsem); wait(write_count_lock); write_count--; if(write_count==0) signal(rsem); signal(write_count_lock);
}
最多只有一名读者在rsem上排队,每次读者释放readQueue才有一名新的读者抢rsem,与此同时第一名写者也在抢rsem
如果写者没有抢到rsem,则每次读者计数更新后都有写者和一名读者竞争rsem
如果写者抢到rsem,则会使后来读进程阻塞在rsem和readQueue上。当前储存区内读进程结束后,写者开始写
因为只和一名读者竞争,所以写者的优先权得到进一步提高
readQueue的作用在于每次和读者竞争rsem的写者只有一个,也就不存在公平竞争中当一个读者成功拿到wsem后会不断唤醒另外的读者,导致一个写者和多个读者竞争 -
公平竞争
int write_count = 0,read_count = 0;
semaphore write_count_lock = 1,read_count_lock = 1;
semaphore wsem = 1,rsem = 1;reader(){
wait(rsem);
wait(read_count_lock);
read_count++;
if(read_count==1) wait(wsem);
signal(read_count_lock);
signal(rsem);READ_PROCESS(); wait(read_count_lock); read_count--; if(read_count==0) signal(wsem); signal(read_count_lock);
}
writer(){
wait(write_count_lock);
write_count++;
if(write_count==1) wait(rsem);
signal(write_count_lock);wait(wsem); WRITE_PROCESS(); signal(wsem); wait(write_count_lock); write_count--; if(write_count==0) signal(rsem); signal(write_count_lock);
}
读者在rsem上排队,写者在wsem上排队。第一名写者得到rsem后即限制了读者读的行为(即后来读者在rsem上阻塞),从而使写者处于优先地位(若得不到,则该写者在rsem上阻塞,后来写者因为在write_count_lock上阻塞而无法更新write_count);第一名读者得到wsem即标志着开始读,同时”锁“住排队的写者。
rsem和wsem的抢锁顺序在读者和写者过程中一致,避免了死锁
-
-
-
-
互斥量
-
CPU调度
-
批处理系统
- FCFS:先来先服务
- SJF:最短作业优先
- SRJF:最短剩余时间优先
-
交互式系统
- 轮转调度:分配时间片
- 优先级调度
- 保证调度:计算CPU占有率
- 彩票调度:比如系统掌握一种彩票,作为获奖者可以获得20ms的CPU时间
-
Linux调度器(分时系统)
-
优先级
-
用户空间
-
普通任务[-20,19],数值越小,优先级越大
-
实时任务(一定时间范围内执行),[0,99],数值越大,优先级越大
- 硬实时
- 软实时
-
-
内核空间
-
[0,139],[0,99]实时任务,[100,139]映射为普通任务,数值越小,优先级越大
-
优先级计算
- static_prio,静态优先级
- normal_prio,基于static-prio和调度策略的优先级
- prio,动态优先级
-
负载权重
- nice和时间片的比例关系
-
-
-
CFS
-
思路
-
根据各个进程的权重分配运行时间
-
公式一:分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
-
公式二:vruntime = 实际运行时间 * 1024 / 进程权重
-
公平体现
- virtual runtime(vruntime),它记录着进程已经运行的时间,但是并不是直接记录,而是要根据进程的权重将运行时间放大或者缩小一个比例。
- 权重来源于一个数组,static const int prio_to_weight[40] prio_to_weight[0]=1024
-
-
-
数据结构
-
红黑树
-
节点存储信息:vruntime-min_vruntime
- 目的:防止vruntime溢出
- 原因:vruntime存储的是unsigned long,可能会溢出,所以只需记录进程vruntime的相对大小即可,也就是对vruntime进行离散化,到达回滚效果
-
-
-
虚拟时钟
- 任务运行,虚拟时间增加,移到树的右边
- 高优先级任务,虚拟时钟节奏更慢,移到右侧的速度就慢,有更多机会被调度
-
选择下一个任务
- 直接取缓存中红黑树的最左边节点,比查找执行更快
-
性能
- 在高负载的服务器性能好
- 调度颗粒小
-
-
-
计算公式
- 周转时间=等待时间+执行时间
- 响应比=周转时间/执行时间
死锁
资源
- 可抢占资源,存储器
- 不可抢占资源
简介
-
条件
-
互斥条件
-
占有和等待条件
-
不可抢占条件
-
环路等待条件
- 死锁发生时,系统中一定有由两个或两个以上的进程组成的一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。
-
-
建模
-
T
- D
-
C
- U
-
说明:矩形表示资源,圆形表示进程
-
解决策略
-
检测
-
每种类型多个资源的检测
- 通过矩阵,设置4个数据结构,分别为现有资源,可用资源,当前分配矩阵,请求矩阵
-
每种类型一个资源的检测
- 抽象成有向图,并检测是否有环,Tarjan算法
-
-
恢复
- 抢占资源
- 回滚进程
- 杀死进程
-
避免
-
前景知识
- 安全状态
- 不安全状态
-
单个资源的银行家算法
- 对每个请求进行检查,检查如果满足这一请求是否会达到安全状态
-
多个
-
-
预防
-
互斥
- 一切都使用假脱机技术,允许多个进程同时产生输出,但唯一真正使用的是守护进程
-
占有和等待
- 在开始就请求全部资源
-
不可抢占
- 抢占资源
-
环路等待
- 对资源设置优先级
-
杂例
-
通信死锁
- 超时中断
-
活锁
- 例子:两个人在一条路上相遇并同时给对方让路,导致双方都无法前进
-
饥饿
- FIFO策略解决
内存管理
重定位
-
编译链接
-
载入
-
运行时(动态)
- 进程PCB的基地址配合MMU共同完成
- 最简单的执行逻辑:进程的逻辑地址必须处在基地址和基地址+界限地址中间,而每一个PCB中都会存储自己的基地址和界限地址,运行时再将这两个赋给CPU的寄存器
组织内存的方案
-
位于ROM中的设备驱动程序
- 0xFFFF
- BIOS,基本输入输出系统
-
用户程序
-
位于RAM中的驱动程序
- 0
矛盾
-
内存空间有限
-
解决策略
-
交换
- 内存碎片
- 内存紧缩
-
虚拟内存
-
虚拟地址
虚拟地址和物理地址通过MMU(内存管理单元)完成映射
-
虚拟地址页面对应物理地址的页框
-
页表
-
TLB(快表)加快分页
-
-
-
适配方式
-
位图,建立映射关系表
-
链表
- 首次适配:合适就可以
- 下次:找到合适并记录位置,以便下次查找
- 最佳:顾名思义
- 最差
- 快速:为常用大小维护链表
-
-
缺页中断
-
最优页面置换算法
-
最近未使用,NRU
设R(访问),M(修改)位,
NUR(Not Recently Used)
R位定期被时钟中断清零,以区分最近没被访问和被访问 -
先进先出
-
第二次机会
对NRU和FIFO的优化,检查最老页面的R位,如果R位是0,则置换;R是1,则清0,并放到链表的尾端。
-
最近最少使用,LRU
-
代价大
- 在链表中找到一个页面,删除它,然后把它移动到表头,费时!!!
-
硬件实现
- 要求硬件有计数器C,每次执行完+1,每个页表项都要有这个域。缺页中断就找值最小的那个页面。
-
软件实现
- 老化算法:对NRU的修改以模拟LRU
具体:计数器右移一位,最左边插入R值
- 老化算法:对NRU的修改以模拟LRU
-
-
工作集
- 最近k次内存访问所使用过的页面的集合
- R=1,当前时钟滴答中被访问过,在工作集中;R=0,与规定生存时间标准t作比较,小于等于表示在,大于表示不在,则置换。
-
工作集时钟
- 数据结构:循环链表
- R=1,则R=0,其余和“工作集”相似
-
-
-
物理地址暴露可能破坏OS
分段与分页的关系
-
需要程序员了解
- 否
- 是
-
是否可以超出物理存储器大小
- 是
- 是
-
过程和数据可以被区分并分别保护
- 否
- 是
-
大小浮动的表可以被很容易提供
- 否
- 是
-
用户间过程共享方便
- 否
- 是
-
好处
- 得到更大的线性空间
- 程序和数据被划分为逻辑独立的地址空间,且有助于共享和保护
文件系统
文件类型
-
魔数(用于标识可执行文件)
-
模块头(名称,日期,所有者,保护,大小)
-
数据长度
-
目标模块
-
BSS长度
-
模块头
-
入口点
-
目标模块
-
正文长度
-
标志
-
正文
-
数据
-
重定位位(装入内存后定位)
-
符号表(用于调试)
文件系统的实现
- 连续分配
- 链表
- FAT(File Allocation Table)(做法类似于Dijkstra的路径记录和输出)
I/O设备
显示器
-
原理:CPU向对应外设的控制器或寄存器发送指令具体操作设备
-
过程
- 发出写命令,out指令
- 向CPU发出中断
- 读数据到内存