专栏首页LINUX阅码场宋宝华:为了不忘却的纪念,评Linux 5.13内核

宋宝华:为了不忘却的纪念,评Linux 5.13内核

Linux 5.14于14小时之前发布了,而我5.13的总结还没有写出,我早觉得有写一点东西的必要了,这虽然于搬砖的码农毫不相干,但在追求进步的工程师那里,却大抵只能如此而已。为了不忘却的纪念,我们列出5.13内核的数个激动人心的新特性:

  1. Apple M1的初始
  2. Misc cgroup
  3. Landlock安全模块
  4. 系统调用的堆栈随机化
  5. printk无锁ringbuffer的进一步优化
  6. BPF可调用内核函数
  7. 公共的IO PAGE Fault支持

Apple M1的初始支持

5.13最爆炸性的新闻无非是初始的Apple M1支持,但是然并卵,实用性几乎为0。因为,已经合入的patch非常类似于SoC bringup的初级阶段:

  • 带earlycon支持的UART (samsung-style) 串口驱动
  • Apple中断控制器,支持中断、中断亲和(affinity )和IPI (跨CPU中断)
  • SMP (通过标准spin-table来支持)
  • 基于simplefb的framebuffer驱动
  • Mac Mini的设备树

这样一个东西,是没法用的,发烧友玩玩可以,但是我们感激并欣赏Hector Martin “marcan”领导的Asahi Linux项目开了一个这样的好头。但是,在Apple M1上面跑Ubuntu啥的,近期、中期和长期的选择还是用Parallels虚拟化技术比较好。

Misc cgroup

众所周知,cgroup具备一个强大的控制CPU、内存、I/O等资源在不同的任务群间进行分配的能力。比如,你通过下面的命令,限制A这个群的CFS调度类进程,最多只能耗费20%的CPU:

这个世界上的绝大多数资源都是可以进行抽象的,比如属于cpuacct、cpu、memory、blkio、net_cls什么的,但是,总有一些不同于常人的人,他们既不是男人,也不是女人,而是“妖如果有了仁慈的心”的人。Linux内核的驱动子系统多达100多个,但是还是有极个别驱动不属于这100多类中的任何一类,于是在drivers下面有个misc:

现在内核碰到了类似的问题,它的资源要进行配额控制,但是不属于通用的类型,而是:

  • Secure Encrypted Virtualization (SEV) ASIDs
  • SEV - Encrypted State (SEV-ES) ASIDs

这些有限的 ASIDs用于在AMD平台上,进行虚拟机内存加密,不能归于现有cgroup的任何一类。那么,咱们加个misc类的cgroup吧,于是Misc control-group controller在5.13内核诞生了。这再次证明了,不要重新造轮子,但是你可以在现有的轮子里面放一个“杂交”轮子。Misc cgroup允许进行一些特殊资源的控制,透过3个接口完成。

  • misc.capacity描述资源的能力(只读),比如:
$ cat misc.capacity
res_a 50
res_b 10
  • 透过misc.current描述当前资源的占用(只读),比如:
$ cat misc.current
res_a 3
res_b 0
  • 透过misc.max设置这个cgroup最多只能使用多少资源(可读可写),比如:
# echo res_a 1 > misc.max

同志们,有了这个misc cgroup的支持,以后咱们的阿猫阿狗资源限制,也可以往里面塞了。它相当于开了一道门。

Landlock安全模块

曾经有一个真诚的patch摆在我面前,但是我没有珍惜,发了V1被人怼了后就放弃了,等到失去的时候才后悔莫及,尘世间最痛苦的事莫过于此,如果上天可以给我一个机会再来一次的话,我会对那个patch说我要继续迭代发!如果非要在这个迭代的次数上加上一个期限,我希望是一百遍。5.13内核,最励志的事情无疑是,"Landlock" Lands In Linux 5.13 !在迭代了超过5年之后,安全组件landlock终于合入了Linux内核,这份始于2016年的爱情,终于有了一个美好的结局。为此,Linux内核doc的维护者,LDD3的作者之一Jonathan Corbet发文指出:Kernel development is not for people who lack persistence; changes can take a number of revisions and a lot of time to make it into a mainline release。文章链接:

https://lwn.net/Articles/859908/

