0

0

【Linux内核大揭秘】程序地址空间

星夢妙者

星夢妙者

发布时间:2025-06-20 09:15:15

|

1091人浏览过

|

来源于php中文网

原创

程序地址空间是指一个程序在执行期间可以访问的内存范围。它由操作系统为每个进程分配,以确保进程之间不会相互干扰。地址空间包含了程序所需的所有内存区域,包括代码、已初始化和未初始化的数据、堆(heap)、栈(stack)等。

地址空间的组成分为逻辑地址和物理地址两种:

逻辑地址:是程序在代码中使用的地址,不直接对应物理内存。每个进程都有独立的逻辑地址空间。 物理地址:是真正存储在内存中的位置。通过虚拟内存技术,操作系统将逻辑地址映射到物理地址。这种技术带来了以下优势:

内存隔离:每个进程可以使用相同的逻辑地址空间,而操作系统会隔离各自的实际内存,确保进程之间不会互相影响。 安全性和稳定性:这种隔离机制使得进程无法直接访问其他进程的内存,提高了系统的安全性和稳定性。 总结来说,程序地址空间通过虚拟内存和地址映射技术实现了进程的内存隔离,保障了多任务操作系统的安全和可靠性。

如何理解程序地址空间通过一个现象来了解什么是虚拟内存技术。下面写一个简单的程序:

#include
#include
#include
#include
int gval=100;
int main(){
  printf("我是一个进程:pid:%d,ppid:%d\n",getpid(),getppid());
  pid_t id = fork();
  if(id==0)
  {
    //child
    while(1)
    {
      printf("我是子进程,pid:%d,ppid:%d,gval:%d,&gval:%p\n",getpid(),getppid(),gval,&gval);
      gval++;
      sleep(1);
    }
  }
  else
  {
    //parent
    while(1)
    {
      printf("我是父进程,pid:%d,ppid:%d,gval:%d,&gval:%p\n",getpid(),getppid(),gval,&gval);
      sleep(1);
    }
  }
  return 0;
}

这个程序很简单,通过父进程创建了一个子进程,然后用一个全局变量来证明虽然父子进程共用一套代码,但是数据是分离开的。我们分别打印全局变量的数据和全局变量的地址,然后子进程对应的全局变量需要做++操作。我们来看看运行结果:

【Linux内核大揭秘】程序地址空间

可以看见两个全局变量的地址是相同的,但是还是做到了数据独有呢?这是怎么做到的,通过这个现象,我们可以看出gval的地址肯定不是物理内存的地址,如果是物理内存的地址,如果地址相同,那么值也应该相同,所以这里面肯定是用什么结构把真实的物理内存给保护起来了,首先我们来看看我们以前学过的内存的结构。

【Linux内核大揭秘】程序地址空间

这是我们以前了解到的内存的划分区域,从上面现象可以推出这不是程序地址空间,而是进程地址空间,操作系统为每个进程绘制了一个虚拟地址内存,让每个进程以为自己独占整个内存,进程彼此之间是不知道的,从而达到了一定程度上的隔离。实际上所谓的进程虚拟地址空间本质上是一个内核数据结构(类似于PCB)。这个内核数据结构叫做mm_struct,在PCB中有一个指针指向虚拟地址空间,PCB控制着这个虚拟地址空间,然后mm_struct通过映射,映射到真实的物理内存上。我们画一个简图来理解这个概念:

【Linux内核大揭秘】程序地址空间

如何证明确实在task_struct中有这样一个结构体指针呢,我们来看看Linux内核的原码:

【Linux内核大揭秘】程序地址空间

可以看见task_struct内部确实有一个这样的指针,我们来看看mm_struct内部是什么样的:

【Linux内核大揭秘】程序地址空间

可以看见在mm_struct中有一些start和end的成员变量,这些就代表各个区域的起始位置和末尾位置,地址也是一个数,所以我们可以用一个unsigned long类型来表示每个区域的起始位置和末尾位置。

【Linux内核大揭秘】程序地址空间

虽然我们知道我们取到的地址不是物理内存地址,而是虚拟内存地址,中间是通过一层映射关系来将虚拟内存地址转化为物理内存地址的,那中间到底是怎么做到的,其实在这中间起着关键作用的,有一个内核数据结构叫做页表。

页表

【Linux内核大揭秘】程序地址空间

Designs.ai
Designs.ai

AI设计工具

下载

什么是页表:页表是操作系统内核用来管理虚拟地址和物理地址之间映射的一个数据结构。它的核心作用是支持虚拟内存,使得每个进程可以在自己的独立虚拟地址空间中运行,增强了内存隔离和安全性。简单了解完页表后,我们来解释一下我们刚刚的现象,为什么父进程的gval不变,子进程的gval在改变,两个gval都指向同一块空间。

【Linux内核大揭秘】程序地址空间

首先父进程创建子进程会以自己为模版创建一个PCB,内核会为子进程创建一个新的mm_struct,mm_struct的大部分字段和父进程共享,页表也会被创建,所以这里物理地址指向的是同一块空间。但是当我们修改gval的时候,物理内存会发生写实拷贝,父子进程不再共享gval。

【Linux内核大揭秘】程序地址空间

子进程的gval对应的虚拟地址对应的页表的映射会改变,改变为写实拷贝过后的地址,这样当修改gval时,gval会修改,但是父进程的gval不会被修改,但是gval的地址都是相同的,是因为这是虚拟地址,子进程是以父进程为模版创建的。

页表的细节关于页表,其实页表不存储物理地址和虚拟地址。

【Linux内核大揭秘】程序地址空间

当中还存在权限的管理和标记位等等属性,这个权限管理指的是读写权限,就比如我们在C语言中遇到的下图:

【Linux内核大揭秘】程序地址空间

这个都知道会崩溃,但是为什么会崩溃,其实是因为str对应的权限只有读,没有写的权限,所以会直接崩溃,这时系统层面上的错误,不是语法层面上的错误,所以语法是不会报错的。有效位表示看目标数据是否存在于内存当中,如果该位为 0,意味着页面不在物理内存,访问该页面会触发缺页中断,操作系统会加载页面或进行错误处理。页表其实还有很多属性,这里只陈述这两个属性。

关于堆区我们new出来的空间是否是物理内存?—答案很显然不是的。我们new或者malloc出来的空间也是虚拟内存,有一个问题就来了,结构体就那么大,但是堆区是动态的,那他是如何实现动态开辟的呢,刚刚我们提到了mm_struct有一段区域是存储begin和end的,我们只需要改变end和begin的数字,即可控制虚拟内存。

【Linux内核大揭秘】程序地址空间

在Linux中如何查看各个分段的信息

readelf -S 文件名

【Linux内核大揭秘】程序地址空间

总结通过本篇文章,我们了解了 Linux 程序地址空间的基本结构和分布,包括代码段、数据段、堆、栈以及内核空间的划分。掌握程序地址空间的布局不仅能帮助我们理解进程的内存使用,还能为调试、性能优化和内存管理打下坚实基础。理解 mm_struct、页表以及写时复制等机制,也为深入探索操作系统内核的内存管理提供了关键的思路。希望这些内容能让你在实际开发和学习中更好地应用这些知识,为系统性能和安全性提供支持。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

391

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

615

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

353

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

256

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

597

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

524

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

640

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

600

2023.09.22

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

65

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
CSS3 教程
CSS3 教程

共18课时 | 4.6万人学习

Git 教程
Git 教程

共21课时 | 2.8万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号