计算机内核态、用户态和零拷贝技术
存储介质的性能
话不多说,先看一张图,下图左边是磁盘到内存的不同介质,右边形象地描述了每种介质的读写速率。一句话总结就是越靠近cpu,读写性能越快。了解了不同硬件介质的读写速率后,你会发现零拷贝技术是多么的香,对于追求极致性能的读写系统而言,掌握这个技术是多么的优秀~
上图是当前主流存储介质的读写性能,从磁盘到内存、内存到缓存、缓存到寄存器,每上一个台阶,性能就提升10倍。如果我们打开一个文件去读里面的内容,你会发现时间读取的时间是远大于磁盘提供的这个时延的,这是为什么呢?问题就在内核态和用户态这2个概念后面深藏的I/O逻辑作怪。
内核态和用户态
内核态:也称为内核空间。cpu可以访问内存的所有数据,还控制着外围设备的访问,例如硬盘、网卡、鼠标、键盘等。cpu也可以将自己从一个程序切换到另一个程序。
用户态:也称为用户空间。只能受限的访问内存地址,cpu资源可以被其他程序获取。
计算机资源的管控范围
坦白地说内核态就是一个高级管理员,它可以控制整个资源的权限,用户态就是一个业务,每个人都可以使用它。那计算机为啥要这么分呢?且看下文......
由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络。CPU划分出两个权限等级:用户态和内核态。
32 位操作系统和 64 位操作系统的虚拟地址空间大小是不同的,在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,如下所示:
通过这里可以看出:
32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间; 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。
从用户态到内核态切换可以通过三种方式: 系统调用,其实系统调用本身就是中断,但是软件中断,跟硬中断不同。 异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,就会触发切换。例如:缺页异常。 外设中断:当外设完成用户的请求时,会向CPU发送中断信号。
内核态和用户态是怎么控制数据传输的
计算机A的进程a先要通过系统调用Read(内核态)打开一个磁盘上的文件,这个时候就要把数据copy一次到内核态的PageCache中,进入了内核态; 进程a负责将数据从内核空间的 Page Cache 搬运到用户空间的缓冲区,进入用户态; 进程a负责将数据从用户空间的缓冲区搬运到内核空间的 Socket(资源由内核管控) 缓冲区中,进入内核态。 进程a负责将数据从内核空间的 Socket 缓冲区搬运到的网络中,进入用户态;
read 系统调用读磁盘上的文件时:用户态切换到内核态; read 系统调用完毕:内核态切换回用户态; write 系统调用写到socket时:用户态切换到内核态; write 系统调用完毕:内核态切换回用户态。
CPU 先发出读指令给磁盘控制器(发出一个系统调用),然后返回; 磁盘控制器接受到指令,开始准备数据,把数据拷贝到磁盘控制器的内部缓冲区中,然后产生一个中断; CPU 收到中断信号后,让出CPU资源,把磁盘控制器的缓冲区的数据一次一个字节地拷贝进自己的寄存器,然后再把寄存器里的数据拷贝到内存,而在数据传输的期间 CPU 是无法执行其他任务的。
用户进程a调用系统调用read 方法,向OS内核(资源总管)发出 I/O 请求,请求读取数据到自己的内存缓冲区中,进程进入阻塞状态; OS内核收到请求后,进一步将 I/O 请求发送 DMA,然后让 CPU 执行其他任务; DMA 再将 I/O 请求发送给磁盘控制器; 磁盘控制器收到 DMA 的 I/O 请求,把数据从磁盘拷贝到磁盘控制器的缓冲区中,当磁盘控制器的缓冲区被写满后,它向 DMA 发起中断信号,告知自己缓冲区已满; DMA 收到磁盘的中断信号后,将磁盘控制器缓冲区中的数据拷贝到内核缓冲区中,此时不占用 CPU,CPU 可以执行其他任务; 当 DMA 读取了一个固定buffer的数据,就会发送中断信号给 CPU; CPU 收到 DMA 的信号,知道数据已经Ready,于是将数据从内核拷贝到用户空间,结束系统调用;
零拷贝技术实现的方式
mmap + write(系统调用) sendfile
mmap + write
应用进程调用了 mmap() 后,DMA 会把磁盘的数据拷贝到内核的缓冲区里。因为建立了这个内存的mapping,所以用户态的数据可以直接访问了; 应用进程再调用 write(),CPU将内核缓冲区的数据拷贝到 socket 缓冲区中,这一切都发生在内核态 DMA把内核的 socket 缓冲区里的数据,拷贝到网卡的缓冲区里
sendfile
前2个参数分别是目的端和源端的文件描述符, 后2个参数是源端的偏移量和复制数据的长度,返回值是实际复制数据的长度。
通过 DMA 将磁盘上的数据拷贝到内核缓冲区里; 缓冲区描述符和数据长度传到 socket 缓冲区,这样网卡的 SG-DMA 控制器就可以直接将内核缓存中的数据拷贝到网卡的缓冲区里;
为啥要聊PageCache
数据存在的时间超过了 dirty_expire_centisecs (默认300厘秒,即30秒) 时间; 脏数据所占内存 > dirty_background_ratio,也就是说当脏数据所占用的内存占总内存的比例超过dirty_background_ratio(默认10,即系统内存的10%)的时候会触发pdflush刷新脏数据。
加快对数据的访问 减少磁盘I/O的访问次数,提高系统磁盘寿命 减少对磁盘I/O的访问,提高系统磁盘I/O吞吐量(Page Cache的预读机制)
使用额外的物理内存空间,当物理内存比较紧俏的时候,可能会导致频繁的swap操作,最终会导致系统的磁盘I/O负载上升。 Page Cache没有给应用层提供一个很好的API。导致应用层想要优化Page Cache的使用策略很难。因此一些应用实现了自己的Page管理,比如MySQL的InnoDB存储引擎以16KB的页进行管理。
PageCache 由于长时间被大文件的部分块占据,而导致一些「热点」的小文件可能就无法常驻 PageCache,导致频繁读写磁盘而引起性能下降; PageCache 中的大文件数据,由于没有全部常驻内存,只有部分无法享受到缓存带来的好处,同时过多的DMA 拷贝动作,增加了时延;
vm.dirty_background_ratio 参数优化:当 cached 中缓存当数据占总内存的比例达到这个参数设定的值时将触发刷磁盘操作。把这个参数适当调小,这样可以把原来一个大的 IO 刷盘操作变为多个小的 IO 刷盘操作,从而把 IO 写峰值削平。对于内存很大和磁盘性能比较差的服务器,应该把这个值设置的小一点。 vm.dirty_ratio 参数优化:对于写压力特别大的,建议把这个参数适当调大;对于写压力小的可以适当调小;如果cached的数据所占比例(这里是占总内存的比例)超过这个设置,系统会停止所有的应用层的IO写操作,等待刷完数据后恢复IO。所以万一触发了系统的这个操作,对于用户来说影响非常大的。 vm.dirty_expire_centisecs参数优化:这个参数会和参数vm.dirty_background_ratio 一起来作用,一个表示大小比例,一个表示时间;即满足其中任何一个的条件都达到刷盘的条件。 vm.dirty_writeback_centisecs 参数优化:理论上调小这个参数,可以提高刷磁盘的频率,从而尽快把脏数据刷新到磁盘上。但一定要保证间隔时间内一定可以让数据刷盘完成。 vm.swappiness 参数优化:禁用 swap 空间,设置 vm.swappiness=0
大文件传输怎么做
当调用 read 方法时,切到内核态访问磁盘资源。此时内核会向磁盘发起 I/O 请求,磁盘收到请求后,准备数据。数据读取到控制器缓冲区完成后,就会向内核发起 I/O 中断,通知内核磁盘数据已经准备好; 内核收到 I/O 中断后,将数据从磁盘控制器缓冲区拷贝到 PageCache 里; 内核把 PageCache 中的数据拷贝到用户缓冲区,read 调用返回成功。
内核向磁盘发起读请求,因为是异步请求可以不等待数据就位就可以返回,于是CPU释放出来可以处理其他任务; 当内核将磁盘中的数据拷贝到进程缓冲区后,进程将接收到内核的通知,再去处理数据;
应用程序已经实现了磁盘数据的缓存 大文件传输
或者获取全店资料打包,后续免费获取全店所有新增和更新。
转载申明:转载本号文章请注明作者和来源,本号发布文章若存在版权等问题,请留言联系处理,谢谢。
更多架构相关技术知识总结请参考“架构师全店铺技术资料打包”相关电子书(37本技术资料打包汇总详情可通过“阅读原文”获取)。
全店内容持续更新,现下单“架构师技术全店资料打包汇总(全)”,后续可享全店内容更新“免费”赠阅,价格仅收198元(原总价350元)。
温馨提示:
扫描二维码关注公众号,点击阅读原文链接获取“IT技术全店资料打包汇总(全)”电子书资料详情。
微信扫码关注该文公众号作者