Linux性能分析系统和虚拟文件系统缓存初始化

性能分析系统的初始化profile_init

c 复制代码
void __init profile_init(void)
{
        if (!prof_on)
                return;

        /* only text is profiled */
        prof_len = (_etext - _stext) >> prof_shift;
        prof_buffer = alloc_bootmem(prof_len*sizeof(atomic_t));
}

1. 函数功能概述

profile_init 函数负责初始化内核的代码性能分析系统,为内核文本段的执行频率统计分配内存缓冲区

2. 代码逐段分析

2.1. 函数定义

c 复制代码
void __init profile_init(void)
{
  • void:函数没有返回值
  • __init:宏标记,表示该函数只在内核初始化阶段使用,初始化完成后内存会被释放

2.2. 性能分析开关检查

c 复制代码
        if (!prof_on)
                return;
  • if (!prof_on):检查性能分析是否启用
  • prof_on:全局变量,控制性能分析的开关状态
  • return:如果性能分析未启用,直接返回,不进行后续初始化
  • 作用:允许在编译时或启动参数中禁用性能分析功能

2.3. 计算分析长度(仅分析文本段)

c 复制代码
        /* only text is profiled */
        prof_len = (_etext - _stext) >> prof_shift;
  • 只对文本段(代码段)进行性能分析
  • prof_len:全局变量,存储性能分析缓冲区的长度
  • _etext - _stext:计算内核文本段的大小
    • _stext:内核文本段起始地址(代码开始)
    • _etext:内核文本段结束地址(代码结束)
  • >> prof_shift:右移操作,进行大小缩放
    • prof_shift:全局变量,控制采样的粒度(通常为0或小的整数值)

2.4. 分配性能分析缓冲区

c 复制代码
        prof_buffer = alloc_bootmem(prof_len*sizeof(atomic_t));
}
  • prof_buffer:全局指针,指向性能分析缓冲区
  • alloc_bootmem(prof_len*sizeof(atomic_t)):使用启动内存分配器分配内存
    • prof_len*sizeof(atomic_t):计算需要的总内存大小
    • atomic_t:原子整数类型,用于多CPU安全计数
  • 作用:为每个代码位置分配一个计数器,记录执行次数

3. 关键技术细节详解

3.1. 内核内存布局

复制代码
内核内存映射典型布局:
0x00000000 +----------------+
           |                |
           |    文本段       | ← _stext (代码开始)
           |    (.text)     |
           +----------------+ ← _etext (代码结束)
           |                |
           |    数据段       |
           |    (.data)     |
           +----------------+
           |                |
           |    BSS段       |
           |    (.bss)      |
           +----------------+

3.2. 性能分析缓冲区结构

c 复制代码
// prof_buffer 指向的内存布局
+---------+---------+---------+-----+---------+
| counter | counter | counter | ... | counter | ← atomic_t 数组
+---------+---------+---------+-----+---------+
  地址0     地址1     地址2    ...   地址prof_len-1

// 每个计数器对应一段代码区域
计数器索引 = (指令地址 - _stext) >> prof_shift

3.3. 采样粒度控制

prof_shift 的作用:

c 复制代码
// 示例:不同 prof_shift 值的效果
_stext = 0xC0000000, _etext = 0xC0100000 (16MB代码)

prof_shift = 0:
prof_len = (0xC0100000 - 0xC0000000) >> 0 
          = 0x01000000 = 16,777,216 计数器

prof_shift = 4:  
prof_len = 0x01000000 >> 4 = 0x00100000 = 1,048,576 计数器

prof_shift = 8:
prof_len = 0x01000000 >> 8 = 0x00010000 = 65,536 计数器

作用 :通过调整 prof_shift 可以平衡分析精度和内存开销。

初始 RAM 磁盘检查代码段

c 复制代码
#ifdef CONFIG_BLK_DEV_INITRD
        if (initrd_start && !initrd_below_start_ok &&
                        initrd_start < min_low_pfn << PAGE_SHIFT) {
                printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
                    "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
                initrd_start = 0;
        }
#endif

1. 代码逐段分析

1.1. 条件编译开始

c 复制代码
#ifdef CONFIG_BLK_DEV_INITRD
  • #ifdef CONFIG_BLK_DEV_INITRD:条件编译指令
  • 只有在内核配置了 CONFIG_BLK_DEV_INITRD(支持初始 RAM 磁盘)时,才编译包含的代码
  • 作用:允许在编译时完全禁用 INITRD 相关功能,减少内核大小

1.2. 主要条件检查

