可以系统的学习一下小林coding写的面经,通俗易懂:
小林coding
并发:同一段时间内多个程序执行
共享:系统中的资源可以被内存中多个并发执行的进/线程共同使用
虚拟:通过时分复用以及空分复用(如虚拟内存),把一个物理实体虚拟为多个
异步:系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进
补充:
并发:
进程并发要做到互不干扰,需要使用信号量机制做到进程之间的同步与互斥
线程并发使用 volatile 和 synchronized 以及锁机制实现。
- 通过 volatile 关键字修饰变量,可以实现线程之间的可见性,避免脏读的出现,底层是通过限制 jvm 指令的重排序来实现的,适用于一个线程修改,多个线程读的场景。
- 通过 synchronized 来实现线程的同步,自动锁的思想,底层实现原理:当有进程进入同步代码块之后,利用 jvm 的计数器将锁的标记置为1,该线程就去锁池等待,当第一个线程出来之后,锁的标记会置为0,之后 cpu 会随机分配一个线程再次进入同步代码块。
- 通过 lock 锁的机制,进行手动 lock ,和 unlock ,但是很容易出现死锁。需要注意加锁以及解锁的顺序,就可以避免死锁。
虚拟:
时分复用(时间片轮转)、空分复用(虚拟内存)。
异步:
由于进程是并发执行的,所以进程可能会遇到挂起、阻塞、终端、死锁等情况,所以进程以一种不可预知的速度推进。
进程管理:进程控制,进程同步,进程通信和进程调度
内存管理:内存分配,内存保护,地址映射,内存扩充
设备管理:管理所有外围设备,包括完成用户IO请求,为用户进程分配IO设备,提高IO设备利用率,提高IO速度,方便IO使用
文件管理:管理用户文件和系统文件,方便使用的同时保证安全性。包括磁盘存储空间管理,目录管理,文件读写管理以及文件共享及保护
提供用户接口:程序接口(如API)和用户接口(如GUI)
批处理操作系统:成批处理、系统吞吐量高、资源利用率高、用户不能干预作业的执行;
分时操作系统:多路性、独立性、及时性、交互性;
实时操作系统:及时响应、快速处理、高可靠性和安全性、不要求系统资源利用率;
操作系统的主要组成部分:进程和线程的管理、存储管理、设备管理、文件管理。
静态链接:
在实际开发中,不可能将所有代码都放在一个源文件中,通常会有多个源文件,而且多个源文件之间不是互相独立的,会存在一定的依赖关系,如一个源文件可能需要调用另一个源文件的函数,但是每个源文件都是独立编译的。为了满足这种依赖关系,需要将这些源文件产生的目标文件进行链接,从而形成一个可执行的程序。这样的链接过程就是静态链接。
静态链接的有点是运行速度快,因为可执行程序中已经具备了所有执行程序需要的所有东西。
缺点是会造成空间上的浪费,因为每个可执行文件中对所有需要的目标文件都有一个副本。同时更新会造成困难,每次源文件更新之后,都需要重新编译链接成可执行文件。
多个源文件存在依赖关系,静态链接将这些源文件产生的目标文件进行链接,以副本的方式存在,优点是速度快,缺点是造成空间浪费和更新困难。
动态链接:
动态链接库是程序运行时动态装入内存的模块,格式为.dll,在程序运行是可以随意加载和移除,更新方便,节省内存空间。
什么是编译
什么是链接
什么是运行
运行在用户态下的程序,只能受限地访问内存,不允许访问外围设备。占用CPU的能力被剥夺,CPU资源可以被其他程序获取。
运行在内核态下的程序,可以访问内存所有数据,包括外围设备。
用户态切换到内核态的三种方式:
1)系统调用:用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用,申请使用操作系统提供的服务程序,以完成工作。
2)异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,会触发由当前运行进程切换到处理此异常的内核相关程序中。因此也就转到了内核态,比如缺页异常。
3)外围设备终端:外围设备完成用户请求的操作后,会像CPU发送相应的中断信号。此时,CPU会暂停执行下一条即将要执行的指令,转而执行与中断信号对应的处理程序。如果先前执行的指令是用户态的程序,那么转换的过程自然就发生了由用户态到内核态的转换。
一个进程可以有多个线程,一个线程可以有多个协程。
进程通信方式:
1)管道:半双工,字节流,速度慢,容量有限,只有父子进程能通讯
2)命名管道:任何进程间都能通讯,但速度慢
3)消息队列:链表结构,容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4)共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于5)线程中的线程安全问题。
6)信号量:不能传递复杂消息,只能用来同步。
7)信号:用于通知接收进程某个事件已经发生。
8)套接字:可用于不同机器之间的进程间通信。
线程的通信方式:
1)互斥锁提供了以排他方式防止数据结构被并发修改的方法。
2)读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
3)条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
协程的通信方式:
与线程不同,协程使用程序自定义的调度器进行调度,因此更容易控制协程之间的执行顺序,要想充分利用协程的调度模型,有一个完备的通信机制是很重要的。它主要应该有以下的功能:
进程的状态有下面五种,其中前三种是进程的基本状态:
状态之间的切换
进程同步的四种方法
(1)先来先服务(FCFS,First-Come-First-Served): 此算法的原则是按照作业到达后备作业队列(或进程进入就绪队列)的先后次序来选择作业(或进程)。
(2)短作业优先(SJF,Shortest Process Next):这种调度算法主要用于作业调度,它从作业后备队列中挑选所需运行时间(估计值)最短的作业进入主存运行。
(3)时间片轮转调度算法(RR,Round-Robin):当某个进程执行的时间片用完时,调度程序便停止该进程的执行,并将它送就绪队列的末尾,等待分配下一时间片再执行。然后把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程,在一给定的时间内,均能获得一时间片处理机执行时间。
(4)高响应比优先(HRRN,Highest Response Ratio Next): 按照高响应(RR = (已等待时间+要求运行时间)/(要求运行时间))比优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP然后选择其值最大的作业投入运行。
(5)优先权(Priority)调度算法: 按照进程的优先权大小来调度,使高优先权进程得到优先处理的调度策略称为优先权调度算法。
(6)多级队列调度算法:多队列调度是根据作业的性质和类型的不同,将就绪队列再分为若干个子队列,所有的作业(或进程)按其性质排入相应的队列中,而不同的就绪队列采用不同的调度算法。
死锁的概念:多个进程之间因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
死锁产生的原因主要是:1、 系统资源不足;2、进程推进顺序非法。
1)互斥,一个资源每次只能被一个进程使用;
2)不可剥夺,进程已获得的资源,在未使用完之前,不能强行剥夺;
3)占有并等待,一个进程因请求资源而阻塞时,对已获得的资源保持不放;
4)循环等待,若干进程之间形成一种首尾相接的循环等待资源关系。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
1)预防死锁
破坏互斥条件,破坏不可剥夺条件,破坏请求与保持条件,破坏循环等待条件
2)避免死锁
3)检查死锁
利用死锁定理化简资源分配图以检测死锁的产生。
4)解除死锁
每个进程中,访问临界资源的那段程序块被称为临界区。每次只准许一个进程进入临界区,进入后不允许其他进程进入。
如何解决冲突?
a. 若有若干进程要求进入空闲临界区,一次仅允许一个进程进入
b. 若已有进程进入临界区,其他进程必须等待
c. 进入临界区的进程必须在有限时间内退出
d. 如果进程不能进入临界区,则必须让出CPU
当多线程访问一个对象时,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
那么如何保证线程安全呢?
Windows操纵内存可以分两个层面:物理内存和虚拟内存。
1)首次适应算法:空闲分区以地址递增次序链接,分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。
2)最佳适应算法:空闲分区按容量递增的次序链接,找到第一个能满足要求的空闲分区,也就是挑选尽量小的分区,使碎片尽量小。
3)最坏适应算法:空闲分区以容量递减的次序链接,找到第一个能满足要求的空闲分区,也就是挑选最大的分区,使剩余分区大小趋于均匀。
1)分页存储
用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分为若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
页表用于记录逻辑地址和实际存储地址之间的映射关系,以实现从页号到物理块号的映射。
访问分页系统中内存数据需要两次的内存访问。
① 从内存中访问页表,找到指定的物理块号,加上页内偏移,得到实际物理地址
② 根据第一次访问得到的物理地址,访问内存,存取数据
页号与块号的关系:页号就是逻辑地址,而块号是物理地址。
2)分段存储
分段内存管理中,地址是二维的,其中一维为段号,另一维是段内地址。每个段的长度不一样,每个段内部都从0开始编址。
段内部为连续内存分配,但段与段之间离散分配,因此有了段表机制,以从一个逻辑地址映射到一个物理地址。
3)分页与分段的区别
段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段,如代码段、数据段、堆栈段。每个进程有一个二维地址空间,相互独立,互不打扰。
段式存储管理的优点是:没有内碎片,因为段大小可变,可以通过改变段的大小而消除内碎片。但会产生外碎片,比如4K的段换5K的段,会产生1K的外碎片
页式存储管理是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页,而物理内存划分为同样大小的帧。程序加载时,可将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。
页式存储管理的优点是:没有外碎片,因为页的大小固定,但会产生内碎片,因为一个页可能填充不满。
两者的不同点:目大地信内
1)目的不同:分页是由于系统管理的需要,是信息的物理单位。分段是由于用户的需要,是信息的逻辑单位。
2)大小不同:页的大小固定,由系统决定。段的大小不固定,由其完成的功能决定
3)地址空间不同:页向用户提供一维地址空间,段向用户提供二维地址空间
4)信息共享:页的保护和共享收到限制,段利于存储保护和信息共享
5)内存碎片:分页没有外碎片,但有内碎片;分段没有内碎片,但有外碎片
若同时有很多进程在运行,则需要很多的内存来供应。当一个程序没有内存空间可以用的时候,那么他就无法运行。所以在物理上扩展内存相对有限的条件下,需要尝试一些其他可行的方式在逻辑上扩充内存。
基于局部性原理,将程序的一部分装入内存,而将其余部分留在外存,就可启动程序执行。在程序执行过程中,当所访问的信息不在内存中时,由操作系统将所需要的部分调入内存,然后继续执行程序。另一方面,操作系统将内存中暂时不使用的内容换出到外存上,从而腾出空间存放将要调入内存的信息。这样,系统就好像为用户提供了一个比实际大得多的存储器。
之所以将其称为虚拟的,是因为这种存储器实际上并不存在,只是由于系统提供了部分装入,请求调入和置换功能后,给用户的感觉就好像存在一个比实际物理内存大得多的存储器。虚拟存储器的大小是由计算机的地址结构决定,并不是内存与外存的简单相加。
虚拟存储器的三大特性:
1)多次性:无须在作业运行时,一次性的全部装入内存,而允许被分成多次装入内存运行。
2)对换性:无须作业一直在内存中,允许在作业运行的过程中,进行换入与换出操作。
3)虚拟性:从逻辑上扩充内存容量,使用户看到的内存容量远大于实际的内存容量。
FIFO,先进先出
LRU,last recently used,最近最少使用,根据使用时间到现在的长短来判断
LFU,last frequently used,最近使用次数算法,根据使用次数来判断
OPT,optimal replacement,最优置换算法,保证置换出去的是不再被使用的页,或在实际内存中最晚使用
子进程结束之后,父进程会收到exit消息。但是父进程结束的时候,子进程并不知道父进程结束,因此会成为孤儿进程,被 init 进程收留。
申请的内存所在地址
new操作符从自由存储区(free store,动态内存区域之一)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。
返回类型安全性
new 类型匹配,安全;
malloc void * 类型,需要强制类型转换,不安全。
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
内存分配失败的返回值
new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。
是否需要指定内存大小
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
参考链接:
new与malloc的10点区别
主要有两种策略:
两者的主要区别:
实际工作的线程是谁创建的?
使用线程池,实际工作线程由线程池创建;使用信号量,实际工作由程序员创建。
是否自动限流?
线程池自动,信号量手动。
补充:进程隔离通常是由操作系统来完成的,做到进程隔离需要用到内存管理(内存分配),进程调度(调度策略)等功能。