虚拟机的必要性:
操作系统是唯一的程序,在操作系统上运行多个进程,操作系统分配硬件和软件资源;当多个进程运行在一个操作系统上时,由于指针等问题,此进程可能会影响另一进程的数据安全,是不安全的,操作系统获得硬件的大小是根据其下进程的要求来的,同一操作系统下不同进程是共享内存资源的,因此如果有不同的操作系统,两个在不同操作系统下运行的进程就不会共享内存资源了,这就引出了虚拟机;
虚拟机管理程序(VMM):将总的硬件资源划分为多个部分,这样就形成了仿佛有多个物理机器的假象;操作系统只能分配已经获得的硬件资源,因此不同操作系统下的进程相互之间是不会干扰的;主要作用是分配硬件资源;
第一类VMM:运行在硬件上;只有VMM可以使用特权指令,操作系统和进程都是在用户态运行的;
VMM运行在内核态;
虚拟机管理程序相当于拆分的操作系统,
第二类:运行在宿主操作系统上,可迁移性更好,可在不同操作系统上应用,相当于一个大软件,里面有许多功能,相当于把一个电脑的功能全部迁移到另一个电脑上 ,第一类不可以;第二类分配的资源可能是离散的,同时性能更差,
虚拟机特权指令:将特权指令层级化,加快运行速度的同时保证安全;
中断的意义:如同输出设备一样,程序从内存中取地址的速度要高于IO设备处理数据的速度,为提高效率采用了中断,中断是一种消息传递,
进程:
程序:静态的,存放在磁盘里的可执行文件,实际上是一系列的指令集合;
进程:动态的,程序的一次执行过程;
qq是一个程序,不同的qq进程是打开了多个qq程序;他们是不同的进程,
线程是最小的程序,划分成线程更好;但不同线程之间应该具有优先级,
PID:区分不同进程的数据结构;是进程的身份证;
进程将在内存中占据一定的空间,获得一些CPU的时间片,这些都根据进程的优先级来,
所有的信息被放在PCB中,PCB是一种数据结构;操作系统所需要的所有信息都被放在PCB中,他是PCB存在的唯一标志,进程被创建时操作系统将会为他创建PCB,操作系统根据PCB进行各种进程的切换,硬件资源的划分等,PCB可能会使用数组,
进程的组成:PCB,程序段(指令段),数据段(产生的数据),
进程控制:
实现进程的状态转换,包括运行态,阻塞态,
进程的状态转换使用原语程序:该程序不能被中断;
使用就绪队列,阻塞队列来实现切换;这里考察链表的插入删除;
原语通过特权指令来实现原子性;
进程通信:消息传递(依靠特权指令,以及特定数据结构),共享存储(开辟存储区,对于存储区的访问必须是互斥的),管道通信(固定的两个进程间,根据管道内信息情况,读进程,写进程会陷入就绪态和阻塞态多种模式)
进程的通信需要特权指令 ;
锁的来历:
基于存储区的共享:共享存储;使用相关接口映射到进程本身的内存地址上;但是多个进程不能同时访问共享存储区,需要互斥访问,如何保证互斥呢?答案就是锁!
基于存储区的共享:共享存储区开辟后,如何读写是由进程本身来决定的,而不是操作系统,这种共享方式速度很快,是一种高级通信方式;
基于数据结构的共享:类似于全局变量,共享空间里存放的数据类型是固定的,不同的进程只能以特定的方式进行读取,速度慢,限制多,是一种低级通信方式;如int a[3],那么不同进程只能按int的方式来读取;
消息通信:包括直接通信和间接通信,直接通信依靠原语以及消息队列进行信息传递,
指名道姓;间接通信依靠内核区的特定空间作为传递中介;消息的发出和接收都需要特权指令;
管道通信:本质上是循环队列和缓冲区,写进程以缓冲区的形式看待管道,读进程以循环队列的形式看待管道;
各个进程对于管道的访问是互斥的;管道的大小是固定的,因此管道写满时,写进程将阻塞,直到读进程将数据取走,当管道为空时,读进程将阻塞,直到写进程往管道里写数据;才能唤醒读进程;管道是栈区,读完该区域内变量就会改变,因此同一信息不能多次读写,只能一次;
线程:
线程是CPU调度的基本单位,线程是轻量级进程,
用户级线程;
调度:根据优先级决定那个线程被调度,需要采用优先队列,大根堆小根堆的数据结构;但应该是链表的存储方式;
作业:一个具体的任务,创建一个进程
线程的运行队列需要使用循环队列;
高级调度:按一定的原则从后备队列中选择一个作业调入内存,创建进程;
根据进程的起点终点分为:
高级调度(外存调入内存),创建态到就绪态;
低级调度(内存到CPU),就绪态到运行态;
中级调度(暂时在外存中的到内存):就绪到就绪挂起态,阻塞到阻塞挂起,挂起的意思是进程被挪到外存中,当计算机内存资源不足时,需要把某些优先级低的进程调出内存,暂时安排在外存中,等内存空闲时在调入内存,暂时调到外存后进程状态为挂起状态,中级调度的频率比高级调度 要高,
作业:进程的前身,进程是在内存中有确定地址的指令集合,包括PCB,代码段,数据段,位置在内存中;而作业是在外存中的,只有被操作系统放到内存中才成为进程;作业并不是一经创建就成为进程的,而是在外存中形成作业队列(因此需要一个特殊的外存空间来储存这个队列,这样操作系统才能在指定的区域内读取作业队列),操作系统根据优先级(堆的数据结构)来选择某个作业进入内存,再分配了空间创建PCB后,作业成为内存;
进程的调度方式:高级调度,中级调度,低级调度;
高级调度:创建态->就绪态;外存->内存,将作业后备队列里的某个作业由外存放到内存中,创建为进程;
低级调度:就绪态->运行态,内存->CPU,就绪队列里的进程获得CPU的时间片,进入运行态,
中级调度:挂起态->就绪态,外存->内存,当内存空间不足时,某些进程或作业无法被放入内存,其代码段,数据段以及PCB将被映射到外存中,进入挂起态,等到内存足够时才被调入内存。
进程的调度就是为了进程的状态转换;
进程调度的时机:
特权指令相关的操作是不能进行进程切换(调度)的,除此之外都可以;
当进程访问了一些特殊的资源时,进程的调度可能不能顺利的进行,比如当进程访问了就绪队列时,由于非进程内存是共享资源,就绪队列所在内存区将会被上锁,那么此时进程的调度就无法进行,只有该进程访问结束后才能;进程的切换是相关数据结构的插入删除操作,因此别的资源被上锁时不会出现这种情况;
上段话说:进程在操作系统内核临界区中不能进行进程调度,切换;
临界资源:一个时间段内只能互斥访问的资源,称为临界资源;临界区就是访问临界资源的代码区,
进程切换与锁:除了操作系统内核临界区外都可以被进行调度;
操作系统内核临界区不能进行调度与切换,但一般进程处于临界区时是可以进行切换的,如某进程访问打印机,打印机属于临界资源,该切换也要切换,但内核程序临界区则不能切换;这里说的切换是指该临界区进程,即当进程处于临界区时是可以被切换的,但切换后仍然保留锁,但是内核临界区是不能被切换的,
进程的切换:一个进程让出CPU,另一个进程占用CPU,
进程切换完成的动作:
1.原来进程的PCB保存;
2.新进程的PCB恢复;
因此进程的切换是有代价的,过于频繁的进行进程的切换会使整个系统的效率降低;使得系统大部分时间都花在了进程的切换上,真正用于执行进程的时间减少;
注意:进程调度是相关就绪队列的插入删除操作,只有进程的切换才会让进程运行;
调度程序:
操作系统内专门的程序模块来处理进程的调度与切换;
让谁运行——调度算法;运行多长时间?——时间片大小;
闲逛进程:休眠时的运行进程,不进行任何读写访问操作,
调度算法:
先来先服务;短作业优先;高响应比优先;
针对单个进程的效率计算: 周转时间:完成时间-到达时间;
带权周转时间:周转时间/运行时间;//周转时间比运行时间大多少倍;
等待时间:周转时间-运行时间;
先来先服务(FCFS)
算法类型:非抢占式算法;
优点:算法实现简单,
缺点:对于短作业来说带权周转时间太大,体验不好;对长作业不利,短作业有利;
是否会导致饥饿:不会;
适用于非抢占式调度,按一般队列来;
短作业优先(SJF)
追求最少平均等待时间,最少的平均周转时间,最少的平均带权周转时间;
非抢占式算法;
高响应比优先(HRRN)
进程的并发性导致异步性,各进程以独立的不可预知的速度向前推进,当涉及到对共享资源的访问时,为了解决问题,就出现了锁,一个进程会对共享资源上锁,在进程完成前别的进程不能访问共享资源;共享资源包括数据库,内存空间,播放器;显示器;
共享包括:互斥共享方式,同时共享方式;
把一个时间段内只允许一个进程使用的资源称为临界资源,许多物理设备,数据,变量,内存缓冲区都属于临界资源;对临界资源的访问必须互斥的进行,只有某进程释放临界资源,另一个进程才能访问临界资源;
死锁的前提:进程在完成进入区上锁后,无法执行临界区,此时是否需要解锁?
互斥原则:
1.临界区空闲时,允许其他进入临界区的进程立即进入;
2.忙则等待;
3.优先等待;(不能饥饿)
4.当进程无法进入临界区时,应该立即释放处理机,
进程互斥的实现方法:全局变量来传递信息;包括bool数组和bool值;数组值表示自己是否访问临界区,int值则表示系统允许访问临界区的进程名,只有本进程允许加上系统允许,才能让系统允许进程访问临界区,上锁阶段的代码执行上锁和检查两个操作;
单标志法:使用一个全局变量,根据全局变量的值来决定进程是否访问临界区;缺点:容易造成系统浪费,违反了空闲让进的原则;
双标志检查法:违反了忙则等待的原则;
双标志后检查法:两个都是true,导致都不能访问了;饥饿
皮特森算法:
三个算法都无法实现让权等待的原则,即不会放弃CPU资源;
原语用关中断指令防止切换,关中断指令不适用于多处理机,只适用于操作系统内核进程,
TestAndSet指令
使用硬件实现,执行过程中不能被中断,只能一气呵成;
线程的理解:一个程序出现了若干个子程序,同时运行,也就在此时全局变量,变得有意义;这些子程序最后都在一个main()函数中;操作系统相当于一个大的程序;使用操作系统的全局变量(在堆区,静态区等的变量,)
进程互斥:锁:一个Bool的全局变量;
软件实现互斥的方法:将两个进程的代码交替的写在一起就会发现问题!
硬件实现互斥的方法:
中断屏蔽法:将每个进程访问临界区的代码设置为原语,即不被CPU中断,相应的也就不会出现进程切换;
缺点:不适用于多核处理机,只适用于操作系统内核进程,不适用于用户进程(如果用户进程删去了关中断指令,那么CPU将永远不会发生进程切换);
TestAndSet指令:
TSL指令使用
信号量:信号量是一个变量表示系统中某种资源的数量,如系统中有一台打印机,可以设置一个初值为1的信号量;信号量还可以是更复杂的记录型变量;
注:信号量属于不能随意更改的量,因此它是私有的,只能通过接口进行修改,这种接口是原语,分为wait(S),signal(S);
整形信号量:用一个整数型变量作为信号量,用来表示系统中某种资源的数量;
与普通整数变量的区别:对信号量的操作只有三种,初始化,P操作,V操作;即面向对象经过封装过的整型变量;整形信号量上锁的极值:
缺点:循环消耗时间;不满足让权等待的原则;除此之外进程之间没有优先级;
int S=1;//初始化信号量;表示当前系统中的打印机资源数;
void wait(int S)//wait原语,相当于进入区
{
while(S<=0);
S-=1;
}
void signal(int S)//signal 原语,相当于退出区
{
S+=1;
}
记录型信号量:
//记录型信号量;
struct semaphore{
int value;//剩余资源数;
struct process*L;//该资源的阻塞队列;
};
阻塞队列,就绪队列,有两个原语将进程放入阻塞和就绪队列中,
归纳:记录型信号量确保了信号量的源于操作而且遵守了让全等待的原则;
用信号量机制实现进程互斥,同步,前驱关系:
信号量完美展现了面向对象的设计原则,对数据的保护,通过权限和接口来实现;
信号量关键活动:
1.划定代码中的临界区;
2.设置互斥信号量mutex,初始化;
3.在进入区P(mutex)申请资源,若申请失败将进程移入阻塞队列;
4.在退出区V(mutex)释放资源,将阻塞态中的进程移入就绪态;等待运行;
5.P,V操作必须成对出现。
- 进程同步:各并发进程按要求有序进行;让本来异步并发的进程相互配合有序推进;
举例:由于某些原因,P2的5,6指令必须在P1的1,2指令完成后才能执行,那么我们就必须保证1,2在5执行前完成;
p1(){
1
2
3
4
}
P2(){
5
6
7
8
}
方法一:p1给p2解锁;方法就是p2在指令2完成后进入就绪态,之前都是阻塞态,阻塞态到就绪态的转换使用V操作
方法二信号量机制实现前驱关系:有时一件事情的发生需要多个事情做基础,在计算机中就是一个进程的发生需要多个进程的发生,即图的关系;
多个信号量机制相结合;
读者写者问题:
有读者进程和写者进程两组并发进程共享一个文件,当两个或两个以上的读进程同时访问数据时不糊产生副作用,但写进程和其他进程同时访问数据时会导致数据不一致的错误;因此要求:
1.允许多个读进程同时对文件进行操作;
2.只允许一个写进程往文件中写信息,
3.写进程完成写操作之前不允许其他读写操作;
4.写进程执行操作前,所有的读写进程全部退出(完成);
读者进程与消费者进程 不同:读者进程不会改变数据;因此多个读进程可以同时访问共享数据;
写进程必须再其他进程结束访问后才能在临界区进行写操作;
互斥关系:写进程-写进程;写进程-读进程;
读进程与读进程不存在互斥关系
semphore rw=1;//临界区的信号量;实现对共享文件的互斥访问;
int cnt=0;//记录当前有几个读进程在访问文件;
semphore mutex=1//用于保证对count变量的互斥访问;
semaphore w=1;//用于实现写优先;
写进程:
writer(){
while(1)
{
P(rw);//对rw上锁;
写文件;
V(rw);//解锁
}
}
读进程:
reader(){
while(1)
{
P(mutex);
if(cnt==0)
P(rw);//上锁;
cnt++:
V(mutex);
读文件;
P(mutex);
cnt--;
if(cnt==0)
V(rw);//解锁
V(mutex);
}}
P,V操作会将相关进程插入或移出就绪或阻塞队列;如果cnt!=0,读进程将直接访问临界区;这没有问题;
潜在的问题:只要读进程还在读,写进程就要一致阻塞等待,可能“饿死”,因此这种算法中,读进程时优先的;
李治军操作系统
BIOS 采用汇编的好处:可以严格控制内存,可以严格读取确定地址处的内容,C语言做不到。
开机第一件事:(关注CS:IP指向哪里,CPU执行什么命令)
1.CS,IP指向ROM BIOS映射区,执行指令,先检查硬件,RAM,
2.之后将磁盘0磁道0扇区(的内容,供512个字节)读入0x7c00处,设置CS=0x070c,ip=0x0000;CPU开始执行;改扇区称为引导扇区;
读取过程:
引导扇区的代码:
读取第一步:关注start:1~4行设置ds:di,es:si的地址,rep movw将引导区的代码挪动到内存的一个地址处(es:di处),此后继续顺序执行改代码,但由于挪动了位置(原本执行的地址在BIOS区,现在在es:di处,同样的代码,但不在一个位置了),因此使用jmpi go,INITSEG修改CS:IP的值,保证继续向下执行;
读取第二部:继续读取:关注从哪读取,读多少,读到哪,读完干什么?
从cl开始读,读到ex:bx;读多少呢(ax)al是扇区个数,cl是开始扇区,即将set-up的四个扇区读到ex:bx上,是第一步读取的上边。读完后引发中断,int 0x13.
打印LOGO:显示窗口,涉及到操纵显示器.
读如System模块;
以上命令都是在boot扇区内的命令,之后计算机将执行set-up扇区的指令,
总结
检查硬件,将操作系统读入内存内——这些指令都在BIOS内,因此开机后,将执行BIOS里的指令,完成后开始执行操作系统,命令包括:
检查硬件,读入操作系统代码(将set-up,system模块读入内存,之后执行set-up的命令,)打印LOGO四大部分。
可以看到分成了若干个进程,采用int中断指令来实现。
开机第二件事:执行setup模块的内容(操作系统的代码了)
set-up内容:完成信息的初始化,并改变操作系统的位置到0地址处,此后不再改变位置;并进入保护模式
start:读取信息,如读入内存的大小以方便管理内存,该值放在0x90002处;除此之外还有显卡啊参数,光标位置。
内存有多大:
int 0x15中断指令将获得物理内存的大小;获取的值放在ds:2处;ds的值在第一行得到。最终放在0x90002处,
do-move:将system模块(操作系统代码)移动到0地址处,这也是为什么一开始在0x90000处放置set-up模块。
mov ax,1;mov cr0,ax:切换到保护模式,由16位模式切换到32位模式;
此时CS的值相当于哈希表里的键,在GDT表里查到真正的基址,之后偏移与IP相加形成真正的地址;GDT表使用硬件电路实现。
GDT表由set-up完成,此后寻址,中断命令地址的寻址方式都发生了改变;都变成了查表。
System模块
P6
head.s跳转到main.c,
第一行进行压栈压入main.c的参数,第二行压入操作系统的main.c,之后跳转到set_paging,进行操作系统数据结构的初始化操作,该指令结束后操作系统开始执行,如果main.结束,将执行L6:死循环,因此main.c不会结束。
main.c的指令,最后一句如果申请进程失败,将返回。
mem_init,实行内存的初始化,初始化mem_map[]数组,mem_map[]数组表示内存页使用情况,0表示没有使用,每一页是4kb,因此每次>>12,end_mem来自于0x90002的数值。
最终
从开机到操作系统开始执行,需要经历:
boot->setup->head.s->main.c->mem.init四大过程,分别完成操作系统读入内存,初始化,保护模式切换,初始化等事件。