《操作系统》

Linux

文件

Linux中“一切皆文件”是什么意思?

说一下个人理解。

一切皆文件是某些操作系统对资源的抽象,把资源都抽象成“文件”这么一个对象,然后就可以对这个对象做同一种操作。统一了对它们的操作方法,使得Linux具有了很高的灵活性和可扩展性。

比如对于普通文本文件来讲,可以通过 open/read/write/close 来打开/读取/写入/关闭。

比如对于 Socket 对象来讲,也可以通过 open/read/write/close 来打开/读取/写入/关闭。

比如,ls 是显示目录或者文件基本信息的,但是把进程信息抽象成文件之后,我们就可以使用 ls /proc/xxx 查看进程信息了,把 cpu 的信息抽象成文件用 cat /proc/cpuinfo 就可以查看 cpu 信息了,当然这个只能读不能改变。

再比如对于一些硬件设备比如蓝牙,摄像头等,都能通过 open/read/write/close 来打开/读取/写入/关闭。

也就是说像 Linux 这种奉行“一切皆文件”思想的操作系统,可以对所有资源都使用同一套 api 接口。

下面的视频内容就讲到“蓝牙手柄就是个文件”,通过 /dev/input/event20 就可以获取手柄的输入:

Linux中“一切皆文件”是什么意思? - 知乎 (zhihu.com)

优点:

统一的文件操作接口

方便的文本处理和系统管理

方便的设备管理

安全性

为什么说:Linux中一切皆文件?-腾讯云开发者社区-腾讯云 (tencent.com)

缺点:

和 Windows 系统不同,Linux 系统没有盘。不利之处在于,使用任何硬件设备都必须与根目录下某一目录执行挂载操作,否则无法使用。

硬链接和软链接

硬链接创建的是文件内容的多个名称,而软链接创建的是指向另一个文件的路径。

使用 ln 命令可以方便地创建硬链接和软链接。对于硬链接,不需要任何特殊选项;而对于软链接,则需要加上 -s 选项。

进程管理

进程调度算法

什么时候会发生 CPU 调度

  1. 当进程从运行状态转到等待状态;
  2. 当进程从运行状态转到就绪状态;
  3. 当进程从等待状态转到就绪状态;
  4. 当进程从运行状态转到终止状态;

其中发生在 1 和 4 两种情况下的调度称为「非抢占式调度」,2 和 3 两种情况下发生的调度称为「抢占式调度」。

  • 非抢占式

    当进程正在运行时,它就会一直运行,直到该进程完成或发生某个事件而被阻塞时,才会把 CPU 让给其他进程。

  • 抢占式调度

    顾名思义就是进程正在运行的时,可以被打断,使其把 CPU 让给其他进程。那抢占的原则一般有三种,分别是时间片原则、优先权原则、短作业优先原则。

常见的调度算法

  • 先来先服务调度算法
  • 最短作业优先调度算法
  • 高响应比优先调度算法
  • 时间片轮转调度算法
  • 最高优先级调度算法
  • 多级反馈队列调度算法

Linux系统采用了完全公平调度算法(CFS)作为默认的进程调度算法。CFS的设计目标是为了确保系统中所有运行的进程都获得公平的CPU时间份额。它使用红黑树(一种自平衡二叉查找树)来维护所有可运行进程的排序,确保调度的公平性。CFS不关注进程的实时性,更注重任务的整体平均运行时间,意图让每个进程都能得到合理的CPU时间比例。

内存管理

一文带你了解,虚拟内存、内存分页、分段、段页式内存管理 - 知乎 (zhihu.com)

操作系统就用一张大表管理内存? (qq.com)

内存分段

程序是由若干个逻辑分段组成的,可由代码分段、数据分段、栈段、堆段组成。不同的段是有不同的属性的,所以就用分段(Segmentation)的形式把这些段分离出来。

分段的办法很好,解决了程序本身不需要关心具体的物理内存地址的问题,但它也有一些不足之处:

  • 第一个就是内存碎片的问题。
  • 第二个就是内存交换的效率低的问题。
内存碎片的问题

这里的内存碎片的问题共有两处地方:

  • 外部内存碎片,也就是产生了多个不连续的小物理内存,导致新的程序无法被装载;
  • 内部内存碎片,程序所有的内存都被装载到了物理内存,但是这个程序有部分的内存可能并不是很常使用,这也会导致内存的浪费;

解决外部内存碎片的问题就是内存交换

