当内核有内存泄漏的时候

内存泄露是一个很容易出现的问题,尤其是对于测试不太充分的代码。怎么判断出现内存泄露了呢?很简单,就跑一些简单的测试,等待足够长时间即可。内存总有耗尽的时候,这时候内核会触发OOM,根据oom_score选择一个进程杀掉。这种时候,多半是有问题了。

或者在某个进程运行的时候,看/proc/meminfo 观察空闲内存部分,一直下降多半是有问题了。这就确定有内存泄露了,可能是用户态进程泄露,也可能是内核泄露。可以有多种方法来分辨:

  • 观察meminfo中的slab项,如果这一项有异常的增长或者只增不减一路狂飙,那八成是内核漏出去了
  • OOM killer会根据oom score选择一个进程杀掉,假如选择了desktop Xserver等等,就说明找不到分更高的进城了,这几个进程nice值是很低的。这都能选上,那就是用户态进程没啥占内存特别高的进程了,差不多也能确定是内核泄露了。
  • OOM killer杀了进程之后,观察OOM打出来的内存信息,并没有收回来多少。内核的内存泄露,不管OOM选择的是哪个进程来杀,内存都收不回来的,系统使用起来依然觉得卡卡的,那就是内核泄露了。

现在确定了内核泄露的方法,接下来怎么找到泄漏点呢,下面会介绍几种调试的方法。在此之前,先简单实现一个有内存泄漏的内核模块。

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/list.h>
#include <linux/percpu.h>
#include <linux/fdtable.h>struct list_node {long header[25];struct list_head list;char name[25];
};static LIST_HEAD(test_list);/** Some very simple testing. This function needs to be extended for* proper testing.*/
static int __init module_init(void)
{struct list_node *list;int i;pr_info("This is test mode for memleak debug tools!\n");/*alloc */pr_info("kmalloc(32) = %p\n", kmalloc(32, GFP_KERNEL));pr_info("kmalloc(32) = %p\n", kmalloc(32, GFP_KERNEL));pr_info("kmalloc(1024) = %p\n", kmalloc(1024, GFP_KERNEL));pr_info("kmalloc(1024) = %p\n", kmalloc(1024, GFP_KERNEL));pr_info("kmalloc(2048) = %p\n", kmalloc(2048, GFP_KERNEL));pr_info("kmalloc(2048) = %p\n", kmalloc(2048, GFP_KERNEL));pr_info("kmalloc(4096) = %p\n", kmalloc(4096, GFP_KERNEL));pr_info("kmalloc(4096) = %p\n", kmalloc(4096, GFP_KERNEL));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));pr_info("vmalloc(64) = %p\n", vmalloc(64));/** create a list have 10 nodes*/for (i = 0; i < 10; i++) {list = kzalloc(sizeof(*list), GFP_KERNEL);pr_info("kzalloc(sizeof(*list)) = %p\n", list);if (!list)return -ENOMEM;INIT_LIST_HEAD(&list->list);list_add_tail(&list->list, &test_list);}return 0;
}
module_init(module_init);static void __exit module_exit(void)
{struct test_node *list, *tmp;//delete all node in testlist_for_each_entry_safe(list, tmp, &test_list, list)list_del(&list->list);
}
module_exit(module_exit);MODULE_LICENSE("GPL");

kmemleak

第一个debug的方法也是最简单的——使用Kmemleak,这是一种用于检测内核内存泄漏的工具,可以用来检测目前内核中可能存在的泄露。kmemleak 是一种运行时的工具,它可以在内核运行时进行内存泄漏检测,但也会带来一定的性能开销。因此,通常情况下发行版并不会打开此工具。

Kmemleak使用

  • 先打开Kmemleak的相关配置:
