[译]C++ 协程:理解 co_await 运算符

原文地址:C++ Coroutines: Understanding operator co_await 原文作者:lewissbaker 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:7Ethan 校对者:razertory,noahziheng C++ 协程:理解 co_await 运算符 在之前关于 协程理论的博客 中,我介绍了一些函数和协程在较高层次上的一些不同,但没有详细介绍 C++ 协程技术规范(N4680)中描述的语法和语义。 协程技术规范中,C++ 新增的关键新功能是能够挂起协程,并能够在之后恢复。技术规范为此提供的机制是通过新的 co_await 运算符去实现。 理解 co_await 运算符的工作原理可以帮助我们揭开协程行为的神秘面纱,并了解它们如何被暂停和挂起的。在这篇文章中,我将解释 co_await 操作符的机制,并介绍 Awaitable 和 Awaiter 类型的相关概念。 在深入讲解 co_await 之前,我想简要介绍一下协程的技术规范,以提供一些背景知识。 协程技术规范给我们提供了什么? 三个新的关键字: co_await, co_yield 和 co_return std::experimental 命名空间的几个新类型: coroutine_handle<P> coroutine_traits<Ts…> suspend_always suspend_never 一种能够让库的作者与协程交互并定制它们行为的通用机制。 一个使异步代码变得更加简单的语言工具! C++ 协程技术规范在语言中提供的工具,可以理解为协程的低级汇编语言。 这些工具很难直接以安全的方式使用,主要是供库作者使用,用于构建应用程序开发人员可以安全使用的更高级别的抽象。 未来会将这些新的低级工具交付给即将到来的语言标准(可能是 C++20),以及标准库中伴随的一些高级类型,这些高级类型封装了这些低级构建块,应用程序开发人员将可以通过一种安全的方式轻松访问协程。 编译器与库的交互… Continue reading [译]C++ 协程:理解 co_await 运算符

协程的原理(Coroutine Theory)

原文链接:https://lewissbaker.github.io/2017/09/25/coroutine-theory This is the first of a series of posts on the C++ Coroutines TS, a new language feature that is currently on track for inclusion into the C++20 language standard. 这是C++ Cooutines TS系列文章中的第一篇,这是一种新的语言特性,目前正准备纳入C++20语言标准。 In this series I will cover how the underlying mechanics of C++ Coroutines work as well as show how they can… Continue reading 协程的原理(Coroutine Theory)

Linux 高性能 获取时间

基础 常用时间函数 从The Linux Programming Interface 上看,获取时间最常用的函数是 gettimeofday,常见的中间件也是用该函数么? 1 2 3 #include <sys/time.h> int gettimeofday(struct timeval *tv, struct timezone *tz); Returns 0 on success, or –1 on error Redis 扫了下redis的命令,发现redis有个time command. 调用该command能够返回当前服务器的时间戳,从redis源码 )上看,redis也是调用了gettimeofday获取当前的时间 1 2 3 4 5 6 7 8 9 10 void timeCommand(client *c) { struct timeval tv; gettimeofday(&tv,NULL); addReplyArrayLen(c,2); addReplyBulkLongLong(c,tv.tv_sec); addReplyBulkLongLong(c,tv.tv_usec); }… Continue reading Linux 高性能 获取时间

认认真真地聊聊中断

本来想写内核如何接收一个网络包这个过程,但发现把整个过程捋顺了,还是很难的。 推导整个过程的起点是中断,包括硬中断和软中断。   而这个过程要是讲清楚吧,感觉在整个网络包接收原理的大流程中有点喧宾夺主。但要是一笔带过吧,那对于这块有困惑的人就很难受,一切的起点没整明白在心里总是个疙瘩。   所以,单拎出来一个主题中断,给大家把这个问题搞明白了,之后讲内核收包时直接引过来就好了,不想看的直接过,想看的就可以有篇前置文章详细了解,美滋滋。   另外,整个操作系统就是一个中断驱动的死循环,操作系统原理如果用一行代码解释,下面这样再合适不过了。 while(true) { doNothing();} 其他所有事情都是由操作系统提前注册的中断机制和其对应的中断处理函数完成,我们点击一下鼠标,敲击一下键盘,执行一个程序,都是用中断的方式来通知操作系统帮我们处理这些事件,当没有任何需要操作系统处理的事件时,它就乖乖停在死循环里不出来。   所以,中断非常重要,它也是理解整个操作系统的根基。掌握它,不亏!   那我们开始吧。 1. 五花八门的中断分类 关于中断的分类,教科书上和网上有很多”标准”答案了,如果你用搜索引擎去寻找答案,可能会找出很多不一样的分类结果。   所以我打算直接在 Intel 手册上找个最官方的标准答案。   在 Intel 手册 “Volume 1 Chapter 6.4 Interrupts and Exception” 给出。 翻译过来就是,中断可以分为中断和异常,异常又可以分为故障、陷阱、中止。   第一句话有点奇怪,啥叫中断分为中断和异常呢?   你看好多文章的时候也是这么写的,不知道你有没有曾疑惑过。但其实原文的意思准确说是,CPU 提供了两种中断程序执行的机制,中断和异常。第一个中断是个动词,第二个中断才是真正的机制种类。   好吧,我感觉原文也挺奇怪的,但人家就这么叫,没辙。接下来我只需要翻译一下就好了,再夹杂点自己的解读。   An interrupt is an asynchronous event that is typically triggered by an… Continue reading 认认真真地聊聊中断

