目录

Linux内核内存管理第3部分。

Linux内核中的kmemcheck简介

该kmemcheck机制在Linux内核中的实现

结论

链接


读原文:《Linux内存管理:kmemcheck介绍》

Linux内核内存管理第3部分。

Linux内核中的kmemcheck简介

这是本章的第三部分,描述了Linux内核中的内存管理,在本章的前一部分中,我们遇到了两个与内存管理相关的概念:

  • Fix-Mapped Addresses;
  • ioremap

第一个概念表示虚拟内存中的特殊区域,其相应的物理映射是在编译时计算的。第二个概念提供了将与输入/输出相关的内存映射到虚拟内存的功能。

例如,如果您将查看的输出/proc/iomem

$ sudo cat /proc/iomem00000000-00000fff : reserved
00001000-0009d7ff : System RAM
0009d800-0009ffff : reserved
000a0000-000bffff : PCI Bus 0000:00
000c0000-000cffff : Video ROM
000d0000-000d3fff : PCI Bus 0000:00
000d4000-000d7fff : PCI Bus 0000:00
000d8000-000dbfff : PCI Bus 0000:00
000dc000-000dffff : PCI Bus 0000:00
000e0000-000fffff : reserved
...
...
...

您将看到每个物理设备的系统内存映射。在这里,第一列显示了每种不同类型的内存使用的内存寄存器。第二列列出了位于这些寄存器内的存储器的类型。或例如:

$ sudo cat /proc/ioports0000-0cf7 : PCI Bus 0000:000000-001f : dma10020-0021 : pic10040-0043 : timer00050-0053 : timer10060-0060 : keyboard0064-0064 : keyboard0070-0077 : rtc00080-008f : dma page reg00a0-00a1 : pic200c0-00df : dma200f0-00ff : fpu00f0-00f0 : PNP0C04:0003c0-03df : vga+03f8-03ff : serial04d0-04d1 : pnp 00:060800-087f : pnp 00:010a00-0a0f : pnp 00:040a20-0a2f : pnp 00:040a30-0a3f : pnp 00:04
...
...
...

可以向我们显示用于与设备进行输入或输出通信的当前注册端口区域的列表。内核不直接使用所有内存映射的I / O地址。因此,在Linux内核可以使用此类内存之前,它必须将其映射到虚拟内存空间,这是该ioremap机制的主要目的。请注意,我们仅ioremap在上一部分的早期看到过。很快,我们将研究非早期ioremap功能的实现。但是在此之前,我们必须学习其他东西,例如不同类型的内存分配器等,因为以其他方式很难理解它。

因此,在继续进行Linux内核的非早期内存管理之前,我们将看到一些机制,这些机制为调试,内存泄漏检查,内存控制等提供了特殊的功能。将更容易理解内存管理的方式。在学习了所有这些内容之后,将它们安排在Linux内核中。

正如您可能已经从本部分的标题中猜到的那样,我们将开始考虑kmemcheck中的内存机制。正如我们在其他章节中经常做的那样,我们将从理论上开始考虑,并了解kmemcheck一般的机制,然后,我们将了解如何在Linux内核中实现它。

因此,让我们开始吧。这是什么kmemcheck在Linux内核?您可能从此机制的名称中猜到了,kmemcheck检查内存。确实如此。该kmemcheck机制的要点是检查某些内核代码是否已访问uninitialized memory。让我们来看下面的简单C程序:

#include <stdlib.h>
#include <stdio.h>struct A {int a;
};int main(int argc, char **argv) {struct A *a = malloc(sizeof(struct A));printf("a->a = %d\n", a->a);return 0;
}

在这里,我们为A结构分配内存,并尝试打印该a字段的值。如果我们将编译该程序而没有其他选项:

gcc test.c -o test

该编译器不会显示我们警告说,a申请不未初始化。但是,如果我们将使用valgrind工具运行该程序,则会看到以下输出:

~$   valgrind --leak-check=yes ./test
==28469== Memcheck, a memory error detector
==28469== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28469== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28469== Command: ./test
==28469==
==28469== Conditional jump or move depends on uninitialised value(s)
==28469==    at 0x4E820EA: vfprintf (in /usr/lib64/libc-2.22.so)
==28469==    by 0x4E88D48: printf (in /usr/lib64/libc-2.22.so)
==28469==    by 0x4005B9: main (in /home/alex/test)
==28469==
==28469== Use of uninitialised value of size 8
==28469==    at 0x4E7E0BB: _itoa_word (in /usr/lib64/libc-2.22.so)
==28469==    by 0x4E8262F: vfprintf (in /usr/lib64/libc-2.22.so)
==28469==    by 0x4E88D48: printf (in /usr/lib64/libc-2.22.so)
==28469==    by 0x4005B9: main (in /home/alex/test)
...
...
...