CONFIG_DEBUG_KMEMLEAK           //kmemleak总开关
CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF              //是不是默认禁用kmemleak,非必须;打开选项之后,可以通过cmdline kmemleak=on 打开kmemleak;关闭这个选项,也可以通过cmdline kmemleak=off禁用
  • 编译安装内核
    能看到/sys/kernel/debug/kmemleak这个文件,说明kmemleak已经配置好了。

  • make 并insmod写好的test模块

  • 触发内存scan
    模块插入后等几分钟执行echo scan > /sys/kernel/debug/kmemleak触发一次内存scan去找可能存在的内存泄露,这是整个内存的scan,可能需要点时间。

  • 查看结果
    通过cat /sys/kernel/debug/kmemleak查看内存泄漏的结果,这时候可以下面的结果:

# cat /sys/kernel/debug/kmemleak
unreferenced object 0xffff89862ca702e8 (size 32):
comm "modprobe", pid 2088, jiffies 4294680594 (age 375.486s)hex dump (first 32 bytes):6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b  kkkkkkkkkkkkkkkk6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b 6b a5  kkkkkkkkkkkkkkk.backtrace:[<00000000e0a73ec7>] 0xffffffffc01d3446[<000000000c5d2a46>] do_one_initcall+0x41/0x1df[<0000000046db7e0a>] do_init_module+0x55/0x200[<00000000542b9814>] load_module+0x203c/0x2480[<00000000c2850256>] __do_sys_finit_module+0xba/0xe0[<000000006564e7ef>] do_syscall_64+0x43/0x110[<000000007c873fa6>] entry_SYSCALL_64_after_hwframe+0x44/0xa9

calltrace最上面一行就是执行malloc的位置,可以根据addr2line或者gdb把地址和代码对应起来。是不是超级简单,超级容易。打开选项重编一个内核,就知道所有可能泄漏的位置。

为什么说可能存在的泄露位置呢,因为Kmemleak可能会存在误报的情况。

kmemleak误报

kmemleak工作流程大概分为三部分:标记分配、检测释放和报告泄露。

  1. 标记分配的内存:kmemleak 会跟踪内核中的动态内存分配,包括 kmalloc、kzalloc、vmalloc 等函数分配的内存块。标记这些分配的内存块,并记录它们的地址和大小。

  2. 检测释放:当内存块被释放时,kmemleak 会记录释放的内存块,并在一段时间后检查是否仍然存在对这些内存块的引用。

  3. 报告泄漏:扫描内存并报告这些内存泄漏,扫描方法如下:

    • 标记所有对象为白色:初始时,kmemleak 会将所有的内存对象标记为白色。在后续的扫描中,仍然被标记为白色的对象将被视为孤立对象(orphan)。

    • 扫描内存:从数据段和栈开始扫描内存,检查其中的数值是否与存储在红黑树(rbtree)中的地址相匹配。如果发现指向白色对象的指针,则将该对象添加到灰色列表中。

    • 扫描灰色对象:对灰色对象进行扫描,查找匹配的地址。一些白色对象可能会变为灰色,并被添加到灰色列表的末尾,直到灰色集合完成扫描。

    • 报告孤立对象:剩余的白色对象被视为孤立对象,并通过 /sys/kernel/debug/kmemleak 报告。

内核中本身有一些内存是不需要释放的,比如说核心相关的部分do_init_call函数,或者此块内存地址可以通过其他的什么方法计算得到,这样内存也不会有引用技术,但是kmemleak会把这块内存当泄露处理。

虽然会误报,但是绝大部分情况是非常非常好用的!几乎一切内存泄露都逃不过kmemleak的眼睛,慧眼如炬!

然而接下来来了一个这样的场景:在生产服务器上可能出现了内存泄露或者根本拿不到内核代码的os上出现了内存泄露,这下怎么办呢,Kmemleak是绝对不会在任何一个非debug发行版上开启的,这就引入了第二个方式——如何在不修改内核代码的情况下拿到内核运行的相关内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/186205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【驱动】SPI驱动分析(六)-RK SPI驱动分析

