1.Linux进程照妖镜strace命令
2.Linux神器strace的源码使用方法及实践
3.Spring Boot引起的“堆外内存泄漏”排查及经验总结
4.mmap的系统调用
5.记一次 MySQL5 初始化被 kill 的问题排查 | 京东云技术团队
6.Linux查看系统调用学习指南linux查看系统调用
Linux进程照妖镜strace命令
strace是强大的Linux调试分析工具,专用于跟踪程序执行时的设置系统调用和接收的信号。无需访问源代码,源码适用于不可读或无法重新编译的设置程序。系统调用发生在进程尝试访问硬件设备时,源码如读取磁盘文件或接收网络数据。设置hub指标源码strace则能记录此过程中的源码系统调用详情,包括参数、设置返回值和执行时间。源码
执行strace命令可揭示进程行为,设置但其无输出并不表示进程阻塞。源码它提供了一种观察程序与系统交互的设置方式。例如,源码对于简单的设置`getcwd`函数调用,strace能显示该函数如何获取当前路径,源码并将结果复制到指定缓冲区。同样,对于`write`函数,strace能追踪其如何处理输出内容。
通过逐步增加`printf`函数的使用,我们发现其系统调用数量实际上保持不变,这表明`printf`在连续打印时进行了优化,利用`mmap`函数执行内存拷贝,最后通过`write`函数输出缓冲区内容。实验中,添加换行符后`printf`调用次数增加至三次,这揭示了在遇到换行符时`printf`会刷新输出缓冲区,执行`write`函数将内容写入输出设备。
常用`strace`命令示例包括:跟踪指定命令的系统调用、跟踪特定进程的系统调用情况、统计指定进程的系统调用次数与用时,这些功能有助于深入理解程序运行时的行为,优化系统性能。
Linux神器strace的使用方法及实践
在Linux系统中,strace这个强大的工具如神器般实用,用于诊断、调试和统计程序运行,本文将详细介绍它的使用方法及实践案例。当程序运行异常或系统命令出错而难以通过常规手段定位问题时,台球联机unity源码strace就能派上用场。
当遇到操作系统运维中程序失败、报错信息无法揭示问题根源时,strace能够让我们在无需内核或代码的情况下,跟踪系统调用过程。它是一种不可或缺的诊断工具,系统管理员只需简单操作,即可在不查看源代码的情况下跟踪系统的调用。
strace的参数选项众多,如在CentOS/EulerOS和Ubuntu系统中安装,以及常用参数如 `-c` 用于统计系统调用时间、次数和错误次数,`-d` 显示调试输出,`-p` 根据进程ID追踪等。例如,通过`-e trace=open,close,read,write` 可以追踪ls命令中的文件系统调用,或者通过`-p`跟踪特定进程的系统活动。
以解决“无法解析域名”问题为例,我们可以通过strace命令查看系统在读取文件时的调用情况,如发现缺失了/lib/libnss_dns.so.2文件,说明问题可能出在相关库文件上。解决方法是安装glibc-devel包以获取缺失的文件。
通过strace,我们不仅能解决系统问题,还能深入了解系统的运作机制,提高运维效率。希望这些实例和参数帮助你更好地利用strace进行Linux系统调用的追踪和调试。
Spring Boot引起的“堆外内存泄漏”排查及经验总结
为了更好地实现对项目的管理,我们将组内一个项目迁移到MDP框架(基于Spring Boot),随后我们就发现系统会频繁报出Swap区域使用量过高的异常。笔者被叫去帮忙查看原因,发现配置了4G堆内内存,但是实际使用的物理内存竟然高达7G,确实不正常。JVM参数配置是“-XX:MetaspaceSize=M -XX:MaxMetaspaceSize=M -XX:+AlwaysPreTouch -XX:ReservedCodeCacheSize=m -XX:InitialCodeCacheSize=m, -Xssk -Xmx4g -Xms4g,-XX:+UseG1GC -XX:G1HeapRegionSize=4M”,实际使用的物理内存如下图所示:
使用Java层面的工具定位内存区域(堆内内存、Code区域或者使用unsafe.allocateMemory和DirectByteBuffer申请的堆外内存)。
笔者在项目中添加-XX:NativeMemoryTracking=detailJVM参数重启项目,阻庄指标源码使用命令jcmd pid VM.native_memory detail查看到的内存分布如下:
发现命令显示的committed的内存小于物理内存,因为jcmd命令显示的内存包含堆内内存、Code区域、通过unsafe.allocateMemory和DirectByteBuffer申请的内存,但是不包含其他Native Code(C代码)申请的堆外内存。所以猜测是使用Native Code申请内存所导致的问题。
为了防止误判,笔者使用了pmap查看内存分布,发现大量的M的地址;而这些地址空间不在jcmd命令所给出的地址空间里面,基本上就断定就是这些M的内存所导致。
使用系统层面的工具定位堆外内存。
因为已经基本上确定是Native Code所引起,而Java层面的工具不便于排查此类问题,只能使用系统层面的工具去定位问题。
首先,使用了gperftools去定位问题。
从上图可以看出:使用malloc申请的的内存最高到3G之后就释放了,之后始终维持在M-M。笔者第一反应是:难道Native Code中没有使用malloc申请,直接使用mmap/brk申请的?(gperftools原理就使用动态链接的方式替换了操作系统默认的内存分配器(glibc)。)
然后,使用strace去追踪系统调用。
因为使用gperftools没有追踪到这些内存,于是直接使用命令“strace -f -e"brk,mmap,munmap" -p pid”追踪向OS申请内存请求,但是并没有发现有可疑内存申请。
接着,使用GDB去dump可疑内存。
因为使用strace没有追踪到可疑内存申请;于是想着看看内存中的情况。就是直接使用命令gdp -pid pid进入GDB之后,然后使用命令dump memory mem.bin startAddress endAddressdump内存,其中startAddress和endAddress可以从/proc/pid/smaps中查找。然后使用strings mem.bin查看dump的内容,如下:
从内容上来看,像是解压后的JAR包信息。读取JAR包信息应该是在项目启动的时候,那么在项目启动之后使用strace作用就不是很大了。所以应该在项目启动的时候使用strace,而不是粉色佳人指标源码启动完成之后。
再次,项目启动时使用strace去追踪系统调用。
项目启动使用strace追踪系统调用,发现确实申请了很多M的内存空间,截图如下:
使用该mmap申请的地址空间在pmap对应如下:
最后,使用jstack去查看对应的线程。
因为strace命令中已经显示申请内存的线程ID。直接使用命令jstack pid去查看线程栈,找到对应的线程栈(注意进制和进制转换)如下:
这里基本上就可以看出问题来了:MCC(美团统一配置中心)使用了Reflections进行扫包,底层使用了Spring Boot去加载JAR。因为解压JAR使用Inflater类,需要用到堆外内存,然后使用Btrace去追踪这个类,栈如下:
然后查看使用MCC的地方,发现没有配置扫包路径,默认是扫描所有的包。于是修改代码,配置扫包路径,发布上线后内存问题解决。
为什么堆外内存没有释放掉呢?
虽然问题已经解决了,但是有几个疑问。带着疑问,直接看了一下 Spring Boot Loader那一块的源码。发现Spring Boot对Java JDK的InflaterInputStream进行了包装并且使用了Inflater,而Inflater本身用于解压JAR包的需要用到堆外内存。而包装之后的类ZipInflaterInputStream没有释放Inflater持有的堆外内存。于是以为找到了原因,立马向Spring Boot社区反馈了这个bug。但是反馈之后,就发现Inflater这个对象本身实现了finalize方法,在这个方法中有调用释放堆外内存的逻辑。也就是说Spring Boot依赖于GC释放堆外内存。
使用jmap查看堆内对象时,发现已经基本上没有Inflater这个对象了。于是就怀疑GC的时候,没有调用finalize。带着这样的怀疑,把Inflater进行包装在Spring Boot Loader里面替换成自己包装的网页打码源码Inflater,在finalize进行打点监控,结果finalize方法确实被调用了。于是又去看了Inflater对应的C代码,发现初始化的使用了malloc申请内存,end的时候也调用了free去释放内存。
此时,怀疑free的时候没有真正释放内存,便把Spring Boot包装的InflaterInputStream替换成Java JDK自带的,发现替换之后,内存问题也得以解决了。
再次看gperftools的内存分布情况,发现使用Spring Boot时,内存使用一直在增加,突然某个点内存使用下降了好多(使用量直接由3G降为M左右)。这个点应该就是GC引起的,内存应该释放了,但是在操作系统层面并没有看到内存变化,那是不是没有释放到操作系统,被内存分配器持有了呢?
继续探究,发现系统默认的内存分配器(glibc 2.版本)和使用gperftools内存地址分布差别很明显,2.5G地址使用smaps发现它是属于Native Stack。内存地址分布如下:
到此,基本上可以确定是内存分配器在捣鬼;搜索了一下glibc M,发现glibc从2.开始对每个线程引入内存池(位机器大小就是M内存),原文如下:
按照文中所说去修改MALLOC_ARENA_MAX环境变量,发现没什么效果。查看tcmalloc(gperftools使用的内存分配器)也使用了内存池方式。
为了验证是内存池搞的鬼,就简单写个不带内存池的内存分配器。使用命令gcc zjbmalloc.c -fPIC -shared -o zjbmalloc.so生成动态库,然后使用export LD_PRELOAD=zjbmalloc.so替换掉glibc的内存分配器。其中代码Demo如下:
通过在自定义分配器当中埋点可以发现实际申请的堆外内存始终在M-M之间,gperftools监控显示内存使用量也是在M-M左右。但是从操作系统角度来看进程占用的内存差别很大(这里只是监控堆外内存)。
使用不同分配器进行不同程度的扫包,占用的内存如下:
为什么自定义的malloc申请M,最终占用的物理内存在1.7G呢?因为自定义内存分配器采用的是mmap分配内存,mmap分配内存按需向上取整到整数个页,所以存在着巨大的空间浪费。通过监控发现最终申请的页面数目在k个左右,那实际上向系统申请的内存等于k * 4k(pagesize) = 2G。
为什么这个数据大于1.7G呢?因为操作系统采取的是延迟分配的方式,通过mmap向系统申请内存的时候,系统仅仅返回内存地址并没有分配真实的物理内存。只有在真正使用的时候,系统产生一个缺页中断,然后再分配实际的物理Page。
整个内存分配的流程如上图所示。MCC扫包的默认配置是扫描所有的JAR包。在扫描包的时候,Spring Boot不会主动去释放堆外内存,导致在扫描阶段,堆外内存占用量一直持续飙升。当发生GC的时候,Spring Boot依赖于finalize机制去释放了堆外内存;但是glibc为了性能考虑,并没有真正把内存归返到操作系统,而是留下来放入内存池了,导致应用层以为发生了“内存泄漏”。所以修改MCC的配置路径为特定的JAR包,问题解决。在发表这篇文章时,发现Spring Boot的最新版本(2.0.5.RELEASE)已经做了修改,在ZipInflaterInputStream主动释放了堆外内存不再依赖GC;所以Spring Boot升级到最新版本,这个问题也可以得到解决。
mmap的系统调用
1. 创建内存映射
mmap:进程创建匿名的内存映射,把内存的物理页映射到进程的虚拟地址空间。进程把文件映射到进程的虚拟地址空间,可以像访问内存一样访问文件,不需要调用系统调用read()/write()访问文件,从而避免用户模式和内核模式之间的切换,提高读写文件速度。两个进程针对同一个文件创建共享的内存映射,实现共享内存。
mumap:该调用在进程地址空间中解除一个映射关系,addr是调用mmap()时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误发生。
3. 设置虚拟内存区域的访问权限
mprotect:把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。 prot可以取以下几个值,并且可以用“|”将几个属性合起来使用: 1)PROT_READ:表示内存段内的内容可写; 2)PROT_WRITE:表示内存段内的内容可读; 3)PROT_EXEC:表示内存段中的内容可执行; 4)PROT_NONE:表示内存段中的内容根本没法访问。 需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
0. 查找mmap在内核中的系统调用函数 我现在用的内核版是4..,首先在应用层参考上面解析编写一个mmap使用代码,然后编译成程序,在使用strace工具跟踪其函数调用,可以发现mmap也是调用底层的mmap系统调用,然后我们寻找一下底层的带6个参数的mmap系统调用有哪些:
1.mmap的系统调用 x的位于arch/x/kernel/sys_x_.c文件,如下所示:
arm的位于arch/arm/kernel/sys.c文件,如下所示:
然后都是进入ksys_mmap_pgoff:
然后进入vm_mmap_pgoff:
我们讲解最重要的do_mmap_pgoff函数:
然后进入do_mmap:
do_mmap_pgoff这个函数主要做了两件事,get_unmapped_area获取未映射地址,mmap_region映射。 先看下get_unmapped_area ,他是先找到mm_struct的get_unmapped_area成员,再去执行他:
再看mmap_region的实现:
现在,我们看看匿名映射的函数shmem_zero_setup到底做了什么,其实匿名页实际也映射了文件,只是映射到了/dev/zero上,这样有个好处是,不需要对所有页面进行提前置0,只有当访问到某具体页面的时候才会申请一个0页。
其实说白了,mmap就是在进程mm中创建或者扩展一个vma映射到某个文件,而共享、私有、文件、匿名这些mmap所具有的属性是在哪里体现的呢?上面的源码在不断的设置一些标记位,这些标记位就决定了进程在访问这些内存时内核的行为,mmap仅负责创建一个映射而已。
记一次 MySQL5 初始化被 kill 的问题排查 | 京东云技术团队
近期,京东云技术团队在搭建MySQL5时遇到了容器被kill的问题。经过多次尝试,发现部分容器能正常启动提供服务,但有一些在初始化阶段被kill。问题日志显示,是MySQL在初始化时被kill。进一步排查发现,MySQL配置文件中存在一个8G的匿名内存,引发疑问。通过调整容器内存规格至G,成功启动MySQL,但内存使用情况异常。怀疑问题可能与内存配置相关,但调整配置后问题依旧,排除配置因素。
对比正常启动的机器,发现异常机器是近期添加的云舰系统。通过运维团队排查,发现异常机器与之前的CentOS系统宿主机配置有差异。最终发现可能是资源配置问题,修改open files限制后,问题解决。
问题分析阶段,通过strace日志观察到,MySQL在初始化时尝试分配G内存,被系统kill。分析MySQL源码发现,其在根据系统资源限制分配内存时,可能导致内存分配过多。此问题在MySQL8版本中得到修复。
结论指出,在Linux系统中,open files限制设置过高可能导致问题,建议合理控制该设置。参考文献为京东云开发者社区的文章。
Linux查看系统调用学习指南linux查看系统调用
Linux 是一种开放源代码的操作系统,是众多种操作系统的中最受欢迎的一种,在技术术语中,它也称为一种基于POSIX特性的混合内核操作系统。本文介绍了在Linux环境中查看系统调用的方法。
要在Linux环境下查看系统调用,第一步就是要下载strace,strace是一款用于分析和跟踪系统调用的工具,可以有效的检测出程序的行为。strace的安装非常的简单,只需要输入如下命令即可安装:
`sudo apt-get install strace`
安装完成后,可以通过strace工具查看系统调用,比如可以查看系统中某个应用程序所执行的操作。我们以检测Linux中命令行程序ls的行为为例,只需要输入如下命令即可:
`strace ls –alF`
使用strace可以追踪系统调用,比如它提供了追踪函数调用历史,寄存器和内存状态的功能,可以轻松地获取程序的具体详细执行状态;另外,它还可以实时获取系统的性能状况,可以帮助开发人员更好的调优和系统优化。
系统调用是在Linux操作系统中非常重要的,了解系统调用和控制可以为系统开发编程进程提供有效的参考指导,strace就是帮助查看和跟踪Linux系统调用的有力神器,掌握它的使用方法,就可以轻松查看系统的具体调用状态。
sysctl 参数防篡改 - 基于 ebpf 的实现 [一]
系统调用参数防篡改 - 基于ebpf的实现
本文基于内核代码版本5..0进行讨论。
ebpf能够修改某些函数的返回值,但仅限于允许错误注入的函数,这限制了其应用范围。系统tap能够作用于几乎任何函数,但由于内核API的不稳定,它在不同内核上可能无法运行。
ebpf的优势在于其与内核交互的API保持稳定,特别是用于“系统调用参数防篡改”的一组helper函数。在阅读代码实现时,发现中段插入了一个“BPF_CGROUP_RUN_PROG_SYSCTL”。
这一功能源于v5.2-rc1的commit,旨在限制容器对sysctl的错误写入,要求内核版本不低于5.2,且配置项包含“CONFIG_CGROUP_BPF”。
配套的4个helper函数记录在内核文档“Documentation/bpf/prog_cgroup_sysctl.rst”中,用于读取sysctl参数名称和值、在参数修改时获取写入的值以及覆盖准备写入的值。这些helper与内核原生路径中的过滤函数交互。
使用示例
通过Linux内核源码中的“tools/testing/selftests/bpf”目录下的测试用例可以学习ebpf的使用。在源码根目录下执行make命令编译。
针对sysctl部分,测试用例主体为“test_sysctl.c”,用于将ebpf程序加载至内核,并在对应的点位上附加。ebpf程序可以是直接以ebpf汇编语法写的,也可以是C文件编译成.o二进制文件的形式。
当判断为write操作时,返回0,内核源码中决定sysctl参数读写结果的点位返回“-EPERM”,使得修改不成功。
ebpf prog源文件中的“SEC”宏定义用于指示编译器将函数/变量放在特定的section中,便于用户态loader查找和解析。
为了实现加载和附加程序,使用了“sysctl_write_deny_prog.o”作为附加程序,类型为“BPF_CGROUP_SYSCTL”,方式为“BPF_F_ALLOW_OVERRIDE”。借助“fd”这样的整形数字,用户态程序可以深入内核态获取对应的结构体实例。
最终通过libbpf封装系统调用接口,用户态程序可以通过“bpf”系统调用入口与内核交互。使用strace工具可以追踪这一过程。
了解ebpf helper函数的使用,可以借助现成的工具进行学习,更多详情请参考后续文章。
2024-11-28 17:38
2024-11-28 17:05
2024-11-28 17:03
2024-11-28 15:33
2024-11-28 15:26
2024-11-28 15:11