c 复制代码
        if (initrd_start && !initrd_below_start_ok &&
                        initrd_start < min_low_pfn << PAGE_SHIFT) {

这是一个复合条件检查,包含三个部分:

条件分解:

  • initrd_start:检查 INITRD 起始地址是否有效(非零)
  • !initrd_below_start_ok:检查是否允许 INITRD 在低内存区域之下
  • initrd_start < min_low_pfn << PAGE_SHIFT:检查 INITRD 是否位于有效内存范围之下

1.3. 地址计算解析

c 复制代码
initrd_start < min_low_pfn << PAGE_SHIFT

计算过程:

  • min_low_pfn:最低可用物理页帧号
  • << PAGE_SHIFT:左移页大小位数(通常12位,因为PAGE_SIZE=4096)
  • min_low_pfn << PAGE_SHIFT:将页帧号转换为物理地址
  • 含义:检查 INITRD 起始地址是否低于第一个可用内存页的地址

1.4. 错误信息打印

c 复制代码
                printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
                    "disabling it.\n",initrd_start,min_low_pfn << PAGE_SHIFT);
  • printk(KERN_CRIT ...):关键错误级别日志输出
  • 消息格式:"initrd overwritten (0x%08lx < 0x%08lx) - disabling it."
  • 参数:
    • initrd_start:INITRD 起始地址
    • min_low_pfn << PAGE_SHIFT:最低可用内存地址
  • 作用:告知用户 INITRD 被覆盖的具体地址信息

1.5. 禁用 INITRD

c 复制代码
                initrd_start = 0;
        }
  • initrd_start = 0:将 INITRD 起始地址设为0
  • 禁用 INITRD,因为后续代码检查 initrd_start 是否为0来判断是否存在有效的 INITRD

代码段总结

  1. 安全性检查:确保 INITRD 不会与内核内存冲突
  2. 条件编译:只在需要时包含 INITRD 相关代码
  3. 错误检测:识别 INITRD 位置问题
  4. 优雅降级:通过禁用损坏的 INITRD 避免系统崩溃

虚拟文件系统缓存早期初始化vfs_caches_init_early

c 复制代码
void __init vfs_caches_init_early(void)
{
        dcache_init_early();
        inode_init_early();
}
static void __init dcache_init_early(void)
{
        int loop;

        dentry_hashtable =
                alloc_large_system_hash("Dentry cache",
                                        sizeof(struct hlist_head),
                                        dhash_entries,
                                        13,
                                        0,
                                        &d_hash_shift,
                                        &d_hash_mask);

        for (loop = 0; loop < (1 << d_hash_shift); loop++)
                INIT_HLIST_HEAD(&dentry_hashtable[loop]);
}
void __init inode_init_early(void)
{
        int loop;

        inode_hashtable =
                alloc_large_system_hash("Inode-cache",
                                        sizeof(struct hlist_head),
                                        ihash_entries,
                                        14,
                                        0,
                                        &i_hash_shift,
                                        &i_hash_mask);

        for (loop = 0; loop < (1 << i_hash_shift); loop++)
                INIT_HLIST_HEAD(&inode_hashtable[loop]);
}

1. 函数功能概述

这些函数负责在系统启动早期初始化虚拟文件系统的目录项缓存(dentry cache)和索引节点缓存(inode cache)的哈希表结构,为文件系统操作提供基础数据结构支持

2. 代码逐段分析

2.1. 顶层初始化函数

c 复制代码
void __init vfs_caches_init_early(void)
{
        dcache_init_early();
        inode_init_early();
}
  • void __init:无返回值,__init 表示只在初始化阶段使用
  • vfs_caches_init_early:虚拟文件系统缓存早期初始化
  • dcache_init_early():调用目录项缓存初始化函数
  • inode_init_early():调用索引节点缓存初始化函数
  • 作用:统一初始化 VFS 的两个核心缓存系统

2.2. 目录项缓存初始化函数

c 复制代码
static void __init dcache_init_early(void)
{
        int loop;
  • static:只在当前文件内可见
  • __init:初始化阶段函数
  • int loop:循环计数器变量

2.3. 分配目录项哈希表

c 复制代码
        dentry_hashtable =
                alloc_large_system_hash("Dentry cache",
                                        sizeof(struct hlist_head),
                                        dhash_entries,
                                        13,
                                        0,
                                        &d_hash_shift,
                                        &d_hash_mask);
  • "Dentry cache":哈希表名称,用于调试和显示
  • sizeof(struct hlist_head):每个桶的大小(链表头大小)
  • dhash_entries:期望的哈希表条目数(可配置参数)
  • 13:哈希表移位值(初始建议值,2¹³ = 8192 个桶)
  • 0:标志位,0表示无特殊标志
  • &d_hash_shift:返回实际的移位值
  • &d_hash_mask:返回哈希掩码值

2.4. 初始化目录项哈希桶

c 复制代码
        for (loop = 0; loop < (1 << d_hash_shift); loop++)
                INIT_HLIST_HEAD(&dentry_hashtable[loop]);
}
  • loop = 0:从第一个桶开始
  • loop < (1 << d_hash_shift):循环条件,遍历所有桶
    • 1 << d_hash_shift:计算实际桶数量(2^d_hash_shift)
  • INIT_HLIST_HEAD(&dentry_hashtable[loop]):初始化每个哈希桶的链表头