前言 Linux的spi接口驱动实现目录在kernel\drivers\spi下。这个目录和一些层次比较明显的驱动目录布局不同&#xff0c;全放在这个文件夹下&#xff0c;因此还是只好通过看Kconfig 和 Makefile来找找思路 先看Makefile&#xff0c;里面关键几行&#xff1a; obj-$(CONFIG_SPI…

【C++ Primer Plus学习记录】循环和文本输入

目录 1.使用原始的cin进行输入 2.使用cin.get(char)进行补救 3.使用哪一个cin.get() 4.文件尾条件 循环完后的一项最常见、最重要的任务&#xff1a;逐字符地读取来自文件或键盘的文本。 cin对象支持3种不同模式的单字符输入&#xff0c;其用户接口各不相同。下面介绍如何…

形态学操作—底帽运算

底帽运算&#xff08;Bottom-hat transformation&#xff09;&#xff0c;也称为黑帽运算&#xff0c;是形态学图像处理中的一种操作。它与顶帽运算相反&#xff0c;通过闭运算的结果与原始图像的差异来突出图像中的暗区域特征。 原理 底帽运算通过对图像执行闭运算&#xff0…

代码随想录二刷 |字符串 |反转字符串

代码随想录二刷 &#xff5c;字符串 &#xff5c;反转字符串 题目描述解题思路 & 代码实现 题目描述 344.反转字符串 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。 不要给另外的数组分配额外的空间&#xff0c;你必须…

鸿蒙HarmonyOS应用开发-ColumnRow组件

1 概述 一个丰富的页面需要很多组件组成&#xff0c;那么&#xff0c;我们如何才能让这些组件有条不紊地在页面上布局呢&#xff1f;这就需要借助容器组件来实现。 容器组件是一种比较特殊的组件&#xff0c;它可以包含其他的组件&#xff0c;而且按照一定的规律布局&#xf…

UE5富文本框学习(用途:A(名字)用刀(图片)击杀B(名字))

UE5-UMG教程-通用控件&#xff1a;多格式文本块&#xff08;RichTextBlock&#xff09;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Pu4y1k7Z2/?p54&spm_id_frompageDriver 结果示例&#xff1a; 1.添加富文本框 2.添加文字样式库 点添加&#xff0c;更改每行行…

Embedding压缩之hash embedding

在之前的两篇文章 CTR特征重要性建模&#xff1a;FiBiNet&FiBiNet模型、CTR特征建模&#xff1a;ContextNet & MaskNet中&#xff0c;阐述了特征建模的重要性&#xff0c;并且介绍了一些微博在特征建模方面的研究实践&#xff0c;再次以下面这张图引出今天的主题&#…

相比Javascript, Typescript有哪些优点?

TypeScript 是 JavaScript 的超集&#xff0c;它在 JavaScript 的基础上添加了静态类型和其他一些功能。以下是 TypeScript 相对于 JavaScript 的一些优点&#xff1a; 1、静态类型检查&#xff1a; TypeScript 引入了静态类型&#xff0c;可以在编译时发现并纠正类型错误。这…

linux chown 命令详解

linux chown 命令详解 一、更改文件或目录的所有者和/或所属组二、更改用户权限三、chown与chmod的区别 一、更改文件或目录的所有者和/或所属组 它的基本语法如下&#xff1a; chown [选项]... [所有者][:[所属组]] 文件...其中&#xff0c;选项可以是-R&#xff08;递归更改…

Unity 一些常用注解

在Unity中有一些比较常用的注解&#xff1a; 1、[SerializeField]&#xff1a;将私有字段或属性显示在 Unity 编辑器中&#xff0c;使其可以在 Inspector 窗口中进行编辑。 2、[Range(min, max)]&#xff1a;限制数值字段或属性的范围&#xff0c;在 Inspector 窗口中以滑动条…

glFenceSync

