【Linux】进程地址空间

进程地址空间

前言

Linux操作系统采用虚拟内存技术,因此系统中的所有进程之间以虚拟方式共享内存,对一个进程而言,它好像都可以访问整个系统的所有物理内存,即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。

地址空间概述

进程地址空间由进程可寻址的虚拟内存组成,内存地址的给定的一个值,它要在地址空间范围内,比4021f000。这个值表示的是进程32位地址空间中的一个特定的字节。尽管一个进程可以寻址4GB的虚拟内存(32位的地址空间中),但这并不代表它就有权访问所有的虚拟地址。这些可被访问的合法地址空间称为内存区域。进程只能访问有效内存区域的内存地址,每个内存区域也具有相关权限如相关进程有读、可写、可执行属性。如果一个进程访问了不在有效范围的内存区域,或以不正确的方式访问了一个有效地址,那么内核就会终止该进程。下图是32位的进程地址空间示意图。

在这里插入图片描述

每个段对应的内容如下:

在这里插入图片描述

多进程的地址空间就如下图所示:

在这里插入图片描述

内存描述符mm_struct

struct mm_struct{
        struct vm_area_struct   *mmap;            /*内存区域链表*/
        struct rb_root          mm_rb;            /*VMA形成的红黑树*/
        struct vm_area_struct   *mmap_cache;      /*最近使用的内存区域*/
        unsigned long           free_area_cache;  /*地址空间的第一个空洞*/
        pgd_t                   *pgd;             /*页全局目录*/
        atomic_t                mm_users;         /*使用地址空间的用户数*/
        atomic_t                mm_count;         /*主使用计数器*/
        int                     map_count;        /*内存区域的个数*/
        struct rw_semaphore     mmap_sem;         /*内存区域的信号量*/
        spinlock_t              page_table_lock;  /*页表锁*/
        struct list_head        mmlist;           /*所有mm_struct形成的链表*/
        unsigned long           start_code;       /*代码段的开始地址*/
        unsigned long           end_code;         /*代码段的结束地址*/
        unsigned long           start_data;       /*数据段的首地址*/
        unsigned long           end_data;         /*数据段尾地址*/
        unsigned long           start_brk;        /*堆的首地址*/
        unsigned long           brk;              /*堆的尾地址*/
        unsigned long           start_stack;      /*进程栈的首地址*/
        unsigned long           arg_start;        /*命令行参数的首地址*/
        unsigned long           arg_end;          /*命令行参数的尾地址*/
        unsigned long           env_start;        /*环境变量的首地址*/
        unsigned long           env_end;          /*环境变量的尾地址*/
        unsigned long           rss;              /*所分配的物理页*/
        unsigned long           total_vm;         /*全部页面数目*/
        unsigned long           locked_vm;        /*全部上锁的页面数目*/
        unsigned long           saved_auxv[AT_VECTOR_SIZE]; /*保存的auxv*/
        cpumask_t               cpu_vm_mask;      /*懒惰(lazy)TLB交换掩码*/
        mm_context_t            context;          /*体系结构特殊数据*/
        unsigned long           flags;            /*状态标志*/
        int                     core_waiters;     /*内核转储等待线程*/
        struct core_state       *core_state;      /*核心转储的支持*/
        spinlock_t              ioctx_lock;       /*ATO I/O 链表锁*/
        struct hlist_head       ioctx_list;       /*ATO I/O 链表*/
};

mm_users与mm_count:

(1)mm_user指的就是所有共享此mm_struct描述的进程地址空间的线程数量,即:一个(进程中)线程组中的线程个数,当本进程中的线程退出时,mm_user减1,但只有当所有共享此进程空间的线程退出时,会对mm_count减1,否则不减。

(2)mm_count指的就是对mm_struct本身此结构体的**引用次数。**不管本进程中有多少线程,在没有其他进程或线程引用的情况下,mm_struct为1,因为本进程中的所有线程共享一个进程地址空间,就是创建线程,fork()时,直接将主线程task_struct中的mm域之间给了被fork出来的task_struct的mm域。当有其他进程或线程(除本进程中的线程)引用此mm_struct时,则mm_struct加1.内核线程在运行时会借用其他进程的mm_struct,这样的线程叫"anonymous users",因为他们不关心mm_struct指向的用户空间,也不会去访问这个用户空间.他们只是临时借用.mm_count记录这样的线程.

(3)当本进程中的线程退出时,mm_user会减1,如果mm_user减到0了,则会对mm_count减1,如果此时mm_count也为0了,说明该进程空间没有任何使用者了,则会归还此进程地址空间占的内存给系统。

mmap与mm_rb

mmap以链表的形式存放地址空间中的全部内存区域,mm_rb以红黑树存放。使用的用途不同:

1、mmap结构体作为链表,利于简单、高效地遍历所有元素

2、mm_rb结构作为红-黑树,适合搜索指定元素。

在这里插入图片描述

