Linux内存映射构建艺术:VMA链接与管理的深度剖析

前言

在Linux的虚拟内存管理中,虚拟内存区域(VMA) 就像是建筑工地上的施工蓝图,而将VMA正确链接到各种数据结构中的过程,则如同将这些蓝图精确归档到不同的档案系统中------有的按地址顺序排列,有的按文件关系组织,还有的专门管理匿名内存。这个过程不仅需要极高的精确性,还要保证在各种复杂场景下的高效运作。

想象一下,当进程请求一个新的内存映射时,内核需要:

  • 在数十个现有VMA中找到精确的插入位置
  • 同时维护链表、红黑树、优先级树、匿名VMA链表等多个数据结构
  • 处理文件权限、共享映射、非线性映射等特殊情况
  • 确保所有操作在并发环境下的安全性

本文将深入揭示Linux内核如何通过精巧的算法设计,在毫秒级时间内完成这些复杂的"内存拼图"操作,构建起进程虚拟地址空间的完整骨架

核心架构:四重链接系统

Linux内核为每个VMA维护着四个独立但相互关联的数据结构,形成一个高效的管理网络:

地址空间链表 - 顺序管理的基石

c 复制代码
// 在VMA链表中插入新节点
if (prev) {
    vma->vm_next = prev->vm_next;  // 新节点指向后继
    prev->vm_next = vma;           // 前驱指向新节点
} else {
    mm->mmap = vma;  // 作为链表头
}

作用:保持VMA的地址顺序,支持全量遍历和连续性检查

红黑树 - 快速查找的引擎

c 复制代码
rb_link_node(&vma->vm_rb, rb_parent, rb_link);  // 初步链接
rb_insert_color(&vma->vm_rb, &mm->mm_rb);       // 重新平衡

作用 :实现O(log n)的高效地址查找,支撑find_vma等核心操作

文件映射优先级树 - 共享管理的智慧

c 复制代码
if (vma->vm_flags & VM_NONLINEAR)
    vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);  // 非线性→链表
else
    vma_prio_tree_insert(vma, &mapping->i_mmap);           // 线性→优先级树

作用:管理文件映射关系,支持区间查询和冲突处理

匿名VMA链表 - 反向映射的纽带

c 复制代码
list_add(&vma->anon_vma_node, &anon_vma->head);  // 添加到匿名链表

作用:建立物理页到VMA的反向映射,支撑页面回收和迁移

智能处理:多场景自适应

冲突解决机制

当多个VMA映射相同的文件区间时:

c 复制代码
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
{
    // 根据现有节点的状态智能选择添加策略
    if (!old->shared.vm_set.parent) 
        list_add(...);           // 情况1:添加到现有链表
    else if (old->shared.vm_set.head)
        list_add_tail(...);      // 情况2:添加到链表尾部  
    else {
        // 情况3:创建新的链表关系
        vma->shared.vm_set.head = old;
        old->shared.vm_set.head = vma;
    }
}

合并优化策略

当相邻VMA可以合并时:

c 复制代码
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
{
    if (!vma->anon_vma) {
        vma->anon_vma = next->anon_vma;  // 继承匿名VMA
        list_add(&vma->anon_vma_node, &next->anon_vma_node);
    }
    list_del(&next->anon_vma_node);  // 移除被合并的VMA
}

将新VMA插入到进程的地址空间管理中__insert_vm_struct

c 复制代码
static void
__insert_vm_struct(struct mm_struct * mm, struct vm_area_struct * vma)
{
	struct vm_area_struct * __vma, * prev;
	struct rb_node ** rb_link, * rb_parent;

	__vma = find_vma_prepare(mm, vma->vm_start,&prev, &rb_link, &rb_parent);
	if (__vma && __vma->vm_start < vma->vm_end)
		BUG();
	__vma_link(mm, vma, prev, rb_link, rb_parent);
	mm->map_count++;
}

函数代码分析

c 复制代码
static void
__insert_vm_struct(struct mm_struct * mm, struct vm_area_struct * vma)
  • mm:内存管理结构
  • vma:要插入的虚拟内存区域

变量声明

