[计算机体系]操作系统

进程和线程的关系:
一个进程可以创建和撤销另一个线程,同一个进程中的多个线程可以并发执行。相对进程而言,线程是一个更加接近于执行体的概念,它可以与进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

进程和线程的区别:
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
1、简而言之,一个程序至少有一个进程,一个进程至少有一个线程。
2、线程的划分尺度小于进程,使得多线程程序的并发性高。
3、另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内容,从而极大的提高了程序的运行效率。
4、线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5、从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看作多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

死锁的必要条件,怎么处理死锁:
死锁:指多个进程因竞争共享资源而造成的一种僵局,若无外力作用,这些进程都将永远不能再向前推进。
产生死锁的原因:(1)竞争系统资源(2)进程的推进顺序不当
产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排他性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程应请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程—资源的饿环形链。

解决死锁的基本方法:
预防死锁:
资源一次性分配:(破坏请求和保持条件)
可剥夺资源:即当某进程新的资源未满足时,释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
避免死锁:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
检测死锁:
首先为每个进程和每个资源指定一个唯一的号码,然后建立资源分配表和进程等待表。
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态。
撤销进程:可以直接撤销死锁进程或撤销代价最小的进程,直至有足够的资源可用,死锁状态消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

Window内存管理方式:

先说说内存的概念
1.物理内存:即插在主板上的内存条。他是固定的,内存条的容量多大,物理内存就有多大(集成显卡系统除外)。但是如果程序运行很多或者程序本身很大的话,就会导致大量的物理内存占用,甚至导致物理内存消耗殆尽。
2.虚拟内存:虚拟内存就是在硬盘上划分一块页面文件,充当内存。当程序在运行时,有一部分资源还没有用上或者同时打开几个程序却只操作其中一个程序时,系统没必要将程序所有的资源都塞在物理内存中,于是,系统将这些暂时不用的资源放在虚拟内存上,等到需要时在调出来用。
当程序运行时需要从内存中读出这段程序的代码。代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存的概念。把哪些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存(物理内存)中。这个就是内存管理所要做的事。内存管理还有另外一件事需要做:计算程序片段在主存中的物理位置,以便CPU调度。

内存管理的主要方式
1、块式管理
把主存分为一大块、一大块的,当所需的程序片段不在主存时就分配一块主存空间,把程序片段load入主存,就算所需程序片段只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但是易于管理。
2、页式管理
把主页分为一页一页时,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比快式管理高很多。
3、段式:
把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率又比页式管理高很多,但是也有另一个缺点。一个程序片段可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上(计算机最耗时间的大家都知道是I/O吧)
4、段页式管理
结合了段式管理和页式管理的优点。把主页分为若干页,每一页又分为若干段。

进程的几种状态:
运行态:进程占用CPU,并在CPU上运行。
就绪态:进程已经具备运行条件,但是CPU还没有分配过来。
阻塞态:进程等待某件事发生而暂时不能运行。

这里写图片描述

当然理论上上述三种状态之间分为六种情况:
运行->就绪:这是由调度引起的,只要是进程占用CPU的时间过长
就绪->运行:运行的进程的时间片用完,调度就转到就绪队列中选择合适的进程分配CPU
运行->阻塞:发生了I/O请求或等待某件事的发生
阻塞->就绪:进程所等待的事件发生,就进入就绪队列
以上四种情况可以互相正常转换,不是还有两种情况吗?
阻塞->运行:即使给阻塞进程分配CPU,也无法执行,操作系统在进行调度时不会在阻塞队列进行挑选,其调度的选择对象为就绪队列。
就绪->阻塞:因为就绪态根本就没有执行,何来进入阻塞态?

IPC几种通信方式:

为什么要进行进程间的通讯(IPC (Inter-process communication))

数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间
共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

linux常用的进程间的通讯方式:
(1)管道(pipe):管道可用于具有亲缘关系的进程间的通信,是一种半双工的方式,数据只能单向流动,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,同时除了具有管道的功能外(也是半双工),它还允许无亲缘关系进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。

