底层原理

内核空间&用户空间

OS所管理的内存是虚拟内存,虚拟内存到物理内存之间采用的是段页式地址映射模型(X86)。

以32位Linux为例(32位指的是地址总线的位数),它的寻址空间为232=4G,也就是它最大可以访问到的内存空间。

OS将4G内存空间分为内核空间和用户空间:

  • 从0x00000000 ~ 0xBFFFFFFF 为用户空间

  • 从0xC0000000 ~ 0xFFFFFFFF 为内核空间

内核空间:供内核使用,用户程序无法使用。

用户空间:供应用程序使用。

OS为每个进程分配4G内存。

用户程序可以通过系统调用使用内核空间,因此内核空间被所有用户进程共享。

为什么要区分内核空间和用户空间?

  1. 早期的OS并不区分内核空间和用户空间
  2. 应用程序能访问全部内存
  3. 程序Bug 把OS搞崩溃
  4. 应用程序随便访问内存太危险
  5. 按照CPU指令的对指令重要程度分4个等级ring0、ring1、ring2、ring3(Intel)
  6. Linux/Windows 只用 ring0 & ring3
  7. 运行的指令在ring3 的进程叫用户态,只能访问用户空间
  8. 运行的指令在ring0 的进程叫内核态,能访问全部空间

内核态&用户态

内核态:进程/线程->运行在内核空间时->处于内核态

在内核态下,进程运行在内核空间,此时可以让执行所有CPU指令,可以访问全部内存,也可以访问IO端口。

内核态:进程/线程->运行在用户空间时->处于用户态

在用户态下,进程运行在用户空间,执行的指令需要检查,如:只能访问映射在其地址空间的页表项中规定在用户态下可访问页的虚拟地址。

能执行部分指令,能访问部分内存,不能访问IO端口。

Linux 结构图:

应用程序是如何访问磁盘,读取网卡的数据的?

Java创建线程的过程:new Thread(Runnable ..).start(),Java会调用 JVM 的 native 方法,JVM 的 native 方法会调用 pthread_create 系统函数来创建线程。此时进程从用户态切换到内核态完成系统资源的分配,完成线程的创建。

除了系统调用可以实现用户态到内核态,还有哪些方式?

软中断和硬中断。

软中断指进程发生了异常事件。

硬中断就有很多种,例如时钟周期(晶振)、IO(DMA)等。

内核缓冲区&用户缓冲区

什么是缓冲区?干嘛用的?

  1. 计算机的网卡、硬盘、内存、缓存、CPU的速度差距很大
  2. 外部IO设备的直接读写涉及系统中断
  3. 系统中断前,需要保存中断前的进程数据和状态等信息
  4. 结束中断后,恢复之前的进程数据和状态等信息
  5. 为了减少底层系统频繁中断导致的时间损耗、性能损耗,产生了内核缓冲区
  6. OS对内核缓冲区进行监控,等待缓冲区到达一定数量的时候,再进行IO设备的中断处理,集中执行物理设备的实际IO操作,通过这种机制来提升系统的性能
  7. 至于什么时候执行系统中断(读中断、写中断)由内核来负责,应用程序不care

应用程序的IO操作实际上不是对物理设备进行读写,而是缓冲区区的拷贝。

应用程序调用系统函数read和write函数,而read和write两个系统函数都不负责数据在内核缓冲区和IO设备(磁盘、网卡等)之间的交换。

真正的读写操作是由OS内核完成。

所以应用程序(Java、C/C++)对 socket、文件 的 IO 操作都属于上层应用的开发。

本质上都是在内核缓冲区和进程缓冲区进行数据的拷贝。

内核缓冲区的数量:在Linux中,内核只有一个内核缓冲区

用户/进程缓冲区:每个用户进程都有自己的独立缓冲区

read & write 系统调用流程图:

以 read 系统调用为例:

  1. 等待内核准备好数据
  2. 内核从内核缓冲区向用户缓冲区拷贝数据

如果读一个 socket:

  1. 数据通过网络(有线、无线)到达网卡
  2. 当等待的分组到达时,数据被拷贝到内核缓冲区(DMA)
  3. 内核将数据从内核缓冲区拷贝到用户缓冲区

Java客户端和服务器进行一次请求和响应:

  1. 客户端发送请求:客户端通过 write 系统调用将数据从用户缓冲区拷贝到内核缓冲区;内核将内核缓冲区的数据通过客户端主机的网卡发送到服务端主机
  2. 服务端接收响应:服务端收到客户端网络报文通过DMA拷贝到内核缓冲区;再通过CPU拷贝到 JVM heap
  3. 服务端业务处理:服务端在用户空间中完成业务逻辑
  4. 服务端返回数据:服务端业务处理完成后,构建好响应数据调用 write 系统函数从 JVM heap 拷贝 kernel buffer
  5. 服务端发送数据:内核将 kernel buffer 的数据拷贝到网络协议栈,通过网卡底层通信协议发送到客户端主机