Linux任务切换统计和局部描述符表设置以及加载函数的实现
文章目录
- 一、清除任务的重新调度标志`clear_tsk_need_resched`
- 1.函数功能概述
- 2.`clear_ti_thread_flag()`
- 3. `clear_tsk_thread_flag()`
- 4. `clear_tsk_need_resched()`
- 5.核心概念:`TIF_NEED_RESCHED`
- 二、CPU静止状态计数器`rcu_qsctr_inc`
- 1.典型静止状态
- 2.获取每CPU数据
- 3.递增计数器
- 三、任务离开CPU时统计`sched_info_depart`
- 1. 函数原型和变量声明
- 2. 时间差计算
- 3. 更新任务的CPU时间
- 4. 更新运行队列的CPU时间
- 四、任务准备在CPU运行时统计`sched_info_arrive`
- 1. `sched_info_dequeued` - 重置队列时间
- 2. `sched_info_arrive` - 到达统计主函数
- 3. 等待时间计算
- 4. 重置队列状态和更新统计
- 5. 运行队列统计更新
- 五、任务切换统计`sched_info_switch`
- 1. 函数原型和初始设置
- 2. 离开任务统计处理
- 2.1.空闲任务过滤
- 2.2.执行离开统计
- 3. 到达任务统计处理
- 3.1.空闲任务过滤
- 3.2.执行到达统计
- 六、懒惰TLB刷新模式`enter_lazy_tlb`
- 1. 函数原型和参数
- 3. 获取当前CPU编号
- 4. TLB状态检查
- 5. 切换到懒惰模式
- 6. 懒惰TLB模式的工作原理
- 七、局部描述符表的设置和加载`load_LDT_nolock`
- 1. `set_ldt_desc` 函数
- 2. `_set_tssldt_desc` 宏
- 3. `load_LDT_desc` 宏
- 4. `load_LDT_nolock` 函数
- 5.清除 LDT函数`clear_LDT`
一、清除任务的重新调度标志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_SMPunsigned 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描述符指向默认的LDTset_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