初始化进程创建 fork_init
c
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)相关的子系统,包括任务结构缓存池的创建和线程数量限制的设置
代码详细解释
第一部分:任务结构缓存池初始化
c
#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 缓存行大小作为对齐值,这有利于缓存性能
c
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
:构造函数和析构函数(这里不需要)
第二部分:最大线程数计算
c
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
- 计算最大线程数:基于系统内存页数计算最大允许的线程数量
- 计算公式 :
mempages / (8 * THREAD_SIZE / PAGE_SIZE)
THREAD_SIZE
是每个线程内核栈的大小PAGE_SIZE
是页面大小8 * THREAD_SIZE / PAGE_SIZE
计算每个线程需要的内存页数- 8 是经验值,确保其他结构有内存预留
c
if(max_threads < 20)
max_threads = 20;
- 最小线程数保障:确保系统至少有 20 个线程的容量,这是系统启动所需的最小线程数
第三部分:进程数资源限制设置
c
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
c
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),这些缓存用于高效分配进程相关的数据结构
代码详细解释
第一部分:信号处理相关缓存初始化
c
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)
根据架构不同通常为几百字节
c
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
:信号状态信息(有什么信号)
第二部分:文件系统相关缓存初始化
c
files_cachep = kmem_cache_create("files_cache",
sizeof(struct files_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 文件描述符表缓存 :创建用于分配
files_struct
的 slab 缓存 - 数据结构 :
files_struct
管理进程打开的文件描述符表 - 重要性:每个进程都有独立的文件描述符表,频繁创建和销毁
c
fs_cachep = kmem_cache_create("fs_cache",
sizeof(struct fs_struct), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);
- 文件系统状态缓存 :创建用于分配
fs_struct
的 slab 缓存 - 数据结构 :
fs_struct
包含进程的文件系统相关信息(根目录、当前工作目录等)
第三部分:内存管理相关缓存初始化
c
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
c
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
c
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 子系统的重要组成部分,用于管理页缓存和块设备之间的数据传递
代码详细解释
第一部分:缓冲区头缓存创建
c
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
- 不需要特殊的析构处理
第二部分:缓冲区头数量限制计算
c
/*
* 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热插拔通知器注册
c
hotcpu_notifier(buffer_cpu_notify, 0);
hotcpu_notifier
- 注册一个CPU热插拔通知回调函数buffer_cpu_notify
- 当CPU状态变化时被调用的函数- 优先级 :
0
- 标准优先级
创建新的 buffer_head 对象时进行初始化init_buffer_head
c
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 对象时进行初始化
代码详细解释
第一部分:函数签名和参数
c
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
:构造标志位,控制初始化行为
第二部分:条件检查
c
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 分配器可能在多种场景下调用这个函数
- 只有在真正创建新对象时才需要初始化
- 验证模式只需要检查对象状态,不需要重新初始化
第三部分:类型转换和内存清零
c
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 的实际大小- 优点:一次性清除所有字段,包括未来可能新增的字段
第四部分:链表初始化
c
INIT_LIST_HEAD(&bh->b_assoc_buffers);
}
INIT_LIST_HEAD
:Linux 内核的链表初始化宏b_assoc_buffers
:buffer_head 的关联缓冲区链表- 链表作用:用于将相关的 buffer_head 连接在一起
CPU 热插拔时缓冲区头的清理机制buffer_cpu_notify
c
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 通知器回调函数
c
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 缓冲区清理函数
c
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
:将槽位置空,防止悬空指针
第三部分:缓冲区释放函数链
c
static inline void brelse(struct buffer_head *bh)
{
if (bh)
__brelse(bh);
}
- static inline:内联函数,减少函数调用开销
- 空指针检查 :
if (bh)
- 确保不会对空指针进行操作
- 实际调用 :
__brelse(bh)
- 调用实际的释放函数
第四部分:缓冲区释放核心逻辑
c
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)
:触发内核警告,可能生成堆栈跟踪
第五部分:原子引用计数减少
c
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