目录
一、定义
二、问题引出
三、虚拟地址和物理地址
(一)问题解释
(二)什么是进程地址空间
(三)为什么要有进程地址空间
一、定义
#include <stdio.h>
#include <stdlib.h>//getenv的头文件
int un_gval;
int init_gval=2;
int main()
{
printf("代码地址 :%p\n", main);
const char *str = "hello world";
printf("常量地址 :%p\n", str);
printf("已初始化数据地址:%p\n", &init_gval);
printf("未初始化数据地址:%p\n", &un_gval);
char *heap = (char*)malloc(100);
printf("堆区地址 :%p\n", heap);
printf("栈区地址 :%p\n", &str);
return 0;
}
#include <stdio.h>
#include <stdlib.h>//getenv的头文件
int main()
{
const char *str="hello world";
char *heap1 = (char*)malloc(100);
char *heap2 = (char*)malloc(100);
char *heap3 = (char*)malloc(100);
char *heap4 = (char*)malloc(100);
printf("heap1 address:%p\n", heap1);
printf("heap2 address:%p\n", heap2);
printf("heap3 address:%p\n", heap3);
printf("heap4 address:%p\n", heap4);
printf("Stack1 address: %p\n", &str);
printf("Stack2 address: %p\n", &heap1);//变量heap1是在main函数内部定义
printf("Stack3 address: %p\n", &heap2);
printf("Stack4 address: %p\n", &heap3);
return 0;
}
- 栈整体向下增长,但是一旦全部开辟好以后,局部向上使用
- (例如一个数组int a[10] ,&a[0]<&a[9])
二、问题引出
- fork以后的父子进程,输出地址是一致的,但是变量内容不一样
#include<stdio.h>
#include<unistd.h>
int g_val = 100;
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 0;
}
else if(id == 0)//子进程
{
int cnt = 5;
while(1)
{
printf("child , pid: %d, ppid: %d, g_val: %d, &g_val: %p\n", getpid(),getppid(),g_val,&g_val);
sleep(1);
if(cnt == 0)
{
g_val = 200;
printf("child change g_val: 100->200\n");
}
cnt--;
}
}
else
{
//父进程
while(1)
{
printf("father, pid: %d, ppid: %d, g_val: %d, &g_val: %p\n",getpid(),getppid(),g_val,&g_val);
sleep(1);
}
}
return 0;
}
三、虚拟地址和物理地址
(一)问题解释
- 每一个进程运行之后,都会有一个进程地址空间的存在(在系统层面都要有自己的页表映射结构)
(二)什么是进程地址空间
- 进程地址空间本质上是内存中的一种内核数据结构
- 在Linux当中进程地址空间具体由结构体mm_struct实现
- 进程地址空间是一个虚拟的内存空间,可以看做是一条从
0x00000000
到0xffffffff
的线,这条线上被划分成不同的区域。 - 每个区域在进程地址空间中都有一定范围的地址划分。在实际运行中,这些虚拟地址会被操作系统内核映射到实际的物理内存地址上
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;
unsigned long hiwater_rss; /* High-water RSS usage */
unsigned long hiwater_vm; /* High-water virtual memory usage */
};
页表中的其他字段:
(三)为什么要有进程地址空间
- 进程以统一的视角看待内存,所以任意一个进程,可以通过地址空间+页表将乱序的内存数据,变成有序
- 存在虚拟地址空间,可以有效的进行进程访问内存的安全检查
- 将进程管理和内存管理进行解耦(互不干扰)
- 通过页表,让进程映射到不同物理内存上,从而实现进程的独立性