精华内容
下载资源
问答
  • 一、iOS 获取自身 App 内存占用 照例先从 iOS 开始。iOS 由于系统限制,App 层面只能获取自身的内存信息,无法获取其他 App 的内存信息。所以我们先看如何获取自己 App 的内存信息。 系统接口使用很简单,参考滴滴...

    来源https://justinyan.me/post/3982

    一、iOS 获取自身 App 内存占用

    照例先从 iOS 开始。iOS 由于系统限制,App 层面只能获取自身的内存信息,无法获取其他 App 的内存信息。所以我们先看如何获取自己 App 的内存信息。

    系统接口使用很简单,参考滴滴开源的 DoraemonKit 的实现如下:

    + (NSInteger)useMemoryForApp{
        task_vm_info_data_t vmInfo;
        mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
        kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
        if(kernelReturn == KERN_SUCCESS)
        {
            int64_t memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
            return memoryUsageInByte/1024/1024;
        }
        else
        {
            return -1;
        }
    }
    
    //设备总的内存
    + (NSInteger)totalMemoryForDevice{
        return [NSProcessInfo processInfo].physicalMemory/1024/1024;
    }
    

    关键 API 还是 task_info(),取当前进程的信息,第一个参数为当前进程的 mach port(可参考上一篇讲过对这个 mach port 构造的实现),传入参数 TASK_VM_INFO 获取虚拟内存信息,后两个参数是返回值,传引用。

    可以看到 task_vm_info_data_t 里的 phys_footprint 就是当前进程的内存占用,以 byte 为单位。腾讯开源的 Matrix亦使用一致的实现。

    footprint 这个术语在 Apple 的文档里有曰过: Technical Note TN2434: Minimizing your app's Memory Footprint

    有了当前进程的内存,再获取整个手机的内存,比一下就有当前进程的内存占用率了。获取手机的物理内存信息可以用 NSProcessInfo 的 API,如上面 DoraemonKit 的实现。也可以像腾讯的 Matrix 一样用 sysctl() 的接口:

    + (int)getSysInfo:(uint)typeSpecifier
    {
        size_t size = sizeof(int);
        int results;
        int mib[2] = {CTL_HW, (int) typeSpecifier};
        sysctl(mib, 2, &results, &size, NULL, 0);
        return results;
    }
    
    + (int)totalMemory
    {
        return [MatrixDeviceInfo getSysInfo:HW_PHYSMEM];
    }
    

    1.1 task_info() 函数实现

    kern_return_t
    task_info(
        task_t          task,
        task_flavor_t       flavor,
        task_info_t     task_info_out,
        mach_msg_type_number_t  *task_info_count)
    

    这个函数位于 osfmk/kern/task.c 内部实现并不复杂,大家可以直接看源码。

    函数的第一个参数是用作内核与发起系统调用的进程做 IPC 通信的 mach port,第二个参数是获取信息的类型,函数里一顿 switch-case 猛如虎,剩下就是回传数据了。

    我们看看 TASK_VM_INFO 的 case,这个case 和 TASK_VM_INFO_PURGEABLE 共享逻辑,后者会多一些 purgeable_ 开头的数据返回。

    首先内核会判断调用方是内核进程还是用户进程,内核进程取内核的 map,用户进程去该进程的 map,并加锁。接着就是一顿 map 信息读取了。最后解锁。

    // osfmk/kern/ledger.c
    // 赋值
    vm_info->phys_footprint =
                    (mach_vm_size_t) get_task_phys_footprint(task);
    
    
    // 取自 task_ledgers
    uint64_t get_task_phys_footprint(task_t task)
    {
        kern_return_t ret;
        ledger_amount_t credit, debit;
    
        ret = ledger_get_entries(task->ledger, task_ledgers.phys_footprint, &credit, &debit);
        if (KERN_SUCCESS == ret) {
            return (credit - debit);
        }
    
        return 0;
    }
    

    task_ledgers 是内核维护的对该进程的"账本",每次为该进程分配和释放内存页的时候就往账本上记录一笔,并且分了多个不同的种类。

    // osfmk/kern/task.c
    void
    init_task_ledgers(void)
    

    这个初始化函数里大概创建了 30 种不同类型的账本,phys_footprint 是其中一个。

    // osfmk/i386/pmap.h
    // osfmk/arm/pmap.h
    
    // 增加操作,即分配内存,以页为单位
    #define    pmap_ledger_debit(p, e, a) ledger_debit((p)->ledger, e, a)
    
    // 减少操作,即释放内存,以页为单位
    #define    pmap_ledger_credit(p, e, a) ledger_credit((p)->ledger, e, a)
    

    每次内核为该进程分配和释放内存时就往上记录一笔,以此来追踪进程的内存占用。这里假设各位读者都已了解虚拟内存以及为何按内存页(Memory Page)来分配的相关知识,如果有疑问可 Google 之。

    pmap Mach 内核用来管理内存的一整套系统,代码古老且复杂,一个函数动辄四、五百行。而且 pmap 对于不同的机器有不同的实现,代码中区分了 i386 和 arm 两种实现。本人才疏学浅,一时半会也学不会,只能日后再做学习。不过通过以上代码追踪,我们可以知道为何在 iOS 中读取 phys_footprint 就能得到当前进程的内存占用。

    1.2 task_vm_info_data_ 数据结构

    task_vm_info_data_t 里除了 phys_footprint 还有很多别的东西,我们可以看看这个结构体的定义:

    #define TASK_VM_INFO       22
    #define TASK_VM_INFO_PURGEABLE 23
    
    struct task_vm_info {
                // 虚拟内存大小,以 byte 为单位
            mach_vm_size_t  virtual_size;
        // Memory Region 个数
        integer_t   region_count;
        // 内存分页大小
        integer_t   page_size;
                // 实际物理内存大小,以 byte 为单位
            mach_vm_size_t  resident_size;
            // _peak 记录峰值,写入时会作比较,比原来的大才会更新
            mach_vm_size_t  resident_size_peak;
    
        // 带 _peak 的都是运行过程中记录峰值的
        mach_vm_size_t  device;
        mach_vm_size_t  device_peak;
        mach_vm_size_t  internal;
        mach_vm_size_t  internal_peak;
        mach_vm_size_t  external;
        mach_vm_size_t  external_peak;
        mach_vm_size_t  reusable;
        mach_vm_size_t  reusable_peak;
        mach_vm_size_t  purgeable_volatile_pmap;
        mach_vm_size_t  purgeable_volatile_resident;
        mach_vm_size_t  purgeable_volatile_virtual;
        mach_vm_size_t  compressed;
        mach_vm_size_t  compressed_peak;
        mach_vm_size_t  compressed_lifetime;
    
        /* added for rev1 */
        mach_vm_size_t  phys_footprint;
    
        /* added for rev2 */
        mach_vm_address_t   min_address;
        mach_vm_address_t   max_address;
    };
    typedef struct task_vm_info task_vm_info_data_t;
    

    二、iOS/Mac 上获取系统内存占用信息

    在 macOS 上我们在终端运行 vm_stat 可以看到以下内存信息输出输出:

    ➜  darwin-xnu git:(master) vm_stat
    Mach Virtual Memory Statistics: (page size of 4096 bytes)
    Pages free:                              349761.
    Pages active:                           1152796.
    Pages inactive:                         1090213.
    Pages speculative:                        22734.
    Pages throttled:                              0.
    Pages wired down:                        979685.
    Pages purgeable:                         519551.
    "Translation faults":                 300522536.
    Pages copy-on-write:                   16414066.
    Pages zero filled:                     94760760.
    Pages reactivated:                      4424880.
    Pages purged:                           4220936.
    File-backed pages:                       480042.
    Anonymous pages:                        1785701.
    Pages stored in compressor:             2062437.
    Pages occupied by compressor:            598535.
    Decompressions:                         4489891.
    Compressions:                          11890969.
    Pageins:                                6923471.
    Pageouts:                                 38335.
    Swapins:                                  87588.
    Swapouts:                                432061.
    

    这个系统命令就是通过 host_statistics64() 获取的,代码可见这里。使用的是这个接口:

    // osfmk/kern/host.c
    kern_return_t
    host_statistics64(host_t host, host_flavor_t flavor, host_info64_t info, mach_msg_type_number_t * count)
    

    照例第一个参数填 mach_host_self(),用于跟内核 IPC。第二个参数是取的系统统计信息类型,我们要取内存,所以填 HOST_VM_INFO64。剩下两个就是返回的数据了。

    返回的数据类型会 cast 成 vm_statistics64_t

    // osfmk/mach/vm_statistics.h
    
    /* 
     * vm_statistics64
     *
     * History:
     *  rev0 -  original structure.
     *  rev1 -  added purgable info (purgable_count and purges).
     *  rev2 -  added speculative_count.
     *     ----
     *  rev3 -  changed name to vm_statistics64.
     *      changed some fields in structure to 64-bit on 
     *      arm, i386 and x86_64 architectures.
     *  rev4 -  require 64-bit alignment for efficient access
     *      in the kernel. No change to reported data.
     *
     */
    
    struct vm_statistics64 {
        natural_t   free_count;     /* # 空闲内存页数量,没有被占用的 */
        natural_t   active_count;       /* # 活跃内存页数量,正在使用或者最近被使用 */
        natural_t   inactive_count;     /* # 非活跃内存页数量,有数据,但是最近没有被使用过,下一个可能就要干掉他 */
        natural_t   wire_count;     /* # 系统占用的内存页,不可被换出的 */
        uint64_t    zero_fill_count;    /* # Filled with Zero Page 的页数 */
        uint64_t    reactivations;      /* # 重新激活的页数 inactive to active */
        uint64_t    pageins;        /* # 换入,写入内存 */
        uint64_t    pageouts;       /* # 换出,写入磁盘 */
        uint64_t    faults;         /* # Page fault 次数 */
        uint64_t    cow_faults;     /* # of copy-on-writes */
        uint64_t    lookups;        /* object cache lookups */
        uint64_t    hits;           /* object cache hits */
        uint64_t    purges;         /* # of pages purged */
        natural_t   purgeable_count;    /* # of pages purgeable */
        /*
         * NB: speculative pages are already accounted for in "free_count",
         * so "speculative_count" is the number of "free" pages that are
         * used to hold data that was read speculatively from disk but
         * haven't actually been used by anyone so far.
         * 
         */
        natural_t   speculative_count;  /* # of pages speculative */
    
        /* added for rev1 */
        uint64_t    decompressions;     /* # of pages decompressed */
        uint64_t    compressions;       /* # of pages compressed */
        uint64_t    swapins;        /* # of pages swapped in (via compression segments) */
        uint64_t    swapouts;       /* # of pages swapped out (via compression segments) */
        natural_t   compressor_page_count;  /* # 压缩过个内存 */
        natural_t   throttled_count;    /* # of pages throttled */
        natural_t   external_page_count;    /* # of pages that are file-backed (non-swap) mmap() 映射到磁盘文件的 */
        natural_t   internal_page_count;    /* # of pages that are anonymous malloc() 分配的内存 */
        uint64_t    total_uncompressed_pages_in_compressor; /* # of pages (uncompressed) held within the compressor. */
    } __attribute__((aligned(8)));
    
    typedef struct vm_statistics64  *vm_statistics64_t;
    typedef struct vm_statistics64  vm_statistics64_data_t;
    

    Page Fault 中文翻译为缺页错误之类,其实就是要访问的内存分页已经在虚拟内存里,但是还没加载到物理内存。这时候如果访问合法就从磁盘加载到物理内存,如果不合法(访问 nullptr 之类)就 crash 这个进程。详细解释可以参考这里

    Filled with Zero Page: 操作系统会维护一个 page,里面填满了 0,叫做 zero page。当一个新页被分配的时候,系统就往这个页里填 zero page。我的理解是相当于清空数据保护,防止其他进程读取旧数据吧。

    空闲内存计算

    speculative pages 是 OS X 10.5 引入的一个内核特性。内核先占用了这些 page,但是还没被真的使用,相当于预约。比如说当一个 App 在顺序读取硬盘数据的时候,内核发现它读完了 1, 2, 3 块, 那么很可能它会读 4。这时候内核先预约一块内存页准备给未来有可能会出现的 4。大概是这么个理解,可以参考这里的回答

    在上面的注释中,speculative pages 是被计入 vm_stat.free_count 里的,所以 vm_stat 的实现里,空闲内存的计算减去了这一部分:

    pstat((uint64_t) (vm_stat.free_count - vm_stat.speculative_count), 8);
    

    以上我们就得到了系统内存信息了。不过通过 host_statistics64() 接口取到的数据加一起并不等于系统物理内存,这是由内核统计实现决定了,这里有一个讨论有兴趣可以看看

    有了 active_countspeculative_count 和 wired_count,我们就可以计算内存占用率了?还差一个 compressed

    Memory Compression

    内存压缩技术是从 OS X Mavericks (10.9) 开始引入的(iOS 则是 iOS 7 开始),可以参考官方文档:OS X Mavericks Core Technology Overview

    简单理解为系统会在内存紧张的时候寻找 inactive memory pages 然后开始压缩,以 CPU 时间来换取内存空间。所以 compressed 也要算进使用中的内存。另外还需要记录被压缩的 page 的信息,记录在 compressor_page_count 里,这个也要算进来。

    (active_count + wired_count + speculative_count + compressor_page_count) * page_size
    

    这才是最终的系统内存占用情况,以 byte 为单位。这个接口 host_statistics() 在 iOS 亦适用。

    Mac 上的 iStat Menus App 就是这样计算内存占用的,但是,Activity Monitor.app 却有点不同。留意到他的 Memory Used 有一项叫做 App Memory。这个是根据 internal_page_count 来计算的,所以 Activity Monitor.app 的计算是这样的:

    (internal_page_count + wired_count + compressor_page_count) * page_size
    

    三、KSCrash 的 usableMemory 函数

    KSCrash 是一个开源的 Crash 堆栈信息捕捉库,里面有两个关于内存的函数:

    static uint64_t freeMemory(void)
    {
        vm_statistics_data_t vmStats = {};
        vm_size_t pageSize = 0;
        if(VMStats(&vmStats, &pageSize))
        {
            return ((uint64_t)pageSize) * vmStats.free_count;
        }
        return 0;
    }
    
    static uint64_t usableMemory(void)
    {
        vm_statistics_data_t vmStats = {};
        vm_size_t pageSize = 0;
        if(VMStats(&vmStats, &pageSize))
        {
            return ((uint64_t)pageSize) * (vmStats.active_count +
                                           vmStats.inactive_count +
                                           vmStats.wire_count +
                                           vmStats.free_count);
        }
        return 0;
    }
    

    freeMemory() 是直接返回的 free_countusableMemory() 则是 active_count + inactive_count + wire_count + free_count

    根据这两个函数的实现我猜测 freeMemory() 是想表达当前空闲内存的意思,usableMemory() 则是整个系统一共可以使用的内存有多少。

    理论上 usableMemory 可以用硬件信息代替,但实际上系统接口返回的数据加一起一般都比物理内存少。使用这种方式计算我猜可能也是想获得更准备的系统实际可用内存吧。

    但是根据上文我们已经知道,free_count 还包含了 speculative_count,最好去掉。并且 iOS 7 开始还加入了 memory compression,所以还得加上这个。

    KSCrash 用的接口是 host_statistics(),这个接口没有返回 compression 相关的信息,猜测应该是这个项目开始的时候还没有 host_statistics64() 接口,或者当时 iPhone 的 64 位机器还不够普及(iPhone 5s 开始有 64 位机器)。

    不过我自己实践了一下,即使用 host_statistics64() 接口,加上 compressions 和 compressor_page_count 之后的结果和不加的结果差不多。也有可能当时我的手机并没有使用大量内存所以压缩效果不明显就是。

    mem: 2712944640
    mem2: 2712961024
    

    四、iOS 和 Mac 的差异

    4.1 Mac 有 page out 但是 iOS 没有

    参考 Apple 官方文档 About the Virtual Memory System,Mac 上会有换页行为,也就是当物理内存不够了,就把不活跃的内存页暂存到磁盘上,以此换取更多的内存空间。

    具体的步骤是:

    1. 如果 active list 上的内存页最近没人访问过,就丢进 inactive list 里
    2. 如果一个在 inactive list 上的内存页最近没人访问,那就找到这个页的 VM object
    3. 如果这个 VM object 从来没被 paged (换入或者换出过),就创建一个默认的 pager 给他
    4. 然后用这个默认的 pager 尝试把它换出(page out)
    5. 如果换出成功那就释放该页占用的物理内存,然后把该页从 inactive list 放进 free list

    但是在 iOS 上,系统不会有 page out 行为。这大概是 Apple 当年把 Darwin 系统移植到手机上时遇到的最头痛的问题之一:没有 swap 空间。桌面操作系统发展了几十年,有非常成熟的硬件条件,但是手机并不是。手机自带的空间也很小,属于珍贵资源,同时跟桌面硬件比起来,手机的闪存 I/O 速度太慢。所以普遍手机的操作系统都没有设计 swap。

    所以一旦空闲内存下降到边界,iOS 的内核就会把 inactive 且没有修改过的内存释放掉,而且还可能会给正在运行的 App 发出内存警告,让 App 及时释放内存不然就之间挂掉,也就是俗称的"爆内存"(OOM Out-of-Memory)。

    4.2 iOS 有 jetsam 但是 Mac 没有

    负责把 iOS App 干掉的杀手叫做 jetsam,这个东西在 Mac 上没有。

    jetsam 如何判断要干掉哪些进程?

    这篇 No pressure, Mon! Handling low memory conditions in iOS and Mavericks 和这篇 iOS内存abort(Jetsam) 原理探究 | SatanWoo 对于 jetsam 有些解析。不过 jetsam 相关的代码非常长,直接看的话是真的眼花缭乱。

    看完这两篇文章之后我发现几个地方不太清楚,所以还是自己去走了一遍,但是我从最终的 kill 那一步反推回去,读起来比从一开始看 memory status 一步步往下走要容易一些。所以有兴趣看这部分代码的朋友,建议也从 memorystatus_do_kill() 反推回去。

    1. 开机
    2. arm_init()
    3. kernel_bootstrap()
    4. machine_startup()
    5. kernel_bootstrap()
    6. kernel_bootstrap_thread()
    7. bsd_init()
    8. memorystatus_init()
    9. memorystatus_thread()
    10. memorystatus_act_aggressive()
    11. memorystatus_kill_top_process()
    12. memorystatus_kill_proc()
    13. memorystatus_do_kill()
    14. jetsam_do_kill()
    15. exit_with_reason()
    16. 对每个线程调用 thread_terminate()
    17. thread_terminate_internal()
    18. thread_apc_ast()
    19. thread_terminate_self()
    20. threadcnt == 0 时调用 proc_exit()

    一共 20 层之多,内核代码果然年代久远。 XD

    其中 #1-#8 都是初始化,memorystatus_init() 里面创建了多个(hardcoded 为 3 个)最高优先级的内核线程:

    int max_jetsam_threads = JETSAM_THREADS_LIMIT;
    #define JETSAM_THREADS_LIMIT   3
    
    kernel_thread_start_priority(memorystatus_thread, NULL, 95 /* MAXPRI_KERNEL */, &jetsam_threads[i].thread);
    

    以下条件命中时,会采取行动:

    static boolean_t
    memorystatus_action_needed(void)
    {
    #if CONFIG_EMBEDDED
        return (is_reason_thrashing(kill_under_pressure_cause) ||
                is_reason_zone_map_exhaustion(kill_under_pressure_cause) ||
               memorystatus_available_pages <= memorystatus_available_pages_pressure);
    #else /* CONFIG_EMBEDDED */
        return (is_reason_thrashing(kill_under_pressure_cause) ||
                is_reason_zone_map_exhaustion(kill_under_pressure_cause));
    #endif /* CONFIG_EMBEDDED */
    }
    

    thrashing

    kill_under_pressure_cause 为 thrashing 的条件:

    kMemorystatusKilledFCThrashing
    kMemorystatusKilledVMCompressorThrashing
    kMemorystatusKilledVMCompressorSpaceShortage
    

    会在这里触发 compressor_needs_to_swap(void),当内存需要换页的时候,arm 架构的实现就会判断当前 vm compressor 状态然后抛出上述三种 cause 之一,按照我的理解应该是内存压缩都开始告急了。

    ZoneMapExhaustion

    kill_under_pressure_cause 为 zone_map_exhaustion 的条件:

    kMemorystatusKilledZoneMapExhaustion
    

    这种情况则是由 kill_process_in_largest_zone() 函数发起,如果能找到 alloc 了最大 zone 的一个进程就干掉他,不行就记录 cause,走 jetsam 流程。

    memorystatus_available_pages <= memorystatus_available_pages_pressure

    或者是可用内存页少于系统设定的阈值,这个阈值计算如下:

    unsigned long pressure_threshold_percentage = 15;
    unsigned long delta_percentage = 5;
    
    memorystatus_delta = delta_percentage * atop_64(max_mem) / 100;
    memorystatus_available_pages_pressure = (pressure_threshold_percentage / delta_percentage) * memorystatus_delta;
    

    相当于 atop_64(max_mem) * 15 / 100 也就是最大内存的 15%。max_mem 是 arm_vm_init() 启动时传入的,应该就是硬件内存大小了。

    jetsam 如何杀进程?

    memorystatus_thread() 会先取一波原因:

    /* Cause */
    enum {
        kMemorystatusInvalid                            = JETSAM_REASON_INVALID,
        kMemorystatusKilled                             = JETSAM_REASON_GENERIC,
        kMemorystatusKilledHiwat                        = JETSAM_REASON_MEMORY_HIGHWATER,
        kMemorystatusKilledVnodes                       = JETSAM_REASON_VNODE,
        kMemorystatusKilledVMPageShortage               = JETSAM_REASON_MEMORY_VMPAGESHORTAGE,
        kMemorystatusKilledProcThrashing                = JETSAM_REASON_MEMORY_PROCTHRASHING,
        kMemorystatusKilledFCThrashing                  = JETSAM_REASON_MEMORY_FCTHRASHING,
        kMemorystatusKilledPerProcessLimit              = JETSAM_REASON_MEMORY_PERPROCESSLIMIT,
        kMemorystatusKilledDiskSpaceShortage            = JETSAM_REASON_MEMORY_DISK_SPACE_SHORTAGE,
        kMemorystatusKilledIdleExit                     = JETSAM_REASON_MEMORY_IDLE_EXIT,
        kMemorystatusKilledZoneMapExhaustion            = JETSAM_REASON_ZONE_MAP_EXHAUSTION,
        kMemorystatusKilledVMCompressorThrashing        = JETSAM_REASON_MEMORY_VMCOMPRESSOR_THRASHING,
        kMemorystatusKilledVMCompressorSpaceShortage    = JETSAM_REASON_MEMORY_VMCOMPRESSOR_SPACE_SHORTAGE,
    };
    

    如果是上一节 memorystatus_action_needed() 里的原因则走 memorystatus_kill_hiwat_proc()hiwat 就是 high water。这时候不会立刻杀掉该进程,而是判断一下 phys_footprint 是否超过 memstat_memlimit,超过就干掉。

    这一步如果成功杀掉了,那么这个循环就先结束,如果杀失败了,那就要开始愤怒模式了:

    static boolean_t
    memorystatus_act_aggressive(uint32_t cause, os_reason_t jetsam_reason, int *jld_idle_kills, boolean_t *corpse_list_purged, boolean_t *post_snapshot)
    

    vm_pressure_thread 也会监控 VM Pressure,判断是否要杀进程。

    memorystatus_pages_update() 会触发 vm pressure 检查,非常多地方会触发这个函数,已无力读下去。

    不过最终大家都会会走 memorystatus_do_kill() 调用 jetsam_do_kill(),进入 exit_with_reason() 带一个 SIGKILL 信号。比较有意思是它的代码最末尾是:

    /* Last thread to terminate will call proc_exit() */
        task_terminate_internal(task);
    
        return(0);
    

    我还以为是在 task_terminate_internal() 发了退出信号,但是并没有,这里面只是清理了 IPC 空间,map 之类的内核信息。注释说最后一个线程会调用 proc_exit(),原来是在这里调用的:

    while (p->exit_thread != self) {
            if (sig_try_locked(p) <= 0) {
                proc_transend(p, 1);
                os_reason_free(exit_reason);
    
                if (get_threadtask(self) != task) {
                    proc_unlock(p);
                    return(0);
                            }
                proc_unlock(p);
    
                thread_terminate(self);
                if (!thread_can_terminate) {
                    return 0;
                }
    
                thread_exception_return();
                /* NOTREACHED */
            }
            sig_lock_to_exit(p);
        }
    

    遍历所有线程,然后都调用 thread_terminate() 结束线程,这个函数的实现里面有判断 threadcnt == 0 时就调用 proc_exit(),这里面就会发送我们熟悉的 SIGKILL 信号然后退出进程了。

    4.3 iOS 如何判断 OOM

    但是这些信息内核却并没有抛给应用,所以应用也不知道自己 OOM 了。参考 Tencent/matrix 的实现,也只能用排除法。

    if (info.isAppCrashed) {
        // 普通 crash 捕获框架能抓到的 crash
        s_rebootType = MatrixAppRebootTypeNormalCrash;
    } else if (info.isAppQuitByUser) {
        // 用户主动关闭,来自 UIApplicationWillTerminateNotification
        s_rebootType = MatrixAppRebootTypeQuitByUser;
    } else if (info.isAppQuitByExit) {
        // 利用 atexit() 注册回调
        s_rebootType = MatrixAppRebootTypeQuitByExit;
    } else if (info.isAppWillSuspend || info.isAppBackgroundFetch) {
        // App 主动调用的,matrix 的注释曰: notify the app will suspend, help improve the detection of the plugins
        if (info.isAppSuspendKilled) {
            s_rebootType = MatrixAppRebootTypeAppSuspendCrash;
        } else {
            s_rebootType = MatrixAppRebootTypeAppSuspendOOM;
        }
    } else if ([MatrixAppRebootAnalyzer isAppChange]) {
        // App 升级了
        s_rebootType = MatrixAppRebootTypeAPPVersionChange;
    } else if ([MatrixAppRebootAnalyzer isOSChange]) {
        // 系统升级了
        s_rebootType = MatrixAppRebootTypeOSVersionChange;
    } else if ([MatrixAppRebootAnalyzer isOSReboot]) {
        // 系统重启了
        s_rebootType = MatrixAppRebootTypeOSReboot;
    } else if (info.isAppEnterBackground) {
        // 排除以上情况,剩下的就认为是 OOM,在后台就是后台 OOM
        s_rebootType = MatrixAppRebootTypeAppBackgroundOOM;
    } else if (info.isAppEnterForeground) {
        // 在前台,判断下是否死锁
        if (info.isAppMainThreadBlocked) {
                // 死锁,来自 matrix 的卡顿监控,跟内存无关
            s_rebootType = MatrixAppRebootTypeAppForegroundDeadLoop;
            s_lastDumpFileName = info.dumpFileName;
        } else {
                // 前台 OOM
            s_rebootType = MatrixAppRebootTypeAppForegroundOOM;
            s_lastDumpFileName = @"";
        }
    } else {
        s_rebootType = MatrixAppRebootTypeOtherReason;
    }
    

    五、小结

    iOS/Mac 获取内存占用信息的接口比较简单,但是涉及的概念和实现却非常复杂和庞大,尤其是内核的实现,一个函数动不动就 500 行以上,如果没有配套的书籍讲解,阅读起来十分吃力。所以读这种类型的代码,还是找到关键函数往回推比较简单点。XDDD

    P.S. 使用 kill -l 命令可以看到所有的 tty 信号。SIGHUP 是 1,SIGKILL 是 9。所以我们经常使用的 kill -9 <pid> 命令就是告诉该进程你被 Kill 了。

    P.P.S. memorystatus_do_kill() 函数的参数叫做 victim_p XDDD

    内核系列文章

    参考资料

    展开全文
  • RSS is the total memory actually held in RAM for a process. RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only
      RSS is the total memory actually held in RAM for a process. RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only loaded into memory once regardless
     of how many processes use it. RSS is not an accurate representation of the memory usage for a single process.The aim of this post is to provide information that will assist in interpreting memory reports from various tools so the true memory usage for
     Linux processes and the system can be determined.

    Android has a tool called procrank (/system/xbin/procrank), which lists out the memory usage of Linux processes in order from highest to lowest usage. The sizes reported per process are VSS, RSS, PSS, and USS.

    For the sake of simplicity in this description, memory will be expressed in terms of pages, rather than bytes. Linux systems like ours manage memory in 4096 byte pages at the lowest level.

    VSS (reported as VSZ from ps) is the total accessible address space of a process. This size also includes memory that may not be resident in RAM like mallocs that have been allocated but not written to. VSS is of very little use for determing real memory usage of a process.

    RSS is the total memory actually held in RAM for a process. RSS can be misleading, because it reports the total all of the shared libraries that the process uses, even though a shared library is only loaded into memory once regardless of how many processes use it. RSS is not an accurate representation of the memory usage for a single process.

    PSS differs from RSS in that it reports the proportional size of its shared libraries, i.e. if three processes all use a shared library that has 30 pages, that library will only contribute 10 pages to the PSS that is reported for each of the three processes. PSS is a very useful number because when the PSS for all processes in the system are summed together, that is a good representation for the total memory usage in the system. When a process is killed, the shared libraries that contributed to its PSS will be proportionally distributed to the PSS totals for the remaining processes still using that library. In this way PSS can be slightly misleading, because when a process is killed, PSS does not accurately represent the memory returned to the overall system.

    USS is the total private memory for a process, i.e. that memory that is completely unique to that process. USS is an extremely useful number because it indicates the true incremental cost of running a particular process. When a process is killed, the USS is the total memory that is actually returned to the system. USS is the best number to watch when initially suspicious of memory leaks in a process.

    For systems that have Python available, there is also a nice tool called smem that will report memory statistics including all of these categories.
    展开全文
  • 内核早期内存分配器:...在进一步介绍memblock之前,有必要先了解下系统内存的使用情况:首先,内存中的某些部分是永久的分配给内核的,比如内核代码段和数据段,ramdisk和fdt占用的空间等,它们是系统内存的一部分...

    内核早期内存分配器:memblock
    Linux内核使用伙伴系统管理内存,那么在伙伴系统工作前,如何管理内存?答案
    是memblock。
    memblock在系统启动阶段进行简单的内存管理,记录物理内存的使用情况。
    在进一步介绍memblock之前,有必要先了解下系统内存的使用情况:
    首先,内存中的某些部分是永久的分配给内核的,比如内核代码段和数据段,ramdisk和fdt占
    用的空间等,它们是系统内存的一部分,但是不能被侵占,也不参与内存分配,称之为静态内存
    ;其次,GPU,Camera等都需要预留大量连续内存,这部分内存平时不用,但是系统必须提前预
    留好,称之为预留内存;最后,内存的其余部分称之为动态内存,是需要内核管理的宝贵资源。
    memblock把物理内存划分为若干内存区,按使用类型分别放在memory和reserved两个集
    合(数组)中,memory即动态内存的集合,reserved集合包括静态内存和预留内存。
    1. memblock关键数据结构
    memblock数据结构定义如下:
    memblock相关数据结构十分的简单,内核还为memblock定义了一个全局变量,并为其赋
    初值,如下:
    memory类型的内存集合指向memblock_memory_init_regions数组,最多可以记录128个内
    存区。
    reserved类型的内存集合指向memblock_reserved_init_regions数组,最多可以记录128个内
    存区。
    注:内核代码经常用到类似”__initdata_memblock”的宏定义,通常用来指定变量或函数所在
    的section,该宏的定义如下:
    2. memblock基本操作
    1) 添加内存区
    分别为memory和reserved集合添加内存区,如果新加入的内存区与原有内存区重叠,则合并
    到原有内存区,否则插入新内存区。
    实际工作由memblock_add_range()完成,type参数指定内存集合类型。
    需要注意的是该函数内部会执行两次:
    第一次计算需要插入几个内存区,如果超过允许的最大内存区个数,则double内存区数组;
    第二次执行内存区的实际插入与合并操作。
    2) 移除内存区
    从memory集合移除给定物理地址所指的内存区,如果是内存区域的一部分,则涉及到调
    更多精彩攻略访问gl.baidu.com 1
    整region大小,或者将一个region拆分成两个region。
    系统将不会为移除的内存区建立内存映射,这部分内存区后续应该由DMA或CMA管理。
    3) 分配内存
    使用该函数向kernel申请一块可用的物理内存,memblock使用自顶向下(取决于bottom_up
    的值)的方式查找空闲内存,实际操作是在memory region中查找合适的内存,并加入到reserved
    region中以标记这块内存已经被使用。
    4) 释放内存
    使用该函数来释放由memblock_alloc申请到的物理内存。
    3. 探测系统可用内存
    内核是如何知晓物理内存的拓扑结构呢?相信很多人都有过类似的疑问。
    通过DDR的模式寄存器(MR8),可以很容易获得内存密度,进而推断出内存容量,这部分工
    作通常由bootloader完成,然后使用fdt或者atag等方式传递给内核。
    以fdt为例,内核解析memory节点,取得物理内存的拓扑结构(起始地址及大小),并添加
    到memblock中,代码如下:
    该函数扫描memory节点,并解析reg属性,注意此时DeviceTree还没有执行unflattern操作,
    需要使用”fdt”类型接口解析dtb。
    以4G DDR为例,输出的调试信息如下:
    reg属性由addr和size组成,分别占用2个cell(u32类型数据),上面的reg data可以看成:“0
    00000080 0 00000080, 01000000 0 0 00557e”。
    dtb使用big endian方式存储数据,需要转换成cpu字节序。
    解析出来的内存包含两个Rank,起始地址分别是0x80000000和0x100000000,这是系统的
    可用内存,用来初始化memory region。
    从fdt解析的内存信息是否可信呢?内核有自己的判断,在启动阶段,内核会根据自身的运行
    地址计算内存基地址,即PHYS_OFFSET。
    如果base地址小于phys_offset,则内核使用可信的phys_offset做为主存的基地址。
    这里要注意区分PHYS_OFFSET, PAGE_OFFSET:
    PAGE_OFFSET是内核虚拟地址空间的起始地址,PHYS_OFFSET是RAM在物理空间的起
    始地址,内核空间的地址映射通常具有固定的偏移量,即:
    4. 记录系统预留内存
    这里说的系统预留内存,包括静态内存(内核Image,ramdisk,fdt等占用空间),以及系统
    为Camera,Display等子系统预留的大量连续内存。
    更多精彩攻略访问gl.baidu.com 2
    另外,高通平台通常包含多核,还需要为Modem,TZ/TA等预留运行空间,这部分空间类似
    静态内存,都是永久分配给其它核心使用,根据节点属性,通常由DMA管理。
    arm64_memblock_init()函数初始化系统预留内存,代码如下:
    “no-map”属性决定向reserved region添加内存区,还是从memory region移除内存区,二者差
    别在于内核不会给”no-map”属性的内存区建立内存映射,即该内存区不在动态内存管理范围。
    预留内存还会被添加到reserved_mem数组,为后续的初始化做准备,”reg”属性指定内存区的
    起始地址和大小,如果没有”reg”属性,还需要为内存区分配空间。
    至此,memblock的初始化工作已经基本完成了,主要是记录系统内存的使用情况:
    memory region记录系统了所有可用的动态内存;
    reserved region记录了系统预留内存,这部分内存通常由CMA管理,也属于动态内存范畴;
    reserved_mem数组则记录系统所有预留内存,包括”no-map”属性的内存区,为后续进一步初
    始化工作做准备。
    5. 初始化预留内存区
    内存向来是系统的宝贵资源,预留内存如果仅做为子系统的专用内存,就有点浪费了。
    Linux内核引入CMA(Contiguous Memory Allocator,连续内存分配器)。
    其工作原理是:为驱动预留一段内存,当驱动不用时,Memory Allocator(Buddy System)
    可以分配给用户进程使用;而当驱动需要使用时,就将进程占用的内存通过回收或者迁移的方式
    腾出来,供驱动使用。
    但是并不是所有的预留内存都由CMA管理,像Modem,TA等永久分配给其它核心使用的内
    存空间,内核并不为这部分空间建立内存映射,而是交由DMA管理。
    通过上面的分析,我们看到所有预留内存信息都记录在reserved_mem数组,下面先看看该结
    构体的定义:
    reserved-memory子节点包含预留内存属性,典型定义如下:
    “reg”和”no-map”属性前面介绍过,详细可以参考reserved-memory.txt,”compatible”属性使用
    标准定义,内核注册两种不同的处理方法:
    “removed-dma-pool”表示该内存区位于DMA管理区,内核不可见(没有页表)。
    “shared-dma-pool”表示该内存区位于CMA管理区,平时是可用的,只有需要时才分配给驱动
    使用。
    如果没有”reg”属性,即没有指定预留内存的起始地址,则需要由系统分配预留内存,然后初
    始化reserved_mem的ops成员:
    以”shared-dma-pool”为例,它的初始化函数如下:
    更多精彩攻略访问gl.baidu.com 3
    此处为”shared-dma-pool”类型的内存注册操作方法,cma_init_reserved_mem初始
    化cma_area(CMA管理区)。
    reserved_mem的ops成员包括init和release两个操作方法:
    驱动注册预留内存区时调用device_init方法,为设备指定预留内存操作方法(DMA)或预留内存
    区域(CMA),这些方法包括预留内存的申请,释放和mmap等。
    6. 连续内存分配器(CMA)
    CMA的初始化必须在buddy系统工作之前和memblock分配器初始化完成之后。 在ARM中,
    初始化CMA的接口是:
    @limit: 指定CMA区域的上限,在64位系统上@limit的值通常是0x100000000。
    以命令行参数”cma=32M@0-0xfffffff”为例: size_cmdline = 32M, base_cmdline = 0x0,
    limit_cmdline = 0xffffffff 。
    计算好CMA的size等值以后就进入cma_declare_contiguous中:
    dma_contiguous_default_are是用户自定义CMA管理区,定义如下:
    下面进入cma_init_reserved_mem初始化用户自定义CMA管理区:
    以上只是将CMA区域预留下来,并记录到相关数组,进一步初始化和使用需要等slab等子系
    统初始化完成后了。
    CMA并不直接开放给驱动开发人员,在注册设备时可以使用”memory-region”属性指定要操作
    的内存区域,需要分配DMA内存时,调用DMA相关函数就可以了。
    例如dma_alloc_coherent(),最终DMA相关的分配函数会到达CMA的分配函
    数dma_alloc_from_contiguous()。
    7. 进阶阅读
    memblock和bootmem的区别CMA(连续内存分配器)Contiguous Memory Allocator (CMA)
    源码分析Linux内核最新连续内存分配器(CMA) — 避免预留大块内存内存管理笔记(CMA)
    更多精彩攻略访问gl.baidu.com 4

    转载于:https://www.cnblogs.com/zxc2man/p/8664068.html

    展开全文
  • Linux内核内存

    2018-02-09 14:57:16
    1.linux内核内存相关内容明确:用户空间占用4G虚拟内存的前3G 虚拟地址范围:0x00000000 ~ 0xBFFFFFFF 内核空间占用4G虚拟内存的后1G 虚拟地址范围:0xC0000000 ~ 0xFFFFFFFF明确: 不论在内核空间还是用户空间,...

    1.linux内核内存相关内容

    明确:用户空间占用4G虚拟内存的前3G
    虚拟地址范围:0x00000000 ~ 0xBFFFFFFF
    内核空间占用4G虚拟内存的后1G
    虚拟地址范围:0xC0000000 ~ 0xFFFFFFFF

    明确:
    不论在内核空间还是用户空间,看到的地址都是虚拟地址,只是虚拟地址空间不一样;
    但是CPU最终访问的地址是虚拟地址对应的物理地址;虚拟地址转换成对应的物理地址需要MMU;
    将来MMU利用页表进行地址转换;

    引申:uclinux支持不带MMU的处理器;

    问:用户空间的虚拟内存和物理内存的映射关系?
    问:内核空间的虚拟内存和物理内存的映射关系?



    答:
    4.概念:
    IO空间:
    内存空间:
    对于X86架构,有两类总线:
    一类总线的位宽为16位,地址空间的大小为64K,如果外设连接到这类总线,
    CPU访问外设只能通过in,out两条指令;对于这个地址空间又称IO空间;

    另一类总线的位宽为32位,地址空间的大小为4G(物理地址),如果外设连接到这类总线,
    CPU访问外设通过地址指针的形式,对于这个地址空间又称内存空间,例如:
    *(volatile unsigned long *)0xE0200080 = 0x11000;
    对于ARM架构:
    无IO空间,有内存空间

    切记:将来CPU(ARM)访问某个外设,必须要弄清楚这个外设的地址,
    将来软件上以地址指针的形式访问这个外设即可;
    这个地址可以通过芯片手册和原理图来获取

    5.用户虚拟内存和物理内存的映射:
    采用动态内存映射,需要访问了,动态建立用户虚拟地址和物理地址的映射关系,
    访问完毕,要解除用户虚拟地址和物理地址的映射关系;
    这种动态内存映射造成内存的访问效率不高;
    用户最大能够访问3G物理内存;

    6.内核虚拟内存和物理内存的映射:
    采用静态内存映射,在内核启动的时候,就已经将内核1G的虚拟内存和物理内存做一一的映射关系,
    这种映射对内存的访问效率较高,一一映射:
    内核虚拟地址 物理内存物理地址
    0xC0000000 0x0
    0xC0000001 0x1
    0xC0000002 0x2
    0xC0000003 0x3
    ... ...
    如果采用以上一一映射,最终导致内核只能访问1G的物理内存;


    2.内核1G虚拟内存的划分

    划分的本质目的:最终的目的是既可以保证内存的访问效率,有可以让内核访问所有的物理内存;

    X86平台,内核1G虚拟内存的划分:
    直接内存映射区:
    特点:
    1.直接内存映射区对应的内核虚拟内存,在内核启动的时候,就已经跟物理内存进行一一映射;这块内存的访问效率要高;
    2.如果物理内存大于1G,此时直接内存映射区的大小为896M,如果物理内存小于1G(512M),那么直接内存映射区的大小就是物理内存的大小;
    3.直接内存映射区又称低端内存;

    动态内存映射区:
    特点:
    1.如果要访问某块物理内存或者某个物理地址,
    可以将这个物理内存或者物理地址动态映射到动态内存映射区的虚拟内存上或者虚拟地址上;
    这种内存的访问效率要低,如果不再使用,记得要解除地址映射;
    2.默认大小为120M

    永久内存映射区:
    固定内存映射区:
    特点:
    1.永久是固定,固定就是永久
    2.如果频繁访问某个物理内存或者物理地址,
    可以将这个物理地址或者物理内存映射到永久内存映射区或者固定内存映射区,
    无需频繁建立映射和解除映射,加快内存的访问效率;

    注意:前者在映射的时候会导致休眠,不能用于中断上下文
    后者可以用于中断上下文中;

    3.各自大小为4M

    注意:动态 + 永久 + 固定 = 高端内存

     ARM架构
    S5PV210处理器,内核1G虚拟内存的划分:
    1.启动开发板
    2.查看内核打印信息,找到如下信息:
    Virtual kernel memory layout:
    起始地址 结束地址 大小
    异常向量表
    vector : 0xffff0000 - 0xffff1000 ( 4 kB)

    固定内存映射区
    fixmap : 0xfff00000 - 0xfffe0000 ( 896 kB)

    DMA内存映射区:
    DMA : 0xff000000 - 0xffe00000 ( 14 MB)

    动态内存映射区:
    vmalloc : 0xf4800000 - 0xfc000000 ( 120 MB)

    直接内存映射区
    lowmem : 0xc0000000 - 0xf4000000 ( 832 MB)

    modules : 0xbf000000 - 0xc0000000 ( 16 MB)
    .init : 0xc0008000 - 0xc0037000 ( 188 kB)
    .text : 0xc0037000 - 0xc0838000 (8196 kB)
    .data : 0xc0838000 - 0xc088c900 ( 339 kB)   















    展开全文
  • 一、堆内和堆外内存 1、堆内内存 堆内内存的大小,由 Spark 应用程序启动时的 –executor-memory 或 spark.executor.... 1)Storage内存:任务在缓存 RDD 数据和广播(Broadcast)数据时占用内存 2)Exec...
  • 页 1.32位体系结构支持4KB的页,如果1GB的物理内存,物理内存被划分为262144个页 2.系统中每一个物理页都有一个struct page结构体 区 Linux内核主要使用四种区: ZONE_DMA 这个区包含的页能...内存分配可以占用其它区
  • 内核管理内存的基本单位 每个物理页都由struct page表示,位于<linux/mm_types.h>。 假设每个结构体40byte, 物理页8KB, 4GB物理内存。则有2182^{18}218页,结构体占用20MB,并不大。 struct page{ flags //...
  • 本文研究了共享库如何在32位AIX 5L™(5.3)上占用内存,并演示了以下命令: ps svmon slibclean 禁止 进程图 Genkld 风格 本文讨论了进程的虚拟地址空间,以及内核共享库段,如何检查它们以及...
  • Linux内核使用伙伴系统管理内存,那么在伙伴系统工作前,如何管理内存?...首先,内存中的某些部分是永久的分配给内核的,比如内核代码段和数据段,ramdisk和fdt占用的空间等,它们是系统内存的一部分,但是不能被侵占
  • 在分配一大块内存时,可能竭尽全力也无法找到连续的内存块。在用户空间中这不是问题,因为普通进程设计为使用处理器的分页机制,当然这会降低速度并占用TLB。在内核中也可以使用同样的技术。内核分配了其虚拟地址...
  • 利用slabtop命令,发现有大量的kmalloc-64占用,且一直未释放,初步怀疑是内存泄漏,但不知道谁泄漏的。 通过打开kmemleak,发现有install进程大量__kmalloc且size大小刚好也为64k (线上机器会定期调用/usr/bin/...
  • 内核占用内存开始部分;接下来是共硬盘,软盘使用的高速缓冲区部分,其中扣除显存和bios的640k到1m;然后是虚拟盘;最后一部分是为所有程序可以使用的主内存区。 每台设备内存大小等参数都可能不一样。程序直接...
  • 进程占用虚拟内存空间大并非意味着程序的物理内存也一定占用很大。 虚拟内存是操作系统内核为了对进程地址空间进行管理(process address space management)而精心设计的一个逻辑意义上的内存空间概念。我们程序中...
  • 可通过/proc/net/sockstat查看 cat  /proc/net/sockstat ...mem代表了消耗的内存,单位是PAGE SIZE,一般为4KB。487 * 4KB即为,内核处缓存用户没有及时收取到用户空间的tcp数据所消耗的内存大小。
  • 网络音乐实现在线播放与下载迅雷内核支持在线加载歌词计算机内存占用小。方便使用永久免费 程序安全
  • 所有页框回收就是在内核未耗尽内存之前(因为回收与交换也会使用内存),将在使用过程标记未访问不频繁的部分内存换出到磁盘,释放所占用内存补给系统,以维持内核的正常运转,待被换出的页对应的虚拟地址再次被...
  • 为什么虚拟机VMware+ubuntu编译linux内核和android 后占用电脑内存很多,编译结束后也不会释放占用内存? why? 有什么办法可以让他编译完毕自动释放? 用的是XP 下的虚拟机,内存有限,每次编译完毕退出来才能...
  • Linux 内核有个机制叫OOM killer(Out Of Memory killer),该机制会监控那些占用内存过大,尤其是瞬间占用内存很快的进程,然后防止内存耗尽而自动把该进程杀掉。内核检测到系统内存不足、挑选并杀掉某个进程的过程...
  • 在实际工作中有时需要程序打印出某个进程的内存占用情况以作参考, 下面介绍一种通过Linux下的伪文件系统/proc计算某进程内存占用的程序实现方法. 首先, 为什么会有所谓的 伪文件 呢. Linux系统的文件类型大致可分为...
  • 手机浏览器种类:UC浏览器,QQ浏览器,欧朋浏览器,百度手机浏览器,360安全浏览器,谷歌浏览器,搜狗手机浏览器,猎豹浏览器,其他杂牌浏览器国内的UC和QQ,百度等手机浏览器都是根据Webkit修改过来的内核,国内尚...
  • gitlab占用内存过大

    千次阅读 2018-09-21 15:29:24
    top -ac 看一下开启了多少unicorn worker进程,gitlab默认开启进程数与CPU内核数相同 解决: 修改gitlab.rb文件(根据实际修改) vim /etc/gitlab/gitlab.rb unicorn['worker_processes'] = 8 重新加载配置 ...
  • 第十四章 Android 内核驱动内存管理 14.1 Low Memory Killer 基本原理 Android 的Low Memory Killer 是在标准linux kernel 的OOM 基础上修改而来的一种内存管理 机制当...adj 和占用内存的大小oom_adj 代表进程的优先
  • 这种映射模式再大多数情况下可以节省页面表所占用的空间。因为大多数进程不会用到整个虚拟空间,再虚存空间中通常都留有很大的“空洞”。采用两层的方式,只要一个目录所对应的那部分空间是个空洞,就可以吧
  • 首先当程序需要一块不存在的内存时(也即页表项中已经标记出相应的页面不在内存中),CPU通过80386的页错误异常中断来实现。当一个进程引用一个不存在的内存页面时就会产生...如果此时物理内存已经被占用,则可以借助二
  • 为什么把内核虚拟地址放到从0xc000000开始最上面1g空间,原因是把从0开始的低地址留给用户态和设备层会更加方便些,而这个起点设置为0x c000000其实是任意的,主要考虑是那个版本内核内存占用不超过1g,假设你的系统...
  • Linux内核内存压缩技术

    千次阅读 2017-12-02 22:59:01
    为什么需要内存压缩说到压缩这个词,我们都可以想到是降低占用空间,使同样的空间可以存放更多的东西,如我们平时常用的压缩文件等。内存压缩同样也是为了节省内存内存无论是多大,总是不够用的(至少目前如此)...
  • 6款IE浏览器占用内存比较

    千次阅读 2009-01-22 19:13:00
    浏览器类型:主要分IE内核和非IE内核两类: IE内核: IE、GreenBrowser、傲游(Maxthon)、The World(世界之窗) 非IE内核: Mozilla Firefox、腾讯...而且IE6并不支持多窗口浏览,正因为这样,占用内存会大一些。 单独窗
  • 易语言API取内存占用源码,API取内存占用,GetMemoryByPID,取进程内存信息_,关闭内核对象_,打开进程_
  • Android内存管理: 1.当应用程序关闭以后,后台对应的进程并没有真正的退出(处于休眠状态,一般不占用系统CPU的资源),这是为了下次再启动...b:与linux内核内存管理模块配合: 系统内存主要是Linux内核内存管理所

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,663
精华内容 1,065
关键字:

内核占用内存