由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级——用户态和内核态。这两种状态的主要差别在于:进程所能访问的内存空间和资源是否受到限制。
用户态切换到内核态的3种方式:
所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等。这时候需要将用户态切换为内核态,这种机制叫系统调用, 在CPU中的实现称之为陷阱指令(Trap Instruction)。系统调用过程如下:
共享内存:
顾名思义,共享内存就是两个进程同时共享一块内存,然后在这块内存上的数据可以共同修改和读取,达到通信的目的;共享内存是最快的ipc方式;共享内存常与信号量进行配合使用。
信号量:
信号量是一个控制资源访问的标识符 (简单来说就是一个计数器),具有原子性,主要是用来做PV操作,实现进程间同步。
PV操作是一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关,P表示通过的意思,V表示释放的意思。
句柄可以简单理解为数据:fd[0],fd[1],也就是两个分别用来读和写的文件描述符。
父子进程:通过fork函数创建的新进程是原进程的子进程。
兄弟进程:同一个进程多次fork产生的子进程之间互为兄弟进程。
FIFO文件是一种特殊设备的文件形式,在文件系统中可以看到。程序中可以查看文件stat结构中st_mode成员的值来判断文件是否是FIFO文件。
消息队列:
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息不一定要以先进先出的次序读取,可以实现随机查询。消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
套接字(Socket):
套接字是网络编程的api,通过套接字可以不同的机器间的进程进行通信,常用于客户端进程和服务器进程的通信。
查看进程状态ps -ef
。
查看cpu状态 top
。
Cpu(s):
0.1%us 用户空间占用CPU百分比
0.0%sy 内核空间占用CPU百分比
0.0%ni 用户进程空间内改变过优先级的进程占用CPU百分比
99.9%id 空闲CPU百分比
查看占用端口的进程号netstat -an|grep
。
查看内存:free -m
VIRT:virtual memory usage 虚拟内存
1、进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据等
2、假如进程申请100m的内存,但实际只使用了10m,那么它会增长100m,而不是实际的使用量
RES:resident memory usage 常驻内存
1、进程当前使用的内存大小,但不包括swap out
2、包含其他进程的共享
3、如果申请100m的内存,实际使用10m,它只增长10m,与VIRT相反
4、关于库占用内存的情况,它只统计加载的库文件所占内存大小
SHR:shared memory 共享内存
1、除了自身进程的共享内存,也包括其他进程的共享内存
2、虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小
3、计算某个进程所占的物理内存大小公式:RES – SHR
4、swap out后,它将会降下来
DATA
1、数据占用的内存。如果top没有显示,按f键可以显示出来。
2、真正的该程序要求的数据空间,是真正在运行中要使用的。
从功能上讲,交换分区主要是在内存不够用的时候,将部分内存上的数据交换到swap空间上,以便让系统不会因内存不够用而导致oom或者更致命的情况出现。swap空间是磁盘上的一块区域,可以是一个分区,也可以是一个文件,或者是他们的组合。
在Linux上可以使用swapon -s命令查看当前系统上正在使用的交换空间有哪些。
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。
上下文切换(Context Switch)是一种将CPU资源从一个线程分配给另一个线程的机制。从用户角度看,计算机能够并行运行多个线程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
进程切换分两步:
1.切换页目录以使用新的地址空间。(每一个进程拥有自己独立的内存空间,而线程共享进程的内存空间。)
每个进程有自己的页目录和页表,所以每个进程的地址空间映射的物理内存是不一样的。
2.切换内核栈和硬件上下文
内核在创建进程的时候,会为进程创建相应的堆栈。每一个进程都有两个栈,一个用户栈,存在于用户空间;一个内核栈,存在于内核空间。当进程在用户空间运行时,CPU堆栈指针寄存器里面的内容都是用户栈地址,使用用户栈;当进程在内核空间时,CPU堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。
当进程因为中断或者系统调用陷入到内核态时,进程所使用的堆栈也要从用户栈转到内核栈。进程陷入到内核态后,先把用户态堆栈的地址保存在内核栈之中,然后设置堆栈指针寄存器的内容为内核栈的地址,这样就完成了用户栈向内核栈的转换;当进程从内核态恢复到用户态之后时,在内核态之后的最后将保存在内核栈里面的用户栈的地址恢复到堆栈指针寄存器即可。
尽管每个进程可以拥有属于自己的地址空间,但是所有进程必须共享CPU寄存器,因此,在恢复一个进程的执行前,内核必须确保每个寄存器装入了挂起进程时的值。
进程恢复执行前必须装入寄存器的那一组数据成为硬件上下文。
对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
高级调度:(High-Level Scheduling)又称为作业调度,它决定把后备作业调入内存运行;
低级调度:(Low-Level Scheduling)又称为进程调度,它决定把就绪队列的某进程获得CPU;
中级调度:(Intermediate-Level Scheduling)又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
响应时间: 从用户输入到产生反应的时间
周转时间: 从任务开始到任务结束的时间
CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待的时间。
调度算法:
FIFO或First Come, First Served (FCFS)先来先服务
调度的顺序就是任务到达就绪队列的顺序。
公平、简单(FIFO队列)、非抢占、不适合交互式。
未考虑任务特性,平均等待时间可以缩短。
优先权调度
每个任务关联一个优先权,调度优先权最高的任务。
注意:优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象。
Shortest Job First (SJF)
最短的作业(CPU区间长度最小)最先调度。
SJF可以保证最小的平均等待时间。
Shortest Remaining Job First (SRJF)
SJF的可抢占版本,比SJF更有优势。
Round-Robin(RR)时间片轮转
设置一个时间片,按时间片来轮转调度。
优点: 定时有响应,等待时间较短;缺点: 上下文切换次数较多;
时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
多级队列调度
按照一定的规则建立多个进程队列
不同的队列有固定的优先级(高优先级有抢占权)
不同的队列可以给不同的时间片和采用不同的调度方法
存在问题1:没法区分I/O bound和CPU bound;
存在问题2:也存在一定程度的“饥饿”现象;
多级反馈队列
在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务。
可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”。
最通用的调度算法,多数OS都使用该方法或其变形,如UNIX、Windows等。
所谓的逻辑地址,是指计算机用户(例如程序开发者),看到的地址。
例如,当创建一个长度为100的整型数组时,操作系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。由于整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。事实上,逻辑地址并不一定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。
虚拟内存 使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
在内存管理中,内部碎片是已经被分配出去的的内存空间大于请求所需的内存空间。
外部碎片是指还没有分配出去,但是由于大小太小而无法分配给申请空间的新进程的内存空间空闲块。
页式虚拟存储系统存在内部碎片;段式虚拟存储系统,存在外部碎片
为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页往往装不满,于是形成了内部碎片。
为了共享要分段,在段的换入换出时形成外部碎片,比如5K的段换出后,有一个4k的段进来放到原来5k的地方,于是形成1k的外部碎片。
同步,是指散步在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。
缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾。当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。
缓存的工作原理是当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。
按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。
操作系统本质上是在硬件上运行的软件系统,用于管理计算机硬件与软件资源的程序。
它可以的简单的分为两个部分,外壳和内核。外壳负责与用户进行交互,内核负责与硬件进行交互。
我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成。
这些系统调用按功能大致可分为如下几类:
设备管理。完成设备的请求或释放,以及设备启动等功能。
文件管理。完成文件的读、写、创建及删除等功能。
进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
进程通信。完成进程之间的消息传递或信号传递等功能。
内存管理。完成内存的分配、回收以及获取作业占用内存区大小及地址等功能。
简单分为连续分配管理方式和非连续分配管理方式这两种。连续分配管理方式是指为一个用户程序分配一个连续的内存空间,常见的如 块式管理 。同样地,非连续分配管理方式允许一个程序使用的内存分布在离散或者说不相邻的内存中,常见的如页式管理 和 段式管理。
分页机制和分段机制的共同点和区别?
在分页内存管理中,很重要的两点是:
为了提高内存的空间性能,提出了多级页表的概念;但是提到空间性能是以浪费时间性能为基础的,因此为了补充损失的时间性能,提出了快表(即 TLB)的概念。
快表(TLB):
也叫旁路转换缓冲或者页表缓冲,可以把快表理解为一种特殊的高速缓冲存储器(Cache),其中的内容是页表的一部分或者全部内容。作为页表的 Cache,它的作用与页表相似。如果该页不在快表中,就访问内存中的页表,再从页表中得到物理地址,同时将页表中的该映射表项添加到快表中;当快表填满后,又要登记新页时,就按照一定的淘汰策略淘汰掉快表中的一个页。
多级页表:
引入多级页表的主要目的是为了避免把全部页表一直放在内存中占用过多空间,特别是那些根本就不需要的页表就不需要保留在内存中。给进程分配的内存空间很大的话,对应页表也很大。于是就要建立多级页表,把高层页表(相对级别比较高的)放在内存,靠这个高层页表找底层页表,再在底层页表里找到对应的实页号。部分底层页表在内存,另外的一些就被塞在磁盘,被高层页表点名之后,才调入内存。
CPU 需要将虚拟地址翻译成物理地址,这样才能访问到真实的物理内存。 实际上完成虚拟地址转换为物理地址转换的硬件是 CPU 中含有一个被称为 内存管理单元(Memory Management Unit, MMU) 的硬件。
CPU要寻址的时候,就会将虚拟地址告知给MMU,然后MMU在快表TLB中查询真实的物理地址,如果没有命中,还要去到磁盘中寻找,如下图所示:
局部性原理是虚拟内存技术的基础,正是因为程序运行具有局部性原理,才可以只装入部分程序到内存就开始运行。
局部性原理的主要内容就是:
最佳(Optimal, OPT)置换算法所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面,这样可以保证获得最低的缺页率。但由于人们目前无法预知进程在内存下的若千页面中哪个是未来最长时间内不再被访问的,因而该算法无法实现。一般作为衡量其他置换算法的方法。
总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面进行淘汰。
LRU算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 T,当须淘汰一个页面时,选择现有页面中其 T 值最大的,即最近最久未使用的页面予以淘汰。
最少使用页面置换算法 : 该置换算法选择在之前时期使用最少的页面作为淘汰页。
参考:Linux文件系统概要