实际上,该kmemcheck机制对内核的作用valgrind与对用户空间程序的作用相同。它检查未初始化的内存。

要在Linux内核中启用此机制,您需要在以下位置启用CONFIG_KMEMCHECK内核配置选项:

Kernel hacking-> Memory Debugging

Linux内核配置菜单:

我们不仅可以kmemcheck在Linux内核中启用对该机制的支持,还可以为我们提供一些配置选项。我们将在本部分的下一段中看到所有这些选项。在我们将考虑如何kmemcheck检查内存之前的最后一个注意事项。现在,仅针对x86_64体系结构实现此机制。您可以确定是否查看与arch / x86 / Kconfig x86相关的内核配置文件,您将看到以下几行:

config X86.........select HAVE_ARCH_KMEMCHECK.........

因此,没有其他特定于任何体系结构的东西。

好的,所以我们知道它kmemcheck提供了检查uninitialized memoryLinux内核使用情况以及如何启用它的机制。这些检查如何进行?当Linux内核尝试分配一些内存时,即像这样被称为:

struct my_struct *my_struct = kmalloc(sizeof(struct my_struct), GFP_KERNEL);

或者换句话说,有人要访问页面,则会生成页面错误异常。这是通过将kmemcheck内存页面标记为non-present(这一点的更多信息,您可以在专用于Paging的特殊部分中阅读)来实现。如果page fault发生异常,则异常处理程序会知道该异常,并且在kmemcheck启用的情况下,它将控制权转移给该异常处理程序。之后,kmemcheck将完成它的检查,该页面将被标记为present与被中断的代码就可以继续执行。在这条链上几乎没有什么微妙之处。当执行中断代码的第一条指令时,kmemcheck会将页面标记为non-present再次。这样,将再次捕获对内存的下一次访问。

我们只是kmemcheck从理论角度考虑了这一机制。现在让我们考虑如何在Linux内核中实现它。

kmemcheck机制在Linux内核中的实现

因此,现在我们知道了它是什么kmemcheck以及它在Linux内核中的作用。是时候看看它在Linux内核中的实现了。的实现kmemcheck分为两部分。第一个是通用部分,位于mm / kmemcheck.c源代码文件中,第二个x86_64特定于体系结构的部分位于arch / x86 / mm / kmemcheck目录中。

让我们从这种机制的初始化开始。我们已经知道要kmemcheck在Linux内核中启用该机制,我们必须启用CONFIG_KMEMCHECK内核配置选项。但是除此之外,我们还需要传递以下参数之一:

  • kmemcheck = 0(禁用)
  • kmemcheck = 1(启用)
  • kmemcheck = 2(单次模式)

到Linux内核命令行。前两个很明确,但最后一个需要一些解释。kmemcheck在检测到首次使用未初始化的内存后将关闭该选项时,它将在特殊模式下切换。实际上,默认情况下在Linux内核中启用此模式:

我们知道,从第七部分的章节描述了Linux内核,在内核初始化过程中内核命令行被解析的初始化do_initcall_leveldo_early_param功能。实际上,kmemcheck子系统由两个阶段组成。第一阶段还早。如果我们查看mm / kmemcheck.c源代码文件,我们将看到param_kmemcheck在早期命令行解析期间将调用的函数:

static int __init param_kmemcheck(char *str)
{int val;int ret;if (!str)return -EINVAL;ret = kstrtoint(str, 0, &val);if (ret)return ret;kmemcheck_enabled = val;return 0;
}early_param("kmemcheck", param_kmemcheck);

正如我们已经看到的,param_kmemcheck可能具有以下值之一:(0启用),1(禁用)或2(一次性)。的实现param_kmemcheck非常简单。我们只是将kmemcheck命令行选项的字符串值转换为整数表示形式并将其设置为kmemcheck_enabled变量。

第二阶段将在Linux内核初始化期间执行,而不是在早期initcall初始化期间执行。第二阶段由以下内容表示kmemcheck_init

