第1 2章 纤 程

M i c r o s o f t公司给Wi n d o w s添加了一种纤程,以便能够非常容易地将现有的
U N I X服务器应用程序移植到Wi n d o w s中。U N I X服务器应用程序属于单线程应用程序(由
Wi n d o w s定义) ,但是它能够为多个客户程序提供服务。换句话说,
U N I X应用程序的开发人员已经创建了他们自己的线程结构库,他们能够使用这种线程结构库来仿真纯线程。该线程包能够创建多个堆栈,

保存某些C P U寄存器,并且在它们之间进行切换,以便为客户机请求提供服务。

显然,若要取得最佳的性能,这些 U N I X应用程序必须重新设计,仿真的线程库应该用Wi n d o w s提供的纯线程来替代。然而,这种重新设计需要花费数月甚至更长的时间才能完成,因此许多公司首先将它们现有的
U N I X代码移植到Wi n d o w s中,这样就能够将某些应用软件推向Wi n d o w s市场。

当你将U N I X代码移植到Wi n d o w s中时,一些问题就会因此而产生。尤其是
Wi n d o w s管理线程的内存栈的方法要比简单地分配内存复杂得多。
Wi n d o w s内存栈开始时的物理存储器的容量比较小,然后根据需要逐步扩大。这个过程在第
1 6章“线程的堆栈”中详细介绍。由于结构化异常处理机制的原因,代码的移植就更加复杂了。

为了能够更快和更正确地将它们的代码移植到 Wi n d o w s中,M i c r o s o f t公司在操作系统中添加了纤程。本章将要介绍纤程的概念、负责操作纤程的函数以及如何利用纤程的特性。要记住,如果有设计得更好的使用Wi
n d o w s自身线程的应用程序,那么应该避免使用纤程。

纤程的操作

首先要注意的一个问题是,实现线程的是Wi n d o w s内核。操作系统清楚地知道线程的情况,并且根据M i c r o s o f t定义的算法对线程进行调度。纤程是以用户方式代码来实现的,内核并不知道纤程,并且它们是根据用户定义的算法来调度的。由于你定义了纤程的调度算法,因此,就内核而言,纤程采用非抢占式调度方式。

需要了解的下一个问题是,单线程可以包含一个或多个纤程。就内核而言,线程是抢占调度的,是正在执行的代码。然而,线程每次执行一个纤程的代码 — 你决定究竟执行哪个纤程(随着我们讲解的深入,这些概念将会越来越清楚) 。

当使用纤程时,你必须执行的第一步操作是将现有的线程转换成一个纤程。可以通过调用C o n v e r t T h r e a d To F i b e r函数来执行这项操作:

PVOID ConvertThreadToFiber(PVOID pvParam);

该函数为纤程的执行环境分配相应的内存(约为2 0 0字节) 。该执行环境由下列元素组成:

• 一个用户定义的值,它被初始化为传递给C o n v e r t T h r e a d To F i b e r的p v P a r a m参数的值。

• 结构化异常处理链的头。

• 纤程内存栈的最高和最低地址(当将线程转换成纤程时,这也是线程的内存栈) 。

• CPU寄存器,包括堆栈指针、指令指针和其他。

当对纤程的执行环境进行分配和初始化后,就可以将执行环境的地址与线程关联起来。该线程被转换成一个纤程,而纤程则在该线程上运行。 C o n v e r t T h r e a d To F i b e r函数实际上返回纤程的执行环境的内存地址。虽然必须在晚些时候使用该地址,但是决不应该自己对该执行环境数据进行读写操作,因为必要时纤程函数会为你对该结构的内容进行操作。现在,如果你的纤程(线程)返回或调用E
x i t T h r e a d函数,那么纤程和线程都会终止运行。

除非打算创建更多的纤程以便在同一个线程上运行,否则没有理由将线程转换成纤程。若要创建另一个纤程,该线程(当前正在运行纤程的线程)可以调用 C r e a t e F i b e r函数:

PVOID CreateFiber(

DWORD dwStackSize,

PFIBER_START_POUTINE pfnStartAddress,

PVOID pvParam);

