Linux_进程地址空间

Linux进程空间地址:
----32位
来自其他博主的图
在这里插入图片描述
用下往上看,从低地址到高地址:
我们可以用代码来验证:

在这里插入代码片#include <stdio.h>
#include <stdlib.h>
//初始化数据
int g_val = 100;

//未初始化数据
int g_unval;

int main(int argc, char* argv[], char* env[]) {
    
    //字面常量
    const char* str = "hello Linux";
    
    //正文代码
    printf("code addr: %p\n", main);
    //初始化数据 
    printf("init global addr: %p\n", &g_val);
    //未初始化数据 
    printf("uninit global addr: %p\n", &g_unval);
    
    //堆
    int *p = (int*)malloc(sizeof(int));

    //堆区地址
    printf("heap addr: %p\n", p);

    //栈区地址
    printf("stack addr: %p\n", &p);

    //命令行参数环境变量
    for(int i = 0; i < argc; i++) {
        printf("argv[%d]: %p\n", i, argv[i]);
    }

    for(int i = 0; env[i]; i++) {
        printf("env[%d]: %p\n",i, env[i]);
    }

    return 0;
}
[zt@VM-4-8-centos VMA]$ ./myproc 
code addr: 0x40059d
init global addr: 0x60103c
uninit global addr: 0x601044
heap addr: 0x2257c20
stack addr: 0x7ffec7fad5e8
argv[0]: 0x7ffec7fae7a7
env[0]: 0x7ffec7fae7b0
env[1]: 0x7ffec7fae7c6
env[2]: 0x7ffec7fae7dd
env[3]: 0x7ffec7fae7e8
env[4]: 0x7ffec7fae7f8
env[5]: 0x7ffec7fae806
env[6]: 0x7ffec7fae829
env[7]: 0x7ffec7fae83c
env[8]: 0x7ffec7fae844
env[9]: 0x7ffec7fae886
env[10]: 0x7ffec7faee22
env[11]: 0x7ffec7faee3a
env[12]: 0x7ffec7faee92
env[13]: 0x7ffec7faeea8
env[14]: 0x7ffec7faeeb9
env[15]: 0x7ffec7faeec1
env[16]: 0x7ffec7faeecf
env[17]: 0x7ffec7faeeda
env[18]: 0x7ffec7faef0a
env[19]: 0x7ffec7faef2d
env[20]: 0x7ffec7faef9f
env[21]: 0x7ffec7faefbe
env[22]: 0x7ffec7faefd4
env[23]: 0x7ffec7faefdf
[zt@VM-4-8-centos VMA]$ vim proc.c 
[zt@VM-4-8-centos VMA]$ cat proc.c 

还有一个重要特性:堆是向上增长,栈是向下增长
这是每个进程都要遵守的规则

我们程序用的是物理地址吗?还是虚拟地址?

我将用一段代码来验证:

#include <unistd.h>
#include <stdio.h>