c 复制代码
struct vm_area_struct * __vma, * prev;
struct rb_node ** rb_link, * rb_parent;
  • __vma:找到的现有VMA(可能为NULL)
  • prev:前一个VMA
  • rb_link:红黑树中插入位置的指针
  • rb_parent:红黑树中插入位置的父节点

查找插入位置

c 复制代码
__vma = find_vma_prepare(mm, vma->vm_start,&prev, &rb_link, &rb_parent);
  • mm:内存管理结构

  • vma->vm_start:要插入VMA的起始地址

  • &prev:返回前一个VMA的指针

  • &rb_link:返回红黑树插入位置的指针

  • &rb_parent:返回红黑树插入位置的父节点

  • 返回值 :第一个结束地址大于vma->vm_start的VMA

重叠检查

c 复制代码
if (__vma && __vma->vm_start < vma->vm_end)
	BUG();
  • __vma不为NULL(存在现有VMA)

  • 现有VMA的起始地址小于要插入VMA的结束地址

  • 如果检测到重叠,触发内核错误(内核崩溃)

链接VMA到数据结构

c 复制代码
__vma_link(mm, vma, prev, rb_link, rb_parent);
  • 使用find_vma_prepare准备好的所有信息
  • 将VMA实际插入到链表和红黑树中

更新映射计数

c 复制代码
mm->map_count++;
  • 增加内存管理结构的映射计数
  • 作用:跟踪当前进程有多少个VMA

总结

主要功能:安全地将新VMA插入到进程的地址空间管理中

  1. 位置查找

    • 找到新VMA在地址空间中的正确位置
    • 准备链表和红黑树的插入信息
  2. 重叠检测

    • 严格检查新VMA是否与现有VMA重叠
    • 防止内存管理冲突
  3. 数据结构更新

    • 将VMA插入到链表和红黑树中
    • 维护数据结构的完整性
  4. 统计信息更新

    • 更新进程的VMA映射计数
    • 支持系统监控和限制检查
c 复制代码
static void
__vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
	struct vm_area_struct *prev, struct rb_node **rb_link,
	struct rb_node *rb_parent)
{
	__vma_link_list(mm, vma, prev, rb_parent);
	__vma_link_rb(mm, vma, rb_link, rb_parent);
	__anon_vma_link(vma);
}
static inline void
__vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma,
		struct vm_area_struct *prev, struct rb_node *rb_parent)
{
	if (prev) {
		vma->vm_next = prev->vm_next;
		prev->vm_next = vma;
	} else {
		mm->mmap = vma;
		if (rb_parent)
			vma->vm_next = rb_entry(rb_parent,
					struct vm_area_struct, vm_rb);
		else
			vma->vm_next = NULL;
	}
}
void __vma_link_rb(struct mm_struct *mm, struct vm_area_struct *vma,
		struct rb_node **rb_link, struct rb_node *rb_parent)
{
	rb_link_node(&vma->vm_rb, rb_parent, rb_link);
	rb_insert_color(&vma->vm_rb, &mm->mm_rb);
}
void __anon_vma_link(struct vm_area_struct *vma)
{
	struct anon_vma *anon_vma = vma->anon_vma;

	if (anon_vma) {
		list_add(&vma->anon_vma_node, &anon_vma->head);
		validate_anon_vma(vma);
	}
}
c 复制代码
static void
__vma_link(struct mm_struct *mm, struct vm_area_struct *vma,
	struct vm_area_struct *prev, struct rb_node **rb_link,
	struct rb_node *rb_parent)
{
	__vma_link_list(mm, vma, prev, rb_parent);
	__vma_link_rb(mm, vma, rb_link, rb_parent);
	__anon_vma_link(vma);
}
  • 作用:协调三个子函数,完成VMA的完整链接
  1. 链接到VMA链表
  2. 链接到红黑树
  3. 链接到匿名VMA链表

情况1:有前驱节点

c 复制代码
if (prev) {
	vma->vm_next = prev->vm_next;
	prev->vm_next = vma;
}
  • 插入前: prev → next
  • 插入后: prev → vma → next

情况2:无前驱节点(插入到链表头)

