Linux内核设计与实现---进程地址空间

进程地址空间

  • 1 内存描述符
    • 分配内存描述符
    • 销毁内存描述符
    • mm_struct与内核线程
  • 2 内存区域
    • VMA标志
    • VMA操作
    • 内存区域的树形结构和内存区域的链表结构
  • 3 操作内存区域
    • find_vma()
    • find_vma_prev()
    • find_vma_intersection()
  • 4 mmap()和do_mmap():创建地址空间
    • mmap() 系统调用
  • 5 munmap()和do_munmap():删除地址空间
    • munmap()系统调用
  • 6 页表

内核除了管理本身的内存外,还必须管理进程的地址空间,也就是系统中每个用户空间地址所对应的内存。Linux操作系统采用虚拟内存技术,因此,系统中的所有进程之间以虚拟方法共享内存,对每个进程来说,它们好像都可以访问整个系统的所有物理内存。

进程地址空间由每个进程中的线性地址区组成,每个进程都有一个32位或64位的平坦地址空间,空间的具体大小取决于体系结构,平坦地址空间是指地址空间范围是一个独立的连续空间(比如,地址从0扩展到429496729位地址空间)。一些操作系统提供了段地址空间,这种地址空间并非是一个独立的线性区域,而是被分段的,但现代采用虚拟内存的操作系统通常都使用平坦地址空间而不是分段式的内存模式。通常情况下,每个进程都有唯一的这种平坦地址空间,而进程地址空间之间彼此互不相干,两个不同的进程可以在它们各自地址空间的相同地址内存放不同的数据。进程之间也可以选择共享地址空间,我们称这样的进程为线程。

1 内存描述符

内核使用内存描述符结构体表示进程的地址空间,该结构包含了和进程地址空间有关的全部信息。内存描述符由mm_struct结构体表示,定义在文件linux/sched.h中。

struct mm_struct {struct vm_area_struct * mmap;		/* list of VMAs */struct rb_root mm_rb;struct vm_area_struct * mmap_cache;	/* last find_vma result */unsigned long (*get_unmapped_area) (struct file *filp,unsigned long addr, unsigned long len,unsigned long pgoff, unsigned long flags);void (*unmap_area) (struct vm_area_struct *area);unsigned long mmap_base;		/* base of mmap area */unsigned long free_area_cache;		/* first hole */pgd_t * pgd;atomic_t mm_users;			/* How many users with user space? */atomic_t mm_count;			/* How many references to "struct mm_struct" (users count as 1) */int map_count;				/* number of VMAs */struct rw_semaphore mmap_sem;spinlock_t page_table_lock;		/* Protects page tables, mm->rss, mm->anon_rss */struct list_head mmlist;		/* List of maybe swapped mm's.  These are globally strung* together off init_mm.mmlist, and are protected* by mmlist_lock*/unsigned long start_code, end_code, start_data, end_data;unsigned long start_brk, brk, start_stack;unsigned long arg_start, arg_end, env_start, env_end;unsigned long rss, anon_rss, total_vm, locked_vm, shared_vm;unsigned long exec_vm, stack_vm, reserved_vm, def_flags, nr_ptes;unsigned long saved_auxv[42]; /* for /proc/PID/auxv */unsigned dumpable:1;cpumask_t cpu_vm_mask;/* Architecture-specific MM context */mm_context_t context;/* Token based thrashing protection. */unsigned long swap_token_time;char recent_pagein;/* coredumping support */int core_waiters;struct completion *core_startup_done, core_done;/* aio bits */rwlock_t		ioctx_list_lock;struct kioctx		*ioctx_list;struct kioctx		default_kioctx;
};

mm_users域记录正在使用该地址的进程数目。比如,有两个进程共享该地址空间,那么mm_users的值便等于2;mm_count是mm_struct的主引用计数,只要mm_users不为0,那么mm_count值就等于1。当mm_users的值减为0时,mm_count域的值才为0,如果mm_count的值等于0,说明已经没有任何指向该mm_struct结构体的引用了,这个时候该结构体会被销毁。
mmap和mm_rb这两个数据结构描述的对象是相同的:该地址空间中的全部内存区域。但是mmap是以链表形式存放而后者以红-黑树形式存放。mmap结构体作为链表,利于简单、高效地遍历所有元素,而mm_rb结构体更适合搜索指定元素。
所有的mm_struct结构体都通过自身的mmlist域连接在一个双向链表中,该链表的首元素是init_mm内存描述符,它代表init进程的地址空间,内存描述符的总数存放在mmlist_nr全局变量中,该变量定义在kernel/fork.c中。

