Electronic Joint Business

Solution for E-Business

thread

IRP

在 Windows NT内核的 IO 模型中,有两类 IRP:threaded irp和non-threaded irp,顾名思义,前者跟thread绑定,后者跟thread无关。当一个threaded irp被创建时,创建线程会有一个队列保存该irp,直到irp完成之后才释放。当你试图让这条线程退出时,系统会检测队列看里面是否还有irp没完成,如果有,线程会一直等待,直到所有的irp全部完成。而non-thread irp则正好相反,如果该irp已经返回到了创建它的地方你还继续complete它,BSOD将会发生。1

Threaded IRP
如前面所讲,threaded irp和线程绑定在一起。当user mode程序发起IO操作(比如WriteFile函数),或者一个驱动向另一个驱动发起IO操作(比如ZwWriteFile),IOManager首先找到文件handle对应的驱动栈,获取栈顶的PDEVICE_OBJECT,生成一个irp,将IO操作的各种信息放入irp中,并将此irp放入线程的irp队列中,然后调用IoCallDriver将irp转给驱动栈一层层处理,每往下一层,IO_STACK_LOCATION中的指针就会往下移一个位置(实际情况比这稍微复杂一点点,以后再说,现在先把它想成是一个栈,每往下一层就是push一次),直到最后跟硬件打完交道返回。返回的过程正好与上述相反。最末一层处理完irp后,会调用IoCompleteRequest,此函数会将IO_STACK_LOCATION指针往上移(pop),并最后调用上层驱动设置的CompletRoutine。每层的CompleteRoutine做完相应工作后都会调用IoCompleteRequest讲irp继续往上转,直到栈顶PDEVICE_OBJECT。此时IO_STACK_LOCATION已经在顶部,所谓再上层的CompleteRoutine也不存在,所以本层CompleteRoutine中操作将irp归还给IOManager,IOManager负责回收资源,并从线程irp队列中去除相应的项。

这个过程和函数调用很像,像到你都会发问:为什么搞成这种模式,函数调用和返回不也有压栈出栈的操作吗,干嘛不借用这套机制反而要另起炉灶单独搞一套。答案很简单,就是效率。nt内核的IO操作全是异步的,调用IoCallDriver的线程和CompleteRoutine响应到的线程,不一定是同一条。关于这点我们以后再谈,但它却引出了一个目前我们就需要关心的问题:既然CompleteRotine的线程环境可能已经切换了,那么IOManager如何知道从哪个线程的irp队列里去除相应项?答案也很简单,就是保存在irp中。每一个threaded irp的PIRP->Thread域都保存它的创建线程,IOManager就是用它来寻找线程。注意:有人会想从PIRP->Thread里获得当前线程的信息,这是不对的。

Non-threaded irp
non-threaded irp一般都由驱动创建而不是IOManager,线程不在irp队列里保存它的实例,并且它的PIRP->Thread为NULL。当CompleteRoutine一路返回调到该irp的创建者时,千万不要返还给IOManager,直接free掉就可以了。

irp创建函数
从user mode一路下来的irp一定是threaded irp这很明显。那么内核态的函数中,哪些是创建threaded irp,哪些是创建non-threaed irp的?以下表格列出了各函数:

>>> 阅读全文

 

, , ,

I/O模型:阻塞、非阻塞 & 同步、异步

在进行 I/O 编程时,我们常常见到同步、异步、阻塞和非阻塞等术语,这些术语常常彼此交叉且不是易于理解。

同步
所谓同步,就是在发出一个函数调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,提到同步、异步的时候,特指那些需要一定时间完成的任务,最常见的例子就是 SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的 LRESULT值返回给调用者。

异步
异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知或回调来通知调用者。以 CAsycSocket 类为例(注意,CSocket 从 CAsyncSocket 派生,但是起功能已经由异步转化为同步),当一个客户端通过调用 Connect 函数发出一个连接请求后,调用者线程立刻可以朝下运行。当连接真正建立起来以后,socket底层会发送一个消息通知该对象。这里提到执行部件可以通过三种途径返回结果:状态、通知和回调。使用哪一种依赖于执行部件的实现,除非执行部件提供多种选择,否则不受调用者控制。不过如果执行部件用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低。如果是使用通知的方式,效率则很高,因为执行部件几乎不需要做额外的操作。至于回调函数,和通知区别不大。

另外你也常常会看到用阻塞模式和非阻塞模式来实现 I/O 操作。所谓阻塞,是指当前线程在运行中也会因为某些原因而阻塞。 所有处于阻塞状态的线程的共同特征是: 放弃CPU, 暂停运行, 只有等到导致阻塞的原因消除, 才能恢复运行;

阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。有人也许会把阻塞调用和同步调用等同起来,实际上是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。例如,我们在CSocket中调用Receive函数,如果缓冲区中没有数 据,这个函数就会一直等待,直到有数据才返回。而此时,当前线程还会继续处理各种各样的消息。如果主窗口和调用函数在同一个线程中,除非你在特殊的界面操 作函数中调用,其实主界面还是应该可以刷新。socket接收数据的另外一个函数recv则是一个阻塞调用的例子。当socket工作在阻塞模式的时候, 如果没有数据的情况下调用该函数,则当前线程就会被挂起,直到有数据为止。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回

>>> 阅读全文

 

, , , ,

无锁双向链表

基于锁的同步机制总是受到象优先级反转(priority inversion )和死锁的困扰,即使这样,无锁同步还未引起重视。原因是要编写正确且高效的无锁代码实在困难1。本文演示了一个无锁双向链表的实现,实用且易于理解。第一个无锁双向链表是由 H°akan Sundell 实现的,本文中的方案利用了单字 (single-word) 的比较并转换(CAS — compare-and-swap)原子原语。在附录中提供了本方案与 H°akan Sundell 实现的链表的比较结果。 2

背景: 原子操作与 CAS
原子操作是指一系列必须整体完成的操作步骤,如果任何一步操作没有完成,那么所有完成的步骤都必须回滚,这样就可以保证要么所有操作步骤都未完成,要么所有操作步骤都被完成。在单核系统里,单个的机器指令可以看成是原子操作(如果有编译器优化、乱序执行等情况除外);在多核系统中,单个的机器指令就不是原子操作,因为多核系统里是多指令流并行运行的,一个核在执行一个指令时,其他核同时执行的指令有可能操作同一块内存区域,从而出现数据竞争现象。多核系统中的原子操作通常使用内存栅障(memory barrier)来实现,即一个 CPU 核在执行原子操作时,其他 CPU 核必须停止对内存操作或者不对指定的内存进行操作,这样才能避免数据竞争问题。3

因此在 X86 平台上,Intel 提供了在指令执行期间对总线加锁的手段。CPU 芯片上有一条引线 #HLOCKpin,如果汇编程序中的某条指令带了前缀 “LOCK”,CPU 执行到这条指令时就会 #HLOCKpin 的电位拉低直到该指令结束,从而将总线锁住,这样同一总线上其它 CPU 就暂时不能通过总线访问内存了,这样保证了指令在多处理器环境中的原子性。

所谓比较并交换 (CAS) 是在多线程中可用来获得同步的原子指令。CAS 操作包含三个操作数 —— 目标值(V)、原值(A)和新值(B)。CAS 指令会先将目标值 V 与原值 A 进行比较,如果二者相同,那么就可以新值 B 赋予目标值。

在 Windows 和 .NET 平台,由于历史原因,CAS 被写做 Interlocked API。原子操作在 Intel 架构 CPU 对应的汇编指令有 XCHG、CMPXCHG、INC 等,当然还得加上 LOCK 作为前缀。

>>> 阅读全文

 

, , ,

C++ 11 中的多线程

文章评价:

第一个 C++ 标准发布至今已经过去十三年了,随着新的 C++11(或称 C++0X)标准的推出,C++ 标准委员会所做出的显著改变之一就是支持多线程编程。这是首次在 C++ 语言中为并行编程提供支持,而且与平台无关。

在 C++11 标准之前,编写多线程应用需要依赖于特定的平台扩展,如 Intel TBB、OpenMP、Pthreads 等等。现在有了 C++11 的标准线程库就方便了应用移植(例如 Windows 上的 C++11 多线程应用将很容易移植到 Linux 平台),另外对于熟知 Boost 线程库的用户来说,由于 C++11 标准库中许多命名与结构都和 Boost 相同,上手非常容易。1

C++11 标准库中的类可用于线程操作与同步、公用保护数据及低层次的原子操作。

C++11 标准中涉及多线程编程的主要有四个头文件,分别是:<atomic> ,<thread>,<mutex>,<condition_variable>和<future>:

>>> 阅读全文

 

, , ,

并行程序中的同步与通讯

在任何程序中,总会有些执行阶段要等待操作系统或者其他进程完成 I/O 操作或者同步操作。这时候,程序就无法处理其它工作,因为实际上是操作系统或者其它进程占用了宝贵的 CPU 时间。这就是滥用文件读/写操作的程序会如此之慢的原因。1

