文章目录
一、清除任务的重新调度标志clear_tsk_need_resched
static inline void clear_ti_thread_flag(struct thread_info *ti, int flag)
{
clear_bit(flag,&ti->flags);
}
static inline void clear_tsk_thread_flag(struct task_struct *tsk, int flag)
{
clear_ti_thread_flag(tsk->thread_info,flag);
}
static inline void clear_tsk_need_resched(struct task_struct *tsk)
{
clear_tsk_thread_flag(tsk,TIF_NEED_RESCHED);
}
这段代码定义了三个内联函数,用于清除线程信息(thread_info)和任务结构(task_struct)中的标志位,最终用于清除任务的 重新调度标志(TIF_NEED_RESCHED)
1.函数功能概述
clear_ti_thread_flag():直接清除thread_info中的某个标志位clear_tsk_thread_flag():通过task_struct找到关联的thread_info,再调用clear_ti_thread_flag()清除标志位clear_tsk_need_resched():专门用于清除任务的TIF_NEED_RESCHED标志(表示任务不需要重新调度)
这些函数通常用于 调度器 和 中断上下文 中,控制任务的调度行为
2.clear_ti_thread_flag()
static inline void clear_ti_thread_flag(struct thread_info *ti, int flag)
{
clear_bit(flag, &ti->flags);
}
- 作用:清除
thread_info结构体中的某个标志位。 - 参数
ti:指向thread_info的指针(存储线程的底层信息)flag:要清除的标志位(如TIF_NEED_RESCHED)
3. clear_tsk_thread_flag()
static inline void clear_tsk_thread_flag(struct task_struct *tsk, int flag)
{
clear_ti_thread_flag(tsk->thread_info, flag);
}
- 作用:通过
task_struct间接清除thread_info中的标志位 - 参数
tsk:指向task_struct的指针(表示一个任务/进程)flag:要清除的标志位
4. clear_tsk_need_resched()
static inline void clear_tsk_need_resched(struct task_struct *tsk)
{
clear_tsk_thread_flag(tsk, TIF_NEED_RESCHED);
}
- 作用:专门清除任务的
TIF_NEED_RESCHED标志 - 参数
tsk:指向task_struct的指针。
- 关键操作
TIF_NEED_RESCHED:一个宏定义的标志位,表示任务是否需要被重新调度(例如,更高优先级的任务已就绪)- 调用
clear_tsk_thread_flag()清除该标志
5.核心概念:TIF_NEED_RESCHED
- 作用
- 当调度器决定某个任务需要被抢占时(例如,时间片用完或更高优先级任务就绪),会设置
TIF_NEED_RESCHED标志 - 在返回用户态或中断退出时,内核会检查此标志,若置位则触发调度(调用
schedule())
- 当调度器决定某个任务需要被抢占时(例如,时间片用完或更高优先级任务就绪),会设置
- 清除时机
- 当任务明确不需要重新调度时,需清除此标志,避免重复调度
二、CPU静止状态计数器rcu_qsctr_inc
static inline void rcu_qsctr_inc(int cpu)
{
struct rcu_data *rdp = &per_cpu(rcu_data, cpu);
rdp->qsctr++;
}
1.典型静止状态
// 以下情况被认为是静止状态:
1. CPU处于空闲状态
2. CPU在上下文切换过程中
3. CPU在处理中断退出时
2.获取每CPU数据
struct rcu_data *rdp = &per_cpu(rcu_data, cpu);
per_cpu(rcu_data, cpu):
- 获取指定CPU的RCU数据结构的地址
- 每个CPU有自己独立的RCU数据结
3.递增计数器
rdp->qsctr++;
- 将静止状态计数器加1
三、任务离开CPU时统计sched_info_depart
static inline void sched_info_depart(task_t *t)
{
struct runqueue *rq = task_rq(t);
unsigned long diff = jiffies - t->sched_info.last_arrival;
t->sched_info.cpu_time += diff;
if (rq)
rq->rq_sched_info.cpu_time += diff;
}
这个函数在任务离开CPU时记录调度统计信息,用于性能分析和调度器调优
1. 函数原型和变量声明
static inline void sched_info_depart(task_t *t)
{
struct runqueue *rq = task_rq(t);
unsigned long diff = jiffies - t->sched_info.last_arrival;
参数:
t:正在离开CPU的任务指针
变量:
rq:任务所在的运行队列diff:任务在本次调度周期内在CPU上运行的时间
2. 时间差计算
unsigned long diff = jiffies - t->sched_info.last_arrival;
jiffies:
- 内核全局变量,记录系统启动以来的时钟滴答数
- 每个滴答的时间间隔由
HZ定义
last_arrival:
// 在任务被调度到CPU上时设置:
void sched_info_arrive(task_t *t)
{
...
t->sched_info.last_arrival = now;
...
}
3. 更新任务的CPU时间
t->sched_info.cpu_time += diff;
累计CPU时间:
cpu_time记录任务在整个生命周期中在CPU上运行的总时间- 每次调度周期结束时将本次运行时间累加进去
4. 更新运行队列的CPU时间
if (rq)
rq->rq_sched_info.cpu_time += diff;
- 统计整个CPU的运行负载情况
- 用于负载均衡和性能监控
- 帮助识别繁忙的CPU
四、任务准备在CPU运行时统计sched_info_arrive
static inline void sched_info_dequeued(task_t *t)
{
t->sched_info.last_queued = 0;
}
static inline void sched_info_arrive(task_t *t)
{
unsigned long now = jiffies, diff = 0;
struct runqueue *rq = task_rq(t);
if (t->sched_info.last_queued)
diff = now - t->sched_info.last_queued;
sched_info_dequeued(t);
t->sched_info.run_delay += diff;
t->sched_info.last_arrival = now;
t->sched_info.pcnt++;
if (!rq)
return;
rq->rq_sched_info.run_delay += diff;
rq->rq_sched_info.pcnt++;
}
1. sched_info_dequeued - 重置队列时间
static inline void sched_info_dequeued(task_t *t)
{
t->sched_info.last_queued = 0;
}
功能:
- 将任务的
last_queued字段重置为0 - 表示任务已离开运行队列
last_queued 字段含义:
- 记录任务最后一次进入运行队列的时间戳
- 用于计算任务在队列中等待的时间
2. sched_info_arrive - 到达统计主函数
static inline void sched_info_arrive(task_t *t)
{
unsigned long now = jiffies, diff = 0;
struct runqueue *rq = task_rq(t);
变量初始化:
now:当前时间戳(jiffies)diff:等待时间差,初始为0rq:任务所在的运行队列
3. 等待时间计算
if (t->sched_info.last_queued)
diff = now - t->sched_info.last_queued;
- 只有当
last_queued不为0时才计算等待时间 last_queued为0表示任务没有经过排队等待
4. 重置队列状态和更新统计
sched_info_dequeued(t);
t->sched_info.run_delay += diff;
t->sched_info.last_arrival = now;
t->sched_info.pcnt++;
重置队列状态:
sched_info_dequeued(t); // last_queued = 0
累计运行延迟:
t->sched_info.run_delay += diff;
run_delay:任务在运行队列中等待的总时间- 反映任务的调度延迟
记录到达时间:
t->sched_info.last_arrival = now;
- 为后续的
sched_info_depart计算cpu_time提供基准
增加调度计数:
t->sched_info.pcnt++;
pcnt:任务被调度到CPU运行的次数- 反映任务的调度频率
5. 运行队列统计更新
if (!rq)
return;
rq->rq_sched_info.run_delay += diff;
rq->rq_sched_info.pcnt++;
累计队列延迟:
rq->rq_sched_info.run_delay += diff;
- 整个运行队列中所有任务的等待延迟总和
- 反映CPU的调度压力
增加调度次数:
rq->rq_sched_info.pcnt++;
- 该运行队列的总调度次数
- 反映CPU的调度活跃度
五、任务切换统计sched_info_switch
static inline void sched_info_switch(task_t *prev, task_t *next)
{
struct runqueue *rq = task_rq(prev);
if (prev != rq->idle)
sched_info_depart(prev);
if (next != rq->idle)
sched_info_arrive(next);
}
1. 函数原型和初始设置
static inline void sched_info_switch(task_t *prev, task_t *next)
{
struct runqueue *rq = task_rq(prev);
参数:
prev:即将离开CPU的任务(当前正在运行的任务)next:即将被调度到CPU的任务(将要运行的任务)
获取运行队列:
struct runqueue *rq = task_rq(prev);
- 获取前一个任务所在的运行队列
2. 离开任务统计处理
/*
* prev now departs the cpu. It's not interesting to record
* stats about how efficient we were at scheduling the idle
* process, however.
*/
if (prev != rq->idle)
sched_info_depart(prev);
2.1.空闲任务过滤
if (prev != rq->idle)
- 只有当离开的任务不是空闲任务时才记录统计
- 空闲任务是当没有其他任务可运行时执行的特殊任务
2.2.执行离开统计
sched_info_depart(prev);
- 记录前一个任务在CPU上运行的时间
- 更新任务的
cpu_time累计值 - 更新运行队列的CPU时间统计
3. 到达任务统计处理
if (next != rq->idle)
sched_info_arrive(next);
3.1.空闲任务过滤
if (next != rq->idle)
- 只有当新任务不是空闲任务时才记录到达统计
- 调度空闲任务不视为有意义的调度事件
3.2.执行到达统计
sched_info_arrive(next);
- 记录新任务被调度到CPU的时间
- 计算并累计任务的等待延迟(
run_delay) - 增加任务的调度次数计数(
pcnt)
六、懒惰TLB刷新模式enter_lazy_tlb
static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
{
#ifdef CONFIG_SMP
unsigned cpu = smp_processor_id();
if (per_cpu(cpu_tlbstate, cpu).state == TLBSTATE_OK)
per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_LAZY;
#endif
}
1. 函数原型和参数
static inline void enter_lazy_tlb(struct mm_struct *mm, struct task_struct *tsk)
参数:
mm:内存管理结构,描述地址空间tsk:任务结构
3. 获取当前CPU编号
unsigned cpu = smp_processor_id();
smp_processor_id():
- 返回当前执行代码的CPU编号
- 每个CPU有唯一的标识符
4. TLB状态检查
if (per_cpu(cpu_tlbstate, cpu).state == TLBSTATE_OK)
每CPU变量:
per_cpu(cpu_tlbstate, cpu)
- 每个CPU有自己独立的TLB状态变量
条件逻辑:
- 只有当TLB当前处于
TLBSTATE_OK状态时才进入懒惰模式
5. 切换到懒惰模式
per_cpu(cpu_tlbstate, cpu).state = TLBSTATE_LAZY;
6. 懒惰TLB模式的工作原理
- 标记CPU进入懒惰模式
- 不立即刷新其他CPU的TLB
- 当其他CPU访问失效的TLB条目时再处理
七、局部描述符表的设置和加载load_LDT_nolock
static inline void set_ldt_desc(unsigned int cpu, void *addr, unsigned int size)
{
_set_tssldt_desc(&per_cpu(cpu_gdt_table, cpu)[GDT_ENTRY_LDT], (int)addr, ((size << 3)-1), 0x82);
}
#define _set_tssldt_desc(n,addr,limit,type) \
__asm__ __volatile__ ("movw %w3,0(%2)\n\t" \
"movw %%ax,2(%2)\n\t" \
"rorl $16,%%eax\n\t" \
"movb %%al,4(%2)\n\t" \
"movb %4,5(%2)\n\t" \
"movb $0,6(%2)\n\t" \
"movb %%ah,7(%2)\n\t" \
"rorl $16,%%eax" \
: "=m"(*(n)) : "a" (addr), "r"(n), "ir"(limit), "i"(type))
#define load_LDT_desc() __asm__ __volatile__("lldt %%ax"::"a" (GDT_ENTRY_LDT*8))
static inline void load_LDT_nolock(mm_context_t *pc, int cpu)
{
void *segments = pc->ldt;
int count = pc->size;
if (likely(!count)) {
segments = &default_ldt[0];
count = 5;
}
set_ldt_desc(cpu, segments, count);
load_LDT_desc();
}
1. set_ldt_desc 函数
static inline void set_ldt_desc(unsigned int cpu, void *addr, unsigned int size)
{
_set_tssldt_desc(&per_cpu(cpu_gdt_table, cpu)[GDT_ENTRY_LDT], (int)addr, ((size << 3)-1), 0x82);
}
参数分析:
cpu:目标CPU编号addr:LDT在内存中的起始地址size:LDT中的描述符数量
&per_cpu(cpu_gdt_table, cpu)[GDT_ENTRY_LDT]
per_cpu(cpu_gdt_table, cpu):获取指定CPU的GDT(全局描述符表)[GDT_ENTRY_LDT]:索引到GDT中LDT描述符的位置
(int)addr:将地址转换为整数
((size << 3)-1):
size << 3:将描述符数量乘以8(每个描述符8字节)
0x82:LDT描述符的类型字段
2. _set_tssldt_desc 宏
#define _set_tssldt_desc(n,addr,limit,type) \
__asm__ __volatile__ ("movw %w3,0(%2)\n\t" \
"movw %%ax,2(%2)\n\t" \
"rorl $16,%%eax\n\t" \
"movb %%al,4(%2)\n\t" \
"movb %4,5(%2)\n\t" \
"movb $0,6(%2)\n\t" \
"movb %%ah,7(%2)\n\t" \
"rorl $16,%%eax" \
: "=m"(*(n)) : "a" (addr), "r"(n), "ir"(limit), "i"(type))
movw %w3,0(%2)
%w3:操作数3的低16位(limit)0(%2):操作数2指向的内存地址+0偏移- 作用:将limit的低16位写入描述符的0-1字节
movw %%ax,2(%2)
%%ax:EAX寄存器的低16位(addr的低16位)2(%2):操作数2+2偏移- 作用:将地址的低16位写入描述符的2-3字节
rorl $16,%%eax
- 将EAX寄存器循环右移16位
- 作用:将addr的高16位移动到AX中
movb %%al,4(%2)
%%al:AL寄存器(addr的16-23位)4(%2):操作数2+4偏移- 作用:将地址的16-23位写入描述符的第4字节
movb %4,5(%2)
%4:操作数4(type)5(%2):操作数2+5偏移- 作用:将类型字段写入描述符的第5字节
movb $0,6(%2)
- 将0写入描述符的第6字节
movb %%ah,7(%2)
%%ah:AH寄存器(addr的24-31位)7(%2):操作数2+7偏移- 作用:将地址的最高8位写入描述符的第7字节
rorl $16,%%eax
- 再次右移EAX,恢复原始值
输出操作数:"=m"(*(n))
=m:内存操作数,可写*(n):指向的内存内容
输入操作数:
"a" (addr):addr放入EAX寄存器"r"(n):n放入通用寄存器"ir"(limit):limit可以是立即数或寄存器"i"(type):type必须是立即数
3. load_LDT_desc 宏
#define load_LDT_desc() __asm__ __volatile__("lldt %%ax"::"a" (GDT_ENTRY_LDT*8))
lldt %%ax:
- x86指令:Load LDT Register
%%ax:AX寄存器包含LDT选择子
选择子计算:GDT_ENTRY_LDT*8
- GDT中每个描述符8字节
- 索引需要乘以8得到选择子
- 例如:如果LDT在GDT的第6项,选择子=6×8=48=0x30
4. load_LDT_nolock 函数
static inline void load_LDT_nolock(mm_context_t *pc, int cpu)
{
void *segments = pc->ldt;
int count = pc->size;
if (likely(!count)) {
segments = &default_ldt[0];
count = 5;
}
set_ldt_desc(cpu, segments, count);
load_LDT_desc();
}
参数:
pc:内存管理上下文cpu:目标CPU编号
void *segments = pc->ldt;
- 获取进程的LDT地址
int count = pc->size;
- 获取LDT中的描述符数量
if (likely(!count))
likely():告诉编译器这个条件很可能成立!count:如果LDT为空(count=0)
segments = &default_ldt[0];
- 使用默认LDT
default_ldt:内核提供的默认LDT
count = 5;
- 默认LDT包含5个描述符
set_ldt_desc(cpu, segments, count);
- 在GDT中设置LDT描述符
load_LDT_desc();
- 加载LDT寄存器
5.清除 LDT函数clear_LDT
static inline void clear_LDT(void)
{
int cpu = get_cpu();
set_ldt_desc(cpu, &default_ldt[0], 5);
load_LDT_desc();
put_cpu();
}
这是一个用于清除 LDT(局部描述符表)的内联函数
static inline void clear_LDT(void)
{
// 获取当前CPU编号并禁用内核抢占
int cpu = get_cpu();
get_cpu() 的作用:
- 获取当前运行的 CPU 编号
- 同时禁用内核抢占(确保在操作过程中不会被调度到其他CPU上)
- 这是一个宏:
#define get_cpu() ({ preempt_disable(); smp_processor_id(); })
// 设置LDT描述符指向默认的LDT
set_ldt_desc(cpu, &default_ldt[0], 5);
set_ldt_desc 函数:
- 参数1
cpu:目标CPU编号 - 参数2
&default_ldt[0]:指向默认LDT的指针 - 参数3
5:LDT中的描述符数量(条目数)
默认LDT的含义:
default_ldt通常是一个空的或最小化的LDT- 这个调用实际上是将当前CPU的LDT重置为默认状态
// 加载LDT描述符到CPU的LDTR寄存器
load_LDT_desc();
load_LDT_desc() 的作用:
- 执行
lldt汇编指令,将GDT中的LDT描述符加载到LDTR寄存器 - 这会告诉CPU使用新的LDT
// 重新启用内核抢占
put_cpu();
}
put_cpu() 的作用:
- 与
get_cpu()配对使用 - 重新启用内核抢占
- 允许调度器再次调度当前任务到其他CPU
浙公网安备 33010602011771号