深入理解零拷贝技术

hi,大家好,内存拷贝是比较耗时操作,零拷贝是常用优化手段,今天分享的文章就是Linux系统零拷贝技术,Kafka和MySQL开源组件都用到这个核心技术,希望大家可以掌握。 DMA 与零拷贝技术 注意事项:除了 Direct I/O,与磁盘相关的文件读写操作都有使用到 page cache 技术。 作者:Spongecaptain 原文:https://spongecaptain.cool/SimpleClearFileIO/ 1. 数据的四次拷贝与四次上下文切换 很多应用程序在面临客户端请求时,可以等价为进行如下的系统调用: File.read(file, buf, len); Socket.send(socket, buf, len); 例如消息中间件 Kafka 就是这个应用场景,从磁盘中读取一批消息后原封不动地写入网卡(NIC,Network interface controller)进行发送。 在没有任何优化技术使用的背景下,操作系统为此会进行 4 次数据拷贝,以及 4 次上下文切换,如下图所示: 如果没有优化,读取磁盘数据,再通过网卡传输的场景性能比较差: 4 次 copy: CPU 负责将数据从磁盘搬运到内核空间的 Page Cache 中; CPU 负责将数据从内核空间的 Socket 缓冲区搬运到的网络中; CPU 负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区; CPU 负责将数据从用户空间的缓冲区搬运到内核空间的 Socket 缓冲区中; 4 次上下文切换: read… Continue reading 深入理解零拷贝技术

从创建进程到进入main函数,发生了什么?

