cleanup:在C语言中实现基于作用域的自动资源管理
0 摘要 本文分析了C语言中由GCC/Clang编译器提供的cleanup属性的用法、性能和特点,并介绍了Linux v6.6的cleanup.h中基于cleanup属性实现的轻量级的基于作用域的自动资源管理机制相关代码,并结合内核实际代码分析了其用法和好处,并将这个机制与内核的新晋编程语言——Rust的生命周期机制进行了对比,分析C语言的cleanup属性的局限之处。 1 引子 由于C语言不支持自动内存管理,内存泄漏、锁未释放等安全问题在C程序中相对常见。作为一个新人,编程经验难免不够老到,不出意外还是出意外了。在一次 Bug修 (tí) 复 (jiāo) 后,被Code Review发现我忘记释放自旋锁而导致线程死锁。 我不禁开始思考:C语言应该怎么避免此类问题?即:**C语言如何像C++/Rust等无GC的高级语言那样,在作用域结束时自动释放一些资源?**这让我想起了 GCC 提供的一个编译器扩展:cleanup 属性。 恰好,搜索发现,一年多前,Linux kernel v6.6 基于cleanup属性引入了作用域的资源管理。现将这个特性介绍给大家。 2 cleanup属性详解 如何使用cleanup GCC 和 Clang 都支持cleanup属性。其具体用法如下: Type var __attribute__((__cleanup__(type_destructor_fn))); cleanup属性用于变量声明,并且还需要一个函数作为该属性的参数。编译器会在这个变量的作用域结束时,按照如下形式自动调用给定的函数: type_destructor_fn(&var); 比如,malloc的动态内存,就可以通过cleanup属性在作用域结束的时候自动释放。作用域的具体概念和定义这里就不过多介绍了,可以参考GCC的这篇文档。 void heap_auto_drop(void *p) { void *ptr = *(void **)p; if (p != NULL) free(p); } int foo() { char *ptr __attribute__((__cleanup__(heap_auto_drop))) = NULL; ptr = malloc(10); if (ptr == NULL) return -1; // do somethin here return 0; // 无需手动释放 ptr 指向的内存 } 上面这段代码,foo() 函数的指针ptr指向的内存将在foo()函数执行完成后,由编译器自动完成指针ptr指向的内存释放,无需担心内存泄漏。...
Rust与系统编程 经验总结
Unsafe的使用 尽量分离Unsafe和Safe的作用域,避免内存泄漏; 比如eret或者idle的函数,要提前手动析构,或者把其他逻辑单独放到一个作用域,不能依赖Rust编译器;(看一下panic!宏是怎么实现自动析构的) 可以用unsafe标识特殊函数,例如noreturn的函数,提示编程者注意使用; Clone和Copy trait Copy会改变这个对象在编译时对某些语法的判断,是二进制拷贝,即memcpy,永远只进行比特拷贝;即C++缺省的拷贝构造函数; Copy是相对于Move来说的,如果没有类没有实现Copy那么它就是Move的,假如实现了Copy,那么在变量绑定、函数传参数、函数返回等都默认进行比特拷贝,且原变量还存在。 尽量不实现Copy trait Clone不会改变编译的方式,是开发者手动调用的; RAII对象不实现Clone trait; 更类似C++的自定义拷贝构造函数; 智能指针 与C++等其他无GC的高级语言类似,Rust智能指针需要注意以下几点 使用线程安全的共享指针Arc; 注意循环引用问题,父节点指向子节点应使用StrongPtr(获取所有权),而子节点指向父节点应使用WeakPtr(不获取所有权); Arc指针包裹的结构体(例如Vm和Vcpu),可以使用Arc::new_cyclic()函数进行初始化; Vm创建的时候,会连带创建Vcpu,其中Vcpu包含一个Vm的指针,此时Vm还没有创建完。如果是Vm创建完后,再去创建Vcpu,则每次访问vcpu都需要加锁,这会降低效率。但是把Vm的vcpu_list放到InnerConst中,又会碰到这个“先有鸡还是先有蛋”的问题。 此时应该使用Arc::new_cyclic()进行初始化。 RAII编程思想(编程设计哲学) Resource acquisition is initialization:资源获取就是初始化 或者叫做 Constructor Acquires, Destructor Releases (CADRe) 构造即资源获取,析构即资源释放; Scope-based Resource Management (SBRM) 基于作用域的资源管理; ownership-based resource management (OBRM) 总结一句话:用对象来管理资源; 特性:RAII 将资源与对象生命周期联系起来,这可能与范围的进入和退出不一致。 其实就是 **“利用Rust所有权机制管理系统内存”**这一特例的更通用的表述; Wiki https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization 文中提到C语言也能通过编译器做到自动析构,那么可以配合宏定义做到更便捷的操作 A good and idiomatic way to use GCC and clang attribute((cleanup)) and pointer declarations C智能指针项目 https://snai.pe/posts/c-smart-pointers Safe C++ with RAII:https://zhuanlan.zhihu.com/p/264855981...
虚拟机监控器内存带宽控制
内存带宽控制 论文题目:Supporting Temporal and Spatial Isolation in a Hypervisor for ARM Multicore Platforms 项目代码:https://github.com/pa007/xvisor-next/tree/cache-coloring_and_memory-reservation CONFIG_MEMORY_RESERVATION 解决这个问题的一种简单实用的技术是实现一种内存带宽预留机制,该机制限制给定时间窗口内的内存访问次数,这可能是 Yun 在非虚拟化背景下首次提出的基于软件的解决方案多处理器系统。 Hypervisor上怎么做?给每个vcpu一个内存访问预算budget,并周期性补充;这些信息都是静态配置的; 这种方法可以减少VM由于内存争用而引起的干扰,内存争用会隐式地受到内存预算的限制,并且独立于在其他VM内运行的软件的实际行为。 Intel RDT & ARM MPAM https://cloud-atlas.readthedocs.io/zh_CN/latest/kernel/cpu/intel/intel_rdt/intel_rdt_arch.html MBA (Memory Bandwidth Allocation):https://www.intel.com/content/www/us/en/developer/articles/technical/introduction-to-memory-bandwidth-allocation.html Xen上的一个文档:https://xenbits.xen.org/docs/unstable/features/intel_psr_mba.html Xen对intel QoS的处理:https://wiki.xenproject.org/wiki/Intel_Platform_QoS_Technologies ARM MPAM的寄存器:https://developer.arm.com/documentation/ddi0595/2021-12/External-Registers/MPAMCFG-MBW-MAX–MPAM-Memory-Bandwidth-Maximum-Partition-Configuration-Register ARM上应该怎么做获取内存访问的budget? 通过PMU,可以记录到data memory access的数量,使用硬件功能,减少软件记录的开销。 PMU还可以产生中断,当某个寄存器溢出的时候,这可以用来消耗预算,避免了软件对预算的反复检查。 研究一下ARM PMU tx2的pmu版本:pmuv3 文档:https://developer.arm.com/documentation/ddi0488/h/performance-monitor-unit?lang=en 一个博客,看/proc/interrupt看看pmu的中断https://www.whexy.com/posts/PMU cp15寄存器:https://developer.arm.com/documentation/ddi0406/b/Debug-Architecture/Performance-Monitors/CP15-c9-register-map?lang=en 怎么控制内存带宽? 通过vcpu调度 PMU驱动 所有寄存器 armv8 aarch64 PMU寄存器介绍 PMCR_EL0 Performance Monitors Control Register 控制寄存器 PMCCFILTR_EL0 Performance Monitors Cycle Count Filter Register 过滤器 PMCCNTR_EL0 Performance Monitors Cycle Count Register 时钟周期数 PMCEID0_EL0 Performance Monitors Common Event Identification register 0 检查某个event有没有实现,只读 PMCEID1_EL0 Performance Monitors Common Event Identification register 1 PMCNTENCLR_EL0 Performance Monitors Count Enable Clear register counter使能 PMCNTENSET_EL0 Performance Monitors Count Enable Set register PMEVCNTR_EL0 Performance Monitors Event Count Registers 各类事件的计数 PMEVTYPER_EL0 Performance Monitors Event Type Registers 寄存器对应事件类型 PMINTENCLR_EL1 Performance Monitors Interrupt Enable Clear register 中断 PMINTENSET_EL1 Performance Monitors Interrupt Enable Set register PMOVSCLR_EL0 Performance Monitors Overflow Flag Status Clear Register 溢出状态 PMOVSSET_EL0 Performance Monitors Overflow Flag Status Set register PMSWINC_EL0 Performance Monitors Software Increment register 写这个寄存器可以使某个counter加一 PMUSERENR_EL0 Performance Monitors User Enable Register 设置用户态权限 PMSELR_EL0 Performance Monitors Event Counter Selection Register 选择寄存器 PMXEVCNTR_EL0 Performance Monitors Selected Event Count Register 写入PMSELR_EL0寄存器后对应的值 PMXEVTYPER_EL0 Performance Monitors Selected Event Type Register 设置PMCR_EL0寄存器,控制是否记录EL0, EL1, EL2, EL3的事件;...
Hypernel: 无需两阶段地址翻译的硬件辅助的内核保护框架
标题原文:Hypernel: A Hardware-Assisted Framework for Kernel Protection without Nested Paging 发表于2018年的DAC(CCF A),作者来自韩国首尔国立大学。 介绍 操作系统内核的TCB可信计算基太大,任何小的漏洞都可能导致全盘崩溃。一种解决方案是使用Hypervisor来监控和保护关键系统。常用的有以下三种办法: 特权指令陷入:主要指的是系统寄存器。之前这篇“基于硬件隔离的移动系统安全研究“有类似的方式。 Hypercall 嵌套分页:就是两阶段地址翻译。 但是Hypervisor的引入给整个系统带来了很大的开销。文中的引文指出,即使有硬件虚拟化,两阶段地址翻译仍然带来了30%的访存开销。粗粒度的内存保护也会造成额外开销。Hypervisor对内存的保护粒度是页。如果一个页中包含了敏感数据,那么整个页都会被标记。 为了解决这两个问题,作者提出了Hypernel。Hypernel由两个部分组成:Hypersec和memory bus monitor (MBM)。 Hypersec主要负责提供安全的执行环境,对内核中受信任的部分和不受信任的部分进行隔离。位于EL2,是虚拟化的一部分。 MBM则是处理器和内存之间的系统总线上的一个硬件拓展。通过MBM,Hypernel能够进行更细粒度,也就是字粒度(word granularity)的内存监视。当MBM发现对受保护的敏感数据尝试进行写入时,会引发中断。 其中MBM由FPGA综合以后实现。(感觉难度挺大) 攻击模型:利用任何现有的内核漏洞来改变内核内存。 设计 整个框架被分为两部分。(与TEE无关,看名字可能会认为是基于TrustZone做的) 一部分被称为normal space,主要包含操作系统内核和用户应用。 另外一部分被称为secure space,主要包含安全应用。 为了将normal space和secure space进行隔离,作者赋予了secure space同等于hypervisor的权限。Hypersec位于secure space中,负责提供隔离的执行环境,而MBM则为其提供了更细粒度的内存监视能力。 作者用hypercall替换了内核中负责更新页表的代码(修改了内核代码),使得Hypersec能够直接监视内核页表的更改,并禁止创建任何映射EL2安全空间的页表条目。通过这种方式,Hypersec能够在不使用两阶段翻译的情况下创建隔离环境。 此外,作者利用instruction trapping的功能对特权指令进行了捕获,避免攻击者修改一些重要的系统寄存器例如TTBR导致Hypervisor无法正常工作。 MBM连接在系统总线上来监视CPU和内存。当对监控区域的内存进行修改,MBM会触发中断以通知Hypersec进行处理。MBM可以通过EL2的Hypersec来获知处理器内部的信息,所以不容易被绕过。 工作流程 整个过程就是围绕Hypercall和MBM验证内存写操作,整体比较清晰。 实现和测试 平台 主板:ARM Versatile Express Juno r1 平台,ARMv8-A,双核A57+四核A53 (TX2是四核A57) 子板:ARM. LogicTile Express 20MG Daughter Board,包含一个Xilinx Virtex-7 FPGA, XC7V2000T-1 规模 Hypersec:EL2的Hypervisor,1.5kLoC Linux Kernel:200LoC MBM:FPGA上大约55k个门电路 实现 设置HCR_EL2的TVM位,代理敏感寄存器的操作。 修改内核代码,通过Hypercall来修改页表,由Hypervisor代理这一操作。...