性能分析系统的初始化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
代码段总结
- 安全性检查:确保 INITRD 不会与内核内存冲突
- 条件编译:只在需要时包含 INITRD 相关代码
- 错误检测:识别 INITRD 位置问题
- 优雅降级:通过禁用损坏的 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/16do_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边界