快乐虾
http://blog.csdn.net/lights_joy/
本文适用于
Cygwin checkout-2008-09-28
vs2008
欢迎转载,但请保留作者信息
cygheap是cygwin中使用的一项关键技术,通过自己定义的一块heap空间,可以向fork生成的子进程传递一些特殊的数据。在cygwin提供的文档里这样描述:
Cygwin has recently adopted something called the "cygwin heap". This is
an internal heap that is inherited by forked/execed children. It
consists of process specific information that should be inherited. So
things like the file descriptor table, the current working directory,
and the chroot value live there.
本文对此进行简要介绍。
在cygwin里定义了一个全局变量:
init_cygheap /*NO_COPY*/ *cygheap;
在这里init_cygheap是在cygheap.h中定义的一个结构体:
struct init_cygheap
{
_cmalloc_entry *chain;
char *buckets[32];
cygheap_root root;
cygheap_user user;
user_heap_info user_heap;
mode_t umask;
HANDLE console_h;
cwdstuff cwd;
dtable fdtab;
#ifdef DEBUGGING
cygheap_debug debug;
#endif
struct cyg_sigaction *sigs;
fhandler_tty_slave *ctty; /* Current tty */
#ifdef NEWVFORK
fhandler_tty_slave *ctty_on_hold;
#endif
struct _cygtls **threadlist;
size_t sthreads;
pid_t pid; /* my pid */
HANDLE pid_handle; /* handle for my pid */
struct { /* Equivalent to using LIST_HEAD. */
class inode_t *lh_first;
} inode_list; /* Global inode pointer for adv. locking. */
hook_chain hooks;
void close_ctty ();
int manage_console_count (const char *, int, bool = false) __attribute__ ((regparm (3)));
private:
int console_count;
};
的确如文档所说,它存放了一些进程相关的私有数据。除了cygheap这个全局变量之外,还定义了另一个相关的指针:
void /*NO_COPY*/ *cygheap_max;
这个指针将指向cygheap+1的位置,这也是动态内存分配的起点。
先看看cygwin对上述两个全局指针的初始化:
extern "C" void __stdcall
cygheap_init ()
{
cygheap_protect.init ("cygheap_protect");
if (!cygheap)
{
cygheap = (init_cygheap *) memset (_cygheap_start, 0,
_cygheap_mid - _cygheap_start);
cygheap_max = cygheap;
_csbrk (sizeof (*cygheap));
}
if (!cygheap->fdtab)
cygheap->fdtab.init ();
if (!cygheap->sigs)
sigalloc ();
}
这里提及的两个变量_cygheap_start和_cygheap_mid,它们的值并不在任何c或者c++文件中定义,而是在链接脚本里定义,由链接器在执行链接时计算而得。
.idata ALIGN(__section_alignment__) :
{
……………….
. = ALIGN(16);
__cygheap_start = ABSOLUTE(.);
. = ALIGN(0x10000);
}
.cygheap ALIGN(__section_alignment__) :
{
__cygheap_mid = .;
*(.cygheap)
. = . + (512 * 1024);
. = ALIGN(512 * 1024);
}
__cygheap_end = ABSOLUTE(.);
__cygheap_end1 = __cygheap_mid + SIZEOF(.cygheap);
通过这段链接脚本,cygwin创建了一个叫.cygheap的段,其大小为512K,但是cygheap这个全局指针的值却并不指向这个heap的首地址,而是从数据段的末尾就开始了。
在cygheap_init函数中还调用了一个叫_csbrk的函数:
static void *__stdcall
_csbrk (int sbs)
{
void *prebrk = cygheap_max;
size_t granmask = getpagesize () - 1;
char *newbase = nextpage (prebrk);
cygheap_max = (char *) cygheap_max + sbs;
if (!sbs || (newbase >= cygheap_max) || (cygheap_max <= _cygheap_end))
/* nothing to do */;
else
{
……………..
}
return prebrk;
}
在这个函数里改变了cygheap_max的值,从而保证了cygheap和cygheap_max之间足以存放一个init_cygheap结构体。
在cygheap_max和_cygheap_end之间的空间将用于cygwin内部的动态内存分配。
Vs2008并不支持链接脚本,因此我们只能通过其它方法达到目的:
#pragma section(".cygheap", read, write)
__declspec(allocate(".cygheap"))
char _cygheap_start[1024 * 1024];
char *_cygheap_mid = _cygheap_start + sizeof(init_cygheap);
char *_cygheap_end = _cygheap_start + 1024 * 1024;
就这样,我们在生成的cygwin.dll中插入一个叫.cygheap的数据段,只是我们把它的大小改为1M,而不是512K。
下面就是dump出来的段信息:
SECTION HEADER #5
.cygheap name
100000 virtual size
123000 virtual address (10123000 to 10222FFF)
100000 size of raw data
FEC00 file pointer to raw data (000FEC00 to 001FEBFF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
C0000040 flags
Initialized Data
Read Write
关于在heap上进行内存分配的算法,文档这样说明:
The cygheap memory allocation functions are adapted from memory
allocators developed by DJ Delorie. They are similar to early BSD
malloc and are intended to be relatively lightweight and relatively
fast.
下面看看它到底是怎样进行内存管理的。
当fork子进程时,heap也要复制到子进程中,在此过程中似乎要用到数据类型进行判断(??),为此cygwin在分配内存时要求附加类型信息。
enum cygheap_types
{
HEAP_FHANDLER,
HEAP_STR,
HEAP_ARGV,
HEAP_BUF,
HEAP_MOUNT,
HEAP_SIGS,
HEAP_ARCHETYPES,
HEAP_TLS,
HEAP_COMMUNE,
HEAP_1_START,
HEAP_1_HOOK,
HEAP_1_STR,
HEAP_1_ARGV,
HEAP_1_BUF,
HEAP_1_EXEC,
HEAP_1_MAX = 100,
HEAP_2_STR,
HEAP_MMAP = 200
};
Cygwin使用了一个结构体对此进行描述:
struct cygheap_entry
{
int type;
struct cygheap_entry *next;
char data[0];
};
每一块动态内存的前面都是这样一个结构体,不过在cygwin代码里并没有用到next这个成员。
为了便于管理,cygwin按8,16,32,64…..为大小分配内存,不足的将向上对齐。在每个内存块的前面又添加了一个结构体进行描述:
struct _cmalloc_entry
{
union
{
DWORD b;
char *ptr;
};
struct _cmalloc_entry *prev;
char data[4];
};
cygwin使用了一个数组保存不同大小的内存块的头指针:
char *buckets[32];
当内存回收时,将把内存块按大小链接到空闲的链表中。这样在内存分配时如果发现有空闲块可用则直接从链表中取出使用就可以了,当然速度快。
内存分配由cmalloc完成:
inline static void *
cmalloc (cygheap_types x, DWORD n, const char *fn)
{
cygheap_entry *c;
MALLOC_CHECK;
c = (cygheap_entry *) _cmalloc (sizeof_cygheap (n));
return creturn (x, c, n, fn);
}
extern "C" void * __stdcall
cmalloc (cygheap_types x, DWORD n)
{
return cmalloc (x, n, NULL);
}
其核心分配算法由_cmalloc函数完成:
static void *
_cmalloc (unsigned size)
{
_cmalloc_entry *rvc;
unsigned b, sz;
/* Calculate "bit bucket" and size as a power of two. */
for (b = 3, sz = 8; sz && sz < size; b++, sz <<= 1)
continue;
cygheap_protect.acquire ();
if (cygheap->buckets[b])
{
rvc = (_cmalloc_entry *) cygheap->buckets[b];
cygheap->buckets[b] = rvc->ptr;
rvc->b = b;
}
else
{
rvc = (_cmalloc_entry *) _csbrk (sz + sizeof (_cmalloc_entry));
if (!rvc)
{
cygheap_protect.release ();
return NULL;
}
rvc->b = b;
rvc->prev = cygheap->chain;
cygheap->chain = rvc;
}
cygheap_protect.release ();
return rvc->data;
}
在没有内存块释放的情况下,cygheap->buckets[b]将为空指针,此时将调用_csbrk函数按从低到高的顺序从heap空间中取出一块来。
内存回收由cfree函数完成:
static void __stdcall
_cfree (void *ptr)
{
cygheap_protect.acquire ();
_cmalloc_entry *rvc = to_cmalloc (ptr);
DWORD b = rvc->b;
rvc->ptr = cygheap->buckets[b];
cygheap->buckets[b] = (char *) rvc;
cygheap_protect.release ();
}
可以看到它就是很简单地把这个块放到空闲块的链表中去。
cygwin关键技术:cygheap(2009-9-2)
cygwin关键技术:tls(2009-8-24)
在vs2008下使用cygwin(23):stdin,stdout和stderr(2008-10-21)
在vs2008下使用cygwin(22):使用tls(2008-10-20)