C r e a t e F i b e r首先设法创建一个新内存栈,它的大小由d w S t a c k S i z e参数来指明。通常传递的参数是0,按照默认设置,它创建一个内存栈,其大小可以扩展为
1 M B,不过开始时有两个存储器页面用于该内存栈。如果设定一个非0值,那么就用设定的大小来保存和使用内存栈。接着,C r
e a t e F i b e r函数分配一个新的纤程执行环境结构,并对它进行初始化。该用户定义的值被设置为传递给C r e a t e F i b e r的p v P a r a m参数的值,新内存栈的最高和最低地址被保存,同时,纤程函数的内存地址(作为p
f n S t a r t A d d r e s s参数来传递)也被保存。P f n S t a r t A d d r e s s参数用于设定必须实现的纤程例程的地址,它必须采用下面的原型:

VOID WINAPI FiberFunc(PVOID pvParam);

当纤程被初次调度时,该函数就开始运行,并且将原先传递给 C r e a t e F i b e r的p v P a r a m的值传递给它。可以在这个纤程函数中执行想执行的任何操作。但是该函数的原型规定返回值是V
O I D,这并不是因为返回值没有任何意义,而是因为该函数根本不应该返回。如果纤程确实返回了,那么线程和该线程创建的所有纤程将立即被撤消。

与C o n v e r t T h r e a d To F i b e r函数一样,C r e a t e F i b e r函数也返回纤程运行环境的内存地址。
但是,与C o n v e r t T h r e a d To F i b e r不同的是,这个新纤程并不执行,因为当前运行的纤程仍然在执行。在单个线程上,每次只能运行一个纤程。若要使新纤程能够运行,可以调用
Switch To Fiber函数:

VOID SwitchToFiber(PVOID pvFiberExecutionContext);

Switch To Fiber函数只有一个参数,即
p v F i b e r E x e c u t i o n C o n t e x t,它是上次调用C o n v e r t T h r e a d To F i b e r或C
r e a t e F i b e r函数时返回的纤程的执行环境的内存地址。该内存地址告诉该函数要对哪个纤程进行调度。S w i t c h To F i b e r函数在内部执行下列操作步骤:

1) 它负责将某些当前的C P U寄存器保存在当前运行的纤程执行环境中,包括指令指针寄存器和堆栈指针寄存器。

2) 它将上一次保存在即将运行的纤程的执行环境中的寄存器装入
C P U寄存器。这些寄存器包括堆栈指针寄存器。这样,当线程继续执行时,就可以使用该纤程的内存栈。

3) 它将纤程的执行环境与线程关联起来,线程运行特定的纤程。

4) 它将线程的指令指针设置为已保存的指令指针。线程(纤程)从该纤程上次执行的地方开始继续执行。

S w i t c h To F i b e r函数是纤程获得C P U时间的唯一途径。由于你的代码必须在相应的时间显式调用S w i t c h To
F i b e r函数,因此你对纤程的调度可以实施全面的控制。记住,纤程的调度与线程调度毫不相干。纤程运行所依赖的线程始终都可以由操作系统终止其运行。当线程被调度时,当前选定的纤程开始运行,而其他纤程则不能运行,除非显式调用
S w i t c h To F i b e r函数。若要撤消纤程,可以调用D e l e t e F i b e r函数:

VOID DeleteFiber(PVOID pvFiberExecutionContext);

该函数用于删除p v F i b e r E x e c u t i o n C o n t e x t参数指明的纤程,当然这是纤程的执行环境的地址。该函数能够释放纤程栈使用的内存,然后撤消纤程的执行环境。但是,如果传递了当前与线程相关联的纤程地址,那么该函数就在内部调用
E x i t T h r e a d函数,该线程及其创建的所有纤程全部被撤消。

D e l e t e F i b e r函数通常由一个纤程调用,以便删除另一个纤程。已经删除的纤程的内存栈将被撤消,纤程的执行环境被释放。注意,纤程与线程之间的差别在于,线程通常通过调用E x i t T h r e a d函数将自己撤消。实际上,用一个线程调用
Te r m i n a t e T h r e a d函数来终止另一个线程的运行,是一种不好的方法。如果你确实调用了
Te r m i n a t e T h r e a d函数,系统并不撤消已经终止

运行的线程的内存栈。可以利用纤程的这种能力来删除另一个纤程,后面介绍示例应用程序时将说明这是如何实现的。