c 复制代码
} else {
	mm->mmap = vma;
	if (rb_parent)
		vma->vm_next = rb_entry(rb_parent,
				struct vm_area_struct, vm_rb);
	else
		vma->vm_next = NULL;
}
  • mm->mmap = vma:设置新的链表头
  • 如果有红黑树父节点,通过它找到下一个VMA
  • 否则设置为NULL(只有一个VMA)

链接节点到红黑树

c 复制代码
rb_link_node(&vma->vm_rb, rb_parent, rb_link);
  • &vma->vm_rb:要插入的VMA的红黑树节点

  • rb_parent:父节点

  • rb_link:插入位置的指针

  • 将节点初步链接到红黑树中

重新平衡红黑树

c 复制代码
rb_insert_color(&vma->vm_rb, &mm->mm_rb);
  • 执行红黑树的重新平衡操作,维护树的平衡性质

获取匿名VMA

c 复制代码
struct anon_vma *anon_vma = vma->anon_vma;
  • 从VMA中获取关联的匿名VMA管理结构

链接到匿名VMA链表

c 复制代码
if (anon_vma) {
	list_add(&vma->anon_vma_node, &anon_vma->head);
	validate_anon_vma(vma);
}
  • 只有当VMA有关联的匿名VMA时才处理

  • list_add:将VMA添加到匿名VMA链表的头部

  • validate_anon_vma:调试模式下验证链接正确性

设计原理分析

为什么需要三个独立的链接操作?

链表的作用

  • 顺序遍历:支持O(n)的全量遍历
  • 简单性:易于理解和调试
  • 地址连续性:天然保持地址空间顺序

红黑树的作用

  • 快速查找:支持O(log n)的地址查找
  • 高效管理:支持区间查询和范围操作

匿名VMA链表的作用

  • 反向映射:支持从物理页找到所有映射的VMA
  • 匿名页管理:专门管理没有文件后端的页

总结

主要功能:将VMA完整地链接到进程的所有内存管理数据结构中

c 复制代码
static inline void __vma_link_file(struct vm_area_struct *vma)
{
	struct file * file;

	file = vma->vm_file;
	if (file) {
		struct address_space *mapping = file->f_mapping;

		if (vma->vm_flags & VM_DENYWRITE)
			atomic_dec(&file->f_dentry->d_inode->i_writecount);
		if (vma->vm_flags & VM_SHARED)
			mapping->i_mmap_writable++;

		flush_dcache_mmap_lock(mapping);
		if (unlikely(vma->vm_flags & VM_NONLINEAR))
			vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);
		else
			vma_prio_tree_insert(vma, &mapping->i_mmap);
		flush_dcache_mmap_unlock(mapping);
	}
}
static inline void vma_nonlinear_insert(struct vm_area_struct *vma,
					struct list_head *list)
{
	vma->shared.vm_set.parent = NULL;
	list_add_tail(&vma->shared.vm_set.list, list);
}
void vma_prio_tree_insert(struct vm_area_struct *vma,
			  struct prio_tree_root *root)
{
	struct prio_tree_node *ptr;
	struct vm_area_struct *old;

	vma->shared.vm_set.head = NULL;

	ptr = prio_tree_insert(root, &vma->shared.prio_tree_node);
	if (ptr != &vma->shared.prio_tree_node) {
		old = prio_tree_entry(ptr, struct vm_area_struct,
					shared.prio_tree_node);
		vma_prio_tree_add(vma, old);
	}
}

获取文件指针

c 复制代码
struct file * file;
file = vma->vm_file;
  • 从VMA中获取关联的文件指针

  • 只有当VMA有关联文件时才进行处理(文件映射)

文件权限处理

c 复制代码
struct address_space *mapping = file->f_mapping;
  • 获取文件的页缓存管理结构
c 复制代码
if (vma->vm_flags & VM_DENYWRITE)
	atomic_dec(&file->f_dentry->d_inode->i_writecount);
  • VM_DENYWRITE:表示这个映射阻止其他进程以写模式打开文件
  • atomic_dec:减少文件的写引用计数
  • 效果:阻止其他进程修改文件
c 复制代码
if (vma->vm_flags & VM_SHARED)
	mapping->i_mmap_writable++;
  • VM_SHARED:共享映射,修改会写回文件
  • i_mmap_writable++:增加可写映射计数
  • 用途:跟踪有多少个可写的共享映射

