Linux虚拟内存管理中的active_mm、匿名用户、mm_users和mm_count深入理解

问题:Linux虚拟内存管理中的active_mm作用?

问题的提出:阅读Linux虚拟内存管理源码时,看到复制一个mm,里面赋值了tsk->mmtsk->active_mm,于是回忆这两者的作用。

static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
	struct mm_struct * mm, *oldmm;
	int retval;

	tsk->min_flt = tsk->maj_flt = 0;   //初始化与内存管理相关的task_struct字段
	tsk->cmin_flt = tsk->cmaj_flt = 0;
	tsk->nswap = tsk->cnswap = 0;

	tsk->mm = NULL;
	tsk->active_mm = NULL;

	/*
	 * Are we cloning a kernel thread?
	 *
	 * We need to steal a active VM for that..
	 */
	oldmm = current->mm;   //用当前运行进程的mm复制
	if (!oldmm)   //没有mm的内核线程,所以立即返回
		return 0;

	if (clone_flags & CLONE_VM) {   //如果设置了CLONE_VM标志位,子进程与父进程共享mm
		atomic_inc(&oldmm->mm_users);   //mm_user字段加1
		mm = oldmm;
		goto good_mm;   //good_mm标记设置tsk->mm和tsk->active_mm,并返回成功
	}

	retval = -ENOMEM;
	mm = allocate_mm();
	if (!mm)
		goto fail_nomem;

	/* Copy the current MM stuff.. */
	memcpy(mm, oldmm, sizeof(*mm));
	if (!mm_init(mm))
		goto fail_nomem;

	if (init_new_context(tsk,mm))
		goto free_pt;

	down_write(&oldmm->mmap_sem);
	retval = dup_mmap(mm);
	up_write(&oldmm->mmap_sem);

	if (retval)
		goto free_pt;

	/*
	 * child gets a private LDT (if there was an LDT in the parent)
	 */
	copy_segments(tsk, mm);

good_mm:
	tsk->mm = mm;
	tsk->active_mm = mm;
	return 0;

free_pt:
	mmput(mm);
fail_nomem:
	return retval;
}

此前的笔记:

进程地址空间由mm_struct结构描述,所以一个进程只有一个mm_struct结构,且该结构在进程用户空间中由多个线程共享。内核线程(kernel thread)不需要mm_struct,故task_struct->mm字段总为NULL
那些未访问用户空间的进程所做的TLB刷新操作是无效的,Linux采用“延迟TLB”的技术避免这种刷新操作。Linux通过借用前个任务的mm_struct,并放入task_struct->active_mm中,避免了调用switch_mm()刷新TLB

  • 进入延迟TLB时,在SMP上系统会调用enter_lazy_tlb()确保mm_struct不会被SMP处理器共享,在UP机器上这是一个空操作
  • 进程退出时,系统会在该进程等待父进程回收时调用start_lazy_tlb()

mm_struct有两个引用计数,mm_users and mm_count

  • mm_users:描述存取这个mm_struct用户空间的进程数,存取的内容有页表、文件的映像等。例如线程会增加这个计数,以确保mm_struct不会被过早释放。当这个计数值减为0时,exit_mmap()会删除所有的映像并释放页表,然后减少mm_count值。
  • mm_count:对mm_struct匿名用户的计数。匿名用户不关心用户空间的内容,只是借用mm_struct。例如使用延迟TLB转换的核心线程(kernel thread)。当这个计数减为0时,就可以安全释放掉mm_struct。

新的问题。
Q1:匿名用户是什么?
Q2:mm_users and mm_count的作用?

Q1:匿名用户是什么?

查阅后看到Linus对 Is there a brief description someplace on how "mm" vs. "active_mm" in the task_struct are supposed to be used? 该问题的回答:
https://www.kernel.org/doc/Documentation/vm/active_mm.txt

邮件中提出两个此前没有看过的概念:“real address spaces” and “anonymous address spaces”
an anonymous address space doesn’t care about the user-level page tables at all, so when we do a context switch into an anonymous address space we just leave the previous address space active.
这里对anonymous address spaces进行了描述,可以理解。反过来应该就是real address spaces。邮件中也提到kernel thread基本上被认为是 anonymous address spaces 。到这里也就明白了匿名用户。Q1 解决。

