一、操作系统概述

1. 异常和中断的区别

  • 中断:由CPU以外的事件引起的中断,如I/O中断、时钟中断、控制台中断等。
  • 异常:来自CPU的内部事件或程序执行中的事件引起的过程。如,由于CPU本身故障、程序故障和请求系统服务的指令引起的中断等。

2. 中断和系统调用
(1)中断就是在计算机执行程序的过程中,由于出现了某些特殊事情,使得CPU暂停对程序的执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。
(2)中断一般分为三类
a. 由计算机硬件异常或故障引起的中断,称为内部异常中断;
b. 由程序中执行了引起中断的指令而造成的中断,称为软中断(这也是和我们将要说明的系统调用相关的中断);
c. 由外部设备请求引起的中断,称为外部中断。
(3)中断处理程序:当中断发生的时候,系统需要去对中断进行处理,对这些中断的处理是由操作系统内核中的特定函数进行的,这些处理中断的特定的函数就是我们所说的中断处理程序了。
(4)中断的优先级
中断的优先级说明的是当一个中断正在被处理的时候,处理器能接受的中断的级别。中断的优先级也表明了中断需要被处理的紧急程度。每个中断都有一个对应的优先级,当处理器在处理某一中断的时候,只有比这个中断优先级高的中断可以被处理器接受并且被处理。优先级比这个当前正在被处理的中断优先级要低的中断将会被忽略。
机器错误 > 时钟 > 磁盘 > 网络设备 > 终端 > 软件中断
(5)系统调用相关概念:

  • 进程的执行在系统上的两个级别:用户级和核心级(也称为用户态和系统态);
    用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。处于用户态的程序只能访问用户空间,而处于内核态的程序可以访问用户空间和内核空间。

  • 用户态切换到内核态的方式如下:
    (1)系统调用:程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件(这些均属于系统调用)等,就需要向操作系统发出调用服务的请求,这就是系统调用。
    (2)异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
    (3)外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

  • 用户态和核心态(内核态)之间的区别是什么呢?
    权限不一样。
    (1)用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。
    (2)核心态下的进程能够存取内核和用户地址某些机器指令是特权指令,在用户态下执行特权指令会引起错误。

  • 程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如说打开某一设备、创建文件、读写文件等,就需要向操作系统发出调用服务的请求,这就是系统调用。

  • Linux系统有专门的函数库来提供这些请求操作系统服务的入口,这个函数库中包含了操作系统所提供的对外服务的接口。当进程发出系统调用之后,它所处的运行状态就会由用户态变成核心态。但这个时候,进程本身其实并没有做什么事情,这个时候是由内核在做相应的操作,去完成进程所提出的这些请求。

  • 那么用户态和核心态之间的区别是什么呢?
    用户态的进程能存取它们自己的指令和数据,但不能存取内核指令和数据(或其他进程的指令和数据)。然而,核心态下的进程能够存取内核和用户地址

  • 系统调用和中断的关系,当进程发出系统调用申请的时候,会产生一个软件中断。产生这个软件中断以后,系统会去对这个软中断进行处理,这个时候进程就处于核心态了。

3. 中断和轮询的特点

  • 对I/O设备的程序轮询的方式,是早期的计算机系统对I/O设备的一种管理方式。它定时对各种设备轮流询问一遍有无处理要求。轮流询问之后,有要求的,则加以处理。在处理I/O设备的要求之后,处理机返回继续工作。尽管轮询需要时间,但轮询要比I/O设备的速度要快得多,所以一般不会发生不能及时处理的问题。当然,再快的处理机,能处理的输入输出设备的数量也是有一定限度的。而且,程序轮询毕竟占据了CPU相当一部分处理时间,因此,程序轮询是一种效率较低的方式,在现代计算机系统中已很少应用。
  • 程序中断通常简称中断,是指CPU在正常运行程序的过程中,由于预先安排或发生了各种随机的内部或外部事件,使CPU中断正在运行的程序,而转到为响应的服务程序去处理。
  • 轮询——效率低,等待时间很长,CPU利用率不高。
    中断——容易遗漏一些问题,CPU利用率高。

4. 你知道操作系统的内容分为几块吗?
操作系统的主要组成部分:进程和线程的管理,存储管理,设备管理,文件管理。

5. 一个程序从开始运行到结束的完整过程

  • 预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。
  • 编译:将预处理后的文件转换成汇编语言,生成.s文件
  • 汇编:汇编变为目标代码(机器代码)生成.o的文件
  • 链接:连接目标代码,生成可执行程序

6. 动态链接库与静态链接库的区别

  • 静态库
    静态库是一个外部函数与变量的集合体。静态库的文件内容,通常包含一堆程序员自定的变量与函数,其内容不像动态链接库那么复杂,在编译期间由编译器与链接器将它集成至应用程序内,并制作成目标文件以及可以独立运作的可执行文件。而这个可执行文件与编译可执行文件的程序,都是一种程序的静态创建(static build)
  • 动态库
    (1)动态链接可以在首次载入的时候执行(load-time linking),这是 Linux 的标准做法,会由动态链接器ld-linux.so 完成,比方标准 C 库(libc.so) 通常就是动态链接的,这样所有的程序可以共享同一个库,而不用分别进行封装。
    (2)动态链接也可以在程序开始执行的时候完成(run-time linking),在 Linux 中使用 dlopen()接口来完成(会使用函数指针),通常用于分布式软件,高性能服务器上。而且共享库也可以在多个进程间共享。
    (3)链接使得我们可以用多个对象文件构造我们的程序。可以在程序的不同阶段进行(编译、载入、运行期间均可),理解链接可以帮助我们避免遇到奇怪的错误。
  • 区别:
    (1)使用静态库的时候,静态链接库要参与编译,在生成执行文件之前的链接过程中,要将静态链接库的全部指令直接链接入可执行文件中。而动态库提供了一种方法,使进程可以调用不属于其可执行代码的函数。函数的可执行代码位于一个.dll文件中,该dll包含一个或多个已被编译,链接并与使用它们的进程分开储存的函数。
    (2)静态库中不能再包含其他动态库或静态库,而在动态库中还可以再包含其他动态或者静态库。
    (3)静态库在编译的时候,就将库函数装在到程序中去了,而动态库函数必须在运行的时候才被装载,所以使用静态库速度快一些。

7. 系统调用与函数调用的区别?
(1)一个在用户地址空间执行;一个在内核空间执行
(2)一个是过程调用,开销小;一个需要切换用户空间和内核上下文,开销大
(3)一般相同;不同系统不同

  • 系统调用(System call)是程序向系统内核请求服务的方式。可以包括硬件相关的服务(例如,访问硬盘等),或者创建新进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。
  • 库函数:把一些常用的函数编写完放到一个文件里,编写应用程序时调用,这是由第三方提供的,发生在用户地址空间。
  • 在移植性方面,不同操作系统的系统调用一般是不同的,移植性差;
  • 在调用开销方面,系统调用需要在用户空间和内核环境间切换,开销较大;而库函数调用属于“过程调用”,开销较小。

8. 操作系统拿来干什么的?

  • 操作系统的功能:
    (1).进程和线程的管理 ——进程线程的状态、控制、同步互斥、通信调度等
    (2).存储管理——分配/回收、地址转换、存储保护等
    (3).文件管理——文件目录、文件操作、磁盘空间、文件存取控制
    (4).设备管理——设备驱动、分配回收、缓冲技术等
    (5).用户接口——系统命令、编程接口
  • 操作系统的三个作用:
    (1).资源的管理者
    (2).向用户提供各种服务
    (3).对硬件机器的扩展

9. 操作系统的用户态与内核态?

  • 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
  • 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。

10. 为什么要有用户态和内核态
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 – 用户态和内核态。

11. 什么是RAII资源管理?
它是“Resource Acquisition Is Initialization”的首字母缩写,即资源获取就是初始化。利用对象生命周期来控制程序资源,简单来说就是通过局部对象来处理一些资源问题

12. 为什么要字节对齐?
(1)有些特殊的CPU只能处理4倍开始的内存地址
(2)如果不是整倍数读取会导致读取多次
(3)数据总线为读取数据提供了基础

二、进程管理

