Linux中初始化空循环次数和pid位图初始化

校准处理器速度calibrate_delay

c 复制代码
void __devinit calibrate_delay(void)
{
        unsigned long ticks, loopbit;
        int lps_precision = LPS_PREC;

        if (preset_lpj) {
                loops_per_jiffy = preset_lpj;
                printk("Calibrating delay loop (skipped)... "
                        "%lu.%02lu BogoMIPS preset\n",
                        loops_per_jiffy/(500000/HZ),
                        (loops_per_jiffy/(5000/HZ)) % 100);
        } else {
                loops_per_jiffy = (1<<12);

                printk(KERN_DEBUG "Calibrating delay loop... ");
                while ((loops_per_jiffy <<= 1) != 0) {
                        /* wait for "start of" clock tick */
                        ticks = jiffies;
                        while (ticks == jiffies)
                                /* nothing */;
                        /* Go .. */
                        ticks = jiffies;
                        __delay(loops_per_jiffy);
                        ticks = jiffies - ticks;
                        if (ticks)
                                break;
                }

                /*
                 * Do a binary approximation to get loops_per_jiffy set to
                 * equal one clock (up to lps_precision bits)
                 */
                loops_per_jiffy >>= 1;
                loopbit = loops_per_jiffy;
                while (lps_precision-- && (loopbit >>= 1)) {
                        loops_per_jiffy |= loopbit;
                        ticks = jiffies;
                        while (ticks == jiffies)
                                /* nothing */;
                        ticks = jiffies;
                        __delay(loops_per_jiffy);
                        if (jiffies != ticks)   /* longer than 1 tick */
                                loops_per_jiffy &= ~loopbit;
                }

                /* Round the value and print it */
                printk("%lu.%02lu BogoMIPS (lpj=%lu)\n",
                        loops_per_jiffy/(500000/HZ),
                        (loops_per_jiffy/(5000/HZ)) % 100,
                        loops_per_jiffy);
        }

}

函数功能概述

这个函数用于校准处理器速度,计算 loops_per_jiffy(每个时钟节拍可以执行的循环次数),从而确定系统的 BogoMIPS 值。BogoMIPS 是一个粗略的处理器速度指标,BogoMIPS = Bogus (虚假的/伪造的) + MIPS (每秒百万条指令),表示处理器在一秒内可以执行空循环的次数(以百万为单位)

代码详细解释

第一部分:变量声明和预设值检查

c 复制代码
void __devinit calibrate_delay(void)
{
    unsigned long ticks, loopbit;
    int lps_precision = LPS_PREC;

    if (preset_lpj) {
        loops_per_jiffy = preset_lpj;
        printk("Calibrating delay loop (skipped)... "
                "%lu.%02lu BogoMIPS preset\n",
                loops_per_jiffy/(500000/HZ),
                (loops_per_jiffy/(5000/HZ)) % 100);
    }
  • __devinit 表示这个函数在设备初始化期间使用,初始化完成后可能被释放
  • 声明变量:ticks 用于时间测量,loopbit 用于二分查找,lps_precision 设置校准精度
  • 如果 preset_lpj(预设的 loops_per_jiffy)不为0,直接使用预设值
  • 计算并打印 BogoMIPS 值:loops_per_jiffy/(500000/HZ) 计算整数部分,(loops_per_jiffy/(5000/HZ)) % 100 计算小数部分

第二部分:粗略校准 - 寻找数量级

c 复制代码
    } else {
        loops_per_jiffy = (1<<12);

        printk(KERN_DEBUG "Calibrating delay loop... ");
        while ((loops_per_jiffy <<= 1) != 0) {
            /* wait for "start of" clock tick */
            ticks = jiffies;
            while (ticks == jiffies)
                /* nothing */;
            /* Go .. */
            ticks = jiffies;
            __delay(loops_per_jiffy);
            ticks = jiffies - ticks;
            if (ticks)
                break;
        }
  • 如果没有预设值,从 4096(1<<12)开始
  • 进入循环,每次将 loops_per_jiffy 左移一位(加倍)
  • while (ticks == jiffies) 等待下一个时钟节拍开始
  • 执行 __delay(loops_per_jiffy) 运行指定次数的空循环
  • 计算实际消耗的时间节拍数
  • 如果消耗了时间(ticks != 0),说明循环次数足够多,跳出循环

第三部分:精确校准 - 二分查找

c 复制代码
        loops_per_jiffy >>= 1;
        loopbit = loops_per_jiffy;
        while (lps_precision-- && (loopbit >>= 1)) {
            loops_per_jiffy |= loopbit;
            ticks = jiffies;
            while (ticks == jiffies)
                /* nothing */;
            ticks = jiffies;
            __delay(loops_per_jiffy);
            if (jiffies != ticks)   /* longer than 1 tick */
                loops_per_jiffy &= ~loopbit;
        }
  • loops_per_jiffy >>= 1 回退到上一次成功的值
  • loopbit = loops_per_jiffy 初始化二分查找的步长
  • 循环 lps_precision 次进行精确校准
  • loops_per_jiffy |= loopbit 尝试增加当前精度位
  • 同样等待时钟节拍开始,然后执行延迟
  • 如果执行时间超过1个节拍(jiffies != ticks),说明循环次数过多,清除该精度位

第四部分:结果输出

c 复制代码
        /* Round the value and print it */
        printk("%lu.%02lu BogoMIPS (lpj=%lu)\n",
                loops_per_jiffy/(500000/HZ),
                (loops_per_jiffy/(5000/HZ)) % 100,
                loops_per_jiffy);
    }
}
  • 打印最终的校准结果,格式为:"整数.小数 BogoMIPS (lpj=实际循环次数)"

