Java面试之操作系统

1、冯诺依曼模型

运算器、控制器、存储器、输入设备、输出设备

32位和64位CPU最主要区别是一次性能计算多少字节数据,如果计算的数额不超过 32 位数字的情况下,32 位和 64 位 CPU 之间没什么区别的,只有当计算超过 32 位数字的情况下,64 位的优势才能体现出来

线路位宽  cpu能操作的内存大小 比如cpu想要操作4G的内存,就需要32条地址总线。2^32=4G

2、程序执行的基本过程

一个程序执行的时候,CPU 会根据程序计数器里的内存地址,从内存里面把需要执行的指令读取到指令寄存器里面执行,然后根据指令长度自增,开始顺序读取下一条指令。

3、指令的执行速度

程序的CPU执行时间=指令数*每条指令的平均时钟周期数*时钟周期时间

时钟周期时间=1/电脑主频  如1/2.4G

4、存储

寄存器

CPU cache SRAM 静态随机存储器  

  • 每个 CPU 核心都有一块属于自己的 L1 高速缓存,指令和数据在 L1 是分开存放的,所以 L1 高速缓存通常分成指令缓存数据缓存
  •  L2 高速缓存位置比 L1 高速缓存距离 CPU 核心 更远 大小比 L1 高速缓存更大
  • L3 高速缓存通常是多个 CPU 核心共用的

内存   DRAM (Dynamic Random Access Memory,动态随机存取存储器) 的芯片。数据会被存储在电容里,电容会不断漏电,所以需要「定时刷新」电容,才能保证数据不会被丢失

CPU 从 L1 Cache 读取数据的速度,相比从内存读取的速度,会快 100 多倍

硬盘

  • 固态硬盘:断电后数据还是存在的,而内存、寄存器、高速缓存断电后数据都会丢失。内存的读写速度比 SSD 大概快 10~1000 倍。
  • 机械硬盘:通过物理读写的方式来访问数据的,因此它访问速度是非常慢的,它的速度比内存慢 10W 倍左右。

每个存储器只和相邻的一层存储器设备打交道

5、如何写出让CPU 跑的快的代码

CPU Cache 的数据是从内存中读取过来的,它是以一小块一小块读取数据的

CPU L1 Cache 分为数据缓存和指令缓存,因而需要分别提高它们的缓存命中率:

  • 对于数据缓存,我们在遍历数据的时候,应该按照内存布局的顺序操作,这是因为 CPU Cache 是根据 CPU Cache Line 批量操作数据的,所以顺序地操作连续内存数据时,性能能得到有效的提升;
  • 对于指令缓存,有规律的条件分支语句能够让 CPU 的分支预测器发挥作用,进一步提高执行的效率;

对于多核 CPU 系统,线程可能在不同 CPU 核心来回切换,这样各个核心的缓存命中率就会受到影响,于是要想提高线程的缓存命中率,可以考虑把线程绑定 CPU 到某一个 CPU 核心。 

6、cpu缓存一致性

写数据

写直达 如果cache已经存在,则更新后写入内存  如果cache不存在,则把数据更新到内存

写回     对于已经缓存在 Cache 的数据的写入,只需要更新其数据就可以,不用写入到内存,只有在需要把缓存里面的脏数据交换出去的时候,才把数据同步到内存里,这种方式在缓存命中率高的情况,性能会更好

如何缓存一致

  • 写传播,也就是当某个 CPU 核心发生写入操作时,需要把该事件广播通知给其他核心;
  • 第二点是事物的串行化,顺序

 总线嗅探机制的 MESI 协议  已修改、独占、共享、已失效  

对于在「已修改」或者「独占」状态的 Cache Line,修改更新其数据不需要发送广播给其他 CPU 核心。

7、伪共享

CPU Cache Line 大小一般是 64 个字节 这种因为多个线程同时读写同一个 Cache Line 的不同变量时,而导致 CPU Cache 失效的现象称为伪共享 