也对原问题(Linux虚拟内存管理中mm_struct为什么有active_mm?)有了更深的理解:
kernel threads不关心real address spaces,即 kernel threads 可以使用任何进程的memory address spaces。在切换上下文到 kernel threads 时需要记录下 real address spaces。mm_struct->active_mm就用以记录 real address spaces,即memory address spaces of users。kernel threads通过active_mm确定当前执行流的 user memory address spaces 是什么。

Q2:mm_users and mm_count的作用?

Q2中mm_users and mm_count的作用,结合《Linux虚拟内存管理》和linus的邮件回复。mm_struct有两个计数是为了满足多核的处理。在一个CPU上运行的进程执行流包括用户部分和内核部分,换个说法也许更好,real address spaces and anonymous address spaces。进程执行流中real address spaces会被切换到其他CPU上,所以需要两个计数。

  • a “mm_users” counter that is how many “real address space users” there are.
  • a “mm_count” counter that is the number of “lazy” users (ie anonymous users) plus one if there are any real users.

这里可以用INIT_MM的初始化引证:

#define INIT_MM(name) \
{			 				\
	mm_rb:		RB_ROOT,			\
	pgd:		swapper_pg_dir, 		\
	mm_users:	ATOMIC_INIT(2), 		\
	mm_count:	ATOMIC_INIT(1), 		\
	mmap_sem:	__RWSEM_INITIALIZER(name.mmap_sem), \
	page_table_lock: SPIN_LOCK_UNLOCKED, 		\
	mmlist:		LIST_HEAD_INIT(name.mmlist),	\
}

可以看到INIT_MMmm_count初始化为1。

参考文献:
[1] Linus Torvalds. https://www.kernel.org/doc/Documentation/vm/active_mm.txt. 1999-07-30
[2] 白洛. 深入理解Linux虚拟内存管理. 2006-1
[3] Mel Gorman. Understanding the Linux Virtual Memory Manager. 2004-5-9

