The GNU C Library Reference Manual—Virtual Memory Allocation And Paging笔记(3)

随学随记,暂时未经编程验证  Written by HOOK_TTG(Jamie Jiang)

1、进程需要的一个重要资源就是内存。而内存又使用虚拟内存分页机制进行管理,每页的大小一般是4K字节。虚拟内存是一个很大的线性虚拟地址空间,可以进行数据交换和保存指令。虚拟内存的物理支持硬件可以是物理内存也可以是磁盘。

2、程序不需要关心内存换页机制的具体操作。操作系统会根据实际需要将程序请求的文件等数据读入内存,或者将程序不需要的数据清除或者移出内存并存入磁盘。对程序来说这些操作都是透明的,根本不知道这些底层的操作。

3、进程的虚拟地址空间被划分为很多部分,称为“段”,主要有如下三个段:

  • text:代码段。包含程序的指令、文字量和静态常量。这些都是在被exec系列函数执行时都分配好了内存空间和地址,并且他们的存储空间和地址在从程序执行到程序退出都不会改变。
  • data:数据段。存储程序需要的数据。可以由exec系列函数预分配或者预加载,也可以有程序调用函数改变本段的空间大小。但是,最小尺寸是固定的。
  • stack:堆栈段。包含程序的堆栈区。会根据需要程序需求而增加空间大小,但是不会减小空间尺寸。

4、与内存动态分配相关的函数:

  • void * malloc (size_t size)在“stdlib.h”中定义:创建并返回一个指向“size”指定大小的内存块的指针。如果分配失败,那么就返回“NULL”指针。这个内存块中的内容没有进行初始化,数据是随机的不确定的。需要手动调用“memset”函数进行初始化操作。实际使用中,在为字符串“string”分配内存时,传入参数“size”一定要记住增加一个字节,也就是“size+1”。因为字符串是以“/0”结尾的,需要为这个null字符分配一个空间。
  • void * memset (void *block, int c, size_t size)在“string.h”中定义:这个函数将“c”转换为“unsigned char”类型,并从“block”的内存块第一个字节开始,将“size”个字节的内容都设置成“c”的值,并返回赋值后的内存块指针。
  • void free (void *ptr)在“stdlib.h”中定义:释放由“ptr”指向的内存块,一般是由“malloc”分配的内存块。不要尝试访问已经由“free”释放的内存块的任何数据,否则将发生内存访问错误。如果有必要请在调用“free”前,先将要释放的内存块数据拷贝一份另存以备后用。
  • void * realloc (void *ptr, size_t newsize)在“stdlib.h”中定义:重新设置由“ptr”指向的内存块大小为“newsize”。如果没有空闲的内存空间可用,那么函数将返回一个“NULL”指针。由于“ptr”指向的内存块末尾后面的其他内存空间可能被其他变量等使用;所以,“realloc”将根据需要重新分配一块全新的内存块并将“ptr”指向的内存块内容复制过去,并返回新分配的内存地址指针。如果传入“ptr”参数为“NULL”,那么“realloc”将完成与“malloc”一样的功能。虽然使用方便了,但是有些老的C编译器不支持,将报错。
  • void * calloc (size_t count, size_t eltsize)在“stdlib.h”中定义:分配由“count”和“eltsize”乘积也就是“count*eltsize”大小的内存块,并将内存块的内容全部以“0”填充。

5、一般来说,“malloc”分配的内存块地址是以8的倍数对齐的,对于64位系统则是16的倍数对齐。

6、GNU C允许程序员编写钩子(HOOK)函数来修改“malloc”、“realloc”和“free”这三个函数的执行动作。撰写钩子函数有助于调试程序。在实际操作中务必要注意保存和恢复原函数地址,防止错误的递归调用。

可以将自己编写的钩子函数分别赋值给以下变量即可,在“malloc.h”中定义:

  • __malloc_initialize_hook:钩子函数原型 static void my_init_hook (void)
  • __malloc_hook:钩子函数原型 void *function (size_t size, const void *caller);“caller”参数是在调用“malloc”之前在“stack”栈上的地址。这个参数值允许跟踪程序的内存消耗。
  • __realloc_hook:钩子函数原型 void *function (void *ptr, size_t size, const void *caller)
  • __free_hook:钩子函数原型 void function (void *ptr, const void *caller)
  • __memalign_hook:钩子函数原型 void *function (size_t alignment, size_t size, const void *caller)

7、GNU C还扩展支持一种调试功能。在“mcheck.h”中定义了两个函数:void mtrace (void)void muntrace (void)。可以用来检测程序是否存在内存溢出等问题。“mtrace”函数将在“malloc”、“realloc”和“free”被调用时向一个指定的文件写入调试信息。

#include <mcheck.h>
int
main (int argc, char *argv[])
{
#ifdef DEBUGGING
mtrace ();
#endif
...
}

8、“Obstacks”是一个包含很多对象的内存池。这里的对象泛指任意的数据类型,与C++里面的面向对象的对象概念定义不一样。程序员可以创建很多独立的“Obstacks”并向其中添加对象。“Obstacks”可以包含很多任意大小的对象,但是这些对象在“Obstacks”中都有边界约束,因此不会混乱。