可以把音乐程序占用的那 256MB 内存写到硬盘上,然后再从硬盘上读回来到内存里。不过再读回的时候,我们不能装载回原来的位置,而是紧紧跟着那已经被占用了的 512MB 内存后面。这样就能空缺出连续的时间 256MB 空间,于是新的 200MB 程序就可以装载进来。

这个内存交换空间,在 Linux 系统里,也就是我们经常看到的 Swap 空间,这块空间是从硬盘划分出来的,用于内存与硬盘的空间交换

内存分页

让需要交换的写入或者从磁盘装载的数据更少一点,能少出现一些内存碎片。这个办法,也就是内存分页Paging)。

页、块、页表

将虚拟地址空间以512Byte ~ 8K,作为一个单位,称为,并从0开始依次对每一个页编号。在 Linux 下,每一页的大小为 4KB。

将物理地址按照同样的大小,作为一个单位,称为或者,也从0开始依次对每一个框编号。

操作系统通过维护一张表,这张表上记录了每一对页和框的映射关系,这张表,称为页表

页表实际上存储在 CPU 的内存管理单元MMU) 中,于是 CPU 就可以直接通过 MMU,找出要实际要访问的物理内存地址。

访问

当CPU要访问一个虚拟地址空间对应的物理内存地址时,先将具体的虚拟地址A/页面大小4K,结果的商作为页表号,结果的余作为业内地址偏移。

