Linux性能分析系统和虚拟文件系统缓存初始化
性能分析系统的初始化profile_init
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. 函数定义
void __init profile_init(void)
{
void
:函数没有返回值__init
:宏标记,表示该函数只在内核初始化阶段使用,初始化完成后内存会被释放
2.2. 性能分析开关检查
if (!prof_on)return;
if (!prof_on)
:检查性能分析是否启用prof_on
:全局变量,控制性能分析的开关状态return
:如果性能分析未启用,直接返回,不进行后续初始化- 作用:允许在编译时或启动参数中禁用性能分析功能
2.3. 计算分析长度(仅分析文本段)
/* only text is profiled */prof_len = (_etext - _stext) >> prof_shift;
- 只对文本段(代码段)进行性能分析
prof_len
:全局变量,存储性能分析缓冲区的长度_etext - _stext
:计算内核文本段的大小_stext
:内核文本段起始地址(代码开始)_etext
:内核文本段结束地址(代码结束)
>> prof_shift
:右移操作,进行大小缩放prof_shift
:全局变量,控制采样的粒度(通常为0或小的整数值)
2.4. 分配性能分析缓冲区
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. 性能分析缓冲区结构
// prof_buffer 指向的内存布局
+---------+---------+---------+-----+---------+
| counter | counter | counter | ... | counter | ← atomic_t 数组
+---------+---------+---------+-----+---------+地址0 地址1 地址2 ... 地址prof_len-1// 每个计数器对应一段代码区域
计数器索引 = (指令地址 - _stext) >> prof_shift
3.3. 采样粒度控制
prof_shift
的作用:
// 示例:不同 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 磁盘检查代码段
#ifdef CONFIG_BLK_DEV_INITRDif (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. 条件编译开始
#ifdef CONFIG_BLK_DEV_INITRD
#ifdef CONFIG_BLK_DEV_INITRD
:条件编译指令- 只有在内核配置了
CONFIG_BLK_DEV_INITRD
(支持初始 RAM 磁盘)时,才编译包含的代码 - 作用:允许在编译时完全禁用 INITRD 相关功能,减少内核大小
1.2. 主要条件检查
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. 地址计算解析
initrd_start < min_low_pfn << PAGE_SHIFT
计算过程:
min_low_pfn
:最低可用物理页帧号<< PAGE_SHIFT
:左移页大小位数(通常12位,因为PAGE_SIZE=4096)min_low_pfn << PAGE_SHIFT
:将页帧号转换为物理地址- 含义:检查 INITRD 起始地址是否低于第一个可用内存页的地址
1.4. 错误信息打印
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
initrd_start = 0;}
initrd_start = 0
:将 INITRD 起始地址设为0- 禁用 INITRD,因为后续代码检查
initrd_start
是否为0来判断是否存在有效的 INITRD
代码段总结
- 安全性检查:确保 INITRD 不会与内核内存冲突
- 条件编译:只在需要时包含 INITRD 相关代码
- 错误检测:识别 INITRD 位置问题
- 优雅降级:通过禁用损坏的 INITRD 避免系统崩溃
虚拟文件系统缓存早期初始化vfs_caches_init_early
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. 顶层初始化函数
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. 目录项缓存初始化函数
static void __init dcache_init_early(void)
{int loop;
static
:只在当前文件内可见__init
:初始化阶段函数int loop
:循环计数器变量
2.3. 分配目录项哈希表
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. 初始化目录项哈希桶
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. 索引节点缓存初始化函数
void __init inode_init_early(void)
{int loop;
void __init
:无返回值,初始化阶段函数inode_init_early
:索引节点缓存早期初始化int loop
:循环计数器
2.6. 分配索引节点哈希表
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. 初始化索引节点哈希桶
for (loop = 0; loop < (1 << i_hash_shift); loop++)INIT_HLIST_HEAD(&inode_hashtable[loop]);
}
- 与目录项缓存相同的初始化逻辑
- 遍历所有桶并初始化链表头
分配大型系统哈希表alloc_large_system_hash
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);elsenumentries <<= (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. 函数定义和参数
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. 变量声明
unsigned long long max;unsigned long log2qty, size;void *table;
max
:最大允许的哈希表大小log2qty
:以2为底的对数,表示哈希表大小size
:要分配的内存大小table
:指向分配的内存
2.3. 自动计算条目数(如果未指定)
/* 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. 应用缩放因子
/* limit to 1 bucket per 2^scale bytes of low memory */if (scale > PAGE_SHIFT)numentries >>= (scale - PAGE_SHIFT);elsenumentries <<= (PAGE_SHIFT - scale);}
- 根据缩放因子调整条目数
scale > PAGE_SHIFT
:缩放因子大于页大小移位值,减少条目数else
:增加条目数- 作用:控制每
2^scale
字节内存对应1个哈希桶
2.5. 向上取整到2的幂
/* 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. 限制最大分配大小
/* 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/16do_div(max, bucketsize)
:除以每个桶的大小,得到最大桶数if (numentries > max)
:如果请求的条目数超过最大限制,使用最大值
2.7. 计算对数并尝试分配
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. 分配失败处理
if (!table)panic("Failed to allocate %s hash table\n", tablename);
- 如果所有分配尝试都失败,触发内核恐慌
- 显示具体的哈希表名称,便于调试
2.9. 打印分配信息
printk("%s hash table entries: %d (order: %d, %lu bytes)\n",tablename,(1U << log2qty),long_log2(size) - PAGE_SHIFT,size);
- 输出哈希表分配信息:
- 表名称
- 条目数量(2^log2qty)
- 分配阶数(相对于页大小的对数)
- 总字节数
2.10. 设置输出参数
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. 内存计算算法
// 自动计算条目数的详细过程
numentries = nr_kernel_pages; // 内核页数
numentries += (1 << (20 - PAGE_SHIFT)) - 1; // 加 (1MB对应的页数 - 1)
numentries >>= 20 - PAGE_SHIFT; // 除以1MB对应的页数
numentries <<= 20 - PAGE_SHIFT; // 乘以1MB对应的页数
// 结果:向上舍入到最近的MB边界