前几天,读者群里有小伙伴提问:从进程创建后,到底是怎么进入我写的main函数的? 今天这篇文章就来聊聊这个话题。 首先先划定一下这个问题的讨论范围:C/C++语言 这篇文章主要讨论的是操作系统层面上对于进程、线程的创建初始化等行为,而像Python、Java等基于解释器、虚拟机的语言,如何进入到main函数执行,这背后的路径则更长(包含了解释器和虚拟机内部的执行流程),以后有机会再讨论。所以这里就重点关注C/C++这类native语言的main函数是如何进入的。 本文会兼顾叙述Linux和Windows两个主要平台上的详细流程。 创建进程 第一步,创建进程。 在Linux上,我们要启动一个新的进程,一般通过fork + exec系列函数来实现,前者将当前进程“分叉”出一个孪生子进程,后者负责替换这个子进程的执行文件,来执行子进程的新程序文件。 这里的fork、exec系列函数,是操作系统提供给应用程序的API函数,在其内部最终都会通过系统调用,进入操作系统内核,通过内核中的进程管理机制,来完成一个进程的创建。 操作系统内核将负责进程的创建,主要有下面几个工作要做: 创建内核中用于描述进程的数据结构,在Linux上是task_struct 创建新进程的页目录、页表,用于构建新进程的内存地址空间 在Linux内核中,由于历史原因,Linux内核早期并没有线程的概念,而是用任务:task_struct来描述一个程序的执行实例:进程。 在内核中,一个任务对应就是一个task_struct,也就是一个进程,内核的调度单元也是一个个的个task_struct。 后来,多线程的概念兴起,Linux内核为了支持多线程技术,task_struct实际上表示的变成了一个线程,通过将多个task_struct合并为一组(通过该结构内部的组id字段)再来描述一个进程。因此,Linux上的线程,也称为轻量级进程。 系统调用fork的一个重要使命就是要去创建新进程的task_struct结构,创建完成后,进程就拥有了调度单元。随后将开始可以参与调度并有机会获得执行。 加载可执行文件 通过fork成功创建进程后,此时的子进程和父进程相当于一个细胞进行了有丝分裂,两个进程“几乎”是一模一样的。 而要想子进程执行新的程序,在子进程中还需要用到exec系列函数来实现对进程可执行程序的替换。 exec系列函数同样是系统调用的封装,通过调用它们,将进入内核sys_execve来执行真正的工作。 这个工作细节比较多,其中有一个重要的工作就是加载可执行文件到进程空间并对其进行分析,提取出可执行文件的入口地址。 我们使用C、C++等高级语言编写的代码,最终通过编译器会编译生成可执行文件,在Linux上,是ELF格式,在Windows上,称之为PE文件。 无论是ELF文件还是PE文件,在各自的文件头中,都记录了这个可执行文件的指令入口地址,它指示了程序该从哪里开始执行。 这个入口指向哪里,是我们的main函数吗?这里卖一个关子,先来解决在这之前的一个问题:进程创建后,是如何来到这个入口地址的? 不管在Windows还是Linux上,应用线程都会经常在用户空间和内核空间来回穿梭,这可能出现在以下几种情况发生时: 系统调用 中断 异常 从内核返回时,线程是如何知道自己从哪里进来的,该回到应用空间的哪里去继续执行呢? 答案是,在进入内核空间时,线程将自动保存上下文(其实就是一些寄存器的内容,比如指令寄存器EIP)到线程的堆栈上,记录自己从哪里来的,等到从内核返回时,再从堆栈上加载这些信息,回到原来的地方继续执行。 前面提到,子进程是通过sys_execve系统调用进入到内核中的,在后面完成可执行文件的分析后,拿到了ELF文件的入口地址,将会去修改原来保存在堆栈上的上下文信息,将EIP指向ELF文件的入口地址。这样等sys_execve系统调用结束时,返回到用户空间后,就能够直接转到新的程序入口开始执行代码。 所以,一个非常重要的特点是:exec系列函数正常情况下是不会返回的,一旦进入,完成使命后,执行流程就会转向新的可执行文件入口。 另外需要提一下的是,在Linux上,除了ELF文件,还支持一些其他格式的可执行文件,如MS-DOS、COFF 除了二进制的可执行文件,还支持shell脚本,这个情况下将会将脚本解释器程序作为入口来启动 从ELF入口到main函数 上面交代了,一个新的进程,是如何执行到可执行文件的入口地址的。 同时也留了一个问题,这个入口地址是什么?是我们的main函数吗? 这里有一个简单的C程序,运行起来后输出经典的hello world: #include <stdio.h> int main() {     printf(“hello, world!\n”);     return 0; } 通过gcc编译后,生成了一个ELF可执行文件,通过readelf指令,可以实现对ELF文件的分析,这里可以看到ELF文件的入口地址是0x400430: 随后,我们通过反汇编神器,IDA打开分析这个文件,看一下位于0x400430入口的地方是什么函数? 可以看到,入口地方是一个叫做 _start 的函数,并不是我们的main函数。 在_start的结尾,调用了 __libc_start_main 函数,而这个函数,位于libc.so中。 你可能疑惑,这个函数是哪里冒出来的,我们的代码中并没有用到它呢?… Continue reading 从创建进程到进入main函数,发生了什么?

例用lsof命令恢复linux误删除文件(正在使用的文件)

首先我们要知道lsof这个命令是做什么用的,简单的讲就是列出当前打开的程序,进程或者用户所使用的所有文件(是正在使用的)   在这之前我们要搞清楚一个理论知识:那就是句柄(这里我用缓存来理解的,这样更容易理解)及空间释放问题   打个比方:在生产环境中,有维护人员正在用 tail 命令查看一个日志,然后另一个维护人员用rm -rf 命令把这个日志给删除了。但是这时其实你的空间还没有真正的释放,因为为还有用户占用着这个文件,所以在缓存中这个文件还是存在的。那么我们就可以通这个缓存把文件给恢复回来。下面我们开始实验   1.首先我们用下面的命令查看我们的 less /var/log/messages 然后再按我们键盘上的 ctrl + z 先暂停(注意,这时候我们的 less 命令占用着这个文件)   less /var/log/messages 复制代码 2.然后我们用 rm -rf /var/log/messages 删除这个文件   rm -rf /var/log/messages 复制代码 3.你可以 ls 看下我们的这个文件已经被删除了,这时候不要惊慌,我们现在来恢复,首先,我们要使用 lsof 命令找到 less 这个程序占用着哪些文件   lsof | grep deleted   #过滤出已经被删除的所有文件(这里被删除的意思是程序在占用且被删除),在最后一列找到我们需要恢复的文件 复制代码 4.刚上面的命令的结果如下: $ lsof | grep deleted #过滤出less这个程序所占用的所有文件,在最后一列找到我们需要恢复的文件 rsyslogd   903    root    4w… Continue reading 例用lsof命令恢复linux误删除文件(正在使用的文件)