分配内存描述符

在进程的struct task_struct进程描述符中,mm域存放着该进程使用的内存描述符,所以current->mm便指向当前进程的内存描述符。fork函数利用copy_mm()函数复制父进程的内存描述符,也就是current->mm域给其子进程,而子进程中的mm_struct结构体实际是通过文件kernel/fork.c中的allocate_mm()宏从mm_cachep slab缓存中分配得到的。

如果父进程希望和其子进程共享地址空间,可以在调用clone()时,设置CLONE_VM标志。我们把这样的进程称作线程。当CLONE_VM被指定后,内核就不再需要调用allocate_mm()函数了,而仅仅需要在调用copy_mm()函数中将mm域指向其父进程的内存描述符就可以了。
在这里插入图片描述

销毁内存描述符

当进程退出时,内核会调用exit_mm函数,该函数执行一些常规的销毁工作,同时更新一些统计量。其中,该函数会调用mmput()函数减少内存描述符的mm_users用户计数,如果mm_users降到0,继续调用mmdrop()函数,减少mm_count,如果mm_count也等于0了,说明该内存描述符不再有任何使用者了,那么调用free_mm宏通过kmem_cache_free()将mm_struct结构体归还到mm_cachep_slab缓存中。

mm_struct与内核线程

内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符中mm域为空。

当一个进程被调度时,该进程的mm域指向的地址空间被装载到内存,进程描述符中的active_mm域会被更新,指向新的地址空间。内核线程没有地址空间,所以mm域为NULL,于是当一个内核线程被调用时,就会保留前一个进程的地址空间,随后内核更新内核线程对应的进程描述符中的active_mm域,使其指向前一个进程的内存描述符。

2 内存区域

内存区域由vm_area_struct结构体描述,定义在文件linux/mm.h中,内存区域在内核中也经常被称作虚拟内存区域或VMA。
vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围。内核将每个内存区域作为一个单独的内存对象管理,每个内存区域都拥有一致的属性,

struct vm_area_struct {struct mm_struct * vm_mm;	/* The address space we belong to. */unsigned long vm_start;		/* Our start address within vm_mm. */unsigned long vm_end;		/* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;pgprot_t vm_page_prot;		/* Access permissions of this VMA. */unsigned long vm_flags;		/* Flags, listed below. */struct rb_node vm_rb;/** For areas with an address space and backing store,* linkage into the address_space->i_mmap prio tree, or* linkage to the list of like vmas hanging off its node, or* linkage of vma in the address_space->i_mmap_nonlinear list.*/union {struct {struct list_head list;void *parent;	/* aligns with prio_tree_node parent */struct vm_area_struct *head;} vm_set;struct prio_tree_node prio_tree_node;} shared;/** A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma* list, after a COW of one of the file pages.  A MAP_SHARED vma* can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack* or brk vma (with NULL file) can only be in an anon_vma list.*/struct list_head anon_vma_node;	/* Serialized by anon_vma->lock */struct anon_vma *anon_vma;	/* Serialized by page_table_lock *//* Function pointers to deal with this struct. */struct vm_operations_struct * vm_ops;/* Information about our backing store: */unsigned long vm_pgoff;		/* Offset (within vm_file) in PAGE_SIZEunits, *not* PAGE_CACHE_SIZE */struct file * vm_file;		/* File we map to (can be NULL). */void * vm_private_data;		/* was vm_pte (shared mem) */#ifdef CONFIG_NUMAstruct mempolicy *vm_policy;	/* NUMA policy for the VMA */
#endif
};

每个内存描述符都对应于进程地址空间的唯一区间,vm_start域指向区间的首地址,vm_end域指向区间的尾地址之后的第一个字节,vm_end~vm_start的大小便是内存区间的长度,内存区域的位置就在[vm_start,vm_end]之中,注意,在同一个地址空间内的不同内存区间不能重叠。

vm_mm域指向和VMA相关的mm_struct结构体,注意每个VMA对其相关的mm_struct来说都是唯一的,所以即使两个独立的进程将同一个文件映射到各自的地址空间,它们分别都会有一个vm_area_struct结构体标志自己的内存区域,但是如果两个线程共享一个地址空间,那么它们也同时共享其中所有的vm_area_struct结构体。

