操作系统笔记

进程

Linux 0.11的调度函数schedule()

//综合优先级与时间片的算法
void Schedule(void)
{while(1) {c=-1;next=0;i=NR_TASKS;
	//Linux 0.11中将PCB做成数组
	P=&task[NR_TASKS];//P设成最后一个地址
	

	while(--i){if((*P->state==TASK_RUNNING&&(*P) ->counter>c)//--i表示从后往前移
	//如果状态=就绪,并且counter>c
		C=(*P)->counter,next=i;}
		//返回给C的就是最大的counter
	if(C) break;//c不等于0找到了最大的counter,就break;c都=0,就是现在就绪态的时间片都用完了,所有就绪态进程的时间片都用完了此时就执行下面的for语句
	//找到最大counter就跳转执行switch_to(next),即优先级方法;counter就是优先级,counter本身也作为时间片轮转调度



	for(P=&LAST_TASK;P>&FIRST_TASK;--P)
		(*P)->counter=((*P)->counter>>1)//所有进程置counter等于当前counter右移一位即为当前counter除2,就绪态进程是0的0除2得0再加上counter的初值。(右移比除法快)
		//若为就绪态进程在此处会设成就绪态初值,而阻塞的进程是折了一半再加初值,所以这个阻塞态进程恢复就绪就一定比非阻塞态进程的大。
		//即因为IO阻塞的进程在回来后其counter一定会大,优先级也就更高。阻塞时间越大,counter就会改的越大
						+(*P)->priority;)
	 switch_to(next);
}

counter的两个作用

时间片作用
时间片轮转
在时钟中断里要修改counter
void do timer(…)//在kernel/sched.c
{ if((–current->counter>0) return;//让counter–
current->counter=0;//如果减完=0
schedule();}//调度切换


_timer_interrupt://在kernel/system_call.s中

call_do_timer//do time放在时钟中断中


void sched_init(void) {
set_intr_gate(0x20,&timer_interrupt);

优先级作用
while(–i){if((*P->state==TASK_RUNNING&&(*P) ->counter>c)
C=(*P)->counter,next=i;}
//找counter最大的任务调度,conter表示了优先级


for(P=&LAST_TASK;P>&FIRST_TASK;–P)
(*P)->counter=((*P)->counter>>1)+(*P)->priority;)//将优先级进行了动态调整,IO时间越长,优先级就越大

  • 受到Io约束的阻塞越长优先级就越大,而受到IO约束(IO需要多的)的正好是前台进程的特征,前台进程要求响应要快(优先级算法响应快,但周转时间大)
  • counter保证了响应时间的界,每个进程时间片最大是2P。最大周转时间也就2np
  • 后台进程一直按照counter轮转,近似SJF调度
  • 每个进程只用维护一个counter变量,简单、高效。后台特征是CPU约束型(CPU需要多的)则后台进程关注周转时间(短作业优先周转时间最小,但响应时间没有限制)
  • 时间片轮转,保证响应时间有最大值限制,可以保证响应时间的速度
  • 要求后台周转时间小,且前台进程可以快速响应,schedul函数就做到了这两点,并结合轮转算法
  • 前台任务时间片轮转,后台任务短作业优先,前后台任务之间采用优先级调度,同时要让前台任务优先级较大。但前台任务的优先级不能固定,这样可能会导致某个后台任务一直无法执行,故而`还需要后台任务动态变化任务的优先级,同时后台也需要一定的时间片,否则某个后台任务优先度若高,它就会一直执行下去(后台任务对cpu需求大),应该让他执行一部分再切换.
  • 即需要以轮转调度为核心,在轮转的基础上增加优先级,优先级同时要考虑到短作业先做和前台任务先做---这就是简单高效的schedule函数算法。(该算法可以学习到哪个是前台任务,哪个是后台任务从而做出相应的变换调整)

进程同步与信号量

可能有多个进程,仅一个信号是无法表示多个进程的状态(是否睡眠及是否需要唤醒);故引入信号量。
用临界区保护信号量,用信号量实现同步

struct semaphore
{
	int value;//记录资源个数
	PCB *queue;//记录等待在该信号量上的进程
}
P(semaphore s);//消费资源
V(semaphore s);//产生资源

P(semaphore s)
{
	s.value--;//使用资源
	if(s.value<0)//资源数小于0
	sleep(s.queue;)//等待睡眠
}

V(semaphore s)
{
	s.value++;//释放资源
	if(s.value<=0)//有等待睡眠的进程(因没有资源)
	wake up(s.queue;)//唤醒进程
}

用信号量解决生产者消费者问题

int fd=open("buffer.tet");
//用文件定义共享缓冲区
write(fd,0,sizeof(int));//写in
write(fd,0,sizeof(int));//写out
//信号量的定义和初始化
semaphore full=0;//生产的内容的个数
semaphore empty=BUFFER_SIZE;//empty空闲缓冲区个数
semaphore mutex=1;//互斥信号量,只能一个进程进去
//******
Producer(item){
	P(empty);//测试空闲缓冲区个数是否为0
	P(mutex);//测试其值是否为1
	//读入in;将item写入到in位置上
	V(mutex);//进程离开,则释放
	V(full);}//生产者增加内容
//******
Consumer(){
	P(full);//测试生产内容个数是否没有
	P(mutex);
	//读入out;从文件中的out位置读出到item;打印item
	V(mutex);
	V(empty);}//消费者增加空闲缓冲区个数

信号量临界区保护

信号量是整型变量,对其访问修改实现进程同步。

1.为什么要对信号量进行保护?
多个进程共同修改信号量(并发操作共享数据),使得信号量的值出错。这与调度顺序有关,无法得知时间片何时到时。
2.如何对信号量进行保护(基本思路)?
生产者P1检查并给其上锁;生产者P2检查发现有锁则空转或阻塞;P1执行完开锁,P2再检查上锁。

临界区:一次仅允许一个进程进入的那段代码是临界区(读写信号量的那段代码,需要对其进行保护)

  • 基本的临界区
    • 互斥进入:一个进入,其他进程则不准进
  • 更好的临界区
    • 有空让进:临界区没有其他进程就必须让进
    • 有限等待

在进入区进行"上锁"操作;在退出区进行"开锁操作"
在这里插入图片描述

临界区的进入
软件的方式

1.轮转法存在问题(类似值日,轮到的进入)
在这里插入图片描述

//对于P0来说,turn=0时轮到它P0
//对于P1来说,turn=1时轮到它P1
//进程P0
while(turn!=0);//表示没轮到P0,则P0空转;
临界区
turn=1//轮到P1,P1进入
剩余区

存在问题:当P1阻塞,P0完成后不能接着再次进入,即使P1不再临界区,不满足有空让进。
2.标记法存在问题
加一个标记(便条),表示现在临界区是否有进程;当P0想进入时,就打个标记,若发现有标记了,则空转等待。
在这里插入图片描述

//进程P0
flag[0]=true//P0打标记
while(flag[1]);//若发现当有P1打的flag[1]标记(即flag[1]=true),则P0等待;若flag[1]=false则进入
临界区
flag[0]=false;//一旦进去并退出后,标记置空
剩余区

存在问题:可能两个进程同时进行标记,后两个进程发现有对方的标记都进行等待,没有进程进入,造成无限等待
3.非对称标记(peterson算法)
让其中一个进程足够勤劳,过一会就看一下。结合标记和轮转的思想
在这里插入图片描述

//进程P0
flag[0]=true;//P0做标记
turn=1;//当P0跑完,则将turn置为1表示该轮到P1
while(flag[1]&&turn==1);//发现P1做了标记,并且也轮到P1;则P0空转
临界区
flag[0]=false;
剩余区

4.多个进程(面包店算法)
仍然标记与轮转结合
面包店:每个进店的客户都获得一个号码,号码最小的先服务;号码相同时,名字靠前的先服务。

  • 轮转方式:每个进程都获得一个序号,序号最小的进入
  • 标记方式:获得了序号的进程都表示想进,不为0的序号即标记,进程离开时序号为0
    • 每次取得的号是当前号+1
      在这里插入图片描述
//进程Pi
choosing[i]=ture;num[i]=max(num[0],....,num[n-1])+1)//取号,取当前号最大的+1的号
choosing[i]=false;for(j=0;j<n;j++){while(choosing[j]);//当有人正在选号,则需要等待
while((num[j]!=0)&&(num[j],j)<(num[i],i));}//当不再有人选号,则开始判断:若进程j想进入(不为0则是想进入),则进程j先与进程i的号码比较,j的序号小则先进
临界区
num[i]=0;
剩余区

一直往后取号,存在溢出问题,需要做处理

硬件的方式

临界区只允许一个进程进入,另一个进程只有被调度才能执行想要进入临界区。可以利用硬件阻止另一个进程被调度,而要调度必须要中断,可以阻止中断。即硬件提供关中断命令。
在这里插入图片描述

cli();//关中断
sti();//开中断

在多CPU情况下,不好

临界区保护的硬件原子指令法

上锁就是将一个变量置0或置1。就像一个信号量mutex。mutex=1表示有资源;当一个进程进入后就没有资源,mutex再置0。但是用mutex信号量去实现,那么修改mutex也需要保护。
在执行时不可中断的操作称为原子操作。
将修改mutex的指令做成原子指令,使得它要么执行完成,要么不执行。
在这里插入图片描述

boolean
	TestAndSet(boolean &x)//已经被锁上,x就为true
{
	boolean rv=x;//若锁上x=ture被赋给rv;若没锁上rv为false
	x=ture;//没锁的情况下将x锁上
	return rv;//锁了返回rv,则返回true;没锁的情况下返回false
	//即锁了返回true;没锁则返回false且将x锁上
}
while(TestAndSet(&lock));//如果已经被锁上,则空转;没锁上则直接进入,此时x已经被锁中途不会停下TestAndSet(&lock)就会被完整执行
临界区
lock=false//退出时开锁
剩余区

信号量的代码实现

Producer(item){
	P(empty);
	...
	V(full);
}

sem.c//进入内核
typedef struct{
	char name[20];
	int value;
	task_struct*queue;
}semtable[20];//定义了一个全局数组,这个数组定义了信号量的名字、值、队列
sys_sem_open(char *name)
{
	在semtable中寻找name对上的;
	没找到则创建;
	返回对应的下标;}
}
//用户态程序producer.c
main(){
	sd=sem_open("empty");//打开名字为empty的信号量(获取信号量)
	for(i=1 to 5)
	sem_wait(sd);
	write(fd,&i,4)
}

sys_sem_wait(int sd){
	cli();
	if(semtable[sd].value)--<0){
	设置自己为阻塞,将自己加入semtable[sd].queue队列中;schedule;}//自己睡眠了
	sti();}
//在磁盘上读磁盘块
bread(int dev,int block){
	struct buffer_head*bh;//申请空闲缓冲
	ll_rw_block(READ,bh);//启动读命令
	wait_on_buffer(bh);//缓冲区带着一个信号量阻塞

启动磁盘后睡眠,等待磁盘读完由磁盘中断将其唤醒并加入就绪队列,也是一种同步

lock_buffer(buffer_head*bh)
{
	cli();
	while(bh->b_lock)???//判断lock,如果等于1则要sleep,不为1则上锁(没有负值)
		sleep_on(&bh->b_wait);
	bh->b_lock=1;//上锁
	sti();}


void sleep_on(struct task_struct **P){//P是一个指向task_struct结构体的指针的指针
	struct task_struct *p;
	//***********
	//以下两句话是将自己放入阻塞队列
	tmp=*p;//
	*p=current;//
	//**********
	current->stste=TASK_UNINTERRUPTIBLE;//将自己的状态变为阻塞态
	schedule();//实现进程调度函数
	//tmp指向阻塞队列中下一个进程
	if(tmp)//如果有下一个进程
		tmp->state=0;}//将这下一个进程状态置0变为就绪态
		//依次类推,将阻塞队列中进程依次全部唤醒
		//将其全唤醒后由schedule依照优先级决定谁先执行
struct task_struct *tmp;
tmp=*p;
*p=current;//current指向的是当前正在执行的进程
//此时新的阻塞队列的队首指向current指向当前正在执行的进程
//当前进程的PCB放在队首,队首指向新放入的PCB
//tmp指向阻塞队列中下一个进程

在这里插入图片描述

//磁盘中断
static void read_intr(void){
...
end_request(1);

end_request(int uptodate){
...
unlock_buffer(CURRENT->bh);

unlock_buffer(struct buffer_head*bh){
	bh->b_lock=0;
	wake_up(&bh->b_wait);}//唤醒

wake_up(struct task_struct **P){
	if (p && *p){
		(**p).state=0;//将PCB状态置0即为就绪
		*p=NULL;
	}
}

在这里插入图片描述

内存管理

第一步:分段,建立段表

//在Linux/kerbel/fork.c中
int copy_process(int nr,long edp,...)
{
	...
	copy_mem(nr,p);
int copy_mem(int nr,task_struct*p)
{
	unsigned long new_data_base;
	new_data_base=nr*0x4000000;//64M*nr,n就是第几个进程就是几;此处就是分割虚拟内存
	set_base(p->ldt[1],new_data_base);//设置基址,建段表(p是PCB),进程切换段表跟着切换
	set_base(p->ldt[2],new_data_base);//同上建段表

代码示例图如下
在这里插入图片描述

  • 每个进程的代码段、数据段都是一个段
  • 每个进程占64M虚拟空间,互不重叠(不重叠意味着可以各进程可以共用一套页表),实际操作系统中页很有可能重叠,则每个进程都有自己的页表与段表。

第二步:分配内存(物理页),建立页表

int copy_mem(int nr,task_struct*p)
{
	unsigned long new_data_base;
	old_data_base=get_base(current->ldt[2]);
	copy_page_tables(old_data_base,new_data_base,data_limit);//共用父进程的内存(因为是fork的父进程的,而父进程已经分配好了),建立页表(只需要拷贝父进程的页表即可),参数为两个虚拟地址
	//old_data_base是父进程的虚拟地址
int copy_page_tables(unsigned long from,unsigned long to,long size)
{	
	from_dir=(unsigned long*)((from>>20)&0xffc);//from是一个32位的虚拟地址,将它右移20位(也就是右移22位再乘4的结果;右移22位得到目录项编号,每项4字节)
	to_dir=(unsigned long *)((to>>20)&0xffc);
	size=(unsigned long)(size+0x3fffff)>>22;
	for(;size-->0;from_dir++,to_dir++){
		from_page_table=(0xfffff000&*from_dir);//size是可能有好多页目录需要处理,from_dir是父进程的页目录,to_dir是子进程的页目录
		to_page_table=get_free_page();//要用了才建立映射,才分配页表
		*to_dir=((unsigned long)to_page_table)|7;//申请新页后将页的地址赋给它

将子进程指针与父进程指向同一页,然后将其变为只读,他们共享这一页
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
使用内存
在这里插入图片描述

内存的换入换出

  • 实现虚拟内存必须要有换入换出,虚拟内存是连接分段和分页的关键所在
  • 请求调页的时候才换入并且建立逻辑到物理的映射
  • 请求访问(地址)——>发现没有映射、缺页,则中断——>调页(从磁盘上),映射
  • 缺页造成的中断,应使PC指针不动,不进行自动加1,以便调页后中断结束后继续执行该指令,而不是自动加1后执行了下一条指令
中断号名称说明
12Segment not Present描述符所指的段不存在
14Page fault页不存在内存
void trap_init(void)
{set_trap_gate(14,&page_fault);}//系统初始化时设置14号中断由page_fault去处理

#define set_trap_gate(n,addr)\ //宏定义中要换行必须用'\'结尾
	_set_gate(&idt[n],15,0,addr);//初始化修改idt表

在这里插入图片描述

//do_no_page
//这页不在,从磁盘中把这页读进来,并且完成映射
//在linux/ mm/memory.c中
void do_ no_ page (unsigned long error_ code ,
			unsigned long address )//这里是虚拟地址
{ address&=0xfffff000; //页面地址,得到虚拟页号
tmp= address- current->start_ code; //页面対应的偏移
if (!current->executable N tmp>=current->end data) {//不是代码和数据
		get_ empty_ page (address) ; return;}
page=get_ free_ page();//得到物理空闲页
bread_page(page,current->executable->i_dev,nr);//在磁盘上读,读到空闲页上,读系统文件
put_ page (page, address) ;//添写页表,建立映射


void get_ empty_ page (unsigned long address)
unsigned long tmp = get_ free page () ;
put_ page (tmp,address) ;}
//put_page
//建立映射,修改页表
//在linux/mm/memory.c中
unsigned long put_ page (unsigned long page, //物理地址
		   unsigned long address)
{unsigned long tmp, *page_ table;
 page_ table= (unsigned long *) ( (address>>20) &ffc) ;//页目录项
 if((*page_ table) &1)//页表项
	page_ table= (unsigned long*) (0xfffff000&*page_ table) ;
 else{
	tmp=get_ free_ page() ;
	*page_ table= tmp|7;
	page_ table= (unsigned long*) tmp;}
 page_ table[ (address>>12) &0x3ff] = page|7;//页表项与物理页建立映射
 return page ;

置换算法之LRU

在这里插入图片描述

  • A在第一个时刻使用;
  • B在第二个时刻使用;
  • C在第三个时刻使用;
  • 又来的A在第四个时刻使用;
  • 又来的B在第五个时刻使用;
  • 来的D,选择数值最小的C换出,在第6个时刻使用;
  • 来的A在第七个时刻使用;
  • 来的D在第8个时刻使用;
  • 来的B在第九个时刻使用;
  • 来的C,选择当前数值最小的A换出,然后在第十个时刻使用;
  • 来的B在第十一个时刻使用;
    …以此类推
    数值大是当前的,选择数值最小的换出
    在这里插入图片描述
    栈的顶部是最近使用的
  • 再来A则把B和C往下沉,把A浮上来
  • 淘汰替换时选择栈底的淘汰
    在这里插入图片描述
  • 如果最近访问过这一页,则将它置为1;每访问一页将它置为1,表示访问过。
  • 如何淘汰?使用再给一次机会算法(SCR(也叫CLOCK算法))
    • 如果R是1表示最近访问过,再给一次机会,则将它置为0不淘汰它,但要是转了一圈后还是0(转的这段时间中没有被访问过),则要淘汰替换它;R是0则没访问过,要淘汰它
    • 设置1或0,可以硬件自动设置
      在这里插入图片描述
      缺页很少,现在用到的页一直在用,退化为FIFO了
  • 用扫描指针清除R位。指针转动速度快
  • 另一个指针用来选择淘汰页,转动速度慢
    在这里插入图片描述
    分配的页框数:指针在多少个页之间循环的数
    页框分配太少,急剧下降那里的现象称为颠簸
    页框分配太少会颠簸
    局部:应该分配的页框数——>求工作集来求一个局部或也可以试着分配几个后做动态调整
    在这里插入图片描述

IO与显示器

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值