进程/线程切换究竟需要多少开销?

进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销。那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进程切换节约很多CPU时间吗?带着这些疑问,让我们进入正题。 1 进程以及进程切换 进程是操作系统的伟大发明之一,对应用程序屏蔽了CPU调度、内存管理等硬件细节,而抽象出一个进程的概念,让应用程序专心于实现自己的业务逻辑既可,而且在有限的CPU上可以“同时”进行许多个任务。但是它为用户带来方便的同时,也引入了一些额外的开销。如下图,在进程运行中间的时间里,虽然CPU也在忙于干活,但是却没有完成任何的用户工作,这就是进程机制带来的额外开销。 在进程A切换到进程B的过程中,先保存A进程的上下文,以便于等A恢复运行的时候,能够知道A进程的下一条指令是啥。然后将要运行的B进程的上下文恢复到寄存器中。这个过程被称为上下文切换。上下文切换开销在进程不多、切换不频繁的应用场景下问题不大。但是现在Linux操作系统被用到了高并发的网络程序后端服务器。在单机支持成千上万个用户请求的时候,这个开销就得拿出来说道说道了。因为用户进程在请求Redis、Mysql数据等网络IO阻塞掉的时候,或者在进程时间片到了,都会引发上下文切换。 2 一次简单的进程切换开销测试 废话不多说,我们先用个实验测试一下,到底一次上下文切换需要多长的CPU时间!实验方法是创建两个进程并在它们之间传送一个令牌。其中一个进程在读取令牌时就会引起阻塞。另一个进程发送令牌后等待其返回时也处于阻塞状态。如此往返传送一定的次数,然后统计他们的平均单次切换时间开销。编译、运行 # gcc main.c -o main # ./main./main Before Context Switch Time1565352257 s, 774767 us After Context SWitch Time1565352257 s, 842852 us 每次执行的时间会有差异,多次运行后平均每次上下文切换耗时3.5us左右。当然了这个数字因机器而异,而且建议在实机上测试。 前文我们测试系统调用的时候,最低值是200ns。可见,上下文切换开销要比系统调用的开销要大。系统调用只是在进程内将用户态切换到内核态,然后再切回来,而上下文切换可是直接从进程A切换到了进程B。显然这个上下文切换需要完成的工作量更大。 3 进程切换开销分析 那么上下文切换的时候,CPU的开销都具体有哪些呢?开销分成两种,一种是直接开销、一种是间接开销。 直接开销就是在切换时,cpu必须做的事情,包括: 1、切换页表全局目录 2、切换内核态堆栈 3、切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文) ip(instruction pointer):指向当前执行指令的下一条指令 bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址 sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址 cr3:页目录基址寄存器,保存页目录表的物理地址 …… 4、刷新TLB 5、系统调度器的代码执行   间接开销主要指的是虽然切换到一个新进程后,由于各种缓存并不热,速度运行会慢一些。如果进程始终都在一个CPU上调度还好一些,如果跨CPU的话,之前热起来的TLB、L1、L2、L3因为运行的进程已经变了,所以以局部性原理cache起来的代码、数据也都没有用了,导致新进程穿透到内存的IO会变多。其实我们上面的实验并没有很好地测量到这种情况,所以实际的上下文切换开销可能比3.5us要大。 想了解更详细操作过程的同学请参考《深入理解Linux内核》中的第三章和第九章。 4 一个更为专业的测试工具-lmbench… Continue reading 进程/线程切换究竟需要多少开销?

图解Linux网络包接收过程

