技术标签: Linux
经常使用top命令了解进程信息,其中包括内存方面的信息。命令top帮助文档是这么解释各个字段的。
VIRT , Virtual Image (kb)
RES, Resident size (kb)
SHR, Shared Mem size (kb)
%MEM, Memory usage(kb)
SWAP, Swapped size (kb)
CODE, Code size (kb)
DATA, Data+Stack size (kb)
nFLT, Page Fault count
nDRT, Dirty Pages count
尽管有注释,但依然感觉有些晦涩,不知所指何意?
进程内存空间
正在运行的程序,叫进程。每个进程都有完全属于自己的,独立的,不被干扰的内存空间。此空间,被分成几个段(Segment),分别是Text, Data, BSS, Heap, Stack。用户进程内存空间,也是系统内核分配给该进程的VM(虚拟内存),但并不表示这个进程占用了这么多的RAM(物理内存)。这个空间有多大?命令top输出的VIRT值告诉了我们各个进程内存空间的大小(进程内存空间随着程序的执行会增大或者缩小)。你还可以通过/proc//maps,或者pmap –d 了解某个进程内存空间都分布,比如:
1 2 3 4 5 6 7 8 9 10 11 |
|
PS:线性地址,访问权限, offset, 设备号,inode,映射文件
“内存总是被进程占用”,这句话换过来可以这么理解:进程总是需要内存。当fork()或者exec()一个进程的时候,系统内核就会分配一定量的VM给进程,作为进程的内存空间,大小由BSS段,Data段的已定义的全局变量、静态变量、Text段中的字符直接量、程序本身的内存映像等,还有Stack段的局部变量决定。当然,还可以通过malloc()等函数动态分配内存,向上扩大heap。
动态分配与静态分配,二者最大的区别在于:1. 直到Run-Time的时候,执行动态分配,而在compile-time的时候,就已经决定好了分配多少Text+Data+BSS+Stack。2.通过malloc()动态分配的内存,需要程序员手工调用free()释放内存,否则容易导致内存泄露,而静态分配的内存则在进程执行结束后系统释放(Text, Data), 但Stack段中的数据很短暂,函数退出立即被销毁。
我们使用几个示例小程序,加深理解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
|
PS:使用malloc(),特别要留意heap段中的内存不用时,尽早手工free()。通过top输出的VIRT和RES两值来观察进程占用VM和RAM大小。
本节结束之前,介绍工具size。因为Text, BSS, Data段在编译时已经决定了进程将占用多少VM。可以通过size,知道这些信息。
# gcc example_2_3.c -o example_2_3
# size example_2_3
text data bss dec hex filename
1403 272 8 1683 693 example_2_3
编码人员在编写程序之际,时常要处理变化数据,无法预料要处理的数据集变化是否大(phper可能难以理解),所以除了变量之外,还需要动态分配内存。GNU libc库提供了二个内存分配函数,分别是malloc()和calloc()。调用malloc(size_t size)函数分配内存成功,总会分配size字节VM(再次强调不是RAM),并返回一个指向刚才所分配内存区域的开端地址。分配的内存会为进程一直保留着,直到你显示地调用free()释放它(当然,整个进程结束,静态和动态分配的内存都会被系统回收)。开发人员有责任尽早将动态分配的内存释放回系统。记住一句话:尽早free()!
我们来看看,malloc()小示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
PS:系统调用brk(0)取得当前堆的地址,也称为断点。
通过跟踪系统内核调用,可见glibc函数malloc()总是通过brk()或mmap()系统调用来满足内存分配需求。函数malloc(),根据不同大小内存要求来选择brk(),还是mmap(), 128Kbytes是临界值。小块内存(<=128kbytes),会调用brk(),它将数据段的最高地址往更高处推(堆从底部向上增长)。大块内存,则使用mmap()进行匿名映射(设置标志MAP_ANONYMOUS)来分配内存,与堆无关,在堆之外。这样做是有道理的,试想:如果大块内存,也调用brk(),则容易被小块内存钉住,必竟用大块内存不是很频繁;反过来,小块内存分配更为频繁得多,如果也使用mmap(),频繁的创建内存映射会导致更多的开销,还有一点就是,内存映射的大小要求必须是“页”(单位,内存页面大小,默认4Kbytes或8Kbytes)的倍数,如果只是为了”hello world”这样小数据就映射一“页”内存,那实在是太浪费了。
跟malloc()一样,释放内存函数free(),也会根据内存大小,选择使用brk()将断点往低处回推,或者选择调用munmap()解除映射。有一点需要注意:并不是每次调用free()小块内存,都会马上调用brk(),即堆并不会在每次内存被释放后就被缩减,而是会被glibc保留给下次malloc()使用(必竟小块内存分配较为频繁),直到glibc发现堆空闲大小显著大于内存分配所需数量时,则会调用brk()。但每次free()大块内存,都会调用munmap()解除映射。下面是二张malloc()小块内存和大块内存的示例图。
示意图:函数malloc(100000),小于128kbytes,往高处推(heap->)。留意紫圈标注
示意图:函数malloc(1024*1024),大于128kbytes,在heap与stack之间。留意紫圈。PS:图中的Data Segment泛指BSS, Data, Heap。有些文档有说明:数据段有三个子区域,分别是BSS, Data, Heap。
每次调用malloc(),系统都只是给进程分配线性地址(VM),并没有随即分配页框(RAM)。系统尽量将分配页框的工作推迟到最后一刻—用到时缺页异常处理。这种页框按需延迟分配策略最大好处之一:充分有效地善用系统稀缺资源RAM。
当指针引用的内存页没有驻留在RAM中,即在RAM找不到与之对应的页框,则会发生缺页异常(对进程来说是透明的),内核便陷入缺页异常处理。发生缺页异常有几种情况:1.只分配了线性地址,并没有分配页框,常发生在第一次访问某内存页。2.已经分配了页框,但页框被回收,换出至磁盘(交换区)。3.引用的内存页,在进程空间之外,不属于该进程,可能已被free()。我们使用一段伪代码来大致了解缺页异常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
L0,函数malloc()通过brk()给进程分配了100Kbytes的线性地址区域(VM).然而,系统并没有随即分配页框(RAM)。即此时,进程没有占用100Kbytes的物理内存。这也表明了,你时常在使用top的时候VIRT值增大,而RES值却不变的原因。
L1,通过*p引用了100Kbytes的第一页(4Kbytes)。因为是第一次引用此页,在RAM中找不到与之相对应的页框。发生缺页异常(对于进程而言缺页异常是透明的),系统灵敏地捕获这一异常,进入缺页异常处理阶段:接下来,系统会分配一个页框(RAM)映射给它。我们把这种情况(被访问的页还没有被放在任何一个页框中,内核分配一新的页框并适当初始化来满足调用请求),也称为Demand Paging。
L2,过了很长一段时间,通过*p再次引用100Kbytes的第一页。若系统在RAM找不到它映射的页框(可能交换至磁盘了)。发生缺页异常,并被系统捕获进入缺页异常处理。接下来,系统则会分配一页页框(RAM),找到备份在磁盘的那“页”,并将它换入内存(其实因为换入操作比较昂贵,所以不总是只换入一页,而是预换入多页。这也表明某些文档说:”vmstat某时出现不少si并不能意味着物理内存不足”)。凡是类似这种会迫使进程去睡眠(很可能是由于当前磁盘数据填充至页框(RAM)所花的时间),阻塞当前进程的缺页异常处理称为主缺页(major falut),也称为大缺页(参见下图)。相反,不会阻塞进程的缺页,称为次缺页(minor fault),也称为小缺面。
L3,引用了100Kbytes的第二页。参见第一次访问100Kbytes第一页, Demand Paging。
L4,释放了内存:线性地址区域被删除,页框也被释放。
L5,再次通过*p引用内存页,已被free()了(用户进程本身并不知道)。发生缺页异常,缺面异常处理程序会检查出这个缺页不在进程内存空间之内。对待这种编程错误引起的缺页异常,系统会杀掉这个进程,并且报告著名的段错误(Segmentation fault)。
主缺页异常处理过程示意图,参见Page Fault Handling
随着网络并发用户数量增多,进程数量越来越多(比如一般守护进程会fork()子进程来处理用户请求),缺页异常也就更频繁,需要缓存更多的磁盘数据(参考下篇OS Page Cache),RAM也就越来越紧少。为了保证有够用的页框供给缺页异常处理,Linux有一套自己的做法,称为PFRA。PFRA总会从用户态进内存程空间和页面缓存中,“窃取”页框满足供给。所谓”窃取”,指的是:将用户进程内存空间对应占用的页框中的数据swap out至磁盘(称为交换区),或者将OS页面缓存中的内存页(还有用户进程mmap()的内存页)flush(同步fsync())至磁盘设备。PS:如果你观察到因为RAM不足导致系统病态式般慢,通常都是因为缺页异常处理,以及PFRA在”盗页”。我们从以下几个方面了解PFRA。
候选页框:找出哪些页框是可以被回收?
进程内存空间占用的页框,比如数据段中的页(Heap, Data),还有在Heap与Stack之间的匿名映射页(比如由malloc()分配的大内存)。但不包括Stack段中的页。
进程空间mmap()的内存页,有映射文件,非匿名映射。
缓存在页面缓存中Buffer/Cache占用的页框。也称OS Page Cache。
页框回收策略:确定了要回收的页框,就要进一步确定先回收哪些候选页框
尽量先回收页面缓存中的Buffer/Cache。其次再回收内存空间占用的页框。
进程空间占用的页框,要是没有被锁定,都可以回收。所以,当某进程睡眠久了,占用的页框会逐渐地交换出去至交换区。
使收LRU置换算法,将那些久而未用的页框优先被回收。这种被放在LRU的unused链表的页,常被认为接下来也不太可能会被引用。
相对回收Buffer/Cache而言,回收进程内存页,昂贵很多。所以,Linux默认只有swap_tendency(交换倾向值)值不小于100时,才会选择换出进程占用的RES。其实交换倾向值描述的是:系统越忙,且RES都被进程占用了,Buffer/Cache只占了一点点的时候,才开始回收进程占用页框。PS:这正表明了,某些DBA提议将MySQL InnoDB服务器vm.swappiness值设置为0,以此让InnoDB Buffer Pool数据在RES呆得更久。
如果实在是没有页框可回收,PFRA使出最狠一招,杀掉一个用户态进程,并释放这些被占的页框。当然,这个被杀的进程不是胡乱选的,至少应该是占用较多页框,运行优选级低,且不是root用户的进程。
激活回收页框:什么时候会回收页框?
紧急回收。系统内核发现没有够用的页框分配,供给读文件和内存缺页处理的时候,系统内核开始”紧急回收页框”。唤醒pdflush内核线程,先将1024页脏页从页面缓存写回磁盘。然后开始回收32页框,若反复回收13次,还收不齐32页框,则发狠杀一个进程。
周期性回收。在紧急回收之前,PFRA还会唤醒内核线程kswapd。为了避免更多的“紧急回收”,当发现空闲页框数量低于设置的警告值时,内核线程kswapd就会被唤醒,回收页框。直到空闲的页框的数量达到设定的安全值。PS:当RES资源紧张的时候,你可以通过ps命令看到更多的kswapd线程被唤醒。
OOM。在高峰时期,RES高度紧张的时候,kswapd持续回收的页框供不应求,直到进入”紧急回收”,直到 OOM。
这二个关键字在很多地方出现,译过来应该是Paging(调页),Swapping(交换)。PS:英语里面用得多的动词加上ing,就成了名词,比如building。咬文嚼字,实在是太难。看二图
Swapping的大部分时间花在数据传输上,交换的数据也越多,意味时间开销也随之增加。对于进程而言,这个过程是透明的。由于RAM资源不足,PFRA会将部分匿名页框的数据写入到交换区(swap area),备份之,这个动作称为so(swap out)。等到发生内存缺页异常的时候,缺页异常处理程序会将交换区(磁盘)的页面又读回物理内存,这个动作称为si(swap in)。每次Swapping,都有可能不只是一页数据,不管是si,还是so。Swapping意味着磁盘操作,更新页表等操作,这些操作开销都不小,会阻塞用户态进程。所以,持续飚高的si/so意味着物理内存资源是性能瓶颈。
Paging,前文我们有说过Demand Paging。通过线性地址找到物理地址,找到页框。这个过程,可以认为是Paging,对于进程来讲,也是透明的。Paging意味着产生缺页异常,也有可能是大缺页,也就意味着浪费更多的CPU时间片资源。
1.用户进程内存空间分为5段,Text, DATA, BSS, Heap, Stack。其中Text只读可执行,DATA全局变量和静态变量,Heap用完就尽早free(),Stack里面的数据是临时的,退出函数就没了。
2.glibc malloc()动态分配内存。使用brk()或者mmap(),128Kbytes是一个临界值。避免内存泄露,避免野指针。
3.内核会尽量延后Demand Paging。主缺页是昂贵的。
4.先回收Buffer/Cache占用的页框,然后程序占用的页框,使用LRU置换算法。调小vm.swappiness值可以减少Swapping,减少大缺页。
5.更少的Paging和Swapping
6.fork()继承父进程的地址空间,不过是只读,使用cow技术,fork()函数特殊在于它返回二次。
文章浏览阅读3.8k次。1、将下载好的萤石js插件,添加到SoringBoot项目中。位置可参考下图所示。(容易出错的地方,在将js插件在html页面引入时,发生路径错误的问题)所以如果对页面中引入js的路径不清楚,可参考下图所示存放路径。2、将ezuikit.js引入到demo-live.html中。(可直接将如下代码复制到你创建的html页面中)<!DOCTYPE html><html lan..._ezuikit 测试的url
文章浏览阅读322次。第二步,在弹出的对话框选择,设备驱动—>PLC—>莫迪康—>ModbusRTU—>COM,根据配置软件选择的协议选期期,这里以此为例,然后点击“下一步”。第四步,把使用虚拟串口打勾(GPRS设备),根据需要选择要生成虚拟口,这里以选择KVCOM1为例,然后点击“下一步”设备ID即Modbus地址(1-255) 使用DTU时,为下485接口上的设备地址。第六步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“第五步,Modbus的从机地址,与配置软件相同,这里以1为例,点击“下一步“_组态王ua
文章浏览阅读9.4k次,点赞22次,收藏19次。安装npm相当于安装node.js,Node.js已自带npm,安装Node.js时会一起安装,npm的作用就是对Node.js依赖的包进行管理,也可以理解为用来安装/卸载Node.js需要装的东西_npm安装配置
文章浏览阅读748次,点赞21次,收藏26次。大家好,小编来为大家解答以下问题,python基础训练100题,python入门100例题,现在让我们一起来看看吧!宝子们还在新手村练级的时候,不单要吸入基础知识,夯实自己的理论基础,还要去实际操作练练手啊!由于文章篇幅限制,不可能将100道题全部呈现在此除了这些,下面还有我整理好的基础入门学习资料,视频和讲解文案都很齐全,用来入门绝对靠谱,需要的自提。保证100%免费这不,贴心的我爆肝给大家整理了这份今天给大家分享100道Python练习题。大家一定要给我三连啊~
文章浏览阅读1k次。 为了在 Linux ( Ubuntu) 上安装sublime,一般大家都会选择常见的教程或是 sublime 官网教程,然而在国内这种方法可能失效。为此,需要用安装包安装。以下就是使用官网安装包安装的教程。打开 sublime 官网后,点击右上角 download, 或是直接访问点击打开链接,即可看到各个平台上的安装包。选择 Linux 64 位版并下载。下载后,打开终端,进入安装..._ubuntu 安装sumlime text打不开
文章浏览阅读563次,点赞13次,收藏6次。CrossOver24是一款类虚拟机软件,专为macOS和Linux用户设计。它的核心技术是Wine,这是一种在Linux和macOS等非Windows操作系统上运行Windows应用程序的开源软件。通过CrossOver24,用户可以在不购买Windows授权或使用传统虚拟机的情况下,直接在Mac或Linux系统上运行Windows软件和游戏。该软件还提供了丰富的功能,如自动配置、无缝集成和实时传输等,以实现高效的跨平台操作体验。
文章浏览阅读1.7k次。一个用聊天的方式让ChatGPT帮我写的线程安全的环形List_为什么gpt一写list就卡
文章浏览阅读336次。我们在前面的文章里曾写过Web应用中乱码产生的原因和处理方式,旧文回顾:深度揭秘乱码问题背后的原因及解决方式其中我们提到可以通过Filter的方式来设置请求和响应的encoding,来解..._filterconfig selectencoding
文章浏览阅读651次。转自:http://www.jb51.net/article/36480.htmencodeURI和decodeURI是成对来使用的,因为浏览器的地址栏有中文字符的话,可以会出现不可预期的错误,所以可以encodeURI把非英文字符转化为英文编码,decodeURI可以用来把字符还原回来_js encodeur decodeurl
文章浏览阅读1.9w次,点赞6次,收藏3次。前言在日常的Android开发当中,我们肯定要打包apk。但是今天我打包的时候遇到一个很奇怪的问题Android The destination folder does not exist or is not writeable,大意是目标文件夹不存在或不可写。出现问题的原因以及解决办法上面有说报错的中文大意是:目标文件夹不存在或不可写。其实问题就在我们的打包界面当中图中标红的Desti..._the destination folder does not exist or is not writeable
文章浏览阅读94次。一、配置代码编辑区的样式 <1>打开Eclipse,Help —> Install NewSoftware,界面如下: <2>点击add...,按下图所示操作: name:随意填写,Location:http://eclipse-color-th..._ecplise高大上设置
文章浏览阅读2.8k次。一,下载mysql:http://dev.mysql.com/downloads/mysql/; 打开页面之后,在Select Platform:下选择linux Generic,如果没有出现Linux的选项,请换一个浏览器试试。我用的谷歌版本不可以,换一个别的浏览器就行了,如果还是不行,需要换一个翻墙的浏览器。 二,下载完后解压缩并放到安装文件夹下: 1、MySQL-client-5.6.2_linux mysql 安装 mysql-5.6.24-1.linux_glibc2.5.x86_64.rpm-bundle