为了使操作更加方便,还可以使用另外两个纤程函数。一个线程每次可以执行一个纤程,操作系统始终都知道当前哪个纤程与该线程相关联。如果想要获得当前运行的纤程的执行环境的地址,可以调用G e t C u r r e n t F i b e r函数:

PVOID GetCurrentFiber();

另一个使用非常方便的函数是G e t F i b e r D a t a:

PVOID GetFiberData();

前面讲过,每个纤程的执行环境包含一个用户定义的值。这个值使用作为 C o n v e r t T h r e a dTo F i b e r或C r e a t e F i b e r的p
v P a r a m参数而传递的值进行初始化。该值也可以作为纤程函数的参数来传递。G e t F i b e r D a t a只是查看当前执行的纤程的执行环境,并返回保存的值。

无论G e t C u r r e n t F i b e r还是G e t F i b e r D a t a,运行速度都很快,并且通常是作为内蕴函数(infrinsic
funcfion)来实现的,这意味着编译器能够为这些函数生成内联代码。

Windows核心编程 第十二章 纤程的更多相关文章

  1. Windows核心编程:第12章 纤程

    Github https://github.com/gongluck/Windows-Core-Program.git //第12章 纤程.cpp: 定义应用程序的入口点. // #include & ...

  2. Windows核心编程 第十五章 在应用程序中使用虚拟内存

    第1 5章 在应用程序中使用虚拟内存 Wi n d o w s提供了3种进行内存管理的方法,它们是: • 虚拟内存,最适合用来管理大型对象或结构数组. • 内存映射文件,最适合用来管理大型数据流(通常 ...

  3. Windows核心编程 第十九章 DLL基础

    第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...

  4. Windows核心编程 第十四章 虚拟内存

    第1 4章 虚 拟 内 存 <这一章没啥,是说的几个内存相关的函数 > 14.1 系统信息 许多操作系统的值是根据主机而定的,比如页面的大小,分配粒度的大小等.这些值决不应该用硬编码的形式 ...

  5. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  6. 【windows核心编程】 第六章 线程基础

    Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ①    一个是线程的内核 ...

  7. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

  8. Windows核心编程:第4章 进程

    Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...

  9. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

随机推荐

  1. 进阶Java多线程

    一.多线程创建方式 1.1.继承Thread类创建线程类 1.实现步骤 定义一个继承Thread类的子类,并重写该类的run()方法: 创建Thread子类的实例,即创建了线程对象: 调用该线程对象的 ...

  2. python 常用的库

    本节大纲: 模块介绍 time &datetime模块 random os sys shutil json & picle shelve xml处理 yaml处理 configpars ...

  3. (2)MySQL进阶篇SQL优化(show status、explain分析)

    1.概述 在应用系统开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产环境的影响也 ...

  4. python-类的多态的理解

    了解多态 多态指的是一类事物有多种形态 .定义:多态是一中使用对象的方式,更容易编写出通用的代码,做出通用的编程,一适应需求的不断变化 实现步骤: 1.定义父类,并提供公共方法 2.定义子类,并重写父 ...

  5. hdu 4622 (hash+“map”)

    题目链接:https://vjudge.net/problem/HDU-4622 题意:给定t组字符串每组m条询问--求问每条询问区间内有多少不同的子串. 题解:把每个询问区间的字符串hash一下存图 ...

  6. Rust 内置 trait :PartialEq 和 Eq

    GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...

  7. Android Studio之圆形按钮设计

    •效果展示图 •实现方法 点击 app/src/main/res 找到 drawable 文件夹,右击->New->Drawable Resource File. 创建一个 $drawab ...

  8. dll远线程注入

    原理 核心函数 CreateRemoteThread:让在其他进程中创建一个线程变成可能 核心思想 HANDLE WINAPI CreateRemoteThread( __in HANDLE hPro ...

  9. PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642

    PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642 题目描述 给定一段一段的绳子,你需要把它们串成一条绳.每次串连的时候,是把两段绳子对折,再如下 ...

  10. PAT A1025 考生排名问题

    题目要求:有n个考场,每个考场有若干个考生,现给出各个考场中考生的准考证号与分数,按照分数从高到低排序,并按顺序输出所有考生的准考证号,排名,考场号以及所在的考场排名 #include<cstd ...