(3)信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事件发生了,除了进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
(4)消息队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点
(5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(6)内存映射:内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
(7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)套接字(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。

虚拟内存:
进程执行的前提是进程的全部页都已经在内存中。
虚拟内存技术就解决了这个问题,不需要一个进程把全部的页都放在内存才能执行。
动态载入虽然也解决了这个问题,但是需要程序员完成,非常麻烦。
其实我们发现,一个程序包含了很多条件语句还有异常处理等,这些代码肯定要选择执行的。所以全部调入会显得冗余,增加了磁盘到内存的传输时间。并且减少一个进程的空间可以使得内存容纳更多的进程,增加
多道程序的度。
虚拟地址空间就是一个逻辑视图,通过MMU映射到物理地址空间。
稀地址空间:进程的堆和栈之间的空白的虚拟地址空间。
进程虚拟地址空间内容:堆向上伸展,栈向下伸展,代码和数据。中间放共享库。
虚拟内存可以通过共享页将文件或内存共享给多个进程。每个进程都认为库位于自己的虚拟地址空间。
按需调页:一个进程分成多个页,在需要时调入相应的页,也称为懒惰交换。
按需调页对于性能的影响很大,因为会发生页错误,所以尽量减少页错误。
交换是将整个进程看成一个整体,通过交换程序进行交换,而懒惰交换是将一个进程看成多个页,通过调页程序进行交换。
而我们通过有一张页表维护有效-无效位来区分哪些页在内存,哪些页在磁盘。如果在内存且页号有效,则记为v,如果在磁盘上,则为i。
如果访问无效位的页,则通过页错误方式来解决即如果发现要访问的页不在内存,则:
1.终止进程,保存现场,并通知操作系统。
2.寻找空闲帧,如果没找到,用到页面置换技术。
3.将磁盘上的页调度到相应帧中。
4.重新开始指令。
纯粹按需调页:不断页错误,不断将页调入内存。
页错误发生也是有不同代价的,如果在取指时就发生,则只需要重新取指,如果在获得操作数时发生,则需要重新取指,译码,取操作数,代价就大了很多。
但是如果ADD A,B,A A+B->A,当写入A时出错,重新执行这条指令,但是A已经被改变了,所以结果会不对。解决方案是用临时的寄存器保存原来的值。
有效访问时间:(1-p)内存访问时间+p页错误时间。
页错误的具体过程:
1.告知操作系统。
2.保存现场。
3.确定页表的i代表页错误,因为i可能代表页不存在。
4.磁盘读入空闲帧。
5.修改页表和帧表。
6.恢复。
页错误处理时间主要是:
1.处理页错误。
2.读入页。
3.恢复。
磁盘专门有个交换空间,用于交换数据,处理速度要快。可以在交换空间执行按页调度。
写时复制:创建进程的开始可能不需要按需调页,子进程共享父进程的资源和地址空间。当任何一个进程写入时,就为另一个进程创建一个页面副本。fork
分配空闲页当作页面副本也有技巧,可以通过空闲缓冲池,分配时采用按需填零即只在分配之时清零,清除以前内容。
增加多道程序的度数会有“过度分配”的情况,当页错误发生,需要调入页面,但是内存中没有空闲帧,那么必须页置换。页置换是将页错误的时间加倍了,因为要换入和换出。
基本页置换:
按需调页中需要在内存中有一块空闲帧来存放调入的页,如果没有空闲帧,就要发生页置换。
页置换过程:挑一个牺牲帧,并更新帧表和页表。
可以用修改位或脏位降低开销,当修改位被设置,则意味着这个页从磁盘读入后已被修改,那么就需要写回,如果修改位没被修改,则不需写回。
引用串用来评估一个页置换算法的性能:内存引用的页号序列。如1,2,3 表示引用页号1,2,3。
当不考虑内存帧的数量的情况下,只有页的第一次引用发生页错误,但是如果帧的数量限制,则会复杂的多。
1.FIFO页置换:
将内存的页看成是一个FIFO队列,替换的页是最旧的页即最先调入内存的页,加入的页放到队列的尾部。这不是一个好办法,因为最先进入的页可能是一直要用到的页,如果替换的是活动页,则此页会马上又一次页
错误,换回内存。
Belady 异常:页错误数随着内存帧数的增加而增加。
2.最优置换 OPT
不会产生Belady异常,并且页错误率是最低的。因为他替换的是将来最久才被用到的页。
3.LRU置换
选择的页是最近最少使用的页。
对于引用串,如果倒转,LRU置换和OPT置换的页错误率是一样的。
LRU实现需要硬件支持:
1.计数器。每个页帧会附带一个最近使用时间,置换出的页就是时间最小的。
2.栈。栈顶加入页,栈尾写出页。
4.近似LRU置换
因为LRU实现需要许多硬件支持,因此可能只能近似的实现LRU。
通过添加引用位,每当一个页被引用,该为就置1.通过检查该位,可以知道哪些页未被使用。
(1)附加引用位算法。
在一个页的引用位保留8位,初始为0000 0000,第一次被使用,则1000 0000,第二次未被使用,则0100 0000.以此类推。找到这8位化为10进制后最小的页置换。
(2)二次机会算法。
也是在页中保留一个引用位,如果为1,则给予第二次机会,如果为0,则直接置换。
(3)增强型二次机会算法。
将引用位和修改位作为有序对,(0,0)表示未被引用和修改,则说明是最佳的LRU置换页。
5.基于计数的页置换
(1)Least frequently used LFU:保留一个计数器,记录引用次数。缺点是以前一直用,但是现在不用,则仍会留在内存。
解决方法:定期将计数右移一位。
(2)MFU:最常使用页置换,即把计数最大的页换出。
6.页缓冲算法
7.应用程序:不需要提供虚拟内存。
生磁盘:没有文件系统的磁盘。
接下来讲帧分配,帧分配的最小数量是根据计算机体系结构,最大数量是根据物理内存数量。
分配方法:
1.平均分配。
2.比例分配。根据优先级和进程大小。
帧分配和页置换是相关联的。
全局置换:一个进程从任意帧中选择一个置换,不管该帧是否已分配。 缺点是不能控制页错误率。
局部置换:仅从自己的分配帧中置换。
颠簸:频繁调页。
颠簸发生情景:
当CPU使用率降低时,CPU导入更多进程,如果用全局置换,如果一个进程需要很多帧,因此发生页错误会从其他进程中拿一点,而如果其他进程也页错误,则会从其他进程中拿帧,因此进程都到进程的等待调页设备
队列,CPU使用率降低,恶性循环。
采用局部置换也不能防止系统颠簸。因此我们必须要预先就分配足够多的帧。因此引入了局部模型。
局部模型是那些经常使用的帧,因此进程分配的帧一定要大于局部模型的帧大小。
1.工作集合模型:working-set model 。基于局部性。
工作集合窗口:预设△是最近用到的页。对于△的取值很关键。
工作集合:在工作窗口中使用的页的集合。
如果总的可用帧数量为W,进程i的工作集合为WSi,则W-WSi为其余的可用帧,直到<0就不分配。
如果在内存中的进程的工作集合之和大于可用帧数量,则系统颠簸。
2.页错误频率PFF。
预先设定页错误率的上下界。当页错误率大于上界,则为进程分配更多的帧。这样非常直接。
当对于一个新的局部进行按需调页时,页错误率达到波峰。
如果调用open(),需要先找目录,再找文件,因此需要内存访问+磁盘访问。如果将文件I/O作为内存的普通访问,则称为文件的内存映射。
在内存中的文件可以修改就不立即写回。
前面讲到内存映射文件,下面是内存映射I/O,一组内存专门映射到设备寄存器,读取该块地址如同读取设备寄存器。
内核内存的分配与用户进程的分配内存不同,用户进程都是按页分配,但是内核分配需要从空闲内存池中获取,而不是通过空闲帧链表中获取。
1.Buddy系统。
通过对于连续物理页进行二分,以至于能够最小包容内核需求内存即可。如如果内核需求23KB空间,则最小得到的空间为32KB,因为要是2的幂。
优点:快速合并。
缺点:内部碎片。
2.slab分配。
slab是由一个或多个物理页组成。
cache都有一个或多个slab。而一个内核数据结构都有一个cache。
cache存储内核。内核需要空间就直接从cache中获得。
slab三种状态:满,空,部分满。
slab先从部分满的slab中分配,再从空的slab中分配。否则,分配新的slab,并赋给Cache。
slab没有内部碎片而且能够快速分配。因为已经Cache中创建好了空间。

虚拟地址、逻辑地址、线性地址、物理地址的区别:
物理地址是最简单的了。就是指SDRAM的地址。例如ARM的平台上可能使用64M,物理地址就是从0x30000000到0x34000000
虚拟地址又被称为线性地址,32bit的整数表示0-4G的地址空间。
逻辑地址在X86中使用段:偏移的方式来表示指令或是数据的地址
MMU使用段单元把逻辑地址转为线性地址。paging unit把线性地址转为物理地址

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值