关键概念说明

  • jiffy: 系统时钟节拍,通常是 1ms、4ms 或 10ms,取决于 HZ 值
  • loops_per_jiffy: 在一个 jiffy 内可以执行的空循环次数
  • BogoMIPS: "Bogus MIPS" - 一个近似的处理器速度指标
  • 二分查找: 用于精确确定最大的不超时的循环次数

这个函数通过动态测试确定了处理器执行空循环的速度,为系统中的时间延迟函数提供了准确的基准

初始化PID位图pidmap_init

c 复制代码
void __init pidmap_init(void)
{
        int i;

        pidmap_array->page = (void *)get_zeroed_page(GFP_KERNEL);
        set_bit(0, pidmap_array->page);
        atomic_dec(&pidmap_array->nr_free);

        /*
         * Allocate PID 0, and hash it via all PID types:
         */

        for (i = 0; i < PIDTYPE_MAX; i++)
                attach_pid(current, i, 0);
}

函数功能概述

这个函数用于初始化进程ID(PID)位图,并设置PID 0为已使用状态。PID 0通常分配给内核的空闲进程(swapper进程)

代码详细解释

第一部分:变量声明和基础初始化

c 复制代码
void __init pidmap_init(void)
{
        int i;

        pidmap_array->page = (void *)get_zeroed_page(GFP_KERNEL);
  • __init 表示这个函数在系统初始化期间调用,初始化完成后内存会被释放
  • 声明循环变量 i,用于遍历所有PID类型
  • get_zeroed_page(GFP_KERNEL) 申请一个物理内存页并将其内容清零
    • GFP_KERNEL 是内存分配标志,表示内核正常优先级分配
    • 返回的是页的虚拟地址
  • 将获取的页赋值给 pidmap_array->page,这个位图用于跟踪PID的分配状态

第二部分:保留PID 0

c 复制代码
        set_bit(0, pidmap_array->page);
        atomic_dec(&pidmap_array->nr_free);
  • set_bit(0, pidmap_array->page) 将位图的第0位设置为1
    • 在位图中,1表示已使用,0表示空闲
    • PID 0被保留,不能被普通进程使用
  • atomic_dec(&pidmap_array->nr_free) 原子操作减少空闲PID计数器
    • atomic_dec 保证在多处理器环境下的原子性
    • nr_free 记录当前可用的PID数量
    • 由于PID 0被占用,空闲PID数量减1

第三部分:为所有PID类型注册PID 0

c 复制代码
        /*
         * Allocate PID 0, and hash it via all PID types:
         */

        for (i = 0; i < PIDTYPE_MAX; i++)
                attach_pid(current, i, 0);
}
  • 分配PID 0,并通过所有PID类型进行哈希处理
  • for (i = 0; i < PIDTYPE_MAX; i++) 遍历所有PID类型
    • PIDTYPE_MAX 是PID类型的最大值,通常包括:
      • PIDTYPE_PID:进程ID
      • PIDTYPE_TGID:线程组ID
      • PIDTYPE_PGID:进程组ID
      • PIDTYPE_SID:会话ID
  • attach_pid(current, i, 0) 为当前进程附加PID 0到每种PID类型的哈希表中
    • current 指向当前正在执行的进程(内核的swapper进程)
    • i 是PID类型
    • 0 是PID值