2.5. 索引节点缓存初始化函数

c 复制代码
void __init inode_init_early(void)
{
        int loop;
  • void __init:无返回值,初始化阶段函数
  • inode_init_early:索引节点缓存早期初始化
  • int loop:循环计数器

2.6. 分配索引节点哈希表

c 复制代码
        inode_hashtable =
                alloc_large_system_hash("Inode-cache",
                                        sizeof(struct hlist_head),
                                        ihash_entries,
                                        14,
                                        0,
                                        &i_hash_shift,
                                        &i_hash_mask);

参数对比:

  • "Inode-cache":索引节点缓存名称
  • sizeof(struct hlist_head):相同,链表头大小
  • ihash_entries:索引节点哈希表期望条目数
  • 14:更大的初始移位值(2¹⁴ = 16384 个桶)
  • &i_hash_shift:索引节点哈希移位值
  • &i_hash_mask:索引节点哈希掩码

2.7. 初始化索引节点哈希桶

c 复制代码
        for (loop = 0; loop < (1 << i_hash_shift); loop++)
                INIT_HLIST_HEAD(&inode_hashtable[loop]);
}
  • 与目录项缓存相同的初始化逻辑
  • 遍历所有桶并初始化链表头

分配大型系统哈希表alloc_large_system_hash

c 复制代码
void *__init alloc_large_system_hash(const char *tablename,
                                     unsigned long bucketsize,
                                     unsigned long numentries,
                                     int scale,
                                     int consider_highmem,
                                     unsigned int *_hash_shift,
                                     unsigned int *_hash_mask)
{
        unsigned long long max;
        unsigned long log2qty, size;
        void *table;

        /* allow the kernel cmdline to have a say */
        if (!numentries) {
                /* round applicable memory size up to nearest megabyte */
                numentries = consider_highmem ? nr_all_pages : nr_kernel_pages;
                numentries += (1UL << (20 - PAGE_SHIFT)) - 1;
                numentries >>= 20 - PAGE_SHIFT;
                numentries <<= 20 - PAGE_SHIFT;

                /* limit to 1 bucket per 2^scale bytes of low memory */
                if (scale > PAGE_SHIFT)
                        numentries >>= (scale - PAGE_SHIFT);
                else
                        numentries <<= (PAGE_SHIFT - scale);
        }
        /* rounded up to nearest power of 2 in size */
        numentries = 1UL << (long_log2(numentries) + 1);

        /* limit allocation size to 1/16 total memory */
        max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> 4;
        do_div(max, bucketsize);

        if (numentries > max)
                numentries = max;

        log2qty = long_log2(numentries);

        do {
                size = bucketsize << log2qty;
                table = alloc_bootmem(size);
        } while (!table && size > PAGE_SIZE && --log2qty);

        if (!table)
                panic("Failed to allocate %s hash table\n", tablename);

        printk("%s hash table entries: %d (order: %d, %lu bytes)\n",
               tablename,
               (1U << log2qty),
               long_log2(size) - PAGE_SHIFT,
               size);

        if (_hash_shift)
                *_hash_shift = log2qty;
        if (_hash_mask)
                *_hash_mask = (1 << log2qty) - 1;

        return table;
}

1. 函数功能概述

alloc_large_system_hash 函数负责根据系统内存大小智能分配哈希表,自动调整哈希表大小以平衡性能和内存使用,为内核各种缓存系统提供合适大小的哈希表

2. 代码逐段分析

2.1. 函数定义和参数