因为要对百万、千万、甚至是过亿的用户提供各种网络服务,所以在一线互联网企业里面试和晋升后端开发同学的其中一个重点要求就是要能支撑高并发,要理解性能开销,会进行性能优化。而很多时候,如果你对Linux底层的理解不深的话,遇到很多线上性能瓶颈你会觉得狗拿刺猬,无从下手。 我们今天用图解的方式,来深度理解一下在Linux下网络包的接收过程。还是按照惯例来借用一段最简单的代码开始思考。为了简单起见,我们用udp来举例,如下: int main(){    int serverSocketFd = socket(AF_INET, SOCK_DGRAM, 0);    bind(serverSocketFd, …);    char buff[BUFFSIZE];    int readCount = recvfrom(serverSocketFd, buff, BUFFSIZE, 0, …);    buff[readCount] = ‘\0’;    printf(“Receive from client:%s\n”, buff); } 上面代码是一段udp server接收收据的逻辑。当在开发视角看的时候,只要客户端有对应的数据发送过来,服务器端执行recv_from后就能收到它,并把它打印出来。我们现在想知道的是,当网络包达到网卡,直到我们的recvfrom收到数据,这中间,究竟都发生过什么? 通过本文,你将深入理解Linux网络系统内部是如何实现的,以及各个部分之间如何交互。相信这对你的工作将会有非常大的帮助。本文基于Linux 3.10,源代码参见https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,网卡驱动采用Intel的igb网卡举例。 友情提示,本文略长,可以先Mark后看! 一 Linux网络收包总览 在TCP/IP网络分层模型里,整个协议栈被分成了物理层、链路层、网络层,传输层和应用层。物理层对应的是网卡和网线,应用层对应的是我们常见的Nginx,FTP等等各种应用。Linux实现的是链路层、网络层和传输层这三层。 在Linux内核实现中,链路层协议靠网卡驱动来实现,内核协议栈来实现网络层和传输层。内核对更上层的应用层提供socket接口来供用户进程访问。我们用Linux的视角来看到的TCP/IP网络分层模型应该是下面这个样子的。 图1 Linux视角的网络协议栈 在Linux的源代码中,网络设备驱动对应的逻辑位于driver/net/ethernet, 其中intel系列网卡的驱动在driver/net/ethernet/intel目录下。协议栈模块代码位于kernel和net目录。 内核和网络设备驱动是通过中断的方式来处理的。当设备上有数据到达的时候,会给CPU的相关引脚上触发一个电压变化,以通知CPU来处理数据。对于网络模块来说,由于处理过程比较复杂和耗时,如果在中断函数中完成所有的处理,将会导致中断处理函数(优先级过高)将过度占据CPU,将导致CPU无法响应其它设备,例如鼠标和键盘的消息。因此Linux中断处理函数是分上半部和下半部的。上半部是只进行最简单的工作,快速处理然后释放CPU,接着CPU就可以允许其它中断进来。剩下将绝大部分的工作都放到下半部中,可以慢慢从容处理。2.4以后的内核版本采用的下半部实现方式是软中断,由ksoftirqd内核线程全权处理。和硬中断不同的是,硬中断是通过给CPU物理引脚施加电压变化,而软中断是通过给内存中的一个变量的二进制值以通知软中断处理程序。 好了,大概了解了网卡驱动、硬中断、软中断和ksoftirqd线程之后,我们在这几个概念的基础上给出一个内核收包的路径示意: 图2 Linux内核网络收包总览 当网卡上收到数据以后,Linux中第一个工作的模块是网络驱动。网络驱动会以DMA的方式把网卡上收到的帧写到内存里。再向CPU发起一个中断,以通知CPU有数据到达。第二,当CPU收到中断请求后,会去调用网络驱动注册的中断处理函数。网卡的中断处理函数并不做过多工作,发出软中断请求,然后尽快释放CPU。ksoftirqd检测到有软中断请求到达,调用poll开始轮询收包,收到后交由各级协议栈处理。对于UDP包来说,会被放到用户socket的接收队列中。 我们从上面这张图中已经从整体上把握到了Linux对数据包的处理过程。但是要想了解更多网络模块工作的细节,我们还得往下看。… Continue reading 图解Linux网络包接收过程