映射结构更新

c 复制代码
if (unlikely(vma->vm_flags & VM_NONLINEAR))
	vma_nonlinear_insert(vma, &mapping->i_mmap_nonlinear);
  • 条件unlikely 提示非线性映射是罕见情况
  • 处理:调用非线性映射插入函数
c 复制代码
else
	vma_prio_tree_insert(vma, &mapping->i_mmap);
  • 默认情况:大多数映射是线性的,使用优先级树插入

vma_nonlinear_insert 函数

c 复制代码
static inline void vma_nonlinear_insert(struct vm_area_struct *vma,
					struct list_head *list)
  • 作用:将VMA插入非线性映射链表
c 复制代码
vma->shared.vm_set.parent = NULL;
  • 初始化:非线性映射没有父节点概念
c 复制代码
list_add_tail(&vma->shared.vm_set.list, list);
  • 操作:将VMA添加到链表尾部

vma_prio_tree_insert 函数

c 复制代码
void vma_prio_tree_insert(struct vm_area_struct *vma,
			  struct prio_tree_root *root)
  • 作用:将VMA插入优先级树
c 复制代码
vma->shared.vm_set.head = NULL;
  • 准备:清空共享结构的头指针
c 复制代码
ptr = prio_tree_insert(root, &vma->shared.prio_tree_node);
  • 将VMA的优先级树节点插入到树中
  • 如果插入成功返回自身,如果冲突返回已存在的节点

处理插入冲突

c 复制代码
if (ptr != &vma->shared.prio_tree_node) {
	old = prio_tree_entry(ptr, struct vm_area_struct,
				shared.prio_tree_node);
	vma_prio_tree_add(vma, old);
}
  • 如果插入时发现冲突(相同区间已存在)

  • 获取已存在的VMA

  • 调用 vma_prio_tree_add 将新VMA添加到冲突链表中

功能总结

主要功能:将VMA正确链接到文件的映射管理结构中

  1. 权限管理

    • 处理VM_DENYWRITE标志,控制文件写访问
    • 跟踪VM_SHARED可写映射数量
  2. 映射结构维护

    • 线性映射:插入优先级树,支持快速区间查找
    • 非线性映射:插入链表,管理随机映射关系
  3. 并发控制

    • 使用映射锁保护数据结构
    • 确保缓存一致性
  4. 冲突处理

    • 处理优先级树插入冲突
    • 维护共享映射的正确关系

将映射相同文件区间的新VMA添加到现有VMA的链表中vma_prio_tree_add

c 复制代码
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
{
	/* Leave these BUG_ONs till prio_tree patch stabilizes */
	BUG_ON(RADIX_INDEX(vma) != RADIX_INDEX(old));
	BUG_ON(HEAP_INDEX(vma) != HEAP_INDEX(old));

	vma->shared.vm_set.head = NULL;
	vma->shared.vm_set.parent = NULL;

	if (!old->shared.vm_set.parent)
		list_add(&vma->shared.vm_set.list,
				&old->shared.vm_set.list);
	else if (old->shared.vm_set.head)
		list_add_tail(&vma->shared.vm_set.list,
				&old->shared.vm_set.head->shared.vm_set.list);
	else {
		INIT_LIST_HEAD(&vma->shared.vm_set.list);
		vma->shared.vm_set.head = old;
		old->shared.vm_set.head = vma;
	}
}

函数代码分析

c 复制代码
void vma_prio_tree_add(struct vm_area_struct *vma, struct vm_area_struct *old)
  • vma:要添加的新VMA
  • old:已存在的VMA

初始化新VMA

c 复制代码
vma->shared.vm_set.head = NULL;
vma->shared.vm_set.parent = NULL;
  • head = NULL:新VMA不是链表头

  • parent = NULL:新VMA不是树节点

  • 新VMA被初始化为普通的链表节点

情况1:old不是树节点

c 复制代码
if (!old->shared.vm_set.parent)
	list_add(&vma->shared.vm_set.list,
			&old->shared.vm_set.list);
  • old->shared.vm_set.parent == NULL,old本身是链表节点(不是树节点)

  • list_add:将新VMA添加到old所在的链表中

  • 新VMA被插入到链表头部

