x230 再战黑苹果 10.14.5

距离上一次安装黑苹果,已经3年过去了。第一次安装的时候作为一个新手,参考网上教程,整个安装过程顺利的让我这种小白都震惊了,x230 不愧是为黑苹果而生的一个本本

但是后来因为网卡问题,安装后就没有继续用了,切记!使用黑苹果千万别用usb网卡,那真是非常蛋疼,能上网但是进不去 app store 的,因为 app store 必须要求网卡BSD名字是en0。虽然有一些魔改的教程,但是成功率不高,不建议尝试,比较浪费时间

x230 上黑苹果能用的网卡主流是2种,一种是AR9285,一种是BCM94352,BCM的高级很多,AR的最傻瓜凑合用。我淘了一个AR的把原装网卡换掉了。接下来准备开始安装黑苹果,但是这次就没那么顺利了,为了防止下一次继续掉坑,记录了一下安装过程需要特别注意的一些地方

安装黑苹果的过程可以分为4大块

  1. BIOS 参数修改
  2. 制作U盘启动盘(EFI + 原始镜像)
  3. 安装(硬盘分区,安装)
  4. 硬盘引导

其中网上大部分傻瓜教程或者视频,都是讲2-4,但其实1非常关键,你看那些教程视频装个黑苹果跟装个Windows似的,因为很多前置的坑都填了(就比如BIOS参数),当然是看着很简单了。


cgroup 内存管理之 page cache 回收

page cache 的管理,是内核内存管理里最复杂的一块,也是容器混部场景下,问题最多的地方
我们这里只关注读 cache 的处理,脏页的控制单独讲。所以这篇文章里,无特殊说明 page cache 默认不包括脏页部分
当我们谈到 page cache 时, 我们会关注什么?
有以下几个关键的点
  1. 什么时机会触发 page cache 回收?
  2. 回收过程是什么样的
  3. 不可回收的页面有哪些?
  4. 不容易回收的页面有哪些?
  5. 回收力度如何控制
接下来,我们就这几点,来讲一讲 page cache 的一些内核实现内幕。以及混部场景下,可能会遇到的一些坑
实际上,不同的回收方式,其时机、回收的页面范围、力度、算法都稍有不同,所以下面我们将按照不同的回收方式来详细讲

1. 整机 drop_caches 回收

内核接口 /proc/sys/vm/drop_caches
内核的代码实现入口在 fs/drop_caches.c 里面
这个接口支持3种方式:
  1. echo 1,清理 page cache
  2. echo 2,清理 slab,比如 dentry cache 通常也很消耗内存
  3. echo 3,两种都清理
我们这里只讨论方式1

1.1. 回收时机、力度、算法

只有人为的 echo xx > /proc/sys/vm/drop_caches 时,才会触发 page cache 回收
每次触发 drop_caches,基本上都会把系统能回收的 clean page 一次性全部回收回来,注意,是全部能回收的
所以,这里其实也没有什么的特殊的回收算法了,简单全遍历就完了

1.2. 回收过程

内核代码 fs/drop_caches.c
简单来说,就是
  1. 遍历所有的超级块,super_block
  2. 遍历每个超级块上的所有 inode 对象
  3. 根据 inode->i_mapping 找到每个 inode 的 address_space 空间
  4. 遍历 address_space 下的所有 page
    1. 将 page 从 radix tree 上删除
    2. 调用文件系统的 releasepage 函数释放文件系统资源。这个可以忽略,我看 fs/* 几乎所有文件系统都不实现这个函数了
  5. 释放所有能释放的 page 内存(引用计数为0)
核心逻辑的调用栈如下:
  • drop_caches_sysctl_handler
    • iterate_supers(drop_pagecache_sb, NULL)
      • drop_pagecache_sb, list_for_each_entry(inode, &sb->s_inodes, i_sb_list)
        • invalidate_mapping_pages(inode->i_mapping, 0, -1) // 这个函数的实现在 mm/truncate.c 文件里,Invalidate all the unlocked pages of one inode
          • invalidate_inode_page(page) for page in pagevec_lookup_entries(&pvec)
            • invalidate_complete_page() 删除page的mapping,并从 page cache 的radix-tree 里面剔除,因为下一步就直接 free 内存了
        • pagevec_release(&pvec) // 释放所有的 page 内存空间

1.3. 回收范围

drop_caches 是一个非常轻量级的回收过程,只回收能够立即释放的 page
从 invalidate_inode_page() 我们可以看到,有3种页面,是不会被回收的:
  1. 脏页
  2. 正在回写的页
  3. mmap + MAP_SHARED 方式映射到 page table 的页
  4. PG_SyncReadahead 需要多次drop才能回收
int invalidate_inode_page(struct page *page)
{
        struct address_space *mapping = page_mapping(page);
        if (!mapping)
                return 0;
        if (PageDirty(page) || PageWriteback(page))
                return 0;
        if (page_mapped(page))
                return 0;
        return invalidate_complete_page(mapping, page);
}
注意,page_mapping() 和 page_mapped() 不是一个东西。另外,!mapping 这段代码我没看懂是过滤的啥?
page_mapping() 返回 page 的 address_space,读的是 page->mapping 信息
(1)返回 NULL,说明该页要么是 slab cache,要么是 anon
(2)返回非空,可能是 swap_address_space(),或者就是正常页所在的一个 address_space
struct address_space *page_mapping(struct page *page)
{
        struct address_space *mapping;
        page = compound_head(page);
        /* This happens if someone calls flush_dcache_page on slab page */
        if (unlikely(PageSlab(page)))
                return NULL;
        if (unlikely(PageSwapCache(page))) {
                swp_entry_t entry;

                entry.val = page_private(page);
                return swap_address_space(entry);
        }
        mapping = page->mapping;
        if ((unsigned long)mapping & PAGE_MAPPING_ANON)
                return NULL;
        return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);
}

而 page_mapped 是用来判断 page 是否在 page table 里面。这里用 page_mapped() 主要是用来判断当前 page 是否是一个 mmap + MAP_SHARED 产生的页(因为 MAP_PRIVATE 产生的页不会填充到 page table 里面,具体可以自己看下代码)