当前位置: 首页 > news >正文

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:等待时间差,初始为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_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
http://www.dtcms.com/a/430930.html

相关文章:

  • ICT 数字测试原理 3 --SAFETYGUARD 文件
  • 网站改版用新空间好吗北京市保障房建设投资中心网站首页
  • 中职计算机网站建设教学计划医院网站建设策划书
  • [NOIP 2015 提高组] 神奇的幻方 Java
  • 基于单片机的黑板粉尘检测清除装置(论文+源码)
  • GameObject 常见类型详解 -- 陷阱(TRAP)
  • 日语学习-日语知识点小记-进阶-JLPT-N1阶段应用练习(2):语法 +考え方15+2022年7月N1
  • Windows 系统监控工具:项目架构与实现详解
  • 丹阳企业网站建设如何安装wordpress的备份
  • RAG核心特性:ETL
  • 手机网站显示建设中怎么看公司网站是哪里做的
  • GameObject 常见类型详解 -- 傻瓜(GOOBER)
  • 【Ubuntu 20.04升级python3.9后终端打不开的bug】
  • ttkbootstrap Tableview 右键编辑中文支持解决方案
  • 【数据结构与算法学习笔记】双指针
  • 模仿建设银行网站asp网站开发工具神器
  • C#基础06-函数异常
  • PostgreSQL LIMIT 语句详解
  • 网站开发是什么部门wordpress 缩略图清理
  • Kubernetes网络策略实战:精准控制frontend与backend跨-tail通信
  • 关于制作网站收费标准网站的结构类型
  • 【word解析】从OLE到OMML:公式格式转换的挑战与解决方案
  • 云梦网站开发如何做好企业网站
  • 常德网站制作公司多少钱服务器出租
  • Python 2025:低代码开发与自动化编程新纪元
  • wordpress手机端网站模板建站程序下载
  • SQL 多表查询常用语法速查:INNER JOIN / LEFT JOIN / RIGHT JOIN
  • p2p网贷网站开发页面设计简单吗
  • Java SE “异常处理 + IO + 序列化”面试清单(含超通俗生活案例与深度理解)
  • Redis 数据库管理与通信基础