线程切换之内核E_thead浅析

Wirte by 021.

E_Thread

K_THREAD

  • 线程结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结构体
{
线程名称
线程状态

堆栈起始地址
线程堆栈界限 (终止地址)
线程当前位置 ESP

线程函数参数
线程函数地址
}


线程结构体数组

0 --- 当前线程 ,main

1 --- 创建的新线程


  • 线程状态不同存储

    1
    2
    3
    4
    5
    6
    正在运行线程 --- kpcr

    等待线程 --- 等待链表

    就绪/调度线程 --- 32个链表

线程的生命周期

  • 创建 — 初始化线程变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1,结构体初始化赋值


    2,执行体,函数,参数,赋值



    7, kernelStack 栈顶 赋值,esp 赋值。
    kernelStack = 入栈对象 (1-6)


  • 创建堆栈

    1
    2
    3
    4
    5
    创建堆栈,申请堆栈 stackpage , 0x80000 大小,

    4,堆栈初始化,起始地址、

    5,设定堆栈边界值(终止地址)
  • 寄存器入栈

    1
    6, 入栈  

线程切换 SwapContext

  • 线程不是被动切换,而是主动切换。 意思是,无论是时间片到了的被动切换,还是线程IO阻塞主动出让执行权,都需要当前线程调用切换线程函数.
  • 线程没有使用TSS保存寄存器,而是使用堆栈.
  • 线程切换过程就是堆栈切换过程.
  • 在线程切换过程中,会判断2个线程是否属于同一个进程,如果不是,就切换cr3页面,也就是进程空间也切换了.

image-20211110115259985

  • TheadSwap

    1
    2
    3
    4
    5
    6
    7
    参数 : 1,当前线程(esp)地址, 2,将要被切换线程(edi)地址

    步骤:
    1,保存当前线的栈顶,上图的push操作.
    2,将当前线程(esp)地址,存储到KernelStack中 --- mov [esi kernelStack] ,esp
    3,将内核中 将要被切换edi线程 的地址,交换到当前位置 --- mov esp, [edi kernelStack]

  • 主动切换

    image-20211110123138625

    1
    取出当前CPU kpcr正在执行的线程放到edi中

线程中断

  • 时钟中断

    image-20211110132601818

    • 线程中断条件

      1
      2
      3
      1.时间片到期
      2,备用线程(KPCR.PrcbData.nextThread)
      3,主动调用切换切换线程函数,KiswapThread
    • CPU时间片

      1
      2
      3
      4
      5
      1,KPROCESS.ThreadQuantum 值是当前进程设置的时间片值
      2,_KTHREAD.ThreadQuantum 会读取 KPROCESS.ThreadQuantum的值,在线程初始化的时候

      在win32里面初始为6,每次中断会调用KeupdateRuntime函数会将当前线程_KTHREAD.ThreadQuantum - 3,如果减到0,则将
      KPCR.PrcbData.QuantumEnd 位 置为 非0,表示当前线程时间片到期.
  • Tss

    • 内核堆栈

      1
      2
      3
      4
      5
      initStack 栈底
      kernelStack 栈顶
      stackLimte 边界

      TSS.esp0得到当前线程0环堆栈,线程在切换的时候保证0环线程堆栈的唯一和有序性.
  • 异常中断

    1
    INT N, 页面异常
  • 如何永久占用CPU?

    1
    满足 不调用API , 并且不会出现异常,并调用cli指令,屏蔽时间中断,CPU就会忽略时钟片中断.

线程优先级

  • 线程调度链表

    1
    2
    3
    4
    5
    32个线程调度双向链表。
    KiFindReadyThread 查找方式: 先找 31 ... 30 .. 29, 优先查找高级别的。
    32位:
    0 1 0 1 0 0 0 0 ... 0 , 31位上为1,证明当前有优先级的线程需要处理。
    当调度链表中没有任何需要调度执行的任务时,cpu会执行idelThread.