linux内核调试分析指南 linux内核调试分析指南--上篇 本文档已经转到下面的网址,位于zh-kernel.org的文档停止更新,请访问新网址 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择安装 安装交叉编译工具 bin工具集的使用 qemu的使用 skyeye的使用 UML的使用 vmware的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环境的建立 gdb基础 基本命令 gdb之gui gdb技巧 gdb宏 汇编基础--X86篇 用户手册 AT&T汇编格式 内联汇编 汇编与C函数的相互调用 调用链形成参数传递 C难点的汇编解释 优化级别的影响 汇编基础--ARM篇 用户手册 调用链形成参数传递 源码浏览工具 调用图生成工具 find + grep wine + SI global Source-Navigator vim + cscope/ctags kscope lxr SI等与gdb的特点 调用链、调用树调用图 理想调用链 函数指针调用 调用链的层次 非理想调用链 调用树与调用图 穿越盲区 穿越gdb的盲区 穿越交叉索引工具的盲区 工程方法 bug 与 OOPS linux内核调试分析指南--下篇 ***第二部分:内核分析*** 内核组织层次复杂度 内核层次 内核复杂度 复杂度隔离 gdb在内核分析中的用途 数据验证 界面剥离 参数记忆 路径快照 长程跟踪 整理思路 内核编码的艺术 信息聚集 数据聚集 关系聚集 操作聚集 松散聚集 顺序聚集 链表聚集 哈希聚集 树形聚集 分层聚集 分块聚集 对象聚集 设施客户 设备驱动模型分析 linux设备子系统的组成 设备驱动模型 usb子系统分析 如何阅读分析大型子系统 btrfs文件系统分析 区间树核心代码分析 B树核心代码分析 调试相关子系统 kgdb源码分析 sysrq oprofile kprobes 驱动分析 载入模块符号 ***第三部分:其他工具*** kexec strace ltrace SystemTap MEMWATCH YAMD Magic SysRq 附录:社区交流相关 补丁提交相关文档 补丁制作与提交示范 多补丁发送工具 git使用 Git公共库创建及使用 附录:内核参考书籍文章 内核git库 书籍 子系统官方网站 必看网站 参考文章 私人备忘
<think>好的,我现在需要回答用户关于Linux内核mm_struct的定义使用方法的问题。首先,我需要回忆一下之前提供的引用内容,特别是引用[1]、[2]、[3]中的信息。 根据引用[2],mm_struct是定义在include/linux/mm_types.h中的数据结构,它管理进程的地址空间。每个进程都有自己独立的mm_struct,这样每个进程都有独立的虚拟地址空间。在task_struct中有一个指向mm_struct的指针,这说明进程描述符通过这个指针来访问内存描述符。 用户想了解定义使用方法,我需要详细说明结构体的各个重要字段。比如start_code、end_code表示代码段的范围,start_dataend_data是数据段,start_brkbrk是堆区,start_stackend_stack是栈区,还有mmap_base用于内存映射区域。另外,mm_usersmm_count用于引用计数,这涉及到内存管理的生命周期。 使用方法方面,mm_struct通常在内核中由进程创建时分配,比如fork或exec时。内核线程可能没有mm_struct,而是借用前一个进程的,或者设置为NULL。当进程切换时,内核会切换mm_struct,这涉及到上下文切换页表的更新,如pgd_t指针指向的页表。 需要注意的是,用户可能对进程地址空间的具体管理感兴趣,比如如何分配释放内存区域,或者如何通过mmap系统调用进行内存映射。因此,可以提及mmapmunmap函数的使用,以及它们如何影响mm_struct中的相关字段。 此外,引用[3]提到mm_struct中的pgd_t指针用于页表映射,这涉及到虚拟地址到物理地址的转换过程。这部分可能需要简要说明页表的作用结构。 最后,要确保回答符合格式要求:使用中文,行内公式用$...$,独立公式用$$...$$,结构清晰,问题相关。同时生成相关问题,并正确添加引用标识。</think>### mm_struct的定义与使用方法详解 #### 一、mm_struct的定义 `mm_struct`是Linux内核管理**进程地址空间**的核心数据结构,定义在`include/linux/mm_types.h`中。以下是其关键字段的解析: 1. **地址空间范围**: - 代码段:`[start_code, end_code)`,对应程序的可执行代码。 - 数据段:`[start_data, end_data)`,存储已初始化的全局变量。 - 堆段:`[start_brk, brk)`,动态内存分配区域(如`malloc`调用)[^2]。 - 栈段:`[start_stack, end_stack)`,函数调用栈的地址范围。 - 内存映射基址:`mmap_base`,用于`mmap`系统调用映射文件或匿名内存[^2]。 2. **引用计数与生命周期**: ```c atomic_t mm_users; // 用户空间引用计数(如线程共享地址空间) atomic_t mm_count; // 结构体自身引用计数(如内核线程借用) ``` 当`mm_count`降为0时,内核会释放该`mm_struct`实例。 3. **页表管理**: - `pgd_t *pgd`:指向**页全局目录**(Page Global Directory),用于虚拟地址到物理地址的转换[^3]。 #### 二、使用方法 1. **进程创建时的初始化**: - **用户进程**:通过`fork()`或`exec()`系统调用创建时,内核会为其分配新的`mm_struct`,并复制父进程的地址空间(写时复制机制)。 - **内核线程**:通常不拥有独立的地址空间,`task_struct->mm`设为`NULL`,直接借用前一个进程的`active_mm`[^1]。 2. **地址空间操作示例**: - **内存映射**:通过`mmap()`系统调用扩展地址空间: ```c void *addr = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); ``` 这会更新`mm_struct`中的内存映射区域链表(`vm_area_struct`)。 - **堆扩展**:调用`brk()`调整堆顶指针`brk`,例如`malloc`内部可能触发此操作。 3. **进程切换与页表更新**: 当进程切换时,内核通过`switch_mm()`函数更新CR3寄存器指向新进程的`pgd`,实现地址空间切换[^3]。 #### 三、典型代码片段 ```c // 进程描述符中指向mm_struct的指针 struct task_struct { struct mm_struct *mm, *active_mm; }; // 获取当前进程的页表 pgd_t *pgd = current->mm->pgd; ``` #### 四、设计意义 `mm_struct`实现了**进程地址空间的抽象**,使得每个进程拥有独立的虚拟地址空间(如32位系统4GB布局)。例如,两个进程的`0x08048000`地址可能映射到不同的物理页,保证隔离性[^3]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值