地址空间 (Address Space)

在这里插入图片描述

程序地址空间

什么是地址空间:地址空间是内存中可供程序或进程使用的有效地址的范围。也就是说,它是程序或进程可以访问的内存。内存可以是物理的、也可以是虚拟的,用于执行指令和存储数据。

在之前 c/c++ 的学习中,我们所知道的空间布局图如下:
在这里插入图片描述
我们可以在 Linux 操作系统中,对上面的空间布局图进行验证:

// 验证程序地址空间的分布
#include<stdio.h>
#include<stdlib.h>

int un_g_val;
int g_val=10;

int main(int argc,char *argv[],char *env[])
{
    printf("code addr           :%p\n",main);
    printf("init global addr    :%p\n",&g_val);
    printf("uninit global addr  :%p\n",&un_g_val);
    char *m1 = (char*)malloc(10);
    printf("heap addr           :%p\n",m1);
    printf("stack addr          :%p\n",&m1);
    
    for(int i=0;i<argc;++i)
    {
         printf("argv addr           :%p\n",argv[i]);
    }
    for(int i=0;env[i];++i)
    {
         printf("env addr            :%p\n",env[i]);
    }
    return 0;
}

测试结果如下,符合上图的分布:

在这里插入图片描述

下面来看一段代码及其运行结果:fork() 创建子进程,子进程打印两次之后更改全局变量的值。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>

int g_val = 10;

int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child process
        int count = 0;
        while(1)
        {
            printf("I am a child process! PID : %d,PPID : %d,g_val = %d,&g_val = %p\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
            count++;
            if(count == 2)
            {
                g_val = 7;
                printf("I am a child process,g_val has changed!\n");
            }
        }       
    }
    else 
    {
        // parent process
        while(1)
        {
            printf("I am a parent process! PID : %d,PPID : %d,g_val = %d,&g_val = %p\n",getpid(),getppid(),g_val,&g_val);
            sleep(1);
        }
    }
    return 0;
}

运行结果:

在这里插入图片描述

🎯根据上述运行结果,我们发现,在子进程还没有修改全局数据的时候,输出的变量和地址是一样的,因为子进程按照父进程为模板,父子进程并没有对变量进行任何修改。但是当子进程将代码中的全局变量更改之后,我们发现,父子进程,输出的地址一样,但是变量的内容却不一样。因此我们得出以下结论:

  • 变量的内容不一样,说明父子进程输出的变量绝对不是同一个变量。
  • 但是变量对应的地址确实一样的,说明,该地址绝对不是物理地址!在 Linux 操作系统下,我们将此地址叫做 虚拟地址
  • 我们之前在 c/c++ 中所看到的地址,全部都是虚拟地址。物理地址,我们是看不到的,由 OS 同一进行管理。

🎭 OS 需要将 虚拟地址 转化为 物理地址。

进程地址空间

地址空间是计算机内存中的一个空间。进程地址空间是指在内存中为进程分配的空间。每个进程都有一个地址空间。地址空间有两种类型:物理地址空间、虚拟地址空间。

所以之前所说的程序地址空间是不准确的,准确来说应该称为进程地址空间,进程地址空间是内存中的一种内核数据结构。Linux 中由内存描述符(mm_struct)来描述有关进程地址空间的所有信息。

mm_struct 定义在 <linux/sched.h> 下:

206 struct mm_struct {
207     struct vm_area_struct * mmap;
208     rb_root_t mm_rb;
209     struct vm_area_struct * mmap_cache;
210     pgd_t * pgd;
211     atomic_t mm_users;
212     atomic_t mm_count;
213     int map_count;
214     struct rw_semaphore mmap_sem;
215     spinlock_t page_table_lock;
216 
217     struct list_head mmlist;
221 
222     unsigned long start_code, end_code, start_data, end_data;
223     unsigned long start_brk, brk, start_stack;
224     unsigned long arg_start, arg_end, env_start, env_end;
225     unsigned long rss, total_vm, locked_vm;
226     unsigned long def_flags;
227     unsigned long cpu_vm_mask;
228     unsigned long swap_address;
229 
230     unsigned dumpable:1;
231 
232     /* Architecture-specific MM context */
233     mm_context_t context;
234 };

