如何计算进程占用的物理内存

How to get the process resident set size一文不仅介绍了在WindowsUnix(包括LinuxBSDmacOS等等)操作系统上如何获取进程所使用的峰值和实时物理内存,并提供了现成的代码。在这里我只分析一下Linux上相关功能的实现:

(1)获取峰值内存:

size_t getPeakRSS() {
    struct rusage rusage;
    getrusage(RUSAGE_SELF, &rusage);
    return (size_t)(rusage.ru_maxrss * 1024L);
}

getrusage函数获取进程资源的使用情况。当第一个参数时RUSAGE_SELF时,表示得到当前进程的统计数据。struct rusage中的ru_maxrss即表示该进程物理内存的使用峰值,因为度量单位是kilobytes,故而需要乘以1024

(2)获得实时内存:

size_t getCurrentRSS() {
    long rss = 0L;
    FILE* fp = NULL;
    if ( (fp = fopen( "/proc/self/statm", "r" )) == NULL )
        return (size_t)0L;      /* Can't open? */
    if ( fscanf( fp, "%*s%ld", &rss ) != 1 )
    {
        fclose( fp );
        return (size_t)0L;      /* Can't read? */
    }
    fclose( fp );
    return (size_t)rss * (size_t)sysconf( _SC_PAGESIZE);
}

/proc/self/statm文件共包含7个字段,第二个即是进程当前时刻占用的物理内存,单位是页面大小。要获得精确的字节数,还需通过sysconf系统调用获得页面所占据的空间,通常为4096

 

Linux系统查看可用内存

http://www.linuxatemyram.com/提到使用free命令查看Linux系统使用内存时,used一项会把当前cache的大小也会加进去,这样会造成free这一栏显示的内存特别少:

$ free -m
               total        used        free      shared  buff/cache   available
Mem:           1504        1491          13           0         855      869
Swap:          2047           6        2041

可是实际上,cache根据应用程序的需要是可以回收利用的,因此free这一栏并不能真实地表现有多少“可以使用”的内存。实际系统可用内存应该以available数据为准。

linuxatemyram所提到的free命令也许是比较老的版本,我尝试了RHEL 7.2Ubuntu 16.04Arch Linux3Linux发行版,均没有出现used包含cache的情况:

$ free -m
              total        used        free      shared  buff/cache   available
Mem:          64325       47437        3150        1860       13737       14373

另外,从man free命令中也可以得到,目前计算used的值是要减掉freebuff/cache的:

used Used memory (calculated as total – free – buffers – cache)

可以使用-w命令行选项得到buffcache各自使用的数量:

$ free -wm
              total        used        free      shared     buffers       cache   available
Mem:          64325       48287        2476        1859        1430       12131       13524

需要注意的是,free表示的是当前完全没有被程序使用的内存;而cache在有需要时,是可以被释放出来以供其它进程使用的(当然,并不是所有cache都可以释放,比如当前被用作ramfs的内存)。而available才真正表明系统目前可以提供给应用程序使用的内存。/proc/meminfo3.14内核版本开始提供MemAvailable的值;在2.6.27~3.14版本之间,是free程序自己计算available的值;早于2.6.27版本,available的值则同free一样。

参考资料:
Understanding output of free in Ubuntu 16.04
How can I get the amount of available memory portably across distributions?

Swarmkit笔记(12)——swarmctl创建service时指定资源限制

swarmctl创建service时可以指定CPUmemory资源限制:

# swarmctl service create --help
Create a service

Usage:
  swarmctl service create [flags]

Flags:
......
  --cpu-limit string            CPU cores limit (e.g. 0.5)
  --cpu-reservation string      number of CPU cores reserved (e.g. 0.5)
......
  --memory-limit string         memory limit (e.g. 512m)
  --memory-reservation string   amount of reserved memory (e.g. 512m)
    ......

*-reservation的作用是为container分配并“占住”相应的资源,所以这些资源对container一定是可用的;*-limit是限制container进程所使用的资源。解析资源的代码位于cmd/swarmctl/service/flagparser/resource.go文件。

参考资料:
Docker service Limits and Reservations

docker笔记(16)——为container指定CPU资源

Docker run命令的--cpuset-cpus选项,指定container运行在特定的CPU core上。举例如下:

# docker run -ti --rm --cpuset-cpus=1,6 redis

另外还有一个--cpu-shares选项,它是一个相对权重(relative weight),其默认值是1024。即如果两个运行的container--cpu-shares值都是1024的话,则占用CPU资源的比例就相等。

Freecolor工具简介

FreeBSD没有free命令,但是提供了一个freecolor命令,用来查看还有多少可用的内存。Freecolor官网在这里,其代码逻辑比较简单。如果系统提供/proc/meminfo,就直接从这个虚拟文件系统获取信息,否则就透过libstatgrab这个第三方库。因为FreeBSD默认不会挂载/proc文件系统,所以会使用libstatgrab

