校准处理器速度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
:进程IDPIDTYPE_TGID
:线程组IDPIDTYPE_PGID
:进程组IDPIDTYPE_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
- 进程IDPIDTYPE_TGID
- 线程组IDPIDTYPE_PGID
- 进程组IDPIDTYPE_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在尾部