情况2:old是树节点且有链表

c 复制代码
else if (old->shared.vm_set.head)
	list_add_tail(&vma->shared.vm_set.list,
			&old->shared.vm_set.head->shared.vm_set.list);
  • old->shared.vm_set.parent != NULL(old是树节点)

  • old->shared.vm_set.head != NULL(old已经有链表)

  • list_add_tail:将新VMA添加到链表尾部

  • 通过old->shared.vm_set.head找到链表头

情况3:old是树节点但无链表

c 复制代码
else {
	INIT_LIST_HEAD(&vma->shared.vm_set.list);
	vma->shared.vm_set.head = old;
	old->shared.vm_set.head = vma;
}
  • old->shared.vm_set.parent != NULL(old是树节点)
  • old->shared.vm_set.head == NULL(old还没有链表)
  1. INIT_LIST_HEAD(&vma->shared.vm_set.list):初始化新VMA的链表头
  2. vma->shared.vm_set.head = old:新VMA指向树节点作为链表头
  3. old->shared.vm_set.head = vma:树节点指向新VMA作为链表头

总结

主要功能:将映射相同文件区间的新VMA添加到现有VMA的链表中

  1. 一致性验证

    • 确保新老VMA映射完全相同的文件区间
    • 通过基数索引和堆索引双重验证
  2. 节点类型识别

    • 根据old节点的指针状态识别其角色
    • 支持树节点、链表头、链表节点三种状态
  3. 链表管理

    • 处理三种不同的添加场景
    • 维护正确的链表结构
  4. 并发安全设计

    • 通过指针关系而非标志位识别节点类型
    • 避免锁的复杂依赖
c 复制代码
void __anon_vma_link(struct vm_area_struct *vma)
{
	struct anon_vma *anon_vma = vma->anon_vma;

	if (anon_vma) {
		list_add(&vma->anon_vma_node, &anon_vma->head);
		validate_anon_vma(vma);
	}
}
static inline void validate_anon_vma(struct vm_area_struct *find_vma)
{
#ifdef RMAP_DEBUG
	struct anon_vma *anon_vma = find_vma->anon_vma;
	struct vm_area_struct *vma;
	unsigned int mapcount = 0;
	int found = 0;

	list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {
		mapcount++;
		BUG_ON(mapcount > 100000);
		if (vma == find_vma)
			found = 1;
	}
	BUG_ON(!found);
#endif
}
  • 将VMA链接到其匿名VMA的链表中

获取匿名VMA指针

c 复制代码
struct anon_vma *anon_vma = vma->anon_vma;
  • 从VMA结构中获取关联的匿名VMA管理结构