如何避免?

 多个线程共享的热点数据,即经常会修改的数据,应该避免这些数据刚好在同一个 Cache Line 中,否则就会出现为伪共享的问题。

在 Linux 内核中存在 __cacheline_aligned_in_smp 宏定义,是用于解决伪共享的问题。是用空间换时间

字节填充

8、cpu选择线程

  • SCHED_DEADLINE:是按照 deadline 进行调度的,距离当前时间点最近的 deadline 的任务会被优先调度;实时任务总是会比普通任务优先被执行
  • SCHED_FIFO:对于相同优先级的任务,按先来先服务的原则,但是优先级更高的任务,可以抢占低优先级的任务,也就是优先级高的可以「插队」;
  • SCHED_RR:对于相同优先级的任务,轮流着运行,每个任务都有一定的时间片,当用完时间片的任务会被放到队列尾部,以保证相同优先级任务的公平性,但是高优先级的任务依然可以抢占低优先级的任务;

9、软中断 

中断处理程序的上部分和下半部可以理解为:

  • 上半部直接处理硬件请求,也就是硬中断,主要是负责耗时短的工作,特点是快速执行;
  • 下半部是由内核触发,也就说软中断,主要是负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行;

 Linux 中的软中断包括网络收发、定时、调度、RCU 锁等各种类型  软中断的处理是通过“ksoftirqd”内核线程来实现的

10、补码

有了补码,负数的加减法操作,实际上是和正数加减法操作一样的

十进制整数转二进制使用的是「除 2 取余法」,倒序     

十进制小数使用的是「乘 2 取整法」正序

计算机存小数 符号位 指数位 尾数位

小数计算不精确的原因

因为有的小数无法可以用「完整」的二进制来表示,所以计算机里只能采用近似数的方式来保存,那两个近似数相加,得到的必然也是一个近似数。

11、内核

内核作为应用连接硬件设备的桥梁

  • 进程调度
  • 内存管理
  • 为进程与硬件设备之间提供通信
  • 提供系统调用 应用程序要运行更高权限运行的服务,那么就需要有系统调用,它是用户程序与操作系统之间的接口。

linux内核设计的理念

  • 多任务  并发并行
  • 对称多处理  每个 CPU 的地位是相等的
  • 可执行文件链接格式   Linux 可执行文件格式叫作 ELF,Windows 可执行文件格式叫作 PE。
  • 宏内核    Linux 的内核是宏内核    Windows的内核设计是混合型内核,   

微内核,有一个最小版本的内核,一些模块和服务则由用户态管理;

混合内核,是宏内核和微内核的结合体,内核中抽象出了微内核的概念,也就是内核中会有一个小型的内核,其他模块就在这个基础上搭建,整个内核是个完整的程序;

宏内核,包含多个模块,整个内核像一个完整的程序;

12、虚拟内存

 操作系统会提供一种机制,将不同进程的虚拟地址和不同内存的物理地址映射起来。多进程之间地址冲突问题

虚拟内存还可以是的进程对运行内存超过物理内存的大小

页表里的页表项中除了物理地址之外,还有一些标记属性的比特,比如控制一个页的读写权限,标记该页是否存在等。在内存访问方面,操作系统提供了更好的安全性。

操作系统是如何管理虚拟地址与物理地址之间的关系? 

内存分段  虚拟地址是通过段表与物理地址进行映射的,分段机制会把程序的虚拟地址分成 4 个段,每个段在段表中有一个项,在这一项找到段的基地址,再加上偏移量,于是就能找到物理内存中的地址

问题:内存碎片   内存交换的效率低

  • 每个段的长度不固定,所以多个段未必能恰好使用所有的内存空间,会产生了多个不连续的小物理内存,导致新的程序无法被装载,所以会出现外部内存碎片的问题

内存分页 

