Linux中进程创建和缓存对象初始化fork_init、proc_caches_init和buffer_init
初始化进程创建 fork_init
void __init fork_init(unsigned long mempages)
{
#ifndef __HAVE_ARCH_TASK_STRUCT_ALLOCATOR
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN L1_CACHE_BYTES
#endif/* create a slab on which task_structs can be allocated */task_struct_cachep =kmem_cache_create("task_struct", sizeof(struct task_struct),ARCH_MIN_TASKALIGN, SLAB_PANIC, NULL, NULL);
#endif/** The default maximum number of threads is set to a safe* value: the thread structures can take up at most half* of memory.*/max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);/** we need to allow at least 20 threads to boot a system*/if(max_threads < 20)max_threads = 20;init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
}
函数功能概述
fork_init
函数用于初始化进程创建(fork)相关的子系统,包括任务结构缓存池的创建和线程数量限制的设置
代码详细解释
第一部分:任务结构缓存池初始化
#ifndef __HAVE_ARCH_TASK_STRUCT_ALLOCATOR
#ifndef ARCH_MIN_TASKALIGN
#define ARCH_MIN_TASKALIGN L1_CACHE_BYTES
#endif
- 条件编译检查:如果没有定义架构特定的任务结构分配器(
__HAVE_ARCH_TASK_STRUCT_ALLOCATOR
),则使用默认的 slab 分配器 - 对齐设置:如果没有定义架构最小任务对齐(
ARCH_MIN_TASKALIGN
),则默认使用 L1 缓存行大小作为对齐值,这有利于缓存性能
task_struct_cachep =kmem_cache_create("task_struct", sizeof(struct task_struct),ARCH_MIN_TASKALIGN, SLAB_PANIC, NULL, NULL);
#endif
- 创建 slab 缓存:使用
kmem_cache_create
创建一个专门用于分配task_struct
的 slab 缓存池 - 参数说明:
"task_struct"
:缓存名称sizeof(struct task_struct)
:每个对象的大小ARCH_MIN_TASKALIGN
:对齐要求SLAB_PANIC
:标志位,表示如果创建失败则系统崩溃NULL, NULL
:构造函数和析构函数(这里不需要)
第二部分:最大线程数计算
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
- 计算最大线程数:基于系统内存页数计算最大允许的线程数量
- 计算公式:
mempages / (8 * THREAD_SIZE / PAGE_SIZE)
THREAD_SIZE
是每个线程内核栈的大小PAGE_SIZE
是页面大小8 * THREAD_SIZE / PAGE_SIZE
计算每个线程需要的内存页数- 8 是经验值,确保其他结构有内存预留
if(max_threads < 20)max_threads = 20;
- 最小线程数保障:确保系统至少有 20 个线程的容量,这是系统启动所需的最小线程数
第三部分:进程数资源限制设置
init_task.signal->rlim[RLIMIT_NPROC].rlim_cur = max_threads/2;init_task.signal->rlim[RLIMIT_NPROC].rlim_max = max_threads/2;
- 设置进程数限制:为初始进程(
init_task
)设置 RLIMIT_NPROC 资源限制 - 限制值:当前值(
rlim_cur
)和最大值(rlim_max
)都设置为最大线程数的一半 - RLIMIT_NPROC:限制每个用户能够创建的进程数量
函数功能总结
- 任务结构缓存初始化:为
task_struct
创建专用的内存缓存池,提高进程创建时的内存分配效率 - 线程数量限制计算:根据系统内存大小动态计算最大线程数,确保系统稳定性
- 资源限制设置:设置每个用户能够创建的进程数上限,防止资源耗尽
初始化进程管理相关的各种内核缓存proc_caches_init
void __init proc_caches_init(void)
{sighand_cachep = kmem_cache_create("sighand_cache",sizeof(struct sighand_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);signal_cachep = kmem_cache_create("signal_cache",sizeof(struct signal_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);files_cachep = kmem_cache_create("files_cache",sizeof(struct files_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);fs_cachep = kmem_cache_create("fs_cache",sizeof(struct fs_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);vm_area_cachep = kmem_cache_create("vm_area_struct",sizeof(struct vm_area_struct), 0,SLAB_PANIC, NULL, NULL);mm_cachep = kmem_cache_create("mm_struct",sizeof(struct mm_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
}
函数功能概述
proc_caches_init
函数用于初始化进程管理相关的各种内核缓存(slab cache),这些缓存用于高效分配进程相关的数据结构
代码详细解释
第一部分:信号处理相关缓存初始化
sighand_cachep = kmem_cache_create("sighand_cache",sizeof(struct sighand_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 信号处理结构缓存:创建用于分配
sighand_struct
的 slab 缓存 - 数据结构:
sighand_struct
管理进程的信号处理程序(信号处理器函数指针数组) - 标志:
SLAB_HWCACHE_ALIGN
确保缓存行对齐,提高缓存性能 - 大小:
sizeof(struct sighand_struct)
根据架构不同通常为几百字节
signal_cachep = kmem_cache_create("signal_cache",sizeof(struct signal_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 信号状态结构缓存:创建用于分配
signal_struct
的 slab 缓存 - 数据结构:
signal_struct
包含进程的信号状态信息(待处理信号、信号阻塞掩码等) - 与
sighand_struct
的区别:sighand_struct
:信号处理行为(怎么做)signal_struct
:信号状态信息(有什么信号)
第二部分:文件系统相关缓存初始化
files_cachep = kmem_cache_create("files_cache",sizeof(struct files_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 文件描述符表缓存:创建用于分配
files_struct
的 slab 缓存 - 数据结构:
files_struct
管理进程打开的文件描述符表 - 重要性:每个进程都有独立的文件描述符表,频繁创建和销毁
fs_cachep = kmem_cache_create("fs_cache",sizeof(struct fs_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 文件系统状态缓存:创建用于分配
fs_struct
的 slab 缓存 - 数据结构:
fs_struct
包含进程的文件系统相关信息(根目录、当前工作目录等)
第三部分:内存管理相关缓存初始化
vm_area_cachep = kmem_cache_create("vm_area_struct",sizeof(struct vm_area_struct), 0,SLAB_PANIC, NULL, NULL);
- 虚拟内存区域缓存:创建用于分配
vm_area_struct
的 slab 缓存 - 数据结构:
vm_area_struct
描述进程地址空间的一个虚拟内存区域 - 特点:没有使用
SLAB_HWCACHE_ALIGN
,因为 VMA 访问模式对缓存不敏感 - 使用频率:非常高频,进程可能有数十到数百个 VMA
mm_cachep = kmem_cache_create("mm_struct",sizeof(struct mm_struct), 0,SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 内存描述符缓存:创建用于分配
mm_struct
的 slab 缓存 - 数据结构:
mm_struct
是进程内存管理的核心结构,包含所有内存区域信息 - 重要性:每个进程都有一个 mm_struct(内核线程除外)
关键参数说明
SLAB 标志位
- SLAB_HWCACHE_ALIGN:确保对象在缓存行边界对齐,减少伪共享
- SLAB_PANIC:如果缓存创建失败,直接内核崩溃(因为这些缓存是系统运行必需的)
初始化缓冲区头buffer_init
void __init buffer_init(void)
{int nrpages;bh_cachep = kmem_cache_create("buffer_head",sizeof(struct buffer_head), 0,SLAB_PANIC, init_buffer_head, NULL);/** Limit the bh occupancy to 10% of ZONE_NORMAL*/nrpages = (nr_free_buffer_pages() * 10) / 100;max_buffer_heads = nrpages * (PAGE_SIZE / sizeof(struct buffer_head));hotcpu_notifier(buffer_cpu_notify, 0);
}
函数功能概述
buffer_init
函数用于初始化缓冲区头(buffer_head)缓存系统,这是 Linux 块设备 I/O 子系统的重要组成部分,用于管理页缓存和块设备之间的数据传递
代码详细解释
第一部分:缓冲区头缓存创建
bh_cachep = kmem_cache_create("buffer_head",sizeof(struct buffer_head), 0,SLAB_PANIC, init_buffer_head, NULL);
- 缓存名称:
"buffer_head"
- 明确标识这是缓冲区头对象的缓存 - 对象大小:
sizeof(struct buffer_head)
- 对齐参数:
0
- 使用默认对齐方式 - 标志:
SLAB_PANIC
- 如果创建失败则系统崩溃,因为这是关键基础设施 - 构造函数:
init_buffer_head
- 重要的初始化函数(后面详细解释) - 析构函数:
NULL
- 不需要特殊的析构处理
第二部分:缓冲区头数量限制计算
/** Limit the bh occupancy to 10% of ZONE_NORMAL*/nrpages = (nr_free_buffer_pages() * 10) / 100;max_buffer_heads = nrpages * (PAGE_SIZE / sizeof(struct buffer_head));
- 限制缓冲区头占用 ZONE_NORMAL 区域的 10%
- ZONE_NORMAL 是内核直接映射的物理内存区域
计算过程:
-
nr_free_buffer_pages()
- 获取可用的缓冲区页数- 这个函数返回 ZONE_NORMAL 中可用于缓冲区缓存的内存页数
-
(nr_free_buffer_pages() * 10) / 100
- 计算 10% 的可用页数- 这是系统为缓冲区头分配预留的内存页数上限
-
PAGE_SIZE / sizeof(struct buffer_head)
- 计算每页可以存放的缓冲区头数量 -
max_buffer_heads = nrpages * (PAGE_SIZE / sizeof(struct buffer_head))
- 最终计算出系统允许的最大缓冲区头数量
第三部分:CPU热插拔通知器注册
hotcpu_notifier(buffer_cpu_notify, 0);
hotcpu_notifier
- 注册一个CPU热插拔通知回调函数buffer_cpu_notify
- 当CPU状态变化时被调用的函数- 优先级:
0
- 标准优先级
创建新的 buffer_head 对象时进行初始化init_buffer_head
static void
init_buffer_head(void *data, kmem_cache_t *cachep, unsigned long flags)
{if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==SLAB_CTOR_CONSTRUCTOR) {struct buffer_head * bh = (struct buffer_head *)data;memset(bh, 0, sizeof(*bh));INIT_LIST_HEAD(&bh->b_assoc_buffers);}
}
函数功能概述
init_buffer_head
是 buffer_head 对象的 slab 构造函数,用于在 slab 分配器创建新的 buffer_head 对象时进行初始化
代码详细解释
第一部分:函数签名和参数
static void
init_buffer_head(void *data, kmem_cache_t *cachep, unsigned long flags)
- static:函数只在当前文件内可见
- void:没有返回值
- 参数说明:
void *data
:指向要初始化的 slab 对象的指针(这里是 buffer_head)kmem_cache_t *cachep
:指向所属 slab 缓存的指针unsigned long flags
:构造标志位,控制初始化行为
第二部分:条件检查
if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==SLAB_CTOR_CONSTRUCTOR) {
- 位掩码操作:
flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)
SLAB_CTOR_VERIFY
:验证模式标志SLAB_CTOR_CONSTRUCTOR
:构造函数模式标志
- 条件判断:检查是否处于纯构造函数模式(不是验证模式)
- 为什么需要这个检查:
- slab 分配器可能在多种场景下调用这个函数
- 只有在真正创建新对象时才需要初始化
- 验证模式只需要检查对象状态,不需要重新初始化
第三部分:类型转换和内存清零
struct buffer_head * bh = (struct buffer_head *)data;memset(bh, 0, sizeof(*bh));
-
类型转换:
(struct buffer_head *)data
- 将泛型 void 指针转换为具体的 buffer_head 指针
- 这样可以直接访问 buffer_head 的各个字段
-
内存清零:
memset(bh, 0, sizeof(*bh))
- 将整个 buffer_head 结构体所有字节设置为 0
sizeof(*bh)
获取 buffer_head 的实际大小- 优点:一次性清除所有字段,包括未来可能新增的字段
第四部分:链表初始化
INIT_LIST_HEAD(&bh->b_assoc_buffers);}
INIT_LIST_HEAD
:Linux 内核的链表初始化宏b_assoc_buffers
:buffer_head 的关联缓冲区链表- 链表作用:用于将相关的 buffer_head 连接在一起
CPU 热插拔时缓冲区头的清理机制buffer_cpu_notify
static int buffer_cpu_notify(struct notifier_block *self,unsigned long action, void *hcpu)
{if (action == CPU_DEAD)buffer_exit_cpu((unsigned long)hcpu);return NOTIFY_OK;
}
static void buffer_exit_cpu(int cpu)
{int i;struct bh_lru *b = &per_cpu(bh_lrus, cpu);for (i = 0; i < BH_LRU_SIZE; i++) {brelse(b->bhs[i]);b->bhs[i] = NULL;}
}
static inline void brelse(struct buffer_head *bh)
{if (bh)__brelse(bh);
}
void __brelse(struct buffer_head * buf)
{if (atomic_read(&buf->b_count)) {put_bh(buf);return;}printk(KERN_ERR "VFS: brelse: Trying to free free buffer\n");WARN_ON(1);
}
static inline void put_bh(struct buffer_head *bh)
{smp_mb__before_atomic_dec();atomic_dec(&bh->b_count);
}
整体功能概述
这些函数组成了 CPU 热插拔时缓冲区头(buffer_head)的清理机制,确保当 CPU 离线时,该 CPU 的缓冲区缓存被正确清理
代码详细解释
第一部分:CPU 通知器回调函数
static int buffer_cpu_notify(struct notifier_block *self,unsigned long action, void *hcpu)
{if (action == CPU_DEAD)buffer_exit_cpu((unsigned long)hcpu);return NOTIFY_OK;
}
-
参数说明:
self
:指向通知器块本身的指针action
:CPU 状态变化动作(CPU_UP_PREPARE, CPU_STARTING, CPU_DEAD 等)hcpu
:受影响的 CPU 编号(void* 类型,需要转换)
-
条件判断:
if (action == CPU_DEAD)
- 只在 CPU 离线时执行清理操作
- 其他状态(如 CPU 上线)不需要特殊处理
-
函数调用:
buffer_exit_cpu((unsigned long)hcpu)
- 将
hcpu
从 void* 转换为 unsigned long(CPU 编号) - 调用具体的清理函数
- 将
-
返回值:
return NOTIFY_OK;
- 表示这个通知已处理完成
- 允许其他通知器继续处理同一事件
第二部分:CPU 缓冲区清理函数
static void buffer_exit_cpu(int cpu)
{int i;struct bh_lru *b = &per_cpu(bh_lrus, cpu);for (i = 0; i < BH_LRU_SIZE; i++) {brelse(b->bhs[i]);b->bhs[i] = NULL;}
}
-
参数:
int cpu
- 要清理的 CPU 编号 -
局部变量:
int i
:循环计数器struct bh_lru *b
:指向该 CPU 的缓冲区 LRU 缓存
-
per_cpu
变量:&per_cpu(bh_lrus, cpu)
bh_lrus
是 per-CPU 变量,每个 CPU 有自己的缓冲区 LRU 缓存- 获取指定 CPU 的
bh_lru
结构指针
-
循环清理:
for (i = 0; i < BH_LRU_SIZE; i++)
BH_LRU_SIZE
通常是 4 或 8,定义每个 CPU 缓存的缓冲区数量- 遍历 LRU 数组中的所有缓冲区
-
清理操作:
brelse(b->bhs[i])
:释放缓冲区引用b->bhs[i] = NULL
:将槽位置空,防止悬空指针
第三部分:缓冲区释放函数链
static inline void brelse(struct buffer_head *bh)
{if (bh)__brelse(bh);
}
- static inline:内联函数,减少函数调用开销
- 空指针检查:
if (bh)
- 确保不会对空指针进行操作
- 实际调用:
__brelse(bh)
- 调用实际的释放函数
第四部分:缓冲区释放核心逻辑
void __brelse(struct buffer_head * buf)
{if (atomic_read(&buf->b_count)) {put_bh(buf);return;}printk(KERN_ERR "VFS: brelse: Trying to free free buffer\n");WARN_ON(1);
}
-
引用计数检查:
if (atomic_read(&buf->b_count))
b_count
是缓冲区的引用计数atomic_read
原子性地读取计数值- 如果计数 > 0,说明缓冲区正在被使用
-
正常释放路径:
put_bh(buf)
:减少引用计数return
:正常返回
-
错误检测:
- 如果引用计数已经是 0,说明重复释放
printk(KERN_ERR ...)
:输出错误日志到内核日志WARN_ON(1)
:触发内核警告,可能生成堆栈跟踪
第五部分:原子引用计数减少
static inline void put_bh(struct buffer_head *bh)
{smp_mb__before_atomic_dec();atomic_dec(&bh->b_count);
}
-
内存屏障:
smp_mb__before_atomic_dec()
- 在多核系统中确保内存操作的顺序性
- 防止指令重排序导致的数据不一致
-
原子操作:
atomic_dec(&bh->b_count)
- 原子性地将引用计数减 1