一、冯.诺伊曼体系结构中是把计算机划分为:
输入设备,输出设备,存储器,控制器,运算器.
输入设备主要是键盘,鼠标,
输出设备主要是显示器,打印机
控制器与运算器我们一般全称为CPU
存储器主要指硬盘与内存
为什么windows上的应用软件都差不多一样(无论什么语言开发的软件)?
我们开发软件时不会直接面对硬件(只有开发操作系统或驱动程序才直接面对硬件),操作系统封装了硬件的细节信息,应用软件只是使用操作系统提供的API去间接的操作硬件,以windows为例,我们发现运行在上面的软件不管是啥编程语言开发的,用起来貌似都差不多,界面上基本上都是那个些控件,比如些菜单栏,工具栏,下拉列表,单选,多选按钮之类的几乎都一个样,原因很简单,实际上不管啥编程语言,你自己写的函数或者调用一些类库,如果要使用那些硬件资源,最后都归结于去调用windows的API。
所以操作系统是在硬件的基础上抽象一层,只提供给你一些API,然后各种编程语言又在API的基础上再封装一层,提供给你一些语言特性和类库,库函数给你用。
数据处理和数据显示
看下windows的API(实际上可以简单的看成一些C语言库函数),我们发现绝大部分API函数都仅是实现处理数据和显示数据这两功能。API中有很多针对鼠标和键盘的函数,我们可以把这看成是输入数据(信息),消息机制相关的函数就是处理数据,进程和线程也可以看成在内存中处理数据。
然后API中窗口,子窗口控件,菜单,GDI,文字和字体,位图那一堆堆的函数可以看成是显示数据,把数据通过一些好看点的图形界面显示出来。
以前很多人都认为程序 = 数据结构 + 算法,在那会没太注重用户体验,没太注重GUI时是很恰当.现在应该说程序= 数据处理(数据结构和算法) + 数据显示。
数据存储(硬盘和内存)
实际上我们平时在程序中说要分配一块内存(比如new 或malloc),或者操作内存(读写数据),实际上没有真正的直接去操作内存,而只是告诉操作系统我要做这些操作,然后操作系统再给你去直接操作内存;同样我们操作硬盘时,实际上也是操作系统去读写硬盘。
在windows中有所谓的虚拟内存技术,实际上就是由于我们不会直接操作内存才会有这种可能,我们当需要多少内存时,比如4G,而实际内存才2G,但操作系统却说可以有4G内存分配给你,它是把一部分硬盘也当作内存给你分配(至于把多少硬盘空间当作内存用这是可以手动配置大小的)。 由于CPU读取数据时只会在内存中进行(不是必须非得这样,因为如果直接读取硬盘速度会很慢),所以当你使用了虚拟内存后数据会在内存和硬盘之间来回的交换。
---------------------------------------------------------------------------------------------------------------
刚开始的时候电脑功能相当简单,而且用电脑的也都是些高手专家,对硬件相当的了解,此时也没有啥操作系统(实际上可能显示器也可以不需要),电脑磁盘上除了保存一些供你调用的标准库函数外啥都没有,开机后CPU仍然静静的呆着不干嘛,内存里也是空的啥东东都没有(现在我们用的电脑一开机,内存至少会加载操作系统代码,所以永远不可能是空的)。
此时你要做啥操作就敲入些命令,然后就调用保存的库函数,自然是先把库函数加载到内存,然后再在CPU上面跑,跑完了可能就是打印些啥信息出来(此时还没文件的概念,磁盘上不会保存你的结果数据),然后接下来CPU就休息去了,内存也空了,你不去调用库函数时,电脑就傻傻的呆了啥都不干。
像上面说的你敲入些命令时电脑才给你干活,不敲入命令电脑什么都不做,这样效率太低了,于是做了些改进,首先是出现了操作系统的雏形,打开电脑后会加载一些操作系统的代码常驻在内存中,这或许就是守护进程的前身。
分时,多进程
前面说的三种情况都是一次只能一个用户程序在跑,此时有多少内存就用多少,不够用就报错呗,很简单,没涉及啥太复杂的技术,但是这样显然不能充分利用CPU,内存资源.因为内存变得越来越大,而一个程序可能只用占一点点内存,这样会空出一大片,而且CPU也是跑得贼快的,一眨眼就跑完了,只有你进行那些IO操作时可能就慢的像蜗牛了,于是有人就琢磨着,应该可以让几个程序同时跑,一个程序对应一个进程,都同时在内存中占一片地盘.然后就通过分时轮流使用CPU.
此时操作系统做的事就多很多了啊,首先是要保证每个进程的独立,不能让一个进程轻易使用别的进程的内存了,然后有时还得实现进程间的通信,要防止死锁之类的麻烦事。
进程加载进内存,等待获得CPU,然后运行
内存分配
当出现了多进程的概念后,自然就需要想出很多花样出合理的分配和利用内存这个宝贵的东东,操作系统内存管理中涉及到的术语可能看得人头大,实际上用通俗点的话归纳下就做两方面的事:
1.合理的给每个进程分配一块内存,当内存满了时把一部分进程的内存切换出去放硬盘上,恰当的时候再从硬盘切换回内存
2.虚拟内存技术,实际上就是把硬盘也当内存用,只不过要多做些切换罢了,在硬盘和内存之间切换。
内存地址
先不管内存怎么分块,我们首先会想到要用内存时该怎么去访问? 你可能说肯定是通过地址嘛,硬盘访问就是通过一个地址,然后用磁头去读写嘛。
内存与硬盘间来回切换,会导致内存地址变化
如果只让一个进程在内存中,开始跑时全部加载,然后跑完了再释放内存,这样像硬盘一样直接通过内存的实际地址访问是没太多问题的; 但如果是多进程共用内存,并且还会在硬盘和内存间切换,此时就会有问题,假如你的程序跑了一部分,然后全部切换到硬盘,那些保存的信息里面有涉及到内存地址,等再切换到内存中时,你之前占的那内存可能被别的进程占用了,而系统分给你的是另外一块内存,那样的话你根据之前那些内存地址信息再读写数据时就会出错,咋整呢?
内存物理地址与逻辑地址
像在C,C++中我们一般用指针来指向内存地址,那你可能会问指针指的内存地址是内存的物理地址吗 ?
如果是实际地址的话就出现上面说的问题了, 指针指向的实际上是一个逻辑地址,你可以这样想,在一个进程内我们先把所有指针指定一些固定的值(也就是逻辑地址),比如从0开始递增,然后实际上加载到内存中时(每次加载时的起始地址可能不一样),再用逻辑地址做偏移量加上内存中的实际起始地址就是实际的内存地址了,你可能会奇怪那么每个进程的内存块的起始位置放哪里啊? 那个起始地址值是放在寄存器中,寄存器实际上可以看成一个速度更快的内存,在CPU呆一起,所以划分模块时我们一般分到CPU那一块去了。
当然从逻辑地址映射到物理地址肯定不止一种方法的,如果用到了分页技术,我们还可以把每个逻辑地址映射到物理地址中的不同页面(page)。
不过上面说的也不是太准确,我们只能说我们用的绝大部分应用程序中涉及到的指针指向的内存地址是指向逻辑地址的,实际上也有些特殊的情况指针会指向内存物理地址,比如在嵌入式系统中,可能就一个进程用那内存,不用做啥切换,我们就可以用指针直接指向实际的物理内存地址,另外一般的桌面电脑中,内存中会划分出一块固定的内存来加载操作系统代码,操作系统中一部分内核程序会常驻内存(守护进程),一直呆一个固定的地方,不会被切换到硬盘上去,此时指针也可以直接指向物理内存。
虚拟地址空间
虚拟地址与逻辑地址关系
毫无疑问除了物理地址是真实存在的内存地址外,其他地址都是我们虚构出来的,我们知道操作系统把硬件封装抽象了,我们编程时基本不会直接面对硬件,那我们可以把虚拟地址空间看成是抽象出来的内存,当每个程序运行时(在windows中,linux或其他系统我还不确定)系统会给程序分配一个虚拟地址空间,就相当于给你一个内存,你尽量去用就是,不用管其他的事了,当你申请一块内存后会返回内存地址,这个就是逻辑地址,逻辑地址就是指向虚拟地址空间中某块地址。
虚拟地址空间大小
在32位windows系统中,可用的虚拟地址空间是2的32次方,也就是4G,你可能会奇怪这是怎么算出来的啊,因为32位系统顾名思义是因为处理器一次只能处理32位bit的信息,这样一来地址总线传的内存地址最多只能用32个bit来表示,所以只能表示4G的内存地址了,不过我们会把其中的2G分给系统空间,只留下2G给用户空间,所以任何一个win32应用程序运行时系统会给分配2G的虚拟地址空间, 不过你可以指定让用户空间超过2G,最大可增至3G,不过这样系统空间就仅有1G了。
在64们的windows系统中,可用的虚拟地址空间是2的64次方,貌似是40多亿G,但显然我们用不了这么多,用了也没意义,所以我们只用到这些虚拟地址空间的一部分,8T用户空间, 248T系统空间,所以任何一个win64应用程序运行时系统会给分配8T的虚拟地址空间。
我们知道当多进程在内存中共存时,如果内存足够大都够用,大家相安无事自然是最理想的事了.不过现实往往没那么美好.有些时候内存会满了,不够用了.此时必须将进程从内存移到硬盘中去.有空间时可能又会被移回内存来.专业点的说法叫 滚出(roll-out),滚进(roll-in).或者叫换出,换入.
在进行换出,换入时你可能会想到两种方法
(1)以进程为单位换出换进,这是最简单最容易想到的.但是显然不够灵活,因为一个进程所需的内存空间较大.所以这种交换技术现在用的不多
(2)只交换进程的一部分. 通过页式或段式内存管理先把进程的虚拟地址空间划分为若干页面或段,这样交换时就可就交换页或段.