进程可以在其虚拟内存的任何地址读/写吗?

时间:2016-07-21 14:17:31

标签: c++ c memory memory-management operating-system

OS中的进程有自己的虚拟地址空间。比如,我在c程序中使用malloc()函数调用分配一些动态内存,并从它返回的地址中减去一些正值(比如1000)。现在,我尝试阅读该位置上写的内容应该没问题但是写到该位置呢?

虚拟地址空间也有一些只读内存块。它是如何保护的?

8 个答案:

答案 0 :(得分:4)

TL; DR 不,这是不允许的。

在你的情况下,当你得到一个有效的非空指针指向malloc()返回的内存地址时,只有所请求的内存大小被分配给你的进程并且你被允许使用(读取和/或者写入那个空间。

通常,任何分配的内存(编译时或运行时)都与其关联。 overrunning underruning 分配的内存区域被视为无效内存访问,会调用undefined behavior

即使内存 可访问并且在进程地址空间内,也没有什么能阻止操作系统/内存管理器将指针返回到那个特定地址,因此,在 best ,您之前的写入将被覆盖,或者您将覆盖其他一些值。最糟糕的情况,如前所述,UB。

答案 1 :(得分:3)

  

说,我在c程序中使用malloc()函数调用分配一些动态内存,并从它返回的地址中减去一些正值(比如1000)。现在,我尝试阅读那个位置上写的内容应该没问题但是写到那个位置呢?

您可以读取/写入/执行的地址基于进程当前内存映射,该映射由操作系统设置。

在我的linux机器上,如果我在当前shell上运行pmap,我会看到类似这样的内容:

evaitl@bb /proc/13151 $ pmap 13151
13151:   bash
0000000000400000    976K r-x-- bash
00000000006f3000      4K r---- bash
00000000006f4000     36K rw--- bash
00000000006fd000     24K rw---   [ anon ]
0000000001f25000   1840K rw---   [ anon ]
00007ff7cce36000     44K r-x-- libnss_files-2.23.so
00007ff7cce41000   2044K ----- libnss_files-2.23.so
00007ff7cd040000      4K r---- libnss_files-2.23.so
00007ff7cd041000      4K rw--- libnss_files-2.23.so
00007ff7cd042000     24K rw---   [ anon ]
...
[many more lines here...]

每一行都有一个基地址,一个大小和权限。这些被认为是内存段。最后一行要么说明要映射的内容。bash是我的shell。 anon表示这是分配内存,也许是为了bss,也许是来自malloc的堆,或者它可能是一个堆栈。

也会映射共享库,这是libnns_files行的来源。

当你对一些内存进行malloc时,它将来自一个匿名的程序段。如果当前的anon段中没有足够的空间用于堆,则OS将增加其大小。这些细分中的权限几乎肯定是rw

如果您尝试在分配的空间之外进行读/写,则行为未定义。在这种情况下,这意味着您可能幸运并且没有任何反应,或者您可能会跳过未映射的地址并获得SIGSEGV信号。

答案 2 :(得分:2)

  

现在,我尝试阅读该位置上写的内容应该没问题

这不好。根据C ++标准,读取未初始化的内存具有未定义的行为。

  

但写到那个位置怎么样?

也不好。读取或写入未分配的内存也有不确定的行为。

当然,您在中结束的内存地址可能会被分配 - 这是可能的。但即使它恰好是,分配范围之外的指针算法已经是UB。

  

虚拟地址空间也有一些只读内存块。它是如何保护的?

这个超出了C ++(和C)的范围,因为它根本没有定义虚拟内存。这可能因操作系统而异,但至少有一种方法是当进程从OS请求内存时,它会发送指定所需保护类型的标志。请参阅prot argument in the man page of mmap作为示例。操作系统依次设置虚拟页面表。

一旦知道保护类型,如果违反了保护,操作系统可以发出适当的信号,并可能终止该过程。就像进程尝试访问未映射的内存时一样。违规通常由CPU的内存管理单元检测。

答案 3 :(得分:2)

  

OS中的进程有自己的虚拟地址空间。说,我分配   一些动态内存在c程序中使用malloc()函数调用   从返回的地址中减去一些正值(比如1000)   它。现在,我尝试阅读该位置上应该写的内容   没关系,但写到那个地方呢?

不,它不应该没问题,因为保证malloc()分配的内存区域是可访问的。无法保证虚拟地址空间是连续的,因此可以访问区域之前和之后的内存地址(即映射到虚拟地址空间)。

当然,没有人阻止你这样做,但行为真的是不确定的。如果访问非映射的内存地址,它将生成page fault异常,这是硬件CPU异常。当它由操作系统处理时,它会向您的应用程序发送SIGSEGV信号或访问冲突异常(取决于操作系统)。

  

虚拟地址空间也有一些只读内存块。怎么样   可以   保护那个?

首先,重要的是要注意virtual memory映射部分由外部硬件组件实现,称为memory management unit。它可能是否集成在CPU芯片中。除了能够将各种虚拟内存地址映射到物理虚拟地址之外,它还支持使用不同的标记标记这些地址,其中一个标志启用和禁用写保护。

当CPU尝试写入虚拟地址时,标记为只读,因此写保护(例如MOV instruction),MMU会在CPU上触发页面错误异常。

尝试访问不存在的虚拟内存页也是如此。

答案 4 :(得分:1)

在C语言中,对指针进行算术运算以产生另一个指针,该指针不指向同一个对象或对象数组(或者一个接一个地结束)未定义的行为:来自 6.5.6 Additive Operators

  

如果两个指针都有   操作数和结果指向同一个数组对象的元素,或者指向最后一个数组对象的元素   数组对象的元素,评估不得产生溢出;否则,   行为未定义。如果结果指向一个超过数组对象的最后一个元素的那个,那么   不得用作被评估的一元*运算符的操作数

(为了本节的目的,非数组对象被视为长度为1的数组)

你可能会感到不走运,编译器仍然可以生成一个指针,你可以用它来做事情,然后用它们做事情就可以了 - 但究竟是什么东西是任何人的猜测和将是不可靠的,通常很难调试。

如果你很幸运,编译器会产生一个指向内存的指针,它不属于你"并且只要您尝试阅读或写入问题,就会得到一个segmentation fault来提醒您该问题。

答案 5 :(得分:0)

读取/写入未映射的内存地址时系统的行为方式主要取决于您的操作系统实现。当您尝试访问未映射的虚拟地址时,操作系统通常会有不同的行为。当您尝试访问未映射(或映射为非内存,例如映射内存中的文件)时,操作系统正在接受控制(通过陷阱)会发生什么,然后发生的事情就是完全运行系统依赖。假设您已将视频帧缓冲区映射到虚拟地址中的某个位置...然后,在那里写入会使屏幕发生变化。假设您已映射文件,则读/写该内存意味着读取或写入文件。假设您(正在运行的进程)尝试访问交换区域(由于物理内存不足,您的进程已被部分交换),您的进程将停止并开始从二级存储启动该内存,然后重新启动该指令。例如,当您尝试访问未分配的内存时,linux会生成SIGSEGV信号。但是你可以安装一个信号处理程序,在接收到这个信号时被调用,然后,尝试访问未分配的内存意味着在你自己的程序中跳入一段代码来处理这种情况。

但是认为尝试访问未正确获取的内存,以及现代操作系统中的更多内容,通常意味着您的程序行为不正常,通常会崩溃,让系统采取控制措施,它会被杀了。

malloc(3)不是系统调用,而是一个库函数,用于管理RAM上的可变大小分配段,如果您尝试访问返回的前一个或前一个的第一个地址,会发生什么情况。分配的内存单元,表示未定义的行为。这并不意味着您已访问未分配的内存。您可能会在不知情的情况下在代码或数据(或堆栈)中读取完美分配的内存。 malloc(3)倾向于要求操作系统为昂贵的操作系统提供更多内存之间的许多malloc调用来管理连续的大量内存。有关详细信息,请参阅sbrk(2)memmap(2)系统调用联机帮助页。

例如, linux bsd unix 在第0页的每个进程的虚拟地址空间中分配一个条目(对于{ {1}}地址)使空指针无效访问,如果您尝试读取或写入此地址(或该页面中的所有地址),您将收到一个信号(或您的进程被杀死)试试这个:< / p>

NULL

该程序在运行时应该在所有现代操作系统上失败(尝试编译它而不进行优化,因此编译器不会消除main中的代码,因为它实际上没有任何内容)因为您正在尝试访问已分配的(用于特定目的)内存页面。

我系统上的程序(mac OS X,来自BSD unix的衍生产品)只执行以下操作:

int main()
{
    char *p = 0; /* p is pointing to the null address */
    p[0] = '\n'; /* a '\n' is being written to address 0x0000 */
    p[1] = '\0'; /* a '\0' is being written to address 0x0001 */
}

注2

许多现代操作系统(主要是unix派生的)实现了一种称为 COPY ON WRITE 的内存访问。这意味着您可以访问该内存并根据需要进行修改,但是第一次访问该内存进行写入时,会生成页面错误(通常,这是在您收到只读页面时实现的,让故障发生并且使单个页面副本存储您的私有修改)这在$ a.out Segmentation fault: 11 上非常有效,通常后面跟着fork(2)系统调用(只有程序修改的页面才会在进程抛出它们之前被复制所有,节省了大量的电脑电量)

另一种情况是堆栈增长示例。当您在程序中输入/离开堆栈帧时,堆栈会自动增长,因此操作系统必须处理当您在堆栈上exec(2)某个东西时发生的页面错误,并且该推送穿过虚拟页面并进入未知。发生这种情况时,操作系统会自动分配页面并将该区域(页面)转换为更有效的存储器(通常是读写)。

答案 6 :(得分:0)

从技术上讲,进程具有逻辑地址。但是,这通常会混入虚拟地址空间。

可以映射到该逻辑地址空间的虚拟地址数量可以通过以下方式进行限制:

  1. 硬件
  2. 系统资源(特别是页面文件空间)
  3. 系统参数(例如,限制页面大小)
  4. 流程配额
  5. 您的逻辑地址空间由映射到物理页面框架的页面数组组成。并非每个页面都需要具有这样的映射(甚至可能)。

    逻辑地址空间通常分为两个(或更多)区域:系统(所有进程通用)和用户(为每个进程创建)。

    从理论上讲,用户空间中没有任何东西可以作为进程,只存在系统地址空间。

    如果系统没有用尽其整个逻辑地址范围(这是正常的),则根本无法访问未使用的地址。

    现在你的程序开始运行了。 O / S已将一些页面映射到您的逻辑地址空间。可能映射的地址空间非常少。您的应用程序可以将更多页面映射到逻辑地址空间的未映射页面。

      

    说,我在c程序中使用malloc()函数调用分配一些动态内存,并从它返回的地址中减去一些正值(比如1000)。现在,我尝试阅读那个位置上写的内容应该没问题但是写到那个位置呢?

    处理器使用页表将逻辑页映射到物理页帧。如果你这样做,你会说很多事情都会发生:

    1. 地址=&gt;没有页表条目访问冲突。您的系统可能未设置可跨越整个逻辑地址空间的页表。

    2. 地址有一个页面表条目,但标记为无效=&gt;访问违规。

    3. 您正在尝试访问在当前处理器模式下无法访问的页面(例如,用户模式访问仅允许内核模式访问的页面)=&gt;访问违规。

    4.   

      虚拟地址空间也有一些只读内存块。它是如何保护的?

      1. 您正试图以不允许该页面的方式访问页面(例如,写入只读页面,执行到无执行页面)=&gt;访问冲突页面允许访问页面。
      2. [忽略页面错误]

        如果你通过这些测试,你可以访问随机存储器地址。

答案 7 :(得分:-1)

没有。实际上你作为一名程序员来处理这个问题