内存区域:线性地址的间隔,其特征是初始线性地址、长度和一些访问权限。
Linux 操作系统用 vm_area_struct 来管理内存区域。

vm_area_struct 被定义在 <linux/mm.h> 中:

 44 struct vm_area_struct {
 45     struct mm_struct * vm_mm;
 46     unsigned long vm_start;
 47     unsigned long vm_end;
 49 
 50     /* linked list of VM areas per task, sorted by address */
 51     struct vm_area_struct *vm_next;
 52 
 53     pgprot_t vm_page_prot;
 54     unsigned long vm_flags;
 55 
 56     rb_node_t vm_rb;
 57 
 63     struct vm_area_struct *vm_next_share;
 64     struct vm_area_struct **vm_pprev_share;
 65 
 66     /* Function pointers to deal with this struct. */
 67     struct vm_operations_struct * vm_ops;
 68 
 69     /* Information about our backing store: */
 70     unsigned long vm_pgoff;
 72     struct file * vm_file;
 73     unsigned long vm_raend;
 74     void * vm_private_data;
 75 };

在这里插入图片描述
当一个进程被创建时,其对应的进程控制块(task_struct)和进程地址空间(mm_struct)也随之被创建。操作系统可以通过进程的 task_struct 来找进程对应的 mm_struct 。

🎭 如下所示,父进程创建了子进程后,父子进程都有属于自己的进程控制块,父进程和子进程中的进程地址空间中的虚拟地址通过页表映射到物理内存中:

在这里插入图片描述
通过此图我们就可以解释上面代码中的问题,当子进程被创建时,子进程和父进程的数据和代码共享。父子进程的代码和数据通过各自的页表映射到物理内存的同一块区域。因此,当子进程和父进程没有对数据进行修改的时候,它们的数据是一样的,是同一份。但是当子进程更改了全局数据,那么就会在内存中某一空间存储一个更改的新数据,并且更改子进程页表中 g_val 的虚拟地址所映射的物理地址。

虚拟地址存在的意义:

  • 虚拟内存将主存看作是在磁盘地址空间上的高速缓存,主存中只报保存活动区域并根据需要在磁盘和主存之间来回传送数据。
  • 虚拟地址允许我们有内存保护,每个进程的地址空间不被其它程序破坏。
  • 允许通过磁盘来扩展物理内存的使用。
  • 为进程提供的一致的地址空间简化了内存管理。

对于虚拟地址和物理地址的关系,就简单介绍这些了。

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
error l107: address space overflow 是指地址空间溢出错误。在计算机系统中,每个进程都有自己的地址空间,用于存储程序和数据。地址空间包括程序代码、全局变量、局部变量、堆栈以及其他相关的资源。 当出现 error l107: address space overflow 错误时,意味着进程已经使用了超过其可用地址空间的容量。这可能是由于以下几个原因造成的: 1. 程序设计不当:程序中使用了大量的全局变量、静态变量或常量并且没有及时释放。这样会导致地址空间的使用量增大,超过了系统限制的上限。 2. 递归调用深度过大:如果程序使用了递归函数,并且递归深度很大,则会占用大量的堆栈空间。当堆栈空间不足时,就会触发地址空间溢出错误。 3. 动态内存管理问题:使用动态内存分配函数(如malloc、new等)分配内存时,如果没有及时释放已分配的内存,则会导致堆空间的使用量不断增加,从而引发地址空间溢出。 为了解决 error l107: address space overflow 错误,我们可以采取以下措施: 1. 优化程序设计:避免过多使用全局变量、静态变量和常量,尽量使用局部变量,并及时释放不再使用的资源。 2. 减少递归深度:修改递归函数的设计,尽量减少递归调用的深度,或者改用循环来替代递归。 3. 合理管理动态内存:在使用动态内存分配函数分配内存后,应该及时释放已经不再使用的内存空间,以免堆空间的使用量过大。 通过以上措施,我们可以有效地避免和解决 error l107: address space overflow 错误,确保程序的正常运行。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

风&57

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

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

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

打赏作者

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

抵扣说明:

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

余额充值