1. 进程与线程的区别和联系

  • 定义
    进程 是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
    线程 是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
  • 关系
    (1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
    (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
    (3)处理机分给线程,即真正在处理机上运行的是线程。
    (4)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
  • 区别
    (1)进程有自己的独立地址空间,线程没有。进程有自己的独立地址空间(32位进程地址空间0-4G),进程的地址空间之间相互独立,同一进程的各个线程共享进程的资源。线程只有一个内核对象和一个堆栈,所需内存小;
    (2)进程是资源分配的最小单位,线程是CPU调度的最小单位
    (3)进程和线程通信方式不同,进程通信需要进程同步和互斥的手段辅助,以保证数据的一致性,而线程可以直接读写进程数据段来进行通信
    (4)进程的创建,撤销,切换系统开销大,线程开销小,只需保存很设置少量寄存器内容
    (5)一个进程挂掉了不会影响其他进程,而线程挂掉了会影响其他线程
    (6)引入线程的操作系统,多个进程间可以并发执行,多个线程间也可以并发执行。
    (7)与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表少得多。
  • 优缺点:
    线程开销小,但不利于资源的管理和保护;进程与之相反

2. 进程间的通信方式有哪些?线程间的通信方式呢?

  • 进程
    共享内存,消息队列传递,
    无名管道(有血缘关系的进程间),有名管道(允许无亲缘关系关系进程间通信),
    信号,信号量(主要作为进程间以及同一进程内不同线程之间的同步手段。),
    套接字
    几种方式的比较:
    (1) 管道:速度慢、容量有限
    (2) 消息队列:容量收到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!
    (3) 信号量:不能传递复杂信息,只能用来同步。
    (4) 共享内存:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全。
  • 线程 :锁机制(互斥锁、条件变量、读写锁),信号量,信号
    线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。

3. 进程间同步与互斥的区别,线程同步的方式?

  • 区别
    (1) 互斥 :指某一个资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
    (2)同步 :是指在互斥的基础上(大多数情况下),通过其它机制实现访问者对资源的有序访问。大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
    (3)同步:体现的是一种协作性。互斥:体现的是排它性
    (4)同步机制遵循的原则 :空闲让进,忙则等待,有限等待,让权等待;

  • 线程同步的方式:
    (临界区、互斥量、信号量、事件)
    线程同步指多个线程同时访问某资源时,采用一系列的机制以保证同时最多只能一个线程访问该资源。
    临界区:通过对多线程的串行化来访问公共资源或者一段代码,速度快,适合控制数据访问。
    互斥量:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以可以保证公共资源不会同时被多个线程访问。
    信号量:它允许多个线程同一时刻访问同一资源,但是需要限制同一时刻访问此资源的最大线程数目。信号量对象与其他前面几种方法不同,信号允许多个线程同时使用共享资源。
    事件(信号):通过通知操作的方式来保持多线程的同步,还可以方便实现多线程的优先级比较操作。

  • 线程同步不同方式间的总结比较:
    (1) 互斥量和临界区很相似,但是互斥量是可以命名的,它可以跨越进程使用,所以创建互斥量所需要的资源更多,如果只是为了在进程内部使用使用临界区会带来速度上的优势并能够减少资源占用量。
    (2) 互斥量、信号量、事件都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,所以可以使用WaitForSingleObject来等待进程和线程退出。
    (3) 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号量对象可以说是一种资源计数器。

4. C++多线程,互斥,同步
(1)互斥与同步

  • 当有多个线程的时候,经常需要去同步(注:同步不是同时刻)这些线程以访问同一个数据或资源。例如,假设有一个程序,其中一个线程用于把文件读到内存,而另一个线程用于统计文件中的字符数。当然,在把整个文件调入内存之前,统计它的计数是没有意义的。但是,由于每个操作都有自己的线程,操作系统会把两个线程当作是互不相干的任务分别执行,这样就可能在没有把整个文件装入内存时统计字数。为解决此问题,你必须使两个线程同步工作。
  • 所谓同步,是指在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。 如果用对资源的访问来定义的话,同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
  • 所谓互斥,是指散布在不同进程之间的若干程序片断,当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。 如果用对资源的访问来定义的话,互斥某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

(2)多线程同步和互斥有几种实现方法

  • 线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
  • 用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。
  • 内核模式下的方法有:事件,信号量,互斥量。
    (1)临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
    (2)互斥量:为协调共同对一个共享资源的单独访问而设计的。
    (3)信号量:为控制一个具有有限数量用户资源而设计。
    (4)事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

5. 进程间同步的几种方法
进程间同步:把异步环境下的一组并发进程因直接制约而互相发送消息而进行互相合作、互相等待,使得各进程按一定的速度执行的过程称为进程间的同步。
(1)信号量,P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。
(2)管程
管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块,其主要特点如下:
a. 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
b. 一个进程通过调用管程的一个过程进入管程。
c. 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
(3)生产者-消费者模型

6. 进程调度
1)调度种类
高级调度:又称为作业调度,它决定把后备作业调入内存运行;
低级调度:又称为进程调度,它决定把就绪队列的某进程获得CPU;
中级调度:又称为在虚拟存储器中引入,在内、外存对换区进行进程对换。
2)非抢占式调度与抢占式调度
非抢占式:分派程序一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
3)调度策略的设计
响应时间: 从用户输入到产生反应的时间
周转时间: 从任务开始到任务结束的时间

7. 进程的调度算法有哪些?

  • 先来先服务(FCFS):此算法的原则是按照作业到达后备作业队列(或进程进入就绪队列)的先后次序选择作业(或进程)
  • 短作业优先(SJF:Shortest Process First):这种算法主要用于作业调度,它从作业后备序列中挑选所需运行时间最短的作业进入主存运行。
  • 时间片轮转调度算法:当某个进程执行的时间片用完时,调度程序便终止该进程的执行,并将它送到就绪队列的末尾,等待分配下一时间片再执行。然后把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证队列中的所有进程,在已给定的时间内,均能获得一时间片处理机执行时间。
  • 高响应比优先:按照高响应比(已等待时间+要求运行时间)/要求运行时间 优先的原则,在每次选择作业投入运行时,先计算此时后备作业队列中每个作业的响应比RP。选择最大的作业投入运行。
  • 优先权调度算法:按照进程的优先权大小来调度。使高优先权进程得到优先处理的调度策略称为优先权调度算法。注意:优先数越多,优先权越小。
  • 多级队列调度算法:多队列调度是根据作业的性质和类型的不同,将就绪队列再分为若干个队列,所有的作业(进程)按其性质排入相应的队列中,而不同的就绪队列采用不同的调度算法。

8. 什么是临界区?如何解决冲突?
每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进入后不允许其他进程进入。
(1)如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入;
(2)任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则其它所有试图进入临界区的进程必须等待;
(3)进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区;
(4)如果进程不能进入自己的临界区,则应让出CPU,避免进程出现“忙等”现象。

9. 临界资源与临界区的概念

  • 临界资源:临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。
  • 临界区:每个进程中访问临界资源的那段代码称为临界区,每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。

10. 什么是死锁?产生条件?如何避免死锁

  • 死锁的概念: 在2个或多个并发进程中,如果每个进程持有某有资源而又都等待别的进程释放它或他们现在保持的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是2个或多个进程被无限期地阻塞、相互等待的一种状态。
  • 死锁产生的原因:系统资源不足,进程推进顺序非法
  • 产生死锁的必要条件
    1).互斥条件:一个资源每次只能被一个进程使用
    2).不可剥夺条件:进程已获得资源,在未使用完之前,不能被其他进程强行剥夺,只能主动释放
    3).请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    4).循环等待条件:即进程集合{p0,p1,p2,p3……pn};p0正在等待p1占用的资源,p1正在等待p2占用的资源,pn正在等待p0占用的资源。
     只要上述一个条件不成立,就不会发生死锁。
  • 死锁的解除和预防:
    在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。对资源的分配要给予合理规划
  • 死锁的处理策略
    预防策略、避免策略、检测与解除死锁

11. 进/线程的常见状态?以及各种状态之间的转换条件?
A. 进程的常见状态及转换

  • 常见状态:
    就绪:进程已处于准备好运行的状态,即进程已分配到除CPU外的所有必要资源后,只要再获得CPU,便可立即执行。
    运行:进程已经获得CPU,程序正在执行状态。
    阻塞:正在执行的进程由于发生某事件(如I/O请求、申请缓冲区失败等)暂时无法继续执行的状态。
    创建:进程在在被创建,尚未转换到就绪状态。
    创建进程首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息,然后由系统为该进程分配运行时所必需的资源,最后把程序转换到就绪态
    结束状态:进程正在从系统中消失,可能是进程正常结束或者其他原因中断退出运行。
  • 各种状态之间的转换条件
    就绪->运行:就绪态的进程被调度后,获得处理及资源,转为运行态
    运行->就绪:运行态时间片用完后,让出处理机,转为就绪态
    运行->阻塞:当进程请求某一资源的使用和分配或等待某一事件的发生时,就转换为阻塞态
    阻塞->就绪:进程等待事件到来时,如I/O操作结束或中断结束时,将阻塞态转换为就绪态

B. 线程的常见状态及转换

  • 常见状态
    (1)新建状态(New):新创建了一个线程对象。
    (2)就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。
    (3)执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。
    (4)堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。
    堵塞的情况分三种:
    (一)等待堵塞:执行的线程执行wait()方法
    (二)同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。
    (三)其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。
    (5)死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。

12. 上下文切换
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。

