核心:CPU管理、内存管理
操作系统概念
1.操作系统主要内容:定义(CPU管理、内存管理)
- 定义:用于控制和管理计算机的硬件和软件,合理调度CPU和内存等资源,分配给用户和其他软件的接口和环境
- 分类:分时操作系统,实时操作系统。
- 分时操作系统(windows)——计算机以时间片为单位,轮流给各个用户服务。解决了人机交互问题。(各个用户通过终端与计算机交互)
- 实时操作系统(嵌入式):计算机接收到外部信号后及时处理,触发马上反应。(即时性和可靠性)
2.操作系统的基本特性
- 并发性:操作系统可以同时运行多个进程。不管是多核CPU还是单核CPU的计算机。(分时运行,分是获得CPU资源)
- 共享性:同时运行的进程可以共同使用系统中的资源。(互斥和同步访问)同步也是在互斥的基础上实现的访问机制。
- 异步性:进程不是一次性执行完成,而是走走停停的,但是最后结果不变。
- 虚拟性:虚拟是指将物理上的一个实体变为多个对应逻辑物。计算机中主要用于时分复用(CPU)和空分复用(虚拟内存)。
补充:并发指的是多个程序同时运行,但是单核CPU穿插运行两个进程,通过时分复用来提高CPU的效率。
并行指的是在多核CPU上分别运行进程,是并行运算提高计算机性能。
3.计算机的主要功能
- CPU管理——合理分配CPU资源,进行进程管理:进程同步,通信、调度等
- 内存管理——对内存和虚拟内存管理:合理分配内存空间,进程间不发生死锁等异常情况。
- 文件管理——系统、用户文件的管理
- IO设备管理——对外围设备的管理,分配接口等
进程与线程
线程:是CPU调度的最小单位。
进程:是系统资源调度分配基本单位。
1.进程定义
进程定义:进程是对运行程序的封装,一个进程可以包含一个或多个程序。进程是系统资源调度的基本单位。运行的微信、QQ都是一个进程。
生命周期:(获得其他资源和CPU资源的过程)
- 新建:未获得资源
- 就绪:获得其他资源,等待CPU资源
- 运行:获得其他资源,获得CPU资源
- 阻塞:等待触发事件以获得时间片资源,CPU空闲也不会运行
- 终止:退出,释放所有资源
CPU资源以时间片来分配
2.进程和程序的关系
程序由一系列代码组成。而进程由程序、程序包含的数据,进程块组成。
从广度上看,进程是程序的动态体现,程序是进程的静态体现,两者相辅相成。
程序构建进程,进程执行程序。
3.进程间的通信方式——作用——共享内存/套接字/信号/信号量
- 管道通信pipe:多用于父子进程间通信
- 信号signal:通过接收事件信号,告知进程某事已经发生
- 共享内存shared memory:应用最为广泛,进程间通过共享内存上数据的更新,获得信息。
- 信号量:用于进程中不同线程的同步
- 套接字socket:不同计算机进程间的通信
作用:A.数据传输及共享 B事件通知 C进程控制
4.进程同步 / 调度算法——进程获得CPU资源的先后执行顺序——CPU分配
多进程提高了CPU的利用率,但是进程的异步性会给系统造成混乱。进程同步就是:协调进程间的执行顺序,使得并发的进程间可以有效的共享资源、相互合作。
总结:多进程的异步性造成系统混乱,我们需要协调进程执行顺序,让进程间有效共享资源,相互合作。
进程调度算法:CPU分配算法
主要指标:CPU利用率、系统吞吐量、周转时间、等待时间、响应时间等
- A.先来先服务算法(FCFS):先来先服务调度算法,队列实现
- B.短作业优先算法(SJF):优先进行预估时间较短的进程(平均等待时间,周转时间最少)
- C.优先级调度算法:优先级越高的,先分配到CPU
- D.最高响应比算法(HRN): 根据响应比决定优先级,响应比=(等待时间+要求服务时间)/要求服务时间;(兼顾长作业和短作业)
- E.时间片轮转调度(RR): 按到达的先后对进程放入队列中,依次分配相同的时间片,时间片用完,马上剥夺CPU
- F.多级反馈队列调度算法(较好的算法): 设置多个就绪队列,为每个队列赋予不同优先级,优先级不同分配的时间片也不同。第一个队列优先级最高,分配的时间片也越短。
6.死锁现象 / 处理死锁的方法
死锁现象:多个进程争夺同一资源而陷入僵局,导致无法推进下去,进程无限阻塞,相互等待资源,最后卡死。
死锁原因:进程竞争资源,资源释放不合理
死锁产生的四大必要条件——发生死锁,四条至少一条成立
- —–1.互斥,2.请求和保持,3.不可剥夺,4.循环等待—–
- 互斥条件:一个资源每次只能被一个进程使用。
- 请求和保持条件:一个进程申请新资源时,对以旧资源不放
- 不可剥夺条件:进程获得资源在未使用完前,是不会释放它的
- 环路等待条件:若干进程之间形成一种头尾相接的环形等待资源关系
处理死锁的方法:打破四大条件
- 1.互斥条件不可打破
- 2.打破请和保持条件——实行预分配,只有满足进程所有资源要求时,才分配
- 3.打破不可剥夺条件——允许进程剥夺其他进程占有的资源,但是降低系统性能
- 4.打破环路等待条件——对资源进行编号,只有占用了小号资源才能申请大号资源。
- 基本思想都是:动态检测资源分配,从而保证系统处于安全状态。
最常见的是银行家算法:检查申请者对资源的最大需求,如果各类资源都满足,就分配。这样可以很快完成计算,然后释放申请者申请的资源。
7.临界资源的概念
共享性,让进程间可以共享系统资源。但是有的资源只能被单一进程使用,我们称其为临界资源。比如:打印机
临界资源的访问必须遵循互斥原则,访问临界资源的代码,我们称为临界区。
8.线程同步——按照某种特定顺序访问临界资源,避免线程冲突——内存资源分配
- 1.临界区(用户态)Critical Section WINDOWS特有
- 2.互斥量Mutex(内核态)
- 3.信号量Semaphore(限制访问某些资源的线程数量)
- 4.事件对象(内核)
- Linux线程同步方式:——无临界区
- 互斥量
- 信号量
- 事件对象
- Windows线程同步方式:
- 互斥量
- 临界区
- 信号量
- 事件对象
tips:互斥和临界区采用互斥控制,拥有才会执行,执行完释放。
信号量和事件对象采用通知控制,主要用于同步。
特点:
- A:临界区只能同步本进程内线程,(互斥量、信号量、事件)不可跨进程同步线程。
- B:
- 临界区在用户态下操作,速度快。
- 互斥量在内核态下进行锁操作,速度慢。
- C:Linux下不可以使用临界区
9.线程简介
线程是轻量化的进程,是进程组成的基本单位。不拥有系统所有资源,但是同一进程内的线程共享进程所有资源。
线程组成:线程ID,当前指令指针,寄存器,堆栈。
- 1.线程ID:用于表示线程
- 2.寄存器组的值:线程并发运行,切换时需要将原线程寄存器状态保存,用于重新切换时恢复。
- 3.堆栈:是保证线程独立运行的根本。线程函数可以调用函数,所以线程必须拥有自己的函数堆栈,而不受其他函数影响。位于进程的线程共享堆区内。(heap)
- 4.错误返回码
- 5.线程信号屏蔽码
- 6.线程优先级
进程一般包含多个线程,所有线程间也存在着制约关系,有:就绪,阻塞,运行三种状态
10.进程和线程的区别
1.
- 进程是程序的封装,是系统调度的基本单元,用于实现系统的并发性。
- 线程则是进程的子任务,是CPU调度的基本单元,用于实现进程的并发性。
2.线程的开销比进程小很多,易于调度,多用可以提高CPU的利用率,提高程序并发性。
3.进程用于自己独立的内存空间,而线程至共享其父进程的内存空间。
4.父进程内的线程间可以直接通信,而进程间通信需要借助操作系统。
有了进程为什么还要线程?
答:1.进程是系统调度的基本单元,而线程是CPU调度的基本单元。
2.在进程的基础上使用线程可以提高程序并发率,CPU的利用率,同时线程作为进程的子任务开销也较小。
3.进程拥有独立的内存空间,无法直接通信。而线程虽然没有独立的内存空间,但是父进程下的子线程都可以共享进程内数据,可以改善程序结构,提高计算效率。
内存管理
1.内存管理
- A:内存空间的分配与回收
- B:地址转换——内存物理地址与逻辑地址的转换
- C:内存空间的管理:虚拟内存和自动覆盖技术,逻辑上扩充内存。
- D:内存保护:进程间的内存空间互不干扰
2.逻辑地址和物理地址
逻辑地址:程序编译后,每个模块都是从0单元开始编址的,称为模块的逻辑地址。当各个模块链接成一个完整可执行目标时,就成了逻辑地址。
物理地址:内存物理地址的集合,它是地址转换的最终地址。
地址重定位:进程在运行指令和访问数据时都需要物理地址来存取主存。必须将逻辑值转化为物理地址才能正确访问数据,此过程称为地址重定位。
3.内存分配的管理方式(分配程序空间)
A连续分配 / B非连续分配
A连续分配:分配一个连续的内存空间
固定分区分配(产生内部碎片)
示例:
分区A_10MB 分区B_10MB———内部碎片4和 6
进程A_6MB 进程B_4MB
动态分区分配(产生外部碎片)
通过 “紧凑” 技术提高利用率,时间花销大。
B非连续分配:将一个进程分散地装在不相邻的分区中,就无需再凑来节约空间。
*基于分页的存储管理:(无外部碎片,极少数内部碎片) *
将内存分为一个一个相等的小块,再按照块大小拆分进程,放入内存。
*基于分段的存储管理:(分配基本单位为逻辑上的段) *
进程的地址空间按照逻辑分为若干段,每段从0开始编址。以段为单位进行分配,每个段在内存中占据连续空间,但是各个段不相邻。
分页和分段的对比:————页大小固定,段不固定
页是物理单位:为了实现内存的充分利用。是系统管理上的需要,对用户不可见。
段是逻辑单位:为了满足用户的逻辑习惯,以段进行分区包含一个逻辑模块。对用户可见,用户编程需要显示地给出段名。
分页地址空间是一维的。
分段地址空间则是二维的,程序员识别地址时,需要给出段名以及段内地址。
分段比分页更容易实现信息的共享和保护。
段页存储概念:先分段,再分页,内存管理仍采用分页存储管理。
页式存储->提高内存利用效率
段式存储->反映程序的逻辑结构,利于段的共享和信息保护、程序可在执行时再动态链接
虚拟内存管理:
进程的驻留性,进程数据必须一次装进内存中,直至作业完成。这就导致了很多暂时不用的数据占据了内存大量空间。
虚拟内存概念:————内存 / 外寸 / 置换内存
基于局部性原理,将进程的部分数据装入内存,其余部分驻留外存,即可以运行程序。当进程需要外存中的数据时,我们将数据调入内存,置换出不需要的内存。这样就可以提供一个比实际物理内存大的多的存储器,称为虚拟内存。
————需要的数据放入内存,暂时不需要的放置在外存。需要时再置换回内存。
1.虚拟内存的存储管理方式
分页存储管理,分段存储管理,段页存储管理。
使用最多的是分页管理方式,为了支持虚拟内存的管理,增加:1.请求调页功能 2.页面置换功能
需要的硬件支持:1.一定容量内存+外存 2.页表机制 3.终端机构 4.地址变换机构
2.虚拟内存中的页面置换算法
定义:在进程运行过程中,地址映射发现所访问的页不在内存中,将缺失的页从外存中点入内存中。但是当内存无空闲空间,需要移除页给准备进入的页腾位置。
常用的置换算法:
1.OPT——最佳置换算法——置换出接下来最不会被访问的页。理论上的算法,我们不知道哪个页接下来最不会被访问。常常用来作评价算法,因无法实现。
2.FIFO——先进先出置换算法——最早进入的页被置换出内存:当物理内存快增加,可能置换的次数反而变多,因为先进入的页可能被淘汰但是马上又要用。
3.LRU——最久未使用置换算法——将最久未访问的页置换:LRU算法的性能是最好的,但是需要寄存器和硬件的辅助计算支持,LRU是堆栈类算法。
3.虚拟内存的优缺点
优点:
- 1.提高了逻辑内存大小,内存的利用率
- 2.每个进程互不干扰,都在自己的虚拟内存空间下
缺点:
- 1.增加一些内存开销(虚拟内存的管理)
- 2.增加一些CPU开销(虚拟地址到物理地址的转换、页面置的时间)
4.虚拟内存的特点
定义:页面置换中,当置换出的页,接下来马上又要使用,这样会不断产生缺页中断,这种现象称为颠簸。
需要注意:虚拟内存可以提高逻辑内存,但是页面管理及置换算法不当时,会花费CPU很大的时间去交换页面,而不是执行进程命令,大大降低系统的效率。
6.工作集的概念
定义:某段时间内,进程访问页面的集合。经常被访问的页,我们将其放在工作集内,防止产生抖动现象,因此我们需要选择合适的工作集。
————单独开辟的进程常访问页的内存区域————
作用:选择正确的工作集,可以提高内存的利用率和吞吐效率,减少页面置换的花销。
7.页表寻址——找寻虚拟内存地址对应的物理地址
页表是计算机虚拟内存地址到物理内存地址的映射。页表中的每一项都记录着页的首地址。
首先通过页表的找寻逻辑地址对应的页的首地址,然后通过页的首地址 加上偏移量找到最后的物理地址。
常见问题
问题1.单核计算机多线程程序需要加锁吗
需要,因为程序锁的目的是为了保证线程的同步,防止读取共享数据时发生死锁等线程。单核计算机仍然存在线程同步问题,所以也需要加锁。
问题2.游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么?(互不影响)
开辟进程。同一进程间的线程会相互影响,一个线程死掉会影响其他线程,为了保证用户间互不影响,应该开辟进程。
问题3.请你说一说互斥锁机制,以及互斥锁和读写锁的区别
- 互斥锁:保证单一对象只有单一线程对其进行操作。
- 读写锁:分为读锁和写锁。处于读操作时,允许多个线程同时获得读操作,但是同一时刻只允许一个线程获得写操作。同时写锁会阻塞其他操作
不同:
- 读写锁区分读和写,而互斥锁不区分。
- 互斥锁只允许一个线程访问该对象。读写锁允许多个读者,但是同一时间只允许一个写着。
问题4.怎样确定当前线程是繁忙还是阻塞
linux下用ps命令查看
问题5.windows消息机制知道吗,请说一说
消息机制是系统将用户的具体操作转化为消息,告诉给进程。有点类似进程通信中的信号。
问题6.说一下僵尸进程和孤儿进程(未回收信息的子进程)
父进程创建子进程,子进程再创建新子进程。子进程的结束和父进程的运行是个异步过程。父进程需要调用wait()等操作取得子进程的终止状态。
孤儿进程:父进程结束了,子进程还在运行。孤儿进程会被进程号为1的进程收养。
僵尸进程:子进程退出时,父进程没有调用wait回收子进程的信息,那么子进程的信息仍保留在系统中,这种进程称为僵尸进程。
危害:僵尸进程是进程必须经过的过程,但是如果父进程未来得及处理退出的子进程,就会变成僵尸进程。这样的话僵尸进程保留的那段信息就不会释放,进程号一直被占用,会导致逐渐没有可用的进程号。
解决:通过kill发出指令,僵尸进程变孤儿进程,再由新的父进程接管消灭。在子进程结束的时候父进程及时wait()取得子进程的终止状态。
问题6.请你来介绍一下5种IO模型
- 1.阻塞型IO:等待函数返回,不返回不继续,反复检索函数有没有返回
- 2.非阻塞型IO:每隔一段时间检查IO事件是否就绪,没有仍可以做其他事情
- 3.信号驱动IO:安装一个信号处理函数,进程不阻塞,收到信号处理IO事件。
- 4.复用IO:poll,select函数实现复用模型
- 5.异步IO:可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。
问题7.请你说一说异步编程的事件循环
事件循环就是事件是处理器一个一个依次执行的。当一个事件执行完毕,事件循环就会继续等待下一个事件的触发,不断往复。
当单个线程绑定了两个处理器,那么第二个处理器也会等待第一个处理器执行完毕后,才开始执行。
问题8.请你回答一下操作系统为什么要分内核态(root)和用户态(非root)
为了系统的安全性。CPU中的一些指令用错会导致整个系统崩溃。当用户态满足使用要求时尽量使用用户态,避免发生不必要的错误。
问题9.请问怎么实现线程池
- 1.设置一个生产消费者队列,作为申请线程的临时资源
- 2.初始化n个线程,并运行,加锁去队列中获取任务
- 3.无任务,线程阻塞。有任务,队列加锁,条件变量通知其中阻塞的一个线程运行
问题10.说一下C++源文件从文本——>可执行文件 ——经历的过程
- 1.预处理(产生.i文件):将所以源文件(.cpp)和相关头文件,预处理成一个 .i文件
- 将#define删除,并且宏替换
- 将#ifdef,#ifndef等不需要的代码删除
- 将注释的代码删除
- 处理#include预编译指令,将包含的文件插入到该预编译指令的位置,此过程递归进行。
- 保留#pragma编译器指令,因为编译器需要。
#ifdef,#ifndef作用:防止头文件重复包含
#include<>和#include” “区别:前者从标准库中找,后者从当前目录开始寻找。
2.编译(产生.s文件):(核心)
将预处理后的文件,进行一系列的词法分析,语义分析以及优化后产生相应的 汇编代码文件3.汇编(产生.o/.obj文件):
汇编实质上是把汇编语言代码 翻译成目标机器指令的过程,即生成目标文件。4.链接(产生.out/.exe文件):
链接就是代码中用了别的库,将其和程序进行链接,然后生成可执行文件。按照它们的要求将它们组装起来,链接主要解决的是源代码之间的相互依赖问题,链接的过程包括地址和空间的分配,符号决议,和重定位等这些步骤。
总结
- 预处理(头文件解析,删除注释,宏替换等)
- 编译(将预处理后文件,语义分析,优化后生成汇编语言)
- 汇编(将汇编语言翻译成机器语言,生成目标文件)
- 链接(链接其他使用了的库,和目标文件结合生成可执行文件)
问题11.静态库 / 动态库的区别
- 静态库(.a/lib )/动态库(.so/dll )的区别:
- 两者都由.o目标文件编译生成。可以理解为目标文件的一个集合。
- 静态库在链接过程中,会将静态库和目标文件(.o)一起打包到可执行文件中。删除后,程序仍可以执行。
- 动态库并不会打包到可执行文件中,只有在程序被执行时才会被载入,避免了浪费资源。