glClientWaitSync是OpenGL中用于等待同步对象状态变化的函数&#xff0c;它可以用于在CPU端等待GPU端的某个操作完成。下面是glClientWaitSync相关的函数以及一个简单的例子&#xff1a; glFenceSync&#xff1a;创建一个同步对象。 GLsync glFenceSync(GLenum condition, GLbi…

简单好用!日常写给 ChatGPT 的几个提示词技巧

ChatGPT 很强&#xff0c;但是有时候又显得很蠢&#xff0c;下面是使用 GPT4 的一个实例&#xff1a; 技巧一&#xff1a;三重冒号 """ 引用内容使用三重冒号 """&#xff0c;让 ChatGPT 清晰引用的内容&#xff1a; 技巧二&#xff1a;角色设定…

【C++】string模拟

string讲解&#xff1a;【C】String类-CSDN博客 基本框架 #pragma once #include <iostream> using namespace std; ​ namespace wzf {class string{public:// 默认构造函数string(): _str(new char[1]), _size(0), _capacity(0){_str[0] \0; // 在没有内容时仍要有终…

手把手教你使用NVIDIA Isaac Sim进行机器人仿真,通过stl模型转为isaac sim支持的格式

很好的一篇文章 手把手教你使用NVIDIA Isaac Sim进行机器人仿真① 讲解了通过stl模型转为isaac sim支持的格式 并且使车辆动起来了 手把手教你使用NVIDIA Isaac Sim进行机器人仿真① https://zhuanlan.zhihu.com/p/590860384 仿照这个过程&#xff0c;后续做一个无人机飞行的场…

计算机体系结构----基本概念(一)

本文仅供学习&#xff0c;不作任何商业用途&#xff0c;严禁转载。绝大部分资料来自----计算机系统结构教程(第二版)张晨曦等 计算机体系结构----基本概念 计算机系统设计的定量原理1. Amdahl定律&#xff08;阿姆达尔定律&#xff09;2. CPU性能公式3. 程序的局部性原理 计算…

计算机组成原理期中题库

计算机组成原理题目集 2.1 下面是关于计算机中存储器容量单位的叙述&#xff0c;其中错误的是 A. 最基本的计量单位是字节&#xff08;Byte&#xff09;&#xff0c;一个字节等于8bit B. 一台计算机的编址单位、指令字长和数据字长都一样&#xff0c;且是字节的整数倍 C. 最小…

C++ 学习之函数成员指针的一个小细节

看看下面的代码&#xff0c;你能看出错误吗 class A { public:void fun(){}}; int main() {A a;void (A:: * p)() &A::fun;(*p)(); } 这段代码在调用成员函数时存在问题。正确的方式是使用对象来调用成员函数&#xff0c;而不是通过指针。以下是修正后的代码&#xff1a…

【JavaScript】表单校验

input中允许输入年或年月或年月&#xff0c;即 2023 或 2023-09 或 2023-09-11 pattern:/(^[1-2][0-9][0-9][0-9]-([1][0-2]|0?[1-9])-([12][0-9]|3[01]|0?[1-9])$)|(^[1-2][0-9][0-9][0-9]-([1][0-2]|0?[1-9])$)|(^[1-2][0-9][0-9][0-9]$)/只允许填入英文,数字,下划线和 - …

【Lustre相关】应用部署-02-Lustre软件源码编译

文章目录 一、前言二、编译说明1、配置yum源2、升级内核3、安装依赖3、源码编译 三、Q&A1、/usr/bin/ld: cannot find -lxxxa、问题说明b、原因分析c、解决措施 2、Deprecated feature: REMAKE_INITRDa、问题说明b、解决措施 3、源码编译安装lustre-zfs软件包后&#xff0c…

JAVA基础进阶(十一)

一、创建线程的三种方式 Java语言中是用Thread类来表示线程&#xff0c;线程的创建和开启都是通过Thread类来实现的。 继承Thread类重写run方法。 调用线程对象的start()方法启动线程&#xff08;启动后还是执行run方法的&#xff09;,而不是调用创建的子类对象的run()方法。…