Linux 内核笔记

七月 18, 2025 [linux] #kernel #memory #driver #oom #debug

Linux 内核笔记

物理内存布局

在典型 RISC-V/ARM 主板中,物理地址空间分配如下:

高于 0x8000_0000 的地址访问 DRAM,低于的地址访问 MMIO(内存映射 I/O)设备。主板设计者决定物理地址的路由规则。

可通过 cat /proc/iomem 查看物理内存分布,System RAM 项代表系统可用内存。

预留系统内存

方法一:内核启动参数

在 grub 启动参数中添加:

memmap=4m\$0x10DBFFFFF

表示从 0x10DBFFFFF 起预留 4MB。重启后通过 cat /proc/iomem 验证是否标记为 reserved

方法二:memblock

setup_arch() 中调用 memblock_reserve()

memblock_reserve(0x100000000, 0x2000000);  // 预留 32MB

预留内存可通过 memremap()phy_to_vir(仅限 normal 区)映射访问。

方法三:限制总内存 mem=

在 grub 中添加 mem=2G 限制可用内存,超出部分即被隐藏。隐藏内存只能通过 memremap() 访问,phy_to_vir 不可用。

方法四:CMA

CMA(Contiguous Memory Allocation)动态分配大块连续物理内存,可用于 DMA 等场景,需修改内核配置或设备树。

访问预留内存

// 申请资源
request_mem_region(0x40000000, 0x20000000, "reserve test");

// 映射内存
void* addr = memremap(0x40000000, 0x20000000, MEMREMAP_WB);

// 使用完后释放
iounmap(addr);
release_mem_region(0x40000000, 0x20000000);

earlyoom

当系统内存不足时,earlyoom 在 OOM Killer 之前主动杀掉高内存消耗进程,避免系统卡顿。

apt install earlyoom
systemctl enable --now earlyoom

默认行为:内存和 swap 同时低于 10% 时杀 oom_score 最高的进程;低于 5% 时发 SIGKILL。

配置 /etc/default/earlyoom

EARLYOOM_ARGS="-r 60 -m 1 -s 5 --avoid '(^|/)(init|X|sshd)$'"

查看历史记录:

journalctl -u earlyoom | grep -iE "(sending|killing)"

内核内存泄漏排查

思路

  1. 监控用户进程 PSS(pmap <pid>
  2. 监控 /proc/meminfo,重点关注 Slab 使用量
  3. 使用 slabtop 查看 slab 分配,关注持续增长的 size 类别
  4. 使用 trace-cmd 跟踪 kmalloc/kfree
trace-cmd record -e kmalloc -f 'bytes_alloc==4096' -e kfree -T
trace-cmd report

对比 kmalloc 和 kfree 的 ptr 地址,匹配不上的即为泄漏。

  1. 手动触发 vmcore 用 crash 工具分析:
echo c > /proc/sysrq-trigger
crash ./vmcore ./vmlinux.debug

Linux 总线驱动模型

Linux 设备驱动模型以**总线(bus)为核心,管理设备(device)驱动(driver)**的匹配。

总线注册

static struct bus_type xbus = {
    .name = "xbus",
    .match = xbus_match,  // 设备和驱动匹配回调
};

// 模块加载时注册
bus_register(&xbus);
// 导出属性文件到 /sys
bus_create_file(&xbus, &bus_attr_xbus_test);

设备注册

static struct device xdev = {
    .init_name = "xdev",
    .bus = &xbus,
    .release = xdev_release,
};

device_register(&xdev);
// 导出设备属性
DEVICE_ATTR(xdev_id, S_IRUSR|S_IWUSR, xdev_id_show, xdev_id_store);
device_create_file(&xdev, &dev_attr_xdev_id);

驱动注册

static struct device_driver xdrv = {
    .name = "xdev",      // 与设备名一致才能匹配
    .bus = &xbus,
    .probe = xdrv_probe,
    .remove = xdrv_remove,
};

driver_register(&xdrv);

匹配成功后自动调用驱动的 probe 函数。

属性文件导出宏

用途
BUS_ATTR总线级别属性
DEVICE_ATTR设备级别属性
DRIVER_ATTR_RO / DRIVER_ATTR_WO / DRIVER_ATTR_RW驱动级别属性

属性文件出现在 /sys/bus/<bus_name>/ 对应目录下,用户可通过 cat/echo 读写。