Libstatgrab获得FreeBSD系统内存使用状况的代码:

#elif defined(FREEBSD) || defined(DFBSD)
    /*returns pages*/
    size = sizeof(total_count);
    if (sysctlbyname("vm.stats.vm.v_page_count", &total_count, &size, NULL, 0) < 0) {
        RETURN_WITH_SET_ERROR_WITH_ERRNO("mem", SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_page_count");
    }

    /*returns pages*/
    size = sizeof(free_count);
    if (sysctlbyname("vm.stats.vm.v_free_count", &free_count, &size, NULL, 0) < 0) {
        RETURN_WITH_SET_ERROR_WITH_ERRNO("mem", SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_free_count");
    }

    size = sizeof(inactive_count);
    if (sysctlbyname("vm.stats.vm.v_inactive_count", &inactive_count , &size, NULL, 0) < 0) {
        RETURN_WITH_SET_ERROR_WITH_ERRNO("mem", SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_inactive_count");
    }

    size = sizeof(cache_count);
    if (sysctlbyname("vm.stats.vm.v_cache_count", &cache_count, &size, NULL, 0) < 0) {
        RETURN_WITH_SET_ERROR_WITH_ERRNO("mem", SG_ERROR_SYSCTLBYNAME, "vm.stats.vm.v_cache_count");
    }

    /* Of couse nothing is ever that simple :) And I have inactive pages to
     * deal with too. So I'm going to add them to free memory :)
     */
    mem_stats_buf->cache = (size_t)cache_count;
    mem_stats_buf->cache *= (size_t)sys_page_size;
    mem_stats_buf->total = (size_t)total_count;
    mem_stats_buf->total *= (size_t)sys_page_size;
    mem_stats_buf->free = (size_t)free_count + inactive_count + cache_count;
    mem_stats_buf->free *= (size_t)sys_page_size;
    mem_stats_buf->used = mem_stats_buf->total - mem_stats_buf->free;
#elif defined(WIN32)

可以看到,所有free_countinactive_countcache_count都算作free,即可用的内存。

而获得swap使用率则通过kvm接口:

#elif defined(ALLBSD)
    /* XXX probably not mt-safe! */
    kvmd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, NULL);
    if(kvmd == NULL) {
        RETURN_WITH_SET_ERROR("swap", SG_ERROR_KVM_OPENFILES, NULL);
    }

    if ((kvm_getswapinfo(kvmd, &swapinfo, 1,0)) == -1) {
        kvm_close( kvmd );
        RETURN_WITH_SET_ERROR("swap", SG_ERROR_KVM_GETSWAPINFO, NULL);
    }

    swap_stats_buf->total = (long long)swapinfo.ksw_total;
    swap_stats_buf->used = (long long)swapinfo.ksw_used;
    kvm_close( kvmd );

    swap_stats_buf->total *= sys_page_size;
    swap_stats_buf->used *= sys_page_size;
    swap_stats_buf->free = swap_stats_buf->total - swap_stats_buf->used;
#elif defined(WIN32)

“Memory order”分析笔记

以下图片摘自Memory Reordering Caught in the Act,它描述了memory reorder问题:
代码:

marked-example2

实际执行:

reordered

为什么会发生memory reorder?一言以蔽之,因为性能。

在支持memory reorder的系统上,有以下3order需要考虑:

Program order: the order in which the memory operations are specified in the code running on a given CPU.

Execution order: the order in which the individual memory-reference instructions are executed on a given CPU. The execution order can differ from program order due to both compiler and CPU-implementation optimizations.

Perceived order: the order in which a given CPU perceives its and other CPUs’ memory operations. The perceived order can differ from the execution order due to caching, interconnect and memory-system optimizations. Different CPUs might well perceive the same memory operations as occurring in different orders.

Program order是代码里访问内存的顺序。Execution order是代码在CPU上实际执行的顺序,由于编译器优化和CPU的实现,实际指令执行的顺序有可能和代码顺序不一样。Perceived orderCPU用来“感知”自己或者其它CPU对内存操作,由于cachinginterconnect等原因,这个顺序有可能与代码实际的execution order不同。

 

关于memory order的总结:

A given CPU always perceives its own memory operations as occurring in program order. That is, memory-reordering issues arise only when a CPU is observing other CPUs’ memory operations.

An operation is reordered with a store only if the operation accesses a different location than does the store.

Aligned simple loads and stores are atomic.

Linux-kernel synchronization primitives contain any needed memory barriers, which is a good reason to use these primitives.

参考资料:
Memory Reordering Caught in the Act
Memory Ordering in Modern Microprocessors, Part I

Linux kernel 笔记 (7) ——内存地址类型

Linux中有以下几种内存地址类型:

(1)User virtual addresses
用户空间程序(user-space program)所看到和使用的地址。这些都是虚拟地址(virtual address),需要经过转化后,才能变成实际的内存物理地址。每个进程都拥有自己的虚拟地址空间。

(2)Physical addresses
实际的内存物理地址,也即是CPU用来访问物理内存的地址。

(3)Bus addresses
总线地址。外围设备所看到的物理内存地址。通常情况下,这个地址就是实际的内存物理地址。但是如果系统支持IOMMU的话,总线地址和实际的内存物理地址之间需要做一个映射。

(4)Kernel logic addresses
内核逻辑地址。内核逻辑地址会部分或全部地映射到物理内存地址,所以看起来就像实际的物理内存地址一样,并且这种映射是线性的,一对一的。在许多体系结构上,内核逻辑地址和物理内存地址只差一个常量偏移。内核虚拟地址的值通常存储在指针类型或unsigned long类型的变量里。kmalloc函数返回的就是内核逻辑地址。

(5)Kernel virtual addresses
内核虚拟地址。内核虚拟地址同内核逻辑地址相似的地方是都是把内核地址空间和实际物理内存的地址空间做映射,不同之处在于内核虚拟地址不要求这种映射是线性的,一对一的。内核逻辑地址都是内核虚拟地址,但是反过来则不成立。内核虚拟地址的值通常存储在指针类型变量里。vmallockmap返回的都是内核虚拟地址。

__pa(定义在<asm/page.h>)宏用来把内核逻辑地址映射为实际的内存物理地址;__va则用来把实际的内存物理地址映射为内核逻辑地址(仅限于low memory)。

Linux kernel 笔记 (3) ——page和zone

MMU(Memory Management Unit)用来管理内存的最小单位是一个物理页面(physical page,在32-bit系统上通常每个页面为4k64-bit系统上为8k)。Linux kernel使用struct page结构体(定义在<linux/mm_types.h>)来代表每个物理页面:

struct page {
    /* First double word block */
    unsigned long flags;        /* Atomic flags, some possibly
                     * updated asynchronously */
    struct address_space *mapping;  /* If low bit clear, points to
                     * inode address_space, or NULL.
                     * If page mapped as anonymous
                     * memory, low bit is set, and
                     * it points to anon_vma object:
                     * see PAGE_MAPPING_ANON below.
                     */
    ......
}  

受制于一些硬件限制,有些物理页面不能用来进行某种操作。所以kernel把页面分成不同的zone
ZONE_DMA:用来进行DMA操作的页面;
ZONE_DMA32:也是用来进行DMA操作的页面,不过仅针对32-bit设备;
ZONE_NORMAL:包含常规的,用来映射的页面;
ZONE_HIGHMEM:包含不能永久地映射到kernel的地址空间(address space)的页面。
还有其它的zone定义(例如:ZONE_MOVABLE)。zone_type定义在 <linux/mmzone.h>文件里:

enum zone_type {
#ifdef CONFIG_ZONE_DMA
    /*
     * ZONE_DMA is used when there are devices that are not able
     * to do DMA to all of addressable memory (ZONE_NORMAL). Then we
     * carve out the portion of memory that is needed for these devices.
     * The range is arch specific.
     *
     * Some examples
     *
     * Architecture     Limit
     * ---------------------------
     * parisc, ia64, sparc  <4G
     * s390         <2G
     * arm          Various
     * alpha        Unlimited or 0-16MB.
     *
     * i386, x86_64 and multiple other arches
     *          <16M.
     */
    ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
    /*
     * x86_64 needs two ZONE_DMAs because it supports devices that are
     * only able to do DMA to the lower 16M but also 32 bit devices that
     * can only do DMA areas below 4G.
     */
    ZONE_DMA32,
#endif
    /*
     * Normal addressable memory is in ZONE_NORMAL. DMA operations can be
     * performed on pages in ZONE_NORMAL if the DMA devices support
     * transfers to all addressable memory.
     */
    ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
    /*
     * A memory area that is only addressable by the kernel through
     * mapping portions into its own address space. This is for example
     * used by i386 to allow the kernel to address the memory beyond
     * 900MB. The kernel will set up special mappings (page
     * table entries on i386) for each page that the kernel needs to
     * access.
     */
    ZONE_HIGHMEM,
#endif
    ZONE_MOVABLE,
    __MAX_NR_ZONES
};  

如果CPU体系结构允许DMA操作访问任何地址空间,那就没有ZONE_DMA。同理,Intel X86_64处理器可以访问所有的内存空间,也就不存在ZONE_HIGHMEM。 关于“High memory”,可以参考这篇文章