13. 为什么进程上下文切换比线程上下文切换代价高?

  • 进程切换分两步:
    (1)切换页目录以使用新的地址空间
    (2)切换内核栈和硬件上下文
    对于linux来说,线程和进程的最大区别就在于地址空间,对于线程切换,第1步是不需要做的,第2是进程和线程切换都要做的。
  • 切换的性能消耗:
    (1)线程上下文切换和进程上下文切换一个最主要的区别是线程的切换虚拟内存空间依然是相同的,但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。
    (2)另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说,一旦去切换上下文,处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候,处理的页表缓冲(processor’s Translation Lookaside Buffer (TLB))或者相当的神马东西会被全部刷新,这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中,不会出现这个问题。

14. 内存池、进程池、线程池
首先介绍一个概念“池化技术 ”。池化技术就是:提前保存大量的资源,以备不时之需以及重复使用。池化技术应用广泛,如内存池,线程池,连接池等等。

由于在实际应用当做,分配内存、创建进程、线程都会设计到一些系统调用,系统调用需要导致程序从用户态切换到内核态,是非常耗时的操作。因此,当程序中需要频繁的进行内存申请释放,进程、线程创建销毁等操作时,通常会使用内存池、进程池、线程池技术来提升程序的性能。

  • 线程池:线程池的原理很简单,类似于操作系统中的缓冲区的概念,它的流程如下:先启动若干数量的线程,并让这些线程都处于睡眠状态,当需要一个开辟一个线程去做具体的工作时,就会唤醒线程池中的某一个睡眠线程,让它去做具体工作,当工作完成后,线程又处于睡眠状态,而不是将线程销毁。
  • 进程池与线程池同理。
  • 内存池:内存池是指程序预先从操作系统申请一块足够大内存,此后,当程序中需要申请内存的时候,不是直接向操作系统申请,而是直接从内存池中获取;同理,当程序释放内存的时候,并不真正将内存返回给操作系统,而是返回内存池。当程序退出(或者特定时间)时,内存池才将之前申请的内存真正释放。

15. 什么是线程安全

  • 如果多线程的程序运行结果是可预期的,而且与单线程的程序运行结果一样,那么说明是“线程安全”的。
  • 线程安全问题都是由全局变量及静态变量引起的。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

16. 同步与异步
(1)同步:

  • 定义:是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么,这个进程将会一直等待下去,直到收到返回信息才继续执行下去。
  • 特点:
    (1)同步是阻塞模式;
    (2)同步是按顺序执行,执行完一个再执行下一个,需要等待,协调运行;

(2)异步

  • 是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
  • 特点:
    (1)异步是非阻塞模式,无需等待;
    (2)异步是彼此独立,在等待某事件的过程中,继续做自己的事,不需要等待这一事件完成后再工作。线程是异步实现的一个方式。

(3)优缺点

  • 同步可以避免出现死锁,读脏数据的发生。一般共享某一资源的时候,如果每个人都有修改权限,同时修改一个文件,有可能使一个读取另一个人已经删除了内容,就会出错,同步就不会出错。但,同步需要等待资源访问结束,浪费时间,效率低。
  • 异步可以提高效率,但,安全性较低。

17. 守护、僵尸、孤儿进程的概念

  • 守护进程:运行在后台的一种特殊进程,独立于控制终端并周期性地执行某些任务。
  • 僵尸进程:一个进程 fork 子进程,子进程退出,而父进程没有wait/waitpid子进程,那么子进程的进程描述符仍保存在系统中,这样的进程称为僵尸进程。
  • 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,这些子进程称为孤儿进程。(孤儿进程将由 init 进程收养并对它们完成状态收集工作)
    init进程是所有Linux进程的父进程,它的进程号为1。init命令是Linux操作系统中不可缺少的程序之一,init进程是Linux内核引导运行的,是系统中的第一个进程。

18. Semaphore(信号量) Vs Mutex(互斥锁)

  • 当用户创立多个线程/进程时,如果不同线程/进程同时读写相同的内容,则可能造成读写错误,或者数据不一致。此时,需要通过加锁的方式,控制临界区(critical section)的访问权限。对于semaphore而言,在初始化变量的时候可以控制允许多少个线程/进程同时访问一个临界区,其他的线程/进程会被堵塞,直到有人解锁。
  • Mutex相当于只允许一个线程/进程访问的semaphore。此外,根据实际需要,人们还实现了一种读写锁(read-write lock),它允许同时存在多个阅读者(reader),但任何时候至多只有一个写者(writer),且不能于读者共存。

19. 线程共享资源和独占资源问题

  • 一个进程中的所有线程共享该进程的地址空间,但它们有各自独立的(/私有的)栈(stack),Windows线程的缺省堆栈大小为1M。堆(heap)的分配与栈有所不同,一般是一个进程有一个C运行时堆,这个堆为本进程中所有线程共享,windows进程还有所谓进程默认堆,用户也可以创建自己的堆。
  • 用操作系统术语,线程切换的时候实际上切换的是一个可以称之为线程控制块的结构(TCB),里面保存所有将来用于恢复线程环境必须的信息,包括所有必须保存的寄存器集,线程的状态等。
  • : 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
  • :是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

20. 一个进程可以创建多少线程,和什么有关

  • 1)Linux中
    a. 在Linux32位平台下,一个进程的虚拟内存是4G,内核分走了1G,留给用户用的只有3G。
    b. 线程会占用多少内存,这取决于分配给线程的调用栈大小,可以用ulimit -s命令来查看大小(一般常见的有10M或者是8M)
    c. 创建一个线程占有了10M内存,总共有3G内存可以使用。于是可想而知,最多可以创建差不多300个左右的线程。
  • 2)Windows中
    a. 默认情况下,一个线程的栈要预留1M的内存空间
    b. 而一个进程中可用的内存空间只有2G,所以理论上一个进程中最多可以开2048个线程 ,但是内存当然不可能完全拿来作线程的栈,所以实际数目要比这个值要小。

21. 进程通信方法(Linux和windows下),线程通信方法(Linux和windows下)

(1) 线程间通信:由于多线程共享地址空间和数据空间,所以多个线程间的通信是一个线程的数据可以直接提供给其他线程使用,而不必通过操作系统(也就是内核的调度)。
(2) 进程间的通信则不同,它的数据空间的独立性决定了它的通信相对比较复杂,需要通过操作系统。
(3) 进程的通信机制主要有
管道、有名管道、消息队列、信号量、共享空间、信号、套接字。

  • 管道:它传递数据是单向性的,只能从一方流向另一方,也就是一种半双工的通信方式;只用于有亲缘关系的进程间的通信,亲缘关系也就是父子进程或兄弟进程;没有名字并且大小受限,传输的是无格式的流,所以两进程通信时必须约定好数据通信的格式。管道它就像一个特殊的文件,但这个文件之存在于内存中,在创建管道时,系统为管道分配了一个页面作为数据缓冲区,进程对这个数据缓冲区进行读写,以此来完成通信。其中一个进程只能读一个只能写,所以叫半双工通信,为什么一个只能读一个只能写呢?因为写进程是在缓冲区的末尾写入,读进程是在缓冲区的头部读取,他们各自
    的数据结构不同,所以功能不同。
  • 有名管道:看见这个名字就能知道个大概了,它于管道的不同的是它有名字了。这就不同于管道只能在具有亲缘关系的进程间通信了。它提供了一个路径名与之关联,有了自己的传输格式。有名管道和管道的不同之处还有一点是,有名管道是个设备文件,存储在文件系统中,没有亲缘关系的进程也可以访问,但是它要按照先进先出的原则读取数据。同样也是单双工的。
  • 消息队列:是存放在内核中的消息链表,每个消息队列由消息队列标识符标识,于管道不同的是,消息队列存放在内核中,只有在内核重启时才能删除一个消息队列,内核重启也就是系统重启,同样消息队列的大小也是受限制的。
  • 信号量:也可以说是一个计数器,常用来处理进程或线程同步的问题,特别是对临界资源的访问同步问题。临界资源:为某一时刻只能由一个进程或线程操作的资源,当信号量的值大于或等于0时,表示可以供并发进程访问的临界资源数,当小于0时,表示正在等待使用临界资源的进程数。更重要的是,信号量的值仅能由PV操作来改变。
  • 共享内存:就是分配一块能被其他进程访问的内存。共享内存可以说是最有用的进程间通信方式,也是最快的IPC形式。首先说下在使用共享内存区前,必须通过系统函数将其附加到进程的地址空间或说为映射到进程空间。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到 进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制,互斥锁和信号量都可以。采用共享内存通信的一个显而易 见的好处是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝。对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而 共享内存则只拷贝两次数据[1]:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。实际上,进程之间在共享内存时,并不总是读写少量数据后就 解除映射,有新的通信时,再重新建立共享内存区域。而是保持共享区域,直到通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存 中的内容往往是在解除映射时才写回文件的。因此,采用共享内存的通信方式效率是非常高的。
  • 信号:信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。信号是进程间通信机制中唯一的异步通信机制,可以看作是异步通知,通知接收信号的进程有哪些事情发生了。信号机制经过POSIX实时扩展后,功能更加强大,除了基本通知功能外,还可以传递附加信息。信号事件的发生有两个来源:硬件来源(比如我们按下了键盘或者其它硬件故障);软件来源。信号分为可靠信号和不可靠信号,实时信号和非实时信号。进程有三种方式响应信号1.忽略信号2.捕捉信号3.执行缺省操作。
  • 套接字