内存页面置换算法

  • 缺页中断

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

  • 页面置换算法

    • 最佳页面置换算法(OPT
    • 先进先出置换算法(FIFO
    • 最近最久未使用的置换算法(LRU
    • 时钟页面置换算法(Lock
    • 最不常用置换算法(LFU

    Linux内核现在使用的页面置换算法是两级的软件LRU,也就是分为active和inactive类型的两个链表,并实现软件LRU(NFU)算法

    让我们一起聊聊如何改进 LRU 算法-改进leach算法 (51cto.com)

其他

  • Out of memory: Kill process

    Linux有一个特性:OOM Killer,一个保护机制,用于避免在内存不足的时候不至于出现严重问题,把一些无关的进程优先杀掉,即在内存严重不足时,系统为了继续运转,内核会挑选一个进程,将其杀掉,以释放内存,缓解内存不足情况,不过这种保护是有限的,不能完全的保护进程的运行。

    [689379.844719] Out of memory: Kill process 421 (java) score 1949 or sacrifice child [689379.846596] Killed process 421 (java) total-vm:513937072kB, anon-rss:1299716kB, file-rss:30739736kB

  • dmesg

    dmesg命令显示linux内核的环形缓冲区信息,我们可以从中获得诸如系统架构、cpu、挂载的硬件,RAM等多个运行级别的大量的系统信息。

    如上面的Out of memory: Kill process就是这里发现的

  • sawp

    开启/禁用:swapon和swapoff

  • 虚拟内存和 swap 分区

磁盘

  • 磁盘寻道算法

    • 先来先服务算法
    • 最短寻道时间优先算法
    • 扫描算法
    • 循环扫描算法
    • LOOK 与 C-LOOK 算法
  • 磁盘预读

    预读的长度一般为页(page)的整倍数。页是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。

设备

  • 设备管理

内核态与用户态

操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源。

操作硬盘等资源属于敏感操作,为了内核安全,用户线程不能直接调用。而是采用了操作系统内核提供了系统调用接口,用户线程通过系统调用来实现文件读写。所以直接与硬盘打交道的是操作系统内核。

操作系统将内存按1:3的比例分为了内核空间和用户空间,用户态的运行栈信息保存在用户空间中,内核态的运行栈信息保存在内核空间中。运行栈中保存了当前线程的运行信息,比如执行到了哪些方法,局部变量等。

当发生用户态和内核态之间的切换的时候,运行栈的信息发生了变化,对应的CPU中的寄存器信息也要发生变换。但是用户线程完成系统调用的时候,还是要切换回用户态,继续执行代码的。所以要将发生系统调用之前的用户栈的信息保存起来,也就是将寄存器中的数据保存到线程所属的某块内存区域。这就涉及到了数据的拷贝,同时用户态切换到内核态还需要安全验证等操作。所以用户态和内核态之间的切换是十分耗费资源的。

系统监控

sysstat性能监控工具

sysstat提供了Linux性能监控工具集,包括sar、sadf、mpstat、iostat、pidstat、vmstat等,用于监控Linux系统性能和使用情况。

系统负载

计算密集型与IO密集型

计算密集型与IO密集型这个概念,在面试的时候可能会被问到,主要是在多线程环境中,如何设置线程数,让CPU充分利用,跑出最高效率。

计算密集型:这一类主要是在线程中,按照数学公式,大量求和、求平均、求平方等等操作,这样的任务,大部分需要依赖CPU的计算能力来处理,我们设置线程数一般是:计算机核数n+1。

IO密集型:这一类任务,大部分操作耗时在网络传输、磁盘读写上面,而CPU并未跑满,这类应用比如web服务器,不管是同步阻塞,还是异步非阻塞,他对CPU的利用非常低,耗时操作在IO。为了合理利用CPU,设置线程数一般是:计算机核数n*2。

在设置线程数上,一个是n+1,一个是2n。

Command

Linux命令教程 (yiibai.com)

nohup

nohup 命令运行由 Command参数和任何相关的 Arg参数指定的命令,忽略所有挂断(SIGHUP)信号。在注销后使用 nohup 命令运行后台中的程序。要运行后台中的 nohup 命令,添加 & ( 表示“and”的符号)到命令的尾部。

nohup 是 no hang up 的缩写,就是不挂断的意思。

案例:

  1. nohup command > myout.file 2>&1 &

    在上面的例子中,0 – stdin (standard input),1 – stdout (standard output),2 – stderr (standard error) ;

    2>&1是将标准错误(2)重定向到标准输出(&1),标准输出(&1)再被重定向输入到myout.file文件中。

  2. 0 22 * * * /usr/bin/python /home/pu/download_pdf/download_dfcf_pdf_to_oss.py > /home/pu/download_pdf/download_dfcf_pdf_to_oss.log 2>&1

    这是放在crontab中的定时任务,晚上22点时候怕这个任务,启动这个python的脚本,并把日志写在download_dfcf_pdf_to_oss.log文件中

nohup和&的区别

& : 指在后台运行

nohup : 不挂断的运行,注意并没有后台运行的功能,就是指,用nohup运行命令可以使命令永久的执行下去,和用户终端没有关系,例如我们断开SSH连接都不会影响他的运行,注意了nohup没有后台运行的意思;&才是后台运行

文件目录操作

ls、cd、pwd、mkdir、touch、mv、rm、rmdir、cp

cat、less、tail、head

打包解压

tar、gzip

文件权限

chmod

性能监控

top、free、vmstat、istat

网络

ifconfig、netstat、ss

其他

grep、wc

多路复用

具体来说,多路复用可以实现以下几个方面的功能:

  1. 单个进程或线程能够同时处理多个 I/O 操作,而不必为每个 I/O 操作创建一个新的线程,从而节省系统资源。
  2. 通过多路复用,可以实现非阻塞 I/O,即当没有数据到达时,进程不会被阻塞,而是可以继续处理其他事务。

在实际编程中,常见的多路复用机制包括 select、poll、epoll(在 Linux 中)。这些机制允许程序监视多个文件描述符,并在一个或多个文件描述符准备好进行 I/O 操作时得到通知,从而实现高效的 I/O 处理。

同步阻塞概念

同步、异步:

  • 概念:消息的通知机制
  • 解释:涉及到 IO 通知机制;所谓同步,就是发起调用后,被调用者处理消息,必须等处理完才直接返回结果,没处理完之前是不返回的,调用者主动等待结果;所谓异步,就是发起调用后,被调用者直接返回,但是并没有返回结果,等处理完消息后,通过状态、通知或者回调函数来通知调用者,调用者被动接收结果。

阻塞、非阻塞:

  • 概念:程序等待调用结果时的状态
  • 解释:涉及到 CPU 线程调度;所谓阻塞,就是调用结果返回之前,该执行线程会被挂起,不释放 CPU 执行权,线程不能做其它事情,只能等待,只有等到调用结果返回了,才能接着往下执行;所谓非阻塞,就是在没有获取调用结果时,不是一直等待,线程可以往下执行,如果是同步的,通过轮询的方式检查有没有调用结果返回,如果是异步的,会通知回调。

I/O 模型

  • 阻塞式 I/O
  • 非阻塞式 I/O
  • I/O 复用
  • 信号驱动 I/O
  • 异步 I/O
  • 五大 I/O 模型比较

参考链接:

  1. IO 复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞 区别
  2. 网络 IO 中的同步、异步、阻塞和非阻塞
  3. 迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO 讲的最清楚的好文章
  4. 《Netty Zookeeper Redis 高并发实战》2.2 节
  5. 9.3 高性能网络模式:Reactor 和 Proactor | 小林coding (xiaolincoding.com)

Socket

在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:

  • 一个是「还没完全建立」连接的队列,称为 TCP 半连接队列,这个队列都是没有完成三次握手的连接,此时服务端处于 syn_rcvd 的状态;
  • 一个是「已经建立」连接的队列,称为 TCP 全连接队列,这个队列都是完成了三次握手的连接,此时服务端处于 established 状态;

当 TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。

注意,监听的 Socket 和真正用来传数据的 Socket 是两个:

  • 一个叫作监听 Socket
  • 一个叫作已连接 Socket

select/poll/epoll

9.2 I/O 多路复用:select/poll/epoll | 小林coding (xiaolincoding.com)

epoll 使用示例 - 知乎 (zhihu.com)

linux内核Epoll 实现原理 - jame_xhs's blog (jxhs.me)

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

Select的缺陷

  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,fd越多开销则越大;

  • 每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

  • select支持的文件描述符数量有限,默认是1024。参见/usr/include/linux/posix_types.h中的定义:

    # define __FD_SETSIZE 1024

epoll 通过两个方面,很好解决了 select/poll 的问题。

第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。而 select/poll 内核里没有类似 epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以 select/poll 每次操作时都传入整个 socket 集合给内核,而 epoll 因为在内核维护了红黑树,可以保存所有待检测的 socket ,所以只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

epoll的工作机制更为复杂,我们就解释一下,它是如何解决Select机制的三大缺陷的。

  1. 对于第一个缺点,epoll的解决方案是:它的fd是共享在用户态和内核态之间的,所以可以不必进行从用户态到内核态的一个拷贝,大大节约系统资源。至于如何做到用户态和内核态,大家可以查一下“mmap”,它是一种内存映射的方法。
  2. 对于第二个缺点,epoll的解决方案不像select或poll一样每次都把当前线程轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把当前线程挂一遍(这一遍必不可少),并为每个fd指定一个回调函数。当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表。那么当我们调用epoll_wait时,epoll_wait只需要检查链表中是否有存在就绪的fd即可,效率非常可观
  3. 对于第三个缺点,fd数量的限制,也只有Select存在,Poll和Epoll都不存在。由于Epoll机制中只关心就绪的fd,它相较于Poll需要关心所有fd,在连接较多的场景下,效率更高。在1GB内存的机器上大约是10万左右,一般来说这个数目和系统内存关系很大。

epoll 边缘触发和水平触发

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;
  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

Reactor 模式

Netty的架构模式是在此基础上演变而来的

个人认为 netty 对用户来说是异步,但是实际底层 IO 是 IO 多路复用模型,本质上还是一种同步非阻塞(是的,个人认为 IO 多路复用模型还是同步非阻塞,并且真正的 IO 操作都将阻塞应用线程),他只是多了一个 Selector(需要底层操作系统支持),如此一个线程就可以控制大量的通信(相比传统 IO,不管他是不是非阻塞)。

真正的 IO 操作都将阻塞应用线程

因为在 read 调用时,内核将数据从内核空间拷贝到用户空间的过程都是需要等待的,也就是说这个过程是同步的,如果内核实现的拷贝效率不高,read 调用就会在这个同步过程中等待比较长的时间。

IO 操作的真正耗时

我们开始以为 write 操作是要等到对方收到消息才会返回,但实际上不是这样的。write 操作只负责将数据写到本地操作系统内核的发送缓冲然后就返回了。剩下的事交给操作系统内核异步将数据送到目标机器。但是如果发送缓冲满了,那么就需要等待缓冲空出空闲空间来,这个就是写操作 IO 操作的真正耗时。

我们开始以为 read 操作是从目标机器拉取数据,但实际上不是这样的。read 操作只负责将数据从本地操作系统内核的接收缓冲中取出来就了事了。但是如果缓冲是空的,那么就需要等待数据到来,这个就是读操作 IO 操作的真正耗时。

这里可以配合《Netty、Redis、Zookeeper 高并发实战》2.2 节四种主要的 IO 模型来看一下。

Proactor 模式

Boost.Asio用的是Proactor模式(看C++/boost/asio)。

Proactor/Reactor模式也是否相像,二者都靠消息来驱动,都有回调函数,Proactor中,系统为你做了更多,告诉你结果,Reactor中,只是告诉你有事情发生了,可以做点什么了。

需要说明的是,并不是所有场合非阻塞异步方式的性能都最高,其实活还是那么多,系统帮你多做了些而已。如果只有少数几个连接,多线程+同步方式也许更适合。

惊群效应

多个进程或者线程在等待同一个事件,当事件发生时,所有进程或者线程都会被内核唤醒。然后,通常只有一个进程获得了该事件,并进行处理;其他进程在发现获取事件失败后,又继续进入了等待状态。这在一定程度上降低了系统性能。

具体来说,惊群通常发生在服务器的监听等待调用上。服务器创建监听socket,然后fork多个进程,在每个进程中调用accept或者epoll_wait等待终端的连接。

在高并发(多线程/多进程/多连接)中,会产生惊群的情况有:

  • accept惊群
  • epoll惊群
  • nginx惊群
  • 线程池惊群

“惊群效应"是什么?高并发中的几种惊群效应简介 - 知乎 (zhihu.com)

什么是惊群效应 - 脉脉 (maimai.cn)

零拷贝

  • Java 中的零拷贝

    这篇文章耐心看完,他讲的是真透彻,他从概念上区分了广义和狭义零拷贝,讲解了系统底层层面上的,JDK NIO 层面上的,Kafka、Netty 层面上的。

  • 零拷贝 敖丙

DMA 技术

DMA 是一种允许外围设备(硬件子系统)直接访问系统主内存的机制。也就是说,基于 DMA 访问方式,系统主内存于硬盘或网卡之间的数据传输可以绕开 CPU 的调度。

参考:DMA 技术是什么,在哪里用?看完绝对有收获 - 简书 (jianshu.com)

Linux 支持的 (常见) 零拷贝

mmap 内存映射,sendfile(linux 2.1 支持),Sendfile With DMA Scatter/Gather Copy(可以看作是 sendfile 的增强版,批量 sendfile),splice(linux 2.6.17 支持)。

Linux 零拷贝机制对比:无论是传统 IO 方式,还是引入零拷贝之后,2 次 DMA copy 是都少不了的。因为两次 DMA 都是依赖硬件完成的。

PageCache

磁盘高速缓存

主要是两个优点:缓存最近被访问的数据,预读功能

但是,在传输大文件(GB 级别的文件)的时候,PageCache 会不起作用,那就白白浪费 DRM 多做的一次数据拷贝,造成性能的降低,即使使用了 PageCache 的零拷贝也会损失性能

大文件传输

「异步 I/O + 直接 I/O」来替代零拷贝技术

直接 I/O

Liunx 提供了对这种需求的支持,即在 open() 系统调用中增加参数选项 O_DIRECT, 用它打开的文件便可以绕过内核缓冲区的直接访问,这样便有效避免了 CPU 和内存的多余时间开销。

Java本身并不支持直接IO。

Java NIO

  • Java NIO 引入了用于通道的缓冲区的 ByteBuffer。 ByteBuffer 有三个主要的实现:

    HeapByteBuffer,DirectByteBuffer,MappedByteBuffer

Netty 中的零拷贝

Netty 中的 Zero-copy 与上面我们所提到到 OS 层面上的 Zero-copy 不太一样, Netty 的 Zero-copy 完全是在用户态 (Java 层面) 的,它的 Zero-copy 的更多的是偏向于优化数据操作这样的概念。

  • Netty 提供了 CompositeByteBuf 类,它可以将多个 ByteBuf 合并为一个逻辑上的 ByteBuf,避免了各个 ByteBuf 之间的拷贝。
  • 通过 wrap 操作,我们可以将 byte[] 数组、ByteBuf、 ByteBuffer 等包装成一个 Netty ByteBuf 对象,进而避免了拷贝操作。
  • ByteBuf 支持 slice 操作,因此可以将 ByteBuf 分解为多个共享同一个存储区域的 ByteBuf,避免了内存的拷贝。
  • 通过 FileRegion 包装的 FileChannel.tranferTo 实现文件传输,可以直接将文件缓冲区的数据发送到目标 Channel,避免了传统通过循环 write 方式导致的内存拷贝问题。

前三个都是 广义零拷贝,都是减少不必要数据 copy;偏向于应用层数据优化的操作。

Windows

  • hiberfil.sys 和 pagefile.sys 占用系统空间,其分别是休眠空间和虚拟内存。

  • 模拟 linux 环境

    • wsl2(Windows Subsystem for Linux,Windows下的Linux子系统)
    • cygwin64
  • 虚拟机
    • VMware
    • VirtualBox

Linux 常用服务搭建

(Nginx,Shadowsocks,Ngrok...)

必备软件

  • 系统镜像
  • everything
  • wox(window 快速搜索文件启动程序软件)
  • HTTP 接口测试工具
    • Postman

Copyright © Ariescat all right reserved,powered by Gitbook最后修改时间: 2024-06-30 16:08

results matching ""

    No results matching ""