这时候进程不得不等待操作系统服务例程将 CPU 从其他进程那里释放出来。这种等待也被称为进程处于阻塞状态。诸如 EDA 这类串行计算,整个算法作为单个任务被执行,如果该进程在等待任一种阻塞操作,整个算法就会完全停顿。在处于阻塞状态的这段时间里,另一个进程占用了 CPU 的所有权,所以算法根本没办法进行。

并行程序提供了一种手段,能在同一计算中调用另一个进程以提高 CPU 利用率,当一个进程被阻塞的时候(如等待 I/O 操作或者同步操作),另一个进程则继续处理共同工作的其它部分。这种情况在单 CPU 或者多 CPU 机器上皆可发生。

反之,对于串行程序(即非并行算法)来说,而无论有多少可用的 CPU ,在任何情况下都只能用到单个 CPU。除非有编译器将源程序从串行转成并行,不过能实现这一目标的实属特例。 因此如果想同时利用多个 CPU ,编码时要手工运用某种并行技术。(在源代码中采用特殊指令,如 OpenMP)。

并行程序能将工作划分为较小的部分,因此多个进程可以一起工作并互相协作以便能在更短的时间内计算出同样的结果。这时,在等待 I/O 设备完成操作的同时,并行程序能可以继续处理另一部分工作。此外,并行程序还可以同时使用系统所有可用的 CPU。

>>> 阅读全文

 

, , , , , , ,

ELF 对线程局部储存的处理 (一)

线程使用的增加使得开发者期望有更好的方式来处理线程局部数据 (read-local data)。 POSIX 线程接口的有关定义允许在每个线程中独立存储 void * 对象。不过使用该接口很麻烦。你需要在运行时为对象分配一个键(key),如果这个键不再使用就需要释放它。这项工作繁琐而且易出错。如果和动态加载代码(dynamically loaded code)一起使用,就真的成了个大麻烦。

应对这些问题的方法是扩展编程语言,让编译器接手这些工作。以 C/C++ 来说,可以在定义与声明变量时使用新关键字 __thread。这虽然不是个官方扩展,编译器开发者被鼓励实现它以支持新 ABI。用这个关键字定义及声明的变量会自动地局部分配到每个线程。

__thread int i;
__thread struct states;
extern __thread char *p;

不仅一般用户程序可以受益,运行时环境(runtime)亦能从中获得好处 (如全局变量 errno 必须是线程局部的),而且编译器可以执行构建非自动变量( non-autimatic variable)的优化。注意:用 __thread 来定义自动变量毫无意义也不被允许,这是因为自动变量总是线程局部的。而且,函数作用域中的静态变量应是首选。

 

, ,

Windows 内部探秘之线程

文章评价:
在当今的编程世界中,多线程已经成为 .NET、Java 或 C++ 等各种编程语言的一个必要组成部分。要编写具备高响应且可扩展的应用程序,就必须借、助多线程编程的力量。你也许用过 TPL、PLINQ、Task Factories、线程池、异步编程等等基础类库(Framework Class Libraries –FCL)进行过多任务处理,所有的这些在背后都依赖于 Windows 线程的力量来实现并行的场景。了解 Windows 线程的基本结构对于更好地运用和理解诸如 TPL、PLINQ 这些高级特性总是非常有帮助,并帮助你以可视化的方式了解在系统中多个线程是如何协同工作的,特别是当你在调试多线程应用程序的时候。在这篇文章中,我想和大家分享一些关于Windows线程的基础知识,它可以帮助您了解操作系统如何实现线程的。

Windows 线程由什么组成?
让我们看看 Windows 线程的一些基本组件。在 Windows 线程中有三个基本组件:

  • 线程内核对象
  • TEB

所有这三个组件一起创造了 Windows 线程。我会尝试逐一解释这些组件,但在这之前,让我们先对 Windows 内核和内核对象做简要介绍,因为这些都是Windows 操作系统最重要的部分

操作系统内核
内核是任何操作系统的主要组件。它是应用程序和硬件之间的桥梁。内核提供了抽象层,使得应用程序可以由此与硬件交互。

内核是操作系统最先加载的部分之一,它会一直保留在物理内存中。内核的主要功能是管理计算机的硬件和资源,让其他程序可以运行并使用这些资源。要更多地了解内核,请访问此链接

>>> 阅读全文

 

, ,

用OpenMP开发并行程序