分页是把整个虚拟和物理内存空间切成一段段固定尺寸的大小  在 Linux 下,每一页的大小为 4KB采用了分页,页与页之间是紧密排列的,所以不会有外部碎片。如果内存空间不够,操作系统会把其他正在运行的进程中的「最近没被使用」的内存页面给释放掉,也就是暂时写在硬盘上,称为换出Swap Out)。一旦需要的时候,再加载进来,称为换入Swap In)。所以,一次性写入磁盘的也只有少数的一个页或者几个页,不会花太多时间,内存交换的效率就相对比较高。

问题:页表会非常的庞大

解决方案:多级页表

问题:多了几道转换的工序,这显然就降低了这俩地址转换的速度

解决方案:在 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB

13、malloc是如何分配内存的  

malloc() 源码里默认定义了一个阈值:

  • 如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
  • 如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;

malloc()分配的是虚拟内存  如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了  只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。

malloc 申请的内存,free 释放内存会归还给操作系统吗?

  • malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用
  • malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放

mmap()是通过系统调用来实现的,会发生运行态的切换 

14、内存回收 

应用程序通过 malloc 函数申请内存的时候,实际上申请的是虚拟内存,此时并不会分配物理内存。

当应用程序读写了这块虚拟内存,CPU 就会去访问这个虚拟内存, 这时会发现这个虚拟内存没有映射到物理内存, CPU 就会产生缺页中断,进程会从用户态切换到内核态,并将缺页中断交给内核的 Page Fault Handler (缺页中断函数)处理。

缺页中断处理函数会看是否有空闲的物理内存:

  • 如果有,就直接分配物理内存,并建立虚拟内存与物理内存之间的映射关系。
  • 如果没有空闲的物理内存,那么内核就会开始进行回收内存 (opens new window)的工作,如果回收内存工作结束后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会放最后的大招了触发 OOM (Out of Memory)机制。

后台内存回收 异步

直接内存回收 同步

  • 文件页的回收:对于干净页是直接释放内存,这个操作不会影响性能,而对于脏页会先写回到磁盘再释放内存,这个操作会发生磁盘 I/O 的,这个操作是会影响系统性能的。
  • 匿名页的回收:如果开启了 Swap 机制,那么 Swap 机制会将不常访问的匿名页换出到磁盘中,下次访问时,再从磁盘换入到内存中,这个操作是会影响系统性能的。

直接回收 但是空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM 机制

针对回收内存导致的性能影响,常见的解决方式。

  • 设置 /proc/sys/vm/swappiness,调整文件页和匿名页的回收倾向,尽量倾向于回收文件页;
  • 设置 /proc/sys/vm/min_free_kbytes,调整 kswapd 内核线程异步回收内存的时机;
  • 设置 /proc/sys/vm/zone_reclaim_mode,调整 NUMA 架构下内存回收策略,建议设置为 0,这样在回收本地内存之前,会在其他 Node 寻找空闲内存,从而避免在系统还有很多空闲内存的情况下,因本地 Node 的本地内存不足,发生频繁直接内存回收导致性能下降的问题;

15、 在4G物理内存的机器上,申请8G内存会怎么样

  • 在 32 位操作系统,因为进程理论上最大能申请 3 GB 大小的虚拟内存,所以直接申请 8G 内存,会申请失败。
  • 在 64位 位操作系统,因为进程理论上最大能申请 128 TB 大小的虚拟内存,即使物理内存只有 4GB,申请 8G 内存也是没问题,因为申请的内存是虚拟内存。如果这块虚拟内存被访问了,要看系统有没有 Swap 分区:
    • 如果没有 Swap 分区,因为物理空间不够,进程会被操作系统杀掉,原因是 OOM(内存溢出);
    • 如果有 Swap 分区,即使物理内存只有 4GB,程序也能正常使用 8GB 的内存,进程可以正常运行;

16、进程

进程:运行中的程序

cpu管理进程:多个程序、交替执行  进程有着「运行 - 暂停 - 运行」的活动规律

运行-就绪 进程的切换

