mthoutai

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

一、清除任务的重新调度标志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:等待时间差,初始为0
  • rq:任务所在的运行队列

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 函数:

  • 参数1cpu:目标CPU编号
  • 参数2&default_ldt[0]:指向默认LDT的指针
  • 参数35: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
posted on 2025-10-27 17:17  mthoutai  阅读(1)  评论(0)    收藏  举报