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边界
相关推荐
小王C语言5 小时前
封装红黑树实现mymap和myset
linux·服务器·算法
獭.獭.5 小时前
Linux -- 线程概念
linux·线程·进程·多级页表·缺页异常
望获linux5 小时前
【实时Linux实战系列】使用 u-trace 或 a-trace 进行用户态应用剖析
java·linux·前端·网络·数据库·elasticsearch·操作系统
dessler5 小时前
Elasticsearch(ES)-Logstash
linux·运维·elasticsearch
lht6319356125 小时前
Ubuntu Server系统安装谷歌浏览器
linux·运维·ubuntu
JanelSirry6 小时前
DevOps是什么,有什么作用,一般用来干嘛
linux·运维·devops
---学无止境---6 小时前
Linux中控制台初始化console_init函数的实现
linux
望获linux6 小时前
【实时Linux实战系列】FPGA 与实时 Linux 的协同设计
大数据·linux·服务器·网络·数据库·fpga开发·操作系统
武文斌776 小时前
复习总结最终版:计算机网络
linux·开发语言·学习·计算机网络