[笔记]Windows核心编程《十一》纤程

336 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

参考1

参考2

@[TOC]

前言

目的: 为了更容易将已有的UNIX服务器应用迁移到Windows,增加了纤程。 UNIX应用程序的开发人员创建了线程包,来实现类似线程功能。

使用纤程

  • 线程在windows内核中实现的。
  • 纤程是在用户模式下实现的。
  • 内核仅能调度线程,而纤程由我们调度
  • 一个线程可包含多个纤程。

将一个已有的线程转换为一个纤程

一个已有的线程转换为一个纤程

PVOID ConvertThreadToFiber(PVOID pvParam);

如果函数成功,返回值是纤维的地址;如果函数失败,返回值是零。

这个函数为纤程的执行上下文CONTEXT分配内存。 执行上下文一般包含:

  • 一个用户自定义的值(pvParam)
  • 结构化异常处理链的头
  • 纤程栈的顶部和底部的内存地址
  • 某些CPU寄存器,包括栈指针,指令指针以及其他寄存器

默认情况下x86系统中CPU的浮点状态信息不属于CPU寄存器的一部分,不会为每个纤程都维护一份,而如果纤程要执行浮点操作,那会导致数据被破坏。

为了覆盖系统的默认行为需要使用新的ConvertThreadToFiberEx函数 允许传入FIBER_FLAG_FLOAT_SWITCH标志:

WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
ConvertThreadToFiberEx(
    _In_opt_ LPVOID lpParameter,
    _In_     DWORD dwFlags
    );

除非打算在一个线程中执行多个纤程,否则只为执行一个任务,而将线程转换为纤程没有任何意义。

在当前线程中创建另一个纤程

WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
CreateFiber(
    _In_     SIZE_T dwStackSize,
    _In_     LPFIBER_START_ROUTINE lpStartAddress,
    _In_opt_ LPVOID lpParameter
    );

如果要使用大量的纤程,希望纤程消耗更少的内存使用下列函数来创建纤程。

WINBASEAPI
_Ret_maybenull_
LPVOID
WINAPI
CreateFiberEx(
    _In_     SIZE_T dwStackCommitSize,
    _In_     SIZE_T dwStackReserveSize,
    _In_     DWORD dwFlags,
    _In_     LPFIBER_START_ROUTINE lpStartAddress,
    _In_opt_ LPVOID lpParameter
    );

dwStackCommitSize:设置一开始要调拨的物理存储页。 dwStackReservesize: 运行我们预定指定的虚拟内存。 dwFlags:可以接受FIBER_FLAG_FLOAT_SWITCH将浮点运算状态保留。 lpfnStartAddress:用来指定纤程函数的地址。例如以下就是定义一个纤程函数。

VOID WINAPI FiberFunc(PVOID pvParam);

其他参数和CreateFiber相同。 CreateFiber返回的是为纤程创建的CONTEXT的地址(和ConvertThreadToFiber类似) 但是CreateFiber创建的新纤程默认不会执行。(因为一个线程中只能执行一个纤程)

需要调用SwitchToFiber来切换纤程

WINBASEAPI
VOID
WINAPI
SwitchToFiber(
    _In_ LPVOID lpFiber
    );

lpFiber是用CreateFiber或ConvertThreadToFiber返回的值,告诉函数要调用哪个纤程。SwitchToFiber会执行以下步骤: 1)将一些cpu寄存器目前的值,其中包括指令指针(IP)寄存器和栈指针寄存器(SP) 保存在当前运行的纤程的CONTEXT中 2)从即将运行的纤程的执行CONTEXT中载入先前保存的寄存器值并载入cpu 这些寄存器中包含栈指针寄存器,这样单线程继续执行就会使用新纤程的栈。 3)将新纤程的执行CONTEXT和线程关联,让线程运行指定的纤程。 4)将线程的指令指针设为先前保存的指令指针(IP),这样线程(纤程)就会从上次执行的地方开始继续往下执行。

SwitchToFiber是让纤程得到cpu时间切片的唯一方法。所以纤程的调度完全由我们自己掌控。

调用DeleteFiber来销毁纤程

WINBASEAPI
VOID
WINAPI
DeleteFiber(
    _In_ LPVOID lpFiber
    );

lpFiber是纤程的CONTEXT地址。该函数会删除参数所表示的纤程。如果线程和纤程绑定了,其内部会调用ExitThread同时销毁当前的线程。 (TerminateThread不会销毁线程栈)

将转成了纤程的线程转回来。

ConvertTiberToThread(Ex)

如果为纤程保存一些信息可以使用纤程局部存储区(Fiber Local Storage, FLS)函数

其他纤程相关的函数

获得纤程执行的上下文

PVOID GetCurrentFiber();

返回纤程的用户自定义值(CreateFiber所传递的pvParam)

PVOID GetFiberData();

Counter示例程序