int __init kmemcheck_init(void)
{.........
}early_initcall(kmemcheck_init);

kmemcheck_init函数的主要目标是调用该kmemcheck_selftest函数并检查其结果:

if (!kmemcheck_selftest()) {printk(KERN_INFO "kmemcheck: self-tests failed; disabling\n");kmemcheck_enabled = 0;return -EINVAL;
}printk(KERN_INFO "kmemcheck: Initialized\n");

EINVAL如果此检查失败,则返回。在kmemcheck_selftest不同的存储器存取有关的功能检查大小的操作码一样rep movsbmovzwq等,如果操作码的大小等于预期大小时,kmemcheck_selftest将返回truefalse其他方式。

因此,当有人打电话给您时:

struct my_struct *my_struct = kmalloc(sizeof(struct my_struct), GFP_KERNEL);

通过一系列不同的函数调用,该kmem_getpages函数将被调用。此功能在mm / slab.c源代码文件中定义,并且此功能的主要目标是尝试分配具有给定标志的页面。在此函数的结尾,我们可以看到以下代码:

if (kmemcheck_enabled && !(cachep->flags & SLAB_NOTRACK)) {kmemcheck_alloc_shadow(page, cachep->gfporder, flags, nodeid);if (cachep->ctor)kmemcheck_mark_uninitialized_pages(page, nr_pages);elsekmemcheck_mark_unallocated_pages(page, nr_pages);
}

因此,在这里,我们检查是否kmemcheck启用了if并且SLAB_NOTRACK未在我们non-present为刚分配的页面设置位的标志中设置该位。该SLAB_NOTRACK位告诉我们不要跟踪未初始化的内存。另外,我们检查缓存对象是否具有构造函数(详细信息将在接下来的部分中考虑),然后将分配的页面标记为未初始化或以其他方式未分配。该kmemcheck_alloc_shadow函数在mm / kmemcheck.c源代码文件中定义,并执行以下操作:

void kmemcheck_alloc_shadow(struct page *page, int order, gfp_t flags, int node)
{struct page *shadow;shadow = alloc_pages_node(node, flags | __GFP_NOTRACK, order);for(i = 0; i < pages; ++i)page[i].shadow = page_address(&shadow[i]);kmemcheck_hide_pages(page, pages);
}

首先,它为影子位分配存储空间。如果在页面中设置了该位,则表示该页面被跟踪kmemcheck。在为阴影位分配空间之后,我们用该位填充所有分配的页面。最后,我们仅kmemcheck_hide_pages使用指向已分配页面的指针和这些页面的编号来调用该函数。的kmemcheck_hide_pages是特定体系结构的功能,所以其执行位于拱/ 86 /毫米/ kmemcheck / kmemcheck.c源代码文件。该功能的主要目标是non-present在给定的页面中设置位。让我们看一下该函数的实现:

void kmemcheck_hide_pages(struct page *p, unsigned int n)
{unsigned int i;for (i = 0; i < n; ++i) {unsigned long address;pte_t *pte;unsigned int level;address = (unsigned long) page_address(&p[i]);pte = lookup_address(address, &level);BUG_ON(!pte);BUG_ON(level != PG_LEVEL_4K);set_pte(pte, __pte(pte_val(*pte) & ~_PAGE_PRESENT));set_pte(pte, __pte(pte_val(*pte) | _PAGE_HIDDEN));__flush_tlb_one(address);}
}

在这里,我们浏览所有页面并尝试获取page table entry每个页面。如果此操作成功,我们将在每个页面中取消设置当前位并设置隐藏位。最后,我们刷新了转换后备缓冲区,因为某些页面已更改。从这一点开始,已分配的页面将由跟踪kmemcheck。现在,由于present未设置该位,因此将在将指针返回到分配的空间之后立即执行页面错误,kmalloc并且代码将尝试访问该内存。

您可能还记得Linux内核初始化章节的第二部分,该page fault处理程序位于arch / x86 / mm / fault.c源代码文件中,并由do_page_fault函数表示。从do_page_fault函数开始我们可以看到以下检查:

static noinline void
__do_page_fault(struct pt_regs *regs, unsigned long error_code,unsigned long address)
{.........if (kmemcheck_active(regs))kmemcheck_hide(regs);.........
}

