精华内容
下载资源
问答
  • 进程空间内存分配详解

    千次阅读 2019-08-20 11:50:00
    进程空间的内存分配 从高地址到低地址如图所示: 名称 操作系统内核区 用户不可见 用户栈 栈指针,向下扩展 动态堆 向上扩展 全局区(静态区) .data初始化.bss未初始化 文字常量区(只读数据) ...

    进程空间的内存分配
    从高地址到低地址如图所示:

    名称
    操作系统内核区用户不可见
    用户栈栈指针,向下扩展
    动态堆向上扩展
    全局区(静态区).data初始化.bss未初始化
    文字常量区(只读数据)常量字符串
    程序代码区栈指针,向下扩展

    下面详细介绍这几个区域:

    1.栈区:

    • 由编译器自动分配释放,速度较快
    • 程序在编译期对变量,返回地址,参数和函数分配内存都在栈上进行,函数运行结束,这些局部变量等空间都会被释放
    • 程序运行过程中函数调用时参数的传递也在栈上进行,如递归调用栈
    • 当栈过多的时候,就是导致栈溢出(比如大量的递归调用或者大量的内存分配)
    • 栈是向低地址扩展的数据结构,是一块连续的内存的区域,空间有限

    2. 堆区:

    • 由程序员分配(new,malloc)释放(delete,free),并指明大小,速度较慢
    • 若程序员不释放,导致内存泄漏,new完没有delete
    • 不过在整个程序结束时由操作系统回收,但是这样无疑增加了操作系统的负担
    • 高地址扩展的数据结构,是不连续的内存区域,空间很大,比较灵活
    • 频繁地分配和释放不同大小的堆空间容易产生内存碎片

    3. 全局区(静态区static):

    • 全局变量和静态变量是放在一起的
    • 初始化的全局变量和静态变量放在一块区域.data节
    • 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.bss(不占磁盘空间,不做具体解释)
    • 程序结束后由系统释放

    4. 文字常量区: 常量字符串就是放在这里,程序结束后由系统释放
    5. 程序代码区: 存放函数体的二进制代码。

    几个问题的解释:
    1 为什么栈比堆快?
    以下答案源自网络,作者还未能全部理解,求解释
    栈比堆要快,因为它存取模式使它可以轻松的分配和重新分配内存(指针/整型只是进行简单的递增或者递减运算),然而堆在分配和释放的时候有更多的复杂的 bookkeeping 参与。另外,在栈上的每个字节频繁的被复用也就意味着它可能映射到处理器缓存中,所以很快(译者注:局部性原理)。

    2 关于堆栈的申请内存情况
    只要栈的剩余空间大于所申请空间,系统将为程序分配内存,否则将报异常提示栈溢出。对于堆,操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中

    3 关于函数调用的过程调用栈(这里只做简要介绍,详细原理建议参看南京大学 袁春风老师的计算机系统基础,这本书很好
    栈为执行的线程留出了内存空间,当函数被调用的时候,会有一个过程调用栈,栈内保存函数调用的返回地址,被调用函数的入口参数局部变量等信息,调用结束后,这些信息被释放,然后栈顶指针继续指到返回地址的位置。所以每个线程都有自己的栈,那么栈空间就是私有的。线程结束的时候栈会被回收。一个简单的调用例子,主函数调用了caller,caler又调用了add

    int add(int x, int y)
    {return x + y;}
    int caller{
        int temp1 = 125;
        int temp2 = 80;
        int sum = add(temp1, temp2);
        return sum;
    }
    

    在这里插入图片描述

    (从右往左入栈的原因是,这样第一个参数最靠近栈顶指针,方便找到第一个)

    4 关于递归的缺点
    递归虽然简洁,但是也有很多缺点
    递归是由于函数调用自身,函数调用本身是有时间和空间消耗,每一次函数调用都需要往内存栈分配空间,需要保存现场信息,(返回地址,以方便调用之后返回)局部变量,入口参数,有时还需要调用者保存寄存器等,入栈和出栈都需要时间,所以递归的效率很慢

    第二个问题是栈溢出:每一次函数调用都会在内存栈分配空间,每个进程的栈容量有限,当递归调用的层级太多,就会超出栈容量,造成栈溢出。linux的栈一般是8M

    总结:当你编译之前知道分配数据的大小,并且不是太大的时候,推荐用栈,当在运行期间不知道会需要多大的数据或者需要分配大量内存,建议用堆

    展开全文
  • Linux 进程空间

    千次阅读 2018-04-12 15:53:13
    对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。未初始化过的数据(BSS):在程序运行初未对...


        对于一个进程,其空间分布如下图所示:

                                         



    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

    初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

    未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

    栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。

    堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。



    注:1.Text, BSS, Data段在编译时已经决定了进程将占用多少VM
            可以通过size,知道这些信息:
             

              2. 正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但它可以被多个进程安全的共享。

    二  内核空间和用户空间

              Linux的虚拟地址空间范围为0~4G,Linux内核将这4G字节的空间分为两部分, 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
        Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。
        内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000),另外, 使用虚拟地址可以很好的保护 内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。
          
            多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盒中,这个 沙盒就是虚拟地址空间(virtual address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表。

      进程内存空间分布如下图所示:

                               


           通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间

         : 1.这里是32位内核地址空间划分,64位内核地址空间划分是不同的
              2. 现代的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G 的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用 
                                                    
        
            Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。
            相对地,应用程序则是在“用户空间”中运行。运行在用户空间的应用程序只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。
            将用户空间和内核空间置于这种非对称访问机制下有很好的安全性,能有效抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。
                 内核空间在页表中拥有较高的特权级(ring2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:

                                               

          上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。


     三  进程内存布局

           Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。

                                    

                                                      q
          
              这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。

           几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致, 这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。

         进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。

          通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)


         :动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误

    内存映射段      
         在栈的下方是内存映射段内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。

          与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:
                              


            一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
             

    BBS和数据段
          在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static intcntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。
          数据段保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。

          你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。

         你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”,如下图所示:

                                       


    C语言程序实例分析如下所示:

    [cpp]  view plain  copy
    1.  #include<stdio.h>    
    2.  #include <malloc.h>    
    3.      
    4.  void print(char *,int);    
    5.  int main()    
    6. {    
    7.       char *s1 = "abcde";  //"abcde"作为字符串常量存储在常量区 s1、s2、s5拥有相同的地址  
    8.       char *s2 = "abcde";    
    9.       char s3[] = "abcd";    
    10.       long int *s4[100];    
    11.       char *s5 = "abcde";    
    12.       int a = 5;    
    13.       int b =6;//a,b在栈上,&a>&b地址反向增长    
    14.      
    15.      printf("variables address in main function: s1=%p  s2=%p s3=%p s4=%p s5=%p a=%p b=%p \n",     
    16.              s1,s2,s3,s4,s5,&a,&b);   
    17.      printf("variables address in processcall:n");    
    18.         print("ddddddddd",5);//参数入栈从右至左进行,p先进栈,str后进 &p>&str    
    19.      printf("main=%p print=%p \n",main,print);    
    20.      //打印代码段中主函数和子函数的地址,编译时先编译的地址低,后编译的地址高main<print    
    21.  }    
    22.   
    23.  void print(char *str,int p)    
    24. {    
    25.      char *s1 = "abcde";  //abcde在常量区,s1在栈上    
    26.      char *s2 = "abcde";  //abcde在常量区,s2在栈上 s2-s1=6可能等于0,编译器优化了相同的常量,只在内存保存一份    
    27.      //而&s1>&s2    
    28.      char s3[] = "abcdeee";//abcdeee在常量区,s3在栈上,数组保存的内容为abcdeee的一份拷贝    
    29.     long int *s4[100];    
    30.      char *s5 = "abcde";    
    31.      int a = 5;    
    32.      int b =6;    
    33.      int c;    
    34.      int d;           //a,b,c,d均在栈上,&a>&b>&c>&d地址反向增长    
    35.     char *q=str;   
    36.     int m=p;           
    37.     char *r=(char *)malloc(1);    
    38.     char *w=(char *)malloc(1) ;  // r<w 堆正向增长    
    39.     
    40.     printf("s1=%p s2=%p s3=%p s4=%p s5=%p a=%p b=%p c=%p d=%p str=%p q=%p p=%p m=%p r=%p w=%p \n",    
    41.             s1,s2,s3,s4,s5,&a,&b,&c,&d,&str,q,&p,&m,r,w);   
    42.     /* 栈和堆是在程序运行时候动态分配的,局部变量均在栈上分配。 
    43.         栈是反向增长的,地址递减;malloc等分配的内存空间在堆空间。堆是正向增长的,地址递增。   
    44.         r,w变量在栈上(则&r>&w),r,w所指内容在堆中(即r<w)。*/   
    45.  }    
    46.    



    附录:

    栈与堆的区别
               

      

        对于一个进程,其空间分布如下图所示:

                                         



    程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。

    初始化过的数据(Data):在程序运行初已经对变量进行初始化的数据。

    未初始化过的数据(BSS):在程序运行初未对变量进行初始化的数据。

    栈 (Stack):存储局部、临时变量,函数调用时,存储函数的返回指针,用于控制函数的调用和返回。在程序块开始时自动分配内存,结束时自动释放内存,其操作方式类似于数据结构中的栈。

    堆 (Heap):存储动态内存分配,需要程序员手工分配,手工释放.注意它与数据结构中的堆是两回事,分配方式类似于链表。



    注:1.Text, BSS, Data段在编译时已经决定了进程将占用多少VM
            可以通过size,知道这些信息:
             

              2. 正常情况下,Linux进程不能对用来存放程序代码的内存区域执行写操作,即程序代码是以只读的方式加载到内存中,但它可以被多个进程安全的共享。

    二  内核空间和用户空间

              Linux的虚拟地址空间范围为0~4G,Linux内核将这4G字节的空间分为两部分, 将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF)供各个进程使用,称为“用户空间。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
        Linux使用两级保护机制:0级供内核使用,3级供用户程序使用,每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的,最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。
        内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。 虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000),另外, 使用虚拟地址可以很好的保护 内核空间被用户空间破坏,虚拟地址到物理地址转换过程有操作系统和CPU共同完成(操作系统为CPU设置好页表,CPU通过MMU单元进行地址转换)。
          
            多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盒中,这个 沙盒就是虚拟地址空间(virtual address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表。

      进程内存空间分布如下图所示:

                               


           通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间

         : 1.这里是32位内核地址空间划分,64位内核地址空间划分是不同的
              2. 现代的操作系统都处于32位保护模式下。每个进程一般都能寻址4G的物理空间。但是我们的物理内存一般都是几百M,进程怎么能获得4G 的物理空间呢?这就是使用了虚拟地址的好处,通常我们使用一种叫做虚拟内存的技术来实现,因为可以使用硬盘中的一部分来当作内存使用 
                                                    
        
            Linux系统对自身进行了划分,一部分核心软件独立于普通应用程序,运行在较高的特权级别上,它们驻留在被保护的内存空间上,拥有访问硬件设备的所有权限,Linux将此称为内核空间。
            相对地,应用程序则是在“用户空间”中运行。运行在用户空间的应用程序只能看到允许它们使用的部分系统资源,并且不能使用某些特定的系统功能,也不能直接访问内核空间和硬件设备,以及其他一些具体的使用限制。
            将用户空间和内核空间置于这种非对称访问机制下有很好的安全性,能有效抵御恶意用户的窥探,也能防止质量低劣的用户程序的侵害,从而使系统运行得更稳定可靠。
                 内核空间在页表中拥有较高的特权级(ring2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:

                                               

          上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。


     三  进程内存布局

           Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。

                                    

                                                      q
          
              这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。

           几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致, 这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。

         进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。

          通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)


         :动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误

    内存映射段      
         在栈的下方是内存映射段内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。

          与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:
                              


            一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式类似于链表。
             

    BBS和数据段
          在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static intcntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。
          数据段保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。

          你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。

         你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”,如下图所示:

                                       


    C语言程序实例分析如下所示:

    [cpp]  view plain  copy
    1.  #include<stdio.h>    
    2.  #include <malloc.h>    
    3.      
    4.  void print(char *,int);    
    5.  int main()    
    6. {    
    7.       char *s1 = "abcde";  //"abcde"作为字符串常量存储在常量区 s1、s2、s5拥有相同的地址  
    8.       char *s2 = "abcde";    
    9.       char s3[] = "abcd";    
    10.       long int *s4[100];    
    11.       char *s5 = "abcde";    
    12.       int a = 5;    
    13.       int b =6;//a,b在栈上,&a>&b地址反向增长    
    14.      
    15.      printf("variables address in main function: s1=%p  s2=%p s3=%p s4=%p s5=%p a=%p b=%p \n",     
    16.              s1,s2,s3,s4,s5,&a,&b);   
    17.      printf("variables address in processcall:n");    
    18.         print("ddddddddd",5);//参数入栈从右至左进行,p先进栈,str后进 &p>&str    
    19.      printf("main=%p print=%p \n",main,print);    
    20.      //打印代码段中主函数和子函数的地址,编译时先编译的地址低,后编译的地址高main<print    
    21.  }    
    22.   
    23.  void print(char *str,int p)    
    24. {    
    25.      char *s1 = "abcde";  //abcde在常量区,s1在栈上    
    26.      char *s2 = "abcde";  //abcde在常量区,s2在栈上 s2-s1=6可能等于0,编译器优化了相同的常量,只在内存保存一份    
    27.      //而&s1>&s2    
    28.      char s3[] = "abcdeee";//abcdeee在常量区,s3在栈上,数组保存的内容为abcdeee的一份拷贝    
    29.     long int *s4[100];    
    30.      char *s5 = "abcde";    
    31.      int a = 5;    
    32.      int b =6;    
    33.      int c;    
    34.      int d;           //a,b,c,d均在栈上,&a>&b>&c>&d地址反向增长    
    35.     char *q=str;   
    36.     int m=p;           
    37.     char *r=(char *)malloc(1);    
    38.     char *w=(char *)malloc(1) ;  // r<w 堆正向增长    
    39.     
    40.     printf("s1=%p s2=%p s3=%p s4=%p s5=%p a=%p b=%p c=%p d=%p str=%p q=%p p=%p m=%p r=%p w=%p \n",    
    41.             s1,s2,s3,s4,s5,&a,&b,&c,&d,&str,q,&p,&m,r,w);   
    42.     /* 栈和堆是在程序运行时候动态分配的,局部变量均在栈上分配。 
    43.         栈是反向增长的,地址递减;malloc等分配的内存空间在堆空间。堆是正向增长的,地址递增。   
    44.         r,w变量在栈上(则&r>&w),r,w所指内容在堆中(即r<w)。*/   
    45.  }    
    46.    



    附录:

    栈与堆的区别
               

      

    展开全文
  • 进程空间分配和堆栈大小

    千次阅读 2019-07-19 14:54:40
    进程空间分配和堆栈大小 1. Linux中进程空间的分配情况如下:  从上图可以看出,进程的空间分配:与进程相关的数据结构(页表、内核栈、task) ---> 物理内存 ---> 内核代码和数据---> 用户栈 --->...

    进程空间分配和堆栈大小

    1. Linux中进程空间的分配情况如下:

      从上图可以看出,进程的空间分配:与进程相关的数据结构(页表、内核栈、task) ---> 物理内存 ---> 内核代码和数据  --->  用户栈  ---> 共享库的内存映射区 ---> 运行时堆 --->未初始化数据段.bss --->  已初始化数据段.data  --->  代码段.text

    2. 进程的堆栈大小:

    •  32位Windows,一个进程栈的默认大小是1M,在vs的编译属性可以修改程序运行时进程的栈大小。
    •  Linux下进程栈的默认大小是10M,可以通过 ulimit -s查看并修改默认栈大小。
    •  默认一个线程要预留1M左右的栈大小,所以进程中有N个线程时,Windows下大概有N*M的栈大小。
    •  堆的大小理论上大概等于进程虚拟空间大小-内核虚拟内存大小。windows下,进程的高位2G留给内核,低位2G留给用户,所以进程堆的大小小于2G。Linux下,进程的高位1G留给内核,低位3G留给用户,所以进程堆大小小于3G。

    3. 进程的最大线程数:

    •  32位windows下,一个进程空间4G,内核占2G,留给用户只有2G,一个线程默认栈是1M,所以一个进程最大开2048个线程。当然内存不会完全拿来做线程的栈,所以最大线程数实际值要小于2048,大概2000个。
    •  32位Linux下,一个进程空间4G,内核占1G,用户留3G,一个线程默认8M,所以最多380个左右线程。(ps:ulimit -a 查看电脑的最大进程数,大概7000多个)
    • https://www.cnblogs.com/ladawn/p/8449399.html  
    •  
    • linux查看修改线程默认栈空间大小(ulimit -s)

      1.linux查看修改线程默认栈空间大小 ulimit -s

      a、通过命令 ulimit -s 查看linux的默认栈空间大小,默认情况下 为10240 即10M

      b、通过命令 ulimit -s 设置大小值 临时改变栈空间大小:ulimit -s 102400, 即修改为100M

      c、可以在/etc/rc.local 内 加入 ulimit -s 102400 则可以开机就设置栈空间大小

      d、在/etc/security/limits.conf 中也可以改变栈空间大小:

      #<domain> <type> <item> <value>

      * soft stack 102400

      重新登录,执行ulimit -s 即可看到改为102400 即100M

       

      2.为啥linux要限制用户进程的栈内存大小。

      Why does Linux have a default stack size soft limit of 8 MB?

      The point is to protect the OS.

      Programs that have a legitimate reason to need more stack are rare. On the other hand, programmer mistakes are common, and sometimes said mistakes lead to code that gets stuck in an infinite loop. And if that infinite loop happens to contain a recursive function call, the stack would quickly eat all the available memory. The soft limit on the stack size prevents this: the program will crash but the rest of the OS will be unaffected.

      Note that as this is only a soft limit, you can actually modify it from within your program (see setrlimit(2): get/set resource limits) if you really need to.

     

    展开全文
  • 28-进程空间与 fork 函数原理

    千次阅读 多人点赞 2016-12-18 20:49:18
    前面对 fork 函数牛刀小试,相信你已基本掌握了简单的“影分身术”了,不过在篇末,却为各位留下了一些坑位。为了能够说明白一些问题,本篇将讨论有关...1. 进程空间这里的进程空间,说的就是进程虚拟地址空间。稍微懂

    前面对 fork 函数牛刀小试,相信你已基本掌握了简单的“影分身术”了,不过在篇末,却为各位留下了一些坑位。为了能够说明白一些问题,本篇将讨论有关进程的一些必备知识,以及 fork 函数的底层实现。如此一来,也方便加深对其它有关问题的理解。当然,如果你对此完全不感兴趣,大可跳过。

    本文所讨论的范围,限制在 32 位的 linux 操作系统。

    1. 进程空间

    这里的进程空间,说的就是进程虚拟地址空间。

    稍微懂点编程和操作系统的同学都应该知道,每个进程都有自己的 4GB 虚拟地址空间,这具有深远的意义。有个经典的 C 语言的例子,就是进程 A 中往地址 0x50000000 写入一个 int 类型的整数 100,然后在进程 B 的地址0x50000000 写入另一个 int 类型的整数 1000,最后两个进程再各自通过 printf 函数打印这两个地址保存的值,发现进程 A 仍然可以正常打印 100,而进程 B 正常打印出 1000.

    这段代码,可以在windows 系统的 visual studio 模拟(没有使用 linux 是因为 linux 不直接提供在指定地址上分配内存的函数,另外在概念上,windows 的进程空间和 linux 是互通的,不会有太大区别)。

    • 进程 A 的代码
    #include <windows.h>
    
    int main()
    {
        int *buf = (int*)0x50000000;
        LPVOID res = VirtualAlloc((LPVOID)buf, sizeof(int), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        if (res != buf) {
            printf("ERROR!\n");
            return 1;
        }
    
        *buf = 100;
    
        printf("A = %d\n", *buf);
    
        Sleep(3000);
        return 0;
    }
    • 进程 B 的代码
    
    #include <windows.h>
    
    int main()
    {
        int *buf = (int*)0x50000000;
        LPVOID res = VirtualAlloc((LPVOID)buf, sizeof(int), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
        if (res != buf) {
            printf("ERROR!\n");
            return 1;
        }
    
        *buf = 1000;
    
        printf("B = %d\n", *buf);
    
        Sleep(3000);
        return 0;
    }

    然后开启了两个控制台窗口,同时(只要 A 进程没结束的情况下启动 B 程序就行了)运行这两个程序,结果如下。


    这里写图片描述
    图 1

    形成图 1 中现象的原因在于进程 A 和进程 B 的地址空间是隔离的,尽管它们都在地址 0x50000000 这个位置写了不同的值,但是各自都互不影响。这就好像我在我家菜地的5号地种的土豆,你在你家菜地的5号地种的黄瓜,根本就是谁也挨不着谁。


    这里写图片描述
    图 2

    2 虚拟地址到物理地址的映射

    对于进程来说,其实属于进程那一块菜地,是虚拟的,是假的。这中间有一步到物理地址的映射过程。就好像上面的菜地,你看到的是菜地也是假的。要怎么理解这件事情,难道你看到的不是菜地吗?

    2.1 种菜的故事

    不妨假设一下,亲自下地种菜的人不是你本人,而是由另一个管家代劳。而你,只负责下达命令就行了。当你来到这个世上,你老爸就告诉你,你有一块的菜地,被分成了16个小块,你可以在任意一块地上随便种,你想种什么,种在哪块地上,和管家说一声就行了,你想从地上摘什么菜,也和管家说就行了。你爸把菜地大概的样子画给你看,就像图 2 中的那样。

    除此之外,你还有一个弟弟,你老爸也和你弟说了相同的话,你弟弟也有和你一块一样大小的地,而且你弟弟要种菜,也只要给你家的管家下达命令就行。

    就这样,你和你弟弟就在图 2 那样的菜地上,各自快乐的种着菜,生活了几年;可是你并不知道你家菜地真实情况是长啥样,你弟弟也同样不知道。你和你弟弟知道的菜地的样子,仅仅就是你老爸当初给你们画出来的图 2 的样子。

    其实对你来说,菜地长啥样都无所谓了。你的菜地上有什么,统统问管家,你想在哪块地上种什么,也由管家代劳。

    2.2 菜地长什么样

    就这样,5年过去了。种了 5 年菜的你已经不耐烦了,突然有一天,你想亲自看看你的菜地。你不顾一切管家的阻挠,带着你弟弟,一起来到了所谓的菜地,这时候,你们慌了。你们看到的菜地,实际是图 3 这样的。根本就没有你和你弟弟的菜地,你们种的菜,全都只是在图 3 这块地上。


    这里写图片描述
    图 3

    管家气喘吁吁的正好赶来了,你和你弟弟刚好和他对簿公堂。只有一块地,却骗了你们 5 年。管家是如何做到的?再三追问下,管家拿出了他的两本笔记本,说秘密都在这里了。其中一本记录了你的菜地使用情况,另一本记录了你弟弟的菜地使用情况。

    管家继续说到,如果你说要去你的 5 号地取土豆,他就会翻翻笔记本,看看你的 5 号地对应实际哪块地,比如现在就对应着实际的 3 号地,管家就会去 3 号地把东西取给你。对你弟弟也是一样,只要拿出相应对应的笔记本就可以很快查到。这就好像是下面这个样子。


    这里写图片描述
    图 4

    2.3 进程的那块地

    看完前面的故事,我希望你能够触类旁通,每个进程生来认为自己有 4GB 的虚拟地址,可却被“管家”(操作系统)欺骗了一生,它没有你和你弟那么幸运,最终破解这个分地谜题。进程在自己的土地上心情的挥霍,它意识不到真正的菜地到底有多大(你的物理内存),只要“管家”能够满足它的需求就行了(管家甚至做了内存交换这种事,因为实际的土地只有 0 到 20 号,一共 21 块地,而你和你弟弟的地加起来有 32 块,万一土地不够了,管家就会偷偷的把你们不经常用的土地里的东西取到硬盘上)。

    值得一说的是管家的笔记本,对应到进程这里就是页表的概念,有关这一块的知识,不详细展开,对细节感兴趣的同学,请参考我的另一个笔记《OS学习笔记》

    3. fork 干了什么

    有了进程空间的概念,就很容易知道 fork 做了什么事。以前面的菜地为例,再假设你没有弟弟。fork 的含义有点类似下面这样:

    1. 你告诉你老爸,给我生个弟弟出来。
    2. 你告诉你的管家,给我弟弟种一片和我的菜地一模一样的菜地出来。

    这一切完成后,菜地应该是这样的。


    这里写图片描述
    图 5 你弟弟复制了一份你的菜地

    那么再此之后,你种什么,你弟弟种什么就各不相干了。

    相对于进程来说,这两个进程的进程空间是一模一样

    4. 上一篇的遗留问题

    如果你完成上一篇最后一节总结里的实验,你会惊讶的发现,tmp 文件内容和直接运行 ./myfork 打印在屏幕上的结果是完全不一样的。

    tmp 文件里的内容,应该像下面这样:

    Hello, I'm father
    before fork
    I'm father 3542; my child is 3543
    before fork
    I'm child 3543; my father is 3542

    执行 ./myfork 打印到屏幕上的内容,却是这样:

    Hello, I'm father
    before fork
    I'm father 4382; my child is 4383
    I'm child 4383; my father is 4382

    你比较感兴趣的地方应该在于 tmp 文件里的 before fork 竟然出现了两次!这个谜题在这一篇相信你可以搞清楚,原因在于 printf 这个函数,它是带缓冲区的!!!

    当 printf 接收到字符串后,第一件事情是把字符串复制到一个 char 数组(缓冲区)里,当这个数组遇到了特定的字符,比如 ‘\n’ 字符,回车或者装满等等,就会立即把字符写到屏幕终端上。

    而当我们把 printf 重定向到文件的时候,如果 printf 函数遇到 ‘\n’ 字符,并不会立即把字符写到文件里,这是 printf 函数将字符定向到屏幕和文件的重要区别

    所以当 ./myfork > tmp 这个进程执行到 fork 的时候,printf 里的缓冲区数据还没来得及被刷新到 tmp 文件里,就被 fork 函数复制了,同时,printf 的缓冲区也被复制了一模一样的一份出来。这也就是为什么子进程里为什么子进程也会输出 before fork 的原因。

    5. 关于 fork 函数的返回值

    前一节讲了 fork 就是将进程的地址空间完完全全的复制了一份(不是100%复制,除少数几个地方外),实际在复制完后,操作系统会悄悄的修改复制出来的进程空间里的 fork 函数的返回值,把它改成 0(准确的说不是改,而是根本就没有复制原来的值,直接把它赋值为 0)。

    这也就是子进程 fork 函数返回了 0 的原因。

    6. 写时复制技术

    到此 fork 并没结束。早在 linux 0.11 里,fork 函数就实现了 Copy On Write(COW) 机制,也就是写时复制技术。什么意思呢?

    你告诉管家给你弟弟种一份一模一样的菜地时,管家并没有照做,他干了下面这样的事情:


    这里写图片描述
    图6 读时共享

    不得不说,这管家已经懒到家了……但是也不得不说,管家很聪明。

    当你弟弟只是问问管家,我的 5 号地种了什么?管家什么也不用管,直接查查笔记就告诉你弟弟,这是土豆就行了。

    当你和你弟弟任何一方想挖掉在 5 号地上的土豆了怎么办?这岂不是很糟糕?不会的。聪明的管家早就意识到了这个问题。如果你弟弟需要挖掉他 5 号地,管家这时候才会真正的把你的 5 号地再复制一份出来,然后从真正的土地做一个映射,分配到你弟弟的 5 号地上,你弟弟爱怎么挖怎么挖,都不会影响到你。


    这里写图片描述
    图 7 写时复制

    7. 总结

    • 理解进程空间的概念,知道进程的地址是虚拟的
    • 知道进程地址空间之间是隔离的
    • 理解进程虚拟地址和物理地址之间的映射关系
    • 理解 fork 函数的工作原理
    • 理解为什么子进程的 fork 函数返回 0
    • 理解写时复制技术
    展开全文
  • Linux - 进程(一) 进程空间

    千次阅读 2014-09-06 13:19:44
    Linux 进程空间介绍
  • Linux进程地址空间进程的内存分布

    万次阅读 多人点赞 2018-05-15 20:13:18
    原网址为:https://blog.csdn.net/yusiguyuan/article/details/45155035一 进程空间分布概述 对于一个进程,其空间分布如下图所示: 程序段(Text):程序代码在内存中的映射,存放函数体的二进制代码。初始化过的...
  • 进程地址空间

    千次阅读 2018-08-19 15:33:14
    进程的地址空间 背景 Linux32位操作系统 内核版本:Linux version 2.6.32-754.3.5.el6.i686 Linux可执行程序文件空间布局 查看文件结构命令 size + 可执行文件名 size - list section sizes and ...
  • 进程基本概念、进程地址空间

    千次阅读 2018-03-31 17:54:14
    强调内容今天来谈一谈进程的一些基本概念,认识一些进程状态,重新认识一下程序地址空间进程地址空间),进程调度算法,环境变量等属性。 一、进程 1.什么是进程? 程序的一个执行实例,正在执行的程序,是一个...
  • DLL和进程的地址空间

    万次阅读 2018-11-05 11:38:37
    DLL和进程的地址空间一,MT和MD的区别二,显示链接与隐式链接三,DLL和进程的地址空间 DLL是Windows开发人员经常使用到的一种技术,比如我们经常会把相同功能的代码封装到一个模块中,然后供其他需要使用该模块的...
  • linux 进程地址空间分布

    千次阅读 2018-07-15 22:43:00
    在32位操作系统中,内存空间拥有4GB的寻址能力。操作系统会把高地址的空间分配给内核,称为内核空间。(1)内核空间:默认情况下,Windows将高地址的2GB空间分配给内核,Linux将高地址的1GB空间分配给内核。剩下的2...
  • 经常使用 top 命令了解进程信息,其中包括内存方面的信息。命令top帮助文档是这么解释各个字段的。 VIRT , Virtual Image (kb) RES, Resident size (kb) SHR, Shared Mem size (kb) %MEM, Memory usage(kb) ...
  • 进程虚拟地址空间

    千次阅读 2018-11-06 23:19:45
    进程虚拟地址空间的引入  1.程序与进程的区别 程序: 静态 预先编译好的指令和数据的集合 的一个文件 #菜谱 进程:动态 程序运行的过程 #炒菜的过程  2.虚拟地址空间: 程序运行后拥有自己独立的虚拟空间 大小...
  • 进程4G虚拟内存空间的分配

    千次阅读 2018-11-13 19:23:39
    当一个程序运行时,系统会为每一个进程分配一个4G的虚拟内存空间,用来保存进程运行所需要的各种资源(详细资源列表后面会谈到),并创建task_struct进程控制块,保存进程的属性(进程ID、父进程进程状态、使用的...
  • 进程分析目标: 识别出进程和他们的父进程或者子进程(恶意进程的启动进程和别... 进程地址空间分析目标: 学会怎样从一个内存dump中提取出进程的堆,栈,可执行代码的区域 Cat /proc/&lt;pid&gt;/maps...
  • 当一般数据文件被映射进了进程空间后,区域状态变成映射。 ·   物理存储器 Windows 各系列支持的内存上限是不一样的,从 2G 到 64G 不等。理论上 32 位 CPU ,硬件上只能支持 4G 内存的寻址;能支持超过 4G ...
  • 进程的内存空间布局

    千次阅读 2018-05-09 08:56:01
    进程的内存布局在结构上是有规律的,对于 linux 系统上的进程,其内存空间一般可以粗略地分为以下几大段,从高内存到低内存排列:1、内核态内存空间,其大小一般比较固定(可以编译时调整),但 32 位系统和 64 位...
  • c++进程内存空间分布

    千次阅读 2018-08-06 13:49:52
    c++进程内存空间分布(注意各部分的内存地址谁高谁低,注意栈从高到低分配,堆从低到高分配)  内存分布分为5个部分,从高地址到低地址一次为 栈区(stack),堆区(heap),未初始化数据段(uninitialized data)...
  • pmap命令用于显示一个或多个进程的内存状态,报告进程的地址空间和内存状态信息。 一般使用 pmap pid 一般参数选项如下 -x extended显示扩展格式 -d device显示设备格式 -q quiet不显示header/footer行 -V ...
  • Linux系统下进程内存空间模型

    千次阅读 2018-04-25 16:35:37
    参考链接:https://blog.csdn.net/gfgdsg/article/details/42709943Linux下使用虚拟内存空间给每一个进程,32位操作系统下,每个进程都有独立的4G虚拟内存空间。其中包括:1、内核区:用户代码不可见的区域,页表就...
  • 进程地址空间(分段/分页)

    千次阅读 2018-08-27 12:58:17
    对于一个进程空间分布图如下: 引子:猜猜下面输出结果,为什么呢? #include &amp;lt;stdio.h&amp;gt; #include &amp;lt;unistd.h&amp;gt; #include &amp;lt;stdlib.h&amp;gt; int g_...
  • Linux进程地址空间的理解

    千次阅读 2014-10-28 15:57:40
    补充一点,linux中进程和线程几乎没有什么区别,就是看它是否共享进程地址空间,共享进程地址空间就是线程,反之就是进程。 所以,如果子进程和父进程共享了进程地址空间,那么父子进程都可以看做线程。如果...
  • 操作系统原理:进程地址空间

    千次阅读 2017-03-07 00:17:39
    程序执行后进程地址空间布局则和操作系统密切相关。在将应用程序加载到内存空间执行时,操作系统负责代码段与数据段的加载,并在内存中为这些段分配空间。Linux的进程地址空间大致如下: Linux内核虚拟存储 ...
  • Linux的命名空间详解--Linux进程的管理与调度(二)

    万次阅读 多人点赞 2016-05-12 13:20:26
    日期 内核版本 架构 作者 GitHub CSDN 2016-05-12 ... Linux-进程管理与调度 Linux Namespaces机制提供一种资源隔离方案。 PID,IPC,Network等系统资源不再是全局性的,而是属于特定的Namespace。每个Name
  • 进程虚拟地址为什么是4G大小?

    千次阅读 2018-10-16 21:55:39
    1、创建一个进程时,操作系统会为该进程分配一个 4GB 大小的虚拟 进程地址空间。 之所以是 4GB ,是因为在 32 位的操作系统中,一个指针长度是 4 字节 (32位), 2的32次 方个地址寻址能力是从 0x00000000~0...
  • vfork创建的子进程与父进程地址空间关系 存储unix编程 在《UNIX环境高级编程》一书的第八章中,有一道课后习题如下: 回忆图7-3典型的存储空间布局。由于对应于每个函数调用的栈帧通常存储在栈中,并在...
  • 程序运行时创建进程 链接:将编译后的目标模块链接成一个可执行程序。有静态链接和动态链接之分。 静态链接:在程序运行前,将目标模块链接成一个完整的装入模块 需要做的两个任务:1.修改逻辑地址 2.变换...
  • 内存管理分析之一:Linux进程空间与虚拟地址的好处内存管理分析之二:MMU机制在进程中使用时的一些问题内存管理分析之三:了解这些概念就了解了整个MMU机制使用虚拟地址的好处现代操作系统使用了虚拟地址的方式管理...
  • Linux下x86_64进程地址空间布局

    千次阅读 2015-03-08 23:33:26
    关于Linux 32位内存下的内存空间布局,可以参考这篇博文Linux下C程序进程地址空间局关于源代码中各种数据类型/代码在elf格式文件以及进程空间中所处的段,在x86_64下和i386下是类似的,本文主要关注vm.legacy_va_...
  • linux进程空间

    千次阅读 2011-07-22 10:03:54
    虚拟内存---linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间,该空间是大小为3G,用户看到和接触的都是虚拟地址,无法看到实际的物理地址。利用这种虚拟地址不但能起到保护操作系统的作用,...
  • Linux内存占用分析 进程内存空间

    万次阅读 2012-09-28 14:30:48
    经常使用top命令了解进程信息,其中包括内存方面的信息。命令top帮助文档是这么解释各个字段的。 VIRT , Virtual Image (kb) RES, Resident size (kb) SHR, Shared Mem size (kb) %MEM, Memory usage(kb) SWAP,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 788,233
精华内容 315,293
关键字:

进程空间