浅谈Linux内存管理那些事儿

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 白日梦蓝 刺猬乐队 – 白日梦蓝 1 前言 内存管理是Linux内核中非常重要的部分,今天和大家一起学习一下。 当我们要学习一个新知识点时,比较好的过程是先理解出现这个技术点的 背景原因,同期其他解决方案,新技术点解决了什么问题以及它存在哪些不足和改进之处,这样整个学习过程是 闭环 的,个人觉得这是个很好的学习思路。 凡事都是相通的,计算机学科的一些问题在现实生活中都可以找到原型,所以我觉得计算机科学家大部分都是善于观察生活并总结归纳的。人类社会就是一台复杂的机器,其中充满了机制和规则,所以有时候跳进代码海洋不如先回到生活之中,寻找原型再探究代码,可能理解会更深刻。 linux内存管理卷帙浩繁,本文只能层层递进地带你领略冰山轮廓,通过本文你将了解到以下内容: 为什么需要管理内存 linux段页管理机制 内存碎片的产生机理 伙伴系统的基本原理 伙伴系统的优势和不足 slab分配器的基本原理 2 为什么需要管理内存 老子的著名观点是无为而治,简单说就是不过多干预而充分依靠自觉就可以有条不紊地运作,理想是美好的,现实是残酷的。 在linux系统中如果以一种原始简单的方式管理内存是存在一些问题的,我们来看几个场景。 2.1 内存管理的问题 进程空间隔离问题 假如现在有ABC三个进程运行在linux的内存空间,设定os给进程A分配的地址空间是0-20M 进程B地址空间30-80M,进程C地址空间90-120M,如图:   在某些时候程序空间的访问可能出现问题,比如进程A访问了属于进程B的空间,进程B访问了属于进程C的空间,甚至修改了空间的值,这样就会造成混乱和错误,所以实际中是不允许这种情况发生的。   内存效率和内存不足问题 机器的内存是有限资源,而进程数量是无法确定的,如果在某些时候已经启动的进程占据了所有内存空间,此时就无法启动新进程了,因为没有新内存可分配了,但是我们观察到已经启动的进程有时候是在睡大觉,也就是给了内存也不用,这样效率确实是有点低,所以我们需要一个管理员把不用的内存倒腾出来,另外连续内存实在是很珍贵,很多时候我们没法有效及时地分配连续内存,因此虚拟化和离散化可能会有效提高内存的使用率。   程序定位调试和编译运行问题 由于程序运行时的位置时不确定的,我们在定位问题、调试代码、编译执行时都会存在很多问题,我们希望每个进程有一致且完整的地址空间,同样的起始位置放置了堆、栈以及代码段等,从而简化编译和执行过程中的 linker 链接器、loader 加载器的使用。 2.2 虚拟地址空间 为了解决上述的一些问题,linux系统引入了虚拟空间的概念,虚拟化的出现和硬件有密不可分的联系,可以说是软硬件组合的结果,虚拟地址空间就是在程序和物理空间所增加的中间层,这也是内存管理的重点。   磁盘 disk 作为一种大容量的存储也作为”内存”的一部分参与程序的运行,内存管理系统会将不常用非活跃内存进行页面换出,可以认为内存是磁盘的缓存,内存中保留了活跃的数据,从而间接扩展了有限的物理内存空间,这部分空间称为虚拟内存是相对于物理内存而言的。 3.段页管理机制 本文并不深入地将分段管理内存和分页管理内存,因为将这些细节的优秀文章很多,感兴趣的使用搜索引擎一键即达。 段页机制也不是一蹴而就的,经历了单纯物理分段、单纯分页、单纯逻辑分段等阶段,最终演进出来了分段和分页结合的内存管理方式,段页结合获得了分段和分页的优势也避免了单一模式的弊端,是一种比较好的管理模式。 本文对于段页管理机制只想通俗地说明一些概念,段页管理机制是分段式管理和分页式管理的组合,段式管理是逻辑上的管理方式,分页管理是偏物理上的管理方式。 计算机里面的一些技术和实现都可以在现实生活中找到缩影,所谓艺术和科技源自生活大概就是这个意思吧。 举个栗子:在进行居民户籍管理时都会有区县市的概念,但是实际上并没有这种实体,都是逻辑上的,增加了这些行政单位之后可以让地址管理更加直接。 对于我们居民来说唯一的实体就是自己的房子住所,这是物理上的单位,是真实存在的,这也是最基本的单位。 对比linux段页时管理来说,段是逻辑上的单位相当于区县市的概念,页是物理上的单位相当于小区/房屋的概念,这样就方便很多。 多级页表也很好理解,总的物理内存假如有4GB,页大小为4KB,那么就总共有2^20个页,数量还是非常大的,这样编号来建立索引寻址比较不方便,所以引入多级页表,来减少存储便于管理。 段页机制加持下的逻辑地址和物理地址的映射关系简图,也就是虚拟地址到物理地址的对应关系:… Continue reading 浅谈Linux内存管理那些事儿