所以,没有耐力、不能持之以恒,想一夜暴富的人,真地不适合做kernel开发。Landlock LSM主要给非特权进程提供安全沙盒的能力,比如你可以对一个普通进程,施加自定义的文件系统访问控制策略。

它的操作原理是,先创建一个规则集ruleset,比如,如下的ruleset就是涉及到文件的读、写、执、读DIR、写DIR等:

ruleset对用户以文件描述符fd的形式存在,再次证明了“一切都是文件”。接下来,我们可以透过这个fd,向这个ruleset里面添加rule,比如我们添加一个/usr目录的“读”规则,这样进程就不能写/usr了:

我们把这个ruleset施加起来让它生效:

想要体验的童鞋可以用这个例子启动你的进程,它设置好ruleset后,会去call exec启动命令行参数指定的程序:

https://github.com/landlock-lsm/linux/blob/landlock-v34/samples/landlock/sandboxer.c

LL_FS_RO环境变量是可读文件的列表,LL_FS_RW环境变量是可读写文件的列表,运行方法:

LL_FS_RO=”只读路径” \
LL_FS_RW=”可写路径” \
sandboxer  ./a.out

a.out是你的想要安全沙盒的程序。

在下已经一睹为快,在/home/baohua下面创建2个目录1,2,然后创建/home/baohua/1/1和/home/baohua/2/1这2个文件,限制第一个目录只读:

童鞋们看明白了吗?我用sandboxer去启动cat,2个文件都是成功的。但是,去启动echo,/home/baohua/1/1是不允许写的,但是/home/baohua/2/1是可以写的。实际上,/home/baohua/1/1和/home/baohua/2/1并没有丝毫的不同。landlock在发挥作用了!

系统调用的堆栈随机化

这是一项安全增强,它允许对系统调用发生时,内核使用的堆栈添加一个随机偏移。这给基于stack的攻击增加了难度,因为stack攻击通常要求stack有个固定的layout。现在每次系统调用,stack的layout都变化的话,黑客就比较捉摸不定了。比如ARM64主要修改了invoke_syscall()这个函数:

这个东西听起来很高大上,但是它的原理可能简单地你想哭,NO BB! show me the code:

它实际上就是每次系统调用把offset随机化一下,然后通过__builtin_alloca()从stack里面分配一些stack空间,于是导致stack的位置移动。我们可以写个非常简单的应用程序来验证原理:

然后编译

gcc 1.c  -fno-stack-protector -O0

运行:

亲爱的,你有没有发现,10次函数调用的时候,每次stack临时变量的位置都不一样!!?

printk无锁ringbuffer的进一步优化

锁什么,不锁什么,锁大还是锁小,从来都是一个问题。宫锁心玉、宫锁珠帘、宫锁沉香、宫锁连城、宫锁printk......

内核工程师,可能真地被printk宠坏了,printk的优势是在Linux的任意CPU、任意线程、任意中断(甚至包括NMI)都可以调用,呼之即来挥之即去。你有没有想过,printk的实现里面可能有很大的锁代价的?你怎么保证一个人在打印”abc”,另外一个人再打印”def”,它不把2个人的打印串扰呢?如何避免各种死锁的可能性?很多操作系统为了避免这种代价,干脆禁止了一些上下文对类似print函数的调用,比如VxWorks的中断服务程序是不能调用printf()的。所以Linux的printk是一个极端复杂的存在。John Ogness <john.ogness@linutronix.de>童鞋曾经说过:“If it is part of printk, it is already implicitly on every line of code.”

生命不息,内卷不止。printk在内核不断演进,可以看成一个锁粒度逐步缩小,直至lockless的一个典范。

1991年0.01版的printk非常简单,没有现代意义上的logbuf这个环形缓冲区,直接把buffer往tty里面写:

这个时候,显然还没有loglevel,console的概念,也完全不支持多核;上世纪90年代的内核逐步在printk加入了ringbuffer(logbuf)、loglevl、console等的概念,以及对syslogd等用户态服务唤醒的支持。

