网络编程进阶学习笔记--高并发IO的底层原理

前言

本笔记是我对尼恩高并发三部曲的学习笔记,定位为网络编程的进阶学习笔记,看这个笔记的人应当对netty有过基础的学习,没有任何网络里编程经验的同学建议看看我的nety基础系列的文章,在看本系列的文章学习起来可能回更加高效,链接奉上。本系列预计用两个月完成更新。
链接:https://www.yuque.com/u2196512/mgr9wm

高并发IO的底层原理

操作系统中的基础知识

用户态与内核态

系统调用将Linux整个体系分为用户态和内核态

image.png

image.png

为什么需要划分内核态于用户态

为了安全。
1.限制不同程序之间的访问能力,防止他们从别的数据获取数据,或者获取外围设备的数据,并发送到网络中,所以cpu划分出两个等级----用户态与内核态。
2.在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。这些操作是十分危险的,所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通的应用程序只能使用那些不会造成灾难的指令。

内核态与用户态的权限

用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。也就是说用户态具有的权限仅仅是访问本程序的内存

内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。

用户态与内核态的切换

所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等. 而唯一可以做这些事情的就是操作系统(所以我们需要使用操作系统所提供的接口来进行这些操作,我们调用操作系统提供的接口,由操作系统去执行,操作系统也是一个软件,不过他具有内核态的权限), 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作.
这时需要一个这样的机制: 用户态程序切换到内核态, 但是不能控制在内核态中执行的指令
这种机制叫系统调用, 在CPU中的实现称之为陷阱指令(Trap Instruction)
他们的工作流程如下:

  1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务.
  2. 用户态程序执行陷阱指令
  3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问
  4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数, 并执行程序请求的服务
  5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果

当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

内核态与用户态是操作系统的两种运行级别,跟intel cpu没有必然的联系, intel cpu提供Ring0-Ring3三种级别的运行模式,Ring0级别最高,Ring3最低。Linux使用了Ring3级别运行用户态,Ring0作为 内核态,没有使用Ring1和Ring2。Ring3状态不能访问Ring0的地址空间,包括代码和数据。Linux进程的4GB地址空间,3G-4G部 分大家是共享的,是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据。用户运行一个程序,该程序所创建的进程开始是运 行在用户态的,如果要执行文件操作,网络数据发送等操作,必须通过write,send等系统调用,这些系统调用会调用内核中的代码来完成操作,这时,必 须切换到Ring0,然后进入3GB-4GB中的内核地址空间去执行这些代码完成操作,完成后,切换回Ring3,回到用户态。这样,用户态的程序就不能 随意操作内核地址空间,具有一定的安全保护作用。
至于说保护模式,是说通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程的地址空间中的数据。

为什么用户态与内核态切换消耗资源

linux下每个进程的栈有两个,一个是用户态栈,一个是内核态栈。在需要从用户态栈切换到内核的时候,需要进行执行栈的转换,保存用户态的状态,包括寄存器状态,然后执行内核态操作,操作完成后要恢复现场,切换到用户态,这个过程是耗时的。当然这里有很多细节,但是我不懂,只了解宏观上的原因。

应用程序的读写操作是如何实现的

操作系统层面的read系统调用,并不是直接从物理设备把数据读取到应用的内存中; write系统调用,也不是直接把数据写入到物理设备。上层应用无论是调用操作系统的 read,还是调用操作系统的write,都会涉及缓冲区。具体来说,上层应用通过操作系统的 read系统调用,是把数据从内核缓冲区复制到应用程序的进程缓冲区;上层应用通过操作 系统的write系统调用,是把数据从应用程序的进程缓冲区复制到操作系统内核缓冲区。

简单来说,应用程序的IO操作,实际上不是物理设备级别的读写,而是缓存的复制。 read&write两大系统调用,都不负责数据在内核缓冲区和物理设备(如磁盘、网卡等)之 间的交换。这项底层的读写交换操作,是由操作系统内核(Kernel)来完成的。所以,应 用程序中的IO操作,无论是对Socket的IO操作,还是对文件的IO操作,都属于上层应用的 开发,它们的在输入(Input)和输出(Output)维度上的执行流程,都是类似的,都是在内核缓冲区和进程缓冲区之间的进行数据交换。

即一个IO过程包括两个步骤,用户空间的处理与内核空间的处理,我们调用系统的read函数,相当于是将用户态的数据复制到内核缓冲区中,而核心空间处理部分则是read系统调用在linux内核空间中处理的整个过程也即写入硬盘的过程。
下面从这两个角度进行分析

Read系统调用在用户空间的处理过程

Linux 系统调用(SCI,system call interface)的实现机制实际上是一个多路汇聚以及分解的过程,该汇聚点就是 0x80 中断这个入口点(X86 系统结构)。也就是说,所有系统调用都从用户空间中汇聚到 0x80 中断点,同时保存具体的系统调用号。当 0x80 中断处理程序运行时,将根据系统调用号对不同的系统调用分别处理(调用不同的内核函数处理)。

Read系统调用在核心空间的处理过程

0x80

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值