A、Windous下多线程通信方法

  • 1.全局变量
    由于同一进程下的线程之间共享数据空间。当需要有多个线程来访问一个全局变量时,通常我们会在这个全局变量前加上volatile声明,以防编译器对此变量进行优化。
  • 2.Message消息机制
    常用的Message通信的接口主要有两个:PostMessage和PostThreadMessage,
    PostMessage为线程向主窗口发送消息。而PostThreadMessage是任意两个线程之间的通信接口。
  • 3.CEvent对象
    CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法。

B、Linux下多线程通信方法

  • 多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  • 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

22. 信号量的PV操作

  • wait(s)----------> P操作
  • signal(s)-------> V操作
wait(S){   //S表示资源数目while(S<0);S=S-1;
}
signal(S){S=S+1;
}

记录型信号量

typedef struct{int value;struct process *L;
};semaphore;

22. 哲学家就餐,银行家,读者写者,生产者消费者(怎么加锁解锁,伪代码)
(一)生产者消费者模型

semaphore mutex=1;      //临界区互斥信号量
semaphore empty=n;     //空闲缓冲区
semaphore full=0;      //缓冲区初始化为空
producer(){             //生产者进程while(1){produce an item in nextp;   //生产数据P(empty);                 //获取空缓冲区单元P(mutex); (互斥夹紧)      //进入临界区add nextp to buffer;     //将数据放入到缓存区V(mutex);                    //离开缓存区,释放互斥信号量V(full);                  //满缓冲区数加1}
}
consumer(){while(1){P(full);P(mutex);remove an item from buffer;V(mutex);V(empty);consume the item;
}

(二)读者-写者模型

//读进程优先
int count=0;
semaphore mutex=1;
semaphore rw=1;
writer(){               while(1){P(rw);writing;V(rw);}
}
reader(){while(1){P(mutex);if(count==0)P(rw);count++;V(mutex);reading;P(mutex);count--;if(count==0)V(rw);V(mutex);}
}

(三)哲学家就餐

semaphore chopstick[5]={1, 1, 1, 1, 1};     //初始化信号量
semaphore mutex=1;                         //设置取筷子的信号量
Pi(){                           //i号哲学家的进程do{P(mutex);              //取筷子前获得互斥量         P(chopstick[i]);        //取左边筷子P(chopstick[(i+1)%5]);  //取右边筷子V(mutex);eat;V(chopstick[i]);P(chopstick[(i+1)%5]);think;}while(1);
}

(四)吸烟者问题

int random;
semaphore offer1=0;
semaphore offer2=0;
semaphore offer3=0;
semaphore finish=0;
process P1()    //供应者
{while(1){random=任意一个随机数;random=random%3;if(random==0)V(offer1);else if(random==1)V(offer2);else if(random==2)V(offer3);任意两种材料放在桌子上P(finish);}
}
process P2(){while(1){P(offer3);V(finish);}
}
process P3(){while(1){P(offer2);V(finish);}
}
process P4(){while(1){P(offer`);V(finish);}
}

23. 线程池的了解、优点、调度处理方式和保护任务队列的方式
池的概念:就是为了重用,减少因为创建销毁带来的性能开销;
线程池能控制最大的并发数目,提高资源利用率,便于管理和使用

24. 怎么回收线程
为了资源的重复利用,反复创建线程也会需要消耗系统资源的, 线程池一般是一开始就预留的创建几个线程。比如4个。然后会有一些任务队列,当你需要进行任务处理的时候,就把任务push到这个队列。线程池中的线程就会像消费者一样,从队列中pop出任务来进行处理,当队列中没有任务的时候,线程池的线程就阻塞等待等。

25. 僵尸进程问题
子进程结束但是没有等到父进程wait他,那么他将会变为僵尸进程。内核仍然会保留它的一些信息,这些信息会一直保留到系统为她传递给它的父进程为止,如果父进程结束了也over了。

26. 条件变量用在什么场景,和互斥锁的区别是

  • 互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥锁加锁的线程将会阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥锁加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。
  • 条件变量(cond)是在多线程程序中用来实现"等待–》唤醒"逻辑常用的方法。条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使“条件成立”。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。在函数返回时,互斥量再次被锁住。
  • 互斥锁和条件变量所解决的,是不同的问题,不同的场景。
    互斥锁解决的是在 shared memory space 模型下,多个线程对同一个全局变量的访问的竞争问题。由于写操作的非原子性(从内存中读进寄存器,修改,如果其他线程完成了对这个变量的修改,则旧的修改就被覆盖,等等问题),必须保证同一时间只有一个线程在进行写操作。这就涉及到了互斥锁,将临界区的操作锁起来,保证只有一个线程在进行操作。多个线程在等待同一把锁的时候,按照 FIFO 组织队列,当锁被释放时,队头线程获得锁(由操作系统管理,具体不表)。没有获得锁的线程继续被 block,换言之,它们是因为没有获得锁而被 block。
  • 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程会先被阻塞,然后解开互斥锁,等待条件变量发生变化。一旦其他的某个线程改变了条件变量,会发出一个signal通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
    总结为:条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。

27. 使用锁是有开销的,如何改进

  • 互斥锁的开销主要在内核态与用户态的切换:
    申请锁时,从用户态进入内核态,申请到后从内核态返回用户态(两次切换);没有申请到时阻塞睡眠在内核态。使用完资源后释放锁,从用户态进入内核态,唤醒阻塞等待锁的进程,返回用户态(又两次切换);被唤醒进程在内核态申请到锁,返回用户态(可能其他申请锁的进程又要阻塞)。所以,使用一次锁,包括申请,持有到释放,当前进程要进行四次用户态与内核态的切换。同时,其他竞争锁的进程在这个过程中也要进行一次切换。
  • 自旋锁
    与互斥锁不同的是自旋锁不会引起调用者睡眠。如果自旋锁已经被别的进程保持,调用者就轮询(不断的消耗CPU的时间)是否该自旋锁的保持者已经释放了锁

28. 了解协程吗?
协程:用户态的轻量级线程,有自己的寄存器和栈

协程,又称微线程。子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

  • 协程的特点在于是一个线程执行,那和多线程比,协程有何优势?
    (1)最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
    (2)第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

29. 进程切换?

  • 所谓的“进程上下文”,就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容,当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
  • 一个进程在执行时,CPU的所有寄存器中的值、进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换到另一个进程时,它需要保存当前进程的所有状态,即保存当前进程的上下文,以便在再次执行该进程时,能够必得到切换时的状态执行下去。在LINUX中,当前进程上下文均保存在进程的任务数据结构中。在发生中断时,内核就在被中断进程的上下文中,在内核态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中继服务结束时能恢复被中断进程的执行。

30. 进程的内存空间分布?堆与栈的区别?为什么用堆这种数据结构?
(一) 进程的内存空间分布

(二) 堆和栈的区别

  • 堆:是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
  • 栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

31. 线程池的作用是什么?
处理线程多并发,用一个数组保存线程,然后一直放着,如果没用就用条件变量让它休眠,如果加入一个新的任务就唤醒其中一个去执行这个任务。

32. pthread_cond_signal和pthread_cond_broadcast的区别

  • pthread_cond_signal表示唤醒睡眠线程中的一个
    【单播,可能按照优先级或者先来后到的原则】
  • pthread_cond_boardcast表示唤醒所有睡眠线程【广播】

32. 死锁的原因?条件?如何预防?又如何避免?如何解除?
原因:系统资源不足;进程运行推进顺序不合适;资源分配不当
条件:互斥;不剥夺;循环等待;请求与保持
预防:破坏任意一个条件
避免:银行家算法
检测:资源分配图简化法

33. 线程私有和共享那些资源?进程私有和共享那些资源?

  • 线程私有:线程栈,寄存器,程序寄存器
    共享:堆,地址空间,全局变量,静态变量
  • 进程私有:地址空间,堆,全局变量,栈,寄存器
    共享:代码段,公共数据,进程目录,进程ID

34. 什么是守护进程?如何查看守护进程?什么是僵尸进程?如何查看僵尸进程?
守护进程:它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
查看守护进程:ps axj
僵尸进程:进程退出,但是占用资源没有被回收
查看僵尸进程:ps aux | grep Z

35 什么是信号?
进程间通信机制中唯一的异步通信机制

36. kill函数的每一个参数的作用?

int kill(pid_t pid, int sig);

pid>0:发给ID为pid的进程
Pid=0:发给进程组所有的进程
Pid=-1:发给所有的进程
Pid<-1:发给指定进程组的进程

37. 确保线程安全的几种方式?
1)原子操作(2)同步与锁(3)可重入(4)阻止过度优化volatile
38. 创建进程的步骤?
(1)申请空的PCB
(2)为新进程分配资源
(3)初始化PCB
(4)将新进程插入就绪队列中

39. 进程切换发生的原因?处理进程切换的步骤?

  • 原因:中断发生;更高优先级进程唤醒;进程消耗完了时间片;资源阻塞;
  • 步骤:
    (1)保存处理器的上下文
    (2)用新状态和其它相关信息更新正在运行进程的PCB
    (3)将原来的进程移到合适的队列中【就绪,阻塞】
    (4)选择另外一个执行的进程,更新被选中进程的PCB,将它加载进CPU

40. 信号的生命周期?
信号产生-》信号在进程中注册-》信号在进程中的注销-》执行信号处理函数

41. 信号的产生方式?
(1)当用户按某些终端键时产生信号
(2)硬件异常产生信号【内存非法访问】
(3)软件异常产生信号【某一个条件达到时】
(4)调用kill函数产生信号【接受和发送的所有者必须相同,或者发送的进程所有者必须为超级用户】
(5)运行kill命令产生信号

42. 信号处理方式?
1)执行默认处理方式(2)忽略处理(3)执行用户自定义的函数

43 . 两个线程交叉打印A和B

  • 方法一:信号量
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>sem_t blank_number, product_number;void *producer(void *arg)
{while (1) {sem_wait(&blank_number);//给blank_number信号量减1,如果blank_number为0,在这里阻塞,直到blank_number不为0printf("%c\n",65);sem_post(&product_number);//给product_number加1sleep(rand()%5);}
}
void *consumer(void *arg)
{while (1) {sem_wait(&product_number);printf("%c\n",66);sem_post(&blank_number);sleep(rand()%5);
}
}
int main(int argc, char *argv[])
{pthread_t pid, cid;sem_init(&blank_number, 0, 1);//对信号量进行初始化sem_init(&product_number, 0, 0);pthread_create(&pid, NULL, producer, NULL);pthread_create(&cid, NULL, consumer, NULL);pthread_join(pid, NULL);//回收线程,当然这块两个线程由于一直在while循环,线程会一直阻塞在这句话pthread_join(cid, NULL);sem_destroy(&blank_number);//销毁信号量sem_destroy(&product_number);return 0;
}
  • 方法二:互斥量
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>pthread_mutex_t mutex;
int flag=1;//因为是linux下c语言实现的,所以不能写成bool flag=true;因为bool类型是C++新增的类型
void *printA(void *arg)
{while (1) {pthread_mutex_lock(&mutex);if(flag==1)printf("%c\n",65);flag=0;pthread_mutex_unlock(&mutex);sleep(rand()%5);}
}
void *printB(void *arg)
{while (1) {pthread_mutex_lock(&mutex);if(flag==0)printf("%c\n",66);flag=1;pthread_mutex_unlock(&mutex);sleep(rand()%5);}
}
int main(int argc, char *argv[])
{pthread_t aid, bid;pthread_mutex_init(&mutex,NULL);pthread_create(&aid, NULL, printA, NULL);//创建线程pthread_create(&bid, NULL, printB, NULL);pthread_join(aid, NULL);//回收线程pthread_join(bid, NULL);pthread_mutex_destroy(&mutex);//销毁互斥量return 0;
}
  • 方法三
#include <thread>
#include <iostream>
#include <mutex>
#include <condition_variable>std::mutex data_mutex;
std::condition_variable data_var;
bool flag = true;void printA()
{while(1){std::this_thread::sleep_for(std::chrono::seconds(1));std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "A" <<std::endl;flag = false;data_var.notify_one();}
}void printB()
{while(1){std::unique_lock<std::mutex> lck(data_mutex) ;data_var.wait(lck,[]{return !flag;});std::cout<<"thread: "<< std::this_thread::get_id() << "   printf: " << "B" <<std::endl;flag = true;data_var.notify_one();}
}int main()
{std::thread tA(printA);std::thread tB(printB);tA.join();tB.join();return 0;
}

44. Select,poll和epoll的区别?为什么?
Select和poll缺点:(1)每次调用select都需要将fd集合从用户态拷贝到内核态(2)每一次调用select都需要在内核中遍历所有的fd(3)select支持的文件描述符太小,默认1024,poll没有限制

Epoll:使用红黑树来存储fd,同时每一次通过epoll__ctl来将fd加入内核中,同时通过双向列表来返回已经出发某一个事件的fd

三、内存管理

1. Windows下的内存是如何管理的
1). 虚拟内存:
最适合用来管理大型对象或者结构数组
2). 内存映射文件:
最适合用来管理大型数据流(通常来自文件)以及在单个计算机上运行多个进程之间共享数据
3). 内存堆栈:
最适合用来管理大量的小对象

2. Windows消息调度机制是?
消息队列;

3. 分段和分页的区别?
(1)页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。
(2)段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好的满足用户的需要。
(3)页的大小固定且由系统确定(一般为4k),把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。段的长度却不固定,决定于用户所编写的程序,通常由编辑程序在对源程序进行编辑时,根据信息的性质来划分。
(4)分页的作业地址空间是一维的,即单一的线性空间,程序员只须利用一个记忆符,即可表示一地址。分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。

4. 分段和分页,段页式存储

  • 分页存储管理:用户程序的地址空间被划分成若干固定大小的区域,称为“页”,相应地,内存空间分成若干个物理块,页和块的大小相等。可将用户程序的任一页放在内存的任一块中,实现了离散分配。
  • 分段存储管理:将用户程序地址空间分成若干个大小不等的段,每段可以定义一组相对完整的逻辑信息。存储分配时,以段为单位,段与段在内存中可以不相邻接,也实现了离散分配。
  • 段页式存储管理
    分页系统能有效地提高内存的利用率,而分段系统能反映程序的逻辑结构,便于段的共享与保护,将分页与分段两种存储方式结合起来,就形成了段页式存储管理方式。
    在段页式存储管理系统中,作业的地址空间首先被分成若干个逻辑分段,每段都有自己的段号,然后再将每段分成若干个大小相等的页。对于主存空间也分成大小相等的页,主存的分配以页为单位。
    段页式系统中,作业的地址结构包含三部分的内容:段号 页号 页内位移量
    程序员按照分段系统的地址结构将地址分为段号与段内位移量,地址变换机构将段内位移量分解为页号和页内位移量。
    为实现段页式存储管理,系统应为每个进程设置一个段表,包括每段的段号,该段的页表始址和页表长度。每个段有自己的页表,记录段中的每一页的页号和存放在主存中的物理块号。

5. 什么叫做虚拟内存?优缺点?它和主存的关系如何?

  • 定义: 具有请求调入功能和置换功能,能从逻辑上对内存容量加以扩充得一种存储器系统。大小由计算机的地址空间决定,实际容量是主存与辅存的和。
  • 与传统存储器比较虚拟存储器有以下三个主要特征:
    (1)多次性,是指无需在作业运行时一次性地全部装入内存,而是允许被分成多次调入内存运行。
    (2)对换性,是指无需在作业运行时一直常驻内存,而是允许在作业的运行过程中,进行换进和换出。
    (3)虚拟性,是指从逻辑上扩充内存的容量,使用户所看到的内存容量,远大于实际的内存容量。
  • 关系
    虚拟内存是一些系统页文件,存放在磁盘上,每个系统页文件大小为4K,物理内存也被分页,每个页大小也为4K,这样虚拟页文件和物理内存页就可以对应,实际上虚拟内存就是用于物理内存的临时存放的磁盘空间。页文件就是内存页,物理内存中每页叫物理页,磁盘上的页文件叫虚拟页,物理页+虚拟页就是系统所有使用的页文件的总和。
  • 虚拟内存的实现
    虚拟内存的实现建立在离散分配的内存管理方式基础上
    (1)请求分页存储管理。
    (2)请求分段存储管理。
    (3)请求段页式存储管理

6. 页面置换方法详细介绍

  • 最佳置换算法(OPT)(理想置换算法):从主存中移出永远不再需要的页面;如无这样的页面存在,则选择最长时间不需要访问的页面,这样可以保证获得最低的缺页率。
  • 先进先出置换算法(FIFO):是最简单的页面置换算法。这种算法的基本思想是:当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。其理由是:最早调入主存的页面不再被使用的可能性最大。
    缺点:对于有些经常被访问的页面如含有全局变量、常用函数、例程等的页面,不能保证这些不被淘汰。
  • 最近最久未使用(LRU)算法:这种算法的基本思想是:利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。
  • 时钟(CLOCK)置换算法:LRU算法的性能接近于OPT,但是实现起来比较困难,且开销大;FIFO算法实现简单,但性能差。所以操作系统的设计者尝试了很多算法,试图用比较小的开销接近LRU的性能,这类算法都是CLOCK算法的变体。
    简单的CLOCK算法是给每一帧关联一个附加位,称为使用位。当某一页首次装入主存时,该帧的使用位设置为1;当该页随后再被访问到时,它的使用位也被置为1。对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。由于该算法循环地检查各页面的情况,故称为CLOCK算法,又称为最近未用(Not Recently Used, NRU)算法。

7. 虚拟地址空间

8. 什么是缓冲区溢出?有什么危害?其原因是什么?
(1)缓冲区溢出是指当计算机向缓冲区内填充数据时超过了缓冲区本身的容量,溢出的数据覆盖在合法数据上。
(2)危害:缓冲区溢出中,最为危险的是堆栈溢出,因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址,带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如得到shell,然后为所欲为。通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
(3)造成缓冲区溢出的主原因是程序中没有仔细检查用户输入的参数。

9. 逻辑地址 Vs 物理地址 Vs 虚拟内存

  • 所谓的逻辑地址,是指计算机用户(例如程序开发者),看到的地址。例如,当创建一个长度为100的整型数组时,操作系统返回一个逻辑上的连续空间:指针指向数组第一个元素的内存地址。由于整型元素的大小为4个字节,故第二个元素的地址时起始地址加4,以此类推。事实上,逻辑地址并不一定是元素存储的真实地址,即数组元素的物理地址(在内存条中所处的位置),并非是连续的,只是操作系统通过地址映射,将逻辑地址映射成连续的,这样更符合人们的直观思维。
  • 另一个重要概念是虚拟内存。操作系统读写内存的速度可以比读写磁盘的速度快几个量级。但是,内存价格也相对较高,不能大规模扩展。于是,操作系统可以通过将部分不太常用的数据移出内存,“存放到价格相对较低的磁盘缓存,以实现内存扩展。操作系统还可以通过算法预测哪部分存储到磁盘缓存的数据需要进行读写,提前把这部分数据读回内存。虚拟内存空间相对磁盘而言要小很多,因此,即使搜索虚拟内存空间也比直接搜索磁盘要快。唯一慢于磁盘的可能是,内存、虚拟内存中都没有所需要的数据,最终还需要从硬盘中直接读取。这就是为什么内存和虚拟内存中需要存储会被重复读写的数据,否则就失去了缓存的意义。现代计算机中有一个专门的转译缓冲区(Translation Lookaside Buffer,TLB),用来实现虚拟地址到物理地址的快速转换。
  • 与内存/虚拟内存相关的还有如下两个概念:
    (1)Resident Set: 当一个进程在运行的时候,操作系统不会一次性加载进程的所有数据到内存,只会加载一部分正在用,以及预期要用的数据。其他数据可能存储在虚拟内存,交换区和硬盘文件系统上。被加载到内存的部分就是resident set。
    (2)Thrashing: 由于resident set包含预期要用的数据,理想情况下,进程运行过程中用到的数据都会逐步加载进resident set。但事实往往并非如此:每当需要的内存页面(page)不在resident set中时,操作系统必须从虚拟内存或硬盘中读数据,这个过程被称为内存页面错误(page faults)。当操作系统需要花费大量时间去处理页面错误的情况就是thrashing。

10. 内部碎片与外部碎片

  • 在内存管理中,内部碎片是已经被分配出去的的内存空间大于请求所需的内存空间。
  • 外部碎片是指还没有分配出去,但是由于大小太小而无法分配给申请空间的新进程的内存空间空闲块。
  • 固定分区存在内部碎片,可变式分区分配会存在外部碎片;
    页式虚拟存储系统存在内部碎片;段式虚拟存储系统,存在外部碎片
  • 为了有效的利用内存,使内存产生更少的碎片,要对内存分页,内存以页为单位来使用,最后一页往往装不满,于是形成了内部碎片。
    为了共享要分段,在段的换入换出时形成外部碎片,比如5K的段换出后,有一个4k的段进来放到原来5k的地方,于是形成1k的外部碎片。

11. 虚拟内存机制?mmu?
(一)虚拟内存机制
(1)为什么要有虚拟内存
在早期的计算机中,是没有虚拟内存的概念的。我们要运行一个程序,会把程序全部装入内存,然后运行。
当运行多个程序时,经常会出现以下问题:
1)进程地址空间不隔离,没有权限保护。
由于程序都是直接访问物理内存,所以一个进程可以修改其他进程的内存数据,甚至修改内核地址空间中的数据。
2)内存使用效率低
当内存空间不足时,要将其他程序暂时拷贝到硬盘,然后将新的程序装入内存运行。
由于大量的数据装入装出,内存使用效率会十分低下。
3)程序运行的地址不确定
因为内存地址是随机分配的,所以程序运行的地址也是不确定的。
(2)虚拟地址和物理地址
对于32位系统,寻址指针为4字节,对应的虚拟地址空间为0-2^32,即0-4G。
对于64位系统,寻址指针为8字节,对应的虚拟地址空间为0-2^64,即0-16G。
要注意的是,这个地址空间是虚拟的,并非实际存在的。
Linux内核把虚拟地址空间分为两部分:用户进程空间,内核进程空间。
在缓存原理中,换入/换出的数据以块为最小单位。在内存管理时,==页是地址空间的最小单位。==虚拟页和物理页的大小是一样的,通常为4KB。

  • 虚拟页和物理页存在着以下关系:
    虚拟页和磁盘文件映射,然后缓存到物理页。
    根据是否映射,是否缓存,可以将虚拟页的状态分为以下三种:
    1)未映射的页
    即虚拟页没有映射到磁盘文件
    2)未缓存的页
    虚拟页映射到了磁盘文件,但是没有缓存到物理页,也就是内存上。
    3)缓存的页
    虚拟页映射到了磁盘文件,并且缓存到物理页

(3)虚拟地址的工作原理
对于进程来说,使用的都是虚拟地址。每个进程维护一个单独的页表。何为页表?
页表是一种数组结构,存放着各虚拟页的状态,是否映射,是否缓存。
1)数组的索引号,表示虚拟页号
2)数组的值
若为null,表示未映射的页
若非null,第一位表示有效位,为1,表明缓存的页;为0,表明未缓存的页。
其余位表示缓存到的物理页号。

进程执行时,当需要访问虚拟地址中存放的值时,步骤如下:

  • 1)CPU会先找到虚拟地址所在的虚拟页(VP3),根据页表,找出页表中第3条的值。
    判断有效位,为1,DRMA缓存命中,获根据物理页号,找到物理页中的内容,返回。
  • 2)若有效位为0,产生缺页异常,调用内核缺页异常处理程序。
    它会选择一个物理页(如PP4),作为牺牲页,将该页的内容刷新到磁盘文件。然后,把VP3映射的磁盘文件,缓存到该物理页。页表中的第3条,有效位变1,同时,物理页号表号变为PP4。
  • 3)缺页异常处理完毕后,返回中断前的指令,重新执行,此时缓存命中,执行1)
  • 4)将找到的内容映射到高速缓存,CPU从高速缓存中获取该值,结束。

(4).使用虚拟地址需要注意的问题
1)磁盘和主存传送页的活动叫做页面调度。页面调度会引起磁盘流量,如果程序的局部性不好,会频繁进行页面调度,叫做“缓存颠簸”。
2)一级页表占用的空间是比较大的,根据按需调度的原则,一般使用的是多级页表,即一级页表指向二级页表,这样大大压缩了页表的大小。
(5).地址翻译
地址翻译指的是DRAM缓存命中时,由虚拟地址找到物理地址的过程。该过程是完全由硬件来完成的。

  • 1)CPU有一个专门的页表基地址寄存器(PTBR)指向当前页表的基地址,快速定位到该进程的页表。
  • 2)根据虚拟页号,找到虚拟地址在页表的值。
  • 3)根据值中的物理页号,找到物理地址。

(6).Linux中的虚拟内存机制
Linux把虚拟内存划分成区域area的集合,一个area包括连续的多个页。
请求分页系统中,只需要将当前需要的一部分页面装入内存,而其余部分留在外存,就可以启动程序。在执行的过程中,当所要访问的页面不在内存时,通过调页功能将其调入,同事可以通过置换功能将当前不用的页面换到外存上,以腾出内存空间

  • 在Linux中,当发生缺页异常时,步骤如下:
    1)缺页异常程序,检查虚拟地址在哪个area内。
    2)访问的虚拟页若没有读写权限,则触发一个保护异常,终止进程。
    3)选择牺牲页,刷新到磁盘,从磁盘加载缺失的内容到物理页,更新页表。

(7)深入理解

  • 1.每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理地址
  • 2.所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上
  • 3.进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要页表记录
  • 4.页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存的地址
  • 5.当进程访问某个虚拟地址,去查看页表,如果对应的数据不在物理内存中,,则缺页异常
  • 6.缺页异常的处理过程,就是把进程需要的数据从磁盘拷贝到物理内存中,如果内存已经满了 ,没有空地方,那就找一个页进行覆盖,当然如果被覆盖的页曾经被修改过,需要将此页写回磁盘

(二)MMU
从CPU到MMU的地址称为虚拟地址( Virtual Address,以下简称VA) ,而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址

  1. 在操作系统初始化或者分配、释放内存时,会执行一些指令在物理内存中填写页表,然后,用指令设置MMU,告诉MMU页表在物理内存中的什么位置。
  2. 设置好之后, CPU每次执行访问内存的指令都会自动引发MMU做查表和地址转换的操作,地址转换操作完全由硬件完成,不需要用指令控制MMU去做。

12. 能否实现一个LRU算法
选择最近最长时间未访问过的页面淘汰,认为过去一段时间内未被访问过的页面在最近的将来可能也不会淘汰。
查找快,插入快,删除快,有顺序之分。必须有顺序之分,以区分最近使用的和久未使用的数据;而且我们要在 cache 中查找键是否已存在;如果容量满了要删除最后一个数据;每次访问还要把数据插入到队头。
LRU 缓存算法的核心数据结构就是哈希链表,双向链表和哈希表的结合体。

13. 海量数据的bitmap使用原理
所谓的BitMap就是用一个bit位来标记某个元素所对应的value,而key即是该元素,由于BitMap使用了bit位来存储数据,因此可以大大节省存储空间。
基本思想:
这此我用一个简单的例子来详细介绍BitMap算法的原理。假设我们要对0-7内的5个元素(4,7,2,5,3)进行排序(这里假设元素没有重复)。我们可以使用BitMap算法达到排序目的。要表示8个数,我们需要8个byte。

  • 1.首先我们开辟一个字节(8byte)的空间,将这些空间的所有的byte位都设置为0
  • 2.然后便利这5个元素,第一个元素是4,因为下边从0开始,因此我们把第五个字节的值设置为1
  • 3.然后再处理剩下的四个元素,最终8个字节的状态如下图
  • 4.现在我们遍历一次bytes区域,把值为1的byte的位置输出(2,3,4,5,7),这样便达到了排序的目的

14. memcache了解
分布式内存对象缓存系统,用于动态Web应用以减轻数据库的负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高了网站访问的速度。存储键值对的HashMap。适合对数据库有高并发读写和海量数据处理的场景。

15. 一般情况下在Linux/windows平台下栈空间的大小
win是编译器决定栈的大小,记录在可执行文件中,默认是1M。linux是os来决定的,在系统环境变量中设置, ulimit -s 命令查看修改,我的是8M

16. 内存泄露与内存溢出的区别

  • 定义
    内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
    内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。
  • 二者关系
    (1)内存泄漏的堆积最终会导致内存溢出
    (2)内存溢出就是你要的内存空间超过了系统实际分配给你的空间,此时系统相当于没法满足你的需求,就会报内存溢出的错误。
    (3)内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序。
    (4)内存溢出:一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。比方说栈,栈满时再做进栈必定产生空间溢出,叫上溢,栈空时再做退栈也产生空间溢出,称为下溢。就是分配的内存不足以放下数据项序列,称为内存溢出。说白了就是我承受不了那么多,那我就报错
  • 内存溢出原因:
    (1)内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
    (2)集合类中有对对象的引用,使用完后未清空
    (3)代码中存在死循环或循环产生过多重复的对象实体;
    (4)使用的第三方软件中的BUG;
    (5)启动参数内存值设定的过小
  • 内存溢出的解决方案:
    (1)直接增加内存
    (2)检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。
    (3)对代码进行走查和分析,找出可能发生内存溢出的位置。
    (4)使用内存查看工具动态查看内存使用情况

17. 编程时如何定位内存泄漏

  • 常见的内存错误:
    (1)内存分配未成功,却使用了它
    (2)内存分配成功,但尚未初始化就引用它
    (3)内存分配成功且初始化,但操作越过了内存的边界
    (4)忘记释放内存,造成内存泄漏
    (5)释放了内存却继续使用它
  • 方法
    (1)重载new/delete操作符
    重载new/delete操作符,用list或者map记录对内存的使用情况。new一次,保存一个节点,delete一次,就删除节点。
    最后检测容器里是否还有节点,如果有节点就是有泄漏。也可以记录下哪一行代码分配的内存被泄漏。
    类似的方法:在每次调用new时加个打印,每次调用delete时也加个打印。
    (2)查看进程maps表
    在实际调试过程中,怀疑某处发生了内存泄漏,可以查看该进程的maps表,看进程的堆或mmap段的虚拟地址空间是否持续增加。如果是,说明可能发生了内存泄漏。如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存。

18. 缓冲区实现,为什么不使用循环队列

  • 缓冲区的设计:
    (1)缓冲区是一个先进先出队列。写入模块将信息插入队列;读出模块将信息弹出队列。
    (2)写入模块与读出模块需要进行信息的协调和同步。
    (3)对于多线程和多进程的写入或读出模块,写入模块间以及读出模块间需要进行临界区处理。
  • 循环队列实现
    环形队列的特点是,不需要进行动态的内存释放和分配,使用固定大小的内存空间反复使用。在实际的队列插入和弹出操作中,是不断交叉进行的。空队列时头指针和尾指针重合且都为0,当分配一个内存时head(头指针)加1;当释放一个内存时tail(尾指针)加1。这样就实现了先入先出。当入队操作时,head会增加;而出队操作时,tail会增加。入队的速度快的时候,head有可能追上 tail,这个时候说明队列已经满了,不能再进行入队的操作了,需要等待出队 操作腾出队列的空间。当 出队 的操作快,使得 tail 追上 head,这个时候说明队列已空了,不能再进行出队 操作了,需要等待 入队 进来数据。

19. 虚拟内存实现有哪几种方式?有什么意义?
三种:请求分页存储管理;请求分段存储管理;请求段页式存储管理
20. 请求页面置换策略有哪些方式?他们的区别是什么?各自有什么算法解决?
全局和局部;
全局:在整个内存空间置换
局部:在本进程中进行置换
全局:(1)工作集算法(2)缺页率置换算法
局部:(1)最优算法(2)FIFO先进先出算法(3)LRU最近最久未使用(4)时钟算法

四、文件管理

1. 文件读写使用的系统调用
open,close,creat,read,write

#include<unistd.h>ssize_t read(int fd, void *buf, size_t count);
//read()函数从文件描述符fd对应的文件中读取count字节,放到buf开始的缓冲区。ssize_t write(int fd, const void *buf, size_t count);
//write()函数向文件描述符fd写入数据,数据的大小有count指定,buf为要写入数据的指针。int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
//open()函数根据用户设置的标志flags和模式mode在路径pathname下建立或者打开一个文件。
flags的取值:O_RDONLY:只读O_WRONLY:只写O_RDWR:读写O_APPEND:对文件的读写追加到文件的尾端O_CREAT:文件不存在则创建它,同时第三个参数mode需要设定新文件的权限.O_TRUNC:将文件的长度断为0。常用来对文件进行清空。
mode的取值:S_IRWXU:用户(文件所有者)有读、写、执行的权限S_IRUSR:用户对文件有读的权限S_IWUSR:用户对文件有写的权限S_IXUSR:用户对文件又执行的权限S_IRWXG:组用户(文件所有者)有读、写、执行的权限S_IRGRP:组用户对文件有读的权限S_IWGRP:组用户对文件有写的权限S_IXGRP:组用户对文件有执行的权限S_IRWXO:其他用户(文件所有者)有读、写、执行的权限S_IROTH:其他用户对文件有读的权限S_IWOTH:其他用户对文件有写的权限S_IXOTH:其他用户对文件有执行的权限int close(int fd);
//close()函数关闭一个文件描述符,关闭以后此文件描述符不再指向任何文件,
//从而描述符可以再次使用。当函数执行成功的时候返回0,如果有错误发生,返回-1.

2. 文件系统的理解(EXT4,XFS,BTRFS)
ext4 :日志文件系统,意味着它会对文件在磁盘中的位置以及任何其它对磁盘的更改做记录。纵观它的所有功能,它还不支持透明压缩、重复数据删除或者透明加密。

ext4 性能不错,但是支持的大小不行;btrfs有很多好用的功能(写复制、扩展校验、快照、清洗、自修复数据、冗余删除以及其它保证数据完整性的功能)

3. 文件处理grep,awk,sed这三个命令必知必会

  • grep是文本过滤器,对一个或多个正则进行匹配输出对应的行或文本,速度快,返回搜索的状态,0成功,1搜不到,2文件不存在;*是匹配其前面的任意字符
  • sed是流编译器,针对行,不改变原内容,文件读行到缓冲区然后处理后送往屏幕。
  • awk:报告生成器。能按照列来处理,功能很全,有自己的语言,处理文本的编程语言工具

五、输入输出(I/O)管理

1. 同步、异步、阻塞、非阻塞?

  • 同步和异步
    (1) 同步: 当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行。
    (2) 异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。
  • 异步调用,要想获得结果,一般有两种方式:
    (1) 主动轮询异步调用的结果;
    (2) 被调用方通过callback来通知调用方调用结果。
  • 阻塞与非阻塞
    (1) 阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,还是非挂起状态。
    (2) 阻塞调用在发出去后,在消息返回之前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活.
    (3) 非阻塞调用在发出去后,不会阻塞当前进/线程,而会立即返回。
    同步与异步,重点在于消息通知的方式;阻塞与非阻塞,重点在于等消息时候的行为。
    出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
    1 老张把水壶放到火上,立等水开。(同步阻塞)老张觉得自己有点傻
    2 老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
    老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
    3 老张把响水壶放到火上,立等水开。(异步阻塞)老张觉得这样傻等意义不大
    4 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
    所谓同步异步,只是对于水壶而言。
    普通水壶,同步;响水壶,异步。
    虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。
    同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。

2. Linux的I/O模型介绍以及同步异步阻塞非阻塞的区别(超级重要)
同步:我调用一个功能,该功能没有结束前,我死等结果。异步:不需要知道该功能结果,该功能有结果后通知我(回调通知)

阻塞:没有接收完数据或者没有得到结果之前,我不会返回。非阻塞,就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞;阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回

5种IO模型:同步(阻塞IO,非阻塞IO(会返回错误,然后IO操作函数会不断测试是否准备好,会占用很多cpu时间),IO复用(select,poll,epoll函数,能实现多个IO操作的阻塞,同时对多个IO函数进行检测),信号驱动IO(不阻塞,考sigio信号)),异步I/O(通过状态、通知和回调来通知调用者的输入输出操作)

3. IO多路复用
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。IO多路复用适用如下场合:

  • 当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。
  • 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  • 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  • 如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。
  • 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
  • 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

4. 同步IO、异步IO、阻塞IO、非阻塞IO之间的联系与区别
这里统一使用Linux下的系统调用recv作为例子,它用于从套接字上接收一个消息,因为是一个系统调用,所以调用时会从用户进程空间切换到内核空间运行一段时间再切换回来。
(一) 阻塞IO模型
使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。

(二) 非阻塞IO模型
改变flags,让recv不管有没有获取到数据都返回,如果没有数据那么一段时间后再调用recv看看,如此循环。B同学也用杯子装水,打开水龙头后发现没有水,它离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,B同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。

(三) IO复用模型
这种IO模型比较特别,分个段。因为它能同时监听多个文件描述符(fd)。这个时候C同学来装水,发现有一排水龙头,舍管阿姨告诉他这些水龙头都还没有水,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了,但不知道是哪个水龙头有水,自己看吧。于是C同学一个个打开,往杯子里装水(recv)。这里再顺便说说鼎鼎大名的epoll(高性能的代名词啊),epoll也属于IO复用模型,主要区别在于舍管阿姨会告诉C同学哪几个水龙头有水了,不需要一个个打开看(当然还有其它区别)。

(四)信号驱动IO模型
通过调用sigaction注册信号函数,等内核数据准备好的时候系统中断当前程序,执行信号函数(在这里面调用recv)。D同学让舍管阿姨等有水的时候通知他(注册信号函数),没多久D同学得知有水了,跑去装水。是不是很像异步IO?很遗憾,它还是同步IO(省不了装水的时间啊)。

(五) 异步IO模型
调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。E同学让舍管阿姨将杯子装满水后通知他。整个过程E同学都可以做别的事情(没有recv),这才是真正的异步IO。

总结
IO分两阶段:

1.数据准备阶段
2.内核空间复制回用户进程缓冲区阶段

一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。

4. IO模型主要有哪些?
阻塞,非阻塞,IO多路复用,异步

要求实现时间跨度为一天的定时器,插入时间复杂度O(1),定时不需要很精确
** IO复用的三种方法(select,poll,epoll高并发多连接时好)深入理解**
** Epoll的ET模式和LT模式**

定时器为什么要用最小堆
由于定时器的触发是由于时间到了,因此只有时间最短的定时器会首先被触发,通过这个原理,我们可以采用最小堆

定时器除了小根堆,还可以怎么做
链表、时间轮

《操作系统学习总结》相关推荐

  1. ComeFuture英伽学院——2020年 全国大学生英语竞赛【C类初赛真题解析】(持续更新)

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  2. ComeFuture英伽学院——2019年 全国大学生英语竞赛【C类初赛真题解析】大小作文——详细解析

    视频:ComeFuture英伽学院--2019年 全国大学生英语竞赛[C类初赛真题解析]大小作文--详细解析 课件:[课件]2019年大学生英语竞赛C类初赛.pdf 视频:2020年全国大学生英语竞赛 ...

  3. 信息学奥赛真题解析(玩具谜题)

    玩具谜题(2016年信息学奥赛提高组真题) 题目描述 小南有一套可爱的玩具小人, 它们各有不同的职业.有一天, 这些玩具小人把小南的眼镜藏了起来.小南发现玩具小人们围成了一个圈,它们有的面朝圈内,有的 ...

  4. 信息学奥赛之初赛 第1轮 讲解(01-08课)

    信息学奥赛之初赛讲解 01 计算机概述 系统基本结构 信息学奥赛之初赛讲解 01 计算机概述 系统基本结构_哔哩哔哩_bilibili 信息学奥赛之初赛讲解 02 软件系统 计算机语言 进制转换 信息 ...

  5. 信息学奥赛一本通习题答案(五)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  6. 信息学奥赛一本通习题答案(三)

    最近在给小学生做C++的入门培训,用的教程是信息学奥赛一本通,刷题网址 http://ybt.ssoier.cn:8088/index.php 现将部分习题的答案放在博客上,希望能给其他有需要的人带来 ...

  7. 信息学奥赛一本通 提高篇 第六部分 数学基础 相关的真题

    第1章   快速幂 1875:[13NOIP提高组]转圈游戏 信息学奥赛一本通(C++版)在线评测系统 第2 章  素数 第 3 章  约数 第 4 章  同余问题 第 5 章  矩阵乘法 第 6 章 ...

  8. 信息学奥赛一本通题目代码(非题库)

    为了完善自己学c++,很多人都去读相关文献,就比如<信息学奥赛一本通>,可又对题目无从下手,从今天开始,我将把书上的题目一 一的解析下来,可以做参考,如果有错,可以告诉我,将在下次解析里重 ...

  9. 信息学奥赛一本通(C++版) 刷题 记录

    总目录详见:https://blog.csdn.net/mrcrack/article/details/86501716 信息学奥赛一本通(C++版) 刷题 记录 http://ybt.ssoier. ...

  10. 最近公共祖先三种算法详解 + 模板题 建议新手收藏 例题: 信息学奥赛一本通 祖孙询问 距离

    首先什么是最近公共祖先?? 如图:红色节点的祖先为红色的1, 2, 3. 绿色节点的祖先为绿色的1, 2, 3, 4. 他们的最近公共祖先即他们最先相交的地方,如在上图中黄色的点就是他们的最近公共祖先 ...

最新文章

  1. docker-部署elk-6.1.3
  2. 在ASP.NET使用javascript的一点小技巧
  3. java error与exception_Java中Error与Exception的区别
  4. GitHub#java#:设计模式
  5. Java面试题超详细讲解系列之四【Jvm篇】
  6. python:for循环修改list的值,应使用range
  7. QT 自带字体样式有哪些?
  8. 如何做好网站SEO,和大家分享十年草根站长优化成绩
  9. 在校开发的装柜辅助系统
  10. 用 Python 实现电影订票系统 | 内附源码
  11. 解决svn报错 : The pristine text with checksum 'e006b124faa4ddf60d8773d1855e6bfa56145874' was not fou
  12. H - Repeating Decimals
  13. canvas画布调节字符间距
  14. #AMBER 分子动力学软件Amber18介绍与基础教程(持续更新)
  15. js实现二维数组去重
  16. Android系统开发-添加USB转串口设备驱动i.MX8M开发
  17. Linux网络环境搭建
  18. 用wireshark抓包分析TLS协议
  19. ai模仿声音软件_利用人工智能技术模仿CEO的声音进行了诈骗
  20. 如何实现最佳的跨平台游戏体验?Unity成亮解密实时渲染

热门文章

  1. 校园网WiFi免认证软件监控
  2. ajax.googleapis firefox,使用Redirector插件解决googleapis公共库加载的问题【转】
  3. 到底还有没有月薪3万以下的程序员?程序员工资真的这么高?
  4. 请教一下水卡校验算法
  5. Poi 4.0设置自定义背景颜色
  6. Weixin4j微信开发网页授权获取openid案例
  7. IAR下载程序只有提示音,没有任何反应。或报错The configuration does not have debuggable output.(A debug-only project shoul
  8. 自学Java day53 使用jvav实现 并查集 数据结构 从jvav到架构师
  9. 清华刘知远团队巨作!Pre-trained Prompt Tuning框架,让超大模型调参变简单
  10. ORACLE 全角数字转半角数字