9、在“obstack.h”头文件中定义了一个“obstack”结构原型:struct obstack;这个结构有一个最小的固定尺寸,里面记录了“obstack”状态和为每个对象分配的内存地址,但是不包含对象自身的任何数据。所以不要尝试通过这个“obstack”结构内容直接访问对象。需要调用下述一些列函数来处理。

10、在“obstack”中的对象被打包成一个很大的内存块,这个块成为“chunks”。“obstack”结构指向一个“chunks”链表。“obstack”是自动管理内存分配和释放的,不需程序员关心内存问题。

11、但是,“obstack”是一个抽象层的应用框架对象,它没有向系统申请和释放内存的能力;因此如果要使用“obstack”,需要为他提供一个内存分配函数和内存释放函数。

       具体操作如下:

       a、首先在用户程序中定义如下信息:

  •  
    •  #define obstack_chunk_alloc xmalloc:将“xmalloc”函数作为“obstack”对象的内存分配函数;
    • #define obstack_chunk_free free:将“ free”函数作为“ obstack”对象的内存释放函数;

             并且创建一个错误句柄回调函数,这个函数在obstack分配内存失败时会调用;因此可以通过这个回调函数来对失败时做一些想做

             的事情。这个函数没有返回值,但是在函数实现代码中应该调用“exit”或者“longjmp”来终止程序运行或者长跳转至其他目标区

             域。

             示例:

             void my_obstack_alloc_failed (void)
               ...
             obstack_alloc_failed_handler = &my_obstack_alloc_failed
;

        b、在使用“obstack”前,需要调用“obstack_init”函数初始化一个“obstack”对象;

             原型定义:int obstack_init (struct obstack *obstack-ptr);可以有两种初始化obstack的方法。

             示例(1):            

             static struct obstack myobstack;//定义一个静态obstack结构类型变量
               ...
             obstack_init (&myobstack);//初始化这个变量

             示例(2):

             struct obstack *myobstack_ptr
                           = (struct obstack *) xmalloc (sizeof (struct obstack));//定义完obstack结构类型变量后,可以手动调用

                                                                                                                     //xmalloc函数为变量分配内存
             obstack_init (myobstack_ptr);//初始化这个变量

       有一点需要注意,由于“obstack_init ”函数总是返回“1”,在以前的版本中在内存分配错误是会返回“0”。

 12、用于“obstack”操作的系列函数:

      a、用于向obstack中分配对象的函数,下列函数在分配内存错误时都会调用obstack_alloc_failed_handler回调函数:

  •  
    •   void * obstack_alloc (struct obstack *obstack-ptr, int size):向“obstack-ptr”指定的“obstack”结构中申请分配一个“size”大小的内存空间,但是不对这个内存空间的原始数据进行初始化操作。执行完毕,返回一个指向这个被分配内存空间的地址。

                    示例:

struct obstack string_obstack;//声明一个obstack结构的变量

char *copystring (char *string)
{
        size_t len = strlen (string) + 1
;//这里计算将要分配的内存空间大小,由于C语言中字符串的末尾以'/0'结

                                       束,因此取“string”参数字符串长度时没有包含进'/0'字符的占位空间,所以要加“1”。
        char *s = (char *) obstack_alloc (&string_obstack, len);//申请分配内存
        memcpy (s, string, len);//将输入的字符串复制到string_obstack中申请好的内存空间中。
        return s;//返回新分配的内存地址
}

  •  
    • void * obstack_copy (struct obstack *obstack-ptr, void *address, int size):分配“size”大小的内存空间,然后将从“address”地址开始复制“size”个字节到新分配的内存空间。
    • void * obstack_copy0 (struct obstack *obstack-ptr, void *address, int size):功能同上,但是会在完成复制后,在复制数据的尾部附加上' /0'字符。这个附加的' /0'字符不占用“ size”参数指定的大小。

                    示例:同样完成上面复制字符串的功能

char *obstack_savestring (char *addr, int size)
{
           return obstack_copy0 (&myobstack, addr, size);
}

      b、释放obstack中对象的函数

  •  
    • void obstack_free (struct obstack *obstack-ptr, void *object):如果“ object”参数传入了 null空指针,那么将释放“ obstack-ptr”指定的“ obstack”中的所有对象,并且“ obstack-ptr”将指向一个未初始化的“ obstack”对象。如果“ object”参数传入了一个有效的对象地址,那么就只释放这个对象所占用的内存空间。这个对象释放后,先前它占用的内存空间将来可以分配给其他的加入的对象。如果一个“ obstack”中的对象都是放完了,那么它先前占用的内存空间将被回收重新编组到 chunks中,以备后用。

13、有一点需要注意,不同的编译器,对操作obstack的接口实现也不同,可能用函数或者宏(macro)实现。如果使用老的编译器,可能将接口用宏实现。那么你可以向函数一样调用这个宏,但是却有很多操作是不允许的。例如,不能取得宏的地址。

        另外,调用宏还需要特别注意一点:由于宏代码是内联展开的,所以宏的第一个参数可能会被调用很多次,那么就必须保证这个参数在每次调用时都保证值得一致性。否则,程序运行后可能会发生不可预料的结果。

        在ISO标准C中,每个功能都被实现成一个函数和一个宏,根据实际需要可以获取函数的指针,但是一般情况下都使用宏。函数和宏在实际使用中的区别,看示例:

char *x;
void *(*funcp)
();//定义一个函数指针变量
/* 下面代码使用的是宏*/
x = (char *) obstack_alloc (obptr, size);
/* 下面代码使用的是函数,注意“(...)” */
x = (char *) (obstack_alloc) (obptr, size);
/* 取得函数地址,赋给funcp变量 */
funcp = obstack_alloc;

但是,如果使用GNU C编译器的话,就不需要注意这些了。因为GNU C做了语义扩展,允许使用宏时只计算一次第一个参数的值。

14、由于obstackchunks需要持续使用内存,可能一次加一个对象,也可能添加一个或多个字节。所以就需要obstack是个可增长的对象。程序员不需要关心是obstack内部如何实现增长的。这里提供了一些函数来实现手动增长obstack对象,但是在增长目的完成后,需要明确调用“obstack_finish”函数来说明增长完毕。在增长的过程中对象的地址是不确定的,因为obstack会根据空间需要将对象复制到一个新的chunk中;所以,在增长过程中不要对这个对象进行操作,否则可能造成对象的数据覆盖掉其他的对象数据,造成多个对象间的数据重叠。在程序运行中将造成不可预料的结果。

函数如下:

  • void obstack_blank (struct obstack *obstack-ptr, int size):obstack-ptr添加一块大小为“size”的未初始化内存空间。如果“size”传入了一个负数,那么将会缩小obstack-ptr的内存空间。但是绝对不能传入一个“0”,否则将会产生不可预料的结果。
  • void obstack_grow (struct obstack *obstack-ptr, void *data, int size):基本功能与“obstack_copy”函数类似。向obstack-ptr添加一块大小为“size”的内存空间,并将从“data”地址开始“size”个字符拷贝到这个分配的内存中。
  • void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size):基本功能与“obstack_copy0”函数类似。向obstack-ptr添加一块大小为“size”的内存空间,并将从“data”地址开始“size”个字符拷贝到这个分配的内存中。最后在拷贝过来的字符序列尾部附加上'/0',这个'/0'所占的“1”个字符空间不包含在“size”中,属于自动追加的。
  • void obstack_1grow (struct obstack *obstack-ptr, char c):一次向obstack-ptr添加一个字符'c'。
  • void obstack_ptr_grow (struct obstack *obstack-ptr, void *data):obstack-ptr中添加sizeof(void*)个字节空间。
  • void obstack_int_grow (struct obstack *obstack-ptr, int data):obstack-ptr中添加size(int)个字节空间,并将“data”的值赋给这个添加的字节空间。
  • void * obstack_finish (struct obstack *obstack-ptr):调用此函数明确说明obstack-ptr增长完毕。此时返回的才是obstack-ptr真实有效地地址指针,然后就可以进行通常obstack对象的操作了。

15、为了在增长obstack的过程中跟踪当前obstack的内存占用大小,可以调用“obstack_object_size”函数。而且一定要在调用“obstack_finish”函数前调用这个函数,否则本函数将返回“0”。

函数原型:int obstack_object_size (struct obstack *obstack-ptr)

如果想在增长obstack的过程中想撤销增长,那么需要先调用“obstack_finish”函数完成增长,然后立刻调用“obstack_free”函数释放增长的空间,这样obstack就恢复到增长前的状态。示例:obstack_free (obstack_ptr, obstack_finish (obstack_ptr));

16、如果需要频繁的增长obstack,那么就最好调用如下扩展的快速函数。因为上述的增长函数会在添加对象时检查当前obstack的空余空间是否够用,然后再添加对象,这样效率太低了。而下述的快速增长函数则不考虑空间问题,空间是否够用需要程序员在增长前先检查当前obstack的空间是否够用,然后再进行增长操作。

  • int obstack_room (struct obstack *obstack-ptr):获取当前obstack-ptr的空余空间大小。
  • void obstack_1grow_fast (struct obstack *obstack-ptr, char c)
  • void obstack_ptr_grow_fast (struct obstack *obstack-ptr, void *data)
  • void obstack_int_grow_fast (struct obstack *obstack-ptr, int data)
  • void obstack_blank_fast (struct obstack *obstack-ptr, int size)

示例:

 void add_string (struct obstack *obstack, const char *ptr, int len)
{
    while (len > 0)
   {
      int room = obstack_room (obstack)
;//获取当前obstack空余空间的大小
      if (room == 0)
     {
        /* obstack没有足够的空闲空间了。向obstack添加一个字符,

             这样系统就会在分配一个新的有足够空间的chunk。*/
        obstack_1grow (obstack, *ptr++);
        len--;
     }
     else
    {
  /* 如果有足够的空余空间,那么就调用快速函数每次添加一个字符*/
        if (room > len)
        room = len;
        len -= room;
        while (room-- > 0)
        obstack_1grow_fast (obstack, *ptr++);
     }
   }
}

还在学习中...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值