关键数据结构

PID位图结构

c 复制代码
struct pidmap {
    atomic_t nr_free;        // 空闲PID数量
    void *page;              // 位图页面
};

PID类型枚举

c 复制代码
enum pid_type {
    PIDTYPE_PID,      // 进程ID
    PIDTYPE_TGID,     // 线程组ID
    PIDTYPE_PGID,     // 进程组ID
    PIDTYPE_SID,      // 会话ID
    PIDTYPE_MAX       // PID类型数量
};

内存布局示意图

复制代码
pidmap_array->page 位图内存页:
+---+---+---+---+---+---+
| 1 | 0 | 0 | 0 | 0 | ... |  (4096字节,32768个PID)
+---+---+---+---+---+---+
  ^   ^   ^   ^   ^
  |   |   |   |   |
PID0 PID1 PID2 PID3 PID4...
  • 第0位:设置为1(PID 0已使用)
  • 其他位:初始为0(空闲状态)

将进程关联到指定的PID attach_pid

c 复制代码
int fastcall attach_pid(task_t *task, enum pid_type type, int nr)
{
        struct pid *pid, *task_pid;

        task_pid = &task->pids[type];
        pid = find_pid(type, nr);
        if (pid == NULL) {
                hlist_add_head(&task_pid->pid_chain,
                                &pid_hash[type][pid_hashfn(nr)]);
                INIT_LIST_HEAD(&task_pid->pid_list);
        } else {
                INIT_HLIST_NODE(&task_pid->pid_chain);
                list_add_tail(&task_pid->pid_list, &pid->pid_list);
        }
        task_pid->nr = nr;

        return 0;
}

函数功能概述

这个函数负责将进程任务关联到指定的PID编号和类型,管理Linux内核中复杂的PID组织结构。它处理PID哈希表和进程链表的维护,支持多线程共享同一PID的场景

代码逐行详细解释

第一部分:函数声明和变量定义