kmemcheck_active得到kmemcheck_context 每个CPU的结构和返回的比较结果balance这种结构具有零的领域:

bool kmemcheck_active(struct pt_regs *regs)
{struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);return data->balance > 0;
}

kmemcheck_context是结构描述了的当前状态kmemcheck的机制。它存储了未初始化的地址,此类地址的数量等balance。此结构的字段表示的当前状态,kmemcheck或者换句话说,它可以告诉我们是否kmemcheck已隐藏页面。如果data->balance大于零,kmemcheck_hide则将调用该函数。这意味着kmemecheck已经present为给定页面设置了位,现在我们需要再次隐藏页面以引起下一步页面错误。该功能将通过取消设置present位来再次隐藏页面地址。这意味着发生了一个kmemcheck已经完成和新页面错误的会话。第一步,kmemcheck_active将返回false,因为data->balance开始时的为零,并且kmemcheck_hide不会被调用。接下来,我们可能会在中看到以下代码行do_page_fault

if (kmemcheck_fault(regs, address, error_code))return;

首先,该kmemcheck_fault功能检查故障是由正确的原因引起的。首先,我们检查标志寄存器并检查我们是否处于正常内核模式:

if (regs->flags & X86_VM_MASK)return false;
if (regs->cs != __KERNEL_CS)return false;

如果这些检查不成功,则我们从kmemcheck_fault函数返回,因为它kmemcheck与页面错误无关。此后,我们尝试查找page table entry与错误地址相关的,如果找不到,则返回:

pte = kmemcheck_pte_lookup(address);
if (!pte)return false;

kmemcheck_fault函数的最后两个步骤是调用该kmemcheck_access函数,该函数检查对给定页面的访问,并通过在给定页面中设置当前位来再次显示地址。该kmemcheck_access功能完成所有主要工作。它检查导致页面错误的当前指令。如果发现错误,则此错误的上下文将保存kmemcheck到环形队列中:

static struct kmemcheck_error error_fifo[CONFIG_KMEMCHECK_QUEUE_SIZE];

kmemcheck机制声明特殊的tasklet:

static DECLARE_TASKLET(kmemcheck_tasklet, &do_wakeup, 0);

计划在计划运行时do_wakeup从arch / x86 / mm / kmemcheck / error.c源代码文件运行该功能。

do_wakeup函数将调用该kmemcheck_error_recall函数,该函数将打印由收集的错误kmemcheck。正如我们已经看到的:

kmemcheck_show(regs);

函数将在kmemcheck_fault函数末尾被调用。此功能将再次设置给定页面的当前位:

if (unlikely(data->balance != 0)) {kmemcheck_show_all();kmemcheck_error_save_bug(regs);data->balance = 0;return;
}

kmemcheck_show_all函数调用kmemcheck_show_addr每个地址:

static unsigned int kmemcheck_show_all(void)
{struct kmemcheck_context *data = this_cpu_ptr(&kmemcheck_context);unsigned int i;unsigned int n;n = 0;for (i = 0; i < data->n_addrs; ++i)n += kmemcheck_show_addr(data->addr[i]);return n;
}

通过kmemcheck_show_addr

int kmemcheck_show_addr(unsigned long address)
{pte_t *pte;pte = kmemcheck_pte_lookup(address);if (!pte)return 0;set_pte(pte, __pte(pte_val(*pte) | _PAGE_PRESENT));__flush_tlb_one(address);return 1;
}

kmemcheck_show函数的最后,如果未设置TF标志,则设置它:

if (!(regs->flags & X86_EFLAGS_TF))data->flags = regs->flags;

我们需要这样做,因为在处理页面错误之后,我们需要在第一次执行指令后再次隐藏页面。在有TF标志的情况下,因此处理器将在执行第一条指令后切换到单步模式。在这种情况下,debug将会发生异常。从这一刻起,页面将再次被隐藏,并且将继续执行。由于此时隐藏了页面,因此将再次出现页面错误异常,并kmemcheck继续检查/收集错误并不时打印它们。

就这样。

结论

这是有关Linux内核内存管理的第三部分的结尾。如果您有任何疑问或建议,请在twitter 0xAX上ping我,给我发送电子邮件或创建问题。在下一部分中,我们将看到另一个与内存调试相关的工具- kmemleak

请注意,英语不是我的母语,对于给您带来的不便,我深表歉意。如果发现任何错误,请将PR发送给我linux-insides。