VMA标志

VMA标志是一种位标志,其定义在linux/mm.h中,它包含在vm_flags域内,标志了内存区域所包含的页面的行为和信息,和物理页的访问权限不同,VMA标志反映了内核处理页面所需要遵守的行为准则,而不是硬件要求。
在这里插入图片描述

VMA操作

vm_area_struct结构体中的vm_ops指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA。vm_area_struct作为通用对象代表了任何类型的内存区域,而操作表描述针对特定的对象实例的特定方法。
操作函数表由vm_operations_struct结构体表示,定义在文件linux/mm.h中

/** These are the virtual MM functions - opening of an area, closing and* unmapping it (needed to keep files on disk up-to-date etc), pointer* to the functions called when a no-page or a wp-page exception occurs. */
struct vm_operations_struct {void (*open)(struct vm_area_struct * area);void (*close)(struct vm_area_struct * area);struct page * (*nopage)(struct vm_area_struct * area, unsigned long address, int *type);int (*populate)(struct vm_area_struct * area, unsigned long address, unsigned long len, pgprot_t prot, unsigned long pgoff, int nonblock);
#ifdef CONFIG_NUMAint (*set_policy)(struct vm_area_struct *vma, struct mempolicy *new);struct mempolicy *(*get_policy)(struct vm_area_struct *vma,unsigned long addr);
#endif
};

内存区域的树形结构和内存区域的链表结构

上面说过,可以通过内存描述符中的mmap和mm_rb域之一访问内存区域,这两个域各自独立地指向与内存描述符相关的全部内存区域对象vm_area_struct。

mmap使用单独链表连接所有的内存区域对象vm_area_struct,每一个vm_area_struct结构体通过自身的vm_next域被连入链表,所有的区域按地址增长的方向排序,mmap域指向链表中第一个内存区域,链中最后一个VMA结构体指针指向空。

mm_rb域使用红-黑树连接所有内存区域对象,mm_rb域指向红-黑树的根结点,地址空间中每一个vm_area_struct结构体通过自身的vm_rb域连接到树中。

链表用于需要遍历全部结点的时候,而红-黑树适用于在地址空间中定位特定内存区域的时候。内核为了内存区域上的各种不同操作都能获得高性能,所以同时使用了这两种数据结构。

3 操作内存区域

内核定义了许多内存区域操作函数,它们都声明在文件linux/mm.h中

find_vma()

find_vma()函数定义在mm/mmap.c中。

/* Look up the first VMA which satisfies  addr < vm_end,  NULL if none. */
extern struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr);

该函数在指定的地址空间中搜索第一个vm_end大于addr的内存区域。换句话说,该函数寻找第一个包含addr或首地址大于addr的内存区域,如果没有发现这样的区域,该函数返回NULL。否则返回指向匹配的内存区域的vm_area_struct结构体指针,返回的结构会被缓存在内存描述符的mmap_cache域中,所以find_vma会先在缓存中查找,如果指定的地址不在缓存中,那么必须搜搜和内存描述符相关的所有内存区域,这种搜索通过红-黑树进行。

find_vma_prev()

find_vma_prev()函数和find_vma()工作方式相同,但是它返回第一个小于addr的VMA。该函数定义和声明分别在文件mm/mmap.c中和文件linux/mm.h中

extern struct vm_area_struct * find_vma_prev(struct mm_struct * mm, unsigned long addr,struct vm_area_struct **pprev)

pprev参数存放指向先于addr的VMA指针。

find_vma_intersection()

find_vma_intersection()返回第一个和指定地址区间相交的VMA。因为该函数和内联函数,所以定义在文件linux/mm.h中:

static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
{struct vm_area_struct * vma = find_vma(mm,start_addr);if (vma && end_addr <= vma->vm_start)vma = NULL;return vma;
}

第一个参数是要搜索的地址空间,start_addr是区间的开始首位置,end_addr是区间的尾位置,

4 mmap()和do_mmap():创建地址空间

内核使用do_mmap()函数创建一个新的线性地址区间。如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。do_mmap()函数会将一个地址区间加入到进程的地址空间中。

do_mmap()函数定义在linux/mm.h中

static inline unsigned long do_mmap(struct file *file, unsigned long addr,unsigned long len, unsigned long prot,unsigned long flag, unsigned long offset)