c 复制代码
int fastcall attach_pid(task_t *task, enum pid_type type, int nr)
{
        struct pid *pid, *task_pid;
  • fastcall:这是一个函数调用约定修饰符,指示编译器使用寄存器来传递参数,而不是通过堆栈,这样可以提高函数调用速度。在x86架构中,前两个参数通常通过ECX和EDX寄存器传递

  • 参数分析

    • task_t *task:指向进程任务描述符的指针,即需要关联PID的进程
    • enum pid_type type:PID类型枚举,包括:
      • PIDTYPE_PID - 进程ID
      • PIDTYPE_TGID - 线程组ID
      • PIDTYPE_PGID - 进程组ID
      • PIDTYPE_SID - 会话ID
    • int nr:具体的PID数值
  • 局部变量

    • struct pid *pid:用于指向在全局哈希表中查找到的现有PID结构
    • struct pid *task_pid:指向当前任务内部的PID结构

第二部分:获取任务PID结构和查找全局PID

c 复制代码
        task_pid = &task->pids[type];
        pid = find_pid(type, nr);
  • task_pid = &task->pids[type]

    • 从任务结构体中获取对应类型的PID结构指针
    • task->pids 是一个数组,每个元素对应一种PID类型
    • 例如:task->pids[PIDTYPE_PID] 是进程ID结构
    • 这个操作获取了任务内部存储PID信息的位置
  • pid = find_pid(type, nr)

    • 调用 find_pid 函数在全局PID哈希表中查找指定的PID
    • 查找过程:pid_hash[type][pid_hashfn(nr)] → 遍历哈希链表
    • 如果找到匹配的PID,返回该PID结构指针
    • 如果没找到,返回NULL

第三部分:处理新PID的情况(首次分配)

c 复制代码
        if (pid == NULL) {
                hlist_add_head(&task_pid->pid_chain,
                                &pid_hash[type][pid_hashfn(nr)]);
                INIT_LIST_HEAD(&task_pid->pid_list);
  • if (pid == NULL):条件判断,如果该PID在全局哈希表中不存在

  • hlist_add_head(&task_pid->pid_chain, &pid_hash[type][pid_hashfn(nr)])

    • pid_hashfn(nr):计算PID的哈希值,将PID数值映射到哈希桶索引
    • pid_hash[type][...]:二维哈希表,第一维是PID类型,第二维是哈希桶
    • hlist_add_head:将任务的pid_chain节点添加到哈希链表的头部
    • 这使该任务成为该PID在哈希表中的第一个代表
  • INIT_LIST_HEAD(&task_pid->pid_list)

    • 初始化一个空的双向链表头
    • 这个链表用于存储所有共享同一PID的任务
    • 对于新PID,当前只有这一个任务,所以初始化空链表

第四部分:处理现有PID的情况(共享PID)

c 复制代码
        } else {
                INIT_HLIST_NODE(&task_pid->pid_chain);
                list_add_tail(&task_pid->pid_list, &pid->pid_list);
        }
  • else:如果该PID已经在全局哈希表中存在

  • INIT_HLIST_NODE(&task_pid->pid_chain)

    • 初始化哈希链表节点,但不将其加入任何哈希表
    • 因为该PID已经在全局哈希表中存在,不需要重复添加
    • 只是确保节点的状态正确,防止未定义行为
  • list_add_tail(&task_pid->pid_list, &pid->pid_list)

    • 将当前任务的pid_list节点添加到现有PID的进程链表尾部
    • 这实现了多个任务共享同一PID的功能
    • 例如:线程组中的所有线程共享同一个TGID

第五部分:设置PID数值并返回

c 复制代码
        task_pid->nr = nr;

        return 0;
}
  • task_pid->nr = nr

    • 设置PID结构中的数值字段
    • 无论新旧PID,都需要记录具体的PID数值
    • 这个数值用于后续的PID查找和比较
  • return 0

    • 函数总是返回成功(0)
    • 因为所有错误情况都应该在调用前被处理

数据结构关系详解

PID结构定义

c 复制代码
struct pid {
    int nr;                      // PID数值
    struct hlist_node pid_chain; // 哈希表链表节点
    struct list_head pid_list;   // 共享同一PID的任务链表
};

场景1:新PID创建

复制代码
执行前:
全局哈希表: 空
任务PID结构: 未初始化

执行过程:
1. 将任务PID结构加入哈希表: pid_hash[type][hash] → task_pid
2. 初始化空进程链表: task_pid→pid_list → [空]
3. 设置PID数值: task_pid→nr = nr

执行后:
全局哈希表: pid_hash[type][hash] → task_pid (通过pid_chain连接)
任务链表: task_pid→pid_list 指向空链表头

场景2:共享现有PID

复制代码
执行前:
全局哈希表: pid_hash[type][hash] → existing_pid
现有PID链表: existing_pid→pid_list → task1 → task2

执行过程:
1. 初始化task_pid的pid_chain(不加入哈希表)
2. 将task_pid加入现有PID的进程链表: existing_pid→pid_list → task1 → task2 → task_pid
3. 设置PID数值: task_pid→nr = nr

执行后:
全局哈希表: 不变,仍然指向existing_pid
现有PID链表: 新增task_pid在尾部
相关推荐
A小辣椒16 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒20 小时前
TShark:基础知识
linux
AlfredZhao1 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩3 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言