链接

  • memory management
  • debugging
  • memory leaks
  • kmemcheck documentation
  • valgrind
  • Paging
  • page fault
  • initcalls
  • opcode
  • translation lookaside buffer
  • per-cpu variables
  • flags register
  • tasklet
  • Previous part

Linux内存管理:kmemcheck介绍相关推荐

  1. linux 内存管理 ppt,Linux内存管理 Memory Manager.ppt

    <Linux内存管理 Memory Manager.ppt>由会员分享,可在线阅读,更多相关<Linux内存管理 Memory Manager.ppt(24页珍藏版)>请在人人 ...

  2. Linux内存管理之基本概念介绍(一)

    Linux内存管理之基本概念介绍(一) Linux内存管理之物理内存管理(二) Linux内存管理之内存管理单元(MMU)(三) Linux内存管理之分配掩码(四) Linux内存管理之伙伴系统(五) ...

  3. 伙伴系统之伙伴系统概述--Linux内存管理(十五)

    日期 内核版本 架构 作者 GitHub CSDN 2016-09-02 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 博文 ...

  4. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

  5. 万字长文,别再说你不懂Linux内存管理了(合辑),30 张图给你安排的明明白白...

    之前写了两篇详细分析 Linux 内存管理的文章,读者好评如潮.但由于是分开两篇来写,而这两篇内容其实是有很强关联的,有读者反馈没有看到另一篇读起来不够不连贯,为方便阅读这次特意把两篇整合在一起,看这 ...

  6. Linux内存管理原理【转】

    转自:http://www.cnblogs.com/zhaoyl/p/3695517.html 本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址 ...

  7. Linux内存管理【转】

    转自:http://www.cnblogs.com/wuchanming/p/4360264.html 转载:http://www.kerneltravel.net/journal/v/mem.htm ...

  8. Linux内存管理原理

    本文以32位机器为准,串讲一些内存管理的知识点. 1. 虚拟地址.物理地址.逻辑地址.线性地址 虚拟地址又叫线性地址.linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻 ...

  9. Linux内存管理 (2)页表的映射过程

    专题:Linux内存管理专题 关键词:swapper_pd_dir.ARM PGD/PTE.Linux PGD/PTE.pgd_offset_k. Linux下的页表映射分为两种,一是Linux自身的 ...

最新文章

  1. convertViewsetTag方法的一点理解
  2. 例题;假设一张纸厚度为0.001米,对折多少次高度可以超过珠峰高度(8848)米。一个篮球第二次弹起的高度是第一次弹起高度的2/3,问弹起n次以后的高度是多少。 主要学习for循环...
  3. 【跟网上的大多数不一样】rstudio plot不显示图片了
  4. 单调队列板子:求滑动窗口中最大值和最小值
  5. Golang管道channel:管道的声明 读取 写入
  6. std::tostring_枚举:如何正确使用name()和toString()方法
  7. 立体匹配十大概念综述---立体匹配算法介绍
  8. Prim和Kruskal求最小生成树
  9. VBA读取html表格内容,科学网—VBA读取word文档表格中table的cell的text文本 - 付安民的博文...
  10. java 私有成员方法_Java Reflection 教程(7):类私有成员变量和方法
  11. 项目实战之服务端分页的实现——SQL
  12. 批处理命令——for
  13. 复杂网络学习的一些常用数据集
  14. 计算机组成原理学习-哈工大《计算机组成原理》第一章
  15. java创建线程的三种方式及其对照
  16. 英语词性的分类及用法详述
  17. 推销计算机英语作文,2018年12月英语四级作文范文:卖电脑
  18. 出口美国的电动自行车UL2849测试和GCC检测标准
  19. 使用WinDbg搭建edk2 DEBUG环境
  20. lisp画弯箭头_在CAD中直接画箭头的命令的一个方法

热门文章

  1. css border渐变_css边框渐变
  2. Docker上安装运行Hbase
  3. 利用DataSnap的回调功能在客户端显示服务器方法的执行进度
  4. CentOS7.5 Linux搭建全文检索--Solr7.4.0单机服务
  5. 提高测试脚本复用性降低DOM结构引起路径变化的影响
  6. 谈谈JavaScript的ECMA5中forEach
  7. 1207.1——C语言 函数
  8. Oracle SQL Tips
  9. oracle中的数据读取与查找
  10. css实现居中的各种方法