OpenMP 是一套可于共享内存并行系统的多线程程序设计的指导语句(Compiler Directive),它最早是由 OpenMP Architecture Review Board 牵头提出的,现在OpenMP为许多厂商所支持,因此具有较高的可移植性。支持 OpenMP 的编程语言包括 C 语言、C++ 和 Fortran 等等,支持 OpenMP 的编译器包括 Sun Compiler,GNU Compiler 和 Intel Compiler,MS VC++ 等等。

OpenMP 提供了对并行算法的高层的抽象描述,程序员通过在源代码中加入专用的 pragma 来指明自己的意图,由此编译器可以自动将程序进行并行化,并在必要之处加入同步互斥以及通信。如果选择忽略这些 pragma,或者编译器不支持 OpenMP 时,程序又可退化为通常的程序(一般为串行),代码仍然可以正常运作,只是不能利用多线程来加速程序执行。

OpenMP 提供的这种对于并行描述的高层抽象降低了并行编程的难度和复杂度,这样程序员可以把更多的精力投入到并行算法本身,而非其具体实现细节。对基于数据分集的多线程程序设计,OpenMP 是一个很好的选择。同时,使用 OpenMP 也提供了更强的灵活性,可以较容易的适应不同的并行系统配置。线程粒度和负载平衡等是传统多线程程序设计中的难题,但在 OpenMP 中,库函数从程序员手中接管了这两方面的部分工作。

但是,作为高层抽象,OpenMP 并不适合需要复杂的线程间同步和互斥的场合。OpenMP 的另一个缺点是不能在非共享内存系统(如计算机集群)上使用。在这样的系统上,MPI 使用较多。

OpenMP 的架构
OpenMP 是作为共享内存系统的标准问世的。它是为在多处理器机上编写并行程序而设计的一个应用编程接口。它包括一套编译指导语句和一个用来支持它的函数库。

>>> 阅读全文

 

, , ,

了解Linux操作系统 (一)进程管理

文章评分:

前言
Linux 是一种由全世界开发者共同开发的开源操作系统。其源代码可以自由获取并可以在 GNU GPL 授权下使用。有很多公司提供不同的系统发行版供用户使用,如Redhat和Novell SUSE。大部分桌面发行版都可以从网站上免费下载,但服务器版一般是需要购买的。

在过去的几年里,Linux 被世界上许多公司的数据中心所使用。如今 Linux 操作系统为科学领域和企业用户所认可。它已经成为一种多种用途的操作系统。你能在多种嵌入式设备中发现它,如:防火墙、手机或电脑主机。所以 Linux 的性能对于科学领域和企业用户来说已经成为一个热门议题。然而一个操作系统可能被用来计算全球的天气预报或者被用来运行数据库等多种用途,Linux 必须能够为各种可能的使用情境提供优良性能。大多数 Linux 发行版含有常规的调校参数来满足所有用户。

IBM 意识到作为一种操作系统,Linux 非常适合在 IBM 系统之上运行企业级应用。大多数企业应用现在都可以运行在 Linux 上,包括文件服务器、打印服务器、数据库服务器、Web服务器、以及沟通和邮件服务器。

在企业级服务器运行 Linux 时需要对其性能进行监控,在必要时需要对服务器进行调优以消除影响用户的性能瓶颈。本红皮书将介绍一些调优 Linux 的方法、监控分析服务器性能的工具、以及对于特定应用的关键性能参数。本文目的是说明怎样分析和调校 Linux 操作系统,从而为在系统上运行的各种不同应用提供优良的性能。

>>> 阅读全文

 

, , , , ,

细谈UI线程的SynchronizationContexts

[b]为什么需要SynchronizationContexts[/b]
在图中的演示里,我试图用ironruby或者ironpython去控制一只小乌龟,(就是那个三角箭头),通过ironruby的命令让小乌龟前进、后退、左转、右转。

这里存在一个线程同步的问题,敲下一个命令之后,屏幕上的UI物件应该要马上按指令运作(体会一下魔兽世界的宏),所以小乌龟工作在一个UI线程里,而命令窗口工作在一个非UI的线程,如果不加任何处理的话,直接在命令窗口里操作UI物件的引用就会导致一个System.InvalidOperationException: “Object is currently in use elsewhere”(.net 2.0),或者得到一个不可知的状态(.net 1.0)。

[b]应用场景和解决方案[/b]

上面提出的一个很常见问题:应用程序有两个线程:线程A和线程B,不过线程B比较特殊,它属于UI线程,当这两个线程同时运行的时候,线程A有个需求:”修改UI对象的属性”,这时候如果你是线程A,你会如何去完成需求呢?!可能有几种解决方案。

第一种方式:

>>> 阅读全文

 

, , ,