c 复制代码
void *__init alloc_large_system_hash(const char *tablename,
                                     unsigned long bucketsize,
                                     unsigned long numentries,
                                     int scale,
                                     int consider_highmem,
                                     unsigned int *_hash_shift,
                                     unsigned int *_hash_mask)
{
  • void *__init:返回分配的内存指针,__init 表示只在初始化阶段使用
  • const char *tablename:哈希表名称,用于调试输出
  • unsigned long bucketsize:每个哈希桶的大小(字节)
  • unsigned long numentries:期望的哈希表条目数
  • int scale:缩放因子,控制内存与哈希表大小的比例
  • int consider_highmem:是否考虑高端内存
  • unsigned int *_hash_shift:返回哈希移位值
  • unsigned int *_hash_mask:返回哈希掩码值

2.2. 变量声明

c 复制代码
        unsigned long long max;
        unsigned long log2qty, size;
        void *table;
  • max:最大允许的哈希表大小
  • log2qty:以2为底的对数,表示哈希表大小
  • size:要分配的内存大小
  • table:指向分配的内存

2.3. 自动计算条目数(如果未指定)

c 复制代码
        /* allow the kernel cmdline to have a say */
        if (!numentries) {
                /* round applicable memory size up to nearest megabyte */
                numentries = consider_highmem ? nr_all_pages : nr_kernel_pages;
                numentries += (1UL << (20 - PAGE_SHIFT)) - 1;
                numentries >>= 20 - PAGE_SHIFT;
                numentries <<= 20 - PAGE_SHIFT;
  • if (!numentries):如果没有指定条目数,自动计算
  • consider_highmem ? nr_all_pages : nr_kernel_pages:根据是否考虑高端内存选择页数
  • 将页数向上舍入到最近的MB边界

2.4. 应用缩放因子

c 复制代码
                /* limit to 1 bucket per 2^scale bytes of low memory */
                if (scale > PAGE_SHIFT)
                        numentries >>= (scale - PAGE_SHIFT);
                else
                        numentries <<= (PAGE_SHIFT - scale);
        }
  • 根据缩放因子调整条目数
  • scale > PAGE_SHIFT:缩放因子大于页大小移位值,减少条目数
  • else:增加条目数
  • 作用 :控制每 2^scale 字节内存对应1个哈希桶

2.5. 向上取整到2的幂

c 复制代码
        /* rounded up to nearest power of 2 in size */
        numentries = 1UL << (long_log2(numentries) + 1);
  • long_log2(numentries):计算以2为底的对数
  • 1UL << (long_log2(numentries) + 1):向上取整到下一个2的幂
  • 目的:确保哈希表大小是2的幂,便于使用位操作进行哈希计算

2.6. 限制最大分配大小

c 复制代码
        /* limit allocation size to 1/16 total memory */
        max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> 4;
        do_div(max, bucketsize);

        if (numentries > max)
                numentries = max;
  • max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> 4:计算总内存的1/16
  • do_div(max, bucketsize):除以每个桶的大小,得到最大桶数
  • if (numentries > max):如果请求的条目数超过最大限制,使用最大值

2.7. 计算对数并尝试分配

c 复制代码
        log2qty = long_log2(numentries);

        do {
                size = bucketsize << log2qty;
                table = alloc_bootmem(size);
        } while (!table && size > PAGE_SIZE && --log2qty);
  • log2qty = long_log2(numentries):计算最终的对数值,做为移位值
  • do-while 循环:尝试分配内存,如果失败则减小大小重试
  • size = bucketsize << log2qty:计算实际要分配的内存大小
  • table = alloc_bootmem(size):使用启动内存分配器分配内存
  • 循环条件:分配失败、大小大于一页、还可以继续减小

2.8. 分配失败处理

c 复制代码
        if (!table)
                panic("Failed to allocate %s hash table\n", tablename);
  • 如果所有分配尝试都失败,触发内核恐慌
  • 显示具体的哈希表名称,便于调试

2.9. 打印分配信息

c 复制代码
        printk("%s hash table entries: %d (order: %d, %lu bytes)\n",
               tablename,
               (1U << log2qty),
               long_log2(size) - PAGE_SHIFT,
               size);
  • 输出哈希表分配信息:
    • 表名称
    • 条目数量(2^log2qty)
    • 分配阶数(相对于页大小的对数)
    • 总字节数

2.10. 设置输出参数

c 复制代码
        if (_hash_shift)
                *_hash_shift = log2qty;
        if (_hash_mask)
                *_hash_mask = (1 << log2qty) - 1;

        return table;
}
  • *_hash_shift = log2qty:设置哈希移位值
  • *_hash_mask = (1 << log2qty) - 1:计算并设置哈希掩码
  • return table:返回分配的内存指针

3. 关键技术细节详解

3.1. 内存计算算法

c 复制代码
// 自动计算条目数的详细过程
numentries = nr_kernel_pages;                    // 内核页数
numentries += (1 << (20 - PAGE_SHIFT)) - 1;      // 加 (1MB对应的页数 - 1)
numentries >>= 20 - PAGE_SHIFT;                  // 除以1MB对应的页数
numentries <<= 20 - PAGE_SHIFT;                  // 乘以1MB对应的页数
// 结果:向上舍入到最近的MB边界
相关推荐
HLC++9 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室10 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy13 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕13 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫15 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生15 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1116 小时前
linux基础IO
linux·运维·服务器
BelongPanda18 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯18 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983419 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器