[转载]Linux程序存储结构与进程结构

来源:互联网 发布:政府网络建设形式主义 编辑:程序博客网 时间:2024/05/17 02:55
1.Linux可执行文件结构
      在Linux系统下,程序是一个普通的可执行文件。可执行文件在存储时分为代码区、数据区和未初始化数据区3个部分。各段基本内容说明如下:
      代码区:存放CPU执行的机器指令。通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。
      代码区的指令包括操作码和操作对象(或对象地址引用)。如果是立即数(即是具体的数值),将直接包含在代码中,如果是局部数据,将在运行时在栈区分配空间,然后再引用该数据的地址,如果是未初始化数据区和数据区,在代码中同样将引用该数据的地址。
      全局初始化数据区/静态数据区(数据段),该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
      未初始化数据区,存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空。

2.Linux进程结构
       在Linux系统下,如果将某个可执行文件加载到内存运行,则将演变成一个或多个进程(多个进程的原因是进程在运行时可以再创建新的进程,但加载时只有一个进程)进程是Linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源。进程的环境由当前系统状态及其父进程信息决定和组成的。
       代码区:加载的是可执行文件代码段,其加载到内存中的位置由加载器完成。
       全局初始化数据区/静态数据区:加载的是可执行文件数据段,位置可位于代码段后,也可以分开。程序在运行之初就为该数据段申请了空间,在程序退出时才释放,因此,存储于数据段(全局初始化,静态初始化)的数据的生命周期为整个程序运行过程。
      未初始化数据区:加载的是可执行文件未初始化数据段,位置可以分开也可以紧靠数据段。程序在运行之初就为该部分申请了空间,在程序退出时才释放,因此,存储于该部分的数据的生存周期为整个程序运行过程。
       栈区:由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间的过程。
       堆区:用于动态内存分配,堆在内存中位于未初始化数据段区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时,有可能由OS回收。【分而治之,进程的结构和汇编是最接近的】
     
       auto变量:
       作用域:一对{}内
       生存域:当前函数
       存储位置:变量默认存储类型,存储在栈区。
      
       extern函数:
       作用域:整个程序
       生存域:整个程序运行期
       存储位置:函数默认存储类型,代码段。

       extern变量:
       作用域:整个程序
       生存域:整个程序运行期
       存储位置:初始化在data段,未初始化在BSS段。

       static函数:
       作用域:当前文件
       生存域:整个程序运行期
       存储位置:代码段。

       static全局变量:
       作用域:当前文件
       生存域:整个程序运行期
       存储位置:初始化在data段,未初始化在BSS段。

       static局部变量:
       作用域:一对{}内
       生存域:整个程序运行期
       存储位置:初始化在data段,未初始化在BSS段。

       register变量:
       作用域:一对{}内
       生存域:当前函数
       存储位置:运行时存储在CPU寄存器中。

       字符串常量:
       作用域:当前文件
       生存域:整个程序运行期
       存储位置:数据段。

       常见内存错误:
       1.返回局部变量地址错误:因为局部变量的生存周期仅在当前函数中,因此,如果在某函数返回局部变量将引起内存错误。
      2.临时空间过大:操作系统在加载某个应用程序时,都将为其分配一定大小的栈空间,如果申请过大的局部变量,将出现栈溢出情况。
      3.申请堆空间后未释放:在使用完堆空间后,一定要释放该段内存空间,虽然程序退出时系统会帮助回收这段内存空间。

      静态分配:编译器在处理程序源代码时分配。
      动态分配:程序在执行时调用malloc()库函数申请分配。
      静态内存分配是在程序执行之前进行的,效率比较高,而动态内存分配则可以灵活地处理未知数目的内存空间申请。

      静态与动态内存分配的主要区别如下:
      静态对象是有名字的变量,可以直接对其进行操作,动态对象是没有名字的变量,需要通过指针间接地对它进行操作。
      静态对象的分配与释放由编译器自动处理,动态对象的分配与释放必须由程序员显式地管理,它通过malloc()和free()两个函数(C++中为new和delete)来完成。对于动态内存的唯一访问方式是通过指针间接的访问。
     
      malloc函数用来在堆中申请内存空间:extern void  *malloc(size_t size);
      malloc函数在内存动态存储区中分配一个长度为size字节的连续空间。返回一个指向所分配的连续存储域的起始地址的指针。当函数未能成功分配存储空间时(如内存不足)返回一个NULL指针。
     使用完该段内存空间后,需要调用free()函数释放原先申请的内存空间。
     1.调用free()释放内存后,不能再去访问被释放的内存空间。该段内存被释放后,很有可能该指针仍然指向该内存单元,但这块内存已经不再属于原来的应用程序,此时的指针为悬挂指针,安全起见,将再次置该指针为空。2.不能两次释放相同的指针,因为释放内存空间后,该空间就交给了内存分配子程序,两次释放内存空间会导致错误,也不能用free来释放非malloc()、calloc()、realloc()函数创建的指针空间,在编程时也不要将指针进行自加操作,使其指向动态分配的内存空间中间的某个位置,然后直接释放,这样也可能引起错误。3.malloc/free是配套使用的,即不需要的内存都需要释放回收。

       realloc更改已经配置的内存空间:realloc用来在堆中更改已经配置的内存空间
       extern void *realloc(void *ptr, size_t size);
       1.如果当前内存段后面拥有需要的内存空间,则直接扩展这段内存空间,realloc将返回原指针。
       2.如果当前内存段后面的空闲字节不够,那么就使用堆中第一个能满足这一要求的内存块,将目前的数据复制到新的位置,并将原来的数据块释放掉,并返回新的内存块地址。
       3.如果申请失败,将返回NULL,此时原来指针仍然有效。
      一般不使ptr = realloc(ptr, size);因为会使原来的指针变为不可用。
      该内存分配函数常常造成释放内存管理混乱。

      alloc是malloc函数的简单封装,主要优点是把动态分配的内存初始化为零。
      ptr = (struct data *)calloc(count, sizeof(struct data));

      alloca分配内存空间:alloca()函数用来在栈中(而不是在堆中)分配size个字节的内存空间,所以函数返回的内存空间会被自动释放掉。若分配成功,返回指针,否则,返回NULL。
      extern void *alloca(size_t size);
   
     内存数据管理函数:
     1.memcpy()函数:
        将n个字节从src所指向的位置拷贝到dest所指向的位置,其函数声明如下:
        extern void *memcpy(void *dest, void *src, size n);
     2.memmove()函数:
        检查并处理原空间和目的空间有无重叠的情况,并处理之,然后再进行拷贝。
        extern void *memmove(void *dest, void *src, size n);
     3.memset()函数:
        初始化指定内存单元
        extern void *memset(void *s, int c, size n);
     4.memchr()函数:
        在一段内存空间中查找某个字符位置第一次出现的位置。
        extern void *memchr(const void *s, int c, size n);
     5.memcmp()函数:
        比较内存单元s1和s2起始位置的前n个字节是否相等,如果相等,返回0,如果s1<s2,返回-1,如果s1>s2,则返回1.