直至1998年,Linux 2.1.80开始支持多核printk,通过一个spin_lock,把所有多核的printk串行化,各个处理器顺序打印(图片来源https://elinux.org/images/7/7c/Elce-printk-v1.pdf):

第2个printk必须等第1个printk彻底完成才能开始,这个printk的效率是非常低的。按照Amdahl定律,此种实现串行度100%,显然scalability很差。

现代意义上的printk,诞生于2001年9月的2.4.10,开始支持异步的打印。这个时候,printk开始使用2个锁:

  • console_lock semaphore:用于在console打印
  • logbuf_lock spinlock:用于写环形缓冲区logbuf

这2个锁其实把写logbuf和在console打印的动作某种意义上并行化了:

只有拿到console_lock的任务负责打印,但是在打印的同时,其他任务只要能拿到logbuf_lock,是可以写logbuf的。

由于printk拿了logbuf这样的锁,如果在printk的过程中,发生不同寻常的NMI(比如,即便logbuf_lock的附加屏蔽IRQ版本——logbuf_lock_irqsave也屏蔽不了NMI),而这个NMI也要printk写logbuf啥的,则可能造成死锁。所以在Linux 3.19后,引入了seq_buffer,NMI的log,写入一个安全的per-CPU的buffer,而不是像其他printk那样写入全局的logbuf。之后,在NMI handler结束后的相对安全的上下文,把per-CPU seq_buffer里面的东西flush出去(比如Linux 4.7通过irq_work延后这个工作)。所以,此时的逻辑变成了:

这样就导致了printk依赖一个临时的所谓safe buffer。这种safe buffer的理念,也被用来避免printk自己递归(printk的实现调用printk)引起的死锁。在递归的printk里面,内容也如NMI那样写入safe buffer,之后在安全的上下文才把这个buffer的内容flush出去。这种思路,其实也是数据结构分化以避免全局锁的思路,比如太平天国洪秀全暂时没有办法夺取北京城,就先在南京城占山为王,然后伺机再取北京。北京城1个数据结构,南京城是另1个。

printk的logbuf有各种NMI、递归的坑的,前面基本就是在想办法绕坑。绕坑的话,进取心实在有限,比如天王后面放弃了007,选择了躺平,天国最后完蛋了。但是内核的进取心很大,在5.10中,内核提交了一个lockless的ringbuffer,可安全地用于一切上下文,避免了死锁,也为避免NMI等场景对临时的per-CPU safe buffer依赖的去除提供了可能性,应该是更加接近printk需求的本质。注意,5.10内核printk的这个lockless ringbuffer支持多个读者、多个写者安全的,它本身的实现比较复杂,更多涉及数据结构的知识,具体的细节可以参考这个commit(大约2000行代码):

但是5.10仍然有少量代码路径依赖 logbuf_lock,比如kmsg_dump、syslog 、格式化消息用的临时buffer等(毕竟5.10之前的代码用logbuf_lock用地比较奔放)。

5.13中,内核进一步移除了 logbuf_lock,从而基本接近了lockless的printk。移除的方法是要么直接删没必要的 logbuf_lock调用,要么用一个特定的更小锁来替换。比如,之前syslog里面的 syslog_seq, syslog_partial, syslog_time ,clear_seq 是靠 logbuf_lock保护的,现在重新引入一个它自己的锁syslog_lock:

这种思路其实就是分而治之,逐步细化瓦解。就像以前内核有个BKL,后面它的使用场景,被一个个更小的锁细化代替,直至最后BKL被彻底消灭一样。

BPF可调用内核函数

技术上来讲BPF程序载入内核的时候,内核会执行严格的检查,内核和BPF程序能实际互动的范围非常有限,主要是内核调用BPF而不是反过来。Linux 5.13内核则允许特定program type的BPF程序直接调用特定的内核函数,为确保调用的安全,目前内核仅仅授权了 tcp_slow_start() 、tcp_cong_avoid_ai()等这种TCP拥塞控制相关的函数(tcp-cc helper)供BPF拥塞控制程序直接调用,这样BPF拥塞控制程序不需要把这些函数再copy-paste一遍。

内核net/ipv4/bpf_tcp_ca.c的代码显示了这个verify的过程,需要在相应的bpf_verifier_ops中添加check_kfunc_call()成员函数:

check_kfunc_call()的成立条件就是特定函数必须是在bpf_tcp_ca_kfunc_ids集合里面的白名单函数,比如:

这个时候,哥在想,如果我把kprobe这种program type的BPF的check_kfunc_call()永远返回真,我不是可以在kprobe的BPF中为所欲为?

比如我可以尝试在任何kprobe点对应的BPF程序上,调用barrysong_hack_print()这个函数?目前还没有尝试,想做实验的童鞋,可以仿照这个commit中的例子完成,这是一个测试案例:

公共的IO PAGE Fault支持

这个特性主要用于用户空间的DMA,特别适用于SVA的场景,Shared Virtual Addressing (SVA)。

在SVA模式下,设备的IOMMU采用和CPU的MMU共享的页表,从而让进程地址空间对设备可见。

图片来源:

https://events19.linuxfoundation.cn/wp-content/uploads/2017/11/Shared-Virtual-Addressing_Yisheng-Xie-_-Bob-Liu.pdf

5.13内核中,ARM SMMU和UACCE (Unified/User-space-access-intended Accelerator Framework) 合入了共享SVA的支持,并将相关IO Page Fault(IOPF)的代码提炼成了通用的drivers/iommu/io-pgfault.c代码。我们都知道,Linux的内存管理重度近乎强迫症式地依赖CPU的page fault,比如demanding page, swap,CoW等,内存都是在page fault发生后申请内卷进来的。现在,设备也共享了进程的内存,这样设备访问这些页面的时候,仍然可能产生类似CPU的page fault帮忙把进程缺少的页面申请出来。不过设备是先发一个中断,然后内核在中断服务程序里面调用handle_mm_fault()来处理缺页,这样设备产生的IOPF同样可以帮忙demanding page(比如设备DMA写malloc()后还没获得的内存)。似乎设备变地非常类似进程里面的一个线程,不过我们仔细一想,这里仍然有一个逻辑讲不通,如果我们把线程和Device并列:

当线程写空指针,CPU会收到同步的Page Fault(在*p=10的指令卡住,并最终给进程产生segment fault);但是进程启动设备在用户态去做DMA,设备写无效的地址,显然也会收到IOPF,但是我们却没办法定位到对应的代码行。在加上中断啥时候进ISR的问题,这种IOPF行为总体对进程而言异步的。比如:

p = malloc(1M);
device_write(p, 2M);

其实写前1MB都没有问题,但是到1MB后,其实就是非法地址了,设备啥时候写完1MB,这个完全是异步的。

另外这个时候,内核应该给进程发什么信号也是个问题?CPU碰到这种情况,显然就是发SIGSEGV;设备这里,IOPF的中断服务程序,目前似乎是没有发,理想情况下,是不是至少也应该发一个类似SIGBUS或者什么信号,不过无论如何,进程也无法同步检测到哪里的代码出了问题,更加不要说支持ASAN(Address Sanitizer)这种内存越界检查技术了。

我们期待后续内存继续对这个问题给出一个明确的说法,也期待更多的童鞋发patch来让内核能自圆其说。

时光永是流逝,街市依旧太平。内核的每个新版本发布,之于搬砖的码农,已泛不起任何的涟漪。但是,钟爱内核的人们,仍然在孜孜不倦地追随。

本文分享自微信公众号 - Linux阅码场(LinuxDev),作者:宋宝华

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-08-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 宋宝华:为了不忘却的纪念,评Linux 5.13内核(上集)

    5.14-rc6了,看起来5.14也快发布了。而我5.13的总结还没有写出,我早觉得有写一点东西的必要了,这虽然于搬砖的码农毫不相干,但在追求上进的工程师那里,...

    Linux阅码场
  • IT皇冠上的明珠! Linux 操作系统学习看过来!

    操作系统堪称是IT皇冠上的明珠,Linux阅码场专注Linux操作系统内核研究, 它的文章云集了国内众多知名企业一线工程师的心得,畅销著作有《linux设备驱动...

    编程珠玑
  • Linux用户态进程的内存管理

    上一篇我们了解了内存在内核态是如何管理的,本篇文章我们一起来看下内存在用户态的使用情况,如果上一篇文章说是内核驱动工程师经常面对的内存管理问题,那本篇就是应用工...

    刘盼
  • 小长假遛娃必备知识点

    ? 不知不觉间,2019年第一个小长假到了,先别着急带娃出去踏青,如果没有知识储备,你可能会经历这样的灵魂问答: ? 娃:“清明节为什么叫清明节?” ? 你...

    鹅老师
  • Linux 能否拿下苹果 M1 阵地?

    自去年 11 月苹果自研 M1 芯片落地以来,业界开发者纷纷对其”打起了主意“,有人称 M1 芯片是否真的如传闻那样打破了 x86 的垄断,不如实测一番;有人打...

    Linux阅码场
  • 32年节目单,挖掘春晚“大数据”

    ? 刘德华是春晚的常客   晨报记者 陈 文    自1983年起,央视春节联欢晚会已经伴随国人走过32个年头。32年,换作人的一生也近1/3,站在而立之年...

    小莹莹
  • 高性能:LEP (LINUX EASY PROFILING) 工具介绍

    然后,在右上方的框内,输入 lepd所在的节点地址 (我们这里还是 10.10.11.11)

    二狗不要跑
  • Linux内核书籍

    《Linux设备驱动》   --  也就是我们所说的LDD3了; 适合一定基础的人阅读,深入学习Linux不可或缺的知识; 《UNIX环境高级编程》 这本书并不...

    233333
  • Linux的进程线程及调度

    操作系统中的经典定义: 进程:资源分配单位。 线程:调度单位。 操作系统中用PCB(Process Control Block, 进程控制块)来描述进程。...

    叶余
  • TME入局背后 音乐NFT的用户市场、应用价值与海外实践

    今年3月,加密艺术家Beeple的NFT作品《Everydays:The First 5000 Days》在佳士得以6025万美元的拍卖价成交,让NFT概念被更...

    用户7358413
  • CSS样式中汉字和字母分别使用不同字体的方法

    说来也巧最近不知道发点什么文章,在后台测试代码的时候看见网友在文章“修改网页自定义字体的CSS代码+图文教程”反馈,怎么在css里汉字和字母使用不同的字体,应该...

    李洋个人博客
  • 【新智元峰会】德国AI教皇盛赞中国人工智能,25位AI领袖强势打造中国新智极

    直播链接 2018新智元产业跃迁AI技术峰会今天隆重启幕,点击链接观看大会盛况: 爱奇艺 http://www.iqiyi.com/l_19rr3aqz...

    新智元
  • 热点关注 | 全国150个线上博物馆汇总 在家足不出户“云看展”

    ? 假期一天天延长, 一天到晚除了吃就是睡, 是不是感到莫名的恐慌? 那是知识的空虚感在作怪, 快来云看展, 足不出户,看遍全国精品展览。 展览资源来源于...

    腾讯文旅
  • 数据分析 | 用数据告诉你中秋那家月饼最畅销

    别不好意思,这是你该得的. 加我微信【hg_liuzl,备注:中秋活动中奖,并送上中奖截图找我兑奖】 另外中秋星球活动继续有效,截止到把中秋抽奖活动兑奖完...

    龙哥
  • 千峰越瓷京城品鉴会在京举行

      8月19日,由中国陶瓷工业协会主办浙江楠宋瓷业有限公司承办的千峰越瓷品鉴暨新闻发布会在北京召开。

    一线新闻
  • 测试方面的面试复盘

    先说结论,基础必须要打牢固,能自己做总结最好,面试时要主动,不要紧张,要珍惜内推机会。

    牛客网
  • 宋宝华:火焰图 全局视野的 Linux 性能剖析

    火焰图的火焰首先来自于根,然后以火苗的形式往上面窜。可以把从靠近地面的根到顶上的每个火苗,想想成一个调用栈。由于火苗有很多根,这正好也和现实生活中程序的执行逻辑...

    Linuxer
  • 清华成北京赛赢家,旷视唐文斌分享本质创业 | 联想高校AI精英挑战赛

    李根 发自 清华东南门 量子位 报道 | 公众号 QbitAI 无论是过去还是现在,联想都在清华尝到甜头。 过去,清华毕业生是联想PC业务供应链的核心保障力量...

    量子位
  • 周志华寄语新智元 | 新智元 AI 创业家余凯、吴甘沙分享

    【新智元导读】科学家和创业者都是劳动者。3月27日新智元开源·生态AI技术峰会上,南京大学教授周志华作为新智元2016人工智能年度人物(学术界别),通过视频发表...

    新智元

扫码关注云+社区

领取腾讯云代金券