Linux内存窥视--brk

    brk()系统调用完成了数据段大小改变的功能,当然包括增加(malloc,申请)和减小(free,释放)两部分了。

    这一系统调用在一般应用中不会出现,但是可以确定一定是被使用最多的,因为其被malloc()调用,malloc()库函数的操作后续给出,但据说,是lib库为应用程序提供了内存管理的方法,当其管理的内存不足的时候,库向内核批量申请一段内存,当然要满足页面对齐的条件,即所分配的空间应该为页面大小的整数倍。

    在查看进程的内存使用情况时,我们也可以通过top得到VSZ的值,也可以使用如下命令得到进程的其他参数:

root@catalyst_24FD52F24E00:/tmp/log# cat /proc/`pidof snmpd`/status

Name:   snmpd

State:  S (sleeping)

Tgid:   4258

Pid:    4258

PPid:   1

TracerPid:      0

Uid:    0       0       0       0

Gid:    0       0       0       0

FDSize: 32

Groups:

VmPeak:     3748 kB

VmSize:     3748 kB

VmLck:         0 kB

VmHWM:      1308 kB

VmRSS:      1308 kB

VmData:      968 kB

VmStk:       136 kB

VmExe:       716 kB

VmLib:      1768 kB

VmPTE:        16 kB

VmSwap:        0 kB

Threads:        1

SigQ:   0/475

SigPnd: 00000000000000000000000000000000

ShdPnd: 00000000000000000000000000000000

SigBlk: 00000000000000000000000000000000

SigIgn: 00000000000000000000000000001004

SigCgt: 0000000000000000000000004000c003

CapInh: 0000000000000000

CapPrm: ffffffffffffffff

CapEff: ffffffffffffffff

CapBnd: ffffffffffffffff

Cpus_allowed:   1

Cpus_allowed_list:      0

voluntary_ctxt_switches:        32233

nonvoluntary_ctxt_switches:     12093

root@catalyst_24FD52F24E00:/tmp/log# 

    其中的VmPeak反应的是该程序所使用所有内存的大小,在内核中,通过mm->total_vm反映出来:

[fs/proc/task_mmu.c: task_mem()]

void task_mem(struct seq_file *m, struct mm_struct *mm)

{

unsigned long data, text, lib, swap;

unsigned long hiwater_vm, total_vm, hiwater_rss, total_rss;


/*

* Note: to minimize their overhead, mm maintains hiwater_vm and

* hiwater_rss only when about to *lower* total_vm or rss.  Any

* collector of these hiwater stats must therefore get total_vm

* and rss too, which will usually be the higher.  Barriers? not

* worth the effort, such snapshots can always be inconsistent.

*/

hiwater_vm = total_vm = mm->total_vm;

...

seq_printf(m,

"VmPeak:\t%8lu kB\n"

"VmSize:\t%8lu kB\n"

"VmLck:\t%8lu kB\n"

"VmHWM:\t%8lu kB\n"

"VmRSS:\t%8lu kB\n"

"VmData:\t%8lu kB\n"

"VmStk:\t%8lu kB\n"

"VmExe:\t%8lu kB\n"

"VmLib:\t%8lu kB\n"

"VmPTE:\t%8lu kB\n"

"VmSwap:\t%8lu kB\n",

hiwater_vm << (PAGE_SHIFT-10),

...

}

    该函数主要是在进程任务的proc下生成对应的status统计信息。

    要想明确获知VmPeak的来源,当然离不开malloc()库函数了,而这一函数将会调用brk()系统调用,下面,开始吧。


    brk()系统调用的部分过程如下:

[mm/mmap.c: brk()]

SYSCALL_DEFINE1(brk, unsigned long, brk)

{

...

/* Ok, looks good - let it rip. */

if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)

goto out;


set_brk:

mm->brk = brk;

out:

retval = mm->brk;

up_write(&mm->mmap_sem);

return retval;

}

    由上面的调用过程不难发现,brk()主要通过do_brk()完成其主要的功能。

    do_brk()的部分代码如下:

[mm/mmap.c: brk()->do_brk()]

/*

 *  this is really a simplified "do_mmap".  it only handles

 *  anonymous maps.  eventually we may be able to do some

 *  brk-specific accounting here.

 */

unsigned long do_brk(unsigned long addr, unsigned long len)

{

...

/*

* Clear old maps.  this also does some error checking for us

*/

 munmap_back:

vma = find_vma_prepare(mm, addr, &prev, &rb_link, &rb_parent);

if (vma && vma->vm_start < addr + len) {

if (do_munmap(mm, addr, len))

return -ENOMEM;

goto munmap_back;

}


/* Check against address space limits *after* clearing old maps... */

if (!may_expand_vm(mm, len >> PAGE_SHIFT))

return -ENOMEM;


if (mm->map_count > sysctl_max_map_count)

return -ENOMEM;


if (security_vm_enough_memory(len >> PAGE_SHIFT))

return -ENOMEM;


/* Can we just expand an old private anonymous mapping? */

vma = vma_merge(mm, prev, addr, addr + len, flags,

NULL, NULL, pgoff, NULL);

if (vma)

goto out;


/*

* create a vma struct for an anonymous mapping

*/

vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);

if (!vma) {

vm_unacct_memory(len >> PAGE_SHIFT);

return -ENOMEM;

}

INIT_LIST_HEAD(&vma->anon_vma_chain);

vma->vm_mm = mm;

vma->vm_start = addr;

vma->vm_end = addr + len;

vma->vm_pgoff = pgoff;

vma->vm_flags = flags;

vma->vm_page_prot = vm_get_page_prot(flags);

vma_link(mm, vma, prev, rb_link, rb_parent);

out:

perf_event_mmap(vma);

mm->total_vm += len >> PAGE_SHIFT;

if (flags & VM_LOCKED) {

if (!mlock_vma_pages_range(vma, addr, addr + len))

mm->locked_vm += (len >> PAGE_SHIFT);

}

return addr;

}

    上面的代码完成了释放、内存申请、内存空间映射关系建立等功能,后续对这些部分进行分解。

    此时关注于另一点:mm中的total_vm成员。

    由上面的代码中发现,total_vm在最后会增加此次调整的长度,而且其单位页面大小,在这里2.6.36为4KB。

    下面一点没有进行对应的验证:由上面的计算来看,应该是在free的时候,total_vm的值会变小才对,因为此时len为负值,所以total_vm应该降低;但是前文说过,lib库为应用程序提供了一个内存管理的方法,只有在其内存不足的时候,才向内核申请内存,然后返回给应用程序其所期望的空间大小,如此,则lib库并不会将应用程序free的内存释放到系统的剩余内存中,所以将会导致total_vm会存在持续增长的情况。那么,此时就存在一个问题了,是lib库导致了total_vm的增长么?total_vm的真正作用是什么?在什么时候,total_vm会降低?为什么有些系统中,



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章