1. GPIO 2. 定时器 3. 串行通信 4. 中断功能GPIO 单片机仅靠CPU和内存是无法运行的! 有效使用单片机不可或缺的“外设功能”是什么呢? 对电子产品进行控制的单片机是由CPU、内存及外设功能等部分组成的(图1)。CPU根据指令(程序),执行运算、数据的读写以及进行条件判断等,而内存则用来保存该程序(记忆)。 外设功能是指为了使单片机便于使用的各种功能。例如,CPU为了与外部的传感器及开关等进行信号交换,就需要“输入/输出端口(I/O端口)”这种外设功能。 而且,将模拟输入信号转换为数字值的“A/D转换器”以及反过来将数字值转换为模拟输出信号的“D/A转换器”则是单片机对各种信号进行处理时不可或缺的外设功能。 另外,还有为了正确测量时间所用的“定时器”以及提供日期和时计的“实时时钟(RTC)”,用于进行与时间相关的处理,此外还有将并行信号(parallel signal)和串行信号(serial signal)进行互相交换的“UART(Universal Asynchronous Receiver Transmitter,通用异步收发器)”等,以便进行通信。 图1:单片机内部结构示意图 了解数字信号的输入/输出端口---“GPIO” 在“输入/输出端口(I/O端口)”中,数字信号的输入/输出端口即“GPIO(General Purpose Input/Output)”也被称为“通用I/O端口”,是一种用于数字信号输入/输出的非常方便的端口。用于将数字输出的传感器值和开关的ON/OFF值传送到单片机的输入端及通过LED来显示单片机的运算结果,以及输出用于驱动电机运行的信号等等。 GPIO被称为通用端口是其引脚既可以用于输入也可以用于输出。在早期的单片机中,引脚都被固定用于输入或输出,但是现在很多单片机中都可以自由地将其设定为输入或输出端口。假设GPIO端子有8个引脚,则可以将4个引脚用于输入,另4个引脚用于输出,也可以将1个引脚用于输入,剩下的7个引脚用于输出。 在GPIO中,为了使CPU和外部设备之间进行数据交换,要相互执行通过程序处理的数字值(0或1)与信号(电压的LOW电平或HIGH电平)的转换。下面是作为RX63N单片机的GPIO端口基础的寄存器(※1)的作用(图2)。 (※1)寄存器(Register):存在于单片机的CPU和外设功能内部中的记忆回路。用于运算和保持CPU的执行状态。由于是作为CPU及外设功能的内部回路,所以在对内存进行写入和读取时速度很快,但容量却非常小,既有可以用于各种用途的寄存器(通用寄存器),又有用于某些限定的功能和用途的特殊寄存器。 图2:GPIO的基本结构示意图 端口方向寄存器(PDR) 决定引脚方向的寄存器,也称为“方向寄存器”。 端口输入数据寄存器(PIDR) 输入时反映所使用的引脚状态的寄存器。从引脚输入LOW电平或者HIGH电平时会将之转换为0或1的值并读取该转换结果。随着引脚的变化数值也将发生变化。所以不会保持读取时的值。 端口输出数据寄存器(PODR) 此寄存器保存用作输出引脚的输出数据。将0或1的值转换为LOW电平或HIGH电平信号并从引脚输出。由于可以与内存一样保持改写前的值,所以在改写前来自引脚的输出电压也将保持不变。 定时器 一手包办有关时间和时刻的处理! 在单片机中,不仅频繁地使用“○月○日○点○分”这种时刻显示,显示过去的时间和一定的周期这种形式也被频繁地使用。例如,“该程序从运行开始过去了多少时间?”、“每秒输送128次信号”等等。另外,还经常被用于“等待指定的时间”、“经过指定的时间后将转移到下一个处理”这样的情况。对这些与时间和时刻有关进行处理的外设功能就是定时器(图1)。 图1:定时器就是进行与时间、时刻有关的处理我们也可以不使用外设功能(硬件)的定时器,而是通过软件来计算时间。下面通过图2来说明使用软件来定时的示例,图中假设循环(重复)部分的处理需要费时1μs(微秒:100万分之1秒)。由此可以计算出该循环部分重复1000次需要花费1ms(毫秒:千分之1秒),重复100万次则需费时1秒。即:通过“等待经过循环处理所指定的时间”来计算时间。但是,CPU将会集中进行时间计算的处理而无法进行其他处理。而且,只能计算一个周期的时间。而现实当中,单片机需要对应0.1秒和1/1024秒等各种周期的时间。另外,CPU的计时器频率(驱动速度)也将对软件产生影响。如果将100MHz驱动的CPU改为50MHz,那么循环1次所需的时间将变为原来的两倍。因此,需要对计算时间的软件进行修正。如上所述,由于在管理上既花时间又容易出错,所以要极力避免通过软件来计算时间。图2:通过软件定时的定时器示例 稍微介绍一下中断的内容…… 下面,我们简单介绍一下和定时器不可分割的技术——“中断功能”。单片机中的“中断功能”是指某个程序在执行过程中,因某种原因而发出“开始进行其他处理”的请求。由于可以使用中断功能,所以可使CPU不集中进行一个处理。让我们想象一下日常生活中用到“中断功能”的情景,当我们把热水倒入方便面盒中后,如果我们在3分钟内一直盯着时钟看,那么这段时间内我们就不能做其它事情。但如果用厨房定时器设定3分钟的时间,在厨房定时器的警报响起之前我们便可以去做其它事情。在这个例子中,“一直盯着时钟看”就相当于前一节中所介绍的“等待经过循环处理所指定的时间”,所以在处理结束之前不能去做其它事情。同时,厨房定时器的警报就相当于中断功能。在中断发生前还可以去做其它事情。单片机的外设功能中有各种各样的定时器,这些定时器在经过指定的时间或处理结束时向CPU发送中断信号。不仅是定时器,很多外设功能都会在“产生变化”、“处理开始/结束”时将中断信息传送给CPU。所以,CPU在中断功能发生前还可以继续做其它工作,因此可提高作业效率。关于中断功能的详细内容,将在本文后面的《外部中断功能IRQ》中做详细介绍。我们先事先了解一下“从外设功能以中断的形式向CPU传送信息”的内容。 各式各样的定时器中,还有“看门狗定时器”! 在单片机的外设功能中,最贴心的定时器是计算到指定时间的定时器和每隔一段时间便发生中断的定时器。在定时器中,最具特色的是WDT(看门狗定时器)。其名字Watch Dog Time中的Watch dog意思为“看门狗”,它的工作就是监视程序是否出现失控。由WDT监视的程序通过事先将设定的值写入WDT后启动。WDT每隔一定时间便减掉写入的值,当程序正常运行时,处理结束前会对WDT清零再结束。但是,如果程序失控(进入意料之外的重复状态且无法停止)时,写入WDT的值将小于0(称为下溢),因此向CPU通知程序出现了失控。在不允许睡眠的重要系统中,单片机中搭载的“看门狗定时器”―WDT发挥了极其重要的作用。 串行通信 单片机与外围设备的连接:并行和串行 单片机是嵌入式设备的“头脑”,其与作为嵌入式设备的“手和脚”是各种外围设备(输入输出设备等)连接。单片机应该怎样与这些外围设备连接才好呢?比如,我们来考虑一下将传感器与单片机连接的情况。如果使用前面介绍的“GPIO”的话,从传感器向单片机传送8位信号时需要使用8个引脚。这种传送模式被称为并行(并行通信)模式(图1―左)。但是,仅一个传感器就需要连接8个引脚,确实太可惜了 ,是否还有连接更少的引脚就能达到同样目的的方法呢? 此时,可使用串行传送模式(串行通信)。“串行”的意思就是直列或直线。通过并行传送模式(Parallerl Transferring Mode)中需要8个引脚才能实现的通信,在串行传送模式(Serial Transfer Mode)中,由于可以以排成一直线的模式进行传送,所以仅需一个引脚就够了(图1―右)。由于在单片机内部是通过并行模式来进行信号交换,所以还需将通过串行模式传送来的信号转换为并行模式(串行并行转换)。相反,从单片机向与单片机串行连接的外围设备传送的信号也需要将信号从并行模式转换为串行模式(并行串行转换)。瑞萨电子的单片机RX63N是通过被称为SCI(Serial Communication Interface,串行通信接口)的单元进行这些转换的。例如,使一个引脚对应1位 的char型变量进行信息交换的是并行通信(Parallel communication),而一个引脚以时分(time division)按每1位进行信息交换的就是串行通信(Serial Communication)。 图1:并行和串行由于串行连接仅使用少数引脚便可进行,所以,近年来多被用于单片机和外围设备之间的连接。GPIO除了用于将驱动电机的信号及 LED闪烁等软件操作结果的信号输出时以外,还被用于通过开关或ON/OFF输出的传感器的输入等。 通过UART便可简单地使用串行通信 由于电特性的不同,以及用于进行通信协议的规定不同,串行通信具有多种方式,其中,最易于使用的应该是“异步通信模式”了。仅需用信号线将单片机和外围设备连接起来便可使用,所以在单片机与动作监视器用的终端之间进行通信时、以及单片机与无线LAN用模块进行通信时使用。 在异步通信模式的串行通信状态下,一字节的文字信息※1在“开始位”(Start bit,意味着开始发送)和“停止位”(Stop bit,意味着停止发送)之间发送(图2)。由此,无需 I2C(Inter-Integrated Circuit,内部集成电路)”及“SPI(Serial Peripheral Interface,串行外设接口)”等时钟信号线(但在其他的串行通信模式中这些时钟信号线是必需的,以对发送和接收的时序进行同步)。另外,还可追加用于检查数据是否已正常发送的“奇偶校验位(Parity bit)※2”。 此通信方式所使用的通信用器件被称为UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器),在瑞萨电子的单片机--RX63N中内置了支持此功能的SCI(串行通信接口)。 (※1)异步串行通信状态下,一般都是从“最低有效位(LSB: Least Significant Bit),即最低二进制数位”开始进行发送的。 (※2)奇偶校验位(Parity bit):在发送时按一定量的数据(在SCI中为7位或8位)中所具有“1”(或“0”)的个数为奇数个时定为“1”,为偶数个时定为“0”的奇偶校验位(使偶校验(Even Parity Check)、数据及奇偶校验位中所包括的“1”的数量成为偶数个的方法),并通过与接收侧进行校验,以检测出数据通信中的错误。反之,如果“1”(或“0”)的个数为奇数时定为“0”,为偶数时定为“1”的方法被称为奇校验(Odd Parity Check)。图2:异步串行通信在异步通信中,能实现按“单片机→外围设备”或“外围设备→单片机”的方向确保数据信号专用的信号线时,被称为全双工通信。另一方面,将通过1根信号线来切换通信方向的方式称为半双工通信。全双工方式时需要2根信号线,可同时进行发送和接收。半双工方式时仅需1根信号线,但必须在发送和接收之间进行切换(图3)。图3:全双工通信和半双工通信 中断功能 提高作业效率的“中断功能”指的是什么? 我们回顾一下“定时器”篇中简单介绍过的“中断功能”概念。任何人都有过这样的经验,就是“将鸡蛋放进沸腾的热水中,直到鸡蛋煮熟的10分钟内要确认好几次时钟”的经历。在单片机的世界中也同样,在等待某种状态达成时,具有对对象进行定期检查的方法。例如,在等待向GPIO(通用I/O端口)的输入从0变为1时,程序可以一定的间隔来检查GPIO的状态。这种处理被称为“轮询”。 轮询虽然是一种了解状态变化的简单方法,但是如果检查的频度低(间隔长)就会错过变化,如果频度过高(间隔短),即使查也查不到变化“空耗”。由于轮询通过简单的程序便能完成处理,所以在掌握对象的变化频度时是有效的。但是,进行多次检查也会给单片机带来负荷,对功耗不利。 因此就要用到“中断功能”。产生中断时,CPU会暂时停止正在执行的任务,转而进行别的任务。也就是有别的任务“穿插”进来的意思(图1) 。当中途穿插进来的任务结束后,CPU再返回处理原来的任务。 图1:中断与轮询设想一下你在工作的同时煮鸡蛋的情况。由于你不想停下手中的工作,所以把鸡蛋放入热水中后就设置定时器并继续工作,10分钟后定时器一响就把鸡蛋从热水中捞起。这时,定时器的鸣叫就是中断 ,而“把鸡蛋从热水中捞起”就是穿插进来的工作。大家可以通过这种方式来了解中断功能。 单片机中的中断处理 中断产生于单片机内部和外部的各种设备。于开关和感应器等单片机外部的中断称为外部引脚中断,来自这些机器的中断信号由名为“IRQ”的引脚接收,再向中断控制器(在RX63N中称被称为“ICUb”)发出通知。IRQ为“Interrupt ReQuest”的略称,意思为“中断请求”。另外,来自单 片机内部的定时器和GPIO、串行通信设备UART等外设机器的中断被称为外部设备中断,中断信号直接从各外部设备通知中断控制器。 在中断控制器中,各种设备的中断信号按照先来后到的顺序,以适当的顺序被传送到CPU。而且,中断被设为无效的设备的中断信号将不会被传送到CPU,也就意味着可以忽视(屏蔽)这些信号。CPU按照从中断控制器接收到的指示来执行对应的程序(中断处理)。 CPU一旦接收到中断控制器的中断信号,首先将终止执行中的程序。然而,会自动保存“从何处重启”的出栈(POP)信息,这被称为“进栈(PUSH)”。进栈结束后,将开始由中断执行的程序。该程序结束时,进栈信息将回 送到CPU,这种现象被称为“出栈”(图2)。由于进栈和出栈都由CPU自动执行,因此程序设计者不必因顺序问题而费心。 图2:中断处理流程例如,通过UART执行串行通信时,经常监视字节是否被接收了而导致效率不佳。所以,多数情况下都对程序进行如下编程,即在信息送达 时就会产生中断并进行适当的处理,另外,使定时器产生中断的情况也不在少数。进行“经过了一定时间后该做什么”这类处理时,应进行如下编程,即通过来自定时器的信号开始进行处理。如上所述,在有效利用单片机方面,中断功能发挥了很大的作用。
常用快捷键 在下面的表格里,展示了 LTspice 当中使用频率非常高的一些快捷键(这些快捷键也可以通过展开菜单栏上的【Edit】进行查看或者使用): 快捷键设置 鼠标依次点击 LTspice 菜单栏上的【Simulate -> Settings】打开设置对话框,选中该对话框里的【Schematic】选项卡,按下该界面上的【Keyboard Shortcuts[*]】按钮: 这样就可以查看和修改 LTspice 全部的快捷键设置,其最新的 24.0.0 版本与早期其它版本的默认快捷键设置有所不同,具体请参考如下界面当中的默认设置: 常量符号 单位符号 注意:表达 106 数量级要使用 MEG 或者 meg,而不是使用 M 或者 m;电容器的参数设置里输入 1 表示的是 1 法拉第,而不能使用 1F 或者 1f。 注意:在 .asc 源文件当中,点命令以 ! 符号作为开始,而注释则是以 ; 作为开始。
在建筑物和工业设施中,电缆的敷设非常重要,但是并不是所有的场所都适合敷设电缆。在敷设电缆时,需要注意电缆质量、方向、保护措施、防盗措施等方面的问题,以确保电缆的安全性和正常使用。 ▷ 电缆在桥架内敷设常被忽略的几个要点: 1、当电缆根数超过12根以上时 有可能会同时过载的多回路或多根多芯电缆无间距成束敷设在同一托盘或梯架内敷设,当电缆根数超过12根以上时,电缆载流量未考虑校正系数,这会导致选择的电缆截面偏小,保护器有可能无法保护电缆,电缆过负荷引发火灾等事故。 【解析】 电缆多层排列,底层电缆会因散热不良导致降低系数更小,此时若按单层排列的降低系数,会导致选择的电缆截面偏小。 1)根据托盘或梯架的尺寸大小,确定电缆排列层数,依据《电力工程电缆设计标准》GB50217-2018附录D.0.6电缆桥架上无间距配置多层并列电缆载流量的校正系数进行选择(下表)。 (2)当电缆采用单层排列方式敷设时,可采用《建筑电气常用数据》19DX101-1中电线电缆载流量降低系数,此时需校验电缆托盘或梯架的截面面积是否满足《低压配电设计规范》GB50054-2011第7.6.14条“电缆在托盘和梯架内敷设时,电缆总截面面积与托盘和梯架横截面面积之比,电力电缆不应大于40%,控制电缆不应大于50%”之规定。 2、中间加隔板的桥架不能保证消防线缆的安全 普通负荷与消防负荷的电缆不能采用中间加隔板隔开的桥架敷设,否则火灾时不能保证消防电缆的安全。 【解析】 虽然,依据《民用建筑电气设计标准》GB51348-2019第8.5.13条规定:不同电压、不同用途的电缆不宜敷设在同一桥架内,当受条件限制需安装在同一层桥架上时,应加隔板隔开。 此规定经时间的检验,在火灾现场发现普通负荷与消防负荷的电缆同桥架敷设,中间加隔板隔开,普通线路发生火灾,消防线缆也同时烧毁。由此看出,中间加隔板不能保证消防线缆的安全。 所以,依据《民用建筑电气设计标准》GB51348-2019第13.8.5.1条规定:建议相同电压等级的消防负荷的电缆采用专用的桥架敷设。 3、电缆在屋面不宜使用金属线槽敷设 空调多联机机组或冷却塔放在建筑物的屋面,其配电线路在屋面无遮阳措施的用金属线槽明敷,在夏季,受太阳直接照射屋面的温度可能超60°C,由于电缆封闭在线槽内,热阻升高,线槽内温度可能同步上升,也有可能上升至60°C甚至更高,而选用的电缆载流量没有按环境温度进行温度校正。造成电缆在实际温度下的载流量偏小,可能导致电缆过载的发生。 【解析】 我们选择电缆线径时,一般会先查找电缆的载流量,多数资料会提供常用电缆的几种常用温度下的载流量数据,在空气中敷设的有25°C、30°C、35°C、40°C等4种,当敷设处的环境温度不同于这4种数据时,载流量应乘以校正系数K,其计算公式为: 式中:θn——电缆现行允许长期工作温度,°C; θa——敷设处的环境温度,℃; θc——已知载流量数据的对应温度,℃。 按电缆实际敷设处的环境温度进行载流量校正计算,再选择电缆。 在户外太阳直接照射的电力电缆,应采取遮阳措施或带防雨措施的可自由敷设而非封闭敷设的有孔托盘、梯架、支架等方式。 ▷ 电缆在电缆沟内敷设常被忽略的要点: 1、电缆支架的间距和垂直净距需符合要求 电缆沟内操作不便,如支架间距过小,会造成日后电缆维护不便。 (1)电缆沟的通道宽度和支架层间垂直的最小净距,依据《低压配电设计规范》 GB50054-2011第7.6.23条,应符合下表的规定。 (2)电缆支架间或固定点间的最大间距,依据《低压配电设计规范》GB50054-2011第7.6.27条,应符合下表的规定。 2、室内电缆沟应有防水措施 变电所设置在地下室最底层时,因建筑防水或结构混凝土密闭性不良时,底板返水情况时有发生,此时电缆沟若不采取防水措施,敷设其内的电缆绝缘性能将会降低,有引发事故的可能。 【解析】 (1)变电所不宜设置在地下室最底层。当中央制冷机房设置在最底层时,其专用变电所可设置在制冷机房的上一层或上部空间,以防止积水侵扰。 (2)当无法避免积水时,依据《低压配电设计规范》GB50054-2011第7.6.24条规定:电缆沟应采取防水措施,其底部排水沟的坡度不应小于0.5%,并应设置水坑,积水可经集水坑用泵排出。 当有条件时,积水可直接排入下水道。并且应满足《民用建筑电气设计标准》GB51348-2019第8.7.3.7条,“电缆沟和电缆隧道应采取防水措施,其底部应做不小于0.5%的坡度坡向集水坑(井);积水可经逆止阀直接接人排水管道或经集水坑(井)用泵排出”的要求。 声明 本号所刊发文章仅为学习交流之用,无商业用途,向原作者致敬。因某些文章转载多次无法找到原作者在此致歉,若有侵权请联系小编,我们将及时删文或者付费转载并注明出处,感谢您的支持!
声明本号所刊发文章仅为学习交流之用,无商业用途,向原作者致敬。因某些文章转载多次无法找到原作者在此致歉,若有侵权请联系小编,我们将及时删文或者付费转载并注明出处,感谢您的支持!(来源:网络,版权归原作者)分享 · 共赢电气圈,一个有态度的圈子
随着新能源车的不断普及,普通大众对新能源汽车接受度的提高,更低成本,更清洁的能源正在改变我们的生活。而电动车的电池安全性和续航里程成为不少消费者和厂家关注的问题。如果电池电量监...
在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,感觉是内存不够用了。其实不然,这是Linux内存管理的一个优秀的特征,主要特点是,物理物理内存有多大,Linux都将其充分利用,将一些程序调用过的硬盘数据读入内存(buffer/Cache),利用内存读写的高速特性来提供Linux系统的数据访问性能高。 1. 什么是Page Cache 当程序去读文件,可以通过read也可以通过mmap去读,当你通过任何一种方式从磁盘读取文件时,内核都会给你申请一个Page cache,用来缓存磁盘上的内容。这样读过一次的数据,下次读取的时候就直接从Page cache里去读,提升了系统的整体性能。 对于Linux可以怎么来观察Page Cache呢?其实,在Linux上直接可以通过命令来看,他们的内容是一致的。 首先最简单的是free命令来看一下 首先我们来看看buffers和cached的定义 Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等。 Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。这样,下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘 buffer cache和page cache在处理上是保持一致的,但是存在概念上的差别,page cache是针对文件的cache,buffer是针对磁盘块数据的cache,仅此而已。 2. 为什么需要page cache 通过上图,我们可以直观的看到,标准的I/O和内存映射会先将数据写到Page Cache,这样做是通过减小I/O次数来提升读写效率。我们来实际的例子,我们先来生成一个1G的文件,然后通过把Page cache清空,确保文件内容不在内存中,一次来比较第一次和第二次读文件的差异。 # 1. 生成一个1G的文件dd if=/dev/zero of=/home/dd.out bs=4096 count=1048576# 2.第一次读取文件的耗时如下:root@root-PC:~# time cat /home/dd.out &> /dev/nullreal 0m1.109suser 0m0.016ssys 0m1.093s# 23.清空Page Cache,先执行一下sync来将脏页同步到磁盘,在执行drop cachesync && echo 3 > /proc/sys/vm/drop_caches# 4. 第二次读取文件的耗时如下: root@root-PC:~# time cat /home/dd.out &> /dev/nullreal 0m36.018suser 0m0.069ssys 0m4.839s 通过这两次详细的过程,可以看出第一次读取文件的耗时远小于第二次耗时 因为第一次读取的时候,由于文件内容已经在生成文件的时候已经存在,所以直接从内存读取的数据 第二次会将缓存数据清掉,会从磁盘上读取内容,磁盘I/O比较耗时,内存相比磁盘会快很多 所以Page Cache存在的意义,减小I/O,提升应用的I/O速度。对于Page Cache方案,我们采用原则如下 如果不想增加应用的复杂度,我们优先使用内核管理的Page Cache 如果应用程序需要做精确控制,就需要不走Cache,因为Page Cache有它自身的局限性,就是对于应用程序太过于透明了,以至于很难有好的控制方法。 3. Page Cache是如何“诞生的” Page Cache的产生有两种不同的方式 Buffered I/O(标准I/O) Memory-Mapped I/O(储存映射IO) 这两种方式分别都是如何产生Page Cache的呢? 从图中可以看到,二者是都能产生Page Cache,但是二者还是有差异的 标准I/O是写的话用户缓存区(User page对应的内存),然后再将用户缓存区里的数据拷贝到内核缓存区(Pagecahe Page对应的内存);如果是读的话则是内核缓存区拷贝到用户缓存区,再从用户缓存区去读数据,也就是Buffer和文件内容不存在映射关系 储存映射IO,则是直接将Page Cache的Page给映射到用户空间,用户直接读写PageCache Page里的数据 从原理来说储存映射I/O要比标准的I/O效率高一些,少了“用户空间到内核空间互相拷贝”的过程。下图是一张简图描述这个过程: 首先,往用户缓冲区Buffer(用户空间)写入数据,然后,Buffer中的数据拷贝到内核的缓冲区(这个是PageCache Page) 如果内核缓冲区还没有这个page,就会发生Page Fault会去分配一个Page;如果有,就直接用这个PageCache的Page 拷贝结束后,该PageCache的Page是一个Dirty Page脏页,然后该Dirty Page中的内容会同步到磁盘,同步到磁盘后,该PageCache Page变味Clean Page并且继续存在系统中 我们可以通过手段来测试脏页,如下图所示 $ cat /proc/vmstat | egrep "dirty|writeback"nr_dirty 44nr_writeback 0nr_writeback_temp 0nr_dirty_threshold 1538253nr_dirty_background_threshold 768187 nr_dirty:表示系统中积压了多少脏页(单位为Page 4KB) nr_writeback则表示有多少脏页正在回写到磁盘中(单位为Page 4KB) 总结 读过程,当内核发起一个读请求时候 先检查请求的数据是否缓存到page Cache中,如果有则直接从内存中读取,不访问磁盘 如果Cache中没有请求数据,就必须从磁盘中读取数据,然后内核将数据缓存到Cache中 这样后续请求就可以命中cache,page可以只缓存一个文件的部分内容,不需要把整个文件都缓存 写过程,当内核发起一个写请求时候 直接写到Cache中,内核会将被写入的Page标记为dirty,并将其加入到dirty list中 内核会周期性的将dirty list中的page回写到磁盘上,从而使磁盘上的数据和内存中缓存的数据一致 4. page cache是如何“死亡” free命令中的buffer/cache中的是“活着”的Page Cache,那他们是什么时候被回收的呢? 回收的主要方式有两种 直接回收: 后台回收: 观察Page cache直接回收和后台回收最简单方便的方式,借助这个工具,可以明确观察内存回收行为 pgscank/s: kswapd(后台回收线程)每秒扫面的Page个数 pgscand/s: Application在内存申请过程中每秒直接扫描的Page个数 pgsteal/s: 扫面的page中每秒被回收的个数 %vmeff: pgsteal/(pgscank+pgscand),回收效率,越接近100说明系统越安全,越接近0,说明系统内存压力越大 pgpgin/s 表示每秒从磁盘或SWAP置换到内存的字节数(KB) pgpgout/s: 表示每秒从内存置换到磁盘或SWAP的字节数(KB) fault/s: 每秒钟系统产生的缺页数,即主缺页与次缺页之和(major + minor) majflt/s: 每秒钟产生的主缺页数. pgfree/s: 每秒被放入空闲队列中的页个数 需要C/C++ Linux服务器架构师学习资料加qun579733396获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享 5.Page Cache性能优化 通过前文我们知道了Linux是用Cache/Buffer缓存数据,提高系统的I/O性能,且有一个回刷任务在适当时候把脏数据回刷到储存介质中。那么接下来我们重点学习优化机制。 包括以下内容 什么时候触发回刷? 脏数据达到多少阈值还是定时触发呢? 内核是如何做到回写机制的 (1) 配置概述 Linux内核在/proc/sys/vm中有透出数个配置文件,可以对触发回刷的时机进行调整。内核的回刷进程是怎么运作的呢?这数个配置文件有什么作用呢? root@public-VirtualBox:~# sysctl -a | grep dirtyvm.dirty_background_bytes = 0vm.dirty_background_ratio = 10vm.dirty_bytes = 0vm.dirty_expire_centisecs = 3000vm.dirty_ratio = 20vm.dirty_writeback_centisecs = 500vm.dirtytime_expire_seconds = 43200 在/proc/sys/vm中有以下文件与回刷脏数据密切相关: vm.dirty_background_ratio: 内存可以填充脏数据的百分比,这些脏数据稍后会写入磁盘。pdflush/flush/kdmflush这些后台进程会稍后清理脏数据。比如,我有32G内存,那么有3.2G(10%的比例)的脏数据可以待着内存里,超过3.2G的话就会有后台进程来清理。 vm.dirty_ratio 可以用脏数据填充的绝对最大系统内存量,当系统到达此点时,必须将所有脏数据提交到磁盘,同时所有新的I/O块都会被阻塞,直到脏数据被写入磁盘。这通常是长I/O卡顿的原因,但这也是保证内存中不会存在过量脏数据的保护机制。 vm.dirty_background_bytes 和 vm.dirty_bytes 另一种指定这些参数的方法。如果设置 xxx_bytes版本,则 xxx_ratio版本将变为0,反之亦然。 vm.dirty_expire_centisecs 指定脏数据能存活的时间。在这里它的值是30秒。当 pdflush/flush/kdmflush 在运行的时候,他们会检查是否有数据超过这个时限,如果有则会把它异步地写到磁盘中。毕竟数据在内存里待太久也会有丢失风险。 vm.dirty_writeback_centisecs 指定多长时间 pdflush/flush/kdmflush 这些进程会唤醒一次,然后检查是否有缓存需要清理。 实际上dirty_ratio的数字大于dirty_background_ratio,是不是就不会达到dirty_ratio呢? 首先达到dirty_background_ratio的条件后触发flush进程进行异步的回写操作,但是这一过程中应用进程仍然可以进行写操作,如果多个应用写入的量大于flush进程刷出的量,那自然就会达到vm.dirty_ratio这个参数所设定的阙值,此时操作系统会转入同步地进行脏页的过程,阻塞应用进程。 (2)配置实例 单纯的配置说明毕竟太抽象。结合网上的分享,我们看看在不同场景下,该如何配置? 场景1:尽可能不丢数据 有些产品形态的数据非常重要,例如行车记录仪。在满足性能要求的情况下,要做到尽可能不丢失数据。 /* 此配置不一定适合您的产品,请根据您的实际情况配置 */dirty_background_ratio = 5dirty_ratio = 10dirty_writeback_centisecs = 50dirty_expire_centisecs = 100 这样的配置有以下特点: 当脏数据达到可用内存的5%时唤醒回刷进程 脏数据达到可用内存的10%时,应用每一笔数据都必须同步等待 每隔500ms唤醒一次回刷进程 当脏数据达到可用内存的5%时唤醒回刷进程 由于发生交通事故时,行车记录仪随时可能断电,事故前1~2s的数据尤为关键。因此在保证性能满足不丢帧的情况下,尽可能回刷数据。 此配置通过减少Cache,更加频繁唤醒回刷进程的方式,尽可能让数据回刷。 此时的性能理论上会比每笔数据都O_SYNC略高,比默认配置性能低,相当于用性能换数据安全。 场景2:追求更高性能 有些产品形态不太可能会掉电,例如服务器。此时不需要考虑数据安全问题,要做到尽可能高的IO性能。 /* 此配置不一定适合您的产品,请根据您的实际情况配置 */dirty_background_ratio = 50dirty_ratio = 80dirty_writeback_centisecs = 2000dirty_expire_centisecs = 12000 这样的配置有以下特点: 当脏数据达到可用内存的50%时唤醒回刷进程 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待 每隔20s唤醒一次回刷进程 内存中脏数据存在时间超过120s则在下一次唤醒时回刷 与场景1相比,场景2的配置通过 增大Cache,延迟回刷唤醒时间来尽可能缓存更多数据,进而实现提高性能 场景3:突然的IO峰值拖慢整体性能 什么是IO峰值?突然间大量的数据写入,导致瞬间IO压力飙升,导致瞬间IO性能狂跌,对行车记录仪而言,有可能触发视频丢帧。 /* 此配置不一定适合您的产品,请根据您的实际情况配置 */dirty_background_ratio = 5dirty_ratio = 80dirty_writeback_centisecs = 500dirty_expire_centisecs = 3000 这样的配置有以下特点: 当脏数据达到可用内存的5%时唤醒回刷进程 当脏数据达到可用内存的80%时,应用每一笔数据都必须同步等待 每隔5s唤醒一次回刷进程 内存中脏数据存在时间超过30s则在下一次唤醒时回刷 这样的配置,通过增大Cache总容量,更加频繁唤醒回刷的方式,解决IO峰值的问题,此时能保证脏数据比例保持在一个比较低的水平,当突然出现峰值,也有足够的Cache来缓存数据。 (3)内核演变 对于回写方式在之前的2.4内核中,使用 bdflush的线程专门负责writeback的操作,因为磁盘I/O操作很慢,而现代操作系统通常具有多个块设备,如果bdflush在其中一个块设备上等待I/O操作的完成,可能需要很长的时间,此时其他块设备还处于空闲状态,这时候,单线程模式的bdflush就称为了影响性能的瓶颈。而此时bdflush是没有周期扫描功能,因此需要配合kupdate线程一起使用。 bdflush 存在的问题: 整个系统仅仅只有一个 bdflush 线程,当系统回写任务较重时,bdflush 线程可能会阻塞在某个磁盘的I/O上, 导致其他磁盘的I/O回写操作不能及时执行 于是在2.6内核中,bdflush机制就被pdflush取代,pdflush是一组线程,根据块设备I/O负载情况,数量从最少的2个到最多的8个不等,如果1S内都没有空闲的pdflush线程可用,内核将创建一个新的pdflush线程,反之某个pdflush线程空闲超过1S,则该线程将会被销毁。pdflush 线程数目是动态的,取决于系统的I/O负载。它是面向系统中所有磁盘的全局任务的。 pdflush 存在的问题: pdflush的数目是动态的,一定程度上缓解了 bdflush 的问题。但是由于 pdflush 是面向所有磁盘的,所以有可能出现多个 pdflush 线程全部阻塞在某个拥塞的磁盘上,同样导致其他磁盘的I/O回写不能及时执行。 于是在内最新的内核中,直接将一个块设备对应一个thread,这种内核线程被称为flusher threads,线程名为“Writeback",执行体为"wb_workfn",通过workqueue机制实现调度。 (4)内核实现 由于内核page cache的作用,写操作实际被延迟写入。当page cache里的数据被用户写入但是没有刷新到磁盘时,则该page为脏页(块设备page cache机制因为以前机械磁盘以扇区为单位读写,引入了buffer_head,每个4K的page进一步划分成8个buffer,通过buffer_head管理,因此可能只设置了部分buffer head为脏)。 脏页在以下情况下将被回写(write back)到磁盘上: 脏页在内存里的时间超过了阈值。 系统的内存紧张,低于某个阈值时,必须将所有脏页回写。 用户强制要求刷盘,如调用sync()、fsync()、close()等系统调用。 以前的Linux通过pbflush机制管理脏页的回写,但因为其管理了所有的磁盘的page/buffer_head,存在严重的性能瓶颈,因此从Linux 2.6.32开始,脏页回写的工作由bdi_writeback机制负责。bdi_writeback机制为每个磁盘都创建一个线程,专门负责这个磁盘的page cache或者buffer cache的数据刷新工作,以提高I/O性能。 在 kernel/sysctl.c中列出了所有的配置文件的信息 static struct ctl_table vm_table[] = { ... { .procname = "dirty_background_ratio", .data = &dirty_background_ratio, .maxlen = sizeof(dirty_background_ratio), .mode = 0644, .proc_handler = dirty_background_ratio_handler, .extra1 = &zero, .extra2 = &one_hundred, }, { .procname = "dirty_ratio", .data = &vm_dirty_ratio, .maxlen = sizeof(vm_dirty_ratio), .mode = 0644, .proc_handler = dirty_ratio_handler, .extra1 = &zero, .extra2 = &one_hundred, }, { .procname = "dirty_writeback_centisecs", .data = &dirty_writeback_interval, .maxlen = sizeof(dirty_writeback_interval), .mode = 0644, .proc_handler = dirty_writeback_centisecs_handler, }, ...} 这些值在mm/page-writeback.c中有全局变量定义 int dirty_background_ratio = 10;int vm_dirty_ratio = 20;unsigned int dirty_writeback_interval = 5 * 100; /* centiseconds */ 通过ps -aux,我们可以看到writeback的内核进程 这实际上是一个工作队列对应的进程,在default_bdi_init()中创建(mm/backing-dev.c) static int __init default_bdi_init(void){ int err; bdi_wq = alloc_workqueue("writeback", WQ_MEM_RECLAIM | WQ_FREEZABLE | WQ_UNBOUND | WQ_SYSFS, 0); if (!bdi_wq) return -ENOMEM; err = bdi_init(&noop_backing_dev_info); return err;} 回刷进程的核心是函数wb_workfn(),通过函数wb_init()绑定。 static int wb_init(struct bdi_writeback *wb, struct backing_dev_info *bdi int blkcg_id, gfp_t gfp){ ... INIT_DELAYED_WORK(&wb->dwork, wb_workfn); ...} 唤醒回刷进程的操作是这样的 static void wb_wakeup(struct bdi_writeback *wb){ spin_lock_bh(&wb->work_lock); if (test_bit(WB_registered, &wb->state)) mod_delayed_work(bdi_wq, &wb->dwork, 0); spin_unlock_bh(&wb->work_lock);} 表示唤醒的回刷任务在工作队列writeback中执行,这样,就把工作队列和回刷工作绑定了,重点看看这个接口做了些什么工作 void wb_workfn(struct work_struct *work){ struct bdi_writeback *wb = container_of(to_delayed_work(work), struct bdi_writeback, dwork); long pages_written; set_worker_desc("flush-%s", dev_name(wb->bdi->dev)); current->flags |= PF_SWAPWRITE; //如果当前不是一个救援工作队列,或者当前bdi设备已注册,这是一般路径 if (likely(!current_is_workqueue_rescuer() || !test_bit(WB_registered, &wb->state))) { ------------------------(1) /* * The normal path. Keep writing back @wb until its * work_list is empty. Note that this path is also taken * if @wb is shutting down even when we're running off the * rescuer as work_list needs to be drained. */ do {从bdi的work_list取出队列里的任务,执行脏页回写 pages_written = wb_do_writeback(wb); trace_writeback_pages_written(pages_written); } while (!list_empty(&wb->work_list)); } else { -----------------------(2) /* * bdi_wq can't get enough workers and we're running off * the emergency worker. Don't hog it. Hopefully, 1024 is * enough for efficient IO. */ pages_written = writeback_inodes_wb(wb, 1024,//只提交一个work并限制写入1024个pages WB_REASON_FORKER_THREAD); trace_writeback_pages_written(pages_written); } //如果上面处理完到现在这段间隔又有了work,再次立马启动回写进程 if (!list_empty(&wb->work_list)) -----------------------(3) mod_delayed_work(bdi_wq, &wb->dwork, 0); else if (wb_has_dirty_io(wb) && dirty_writeback_interval) wb_wakeup_delayed(wb);//如果所有bdi设备上挂的dirty inode回写完,那么就重置定制器, //再过dirty_writeback_interval,即5s后再唤醒回写进程 current->flags &= ~PF_SWAPWRITE;} 正常路径,rescue workerrescue内核线程,内存紧张时创建新的工作线程可能会失败,如果内核中有会需要回收的内存,就调用wb_do_writeback进行回收 如果当前workqueue不能获得足够的worker进行处理,只提交一个work并限制写入1024个pages 这也过程代码较多,暂不去深入分析,重点关注相关的配置是如何起作用的。 (5) 触发回写方式 触发writeback的地方主要有以下几处: 5.1 主动发起 手动执行sysn命令 sync-> SYSCALL_DEFINE0(sync)-> sync_inodes_one_sb-> sync_inodes_sb-> bdi_queue_work syncfs系统调用 SYSCALL_DEFINE1(syncfs, int, fd)-> sync_filesystem-> __sync_filesystem-> sync_inodes_sb-> bdi_queue_work 直接内存回收,内存不足时调用 free_more_memory-> wakeup_flusher_threads-> __bdi_start_writeback-> bdi_queue_work 分配内存空间不足,触发回写脏页腾出内存空间 __alloc_pages_nodemask-> __alloc_pages_slowpath-> __alloc_pages_direct_reclaim-> __perform_reclaim-> try_to_free_pages-> do_try_to_free_pages-> wakeup_flusher_threads-> __bdi_start_writeback-> bdi_queue_work remount/umount操作,需要先将脏页写回 5.2 空间层面 当系统的“dirty”的内存大于某个阈值,该阈值是在总共的“可用内存”(包括free pages 和reclaimable pages)中的占比。 参数“dirty_background_ratio”(默认值10%),或者是绝对字节数“dirty_background_bytes”(默认值为0,表示生效)。两个参数只要谁先达到即可执行,此时就会交给专门负责writeback的background线程去处理。 参数“dirty_ratio”(默认值30%)和“dirty_bates”(默认值为0,表示生效),当“dirty”的内存达到这个比例或数量,进程则会停下write操作(被阻塞),先把“dirty”进行writeback。 5.3 时间层面 周期性的扫描,扫描间隔用参数:dirty_writeback_interval表示,以毫秒为单位。发现存在最近一次更新时间超过某个阈值(参数:dirty_expire_interval,单位毫秒)的pages。如果每个page都维护最近更新时间,开销会很大且扫描会很耗时,因此具体实现不会以page为粒度,而是按inode中记录的dirtying-time来计算。 (6)总结 文件缓存是一项重要的性能改进,在大多数情况下,读缓存在绝大多数情况下是有益无害的(程序可以直接从RAM中读取数据)。写缓存比较复杂,Linux内核将磁盘写入缓存,过段时间再异步将它们刷新到磁盘。这对加速磁盘I/O有很好的效果,但是当数据未写入磁盘时,丢失数据的可能性会增加。
声明 本号所刊发文章仅为学习交流之用,无商业用途,向原作者致敬。因某些文章转载多次无法找到原作者在此致歉,若有侵权请联系小编,我们将及时删文或者付费转载并注明出处,感谢您的支持!