Linux中进程创建和缓存对象初始化fork_init、proc_caches_init和buffer_init

初始化进程创建 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:限制每个用户能够创建的进程数量

函数功能总结

  1. 任务结构缓存初始化 :为 task_struct 创建专用的内存缓存池,提高进程创建时的内存分配效率
  2. 线程数量限制计算:根据系统内存大小动态计算最大线程数,确保系统稳定性
  3. 资源限制设置:设置每个用户能够创建的进程数上限,防止资源耗尽

初始化进程管理相关的各种内核缓存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 是内核直接映射的物理内存区域

计算过程:

  1. nr_free_buffer_pages() - 获取可用的缓冲区页数

    • 这个函数返回 ZONE_NORMAL 中可用于缓冲区缓存的内存页数
  2. (nr_free_buffer_pages() * 10) / 100 - 计算 10% 的可用页数

    • 这是系统为缓冲区头分配预留的内存页数上限
  3. PAGE_SIZE / sizeof(struct buffer_head) - 计算每页可以存放的缓冲区头数量

  4. 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
相关推荐
qq_183802873 小时前
Linux内核idr数据结构使用
linux·运维·服务器
噜啦噜啦嘞好4 小时前
Linux:库制作与原理
linux·运维·服务器
---学无止境---4 小时前
Linux中将EFI从物理模式切换到虚拟模式efi_enter_virtual_mode函数的实现
linux
刘某的Cloud5 小时前
磁盘-IO
linux·运维·系统·磁盘io
我狸才不是赔钱货5 小时前
容器:软件世界的标准集装箱
linux·运维·c++·docker·容器
云知谷5 小时前
【嵌入式基本功】单片机嵌入式学习路线
linux·c语言·c++·单片机·嵌入式硬件
挺6的还6 小时前
Boost搜索引擎
linux
天赐学c语言7 小时前
Linux进程信号(上)
linux·可重入函数·进程信号
ajassi20007 小时前
开源 Linux 服务器与中间件(四)服务器--Tomcat
linux·服务器·开源