该函数映射由file指定的文件,具体映射的是文件中从偏移量offset处开始,长度为len字节的范围内的数据。如果file参数是NULL并且offset参数也是0,那么就代码这次映射没有和文件相关,该情况被称作匿名映射,如果指定了文件名和偏移量,那么该映射被称为文件映射。

addr是可选参数,它指定搜索空闲区域的起始位置。
prot参数指定内存区域中页面的访问权限。访问权限标志定义在文件asm/mman.h中。
flag参数指定了VMA标志,这些标志也定义在文件asm/mman.h中

mmap() 系统调用

在用户空间可以通过mmap()系统调用获取内核函数do_mmap()的功能。

5 munmap()和do_munmap():删除地址空间

do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数定义在文件linux/mm.h中:

extern int do_munmap(struct mm_struct *mm, unsigned long start, size_t len);

第一个参数指定要删除区域所在的地址空间,删除从地址start开始,长度为len字节的地址区间,如果成功,返回0.

munmap()系统调用

系统调用munmap()给用户空间程序提供了一种从自身地址空间删除指定区间的方法。

int munmap(void *start ,size_t length)

该系统调用定义在mm/mmap.c中,它是对do_munmap的一个简单封装

6 页表

虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存,所以当应用程序访问一个虚拟地址时,首先必须将虚拟地址转化为物理地址,然后处理器才能解析地址访问请求。地址的转换工作是通过查询页表完成的。
页表对应的结构体依赖具体的体系结构,所以定义在文件asm/page.h中

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

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

相关文章

JavaScript中带有示例的Math.log10()方法

JavaScript | Math.log10()方法 (JavaScript | Math.log10() Method) Math operations in JavaScript are handled using functions of math library in JavaScript. In this tutorial on Math.log10() method, we will learn about the log10() method and its working with e…

JSP技术

一、jsp脚本和注释 jsp脚本&#xff1a; 1&#xff09;<%java代码%> ----- 内部的java代码翻译到service方法的内部 2&#xff09;<%java变量或表达式> ----- 会被翻译成service方法内部out.print() 3&#xff09;<%!java代码%> ---- 会被翻译成servlet的成…

EL技术

1&#xff0e;EL 表达式概述 EL&#xff08;Express Lanuage&#xff09;表达式可以嵌入在jsp页面内部&#xff0c;减少jsp脚本的编写&#xff0c;EL 出现的目的是要替代jsp页面中脚本的编写。 2&#xff0e;EL从域中取出数据(EL最重要的作用) jsp脚本&#xff1a;<%requ…

SVN+AnkhSVN端配置

对于ankhSVN我想很多人不陌生&#xff0c;因为经常使用&#xff0c;但是我还是发现很多人并不怎么会配置&#xff0c;或者完全不知道其需要配置&#xff0c;如果不配置的话&#xff0c;当两个人同时需要修改某个文件的时候就容易中弹了。SVN默认是不支持“锁定-编辑-解锁”的&a…

Linux内核设计与实现---模块

模块1 构建模块放在内核源代码树中放在内核代码外2 安装模块3 产生模块依赖性4 载入模块5 管理配置选项6 模块参数7 导出符号表Linux内核是模块化组成的&#xff0c;它允许内核在运行时动态地向其中插入或从中删除代码。 与开发的内核核心子系统不同&#xff0c;模块开发更接近…

Linux内核设计与实现---kobject sysfs

kobject sysfs1 kobject2 ktype3 kset4 subsystem5 别混淆了这些结构体6 管理和操作kobject7 引用计数kref8 sysfssysfs中添加和删除kobject向sysfs添加文件9 内核事件层2.6内核增加了一个引人注目的新特性—同一设备模型。设备模型提供了独立的机制专门表示设备&#xff0c;并…

开发Windows Mobile今日插件 -- 内存电量,桌面便笺,桌面记单词

本篇文章讲解的是开发 Windows Mobile 上的今日插件。关于是今日插件&#xff0c;在 PPC 或者 SP SDK 的帮助文档中有相关的章节介绍&#xff0c;在网络上也有一些帖子和资源讲解。在这里简要回顾一下。今日插件就是在windows mobile的桌面上显示的条目&#xff0c;例如系统提供…

算法---递归

递归结题三部曲 何为递归&#xff1f;程序反复调用自身即是递归。 我自己在刚开始解决递归问题的时候&#xff0c;总是会去纠结这一层函数做了什么&#xff0c;它调用自身后的下一层函数又做了什么…然后就会觉得实现一个递归解法十分复杂&#xff0c;根本就无从下手。 相信…

