Windows核心编程 第十二章 纤程
第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核心编程 第十二章 纤程的更多相关文章
- Windows核心编程:第12章 纤程
Github https://github.com/gongluck/Windows-Core-Program.git //第12章 纤程.cpp: 定义应用程序的入口点. // #include & ...
- Windows核心编程 第十五章 在应用程序中使用虚拟内存
第1 5章 在应用程序中使用虚拟内存 Wi n d o w s提供了3种进行内存管理的方法,它们是: • 虚拟内存,最适合用来管理大型对象或结构数组. • 内存映射文件,最适合用来管理大型数据流(通常 ...
- Windows核心编程 第十九章 DLL基础
第1 9章 D L L基础 这章是介绍基本dll,我就记录一些简单应用,dll的坑点以及扩展后面两章会说,到时候在总结. 自从M i c r o s o f t公司推出第一个版本的Wi n d o w ...
- Windows核心编程 第十四章 虚拟内存
第1 4章 虚 拟 内 存 <这一章没啥,是说的几个内存相关的函数 > 14.1 系统信息 许多操作系统的值是根据主机而定的,比如页面的大小,分配粒度的大小等.这些值决不应该用硬编码的形式 ...
- 《Windows核心编程系列》二十谈谈DLL高级技术
本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...
- 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .
http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...
- Windows核心编程:第4章 进程
Github https://github.com/gongluck/Windows-Core-Program.git //第4章 进程.cpp: 定义应用程序的入口点. // #include &q ...
- 《windows核心编程系列》二十一谈谈基址重定位和模块绑定
每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...
随机推荐
- 进阶Java多线程
一.多线程创建方式 1.1.继承Thread类创建线程类 1.实现步骤 定义一个继承Thread类的子类,并重写该类的run()方法: 创建Thread子类的实例,即创建了线程对象: 调用该线程对象的 ...
- python 常用的库
本节大纲: 模块介绍 time &datetime模块 random os sys shutil json & picle shelve xml处理 yaml处理 configpars ...
- (2)MySQL进阶篇SQL优化(show status、explain分析)
1.概述 在应用系统开发过程中,由于初期数据量小,开发人员写SQL语句时更重视功能上的实现,但是当应用系统正式上线后,随着生产数据量的急剧增长,很多SQL语句开始逐渐显露出性能问题,对生产环境的影响也 ...
- python-类的多态的理解
了解多态 多态指的是一类事物有多种形态 .定义:多态是一中使用对象的方式,更容易编写出通用的代码,做出通用的编程,一适应需求的不断变化 实现步骤: 1.定义父类,并提供公共方法 2.定义子类,并重写父 ...
- hdu 4622 (hash+“map”)
题目链接:https://vjudge.net/problem/HDU-4622 题意:给定t组字符串每组m条询问--求问每条询问区间内有多少不同的子串. 题解:把每个询问区间的字符串hash一下存图 ...
- Rust 内置 trait :PartialEq 和 Eq
GitHub: https://github.com/storagezhang Emai: debugzhang@163.com 华为云社区: https://bbs.huaweicloud.com/ ...
- Android Studio之圆形按钮设计
•效果展示图 •实现方法 点击 app/src/main/res 找到 drawable 文件夹,右击->New->Drawable Resource File. 创建一个 $drawab ...
- dll远线程注入
原理 核心函数 CreateRemoteThread:让在其他进程中创建一个线程变成可能 核心思想 HANDLE WINAPI CreateRemoteThread( __in HANDLE hPro ...
- PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642
PAT (Basic Level) Practice (中文)1070 结绳 (25 分) 凌宸1642 题目描述 给定一段一段的绳子,你需要把它们串成一条绳.每次串连的时候,是把两段绳子对折,再如下 ...
- PAT A1025 考生排名问题
题目要求:有n个考场,每个考场有若干个考生,现给出各个考场中考生的准考证号与分数,按照分数从高到低排序,并按顺序输出所有考生的准考证号,排名,考场号以及所在的考场排名 #include<cstd ...