运行-阻塞  进程等待 占据物理内存 换出换入磁盘内存

用进程控制块数据结构来描述进程 PCB 是进程存在的唯一标识  通常是通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列

CPU 上下文切换就是先把前一个任务的 CPU 上下文(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

一个进程切换到另一个进程运行,称为进程的上下文切换。进程是由内核管理和调度的,所以进程的切换只能发生在内核态。所以,进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。

17、线程

线程是进程当中的一条执行流程。同一个进程内多个线程之间可以共享代码段、数据段、打开的文件等资源,但每个线程各自都有一套独立的寄存器和栈,这样可以确保线程的控制流是相对独立的。

线程是调度的基本单位,而进程则是资源拥有的基本单位

  • 当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;
  • 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据

用户线程是基于用户态的线程管理库来实现的,那么线程控制块TCB实现的  用户线程的切换也是由线程库函数来完成的    操作系统不参与线程的调度,如果一个线程发起了系统调用而阻塞,那进程所包含的用户线程都不能执行了。 当一个线程开始运行后,除非它主动地交出 CPU 的使用权,否则它所在的进程当中的其他线程无法运行,因为用户态的线程没法打断当前运行中的线程,它没有这个特权,只有操作系统才有,但是用户线程不是由操作系统管理的。

内核线程 由操作系统管理的,线程对应的 TCB 自然是放在操作系统里的,这样线程的创建、终止和管理都是由操作系统负责。

18、操作系统的调度

当进程从一个运行状态到另外一状态变化的时候,其实会触发一次调度。

CPU利用率   系统吞吐量  周转时间(进程运行+阻塞时间+等待时间的总和)等待时间 响应时间

进程要快

最古老、最简单、最公平且使用最广的算法就是时间片轮转(Round Robin, RR)调度算法

19、进程之间的通信方式

由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信

管道  通信的方式是单向的 通信的数据是无格式的流并且大小受限 匿名管道是只能用于存在父子关系的进程间通信   命名管道突破了匿名管道只能在亲缘关系进程间的通信限制   进程写入的数据都是缓存在内核

消息队列  消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。

共享内存  共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问   但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。

信号量  保护共享资源,以确保任何时刻只能有一个进程访问共享资源

信号  异步通信机制  可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件

socket  不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信  本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

20、多线程冲突

当多线程相互竞争操作共享变量时,由于运气不好,即在执行过程中发生了上下文切换,我们得到了错误的结果  竞争条件   需要互斥

互斥就好比:「操作 A 和操作 B 不能在同一时刻执行」;

实现——锁 信号量 

同步就好比:「操作 A 应在操作 B 之前执行」,「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等;

同步应该遵循的准则:空闲让进,忙则等待,优先等待,让权等待

21、死锁

两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁

死锁的四个条件: 互斥条件,持有并等待条件、不可剥夺条件、环路等待条件

避免死锁问题就只需要破环其中一个条件就可以,最常见的并且可行的就是使用资源有序分配法,来破环环路等待条件。即 线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。

jstack jdk自带的线程堆栈分析工具

22、悲观锁、乐观锁

互斥锁:两次线程上下文切换

自旋锁:忙等待,直到获取到锁

读写锁:允许多个读线程同时持有读锁,提高读的并发性 

读优先锁,写优先锁,都会饿死另一个线程 所以公平读写锁,先入先出

悲观锁认为并发访问共享资源时,冲突概率可能非常高,所以在访问共享资源前,都需要先加锁。

如果并发访问共享资源时,冲突概率非常低的话,就可以使用乐观锁,它的工作方式是,在访问共享资源时,不用先加锁,修改完共享资源后,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作

版本号或时间戳来确保数据的一致性

23、一个进程最多创建几个线程

  • 32 位系统,用户态的虚拟空间只有 3G,如果创建线程时分配的栈空间是 10M,那么一个进程最多只能创建 300 个左右的线程。
  • 64 位系统,用户态的虚拟空间大到有 128T,理论上不会受虚拟内存大小的限制,而会受系统的参数或性能限制

虚拟内存:线程的栈空间是在虚拟内存中分配的,通过页表映射到物理内存。

按需分配:栈空间的物理内存是按需分配的,只有实际使用时才会分配物理内存。

24、进程调度算法

调度算法影响的是等待时间(进程在就绪队列中等待调度的时间总和),而不能影响进程真在使用 CPU 的时间和 I/O 时间。

  • 先来先服务调度算法
  • 最短作业优先调度算法
  • 高响应比优先调度算法
  • 时间片轮转调度算法
  • 最高优先级调度算法
  • 多级反馈队列调度算法  是「时间片轮转算法」和「最高优先级算法」的综合和发展。           

「多级」表示有多个队列,每个队列优先级从高到低,同时优先级越高时间片越短。新的进程会被放入到第一级队列的末尾,按先来先服务的原则排队等待被调度,如果在第一级队列规定的时间片没运行完成,则将其转入到第二级队列的末尾,以此类推,直至完成;

    「反馈」表示如果有新的进程加入优先级高的队列时,立刻停止当前正在运行的进程,转而去运行优先级高的队列; 

25、内存页面置换算法

当 CPU 访问的页面不在物理内存时,便会产生一个缺页中断,请求操作系统将所缺页调入到物理内存。

页面置换算法的功能是,当出现缺页异常,需调入新页面而内存已满时,选择被置换的物理页面,也就是说选择一个物理页面换出到磁盘,然后把需要访问的页面换入到物理页。

  • 最佳页面置换算法(OPT) 置换在「未来」最长时间不访问的页面
  • 先进先出置换算法(FIFO)无法预知页面在下一次访问前所需的等待时间,那我们可以选择在内存驻留时间很长的页面进行中置换
  • 最近最久未使用的置换算法(LRU) 选择最长时间没有被访问的页面进行置换
  • 时钟页面置换算法(Lock
  • 最不常用置换算法(LFU

26、磁盘调度算法

为了提高磁盘的访问性能,一般是通过优化磁盘的访问请求顺序来做到的。

  • 先来先服务算法   寻道时间过长。
  • 最短寻道时间优先算法   可能存在某些请求的饥饿  产生饥饿的原因是磁头在一小块区域来回移动
  • 扫描算法  磁头在一个方向上移动,访问所有未完成的请求,直到磁头到达该方向上的最后的磁道,才调换方向,这就是扫描(Scan)算法    扫描算法使得每个磁道响应的频率存在差异,
  • 循环扫描算法   只有磁头朝某个特定方向移动时,才处理磁道访问请求   磁道只响应一个方向上的请求
  • LOOK 与 C-LOOK 算法   针对扫描算法的优化  磁头在移动到「最远的请求」位置,然后立即反向移动   反向移动的途中会响应请求。        针对循环扫描算法的优化,磁头在每个方向上仅仅移动到最远的请求位置,然后立即反向移动  反向移动的途中不会响应请求

27、键盘敲入A字母时,操作系统期间发生什么

当用户输入了键盘字符,键盘控制器就会产生扫描码数据,并将其缓冲在键盘控制器的寄存器中,紧接着键盘控制器通过总线给 CPU 发送中断请求。CPU 收到中断请求后,操作系统会保存被中断进程的 CPU 上下文,然后调用键盘的中断处理程序。键盘中断处理函数的功能就是从键盘控制器的寄存器的缓冲区读取扫描码,再根据扫描码找到用户在键盘输入的字符  

得到了显示字符的 ASCII 码后,就会把 ASCII 码放到「读缓冲区队列」,接下来就是要把显示字符显示屏幕了,显示设备的驱动程序会定时从「读缓冲区队列」读取数据放到「写缓冲区队列」,最后把「写缓冲区队列」的数据一个一个写入到显示设备的控制器的寄存器中的数据缓冲区,最后将这些数据显示在屏幕里。

显示出结果后,恢复被中断进程的上下文

设备管理  每个设备都有设备控制器   CPU 是通过设备控制器来和设备打交道的。

I/O控制方式   设备控制器里有芯片,它可执行自己的逻辑,也有自己的寄存器,用来与 CPU 进行通信

数据寄存器 命令寄存器 状态寄存器

  • 当 CPU 给设备发送了一个指令,让设备控制器去读设备的数据,它读完的时候,要怎么通知 CPU 呢?

轮询等待  中断   DMA 

DMA   CPU 当要读取磁盘数据的时候,只需给 DMA 控制器发送指令,然后返回去做其他事情,当磁盘数据拷贝到内存后,DMA 控制机器通过中断的方式,告诉 CPU 数据已经准备好了,可以从内存读数据了。仅仅在传送开始和结束时需要 CPU 干预。

设备驱动程序会提供统一的接口给操作系统, 及时响应控制器发来的中断请求,并根据这个中断的类型调用响应的中断处理程序进行处理

28、文件系统

  • 文件系统是操作系统中负责管理持久数据的子系统
  • 索引节点  文件的唯一标识  用来记录文件的元信息  索引节点同样占用磁盘空间
  • 目录项  用来记录文件的名字、索引节点指针以及与其他目录项的层级关联关系  目录项是由内核维护的一个数据结构,不存放于磁盘,而是缓存在内存

多对一,也就是说,一个文件可以有多个别名 多个目录项可以对应一个索引节点

  • 目录也是文件  目录文件在磁盘里面保存子目录或文件。
  • 磁盘进行格式化的时候,会被分成三个存储区域,分别是超级块、索引节点区和数据块区。
  • 虚拟文件系统  用户层和文件系统层引入的中间层
  • 文件系统的基本操作单位是数据块

文件的存储

空闲空间管理   位图法

目录的存储  

为了减少 I/O 操作,把当前使用的文件目录缓存在内存,以后要使用该文件时只要在内存中操作

硬链接

多个目录项中的「索引节点」指向一个文件 硬链接是不可用于跨文件系统的  只有删除文件的所有硬链接以及源文件时,系统才会彻底删除该文件。

软链接   这个文件有独立的 inode,但是这个文件的内容是另外一个文件的路径目标文件被删除了,链接文件还是在的,只不过指向的文件找不到了而已。

文件读写方式

  • 缓冲与非缓冲I/O  根据「是否利用标准库缓冲」
  • 直接 I/O 与非直接 I/O  是否利用操作系统的缓存」
  • 阻塞与非阻塞I/O 

阻塞I/O 阻塞等待的是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程

非阻塞I/O 不断轮询内核 直到数据准备好  最后一次 read 调用,获取数据的过程,是一个同步的过程,是需要等待的过程。这里的同步指的是内核态的数据拷贝到用户程序的缓存区这个过程。

非阻塞I/O多路复用  用户可以再一个线程内是同时处理多个socket的IO请求  内核将数据从内核空间拷贝到应用程序空间,过程都是需要等待的

异步 I/O 是「内核数据准备好」和「数据从内核态拷贝到用户态」这两个过程都不用等待

进程写文件,进程发生崩溃,已写入的数据会丢失吗?

不会的 写数据是写到了page cache中  即使进程崩溃,文件依旧在page cache中。内核会找个时机,将page cache的数据持久化到磁盘。但是如果在这之前系统崩溃,那这部分的数据肯定就会丢失了。如果程序中调用sync函数,写文件的时候,立刻落盘,就可以解决系统崩溃导致的数据丢失问题

page cache

Page Cache 的本质是由 Linux 内核管理的内存区域,能够加快数据访问,减少I/O次数,提高系统磁盘I/O吞吐量

Page Cache 用于缓存文件的页数据,buffer cache 用于缓存块设备(如磁盘)的块数据。

Page Cache 与 buffer cache 的共同目的都是加速数据 I/O

操作系统为基于 Page Cache 的读缓存机制提供预读机制

一致性问题   调用sync   对所有的脏的文件数据元数据都刷新到磁盘中

29、网络系统

零拷贝

  • 传统I/O

  • DMA直接内存访问   在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务

内核缓冲区  读内存的速度还是比读磁盘的速度快很多的  PageCache的优点 缓存最近被访问的数据 +预读功能

  • 传统的文件传输

之所以要发生上下文切换,用户空间没有权限操作磁盘或网卡,内核的权限最高,这些操作设备的过程都需要交由操作系统内核来完成   发生了 4 次用户态与内核态的上下文切换  发生了 4 次数据拷贝

减少系统调用的次数,就可以减少上下文切换的次数

减少数据拷贝的次数,用户的缓冲区是没用存在的必要的

如何实现零拷贝? 

  • mmap + write  mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间

仍然需要通过 CPU 把内核缓冲区的数据拷贝到 socket 缓冲区里,而且仍然需要 4 次上下文切换,因为系统调用还是 2 次。 

  • sendfile   只有 2 次上下文切换,和 3 次数据拷贝

  • 零拷贝   只需要 2 次上下文切换和数据拷贝次数   而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

大文件传输

在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术。对小文件使用零拷贝。

I/O多路复用

tcp socket实现一对一的通信,使用的是同步阻塞的方式,当服务器还没有处理完一个客户端的I/O时,或者读写操作发生阻塞,其他客户端时无法与服务端连接的

 TCP 连接是由四元组唯一确认的,本机IP, 本机端口, 对端IP, 对端端口

tcp连接数收到文件描述符系统内存的限制

多进程模型

父进程所有相关的东西都复制一份,包括文件描述符、内存地址空间、程序计数器、执行的代码等

多线程模型

I/O多路复用  可以只在一个进程里处理多个文件的 I/O

select/poll  内核提供给用户态的多路复用系统调用  进程可以通过一个系统调用函数从内核中获取多个事件  都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合

epoll 使用红黑树来跟踪进程所有待检测的文件描述字  当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,

高性能网络模式  Reactor 和Proactor

Reactor模式   对事件反应,来了一个事件,Reactor就有相对的反应,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程

三种实现方案

单Reactor 单进程/线程  因为只有一个进程,无法充分利用 多核 CPU 的性能; 不适用计算机密集型的场景,只适用于业务处理非常快速的场景。  Redis

单Reactor 多进程/线程

多Reactor 多进程/线程  Netty  

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。
  • 主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端。

Practor 来了事件操作系统来处理,处理完再通知应用进程  采用异步 I/O 实现的异步网络模型,感知的是已完成的读写事件,而不需要像 Reactor 感知到事件后,还需要调用 read 来从内核中获取数据。事件 指的是有新连接、有数据可读、有数据可写的这些 I/O 事件

一致性哈希

负载均衡问题,把请求分发到不同的服务器进行处理   轮询这类的策略只能适用与每个节点的数据都是相同的场景

但是,对于分布式系统,每个节点存储的数据是不同的  一个分布式 KV(key-valu) 缓存系统,某个 key 应该到哪个或者哪些节点上获得,应该是确定的

哈希算法在扩容或者缩容时,必须迁移改变了映射关系的数据,否则会出现查询不到数据的问题。

一致哈希算法是对 2^32 进行取模运算,是一个固定的值

一致性哈希是指将「存储服务器」和「数据」都映射到一个首尾相连的哈希环上

映射的结果值往顺时针的方向的找到第一个节点

需要对指定 key 的值进行读写的时候,要通过下面 2 步进行寻址:

  • 首先,对 key 进行哈希计算,确定此 key 在环上的位置;
  • 然后,从这个位置沿着顺时针方向走,遇到的第一节点就是存储 key 的节点。

因此,在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响。 

一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的问题。

带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景,而且适合节点规模会发生变化的场景。

 

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值