操作系统基础回顾

CPU的分配

早期的计算机CPU资源非常珍贵,如果一个CPU上只能运行一个程序,那么当程序进行IO操作时,CPU就停下来了,这在当时可谓是暴敛天物。于是人们考虑在一个CPU上运行多个程序,并且有一个监控程序,当监控程序发现正在使用CPU资源的程序在进行等待耗时操作时,就立马调用就绪的正在等待CPU资源的另一个程序。这个设计看起来大大提高了CPU资源的利用率,但由于简单粗暴不分程序的轻重缓急的调用方法,使得用户体验非常不好。于是经过稍微改进,即让每个程序都运行一定的时间后主动放弃CPU资源,以让其他程序运行。这就是最初的分时系统。但如果一个程序在进行一个非常耗时的操作,一直霸占着CPU资源不放,那操作系统也没有办法,这很有可能会进入死循环导致系统死机。这在人们看来是不可接受的,所以就有了后来的多任务系统,操作系统接管了所有的硬件资源,并运行在一个受保护的级别,所有的应用程序都以“进程”的方式运行在比操作系统更低级别的环境。每个进程都有自己独立的地址空间,使得进程之间的地址空间相互隔离。每个进程由操作系统统一分配资源,根据进程的优先级的高低都有机会得到CPU,但是如果运行时间超过了一定的限制,操作系统会暂停该进程,将CPU资源分配给其他等待运行的进程。这种CPU的分配方式即所谓的抢占式。如果操作系统分配给各个进程的运行时间很短,使得CPU在各个进程间快速切换,就给用户造成了多进程同时运行的假象。

更好的利用内存

早期的计算机程序是直接运行在物理内存上的,为了有效的利用硬件资源,必须同时运行多个程序,CPU的利用率会比较高,但如何将有限的物理内存分配给多个应用程序,并且要解决三个问题:1、地址空间不隔离;2、内存使用率低;3、程序运行的地址空间不确定。

有人说过一句名言:“在计算机科学领域的所有问题,都可以通过增加一个中间层来解决”。我们可以通过增加一个“虚拟地址”层,将虚拟地址映射到物理地址的方法来解决地址空间不隔离的问题。

最开始人们使用的是“分段”的方法,让我们来看看是什么。

基本思路是将程序所需要的一段虚拟地址空间通过某种方法映射到物理地址空间,比如程序A需要1MB大小的内存空间,虚拟地址从0x00000000~0x00100000,映射到物理地址空间假设为0x00100000~0x00200000,程序B需要100MB大小的内存空间,虚拟地址从0x00000000~0x06400000,映射到物理地址空间假设为0x00C00000~0x07000000。当程序A访问地址0X00001000时,CPU会将地址转换为0x00101000,即虚拟地址空间的字节一一对应物理地址空间的字节。

“分段”的内存分配机制解决了应用进程内存不隔离及内存地址不确定(大多数数据访问指令跳转时的目标地址是固定的,所以要求确定的地址),但对于内存使用率低的问题还是没有解决。假如有程序C需要500MB的内存,当内存不足时需要进行内存与磁盘空间交换,这种分段的方法的基本单位是以程序为单位,势必会造成大量的磁盘访问操作,造成效率严重低下。但是当一个程序在运行时,只有部分内存在频繁的访问,其他的内存段并没有用的,所以需要更小粒度的内存分割映射的方法,这就是“分页”机制,一起看一下。

分页的基本思路是将内存划分为固定大小的页,页大小由硬件决定,或支持多种页大小的硬件,由操作系统决定页的大小。目前几乎所有的PC上的操作系统都是用4KB大小的页。我们把进程虚拟空间中的页脚做“虚拟页”,把物理内存中的页叫做“物理页”,把磁盘中的页叫做“磁盘页”。当应用程序动态申请内存时,首先获取到的是虚拟内存页地址,系统并不在此时分配真正的内存空间。当程序访问这块内存时,系统发现没有实际的物理内存与之对应,于是产生缺页异常,系统进行虚拟地址到物理地址的映射关系建立,进行真实的内存申请分配。当内存使用紧张超过一定的阈值时,系统将会把不常用的物理页数据或之前从磁盘页中读取到物理内存中的数据交换回写放入磁盘页中,当程序再次访问时,系统捕获到页错误异常,再次将磁盘页换入到物理内存中,并建立映射关系。虚拟地址到物理地址中间的转换需要硬件支持,几乎所有的硬件都采用MMU(内存管理单元)来进行页映射,将线性虚拟地址转换为物理地址。具体的物理页映射机制及内存分配机制比较复杂,我们后面再单独分享。

进程中的某些虚拟页映射在磁盘页中,有些页映射在物理页中,有些并没有映射。当不同进程的虚拟页映射在统一物理页上时就可以实现内存共享。同时“分页”机制还可以实现内存保护,可以对页的读写属性设置权限(只有操作系统有权限设置)。

进程与线程

上面我们提到了进程的概念,而在现代PC中由于多核CPU技术的发展,多线程技术应用的更加普遍,下面重点看一看线程相关知识。我们先来看一看进程与线程的定义。

进程:是CPU分配资源的基本单位,进程中可以包含一到多个线程。

线程:是CPU运行与调度的基本单位,同一进程中的线程可以共享进程资源(代码段、数据段、堆、文件句柄等),进程中的线程崩溃意味着这个进程崩溃,而一个进程崩溃不会影响其他进程。线程也拥有自己独立的资源,如栈空间、PC指针、寄存器等。

线程安全

多个线程同时访问一个共享资源,可能造成严重的错误后果。

某些数据访问操作(比如C语言中自增++运算)被编译为汇编代码后不止一条指令,这就可能会出现当一个操作还没执行完成时被操作系统打断执行其他的程序,等再继续回来执行的时候可能这个值已被更改,造成错误。所以我们在线程间操作共享资源时,需要使用原子操作(单条指令)或使用同步机制(信号量或锁)来保证数据的一致性。同时,某些编译器在编译时会优化代码,这就有可能在程序编译时改变代码运行顺序而导致程序运行结果错误,可使用volatile关键字阻止过度优化。附带一篇linux原子操作API文章:https://www.cnblogs.com/wqwlinux/p/6971289.html

同时多个线程可能调用执行同一函数,这就要求函数需要可重入。

一个函数被重入,表示这个函数没有执行完成,由于外部因素或者内部调用,又一次进入函数执行。一个函数要被重入,只有两种情况:

1、多个线程同时执行这个函数;2、函数自身调用自身。

可重入函数表明该函数经过重入之后不会产生任何不良后果。可重入函数的特征如下:

1、不使用任何局部静态变量或全局非const变量。

2、不返回任何局部静态变量或全集非const变量。

3、不依赖于任何单个资源的锁。

4、不调用任何不可重入的函数。

线程安全是在多线程编程中的最重要的环节之一,各位同道一定要慎之又慎。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值