===系统内存===
系统内存的使用情况可以用以下公式表示:
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Active + Inactive + Unevictable + (HugePages_Total * Hugepagesize)】
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【Cached + AnonPages + Buffers + (HugePages_Total * Hugepagesize)】
MemTotal = MemFree +【Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X】+【ΣPss + (Cached – mapped) + Buffers + (HugePages_Total * Hugepagesize)】
File-backed内存,anon匿名内存,Shmem是tmpfs所使用的内存
=cached是和file有关的所有文件映射和文件系统
【buffers + cached 】= 【Active(file) + Inactive(file) + Shmem 】
=AnonPages不算tmpfs,shmem,纯进程申请malloc和占用内存
【Active(anon)+Inactive(anon)+(mlock=Unevictable)】 = 【AnonPages + Shmem】
=ΣPss = grep Pss /proc/[1-9]*/smaps | awk '{total+=$2}; END {print total}'
【AnonPages】=【ΣPss – mapped】
mapped包括代码段和数据段
SHR是RES中”映射至文件”的物理内存总和。包括:
程序的代码段。
动态库的代码段。
通过mmap做的文件映射。
通过mmap做的匿名映射,但指明了MAP_SHARED属性。
通过shmget申请的共享内存。
/proc//smaps内Shared_*统计的是RES中映射数量>=2的物理内存。
/proc//smaps内Private_*统计的是RES中映射数量=1的物理内存。
===进程内存===
程序编译:预编译hello.i,编译hello.s,汇编hello.o,连接a.out
ELF文件一共有4种类型:Relocatable file、Executable file、Shared object file和Core Dump file
.o文件,目标文件只是ELF文件的可重定位文件(Relocatable file)
.so文件是Shared object file
bin可执行文件是Executable file
文件结构:text代码段,data数据段,bss预留段,other sections,(只读,注释,堆栈)
sectios table,string table,symbol table
1,ELF文件大小
size: 输出指定输入文件各段及其总和的大小
text data bss dec hex filename
1492 564 12 2068 814 sleep
ELF文件提供两种视图,使用objdump工具和readelf工具可以查看分析elf文件的格式
link:ELF header,program header table,sections,section header table
exec:ELF header,program header table,segments,section header table
- ELF header: 描述整个文件的组织。
- Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
- sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,
也就是说,在链接阶段,我们可以忽略program header table来处理此文件,
在运行阶段可以忽略section header table来处理此程序
segments与sections是包含的关系,一个segment包含若干个section。
当ELF文件被加载到内存中后,系统会将多个具有相同权限(flg值)section合并一个segment
如果section的长度不是其整数倍,则导致多余部分也将占用一个页
- Section Header Table: 包含了文件各个segction的属性信息
对比三类ELF文件,我们得到了以下结论:
(1)e_type标识了文件类型
(2)Relocatable File(.o文件)不需要执行,因此e_entry字段为0,且没有Program Header Table等执行视图
(3)不同类型的ELF文件的Section也有较大区别,比如只有Relocatable File有.strtab节
2,汇编程序
反汇编代码
查看源代码被翻译成的汇编代码, 大概有3种方法,
1) 通过编译器直接从源文件生成, 如gcc -S
2) 对目标代码反汇编, 一种是静态反汇编, 就是使用objdump
3) 另外一种就是对运行时的代码反汇编, 一般通过gdb disassemble
readelf并不提供反汇编功能.
objdump -d,查看汇编
gdb主要功能的实现依赖于一个系统函数ptrace
gdb使用ptrace的基本流程
1、gdb调试一个新进程:通过fork函数创建一个新进程,
在子进程中执行ptrace(PTRACE_TRACEME, 0, 0, 0)函数,然后通过execv()调用准备调试的程序。
2、attach到已运行进程:将pid传递给gdb,然后执行ptrace(PTRACE_ATTACH, pid, 0, 0)
gdb 设置断点,向该地址写入断点指令INT3,即0xCC,发送SIGSTOP信号
gdb 单步调试,next指令时,会计算下一条语句对应的第一条指令的地址,然后控制目标程序走到该位置停止
3,看data和bss段内容
.data、.bss、.rodata都属于数据段。其中,
.data 存放已初始化的全局变量、常量、静态局部变量。
.bss 存放未初始化的全局变量,静态局部变量,所以此段数据均为0,仅作占位。
.rodata 是只读数据段,此段的数据不可修改,存放常量
static int x1 = 0; bss段
static int x2 = 12; data段
4,符号表是什么
查看ldd依赖项:objdump -x xxx.so | grep "NEEDED"
查看动态符号表: objdump -T xxx.so
查看符号表: objdump -t xxx.so
===OOM===
echo 0 > /proc/sys/vm/zone_reclaim_mode:意味着关闭zone_reclaim模式,可以从其他zone或NUMA节点回收内存
echo m > /proc/sysrq-trigger
lowmem_reserve[]: 0 1951 96891 96891
[38905.295396] Node 0 DMA: 1*4kB 2*8kB 0*16kB 1*32kB 2*64kB 0*128kB 1*256kB 0*512kB 1*1024kB 1*2048kB 3*4096kB = 15796kB
lowmem_reserve[]: 0 0 94940 94940
[38905.295405] Node 0 DMA32: 130*4kB 65*8kB 75*16kB 72*32kB 95*64kB 22*128kB 10*256kB 7*512kB 4*1024kB 2*2048kB 86*4096kB = 380032kB
lowmem_reserve[]: 0 0 0 0
[38905.295414] Node 0 Normal: 12544*4kB 68*8kB 0*16kB 0*32kB 0*64kB 0*128kB 0*256kB 0*512kB 0*1024kB 0*2048kB 1*4096kB = 54816kB
order:1, mode:0x20 可以看出来是GFP_ATOMIC类型的申请,且order = 1(page = 2 )
free pages = 380032KB = 95008 pages < low(1000KB = 250 pages) + lowmem_reserve[normal](94940) = 95190
对于normal自己内存来说,free pages = 56552 KB = 14138 pages,也不用考虑lowmem_reserve(0),
但这时还会考虑申请order(1),减去order 0的12544个page后只剩 14138 - 12544 = 1594,也小于 low / 2 = (48788KB=12197pages) / 2 = 6098 pages。
所以初次申请尝试失败,进入__alloc_pages_slowpath() 尝试进行更为积极一些的申请。
GFP_ATOMIC是直接返回,不会进行回收
direct reclaim会比较消耗时间的原因是,它在回收的时候不会去区分dirty page和clean page,
如果回收的是dirty page,就会触发磁盘IO的操作,它会首先把dirty page里面的内容给刷写到磁盘,
它一次性的会回收32个pages,再去把该page给放到freelist里。
open一个文件时,如果没有使用O_DIRECT这个flag,那就是File I/O, 所有对磁盘文件的访问都要经过内存,
内存会把这部分数据给缓存起来;但是如果使用了O_DIRECT这个flag,那就是Direct I/O, 它会绕过内存而去直接访问磁盘
第二次内存申请尝试
__alloc_pages_slowpath()首先是通过 gfp_to_alloc_flags() 修改alloc_pages,设上更为强硬的标志位。
这块根据原来的GFP_ATOMIC会设上 ALLOC_WMARK_MIN | ALLOC_HARDER | ALLOC_HIGH。但注意的是不会设上 ALLOC_NO_WATERMARKS 标志位。这个标志位不再判断zone的水位限制,属于优先级最高的申请,可以动用所有的reserve内存,
但条件是(!in_interrupt() && ((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE)))),
即要求不能在中断上下文,且是正在进行回收(例如kswapd)或者正在退出的进程。
之后进入拿着新的alloc_pages重新进入get_page_from_pagelist() 尝试第二次申请,
虽然有了 ALLOC_HARDER和ALLOC_HIGH,但是不幸的是在3个zone的zone_watermark_ok检查中还是都无法通过,
例如对于DMA32:free pages = 380032KB = 95008 pages
因为设上了ALLOC_HIGH 所以会将得到的watermark[min]减半,即min = min/2 = 800K / 2 = 400K = 100pages
而又因为设上了ALLOC_HARDER,会再将min砍去1/4,即min = 3 * min / 4 = 100 pages * 3 / 4 = 75 pages
即便如此,min(75 pages) + lowmem_reserve[normal](94940) = 95015,仍大于free pages,仍认为无法分配内存,
同理DMA也不不成功,而normal中 free pages里连续8K的页太少也无法满足分配
第二次失败后,由于没有ALLOC_NO_WATERMARK也不会进入__alloc_pages_high_priority 进行最高优先级的申请,
同时由于是GFP_ATOMIC类型的分配不能阻塞回收或者进入OOM,因此就以申请失败告终。
遇到此种情况可以适当调高 min_free_kbytes 使kswapd较早启动回收,使系统一直留有较多的空闲内存,
同时可以适度降低 lowmem_reserve_ratio(可选),使得内存不足的情况下(主要是normal zone)可以借用DMA32/DMA的内存救急(注意不能也不能过低)
===kdump====
kdump是一种先进的基于kexec的内核崩溃转储机制。当系统崩溃时,kdump使用kexec 启动到第二个内核。第二个内核通常叫做捕获内核,以很小内存启动以捕获转储镜像。
# 立即重新启动计算机
echo "b" > /proc/sysrq-trigger
# 立即关闭计算机
echo "o" > /proc/sysrq-trigger
# 导出内存分配的信息 (可以用/var/log/message 查看)
echo "m" > /proc/sysrq-trigger
# 导出当前CPU寄存器信息和标志位的信息
echo "p" > /proc/sysrq-trigger
# 导出线程状态信息
echo "t" > /proc/sysrq-trigger
# 故意让系统崩溃
echo "c" > /proc/sysrq-trigger
# 立即重新挂载所有的文件系统
echo "s" > /proc/sysrq-trigger
# 立即重新挂载所有的文件系统为只读
echo "u" > /proc/sysrq-trigger