c 复制代码
if (anon_vma) {
  • 只有当VMA有关联的匿名VMA时才进行处理
c 复制代码
list_add(&vma->anon_vma_node, &anon_vma->head);
  • list_add:将VMA添加到匿名VMA链表的头部
  • &vma->anon_vma_node:VMA中的链表节点
  • &anon_vma->head:匿名VMA的链表头
c 复制代码
validate_anon_vma(vma);
  • 调试模式下验证链接的正确性

validate_anon_vma 函数

c 复制代码
static inline void validate_anon_vma(struct vm_area_struct *find_vma)
{
#ifdef RMAP_DEBUG
  • 只有在定义了RMAP_DEBUG时才会编译和运行这个函数
  • 用于调试反向映射系统的正确性

变量声明

c 复制代码
struct anon_vma *anon_vma = find_vma->anon_vma;
struct vm_area_struct *vma;
unsigned int mapcount = 0;
int found = 0;
  • anon_vma:要验证的匿名VMA
  • vma:遍历时的当前VMA
  • mapcount:映射计数,用于检测无限循环
  • found:标记是否找到目标VMA

遍历匿名VMA链表

c 复制代码
list_for_each_entry(vma, &anon_vma->head, anon_vma_node) {
  • 遍历anon_vma->head链表中的所有VMA

映射计数检查

c 复制代码
mapcount++;
BUG_ON(mapcount > 100000);
  • 增加映射计数
  • 如果计数超过100000,触发内核错误
  • 目的:检测可能的无限循环或链表损坏

查找目标VMA

c 复制代码
if (vma == find_vma)
	found = 1;
  • 如果找到目标VMA,设置found标志为1

验证结果检查

c 复制代码
BUG_ON(!found);
  • 如果遍历完整个链表都没有找到目标VMA,触发内核错误
  • 目标VMA必须存在于它声称所属的匿名VMA链表中

设计原理分析

为什么需要匿名VMA链表?

当物理页需要被换出或迁移时,需要找到所有映射该页的VMA,这就是反向映射

匿名页的特殊性

匿名页没有文件后端,需要通过VMA来管理映射关系

总结

主要功能:将VMA正确链接到匿名VMA管理结构中,支持反向映射:

  1. 链表管理
    • 将VMA添加到匿名VMA的链表中
    • 维护VMA与匿名VMA的关联关系

合并两个相邻的匿名VMA管理结构__anon_vma_merge

c 复制代码
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
{
	if (!vma->anon_vma) {
		BUG_ON(!next->anon_vma);
		vma->anon_vma = next->anon_vma;
		list_add(&vma->anon_vma_node, &next->anon_vma_node);
	} else {
		/* if they're both non-null they must be the same */
		BUG_ON(vma->anon_vma != next->anon_vma);
	}
	list_del(&next->anon_vma_node);
}

函数代码分析

c 复制代码
void __anon_vma_merge(struct vm_area_struct *vma, struct vm_area_struct *next)
  • vma:当前VMA(合并后的主VMA)
  • next:下一个VMA(将被合并的VMA)

当前VMA没有匿名VMA的情况

c 复制代码
if (!vma->anon_vma) {
	BUG_ON(!next->anon_vma);
	vma->anon_vma = next->anon_vma;
	list_add(&vma->anon_vma_node, &next->anon_vma_node);
}
  • 当前VMA没有关联的匿名VMA管理结构

  • 验证:确保下一个VMA必须有匿名VMA

  • 当前VMA继承下一个VMA的匿名VMA管理结构

  • 将当前VMA添加到下一个VMA在匿名VMA链表中的位置

当前VMA有匿名VMA的情况

c 复制代码
} else {
	/* if they're both non-null they must be the same */
	BUG_ON(vma->anon_vma != next->anon_vma);
}
  • 确保两个VMA的匿名VMA指针相同
  • 相邻的VMA如果要合并,它们必须共享相同的匿名VMA管理结构

移除下一个VMA的链表节点

c 复制代码
list_del(&next->anon_vma_node);
  • 从匿名VMA链表中移除下一个VMA的节点

  • 下一个VMA不再属于任何匿名VMA链表

  • 下一个VMA的anon_vma_node被从链表中解除链接

总结

主要功能:合并两个相邻的匿名VMA管理结构

  1. 匿名VMA继承

    • 处理当前VMA没有匿名VMA的情况
    • 从下一个VMA继承匿名VMA管理结构
  2. 一致性验证

    • 确保要合并的VMA共享相同的匿名VMA
    • 防止不一致的合并操作
  3. 链表管理

    • 将当前VMA插入到匿名VMA链表的正确位置
    • 从链表中移除下一个VMA
相关推荐
MarcoPage4 小时前
Python 字典推导式入门:一行构建键值对映射
java·linux·python
埃伊蟹黄面5 小时前
计算机的“身体”与“灵魂”:冯·诺依曼架构与操作系统
linux
winner88817 小时前
Linux 软件安装 “命令密码本”:yum/apt/brew 一网打尽
linux·运维·服务器
思麟呀8 小时前
Linux的基础IO流
linux·运维·服务器·开发语言·c++
winner88819 小时前
嵌入式Linux驱动开发全流程:工具协作+核心概念拆解(从入门到理解)
linux·运维·驱动开发
ShiinaKaze9 小时前
fatal error: bits/c++config.h: No such file or directory
linux·gcc·g++
Archy_Wang_19 小时前
脚本自动生成专业Linux巡检报告
linux·运维·服务器
java_logo10 小时前
SGLANG Docker容器化部署指南
linux·运维·docker·容器·eureka·1024程序员节
敲代码的瓦龙11 小时前
操作系统?进程!!!
linux·c++·操作系统