mmlist

所有的mm_struct结构体通过mmlist域连接在一个双链表中,链表的首元素是init_mm内存描述符,代表init进程的地址空间。

虚拟内存区域vm_area_struct

struct vm_area-struct{ 
    struct mm_struct        *vm_mm;      /*相关的mm_struct结构体*/
    unsigned long           vm_start;    /*区间的首地址*/
    unsigned long           vm_end;      /*区间的尾地址*/
    struct vm_area_struct   *vm_next;    /*VMA链表*/
    pgprot_t                vm_page_prot;/*访问控制权限*/
    unsigned long           vm_flags;    /*标志*/
    struct rb_node          vm_rb;       /*树上该VMA的节点*/
    union{
        struct{
            struct list_head   list;
            void               *parent;
            struct vm_area_struct *head;
        }vm_set;
        struct prio_tree_node prio_tree_node;
    }shared;
    struct list_head        anon_vma_node;  /*anon_vma项*/
    struct anon_vma         *anon_vma;      /*匿名VMA对象*/
    struct vm_operations_struct  *vm_ops;   /*相关的操作表*/
    unsigned long           vm_pgoff;       /*文件中的偏移量*/
    struct file             *vm_file;       /*被映射的文件*/
    void                    *vm_private_data; /*私有数据*/
};

如上述结构体,vm_area_struct结构体描述了指定地址空间内连续区间上的一个独立内存范围,每个内存区域都拥有一致的属性,比如访问权限,每一个VMA代表不同类型的内存区域,内存区域的范围是[vm_start,vm_end].

task_struct、mm_struct、vm_area_strcut的关系

一个进程的虚拟地址空间主要由两个数据结来描述:

一个是最高层次的:mm_struct,描述了一个进程的整个虚拟地址空间

一个是较高层次的:vm_area_structs,描述了虚拟地址空间的一个区间(简称虚拟区)

每个进程只有一个mm_struct结构,在每个进程的task_struct结构中,有一个指向该进程的结构。可以说,mm_struct结构是对整个用户空间的描述。

在这里插入图片描述

虚拟内存区域的操作

find_vma():寻找一个针对于指定地址的vma,该vma要么包含了指定的地址,要么位于该地址之后并且离该地址最近

struct vm_area_struct *find_vma(struct mm_struct *mm, unsigned long addr)
{
	struct vm_area_struct *vma = NULL;
 
	if (mm) {
		vma = mm->mmap_cache; //首先尝试mmap_cache中缓存的vma
		/*如果不满足下列条件中的任意一个则从红黑树中查找合适的vma
		  1.缓存vma不存在
		  2.缓存vma的结束地址小于给定的地址
		  3.缓存vma的起始地址大于给定的地址*/
		if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
			struct rb_node * rb_node;
 
			rb_node = mm->mm_rb.rb_node;//获取红黑树根节点
			vma = NULL;
 
			while (rb_node) {
				struct vm_area_struct * vma_tmp;
 
				vma_tmp = rb_entry(rb_node,	  //获取节点对应的vma
						struct vm_area_struct, vm_rb);
 
				/*首先确定vma的结束地址是否大于给定地址,如果是的话,再确定
				  vma的起始地址是否小于给定地址,也就是优先保证给定的地址是
				  处于vma的范围之内的,如果无法保证这点,则只能找到一个距离
				  给定地址最近的vma并且该vma的结束地址要大于给定地址*/
				if (vma_tmp->vm_end > addr) {
					vma = vma_tmp;
					if (vma_tmp->vm_start <= addr)
						break;
					rb_node = rb_node->rb_left;
				}
                else
					rb_node = rb_node->rb_right;
			}
			if (vma)
				mm->mmap_cache = vma;//将结果保存在缓存中
		}
	}
	return vma;
}

**find_vma_prev():**和find_vma()相同,但是返回的是第一个小于addr的VMA

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

**mmap()和do_mmap():**创建地址区间

创建分区的两种情况:

1、创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限,则合并为一个

2、不能合并,这创建的是一个新的VMA

do_mmap()函数的定义如下:

unsigned long do_map(struct *file,unsigned long addr,
                     unsigned long len,unsigned long prot,
                     unsigned long flag,unsigned long offset)

file:指定文件

addr:可选参数,指定搜索空闲区域的起始位置

prot:指定内存区域中页面的访问权限

flag:指定VMA标志

offset与length:指定映射的数据,即文件中从偏移offset处开始,长度为len字节的范围内,如果file参数为NULL并且offset参数也为空,则代表映射和文件没有关系,称为匿名映射。

**文件映射:**文件映射给用户提供了一组措施,好似用户将文件映射到自己地址空间的某个部分,**使用简单的内存访问指令读写文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Gl5IIl8-1603372818075)(6.jpg)]

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

void *mmap2(void *start,
            size_t length,
            int port,
            int flag,
            int fd,
            off_t pgoff)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值