int g_val = 100;
int main() {
    pid_t id = fork();
   
    if(id == 0) {
        //child
        int count = 1;
        while(count++) {
            printf("I am child pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(), getppid(), g_val, &g_val);
            if(count == 5) {
                printf("child chage g_val %d -> %d succes\n", 100 , g_val = 200);
            } 
            sleep(1); 
        }
    }
    if(id > 0) {
        //father
        while(1) {  
            printf("I am father pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(), getppid(), g_val, &g_val);
            sleep(1);
        }    
    }
    return 0;
}

接下来看看不一样的地址空间
在这里插入图片描述
子进程和父进程

g_val

两个进程所打印的地址明明都是一样的
前面打印的值也是一样的
但是子进程修改了g_val
重点来了,同一个地址空间,但却存储不一样的数据?
所以,我们进程用的一定不是物理内存!!!
下面将对这个进行解释!

虚拟地址空间!

什么是虚拟地址(线性地址)?

地址空间本身就是内核中的一种数据结构,每一个进程都有自己独立的虚拟地址,通过映射来建立和物理内存的联系。
我们最终要访问的还是物理地址,那为什么还要这么麻烦?
虚拟地址通过页表映射到物理地址空间,如果这个时候我们要映射的位置是违法,访问非法地址,那么这个时候系统就可以终止我们的进程。

地址空间是怎么设计的?

映射的规则是怎么样的!!!
有一个数据结构存放着大量的。…_start,_end,这两个,标志这各个区域的空间开始和结束!
这个内核数据结构就是:

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 mm_struct *mm, unsigned long addr);
	unsigned long mmap_base;		/* base of mmap area */
	unsigned long task_size;		/* size of task vm space */
	unsigned long cached_hole_size; 	/* if non-zero, the largest hole below free_area_cache */
	unsigned long free_area_cache;		/* first hole of size cached_hole_size or larger */
	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 and some counters */

	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
						 */

	/* Special counters, in some configurations protected by the
	 * page_table_lock, in other configurations by being atomic.
	 */
	mm_counter_t _file_rss;
	mm_counter_t _anon_rss;

	unsigned long hiwater_rss;	/* High-watermark of RSS usage */
	unsigned long hiwater_vm;	/* High-water virtual memory usage */

	unsigned long total_vm, locked_vm, shared_vm, exec_vm;
	unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
	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 saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */

	struct linux_binfmt *binfmt;

	cpumask_t cpu_vm_mask;

	/* Architecture-specific MM context */
	mm_context_t context;

	/* Swap token stuff */
	/*
	 * Last value of global fault stamp as seen by this process.
	 * In other words, this value gives an indication of how long
	 * it has been since this task got the token.
	 * Look at mm/thrash.c
	 */
	unsigned int faultstamp;
	unsigned int token_priority;
	unsigned int last_interval;

	unsigned long flags; /* Must use atomic bitops to access the bits */

	struct core_state *core_state; /* coredumping support */
#ifdef CONFIG_AIO
	spinlock_t		ioctx_lock;
	struct hlist_head	ioctx_list;
#endif
#ifdef CONFIG_MM_OWNER
	/*
	 * "owner" points to a task that is regarded as the canonical
	 * user/owner of this mm. All of the following must be true in
	 * order for it to be changed:
	 *
	 * current == mm->owner
	 * current->mm != mm
	 * new_owner->mm == mm
	 * new_owner->alloc_lock is held
	 */
	struct task_struct *owner;
#endif

#ifdef CONFIG_PROC_FS
	/* store ref to file /proc/<pid>/exe symlink points to */
	struct file *exe_file;
	unsigned long num_exe_file_vmas;
#endif
#ifdef CONFIG_MMU_NOTIFIER
	struct mmu_notifier_mm *mmu_notifier_mm;
#endif
};

这里是源码中的,有各个代码区间的start,end:

	unsigned long total_vm, locked_vm, shared_vm, exec_vm;
	unsigned long stack_vm, reserved_vm, def_flags, nr_ptes;
	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;

虚拟地址空间映射到到物理内存是怎么映射?os算出虚拟地址空间站的字节大小,然后在物理内存中映射,确定一段内存空间,给这个进程用。
只要保证,每一个进程的页表,映射的物理内存的不同区域,就能做到,进程间不会互相干扰,保证进程的独立性!

可以回答上面的问题了

在这里插入图片描述
这是上面那个代码!
现在已经知道了,我们进程用的是虚拟地址,那么父子进程里面的改变不会影响到别人!
在fork()之后,我们操作系统为了提高效率,有一种方案,叫做:写时拷贝一旦父或子进程发生修改,那么操作系统就会拷贝出一份地址空间给修改的进程,来保证进程的独立性!这里不过多讲述。
所以上面的子进程修改,os对他进行写时拷贝!子进程根据父进程来进行复制的所以这两个变量的地址是一样的但是值却是不一样的!两者进程的物理地址根本不是用一个,不会影响。

为什么要有虚拟地址空间?

如果我们直接使用物理内存,很不安全,比如我们写代码的时候会有指针越界,野指针等问题,而且一个物理内存中存在很多进程,不止一个进程!这样不仅会对自己的进程有危险,对其他的进程也不安全,内存本身也是可以读写的,所以我们要存在虚拟地址空间!

1.凡是非法的访问或者映射,os都会识别到,并终止这个进程!!!
有效的保护了物理内存吗?
因为地址空间和页表是os创建并维护的!是不是也就意味着凡是想使用地址空间和页表映射,也一定要在os的监管下来进行访问!!!
也便保存了物理内存中的所有合法数据,包括各个进程,以及内核的相关数据结构!!

2.因为有地址空间的存在,因为有页表的映射的存在我们物理内存中,是不是可以对未来的数据进行任意位置的加载?
当然可以!
物理内存的分配就可以和进程管理,可以 做到没有关系!!!

3.因为在物理内存中理论上可以任意位置加载,那么是不是物理内存中的几乎说有数据和代码在内存中都是乱序的!
但是,因为页表的存在,它可以将地址空间上的虚拟地址和物理地址进行映射,那么是不是在进程视角所有的内存分布,都可以是有序的!

地址空间+页表的存在 可以将内存分布有序化!!!

进程的独立性可以通过地址空间加页表的方式实现

因为有地址空间的存在,每一个进程都认为自己拥有 4GB空间(32),并且各个区域都是有序的,进而可通过页表映射到不同的区域,来实现进程的独立性!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学习新算法

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

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

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

打赏作者

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

抵扣说明:

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

余额充值