给定条件找最小值c语言程序_根据给定条件最小化n的最小步骤

给定条件找最小值c语言程序Problem statement: 问题陈述&#xff1a; Given a number n, count minimum steps to minimize it to 1 performing the following operations: 给定数字n &#xff0c;执行以下操作&#xff0c;计算最少的步骤以将其最小化为1&#xff1a; Operat…

那个年代的苏联歌曲

小时候&#xff0c;不时听父亲提起电影《这里的黎明静悄悄》&#xff0c;怎么也想不到如此美丽的名字为什么要和战争联系起来。后来在大学看了这部电影之后&#xff0c;开始认为这名字是合适的&#xff0c;因为电影讲的是女性——战场中的女性&#xff0c;各自都怀揣着爱情去保…

linux系统编程---进程总结

进程控制总结1 进程创建的三种方式forkvfrokclone2 进程终止进程正常退出returnexit_exit进程异常退出进程收到某个信号&#xff0c;而该信号使进程终止abort3 进程等待进程等待的方法waitwaitpid4 进程替换替换原理替换函数制作一个简单的shell1 进程创建的三种方式 参考文章…

银行账务转账系统(事务处理)

流程如下&#xff1a; 创建项目工程如下&#xff1a; transfer包下的代码如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils;pu…

【msdn wpf forum翻译】TextBox中文本 中对齐 的方法

原文链接&#xff1a;http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/49864e35-1dbf-4292-a361-93f1a8400558问题&#xff1a;TextBox中文本中对齐&#xff0c;使用 TextBox.HorizontalContentAlignment"Center"行不通&#xff08;TextBox.VerticalConte…

c语言 函数的参数传递示例_C语言中带有示例的remove()函数

c语言 函数的参数传递示例C语言中的remove()函数 (remove() function in C) The remove() function is defined in the <stdio.h> header file. remove()函数在<stdio.h>头文件中定义。 Prototype: 原型&#xff1a; int remove(const char* filename);Parameter…

使用ThreadLocal绑定连接资源(事务)

dao层代码如下&#xff1a; package beyond.transfer.dao;import java.sql.Connection; import java.sql.SQLException;import org.apache.commons.dbutils.QueryRunner;import beyond.utils.DataSourceUtils; import beyond.utils.MyDataSourceUtils;public class TransferDa…

算法---栈和队列

栈和队列1 栈栈的顺序存储栈的链式存储2 队列队列的顺序存储队列的链式存储3 栈和队列的应用用栈实现队列用队列实现栈最小栈1 栈 参考文章&#xff1a; https://zhuanlan.zhihu.com/p/346164833 https://zhuanlan.zhihu.com/p/120965372#:~:text%E6%A0%88%E6%98%AF%E4%B8%80%…

在WebBrowser中通过模拟键盘鼠标操控网页中的文件上传控件

引言 这两天沉迷了Google SketchUp&#xff0c;刚刚玩够&#xff0c;一时兴起&#xff0c;研究了一下WebBrowser。 我在《WebBrowser控件使用技巧分享》一文中曾谈到过“我现在可以通过WebBrowser实现对各种Html元素的操控&#xff0c;唯独无法控制Html的上传控件”&#xff0c…

编写最简单的字符设备驱动

编写最简单的字符设备驱动1 编写驱动代码2 编写makefile3 编译和加载驱动4 编写应用程序测试驱动参考文章&#xff1a; linux驱动开发第1讲&#xff1a;带你编写一个最简单的字符设备驱动 linux驱动开发第2讲&#xff1a;应用层的write如何调用到驱动中的write 1 编写驱动代码…

Linux设备驱动开发---字符设备驱动程序

字符设备驱动程序1 主设备和次设备的概念设备号的注册和释放静态方法动态方法区别2 设备文件操作struct file_operations与struct file、struct inode关系3 分配和注册字符设备class_createcdev_adddevice_create4 字符设备驱动程序字符设备通过字符&#xff08;一个接一个的字…

Java中的异常栈轨迹和异常链

Java中允许对异常进行再次抛出&#xff0c;以提交给上一层进行处理&#xff0c;最为明显的例子为Java的常规异常。 常规异常&#xff1a;有Java所定义的异常&#xff0c;不需要异常声明&#xff0c;在未被try-catch的情况下&#xff0c;会被默认上报到main()方法。 Example: pu…