Libin's profile绿色家园PhotosBlogLists Tools Help

Blog


    May 30

    介绍qmake

    qmake是用来为不同的平台的开发项目创建makefile的Trolltech开发一个易于使用的工具。qmake简化了makefile的生成,所以为了创建一个makefile只需要一个只有几行信息的文件。qmake可以供任何一个软件项目使用,而不用管它是不是用Qt写的,尽管它包含了为支持Qt开发所拥有的额外的特征。

    qmake基于一个项目文件这样的信息来生成makefile。项目文件可以由开发者生成。项目文件通常很简单,但是如果需要它是非常完善的。不用修改项目文件,qmake也可以为为Microsoft Visual Studio生成项目。

    qmake的概念

    QMAKESPEC环境变量

    举例来说,如果你在Windows下使用Microsoft Visual Studio,然后你需要把QMAKESPEC环境变量设置为win32-msvc。如果你在Solaris上使用gcc,你需要把QMAKESPEC环境变量设置为solaris-g++

    在qt/mkspecs中的每一个目录里面,都有一个包含了平台和编译器特定信息的qmake.conf文件。这些设置适用于你要使用qmake的任何项目,请不要修改它,除非你是一个专家。例如,假如你所有的应用程序都必须和一个特定的库连接,你可以把这个信息添加到相应的qmake.conf文件中。

    项目(.pro)文件

    一个项目文件是用来告诉qmake关于为这个应用程序创建makefile所需要的细节。例如,一个源文件和头文件的列表、任何应用程序特定配置、例如一个必需要连接的额外库、或者一个额外的包含路径,都应该放到项目文件中。

    “#”注释

    你可以为项目文件添加注释。注释由“#”符号开始,一直到这一行的结束。

    模板

    模板变量告诉qmake为这个应用程序生成哪种makefile。下面是可供使用的选择:

    • app - 建立一个应用程序的makefile。这是默认值,所以如果模板没有被指定,这个将被使用。

    • lib - 建立一个库的makefile。

    • vcapp - 建立一个应用程序的Visual Studio项目文件。

    • vclib - 建立一个库的Visual Studio项目文件。

    • subdirs - 这是一个特殊的模板,它可以创建一个能够进入特定目录并且为一个项目文件生成makefile并且为它调用make的makefile。

    “app”模板

    “app”模板告诉qmake为建立一个应用程序生成一个makefile。当使用这个模板时,下面这些qmake系统变量是被承认的。你应该在你的.pro文件中使用它们来为你的应用程序指定特定信息。

    • HEADERS - 应用程序中的所有头文件的列表。

    • SOURCES - 应用程序中的所有源文件的列表。

    • FORMS - 应用程序中的所有.ui文件(由Qt设计器生成)的列表。

    • LEXSOURCES - 应用程序中的所有lex源文件的列表。

    • YACCSOURCES - 应用程序中的所有yacc源文件的列表。

    • TARGET - 可执行应用程序的名称。默认值为项目文件的名称。(如果需要扩展名,会被自动加上。)

    • DESTDIR - 放置可执行程序目标的目录。

    • DEFINES - 应用程序所需的额外的预处理程序定义的列表。

    • INCLUDEPATH - 应用程序所需的额外的包含路径的列表。

    • DEPENDPATH - 应用程序所依赖的搜索路径。

    • VPATH - 寻找补充文件的搜索路径。

    • DEF_FILE - 只有Windows需要:应用程序所要连接的.def文件。

    • RC_FILE - 只有Windows需要:应用程序的资源文件。

    • RES_FILE - 只有Windows需要:应用程序所要连接的资源文件。

    你只需要使用那些你已经有值的系统变量,例如,如果你不需要任何额外的INCLUDEPATH,那么你就不需要指定它,qmake会为所需的提供默认值。例如,一个实例项目文件也许就像这样:

    TEMPLATE = app
    DESTDIR = c:\helloapp
    HEADERS += hello.h
    SOURCES += hello.cpp
    SOURCES += main.cpp
    DEFINES += QT_DLL
    CONFIG += qt warn_on release

    如果条目是单值的,比如template或者目的目录,我们是用“=”,但如果是多值条目,我们使用“+=”来为这个类型添加现有的条目。使用“=”会用新值替换原有的值,例如,如果我们写了DEFINES=QT_DLL,其它所有的定义都将被删除。

    “lib”模板

    “lib”模板告诉qmake为建立一个库而生成makefile。当使用这个模板时,除了“app”模板中提到系统变量,还有一个VERSION是被支持的。你需要在为库指定特定信息的.pro文件中使用它们。

    • VERSION - 目标库的版本号,比如,2.3.1。

    “subdirs”模板

    “subdirs”模板告诉qmake生成一个makefile,它可以进入到特定子目录并为这个目录中的项目文件生成makefile并且为它调用make。

    在这个模板中只有一个系统变量SUBDIRS可以被识别。这个变量中包含了所要处理的含有项目文件的子目录的列表。这个项目文件的名称是和子目录同名的,这样qmake就可以发现它。例如,如果子目里是“myapp”,那么在这个目录中的项目文件应该被叫做myapp.pro

    CONFIG变量

    配置变量指定了编译器所要使用的选项和所需要被连接的库。配置变量中可以添加任何东西,但只有下面这些选项可以被qmake识别。

    下面这些选项控制着使用哪些编译器标志:

    • release - 应用程序将以release模式连编。如果“debug”被指定,它将被忽略。

    • debug - 应用程序将以debug模式连编。

    • warn_on - 编译器会输出尽可能多的警告信息。如果“warn_off”被指定,它将被忽略。

    • warn_off - 编译器会输出尽可能少的警告信息。

    下面这些选项定义了所要连编的库/应用程序的类型:

    • qt - 应用程序是一个Qt应用程序,并且Qt库将会被连接。

    • thread - 应用程序是一个多线程应用程序。

    • x11 - 应用程序是一个X11应用程序或库。

    • windows - 只用于“app”模板:应用程序是一个Windows下的窗口应用程序。

    • console - 只用于“app”模板:应用程序是一个Windows下的控制台应用程序。

    • dll - 只用于“lib”模板:库是一个共享库(dll)。

    • staticlib - 只用于“lib”模板:库是一个静态库。

    • plugin - 只用于“lib”模板:库是一个插件,这将会使dll选项生效。

    例如,如果你的应用程序使用Qt库,并且你想把它连编为一个可调试的多线程的应用程序,你的项目文件应该会有下面这行:

        CONFIG += qt thread debug

    注意,你必须使用“+=”,不要使用“=”,否则qmake就不能正确使用连编Qt的设置了,比如没法获得所编译的Qt库的类型了。

    May 29

    编译meshlab 1.21全接触


    1. 编译环境:

           a. visual studio 2008 perfessional edition,因为已经安装了sp1,所以不确定它会对编译有何影响。

           b. QT opensource v4.50,只编译了其动态库文件(静态库无法编译完全)。

    执行“Visual Studio 2008 命令提示 ”控制台工具后,在QT根目录执行QT编译环境设置脚本,该脚本同时为meshlab生成vc 项目工程的环境。一下为该脚本原文:

    @echo off

    set cur_dir=%cd%\

    set QTDIR=%cur_dir%

    set QMAKESPEC=win32-msvc2008

    set ConfPara=-debug-and-release -opensource -fast -no-dbus -no-webkit

     

    set PATH=%QTDIR%/bin;%PATH%

    set INCLUDE=%MINGWDIR%/include;%QTDIR%/include;%QWTDIR%/src;%LOG4QTDIR%/src;%INCLUDE%

    set LIB=%MINGWDIR%/lib;%QTDIR%/lib;%QWTDIR%/lib;%LIB%

     

    echo ***********************************************************************

    echo Created By gmail:bygreencn.gmail.com

    echo Includes  : QT 4.5.0Visual Studio 2008

    echo QT        : %QTDIR%

    echo QMAKESPEC: %QMAKESPEC%

    echo ConfPara: %ConfPara%

    echo ***********************************************************************

    @REM pause

    @REM nmake clean

    @REM nmake confclean

    @REM configure.exe %ConfPara%

     

    @REM pause

    @REM echo build it now?

    @REM nmake clean

    cmd /k

     

           c.下载MeshLab's source code version 1.2.1,我下载的是All Inclusive package

    2. 生成所需的VC项目工程文件

    a. 上一步的控制台,进入.\ meshlab\src\external,执行qmake -tp vc -recursive external.pro

    b. 上一步的控制台,进入.\ meshlab\src,执行qmake -tp vc -recursive meshlabv12.pro

    3. 编译meshlab

           a. 首先编译external library。用vc打开.\ meshlab\src\external\external.sln,进入配置管理器,选择编译debugrelease版本,在选择生成解决方案,等待编译全部通过,它会生成三个库文件。bz.lib,3ds.libmuparser.lib,我发现3ds.lib的生成有些问题,会使得meshlabLINK时无法正确连接函数,我在lib3ds\type.h做了一下修改:

    //#ifdef _MSC_VER

    //#ifdef LIB3DS_EXPORTS

    //#define LIB3DSAPI __declspec(dllexport)

    //#else              

    //#define LIB3DSAPI __declspec(dllimport)

    //#endif           

    //#else

    #define LIB3DSAPI

    //#endif

           b.编译meshlabmeshlab所有的plugin工程的设置有些问题,这些工程都制定输出为动态库,但是输出文件却指定为输出为*****.lib,这导致所有的plugins的工程都失败。我的做法是把指定输出为*****.dll,这里应该必须为dll,因为New的对话框中会根据plugins文件夹下的内容动态生成,因为这些应该也是动态加载的,因为静态库是不行的了。

    1). 需要为io_lib指定其需要的lib3ds.lib(external library)

    2). 需要为io_epoch指定其需要的bz2.lib和头文件的位置(external library)

    如果哪个plugin工程出现类似这个错误:

    1>Project : error PRJ0019: 某个工具从以下位置返回了错误代码: "MOC v3dImportDialog.h"

    1>项目: warning PRJ0018 : 未找到下列环境变量:

    1>$(QTDIR)

    只需要用生成VC项目的那个控制台环境进入相应的plugin目录,执行qmake -tp vc -recursive xxxxxx.pro来重新生成该工程即可。

     

    编译整个项目,大概需要十多分钟。然后就可以进入.\meshlab\src\meshlab\debug或者.\meshlab\src\meshlab\release执行meshlab.exe;记得要把QTDLL库文件拷贝到这个目录或者将QTDLL库所在目录加入到系统PATH中啊。

     

    May 13

    破解无线路由器密码

    Posted by chinahang 2009年5月13日

    随着社会的进步!WIFI上网日益普及,特别是大城市中随便在一个小区搜索一下就能找到好多热点,搜索到热点然后链接上去那么我们就可以尽情的享受免费上网服务了。
    不过除了公共场所以及菜鸟用户之外几乎所有的WIFI信号都是加密的,很简单换作是你你也不愿意把自己的带宽免费拿出来给别人用,所以如果你搜索到你附近有热点想免费上网的话请仔细往下学习...
                                                               

                                                           破解静态WEP KEY全过程

     

     

    发现

    首先通过NetStumbler确认客户端已在某AP的覆盖区内,并通过AP信号的参数进行‘踩点’(数据搜集)。

    NetStumbler 下载地址  

    通 过上图的红色框框部分内容确定该SSID名为demonalex的AP为802.11b类型设 备,Encryption属性为‘已加密’,根据802.11b所支持的算法标准,该算法确定为WEP。有一点需要注意:NetStumbler对任何有 使用加密算法的STA[802.11无线站点]都会在Encryption属性上标识为WEP算法,如上图中SSID为gzpia的AP使用的加密算法是 WPA2-AES。

    破解

    下载Win32AirCrack程序集---WinAirCrackPack工具包(下载地址:http://www.demonalex.net/download/wireless/aircrack/WinAircrackPack.zip)。解压缩后得到一个大概4MB的目录,其中包括六个EXE文件:

    aircrack.exe  WIN32aircrack程序

    airdecap.exe      WEP/WPA解码程序

    airodump.exe  数据帧捕捉程序

    Updater.exe WIN32aircrack的升级程序

    WinAircrack.exe      WIN32aircrack图形前端

    wzcook.exe 本地无线网卡缓存中的WEPKEY记录程序

     

    我们本次实验的目的是通过捕捉适当的数据帧进行IV(初始化向量)暴力破解得到WEP KEY,因此只需要使用airodump.exe(捕捉数据帧用)与WinAircrack.exe(破解WEP KEY用)两个程序就可以了。

    首先打开ariodump.exe程序,按照下述操作:

     
    首 先程序会提示本机目前存在的所有无线网卡接口,并要求你输入需要捕捉数据帧的无线网卡接口编号,在这里我选择使用支持通用驱动的BUFFALO WNIC---编号‘26’;然后程序要求你输入该WNIC的芯片类型,目前大多国际通用芯片都是使用‘HermesI/Realtek’子集的,因此选 择‘o’;然后需要输入要捕捉的信号所处的频道,我们需要捕捉的AP所处的频道为‘6’;提示输入捕捉数据帧后存在的文件名及其位置,若不写绝对路径则文 件默认存在在winaircrack的安装目录下,以.cap结尾,我在上例中使用的是‘last’; 最后winaircrack提示:‘是否只写入/记录IV[初始化向量]到cap文件中去?’,我在这里选择‘否/n’;确定以上步骤后程序开始捕捉数据 包。

    下 面的过程就是漫长的等待了,直至上表中‘Packets’列的总数为300000时即可满足实验要求。根据实验的经验所得:当该AP的通信数据流量极度频 繁、数据流量极大时,‘Packets’所对应的数值增长的加速度越大。当程序运行至满足‘Packets’=300000的要求时按Ctrl+C结束该 进程。 此时你会发现在winaircrack的安装目录下将生成last.cap与last.txt两个文件。其中last.cap为通用嗅探器数据包记录文件 类型,可以使用ethereal程序打开查看相关信息;last.txt为此次嗅探任务最终的统计数据(使用‘记事本/notepad’打开 last.txt后得出下图)。

    下面破解工作主要是针对last.cap进行。首先执行WinAirCrack.exe文件:

    单击上图红色框框部分的文件夹按钮,弹出*.cap选定对话框,选择last.cap文件,然后通过点击右方的‘Wep’按钮切换主界面至WEP破解选项界面:

    选 择‘Key size’为64(目前大多数用户都是使用这个长度的WEP KEY,因此这一步骤完全是靠猜测选定该值),最后单击主界面右下方的‘Aircrack the key…’按钮,此时将弹出一个内嵌在cmd.exe下运行的进程对话框,并在提示得出WEP KEY:

    利用

    打开无线网卡的连接参数设置窗口,设置参数为:

    SSIDdemonalex

    频道:6

    WEP KEY111112222264位)

    OK,现在可以享受连入别人WLAN的乐趣了。

    April 28

    让VS 2008支持Subversion插件Ankhsvn

    Visual Studio 2005 有一个开源的Subversion插件,Ankhsvn  (http://ankhsvn.tigris.org/),安装后,VS 2005中将内置Subversion的支持,可以直接在VS里面提交修改。我经常用它和TortoiseSVN 配合来使用Subversion,十分方便。

    可是升级到Visual Studio 2008后,发现Ankhsvn没有集成进来,因为目前的Ankhsvn还不支持VS2008,据说下个版本才会支持VS 2008。

    不过这不影响我们在Visual Studio 2008中使用Ankhsvn,我们可以自己动手修改注册表,将Ankhsvn集成进VS 2008。方法很简单。

    1. 运行 regedit
    2. 找到 HKLMSOFTWAREMicrosoftVisualStudio8.0AddinsAnkh
    3. 右键点击它,选择导出,并指定一个文件保存。
    4. 用记事本或者其他文本编辑器打开这个文件,将其中的VisualStudio8.0替换为VisualStudio9.0
    5. 最后,双击这个修改后的注册表文件,提示是否导入进系统注册表,选择是。

    再次打开Visual Studio 2008后,就会发现Ankhsvn已经集成进系统了。

    为了方便操作,我写了一个vbscript脚本来进行上述操作。使用很简单,将下面的脚本保存到一个文本文件,命名为ankh.vbs,然后双击该文件即可运行。运行后,重新打开Visual Studio 2008,就会发现Ankhsv已经集成进来了。

    顺便再推荐几个常用的免费的插件:

    [FREE VISUAL STUDIO ADD-INS]
    http://searchwindevelopment.techtarget.com/originalContent/0,289142,sid8_gci1262570,00.html


    需要提醒的是,注册表操作不慎可能会导致系统崩溃,因此请谨慎修改注册表。

    Dim shell, filename, fso, file, content
    Set shell = CreateObject("wscript.shell"
    )
    Set fso = CreateObject("Scripting.FileSystemObject"
    )
    filename 
    = "ankh.reg"


    shell.run 
    "reg export HKLMSOFTWAREMicrosoftVisualStudio8.0AddinsAnkh " & filename, 1True

    Set file = fso.OpenTextFile(filename, 1FalseTrue)
    content 
    =
     file.ReadAll
    content 
    = Replace(content, "VisualStudio8.0""VisualStudio9.0"
    )
    content 
    = Replace(content, ".NET 2005"".NET 2008"
    )
    file.Close()

    Set file = fso.OpenTextFile(filename, 2True
    )
    file.Write content
    file.Close()

    shell.run 
    "reg import " & filename, 1true


    fso.DeleteFile filename
    April 16

    Gpredict is a real-time satellite tracking and orbit prediction application

    Gpredict Screenshots

    Below you can see some sample screenshots of gpredict in in action. They illustrate the main features of gpredict. I have a whole album dedicated to screenshots of Gpredict in my Media Gallery. You can also post links to your own screenshots and pictures of Gpredict in action on the forum.

    Main Window, Modules Layouts, and Views

    Gpredict main window Gpredict main window

    Gpredict main window Map View

    List View List View

    Polar View Single Satellite View

    Future Pass Predictions

    Next Pass: Data Next Pass: Polar

    Next Pass: Az/El Next Pass: Data

    Upcoming Passes Upcoming Passes

    Radio and Antenna Rotator Control

    Radio control window Antenna rotator control window

    Preferences and Settings

    Preferences Dialogue Preferences Dialogue

    Preferences Dialogue Preferences Dialogue

    Module Configuration

    April 14

    用VC6.0编译boost 1.38.0

    1. 从boost.org下载下1.38.0的源码

    2.编译jam

        在boost_1_38_0\tools\jam\src下有个build.bat ,修改其ProgramFiles变量为VC安装的目录。

        执行build.bat msvc,等编译成功,会在\boost_1_38_0\tools\jam\src\bin.ntx86下生成bjam.exe

    3.将bjam拷贝到boost_1_38_0\bin,将目录增加环境变量PATH。

    4.编译整个boost

        在根目录boost_1_38_0,执行bjam --toolset=msvc-6.0
     

    另外:

    5. 如果编译boost里面单独的模块,如regex

        进入boost_1_38_0\libs\regex\build

        执行bjam  bjam --toolset=msvc-6.0

        就会在boost_1_38_0\bin.v2\libs\regex\build\msvc-6.0\debug\threading-multi下面生成boost_regex-vc6-mt-gd-1_38_0.dll和boost_regex-vc6-mt-gd-1_38_0.lib库,可以给VC6.0使用。

    March 23

    Git 中文教程

    介绍

    Git --- The stupid content tracker, 傻瓜内容跟踪器。Linus 是这样给我们介绍 Git 的。

    Git 是用于 Linux 内核开发的版本控制工具。与常用的版本控制工具 CVS, Subversion 等不同, 它采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其方便。 Git 的速度很快,这对于诸如 Linux kernel 这样的大项目来说自然很重要。 Git 最为出色的是它的合并跟踪(merge tracing)能力。

    实际上内核开发团队决定开始开发和使用 Git 来作为内核开发的版本控制系统的时候, 世界开源社群的反对声音不少,最大的理由是 Git 太艰涩难懂,从 Git 的内部工作机制来说,的确是这样。 但是随着开发的深入,Git 的正常使用都由一些友好的脚本命令来执行,使 Git 变得非常好用, 即使是用来管理我们自己的开发项目,Git 都是一个友好,有力的工具。 现在,越来越多的著名项目采用 Git 来管理项目开发,例如:wine, U-boot 等,详情看 http://www.kernel.org/git

    作为开源自由原教旨主义项目,Git 没有对版本库的浏览和修改做任何的权限限制。 它只适用于 Linux / Unix 平台,没有 Windows 版本,目前也没有这样的开发计划。

    本文将以 Git 官方文档 Tutorial core-tutorialEveryday GIT 作为蓝本翻译整理,但是暂时去掉了对 Git 内部工作机制的阐述, 力求简明扼要,并加入了作者使用 Git 的过程中的一些心得体会,注意事项,以及更多的例子。 建议你最好通过你所使用的 Unix / Linux 发行版的安装包来安装 Git, 你可以在线浏览本文 ,也可以通过下面的命令来得到本文最新的版本库,并且通过后面的学习用 Git 作为工具参加到本文的创作中来。

    创建一个版本库:git-init-db

    创建一个 Git 版本库是很容易的,只要用命令 git-init-db 就可以了。 现在我们来为本文的写作创建一个版本库:

    $ mkdir gittutorcn
    $ cd gittutorcn
    $ git-init-db

    git 将会作出以下的回应

    defaulting to local storage area

    这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。 你可以用 ls -a 查看一下,并请注意其中的三项内容:

    • 一个叫 HEAD 的文件,我们现在来查看一下它的内容:

      $ cat .git/HEAD

      现在 HEAD 的内容应该是这样:

      ref: refs/heads/master

      我们可以看到,HEAD 文件中的内容其实只是包含了一个索引信息, 并且,这个索引将总是指向你的项目中的当前开发分支。

    • 一个叫 objects 的子目录,它包含了你的项目中的所有对象,我们不必直接地了解到这些对象内容, 我们应该关心是存放在这些对象中的项目的数据。

      Note
      关于 git 对象的分类,以及 git 对象数据库的说明, 请参看 [Discussion]
    • 一个叫 refs 的子目录,它用来保存指向对象的索引。

    具体地说,子目录 refs 包含着两个子目录叫 headstags, 就像他们的名字所表达的意味一样:他们存放了不同的开发分支的的索引, 或者是你用来标定版本的标签的索引。

    请注意:master 是默认的分支,这也是为什么 .git/HEAD 创建的时候就指向 master 的原因,尽管目前它其实并不存在。 git 将假设你会在 master 上开始并展开你以后的工作,除非你自己创建你自己的分支。

    另外,这只是一个约定俗成的习惯而已,实际上你可以将你的工作分支叫任何名字, 而不必在版本库中一定要有一个叫 master 的分支,尽管很多 git 工具都认为 master 分支是存在的。

    现在已经创建好了一个 git 版本库,但是它是空的,还不能做任何事情,下一步就是怎么向版本库植入数据了。

    植入内容跟踪信息:git-add

    为了简明起见,我们创建两个文件作为练习:

    $ echo "Hello world" > hello
    $ echo "Silly example" > example

    我们再用 git-add 命令将这两个文件加入到版本库文件索引当中:

    $ git-add hello example

    git-add 实际上是个脚本命令,它是对 git 内核命令 git-update-index 的调用。 因此上面的命令和下面的命令其实是等价的:

    $ git-update-index --add hello example

    如果你要将某个文件从 git 的目录跟踪系统中清除出去,同样可以用 git-update-index 命令。例如:

    $ git-update-index --force-remove foo.c
    Note
    git-add 可以将某个目录下的所有内容全都纳入内容跟踪之下,例如: git-add ./path/to/your/wanted 。但是在这样做之前, 应该注意先将一些我们不希望跟踪的文件清理掉, 例如,gcc 编译出来的 *.o 文件,vim 的交换文件 .*.swp 之类。

    应该建立一个清晰的概念就是,git-addgit-update-index 只是刷新了 git 的跟踪信息,hello 和 example 这两个文件中的内容并没有提交到 git 的内容跟踪范畴之内。

    提交内容到版本库:git-commit

    既然我们刷新了 Git 的跟踪信息,现在我们看看版本库的状态:

    $ git-status

    我们能看到 git 的状态提示:

    #
    # Initial commit
    #
    #
    # Updated but not checked in:
    # (will commit)
    #
    # new file: example
    # new file: hello
    #

    提示信息告诉我们版本库中加入了两个新的文件,并且 git 提示我们提交这些文件, 我们可以通过 git-commit 命令来提交:

    $ git-commit -m "Initial commit of gittutor reposistory"

    查看当前的工作:git-diff

    git-diff 命令将比较当前的工作目录和版本库数据库中的差异。 现在我们编辑一些文件来体验一下 git 的跟踪功能。

    $ echo "It's a new day for git" >> hello

    我们再来比较一下,当前的工作目录和版本库中的数据的差别。

    $ git-diff

    差异将以典型的 patch 方式表示出来:

    diff --git a/hello b/hello
    index a5c1966..bd9212c 100644
    --- a/hello
    +++ b/hello
    @@ -1 +1,2 @@
    Hello, world
    +It's a new day for git

    此时,我们可以再次使用组合命令 git-update-indexgit-commit 将我们的工作提交到版本库中。

    $ git-update-index hello
    $ git-commit -m "new day for git"

    实际上,如果要提交的文件都是已经纳入 git 版本库的文件,那么不必为这些文件都应用 git-update-index 命令之后再进行提交,下面的命令更简捷并且和上面的命令是等价的。

    $ git-commit -a -m "new day for git"

    管理分支:git-branch

    直至现在为止,我们的项目版本库一直都是只有一个分支 master。 在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支。 下面列举一些常见的分支策略,仅供大家参考:

    • 创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰, 也方便与他人交流协作。

    • 当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。

    • 合并别人的工作的时候,最好是创建一个临时的分支,关于如何用临时分支合并别人的工作的技巧, 将会在后面讲述。

    创建分支

    下面的命令将创建我自己的工作分支,名叫 robin,并且将以后的工作转移到这个分支上开展。

    $ git-branch robin
    $ git-checkout robin

    删除分支

    要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如:

    $ git-branch -D branch-name

    查看分支

    运行下面的命令可以得到你当前工作目录的分支列表:

    $ git-branch

    如果你忘记了你现在工作在哪个分支上,运行下面的命令可以告诉你:

    $ cat .git/HEAD 

    查看项目的发展变化和比较差异

    这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令:

    git-show-branch

    git-diff

    git-whatchanged


    我们现在为 robin, master 两个分支都增加一些内容。

    $ git-checkout robin
    $ echo "Work, work, workd" >> hello
    $ git-commit -m "Some workd" -i hello
    $ git-checkout master
    $ echo "Play, play, play" >> hello
    $ echo "Lots of fun" >> example
    $ git-commit -m "Some fun" -i hello example

    git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态, 并且可以看到每次提交的内容是否已进入每个分支。

    $ git-show-branch 

    这个命令让我们看到版本库的发展记录。

    * [master] Some fun
    ! [robin] some work
    --
    * [master] Some fun
    + [robin] some work
    *+ [master^] a new day for git

    譬如我们要查看世系标号为 master^robin 的版本的差异情况, 我们可以使用这样的命令:

    $ git-diff master^ robin

    我们可以看到这两个版本的差异:

    diff --git a/hello b/hello
    index 263414f..cc44c73 100644
    --- a/hello
    +++ b/hello
    @@ -1,2 +1,3 @@
    Hello World
    It's a new day for git
    +Work, work, work
    Note
    关于 GIT 版本世系编号的定义,请参看 git-rev-parse

    我们现在再用 git-whatchanged 命令来看看 master 分支是怎么发展的。

    $ git-checkout master
    $ git-whatchanged
    diff-tree 1d2fa05... (from 3ecebc0...)
    Author: Vortune.Robin
    Date: Tue Mar 21 02:24:31 2006 +0800

    Some fun

    :100644 100644 f24c74a... 7f8b141... M example
    :100644 100644 263414f... 06fa6a2... M hello

    diff-tree 3ecebc0... (from 895f09a...)
    Author: Vortune.Robin
    Date: Tue Mar 21 02:17:23 2006 +0800

    a new day for git

    :100644 100644 557db03... 263414f... M hello

    从上面的内容中我们可以看到,在 robin 分支中的日志为 "Some work" 的内容, 并没有在 master 分支中出现。

    合并两个分支:git-merge

    既然我们为项目创建了不同的分支, 那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。 现在我们看看怎么将 robin 分支上的工作合并到 master 分支中。 现在转移我们当前的工作分支到 master,并且将 robin 分支上的工作合并进来。

    $ git-checkout master
    $ git-merge "Merge work in robin" HEAD robin
    合并两个分支,还有一个更简便的方式,下面的命令和上面的命令是等价的。
    $ git-checkout master
    $ git-pull . robin

    但是,此时 git 会出现合并冲突提示:

    Trying really trivial in-index merge...
    fatal: Merge requires file-level merging
    Nope.
    Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960
    Merging:
    1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
    d2659fcf690ec693c04c82b03202fc5530d50960 some work
    found 1 common ancestor(s):
    3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for git
    Auto-merging hello
    CONFLICT (content): Merge conflict in hello

    Automatic merge failed; fix up by hand

    git 的提示指出,在合并作用于文件 hello 的 'Some fun' 和 'some work' 这两个对象时有冲突, 具体通俗点说,就是在 master, robin 这两个分支中的 hello 文件的某些相同的行中的内容不一样。 我们需要手动解决这些冲突,现在先让我们看看现在的 hello 文件中的内容。

    $ cat hello

    此时的 hello 文件应是这样的,用过其他的版本控制系统的朋友应该很容易看出这个典型的冲突表示格式:

    Hello World
    It's a new day for git
    <<<<<<< HEAD/hello
    Play, play, play
    =======
    Work, work, work
    >>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello

    我们用编辑器将 hello 文件改为:

    Hello World
    It's a new day for git
    Play, play, play
    Work, work, work

    现在可以将手动解决了冲突的文件提交了。

    $ git-commit -i hello

    以上是典型的两路合并(2-way merge)算法,绝大多数情况下已经够用。 但是还有更复杂的三路合并和多内容树合并的情况。详情可参看: git-read-tree git-merge 等文档。

    逆转与恢复:git-reset

    项目跟踪工具的一个重要任务之一,就是使我们能够随时逆转(Undo)和恢复(Redo)某一阶段的工作。

    git-reset 命令就是为这样的任务准备的。 它将当前的工作分支的 定位到以前提交的任何版本中,它有三个重置的算法选项。

    命令形式:

    git-reset [--mixed | --soft | --hard] [<commit-ish>]

    命令的选项:

    --mixed
    仅是重置索引的位置,而不改变你的工作树中的任何东西(即,文件中的所有变化都会被保留, 也不标记他们为待提交状态),并且提示什么内容还没有被更新了。这个是默认的选项。
    --soft
    既不触动索引的位置,也不改变工作树中的任何内容,我们只是要求这些内容成为一份好的内容 (之后才成为真正的提交内容)。 这个选项使你可以将已经提交的东西重新逆转至“已更新但未提交(Updated but not Check in)”的状态。 就像已经执行过 git-update-index 命令,但是还没有执行 git-commit 命令一样。
    --hard
    将工作树中的内容和头索引都切换至指定的版本位置中,也就是说自 <commit-ish> 之后的所有的跟踪内容和工作树中的内容都会全部丢失。 因此,这个选项要慎用,除非你已经非常确定你的确不想再看到那些东西了。

    一个重要技巧--逆转提交与恢复

    可能有人会问,--soft 选项既不重置头索引的位置,也不改变工作树中的内容, 那么它有什么用呢?现在我们介绍一个 --soft 选项的使用技巧。 下面我们用例子来说明:

    $ git-checkout master
    $ git-checkout -b softreset
    $ git-show-branch

    这里我们创建了一个 master 的拷贝分支 softreset, 现在我们可以看到两个分支是在同一起跑线上的。

    ! [master] Merge branch 'robin'
    ! [robin] some work
    * [softreset] Merge branch 'robin'
    ---
    - - [master] Merge branch 'robin'
    + * [master^] Some fun
    ++* [robin] some work

    我们为 文件增加一些内容并提交。

    $ echo "Botch, botch, botch" >> hello
    $ git-commit -a -m "some botch"
    $ git-show-branch

    我们可以看到此时 softresetmaster 推进了一个版本 "some botch" 。

    ! [master] Merge branch 'robin'
    ! [robin] some work
    * [softreset] some botch
    ---
    * [softreset] some botch
    - - [master] Merge branch 'robin'
    + * [master^] Some fun
    ++* [robin] some work

    现在让我们来考虑这样的一种情况,假如我们现在对刚刚提交的内容不满意, 那么我们再编辑项目的内容,再提交的话,那么 "some botch" 的内容就会留在版本库中了。 我们当然不希望将有明显问题的内容留在版本库中,这个时候 --soft 选项就很有用了。 为了深入了解 --soft 的机制,我们看看现在 softreset 分支的头和 ORIG_HEAD 保存的索引。

    $ cat .git/refs/heads/softreset .git/ORIG_HEAD

    结果如下:

    5e7cf906233e052bdca8c598cad2cb5478f9540a
    7bbd1370e2c667d955b6f6652bf8274efdc1fbd3

    现在用 --soft 选项逆转刚才提交的内容:

    git-reset --soft HEAD^

    现在让我们再看看 .git/ORIG_HEAD 的中保存了什么?

    $ cat .git/ORIG_HEAD

    结果如下:

    5e7cf906233e052bdca8c598cad2cb5478f9540a

    看!现在的 .git/ORIG_HEAD 等于逆转前的 .git/refs/heads/softreset 。 也就是说,git-reset --soft HEAD^ 命令逆转了刚才提交的版本进度, 但是它将那次提交的对象的索引拷贝到了 .git/ORIG_HEAD 中。

    我们再编辑 hello 文件成为下面的内容:

    Hello World
    It's a new day for git
    Play, play, play
    Work, work, work
    Nice, nice, nice

    我们甚至可以比较一下现在的工作树中的内容和被取消了的那次提交的内容有什么差异:

    $ git-diff ORIG_HEAD

    结果如下:

    diff --git a/hello b/hello
    index f978676..dd02c32 100644
    --- a/hello
    +++ b/hello
    @@ -2,4 +2,4 @@ Hello World
    It's a new day for git
    Play, play, play
    Work, work, work
    -Botch, botch, botch
    +Nice, nice, nice

    接着,我们可以恢复刚才被取消了的那次提交了。

    $ git-commit -a -c ORIG_HEAD

    注意,这个命令会打开默认的文本编辑器以编辑原来提交的版本日志信息,我们改为 "nice work" 。 大家可以自行用 git-show-branch 命令来查看一下现在的分支状态。 并且我们还可以不断地重复上述的步骤,一直修改到你对这个版本进度满意为止。

    git-reset 命令还有很多的用途和技巧,请参考 git-reset ,以及 Everyday GIT with 20 commands or So

    提取版本库中的数据

    这是个很有用的小技巧,如果你对你现在的工作目录下的东西已经不耐烦了, 随时可以取出你提交过的东西覆盖掉当前的文件,譬如:

    $ git-checkout -f foo.c

    标定版本

    在 git 中,有两种类型的标签,“轻标签”和“署名标签”。

    技术上说,一个“轻标签”和一个分支没有任何区别,只不过我们将它放在了 .git/refs/tags/ 目录, 而不是 heads 目录。因此,打一个“轻标签”再简单不过了。

    $ git-tag my-first-tag

    如果你打算针对某个commit ID来打标签,虽然该命令可以通过gitk里的右键菜单来实现,但是该命令对实际应用是很有帮助的。

    $ git-tag mytag f0af6283824688f9d23426031734657661b54388

    “署名标签”是一个真正的 git 对象,它不但包含指向你想标记的状态的指针,还有一个标记名和信息, 可选的 PGP 签名。你可以通过 -a 或者是 -s 选项来创建“署名标签”。

    $ git-tag -s <tag-name>

    合并外部工作

    通常的情况下,合并其他的人的工作的情况会比合并自己的分支的情况要多, 这在 git 中是非常容易的事情,和你运行 git-merge 命令没有什么区别。 事实上,远程合并的无非就是“抓取(fetch)一个远程的版本库中的工作到一个临时的标签中”, 然后再使用 git-merge 命令。

    可以通过下面的命令来抓取远程版本库:

    $ git-fetch <remote-repository>

    根据不同的远程版本库所使用的通讯协议的路径来替代上面的 remoted-repository 就可以了。

    Rsync

    rsync://remote.machine/patch/to/repo.git/

    SSH

    remote.machine:/path/to/repo.git
    or
    ssh://remote.machine/patch/to/repo.git/

    这是可以上传和下载的双向传输协议,当然,你要有通过 ssh 协议登录远程机器的权限。 它可以找出两端的机器提交过的对象集之中相互缺少了那些对象,从而得到需要传输的最小对象集。 这是最高效地交换两个版本库之间的对象的方式(在 git 兼容的所有传输协议当中)。

    下面是个取得 SSH 远程版本库的命令例子:

    $ git-fetch robin@192.168.1.168:/path/to/gittutorcn.git  (1)

    (1) 这里 robin 是登录的用户名,192.168.1.168 是保存着主版本库的机器的 IP 地址。
    Local directory

    /path/to/repo.git/

    本地目录的情况和 SSH 情况是一样的。

    git Native

    git://remote.machine/path/to/repo.git/

    git 自然协议是设计来用于匿名下载的,它的工作方式类似于 SSH 协议的交换方式。

    HTTP(S)

    http://remote.machine/path/to/repo.git/

    到这里可能有些朋友已经想到, 实际上,我们可以通过 Rsync, SSH 之类的双向传输方式来建立类似 CVS,SVN 这样的中心版本库模式的开发组织形式。

    通过电子邮件交换工作

    读过上一节之后,有的朋友可能要问,如果版本库是通过单向的下载协议发布的,如 HTTP, 我们就无法将工作上传到公共的版本库中。别人也不能访问我的机器来抓取我的工作,那怎么办呢?

    不必担心,我们还有 email !别忘了 git 本来就是为了管理 Linux 的内核开发而设计的。 所以,它非常适合像 Linux Kernel 这样的开发组织形式高度分散,严重依赖 email 来进行交流的项目。

    下面模拟你参加到《Git 中文教程》的编写工作中来,看看我们可以怎么通过 email 进行工作交流。 你可以通过下面的命令下载这个项目的版本库。

    之后,你会在当前目录下得到一个叫 gittutorcn 的目录, 这就是你的项目的工作目录了。默认地,它会有两个分支: masterorigin,你可以直接在 master 下展开工作, 也可以创建你自己的工作分支,但是千万不要修改 origin 分支,切记! 因为它是公共版本库的镜像,如果你修改了它, 那么就不能生成正确的对公共版本库的 patch 文件了。
    Note
    如果你的确修改过 origin 分支的内容,那么在生成 patch 文件之前, 请用 git-reset --hard 命令将它逆转到最原始的,没经过任何修改的状态。

    你可以直接在 master 下开展工作,也可以创建你自己的工作分支。 当你对项目做了一定的工作,并提交到库中。我们用 git-show-branch 命令先看下库的状态。

    * [master] your buddy's contribution
    ! [origin] degining of git-format-patch example
    --
    * [master] your buddy's contribution
    *+ [origin] degining of git-format-patch example

    上面就假设你已经提交了一个叫 "your buddy's contribution" 的工作。 现在我们来看看怎么通过 email 来交流工作了。

    $ git-fetch origin    (1)
    $ git-rebase origin (2)
    $ git-format-patch origin (3)

    (1)更新 origin 分支,防止 origin 分支不是最新的公共版本,产生错误的补丁文件;
    (2)将你在 master 上提交的工作迁移到新的源版本库的状态的基础上;
    (3)生成补丁文件;

    上面的几个命令,会在当前目录下生成一个大概名为 0001-your-buddy-s-contribution.txt 补丁文件, 建议你用文本工具查看一下这个文件的具体形式,然后将这个文件以附件的形式发送到项目维护者的邮箱: vortune@gmail.com

    当项目的维护者收到你的邮件后,只需要用 git-am 命令,就可以将你的工作合并到项目中来。

    $ git-checkout -b buddy-incomming
    $ git-am /path/to/0001-your-buddy-s-contribution.txt

    用 Git 协同工作

    假设 Alice 在一部机器上自己的个人目录中创建了一个项目 /home/alice/project, Bob 想在同一部机器自己的个人目录中为这个项目做点什么。

    Bob 首先这样开始:

    $ git-clone /home/alice/project myrepo

    这样就创建了一个保存着 Alice 的版本库的镜像的新目录 "myrepo"。 这个镜像保存着原始项目的起点和它的发展历程。

    接着 Bob 对项目做了些更改并提交了这些更改:

    (编辑一些文件)

    $ git-commit -a

    (如果需要的话再重复这个步骤)

    当他搞定之后,他告诉 Alice 将他的东西从 /home/bob/myrepo 中引入,她只需要这样:

    $ cd /home/alice/project
    $ git pull /home/bob/myrepo

    这样就将 Bob 的版本库中的 "master" 分支的变化引入了。 Alice 也可以通过在 pull 命令的后面加入参数的方式来引入其他的分支。

    在导入了 Bob 的工作之后,用 "git-whatchanged" 命令可以查看有什么信的提交对象。 如果这段时间里以来,Alice 也对项目做过自己的修改,当 Bob 的修改被合并进来的时候, 那么她需要手动修复所有的合并冲突。

    谨慎的 Alice 在导入 Bob 的工作之前,希望先检查一下。 那么她可以先将 Bob 的工作导入到一个新创建的临时分支中, 以方便研究 Bob 的工作:

    $ git fetch /home/bob/myrepo master:bob-incoming

    这个命令将 Bob 的 master 分支的导入到名为 bob-incoming 的分支中( 不同于 git-pull 命令,git-fetch 命令只是取得 Bob 的开发工作的拷贝, 而不是合并经来)。接着:

    $ git whatchanged -p master..bob-incoming

    这会列出 Bob 自取得 Alice 的 master 分支之后开始工作的所有变化。 检查过这些工作,并做过必须的调整之后, Alice 就可以将变化导入到她的 master 分支中:

    $ git-checkout master
    $git-pull . bob-incoming

    最后的命令就是将 "bob-incoming" 分支的东西导入到 Alice 自己的版本库中的, 稍后,Bob 就可以通过下面的命令同步 Alice 的最新变化。

    $ git-pull

    注意不需为这个命令加入 Alice 的版本库的路径,因为当 Bob 克隆 Alice 的版本库的时候, git 已经将这个路径保存到 .git/remote/origin 文件中,它将会是所以的导入操作的默认路径。

    Bob 可能已经注意到他并没有在他的版本库中创建过分支(但是分支已经存在了):

    $ git branch
    * master
    origin

    "origin" 分支,它是运行 "git-clone" 的时候自动创建的,他是 Alice 的 master 分支的原始镜像, Bob 应该永远不要向这个分支提交任何东西。

    如果 Bob 以后决定在另外一部主机上开展工作,那么他仍然需要通过 SSH 协议从新克隆和导入( Alice 的版本库):

    $ git-clone alice.org:/home/alice/project/ myrepo

    我们可以使用 git 自然协议,或者是 rsync, http 等协议的任何一种,详情请参考 git-pull

    Git 同样可以建立类似 CVS 那样的开发模式,也就是所有开发者都向中心版本库提交工作的方式, 详情参考 git_pushgit for CVS users

    为版本库打包

    在前面,我们已经看到在 .git/objects/??/ 目录中保存着我们创建的每一个 git 对象。 这样的方式对于自动和安全地创建对象很有效,但是对于网络传输则不方便。 git 对象一旦创建了,就不能被改变,但有一个方法可以优化对象的存储,就是将他们“打包到一起”。

    $ git repack

    上面的命令让你做到这点,如果你一直是做着我们的例子过来的, 你现在大约会在 .git/objects/??/ 目录下积累了17个对象。 git-repack 会告诉你有几个对象被打包了, 并且将他们保存在 .git/objects/pack 目录当中。

    Note
    你将会看到两个文件,pack-*.pack and pack-*.idx.git/objects/pack 目录。他们的关系是很密切的, 如果你手动将他们拷贝到别的版本库中的话,你要决定将他们一起拷贝。 前者是保存着所有被打包的数据的文件,后者是随机访问的索引。

    如果你是个偏执狂,就运行一下 git-verity-pack 命令来检查一下有缺陷的包吧, 不过,其实你无须太多担心,我们的程序非常出色 ;-).

    一旦你已经对那些对象打包了,那么那些已经被打过包的原始的对象,就没有必要保留了。

    $ git prune-packed

    会帮你清楚他们。

    如果你好奇的话,你可以在执行 git-prune-repacked 命令之前和之后, 都运行一下 find .git/objects -type f,这样你就能看到有多少没有打包的对象, 以及节省了多少磁盘空间。

    Note
    git pull git-pull 对于 HTTP 传输来说,一个打包过的版本库会将一定数量的 相关联的对象放进一个有关联性的打包中。如果你设想多次从 HTTP 公共版本库中导入数据, 你也许要频繁地 reapck & prune,要么就干脆从不这样做。

    如果你此时再次运行 git-repack,它就会说 "Nothing to pack"。 要是你继续开发,并且积累了一定数量的变迁,再运行 git-repack 将会创建一个新的包, 它会包含你自上次对库打包以来创建的对象。我们建议你尽快在初始化提交之后打包一下你的版本库( 除非你现在的项目是个涂鸦式的草稿项目),并且在项目经历过一段很活跃的时期时, 再运行 git-repack 一下。

    当一个版本库通过 git-pushgit-pull 命令来同步源版本库中打包过的对像的时候, 通常保存到目标版本库中的是解包了的对象,除非你使用的是 rsync(远程同步协议)协议的传输方式。 正是这种容许你在两头的版本库中有不同的打包策略的方式,他意味着你也许在过一段时间之后, 需要在两头的版本库中都重新打包一下。

    发布你的工作

    我们可以通过一个远程的版本库来利用他人的工作,但是,你如何准备一个自己的版本库来供其他人下载呢? 你在自己的工作目录下进行工作,这样你的版本库就被作为.git的一个子目录放在你的工作树下。 你可以让其他人来远程的访问你的版本库,但是实际上这不是通常的做法。推荐的做法是创建一个公共的版本库, 让它可供其他人访问,并且,当你在你的工作目录下做了很好的改动时,你可以更新到公共的版本库中。这通常称为pushing。

    Note
    公共版本库是可以被映像的,kernel.org上的git公共版本库也是这样管理的。

    从你的本地的(私有的)版本库中发布改动到你的远程的(公共的)版本库中需要远程机器上的写权限。你需要一个SSH的 帐号来运行一个简单的命令,git-receive-pack。 首先,你需要在远程机器上创建一个空的版本库来存放你的公共版本库。这个空版本库以后将通过pushing来保持更新。 显然,这个版本库之需要在开始的时候创建一次。

    Note
    git push使用一对命令,git-send-pack在本地机上运行,git-receive-pack在远程机上运行。 这两个命令通过SSH连接来进行通讯。

    你本地的版本库的git目录通常是.git,但是你的公共版本库通常还要加上你的项目名,即.git。 让我们来为my-git创建这样一个版本库。首先,登入远程的机器,创建一个空目录(如果你选择HTTP作为发布方法, 这个空目录需要建在web server的根目录下面):

    $ mkdir my-git.git

    然后运行git init-db命令将这个目录加入git版本库中,这里,因为这个版本库的名字不是通常的.git, 我们需要稍微改动一下命令:

    $ GIT_DIR=my-git.git git-init-db

    有很多种传输方式可以发布公共版本库。这里,要确认这个目录可以通过你选择的传输方式来被其他人访问。你也需要确认 你有git-receive-pack这个程序在$PATH这个路径下。

    Note
    当你直接运行程序的时候,很多sshd的安装版并没有将你的shell作为登陆的shell;这就是说,如果你登陆的shell是bash 的话,被读到的是.bashrc而不是.bash_profile。确认.bashrc设置好了$PATH路径,这样你 才可以运行git-receive-pack命令。
    Note
    如果你打算通过HTTP来发布这个版本库,这是你就应该运行命令chmod +x my-git.git/hooks/post-update。这确认了每次 你导入数据到这个版本库中,git-update-server-info能够被执行。

    现在你的“公共的版本库”可以接受你的任何改动了。回到你的本地机上,运行命令:

    $ git push :/path/to/my-git.git master

    该命令将你的公共版本库和你当前的版本库中指定名称的分支头部同步(这里是master)。举一个实际的例子,你可以 这样来更新公共的git版本库。Kernel.org的镜像网络也这样来同步其他公共的可访问的机器:

    $ git push master.kernel.org:/pub/scm/git/git.git/

    将工作捆绑到一起

    通过 git 的分支功能,你可以非常容易地做到好像在同一时间进行许多“相关-或-无关”的工作一样。

    我们已经通过前面的 "fun and work" 使用两个分支的例子,看到分支是怎么工作的。 这样的思想在多于两个的分支的时候也是一样的,比方说,你现在在 master 的头, 并有些新的代码在 master 中,另外还有两个互不相关的补丁分别在 "commit-fix" 和 "diff-fix" 两个分支中。

    $ git show-branch
    ! [commit-fix] Fix commit message normalization.
    ! [diff-fix] Fix rename detection.
    * [master] Release candidate #1
    ---
    + [diff-fix] Fix rename detection.
    + [diff-fix~1] Better common substring algorithm.
    + [commit-fix] Fix commit message normalization.
    * [master] Release candidate #1
    ++* [diff-fix~2] Pretty-print messages.

    两个补丁我们都测试好了,到这里,你想将他们俩合并起来, 于是你可以先合并 diff-fix ,然后再合并 commit-fix,像这样:

    $ git merge 'Merge fix in diff-fix' master diff-fix
    $ git merge 'Merge fix in commit-fix' master commit-fix

    结果如下:

    $ git show-branch
    ! [commit-fix] Fix commit message normalization.
    ! [diff-fix] Fix rename detection.
    * [master] Merge fix in commit-fix
    ---
    - [master] Merge fix in commit-fix
    + * [commit-fix] Fix commit message normalization.
    - [master~1] Merge fix in diff-fix
    +* [diff-fix] Fix rename detection.
    +* [diff-fix~1] Better common substring algorithm.
    * [master~2] Release candidate #1
    ++* [master~3] Pretty-print messages.

    然而,当你确信你手头上的确是一堆互不相关的项目变化时,就没有任何理由将这堆东西一个个地合并( 假如他们的先后顺序很重要,那么他们就不应该被定以为无关的变化), 你可以一次性将那两个分支合并到当前的分支中,首先我们将我们刚刚做过的事情逆转一下, 我们需要通过将 master 分支重置到 master~2 位置的方法来将它逆转到合并那两个分支之前的状态。

    $ git reset --hard master~2

    你可以用 git-show-branch 来确认一下的确是回到了两次 git-merge 的状态了。 现在你可以用一行命令将那两个分支导入的方式来替代两次运行( 也就是所谓的 炮制章鱼 -- making an Octopusgit-merge

    $ git pull . commit-fix diff-fix
    $ git show-branch
    ! [commit-fix] Fix commit message normalization.
    ! [diff-fix] Fix rename detection.
    * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
    ---
    - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'
    + * [commit-fix] Fix commit message normalization.
    +* [diff-fix] Fix rename detection.
    +* [diff-fix~1] Better common substring algorithm.
    * [master~1] Release candidate #1
    ++* [master~2] Pretty-print messages.

    注意那些不适合制作章鱼的场合,尽管你可以那样做。一只“章鱼”往往可以使项目的提交历史更具可读性, 前提是你在同一时间导入的两份以上的变更是互不关联的。 然而,如果你在合并任何分支的过程中出现合并冲突,并且需要手工解决的话, 那意味着这些分支当中有相互干涉的开发工作在进行,那么你就应该将这个两个冲突先合并, 并且记录下你是如何解决这个冲突,以及你首先处理他们的理由。(译者按:处理完冲突之后, 你就可以放心制作“章鱼”了) 否则的话将会造成项目的发展历史很难跟踪。

    管理版本库

    版本库的管理员可以用下面的工具来建立和维护版本库。

    • git-daemon(1) 容许匿名下载版本库。

    • git-shell(1) 面向中心版本库模式 的用户的类似 受限的 shell 的命令。

    update hook howto 一个很好的管理中心版本库的例子。

    例子

    在 /pub/scm 上运行 git 守护进程
    $ grep git /etc/inet.conf
    git stream tcp nowait nobody \
    /usr/bin/git-daemon git-daemon --inetd --syslog --export-all /pub/scm

    这个配置行应该在配置文件中用一行来写完。

    仅给开发者 push/pull 的访问权限。
    $ grep git /etc/passwd (1)
    alice:x:1000:1000::/home/alice:/usr/bin/git-shell
    bob:x:1001:1001::/home/bob:/usr/bin/git-shell
    cindy:x:1002:1002::/home/cindy:/usr/bin/git-shell
    david:x:1003:1003::/home/david:/usr/bin/git-shell
    $ grep git /etc/shells (2)
    /usr/bin/git-shell


    (1) 将用户的登录 shell 设定为 /usr/bin/git-shell,
    它除了运行 "git-push" 和 "git-pull" 不能做任何事。
    这样用户就可以通过 ssh 来访问机器。
    (2) 许多的发行版需要在 /etc/shells 配置文件中列明要用什么 shell 来作为登录 shell。
    CVS - 模式的公共库。
    $ grep git /etc/group (1)
    git:x:9418:alice,bob,cindy,david
    $ cd /home/devo.git
    $ ls -l (2)
    lrwxrwxrwx 1 david git 17 Dec 4 22:40 HEAD -> refs/heads/master
    drwxrwsr-x 2 david git 4096 Dec 4 22:40 branches
    -rw-rw-r-- 1 david git 84 Dec 4 22:40 config
    -rw-rw-r-- 1 david git 58 Dec 4 22:40 description
    drwxrwsr-x 2 david git 4096 Dec 4 22:40 hooks
    -rw-rw-r-- 1 david git 37504 Dec 4 22:40 index
    drwxrwsr-x 2 david git 4096 Dec 4 22:40 info
    drwxrwsr-x 4 david git 4096 Dec 4 22:40 objects
    drwxrwsr-x 4 david git 4096 Nov 7 14:58 refs
    drwxrwsr-x 2 david git 4096 Dec 4 22:40 remotes
    $ ls -l hooks/update (3)
    -r-xr-xr-x 1 david git 3536 Dec 4 22:40 update
    $ cat info/allowed-users (4)
    refs/heads/master alice\|cindy
    refs/heads/doc-update bob
    refs/tags/v[0-9]* david

    (1) 将所有的开发人员都作为 git 组的成员。
    (2) 并且给予他们公共版本库的写权限。
    (3) 用一个在 Documentation/howto/ 中的 Carl 写的例子来实现版本库的分支控制策略。
    (4) Alice 和 Cindy 可以提交入 master 分支,只有 Bob 能提交入 doc-update 分支,
    David 则是发行经理只有他能创建并且 push 版本标签。
    支持默协议传输的 HTTP 服务器。
    dev$ git update-server-info (1)
    dev$ ftp user@isp.example.com (2)
    ftp> cp -r .git /home/user/myproject.git

    (1) 保证 info/refs 和 object/info/packs 是最新的。
    (2) 上传到你的 HTTP 服务器主机。

    项目开发的模式推介

    尽管 git 是一个正式项目发布系统,它却可以方便地将你的项目建立在松散的开发人员组织形式上。 Linux 内核的开发,就是按这样的模式进行的。在 Randy Dunlap 的著作中("Merge to Mainline" 第17页) 就有很好的介绍(http://tinyurl.com/a2jdg)。

    需要强调的是正真的非常规的开发组织形式, git 这种组织形式,意味着对于工作流程的约束,没有任何强迫性的原则。 你不必从唯一一个远程版本库中导入(工作目录)。

    项目领导人(project lead)的工作推介

    1. 在你自己的本地机器上准备好主版本库。你的所有工作都在这里完成。

    2. 准备一个能让大家访问的公共版本库。

      如果其他人是通过默协议的方式(http)来导入版本库的,那么你有必要保持这个 默协议的友好性git-init-db 之后,复制自标准模板库的 $GIT_DIR/hooks/post-update 将包含一个对 git-update-server-info 的调用,但是 post-update 默认是不能唤起它自身的。 通过 chmod +x post-update 命令使能它。这样让 git-update-server-info 保证那些必要的文件是最新的。

    3. 将你的主版本库推入公共版本库。

    4. git-repack 公共版本库。这将建立一个包含初始化提交对象集的打包作为项目的起始线, 可能的话,执行一下 git-prune,要是你的公共库是通过 pull 操作来从你打包过的版本库中导入的。

    5. 在你的主版本库中开展工作,这些工作可能是你自己的最项目的编辑, 可能是你由 email 收到的一个补丁,也可能是你从这个项目的“子系统负责人” 的公共库中导入的工作等等。

      你可以在任何你喜欢的时候重新打包你的这个私人的版本库。

    6. 将项目的进度推入公共库中,并给大家公布一下。

    7. 尽管一段时间以后,"git-repack" 公共库。并回到第5步继续工作。

    项目的子系统负责人(subsystem maintainer)也有自己的公共库,工作流程大致如下:

    1. 准被一个你自己的工作目录,它通过 git-clone 克隆自项目领导人的公共库。 原始的克隆地址(URL)将被保存在 .git/remotes/origin 中。

    2. 准备一个可以给大家访问的公共库,就像项目领导人所做的那样。

    3. 复制项目领导人的公共库中的打包文件到你的公共库中, 除非你的公共库和项目领导人的公共库是在同一部主机上。 以后你就可以通过 objects/info/alternates 文件的指向来 浏览它所指向的版本库了。

    4. 将你的主版本库推入你的公共版本库,并运行 git-repack, 如果你的公共库是通过的公共库是通过 pull 来导入的数据的话, 再执行一下 git-prune
    5. 在你的主版本库中开展工作。这些工作可能包括你自己的编辑,来自 email 的补丁, 从项目领导人,“下一级子项目负责人”的公共库哪里导入的工作等等。

      你可以在任何时候重新打包你的私人版本库。

    6. 将你的变更推入公共库中,并且请“项目领导人”和“下级子系统负责人”导入这些变更。

    7. 每隔一段时间之后,git-repack 公共库。回到第 5 步继续工作。

    “一般开发人员”无须自己的公共库,大致的工作方式是:

    1. 准备你的工作库,它应该用 git-clone 克隆自“项目领导人”的公共库( 如果你只是开发子项目,那么就克隆“子项目负责人”的)。 克隆的源地址(URL)会被保存到 .git/remotes/origin 中。

    2. 在你的个人版本库中的 master 分支中开展工作。

    3. 每隔一段时间,向上游的版本库运行一下 git-fetch origin 。 这样只会做 git-pull 一半的操作,即只克隆不合并。 公共版本库的新的头就会被保存到 .git/refs/heads/origins

    4. git-cherry origin 命令,看一下你有什么补丁被接纳了。 并用 git-rebase origin 命令将你以往的变更迁移到最新的上游版本库的状态中。 (关于 git-rebase 命令,请参考 git-rebase

    5. git-format-patch origin 生成 email 形式的补丁并发给上游的维护者。 回到第二步接着工作。

    Last updated 27-Mar-2006 15:20:34 UTC

    学习 Git

    作者:孔建军

    1 Git 介绍

    Git 版本控制工具的作者是Linux之父Linus Trovalds,最初是专门针对Linux内核开发的特点编写的,即协作人员异地分布、人数众多、项目规模巨大、复杂度高等。与常用的版本控制 CVS,Subversion等不同,它采用了分布式版本库的方式,不毕服务器断软件支持,使源代码的发布和维护极其方便。Git的本地查询、搜索,补丁 制作、提交和应用,项目跟踪,分支合并等功能,可以大大提高开发效率,具有较强的灵活性。有人认为Git太艰涩难懂,实际上结合一些有用的脚本命令使用, 会使其变得非常好用。
    Linux kernel、Wine、U-boot等著名项目都采用Git管理,页面(http://git.kernel.org/)列举的项目全是用git维护的,比较有趣的是Git本身也采用Git进行版本控制,详见:http://git.kernel.org/?p=git/git.git;a=summary

    2 Git 配置

    Git命令的使用,一般有两种两种形式,一种是git后面带参数(如:git add),另一种是直接减号连接的一条命令(如:git-add),后面讲解全部使用后者,这样可以避免空格的使用带来的问题。

    • $ ssh-keygen -b 1024 -t dsa 生成密钥,用户通信加解密。1024为生成密钥大小,dsa为指定的加密类型。如果接受默认设置,那么私钥和公钥文件分别位于:~/.ssh /id_dsa和~/.ssh/id_dsa.pub。用户需要向服务器管理员提供公钥(id_dsa.pub),在用户同步版本库时对用户进行身份认 证。用户必须妥善保管私钥。
    • $ git-config user.name jianjun 配置用户名,在生成补丁、日志时使用。git-config命令带--global选项是对所有用户信息进行配置,默认只针对对当前用户。
    • $ git-config user.email jianjun@zeuux.org 配置用户邮件,用于发送补丁。
    • $ git-config sendemail.smtpserver /usr/local/bin/msmtp 配置补丁邮件的发送软件,这里也可以用其他的,如sendmail。
    • $ git-config --list 用户可以通过git-config的其他选项来对git做其他配置,--list可以查看用户已经选项。如:
      $ git-config --list
      user.name=jianjun
      user.email=jianjun@zeuux.com
      sendemail.smtpserver=/usr/bin/msmtp
      diff.color=auto
      pack.window=64
      merge.summary=true

    3 Git 使用

    • 创建一个版本库
      $ mkdir myDir
      $ cd myDir
      $ git-init-db
      Initialized empty Git repository in .git/
      创建工作目录 myDir,进入工作目录,并初始化版本库。此时会在myDir/目录下生成一个名为.git的目录,里边有三个文件,分别是存放指向项目当前分支索引信息的HEAD文件、包含项目所有对象的object子目录、保存指向对象索引的refs目录。
    • 植入内容跟踪信息
      $ echo "new world" > newfile
      $ git-add newfile
      创建新文件newfile,写入"new world",并用git-add命令将此文件加入到版本库文件索引当中。
    • 提交内容到版本库
      $ git-commit -m "add newfile" newfile
      Created initial commit 5ce224d: add newfile
      1 files changed, 2 insertions(+), 0 deletions(-)
      create mode 100644 newfile
      把前面的修改提交到版本库中,前提是已经用git-add命令把此文件加入到版本库文件索引当中。
    • 查看当前的工作
      $ git-status
      # On branch master
      # Changes to be committed:
      # (use "git reset HEAD ..." to unstage)
      # new file: h
      $ echo "Love zeuux" >> newfile
      $ git-diff
      diff --git a/newfile b/newfile
      index e83176b..7d02259 100644
      --- a/newfile
      +++ b/newfile
      @@ -1 +1,2 @@
      new world
      +Love zeuux
      git-status可查看当前分支状态,git-diff查看当前分支更改情况。
    • 管理分支
      $ git-branch mybranch
      $ git-checkout mybranch
      Switched to branch "mybranch"
      $ git-branch -D mybranch
      Deleted branch mybranch.
      创建名为mybranch的分支,并把以后工作转移到这个分支上开展。git-branch带-D选项为删除指定的分支,不能删除用户所在当前分支,必须用git-checkout切换到其他分支才行。
    • 查看项目的发展变化和比较差异
      $ git-show-branch
      * master
      mybranch
      $ git-whatchanged
      commit 7182ae4912487692d4f91ded1e74d99e0fc12e49
      Author: Jianjun Kong
      Date: Tue Jul 8 23:45:27 2008 +0800
      add new line
      :000000 100644 0000000... 6991a4a... A h
      commit cc08d852365d605520d71a3352841a7eadf64428
      Author: Jianjun Kong
      Date: Tue Jul 8 23:42:52 2008 +0800
      update
      :100644 100644 7d02259... 033d3e5... M newfile
      $ git-diff mybranch
      diff --git a/h b/h
      new file mode 100644
      index 0000000..6991a4a
      --- /dev/null
      +++ b/h
      @@ -0,0 +1,2 @@
      +helo
      git-show-branch用来列出当前版本库中的所有分支,git-whatchanged可以列出项目开发中的修改历史。git-dff mybranch是来比较当前分支与mybranch分支的差异的,当然也可使用 git-diff mybranch anotherbranch对任意两个分支做对比。
    • 合并两个分支
      $ git-checkout master
      $ git-merge "Merge work in mybranch" mybranch
      切换到master分支,并把mybranch上的工作合并到master上来。此时有可能有冲突无法合并,会给出警告,用户可根据提示手动合并一些文件。
    • 逆转与恢复
      $ git-reset --soft HEAD^
      逆转上次提交的版本进度
      $ git-reset --hard 7182ae4912487692d4f91ded1e74d99e0fc12e49
      强行逆转到索引指定的版本,--hard选项要慎重使用,有事可能破坏正常文件。
      $ git-revert
      也可撤销上次对版本库的提交,但这本身也会产生一个commit,用得多了会使log看起来不那么干净。
    • 用 Git 协同工作
      $ git-clone git://repo.or.cz/xylftp.git 
      克隆远程版本库
      $ git-push git+ssh://kongjianjun@repo.or.cz/srv/git/pigeons.git master:master
      将同步本地版本库中master分支同步到远程服务器上版本库的master分支
      $ git-pull git+ssh://kongjianjun@repo.or.cz/srv/git/pigeons.git master:master
      将远程服务器上的版本库中的master分支同步到本地版本库的master分支
      $ git-fetch orign
      克隆上游版本库
      $ git-format-patch -s orign
      0001-add-new-line.patch
      对比当前分支与orign生成补丁,-s选项指定生成补丁中带有Signed-off-by: jianjun
      $ git-send-email --to zeuux-www@zeuux.org --cc wangcong@zeuux.org 0001-add-new-line.patch
      将补丁0001-add-new-line.patch发送到zeuux-www@zeuux.org,并抄送一份给wangcong@zeuux.org
    • 为版本库打包
      $ git-repack
      将对象打包,并保存在 .git/objects/pack 目录当中
      $ git-prune-packed
      清楚那些已经被打过包的原始的对象
    • 特殊文件 工作目录myDir/下有一个名为.gitignore的文件,用来排除一些文件,包括程序编译的中间文件和目标文件等,当然也可包括它自己在内。没用使用git-add添加的文件,不会被跟踪,但会在git-commit等是输出多余信息,也会使日志变得很乱。
      .gitignore
      .*
      *.o
      *.o.*
      *.a
      *.s
      *.ko
      *.so
      *.so.dbg
      *.mod.c
      *.i
      *.lst
      *.symtypes
      *.order

    4 Git的项目开发模式

    Git作为一个正式项目发布系统,它能够极其有效的组织松散的开发人员,是一种非常规的开发组织形式,对工作流程没有任何强迫性的约束,比较灵活。

    • 项目领导人 1.在自己本地机器上创建主版本库,并在此进行所有工作。
      2.准备一个能让大家访问的公共版本库。
      3.将你的主版本库推入公共版本库。
      4.git-repack 公共版本库。这将建立一个包含初始化提交对象集的打包作为项目的起始线。
      5.在你的主版本库中开展工作,包括自己的工作、收到的邮件补丁、“子系统负责人” 的公共库中导入的工作等等。
      6.将项目的进度推入公共库中,并给大家公布一下。
      7.尽管一段时间以后,"git-repack" 公共库。并回到第5步继续工作。
    • 项目的子系统负责人 1.新建一个你自己的工作目录,通过 git-clone 克隆项目领导人的公共库。
      2.准备一个可以给大家访问的公共库,就像项目领导人所做的那样。
      3.复制项目领导人的公共库中的打包文件到你的公共库中。
      4.将你的主版本库推入你的公共版本库,并运行 git-repack,如果你的公共库是通过的公共库是通过pull来导入的数据的话,再执行一下git-prune。
      5.在你的主版本库中开展工作。这些工作包括自己的工作、收到的邮件补丁、“下一级子项目负责人”的公共库中导入的工作等等。
      6.将你的变更推入公共库中,并且请“项目领导人”和“下级子系统负责人”导入这些变更。
      7.每隔一段时间之后,git-repack 公共库。回到第 5 步继续工作。
    • 一般开发人员 1.通过git-clone克隆“项目领导人”的公共库,作为自己的工作库。
      2.在你的个人版本库中的 master 分支中开展工作。
      3.每隔一段时间,向上游的版本库运行一下 git-fetch origin 。这样只会做 git-pull 一半的操作,即只克隆不合并。
      4.用 git-cherry origin 命令,看一下你有什么补丁被接纳了。并用 git-rebase origin 命令将你以往的变更迁移到最新的上游版本库的状态中。
      5.用git-format-patch origin生成email形式的补丁并发给上游的维护者。回到第二步接着工作。

    5 免费git项目注册

    网址:http://repo.or.cz
    注册用户:http://repo.or.cz/m/reguser.cgi
    注册项目:http://repo.or.cz/m/regproj.cgi
    注册用户需要提供自己的公钥,可由上面提到的ssh-keygen生成。

    0、使用git-init-db在本地创建版本库;
    1、使用git-add添加要跟踪的文件;
    2、修改,并使用git-commit提交修改到本地版本库;
    3、使用git-push命令将本地版本库同步到服务器端;
    4、其他用户可使用git-clone来克隆项目,并在本地开展自己的工作。

    6 总结

    7 参考资料

    February 26

    Collection Class Notes------especial CTypedPtrList

    Collection classes covered are Collection classes not covered are
    Arrays  

    CArray

    CTypedPtrArray

    CObArray

    CPtrArray

    CByteArray

    CDWordArray

    CStringArray

    CUintArray

    CWordArray

    Lists  

    CTypedPtrList

    CObList

    CPtrList

    CStringList
    Maps  

    CMap

    CTypedPtrMap

    CMapWordToPtr

    CMapPtrToWord

    CMapPtrToPtr

    CMapStringToPtr

    CMapWordToOb

    CMapStringToOb

    CMapStringToString

    Expand the Header Files folder, and open file StdAfx.h. At the end of the file, type:
    #include <afxtempl.h>    // MFC templates
    /////////////////////////////////////////////////////////////////////////
    CTypedPtrList < class BASE_CLASS, class TYPE >
    
        BASE_CLASS  Base class of the typed pointer list class;
                    must be a pointer list class (CObList or CPtrList).
    
        TYPE        Type of the elements stored in the base-class list.
    
        The CTypedPtrList class provides a type-safe 
        “wrapper” for objects of class
        CPtrList. When you use CTypedPtrList 
        rather than CObList or CPtrList, the
        C++ type-checking facility helps 
        eliminate errors caused by mismatched
        pointer types.
    
        In addition, the CTypedPtrList wrapper 
        performs much of the casting that
        would be required if you used CObList or CPtrList.
    
        ==================================================
        Class Members
        ==================================================
        AddHead
            POSITION AddHead( TYPE newElement );
            void AddHead( CTypedPtrList<BASE_CLASS, TYPE> *pNewList );
        AddTail
            POSITION AddTail( TYPE newElement );
            void AddTail( CTypedPtrList<BASE_CLASS, TYPE> *pNewList );
        GetAt
            TYPE& GetAt( POSITION position );
            TYPE GetAt( POSITION position ) const;
        GetHead
            TYPE& GetHead( );
            TYPE GetHead( ) const;
        GetNext
            TYPE& GetNext( POSITION& rPosition );
            TYPE GetNext( POSITION& rPosition ) const;
        GetPrev
            TYPE& GetPrev(POSITION& rPosition );
            TYPE GetPrev( POSITION& rPosition ) const;
        GetTail
            TYPE& GetTail( );
            TYPE GetTail( ) const;
        RemoveHead
            TYPE RemoveHead( );
        RemoveTail
            TYPE RemoveTail( );
        SetAt
            void SetAt( POSITION pos, TYPE newElement );
    
        ==================================================
        Create
        ==================================================
        typedef CTypedPtrList<CPtrList, CMyStruct*> CMyStructList;
        CMyStructList m_myPtrList;
    
        typedef CTypedPtrList<CObList, CMyObject*>  CMyObList;
        CMyObList m_myObList;
    
        ==================================================
        Insert
        ==================================================
        CMyStruct* pMyStruct = new CMyStruct();
        pMyStruct->m_int = 1234;
        pMyStruct->m_float = 12.34f;
        pMyStruct->m_str.LoadString(IDS_INITIAL_STRING);
        m_myPtrList.AddTail(pMyStruct);
    
        m_myPtrList.InsertBefore(pos, pMyStruct);
    
        CMyObject* pMyObject = new CMyObject();
        m_myObList.AddTail(pMyObject);
    
        ==================================================
        Iterate
        ==================================================
        POSITION pos = m_myPtrList.GetHeadPosition();
        while( pos != NULL )
        {
            CMyStruct* pMyStruct = m_myPtrList.GetNext( pos );
        }
    
        ==================================================
        Find
        ==================================================
        pos = m_myPtrList.Find(pMyStruct);
    
        ==================================================
        Update
        ==================================================
        m_myPtrList.SetAt(pos, pMyStruct);
    
        ==================================================
        Delete
        ==================================================
        m_myPtrList.RemoveAt(pos);
    
        POSITION pos = m_myPtrList.GetHeadPosition();
        while (pos != NULL)
        {
            delete m_myPtrList.GetNext(pos);
        }
        m_myPtrList.RemoveAll();
    
        while (!m_myObList.IsEmpty())
        {
            delete m_myObList.GetHead();
            m_myObList.RemoveHead();
        }
    
        ==================================================
        Serialize
        ==================================================
        nCount = (WORD)m_myPtrList.GetCount();
        if (ar.IsStoring())
        {
            ar << nCount;
            pos = m_myPtrList.GetHeadPosition();
            while (pos != NULL)
            {
                CMyStruct* pMyStruct = m_myPtrList.GetNext(pos);
                w = (WORD)pMyStruct->m_int;
                ar << w;
                ar << pMyStruct->m_float;
                ar << pMyStruct->m_str;
                nCount--;
            }
            ASSERT(nCount == 0);
        }
        else
        {
            ar >> nCount;
            while (nCount-- > 0)
            {
                CMyStruct* pMyStruct = new CMyStruct;
                ar >> w;
                pMyStruct->m_int = w;
                ar >> pMyStruct->m_float;
                ar >> pMyStruct->m_str;
                m_myPtrList.AddTail(pMyStruct);
            }
        }
    
        m_myObList.Serialize(ar);
        // Note: CMyObject serializes itself
        void CMyObject::Serialize(CArchive& ar)
        {
            CObject::Serialize( ar );
            if( ar.IsStoring() )
                ar << i;
            else
                ar >> i;
        }
    January 13

    Index.dat Analyzer v2.5

    Index.dat Analyzer is a tool to view, examine and delete contents of index.dat files.
    Index.dat files are hidden files on your computer that contain all tracks of your online activity, where have you been on internet, what sites you visited, list of URL-s, files and documents you recently accessed. Index.dat files stored on your computer are obviously a potential privacy threat as they can be found and viewed without your knowledge.

    傲游文件服务器

    您可以在这里找到傲游浏览器及相关文件的下载.

     

    Windows Sysinternals Suite Build 2009.01.12


    Sysinternals Suite是微软发布的一套非常强大的免费工具程序集.我想介绍就不用多说了吧.用好Windows Sysinternals Suite里的工具,你将更有能力处理Windows的各种问题,而且不花一毛钱.Sysinternals 之前为Winternals公司提供的免费工具,Winternals原本是一间主力产品为系统复原与资料保护的公司,为了解决工程师平常在工作上遇到的各种问题,便开发出许多小工具.之后他们将这些工具集合起来称为Sysinternals,并放在网路供人免费下载,其中也包含部分工具的原始码,一直以来都颇受IT专家社群的好评.

    The Suite is a bundling of the following selected Sysinternals Utilities:
        * AccessChk: This tool shows you the accesses the user or group you specify has to files, Registry keys or Windows services.
        * AccessEnum: This simple yet powerful security tool shows you who has what access to directories, files and Registry keys on your systems. Use it to find holes in your permissions.
        * AdExplorer: Active Directory Explorer is an advanced Active Directory (AD) viewer and editor.
        * AdInsight: An LDAP (Light-weight Directory Access Protocol) real-time monitoring tool aimed at troubleshooting Active Directory client applications.
        * AdRestore: Undelete Server 2003 Active Directory objects.
        * Autologon: Bypass password screen during logon.
        * Autoruns: See what programs are configured to startup automatically when your system boots and you login. Autoruns also shows you the full list of Registry and file locations where applications can configure auto-start settings.
        * BgInfo: This fully-configurable program automatically generates desktop backgrounds that include important information about the system including IP addresses, computer name, network adapters, and more.
        * BlueScreen: This screen saver not only accurately simulates Blue Screens, but simulated reboots as well (complete with CHKDSK), and works on Windows NT 4, Windows 2000, Windows XP, Server 2003 and Windows 9x.
        * CacheSet: CacheSet is a program that allows you to control the Cache Manager's working set size using functions provided by NT. It's compatible with all versions of NT.
        * ClockRes: View the resolution of the system clock, which is also the maximum timer resolution
        * Contig: Wish you could quickly defragment your frequently used files? Use Contig to optimize individual files, or to create new files that are contiguous.
        * Ctrl2cap: This is a kernel-mode driver that demonstrates keyboard input filtering just above the keyboard class driver in order to turn caps-locks into control keys. Filtering at this level allows conversion and hiding of keys before NT even "sees" them. Ctrl2cap also shows how to use NtDisplayString() to print messages to the initialization blue-screen.
        * DebugView: Another first from Sysinternals: This program intercepts calls made to DbgPrint by device drivers and OutputDebugString made by Win32 programs. It allows for viewing and recording of debug session output on your local machine or across the Internet without an active debugger.
        * DiskExt: Display volume disk-mappings
        * DiskView: Graphical disk sector utility
        * Diskmon: This utility captures all hard disk activity or acts like a software disk activity light in your system tray.
        * Du: View disk usage by directory
        * EFSDump: View information for encrypted files
        * Filemon: This monitoring tool lets you see all file system activity in real-time.
        * Handle: This handy command-line utility will show you what files are open by which processes, and much more.
        * Hex2dec: Convert hex numbers to decimal and vice versa.
        * Junction: Create Win2K NTFS symbolic links
        * LDMDump: Dump the contents of the Logical Disk Manager's on-disk database, which describes the partitioning of Windows 2000 Dynamic disks.
        * ListDLLs: List all the DLLs that are currently loaded, including where they are loaded and their version numbers. Version 2.0 prints the full path names of loaded modules.
        * LiveKd: Use Microsoft kernel debuggers to examine a live system.
        * LoadOrder: See the order in which devices are loaded on your WinNT/2K system
        * MoveFile: Allows you to schedule move and delete commands for the next reboot.
        * LogonSessions: List the active logon sessions on a system.
        * NewSID: Learn about the computer SID problem everybody has been talking about and get a free computer SID changer, NewSID.
        * NTFSInfo: Use NTFSInfo to see detailed information about NTFS volumes, including the size and location of the Master File Table (MFT) and MFT-zone, as well as the sizes of the NTFS meta-data files.
        * PageDefrag: Defragment your paging files and Registry hives!
        * PendMoves: Enumerate the list of file rename and delete commands that will be executed the next boot
        * Portmon: Monitor serial and parallel port activity with this advanced monitoring tool. It knows about all standard serial and parallel IOCTLs and even shows you a portion of the data being sent and received. Version 3.x has powerful new UI enhancements and advanced filtering capabilities.
        * Process Explorer: Find out what files, registry keys and other objects processes have open, which DLLs they have loaded, and more. This uniquely powerful utility will even show you who owns each process.
        * Process Monitor: Monitor file system, Registry, process, thread and DLL activity in real-time.
        * ProcFeatures: This applet reports processor and Windows support for Physical Address Extensions and No Execute buffer overflow protection.
        * PsExec: Execute processes with limited-user rights.
        * PsFile: See what files are opened remotely.
        * PsGetSid: Displays the SID of a computer or a user.
        * PsInfo: Obtain information about a system.
        * PsKill: Terminate local or remote processes.
        * PsList: Show information about processes and threads.
        * PsLoggedOn: Show users logged on to a system
        * PsLogList: Dump event log records.
        * PsPasswd: Changes account passwords.
        * PsService: View and control services.
        * PsShutdown: Shuts down and optionally reboots a computer.
        * PsSuspend: Suspend and resume processes.
        * PsTools: The PsTools suite includes command-line utilities for listing the processes running on local or remote computers, running processes remotely, rebooting computers, dumping event logs, and more.
        * RegDelNull: Scan for and delete Registry keys that contain embedded null-characters that are otherwise undeleteable by standard Registry-editing tools.
        * RegHide: Creates a key called "HKEY_LOCAL_MACHINESoftwareSysinternalsCan't touch me!0" using the Native API, and inside this key it creates a value.
        * Regjump: Jump to the registry path you specify in Regedit.
        * Regmon: This monitoring tool lets you see all Registry activity in real-time.
        * RootkitRevealer: Scan your system for rootkit-based malware
        * SDelete: Securely overwrite your sensitive files and cleanse your free space of previously deleted files using this DoD-compliant secure delete program.
        * ShareEnum: Scan file shares on your network and view their security settings to close security holes.
        * Sigcheck: Dump file version information and verify that images on your system are digitally signed.
        * Streams: Reveal NTFS alternate streams
        * Strings: Search for ANSI and UNICODE strings in binaryimages.
        * Sync: Flush cached data to disk
        * TCPView: Active socket command-line viewer.
        * VolumeId: Set Volume ID of FAT or NTFS drives
        * Whois: See who owns an Internet address.
        * Winobj: The ultimate Object Manager namespace viewer is here.
        * ZoomIt: Presentation utility for zooming and drawing on the screen.

    下载:Windows Sysinternals Suite Build 2009.01.12

    September 02

    InfoWorld2008最佳开源软件大奖


    新闻来源:CHIP中文版
    事实上,InfoWorld的年度开源软件大奖很有分量,不过遗憾的是因为没有中文版本,所以很少有国内用户关注这个奖项
    InfoWorld 2008年的"开源软件大奖"最新出炉,CHIP软件社区乘此机会将InfoWorld 2008年的"开源软件大奖"中文化并进行整理,希望能够为中国用户带来便利,也希望能够为开源社区共享绵薄之力。

    事实上,千万不要把开源软件想得那么神秘,比如在"InfoWorld2008最佳开源软件大奖"中:
    WordPress(Blog搭建)
    MediaWiki(Wiki搭建)
    VNC(远程桌面)
    Flex(Adobe的动画创建SDK)
    Puppy Linux(超小体积的Linux系统)
    Ubuntu(最受欢迎的桌面Linux系统)
    MYSQL(优秀的开源数据库)
    phpMyAdmin(MYSQL管理软件)
    VirtualBox(虚拟机软件)
    Audacity(超赞的音频编辑软件)
    Blender(3D建模软件)
    FireFox(这个还用说吗?)
    GIMP(开源的Photoshop!)
    OpenOffice(开源的办公软件)
    PDFCreator(PDF创建)
    TrueCrypt(非常好的加密软件)
    WinMerge(文件比较合并)
    这些我们耳熟能详的软件赫然在目。
    此外,还有诸如Ophcrack(Windows密码恢复)、Wireshark(网络嗅探),inSSIDer(WIFI网络扫描软件),Prototype(JS基础框架)……等软件的实用和研究价值也非常高。


    协同类软件、开发工具

    一、collaboration|协同类软件
    InfoWorld评选的协同(collaboration)类软件主要包括社会化软件、BLOG系统、日程和邮件系统管理平台构建系统等软件,共5个产品入选。
    1. elgg:社会性网络构建平台
    elgg作为一个社会性网络构建平台,一经问世,即好评如潮,更有人将其与大名鼎鼎的moodle相提并论。这个平台不仅获得了2007年最佳社会性网络开源CMS平台的荣誉,在今年得到了info world的最高评价。点击这里下载elgg

    2. MediaWiki:Wiki应用构建平台

    说MediaWiki是最佳的Wiki应用构建平台,恐怕没有人会反对吧?风靡全球的Wikipedia已经可以充分证明MediaWiki的强大。点击这里下载MediaWiki

    3. Scalix:邮件和日程协同管理
    提到开源的邮件和日程协同管理软件,不少人会想到Zimbra,不过info world 2008年选择的是Scalix。关于Scalix的更多信息,访问这里:http://www.scalix.com

    4. VNC:远程管理软件
    最佳开源远程管理软件是VNC,嗯,这个软件入选算是毫无悬念,还有一个软件是TeamViewer,也是非常优秀的作品,不过不是开源的。点击这里下载VNC

    5. WordPress:BLOG系统构建软件
    BLOG系统构建软件WordPress,这个应该没有任何疑问,WordPress几乎统治了PHP+MYSQL构建BLOG的世界,这个产品无论在品质、更新速度、插件数量、模板数量、用户数量……等很多方面都首屈一指。点击这里下载WordPress
    ----------------------------------------------------------------------------------------------
    二、developer tools|开发工具
    InfoWorld评选的开发工具包括了富Internet应用开发套件、AJAX应用开发套件、业务规则管理系统(business rule management system)、版本控制软件、Web service测试工具等,共8个产品入选。
    1. db4o:面向对象数据库引擎
    db4o是一个开源的纯面向对象数据库引擎,对于Java与.NET开发者来说都是一个简单易用的对象持久化工具,使用简单。点击这里下载db4o

    2. Git:版本控制软件
    InfoWorld 2008选择的开源版本控制软件是Git,我对这个软件真是不太了解,以前接触和听说更多的是subversion和CVS。翻了一下资料,发现 Linux Kernel、Wine、Ruby On Rails等软件用的版本控制软件就是Git,想必这个软件还是有过人之处的。点击这里下载Git

    3. HttpClient:Http协议组件包
    HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。点击这里下载HttpClient。

    4. TBB:多核CPU优化开发
    为了使并行编程更容易和更简便,英特尔发布了作为开放资源项目的英特尔Intel Threading Building Blocks2.0 (Intel TBB),作为多核开放资源应用程序的一个解决方案。点击这里下载TBB。

    5. JBoss Drools:业务规则引擎
    JBoss Drools是一款开源的业务规则引擎,它的设计目的是允许插件式的语言实现。它使用脚本方式将规则集中写在规则库文件当中,使得设计人员更容易管理。点击这里下载JBoss Drools。

    6. Flex:富Internet应用构建
    InfoWorld把"富Internet应用构建"这个奖项颁给了Adobe——准确来说是Adobe开源Flex的行为,通过开源,Flex开发者可以通过阅读研究Flex源码,更深刻的理解Flex并进一步增强它,从而在Flex平台上创造出更多更出色的应用。点击这里下载Adobe Flex 3 SDK

    7. Prototype:JS基础框架
    WOW,Prototype能够得到如此高的评价真是有点让人惊讶呢,是不是?当然,今天的Prototype已经不是一个单纯的js代码库了,它上升到了框架的高度。点击这里下载Prototype

    8. SoapUI:Web Services测试工具
    这是一款先进的Web Services测试工具,它既可以当作独立的桌面软件使用,也可以通过插件方式与Eclipse等IDE相整合。点击这里下载SoapUI

    网络应用、平台和中间件

    三、networking|网络应用
    InfoWorld评选的网络应用包括了IP电话、VOIP电话系统、日志文件分析、WIFI信号扫描等软件,共8个产品入选。
    1. Asterisk:软VOIP电话系统解决方案
    Asterisk 是一个应用于VoIP的开放源代码PBX系统(Private Branch eXchange,公司/机构用于连接专用和公用电话网络的现场数字或模拟电话交换台)它提供了呼叫转移、故障转移、IPv6支持、通话监控、通话排队、 查询以及其他多种功能。点击这里下载Asterisk。

    2. AWStats:日志分析工具
    AWStats主要通过读取IIS、Apache等服务器的日志信息,从而能够对网站、FTP服务器、邮件服务器进行各方面的信息统计和分析,并以图形化的方式展现出来,无论搭建还是使用都很方便。点击这里下载AWStats。

    3. inSSIDer:WIFI网络扫描软件
    inSSIDer 在国内的知名度远远不如NetStumbler——甚至都没有人知道inSSIDer这个软件,而NetStumbler的汉化版已经满地都是了。在 CHIP看来,NetStumbler的功能其实比inSSIDer更加强大,inSSIDer胜在界面直观,简单易用。点击这里下载inSSIDer。

    4. Nagios:系统和网络的应用监控程序
    Nagios是一个监控系统和网络的应用程序,它可以监控的信息包括:网络服务(SMTP、POP3、HTTP、NNTP、PING 等)、主机资源(处理器负载、磁盘使用情况等)、在服务或主机产生问题和修复时通知用户。点击这里下载Nagios。

    5. NDISwrapper:Linux系统使用Windows的WIFI驱动
    NDISwrapper事实上是为了解决在Linux下没有WIFI设备驱动但又想使用WIFI设备的用户准备的,它可以欺骗WIFI设备,让设备以为是在Windows环境下工作,用户从而不用再担心驱动的问题。点击这里下载NDISwrapper。

    6. Vyatta:路由器/防火墙
    Vyatta是一份完整的、即刻可用的、基于Debian的发行,它被设计为能将一套标准的x86硬件转换为企业级的路由器/防火墙。点击这里下载Vyatta。

    7. Wireshark:网络嗅探
    Ethereal 和在Windows系统中常用的sniffer pro并称网络嗅探工具双雄,不过和sniffer pro不同的是Ethereal在Linux类系统中应用更为广泛。而Wireshark软件则是Ethereal的后续版本,他是在Ethereal被 收购后推出的最新网络嗅探软件,在功能上比前身更加强大。点击这里下载Wireshark。

    8. Zenoss Core:网络监控软件
    在网络监控软件中,InfoWolrd 2008的获奖产品是Zenoss Core,这个产品最大的特色是功能丰富且简单易用——它的安装完全是普通桌面软件的向导方式,信息报告和反馈也是完全的图形化方式,直观有效。点击这里下载Zenoss Core。
    ----------------------------------------------------------------------------------------------
    四、platforms and middleware|平台和中间件
    InfoWorld评选的平台和中间件包括了操作系统、桌面虚拟化、数据库、应用整合等, 共9个产品入选。
    1. CentOS:服务器操作系统
    CentOS 是RHEL(Red Hat Enterprise Linux)源代码再编译的产物,而且在RHEL的基础上修正了不少已知的Bug,相对于其他Linux发行版,其稳定性值得信赖,今天的CentOS已 经被很多Linux用户和网络管理员认定为最好的开源服务器操作系统之一。
    CentOS的容量大概为600MB,需要的用户可以去http://centos.org/modules/tinycontent/index.php?id=15 下载,那里有非常多的镜像站点可供选择。

    2. JBossESB:SOA业务组件
    JBossESB是SOA的一个关键组件,它作为企业应用程序、业务服务、业务组件与中间件交互的一个媒介,对实现整合及业务流程自动化起重要作用。
    个人用户很少会知道这个产品,这里就略过不谈吧,想了解更多信息,请访问:http://www.jboss.org/jbossesb/

    3. MYSQL:数据库平台
    InfoWorld 在2008年选择了MYSQL而不是SQLite,这多少会令SQLite的粉丝感到遗憾,不过想想也是,想在全球有多少成功的项目是构建在MYSQL之 上的啊,光是这一点,SQLite就绝难和MYSQL抗衡,并且在被SUN收购以后,MYSQL更加成熟和壮大。点击这里下载MYSQL

    4. phpMyAdmin:最佳开源MySQL管理工具
    由于MYSQL的成功,MYSQL的管理软件也附带着"沾光",这不,MYSQL的管理软件phpMyAdmin就获得了InfoWorld 2008最佳开源MySQL管理工具的荣誉。点击这里下载phpMyAdmin

    5. Puppy Linux:小操作系统
    InfoWorld把2008年的Small-footprint OS操作系统颁给了Puppy Linux,也许有人会说Damn Smal Linux更加小巧和值得推荐。不过Puppy Linux的定制版更多,系统中集成的应用相对更加丰富。点击这里下载Puppy Linux

    6. Jitterbit:数据整合解决方案
    用户可以使用Jitterbit来集成不同的应用、不同的数据库以及不同的数据源,它支持的数据类型、应用非常广泛。点击这里下载Jitterbit。

    7. Ubuntu:开源桌面系统
    最佳开源桌面操作系统的奖项颁布给了Ubuntu,这个毫无悬念,只看好多国际PC厂商都开始将Ubuntu系统作为标配的操作系统就可以想见这个操作系统有多么受欢迎。点击这里下载Ubuntu

    8. VirtualBox:虚拟桌面
    VirtualBox是一款虚拟机软件,它功能丰富,性能也不错,现在被SUN收购并成了开源产品,相信日后会走得更远。点击这里下载VirtualBox

    9. Xen:服务器虚拟机
    说完了桌面虚拟机,轮到服务器虚拟机,InfoWorld选择的是Xen,Xen是一种著名的开放源代码的虚拟化技术,它基于Linux平台。点击这里下载Xen。

    应用软件、安全软件、存储软件

    五、productivity apps|应用软件
    InfoWorld评选的应用软件包括Office套装、网络浏览、图像编辑、音频编辑、3D建模工具等,共6个产品入选。
    1. Audacity:音频编辑软件
    Audacity当选最佳音频编辑软件,相信地球人都不会反对,这个软件功能强大到不亚于专业软件的地步、支持多种语言界面、并且还是开源的,不选它选谁呢?点击这里下载Audacity

    2. Blender:3D建模
    开源的3-D建模产品,Blender的大名的确是如雷贯耳,相信很多人都会很高兴这个产品入选了。点击这里下载Blender

    3. FireFox:网络浏览
    在这么多与IE相抗衡的浏览器中,FF是到目前为止最成功的一个,另外一个产品是Opera,不过后者不是开源的,FF入选自然是首当其冲。点击这里下载FireFox

    4. GIMP:图像编辑
    GIMP是被誉为Linux下的PhotoShop的好软件,当然,这个软件也有Windows版本,是个相当不错的产品。点击这里下载GIMP。

    5. OpenOffice:办公软件套装
    办公软件套装,应该说OpenOffice是惟一的也是最好的选择,舍它之外,还有什么呢?点击这里下载OpenOffice

    6. PDFCreator:PDF创建软件
    除了PDF文件创建之外,PDFCreator还支持给文件加密等功能,此外,它还能将所有可打印文档输出为PNG, JPG, TIFF, BMP, PCX, PS, EPS等多种文件格式。点击这里下载PDFCreator
    ----------------------------------------------------------------------------------------------
    六、security|安全
    InfoWorld评选的应用产品包括应用软件安全、系统安全、防火墙、密码相关软件等,共有8个软件入选。
    1. AppArmor:应用软件安全
    应用程序安全的入选产品是来自Novell的AppArmor,国内搞安全和Linux研究的用户想必对这个软件并不陌生——SUSE Linux中就包含了AppArmor这个重要的安全组件。了解关于AppArmor的更多信息,请访问:http://forge.novell.com/modules/xfmod/project/?apparmor 。下载AppArmor,请访问:http://forge.novell.com/modules/xfcontent/downloads.php/apparmor/AppArmor-2.1.2/

    2. Metasploit:渗透测试、漏洞研究
    Metasploit Framework (MSF)是2003年以开放源代码方式发布、可自由获取的开发框架,这个环境为渗透测试、shellcode 编写和漏洞研究提供了一个可靠的平台。点击这里下载Metasploit。

    3. Ophcrack:Windows密码恢复
    Ophcrack不仅有Linux版本,还有Windows版本哦,这样的软件并不常见。点击这里下载Ophcrack。

    4. SmoothWall Express:防火墙软件
    一个开放源码、并基于GNU/Linux操作系统的防火墙软件。点击这里下载SmoothWall Express。

    5. Snort with Base:入侵检测
    获得InfoWorld推荐的是带有BASE(Basic Analysis and Security Engine,http://base.secureideas.net/index.php )的Snort,
    Snort是一个免费的、跨平台的软件包,用作嗅探器、日志记录和入侵探测器。点击这里下载Snort。

    6. Splunk:安全日志分析
    一个运行于 Unix 环境下的日志分析软件,Splunk可以支持任何服务器产生的日志,其对日志进行处理的方式是进行高效索引之后让管理员可以对日志中出现的各种情况进行搜索,并且通过非常好的图形化的方式展现出来。点击这里下载Splunk。

    7. TrueCrypt:磁盘加密软件
    这个软件当然超赞,CHIP软件社区也推荐过无数次,无数软件高手也极其推崇这个软件。不过InfoWorld再次把这样的桌面级软件和服务器级别的专业领域软件混在一起,真是让人有点摸不着头脑啊:)。点击这里下载TrueCrypt

    8. Untangle:网关安全
    Untangle是一系列安全产品的打包合集,它包括了病毒、恶意软件、钓鱼软件的检测和查杀,包括了ClamAV, Snort, SpamAssassin, OpenVPN, iptables等开源的安全工具。了解Untangle的更多相关信息,请访问:http://www.untangle.com/ 。如果需要下载Untangle的话,请访问:http://www.untangle.com/index.php?option=com_content&task=view&id=226&Itemid=739
    ----------------------------------------------------------------------------------------------
    七、存储软件
    InfoWorld评选的存储软件包括数据备份、存储服务器、在线数据备份、文件管理等软件,共有6个软件入选。
    1. Amanda:网络备份软件
    InfoWorld选择的2008最佳开源网络备份软件是Amanda,这个软件分为客户端和服务器端,支持Linux以及Windows操作系统。点击这里下载Amanda。

    2. FreeNAS:存储服务器
    FreeNAS 是一套免费的NAS服务器,它能将一部普通PC变成网络存储服务器。该软件基于FreeBSD,Samba 及PHP,支持CIFS (samba), FTP, NFS protocols, Software RAID (0,1,5) 及 web 界面的设定工具。点击这里下载FreeNAS。

    3. Free Online Backup:在线备份
    这个Free Online Backup说实话很奇怪,它竟然只有Windows的版本,并且只支持命令行工作模式。点击这里下载Free Online Backup。

    4. WinMerge:文件比较
    WinMerge可以快速清楚地让你找出文字文件中的不同之处,对于经常修改代码或文章的网友会十分有用。点击这里下载WinMerge

    5. smartmontools:磁盘监控
    smartmontools包含两个应用(都是命令行方式的):smartctl和smartd,分别用来监控硬盘的SMART状态(smartd)或者对SMART设置进行调整(smartctl)。点击这里下载smartmontools。

    6. StorageIM:存储管理系统
    运行StorageIM的客户端可以自动发现满足CIM和SMI-S管理标准的系统并报告这些系统的存储状况。StorageIM后台采用的是MYSQL数据库,在安装StorageIM的时候,这个MYSQL也会被安装到系统当中。点击这里下载StorageIM。

    August 22

    将 Windows IPC 应用移植到 Linux,第1 部分: 进程和线程

    级别: 初级

    Srinivasan S. Muthuswamy (smuthusw@in.ibm.com), 软件工程师, IBM Global Services Group
    Kavitha Varadarajan (vkavitha@in.ibm.com), 软件工程师, IBM India Software Lab

    2005 年 5 月 08 日

    随着开发者将原本普遍的 Windows® 应用迁移到 Linux™ 平台,正在进行的向开源迁移的浪潮有可能引发极大的移植问题。这个由三部分构成的系列文章提供一个映射指南,并附有例子,能够简化从 Windows 到 Linux 的转变。第 1 部分介绍了进程和线程。

    当前,很多全球商务和服务都正在趋于开源 —— 业界的所有主要参与者都在争取实现此目标。这一趋势催生了一个重要的迁移模式:为不同平台(Windows、OS2、Solaris 等)维持的现有产品将被移植到开放源代码的 Linux 平台。

    很多应用程序在设计时并未考虑到需要将它们移植到 Linux。这有可能使移植成为一件痛苦的事情,但并非绝对如此。本系列文章的目的是,帮助您将涉及到 IPC 和线程原语的复杂应用程序从 Windows 迁移到 Linux。我们与您分享迁移这些关键应用程序的经验,包括要求线程同步的多线程应用程序以及要求进程间同步的多进程应用程序。

    简言之,可以将此系列文章看作是一个映射文档 —— 它提供了与线程、进程和进程间通信元素(互斥体、信号量等等)相关的各种 Windows 调用到 Linux 调用的映射。我们将那些映射分为三个部分:

    • 第 1 部分涉及的是进程和线程。
    • 第 2 部分处理的是信号量与事件。
    • 第 3 部分涵盖了信号量、关键区域和等待函数。

    进程

    Windows 中和 Linux 中的基本执行单位是不同的。在 Windows 中,线程是基本执行单位,进程是一个容纳线程的容器。

    在 Linux 中,基本执行单位是进程。Windows API 所提供的功能可以直接映射到 Linux 系统调用:

    表 1. 进程映射

    Windows Linux 类别
    CreateProcess()
    CreateProcessAsUser()
    fork()
    setuid()
    exec()
    可映射
    TerminateProcess() kill() 可映射
    SetThreadpriority()
    GetThreadPriority()
    Setpriority()
    getPriority()
    可映射
    GetCurrentProcessID() getpid() 可映射
    Exitprocess() exit() 可映射
    Waitforsingleobject()
    Waitformultipleobject()
    GetExitCodeProcess()
    waitpid()
    Using Sys V semaphores, Waitforsingleobject/multipleobject
    不能实现
    与上下文相关
    GetEnvironmentVariable
    SetEnvironmentVariable
    getenv()
    setenv()
    可映射

    "类别"一列(解释了本文中所使用的分类结构)表明了 Windows 结构是否 可映射 或者 与上下文相关

    • 如果可映射,则 Windows 结构可以映射到特定的 Linux 结构(需要仔细检查类型、参数、返回代码等)。Windows 和 Linux 结构都提供了类似的功能。
    • 如果是与上下文相关,则 Linux 中可能有(也可能没有)相应于给定的 Windows 结构的结构,或者 Linux 可能有不只一个提供类似功能的结构。无论是哪种情况,都要根据应用程序上下文才能确定要使用哪个特定的 Linux 结构。

    创建进程

    在 Windows 中,您可以使用 CreateProcess() 来创建一个新的进程。 CreateProcess() 函数创建一个新的进程及其主线程,如下:

    BOOL CreateProcess(
     LPCTSTR lpApplicationName,                  // name of executable module
      LPTSTR lpCommandLine,                      // command line string
      LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
      LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
      BOOL bInheritHandles,                      // handle inheritance option
      DWORD dwCreationFlags,                     // creation flags
      LPVOID lpEnvironment,                      // new environment block
      LPCTSTR lpCurrentDirectory,                // current directory name
      LPSTARTUPINFO lpStartupInfo,               // startup information
      LPPROCESS_INFORMATION lpProcessInformation // process information
    )
    

    bInheritHandles 确定了子进程是否要继承父进程的句柄。lpApplicationNamelpCommandLine 给出了将要被启动的进程的名称与路径。lpEnvironment 定义了进程可使用的环境变量。

    在 Linux 中,exec* 家族函数使用一个新的进程映像取代当前进程映像(如下所示):

    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int  execle(const  char  *path,  const  char  *arg  , ..., char * const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    

    exec* 的这些版本只是内核函数 execve()int execve(const char *filename, char *const argv [], char *const envp[]))的各种调用接口。在这里,argv 是包含有参数 list 的指针,envp 是包含有环境变量列表(主要是 key=value 对)的指针。

    它必须与 fork() 命令一起使用,所以父进程和子进程都在运行: pid_t fork(void)fork() 会创建一个子进程,与父进程相比只是 PID 和 PPID 不同;实际上,资源利用设为 0。

    默认情况下,exec() 继承父进程的组和用户 ID,这就使得它会依赖于父进程。可以使用以下方法来改变:

    • 设置指定程序文件的 set-uidset-gid
    • 使用 setpgid()setuid() 系统调用

    CreateProcessAsUser() 函数与 CreateProcess() 类似,只是新进程是在用户通过 hToken 参数描述的安全上下文中运行。在 Linux 中,没有与此函数惟一对应的函数,但是可以使用下面的逻辑来实现对它的复制:

    • 使用 fork() 创建一个具有新的 PID 的子进程
    • 使用 setuid() 切换到那个新的 PID
    • 使用 exec() 将现有进程改变为将要执行的进程

    终止进程

    在 Windows 中,您可以使用 TerminateProcess() 强制终止一个运行中的进程。

    BOOL TerminateProcess(
      HANDLE hProcess, // handle to the process
      UINT uExitCode   // exit code for the process
    );
    

    这个函数终止运行中的进程及其相关线程。只是在非常极端的场合才会使用这个函数。

    在 Linux 中,您可以使用 kill() 来强行杀死一个进程: int kill(pid_t pid, int sig)。这个系统调用会终止 id 为 PID 的进程。您也可以使用它向任何组或者进程发出信号。

    使用等待函数

    在子进程依赖于父进程的情况下,您可以在父进程中使用等待函数来等待子进程的终止。在 Windows 中,您可以使用 WaitForSingleObject() 函数调用来实现此功能。

    您可以使用 WaitForMultipleObject() 函数来等待多个对象。

    DWORD WaitForMultipleObjects(
      DWORD nCount,             // number of handles in array
      CONST HANDLE *lpHandles,  // object-handle array
      BOOL bWaitAll,            // wait option
      DWORD dwMilliseconds      // time-out interval
    );
    

    您可以向对象句柄数组(object-handle array)中填充很多需要等待的对象。根据 bWaitALL 选项,您既可以等待所有对象被信号通知,也可以等待其中任意一个被信号通知。

    在这两个函数中,如果您想等待有限的一段时间,则可以在第二个参数中指定时间间隔。如果您想无限制等待,那么使用 INFINITE 作为 dwMilliseconds 的值。将 dwMilliseconds 设置为 0 则只是检测对象的状态并返回。

    在 Linux 中,如果您希望无限期等待进程被杀死,则可以使用 waitpid()。在 Linux 中,使用 waitpid() 调用无法等待限定的时间。

    在这段代码中:pid_t waitpid(pid_t pid, int *status, int options)waitpid() 会无限期等待子进程的终止。在 Windows 和 Linux 中,等待函数会挂起当前进程的执行,直到它完成等待,不过,在 Windows 中可以选择在指定的时间后退出。使用 System V 信号量,您可以实现类似于 WaitForSingleObject()WaitForMultipleObject() 的限时等待或者 NO WAIT 功能,在本系列的第 2 部分中将讨论此内容。本系列的第 3 部分将深入讨论等待函数。

    退出进程

    退出进程指的是优雅(graceful)地退出进程,并完成适当的清除工作。在 Windows 中,您可以使用 ExitProcess() 来执行此操作。

    VOID ExitProcess(
      UINT uExitCode   // exit code for all threads
    );
    

    ExitProcess() 是在进程结束处执行的方法。这个函数能够干净地停止进程。包括调用所有链接到的动态链接库(DLL)的入口点函数,给出一个值,指出这个进程正在解除那个 DLL 的链接。

    Linux 中与 ExitProcess() 相对应的是 exit()void exit(int status);

    exit() 函数会令程序正常终止,并将 &0377 状态值返回给父进程。 C 语言标准规定了两个定义(EXIT_SUCCESSEXIT_FAILURE),可以被传递到状态参数,以说明终止成功或者不成功。

    环境变量

    每个进程都拥有关联到它的一组环境,其中主要是 name=value 对,指明进程可以访问的各种环境变量。尽管我们可以在创建进程时指定环境,不过也有特定函数可以在进程创建后设置和获得环境变量。

    在 Windows 中,您可以使用 GetEnvironmentVariable()SetEnvironmentVariable() 来获得和设置环境变量。

    DWORD GetEnvironmentVariable(
      LPCTSTR lpName,  // environment variable name
      LPTSTR lpBuffer, // buffer for variable value
      DWORD nSize      // size of buffer
    );
    

    如果成功,则此函数返回值缓存的大小,如果指定的名称并不是一个合法的环境变量名,则返回 0。 SetEnvironmentVariable() 函数为当前进程设置指定的环境变量的内容。

    BOOL SetEnvironmentVariable(
      LPCTSTR lpName,  // environment variable name
      LPCTSTR lpValue  // new value for variable
    );
    

    如果函数成功,则返回值非零。如果函数失败,则返回值为零。

    在 Linux 中,getenv()setenv() 系统调用提供了相应的功能。

    char *getenv(const char *name);
    int setenv(const char *name, const char *value, int overwrite);
    

    getenv() 函数会在环境列表中搜索与名称字符串相匹配的字符串。这个函数会返回一个指向环境中的值的指针,或者如果不匹配则返回 NULL。setenv() 函数将变量名和值添加到环境中,如果那个名称并不存在。如果环境中已经存在那个名称,而且如果 overwrite 非零,则它的值会被修改为 value。如果 overwrite 为零,则 name 的值不会被改变。如果成功,则 setenv() 会返回零,如果环境中空间不足,则返回 -1。

    例子

    下面的例子解释了我们在本节中讨论的内容。


    清单 1. Windows 进程代码

    				
    //Sample Application that explain process concepts
    //Parameters Declaration/Definition
    int TimetoWait;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    LPTSTR lpszCurrValue,LPTSTR lpszVariable;
    TCHAR tchBuf[BUFSIZE];
    BOOL fSuccess;
    if(argc > 2)
    {
        printf("InvalidArgument");
        ExitProcess(1); //Failure
    }
    //Get and display an  environment variable PATH
    lpszCurrValue = ((GetEnvironmentVariable("PATH",tchBuf, BUFSIZE) > 0) ? tchBuf : NULL);
    lpszVariable = lpszCurrValue;
    //Display the environment variable
    while (*lpszVariable)
        putchar(*lpszVariable++);
    putchar('\n');
    //Initialise si and pi
    ZeroMemory( &si, sizeof(si) );
    si.cb = sizeof(si);
    ZeroMemory( &pi, sizeof(pi) );
    //Create a childProcess
    if( !CreateProcess( NULL,             // No module name (use command line).
                        "SomeProcess",    // Command line.
                        NULL,             // Process handle not inheritable.
                        NULL,             // Thread handle not inheritable.
                        FALSE,            // Set handle inheritance to FALSE.
                        0,                // No creation flags.
                        NULL,             // Use parent's environment block.
                        NULL,             // Use parent's starting directory.
                        &si,              // Pointer to STARTUPINFO structure.
                        &pi )             // Pointer to PROCESS_INFORMATION structure.
                            )
    {
        printf( "CreateProcess failed." );
    }
    // Wait until child process exits.
    if(argc == 2)
    {
        TIMEOUT = atoi(argv[1]);
        ret = WaitForSingleObject( pi.hProcess, TIMEOUT );
        if(ret == WAIT_TIMEOUT)
        {
            TerminateProcess(pi.hProcess);
        }
    }
    else
    {
        WaitForSingleObject( pi.hProcess, INFINITE );
        ...
    }
        ExitProcess(0); //Success
    


    清单 2. 相应的 Linux 进程代码

    				
    #include <stdlib.h>
    int main(int argc,char *argv[])
    {
        //Parameters Declaration/Definition
        char PathName[255];
        char *Argptr[20];
        int rc;
        char *EnvValue,*lpszVariable;
        if(argc > 1)
        {
            printf(" Wrong parameters !!");
            exit(EXIT_FAILURE);
        }
        //Get and display an  environment variable PATH
        EnvValue = getenv("PATH");
        if(EnvValue == NULL)
        {
            printf("Invalid environment variable passed as param !!");
        }else
        {
            lpszVariable = EnvValue;
            while (*lpszVariable)
                putchar(*lpszVariable++);
            putchar('\n');
        }
        rc = fork(); //variable rc's value on success would be process ID in the parent
                     //process, and 0 in the child's thread of execution.
        switch(rc)
        {
            case -1:
                printf("Fork() function failed !!");
                ret = -1;
                break;
            case 0:
                printf("Child process...");
                setpgid(0,0);  //Change the parent grp ID to 0
            ret = execv(PathName,Argptr); // there are other flavours of exec available,
                                          // u can use any of them based on the arguments.
            if(ret == -1)
            {
                kill(getpid(),0);
            }
            break;
             default:
                 // infinitely waits for child process to die
                 Waitpid(rc,&status,WNOHANG);
                 //Note RC will have PID returned since this is parent process.
                 break;
        }
        exit(EXIT_SUCCESS);
    }
    





    回页首


    线程

    在 Windows 中,线程是基本的执行单位。在进程的上下文中会有一个或多个线程在运行。调度代码在内核中实现。没有单独的"调度器(scheduler)"模块或例程。

    Linux 内核使用的是进程模型,而不是线程模型。Linux 内核提供了一个轻量级进程框架来创建线程;实际的线程在用户空间中实现。在 Linux 中有多种可用的线程库(LinuxThreads、NGPT、NPTL 等等)。本文中的资料基于 LinuxThreads 库,不过这里的资料也适用于 Red Hat 的 Native POSIX Threading Library(NPTL)。

    本节描述 Windows 和 Linux 中的线程。内容涵盖了创建线程、设置其属性以及修改其优先级。

    表 2. 线程映射

    Windows Linux 类别
    CreateThread pthread_create
    pthread_attr_init
    pthread_attr_setstacksize
    pthread_attr_destroy
    可映射
    ThreadExit pthread_exit 可映射
    WaitForSingleObject pthread_join
    pthread_attr_setdetachstate
    pthread_detach
    可映射
    SetPriorityClass
    SetThreadPriority
    setpriority
    sched_setscheduler
    sched_setparam

    pthread_setschedparam
    pthread_setschedpolicy
    pthread_attr_setschedparam
    pthread_attr_setschedpolicy
    与上下文相关

    创建线程

    在 Windows 中,您可以使用 CreateThread() 来创建线程,创建的线程在调用进程的虚拟地址空间中运行。

    HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES lpThreadAttributes,     // SD
      SIZE_T dwStackSize,                           // initial stack size
      LPTHREAD_START_ROUTINE lpStartAddress,        // thread function
      LPVOID lpParameter,                           // thread argument
      DWORD dwCreationFlags,                        // creation option
      LPDWORD lpThreadId                            // thread identifier
    );
    

    lpThreadAttributes 是指向线程属性的指针,决定了线程句柄是否能由子进程继承。

    Linux 使用 pthread 库调用 pthread_create() 来派生线程:

    int pthread_create (pthread_t *thread_id, pthread_attr_t *threadAttr,
                        void * (*start_address)(void *), void * arg);
    

    注意:在 Windows 中,受可用虚拟内存的限制,一个进程可以创建的线程数目是有限的。默认情况下,每个线程有一兆栈空间。因此,您最多可以创建 2,028 个线程。如果您减小默认栈大小,那么可以创建更多线程。在 Linux 中,使用 ULIMIT -a(limits for all users)可以获得每个用户可以创建的线程的最大数目,可以使用 ULIMIT -u 来修改它,不过只有在登录时才可以这样做。 /usr/Include/limit.h 和 ulimit.h 下的头文件定义了这些内容。您可以修改它们并重新编译内核,以使其永久生效。对于 POSIX 线程限制而言,local_lim.h 中定义的 THREAD_THREADS_MAX 宏定义了数目的上限。

    指定线程函数

    CreateThread() 中的 lpStartAddress 参数是刚创建的线程要执行的函数的地址。

    pthread_create() 库调用的 start_address 参数是刚创建的线程要执行的函数的地址。

    传递给线程函数的参数

    在 Windows 中,系统调用 CreateThread() 的参数 lpParameter 指定了要传递给刚创建的线程的参数。它指明了将要传递给新线程的数据条目的地址。

    在 Linux 中,库调用 pthread_create() 的参数 arg 指定了将要传递给新线程的参数。

    设置栈大小

    在 Windows 中,CreateThread() 的参数 dwStackSize 是将要分配给新线程的以字节为单位的栈大小。栈大小应该是 4 KB 的非零整数倍,最小为 8 KB。

    在 Linux 中,栈大小在线程属性对象中设置;也就是说,将类型为 pthread_attr_t 的参数 threadAttr 传递给库调用 pthread_create()。在设置任何属性之前,需要通过调用 pthread_attr_init() 来初始化这个对象。使用调用 pthread_attr_destroy() 来销毁属性对象:

    int pthread_attr_init(pthread_attr_t *threadAttr);
    int pthread_attr_destroy(pthread_attr_t *threadAttr);
    

    注意,所有 pthread_attr_setxxxx 调用都有与 pthread_xxxx 调用(如果有)类似的功能,只是您只能在线程创建之前使用 pthread_attr_xxxx,来更新将要作为参数传递给 pthread_create 的属性对象。同时,您在创建线程之后的任意时候都可以使用 pthread_xxxx

    使用调用 pthread_attr_setstacksize() 来设置栈大小: int pthread_attr_setstacksize(pthread_attr_t *threadAttr, int stack_size);

    退出线程

    在 Windows 中,系统调用 ExitThread() 会终止线程。 dwExitCode 是线程的返回值,另一个线程通过调用 GetExitCodeThread() 就可以得到它。

    VOID ExitThread(
      DWORD dwExitCode   // exit code for this thread
    );
    

    Linux 中与此相对应的是库调用 pthread_exit()retval 是线程的返回值,可以在另一个线程中通过调用 pthread_join() 来获得它: int pthread_exit(void* retval);

    线程状态

    在 Windows 中,没有保持关于线程终止的显式线程状态。不过,WaitForSingleObject() 让线程能够显式地等待进程中某个指定的或者非指定的线程终止。

    在 Linux 中,默认以可连接(joinable)的状态创建线程。在可连接状态中,另一个线程可以同步这个线程的终止,使用函数 pthread_join() 来重新获得其终止代码。可连接的线程只有在被连接后才释放线程资源。

    Windows 使用 WaitForSingleObject() 来等待某个线程终止:

    DWORD WaitForSingleObject(
      HANDLE hHandle,
      DWORD dwMilliseconds
    );
    

    其中:

    • hHandle 是指向线程句柄的指针。
    • dwMilliseconds 是以毫秒为单位的超时值。如果这个值被设置为 INFINITE,则它会无限期地阻塞进行调用的线程/进程。

    Linux 使用 pthread_join() 来完成同样的功能: int pthread_join(pthread_t *thread, void **thread_return);

    在分离的状态中,线程终止后线程资源会立即被释放。通过对线程属性对象调用 pthread_attr_setdetachstate() 可以设置分离状态: int pthread_attr_setdetachstate (pthread_attr_t *attr, int detachstate);。以可连接状态创建的线程,稍后可以被转为分离状态,方法是使用 pthread_detach() 调用:int pthread_detach (pthread_t id);

    改变优先级

    在 Windows 中,线程的优先级由其进程的优先级等级以及进程优先级等级中的线程优先级层次决定。在 Linux 中,线程本身就是一个执行单位,有其自己的优先级。它与其进程的优先级没有依赖关系。

    在 Windows 中,您可以使用 SetPriorityClass() 来设置特定进程的优先级等级:

    BOOL SetPriorityClass(
      HANDLE hProcess,         // handle to the process
      DWORD dwPriorityClass    // Priority class
    );
    

    dwPriorityClass 是进程的优先级等级,它可以设置为下列值中的任意一个:

    • IDLE_PRIORITY_CLASS
    • BELOW_NORMAL_PRIORITY_CLASS
    • NORMAL_PRIORITY_CLASS
    • ABOVE_NORMAL_PRIORITY_CLASS
    • HIGH_PRIORITY_CLASS
    • REALTIME_PRIORITY_CLASS

    一旦设置了进程的优先级等级,就可以使用 SetThreadPriority() 在进程的优先级等级内部设置线程的优先级层次:

    BOOL SetThreadPriority(
      HANDLE hThread,
      int nPriority
    );
    

    nPriority 是线程的优先级值,它被设置为下列之一;

    • THREAD_PRIORITY_ABOVE_NORMAL 将优先级设置为比优先级等级高 1 级。
    • THREAD_PRIORITY_BELOW_NORMAL 将优先级设置为比优先级等级低 1 级。
    • THREAD_PRIORITY_HIGHEST 将优先级设置为比优先级等级高 2 级。
    • THREAD_PRIORITY_IDLEIDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSHIGH_PRIORITY_CLASS 进程将基优先级设置 1,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 16。
    • THREAD_PRIORITY_LOWEST 将优先级设置为比优先级等级低 2 级。
    • THREAD_PRIORITY_NORMAL 为优先级等级设置为普通优先级。
    • THREAD_PRIORITY_TIME_CRITICALIDLE_PRIORITY_CLASSBELOW_NORMAL_PRIORITY_CLASSNORMAL_PRIORITY_CLASSABOVE_NORMAL_PRIORITY_CLASSHIGH_PRIORITY_CLASS 进程将基优先级设置 15,为 REALTIME_PRIORITY_CLASS 进程将基优先级设置为 31。





    回页首


    进程和线程的例子

    为了结束这一期文章,让我们来看下面类型的进程和线程的一些例子:

    • 普通的或者常规的进程和线程
    • 对时间要求严格的(time-critical)或者实时的进程和线程

    普通的或常规的进程/线程

    使用 Linux 系统调用 setpriority() 来设置或者修改普通进程和线程的优先级层次。参数的范围是 PRIO_PROCESS。将 id 设置为 0 来修改当前进程(或线程)的优先级。此外,delta 是优先级的值 —— 这一次是从 -20 到 20。另外,要注意在 Linux 中较低的 delta 值代表较高的优先级。所以,使用 +20 设置 IDLETIME 优先级,使用 0 设置 REGULAR 优先级。

    在 Windows 中,常规线程的优先级的范围是从 1(较低的优先级)到 15(较高的优先级)。不过,在 Linux 中,普通非实时进程的优先级范围是从 -20(较高的)到 +20(较低的)。在使用之前必须对此进行映射: int setpriority(int scope, int id, int delta);

    对时间要求严格的和实时的进程和线程

    您可以使用 Linux 系统调用 sched_setscheduler() 来修改正在运行的进程的调度优先级: int sched_setscheduler(pit_t pid, int policy, const struct sched_param *param);

    参数 policy 是调度策略。policy 的可能的值是 SCHED_OTHER (常规的非实时调度)、SCHED_RR(实时 round-robin 策略)和 SCHED_FIFO(实时 FIFO 策略)。

    在此,param 是指向描述调度优先级结构体的指针。它的范围是 1 到 99,只用于实时策略。对于其他的(普通的非实时进程),它为零。

    在 Linux 中,作为一个大家所熟知的调度策略,也可以通过使用系统调用 sched_setparam 来仅修改进程优先级: int sched_setparam(pit_t pid, const struct sched_param *param);

    LinuxThreads 库调用 pthread_setschedparamsched_setscheduler 的线程版本,用于动态修改运行着的线程的调度优先级和策略: int pthread_setschedparam(pthread_t target_thread, int policy, const struct sched_param *param);

    参数 target_thread 告知线程要修改谁的优先级;param 指定了优先级。

    LinuxThreads 库会调用 pthread_attr_setschedpolicy,并且您可以在线程被创建之前使用 pthread_attr_setschedparam 来设置线程属性对象的调度策略和优先级层次:

    int pthread_attr_setschedpolicy(pthread attr_t *threadAttr, int policy);
    int pthread_attr_setschedparam(pthread attr_t *threadAttr, const struct sched_param *param);
    

    在 Windows 中,实时线程的优先级范围是从 16(较低的优先级)到 31(较高的优先级)。在 Linux 中,实时线程的优先级范围是从 99(较高的)到 1(较低的优先级)。在使用前必须对此进行映射。

    例子

    下面的清单阐述了本节中的概念。


    清单 3. Windows 线程示例

    				
    				Main Thread
    enum stackSize = 120 * 1024 ;
    // create a thread normal and real time thread
    DWORD  normalTId, realTID;
    HANDLE normalTHandle, realTHandle;
    normalTHandle = CreateThread(
                    NULL,            // default security attributes
                    stackSize,       // 120K
                    NormalThread,    // thread function
                    NULL,            // argument to thread function
                    0,               // use default creation flags
                    &normalTId);     // returns the thread identifier
    // Set the priority class as "High priority"
    SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
    normalTHandle = CreateThread(
                     NULL,            // default security attributes
                     stackSize,       // 120K
                     NormalThread,    // thread function
                     NULL,            // argument to thread function
                     0,               // use default creation flags
                    &normalTId);     // returns the thread identifier
    CloseHandle(threadHandle);
    ...
    ...
    // Thread function
    DWORD WINAPI NormalThread ( LPVOID lpParam )
    {
        HANDLE tHandle,pHandle;
        pHandle = GetCurrentProcess();
        tHandle = GetCurrentThread();
        // Set the priority class as "High priority"
        SetPriorityClass(pHandle, HIGH_PRIORITY_CLASS);
        // increase the priority by 2 points above the priority class
        SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
        // perform job at high priority
        ...
        ...
        ...
        // Reset the priority class as "Normal"
       SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
        // set the priority back to normal
        SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
        // Exit thread
        ExitThread(0);
    }
    // Thread function
    DWORD WINAPI RealTimeThread ( LPVOID lpParam )
    {
        HANDLE tHandle, pHandle ;
        pHandle = GetCurrentProcess();
        tHandle = GetCurrentThread  ();
        // Set the priority class as "Real time"
       SetPriorityClass(pHandle, REALTIME_PRIORITY_CLASS);
        // increase the priority by 2 points above the priority class
        SetThreadPriority(tHandle,THREAD_PRIORITY_HIGHEST);
        // do time critical work
        ...
        ...
        ...
        // Reset the priority class as "Normal"
       SetPriorityClass(pHandle, NORMAL_PRIORITY_CLASS);
        // Reset the priority back to normal
        SetThreadPriority(tHandle,THREAD_PRIORITY_NORMAL);
        ExitThread(0);
    }
    


    清单 4. Linux 相应的线程代码

    				
    static void * RegularThread (void *);
    static void * CriticalThread (void *);
    // Main Thread
           pthread_t thread1, thread2;  // thread identifiers
           pthread_attr_t threadAttr;
           struct sched_param param;  // scheduling priority
           // initialize the thread attribute
           pthread_attr_init(&threadAttr);
           // Set the stack size of the thread
           pthread_attr_setstacksize(&threadAttr, 120*1024);
           // Set thread to detached state. No need for pthread_join
           pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED);
           // Create the threads
           pthread_create(&thread1, &threadAttr, RegularThread, NULL);
           pthread_create(&thread2, &threadAttr, CriticalThread,NULL);
           // Destroy the thread attributes
           pthread_attr_destroy(&threadAttr);
           ...
           ...
      // Regular non-realtime Thread function
      static void * RegularThread (void *d)   {
           int priority = -18;
           // Increase the priority
           setpriority(PRIO_PROCESS, 0, priority);
           // perform high priority job
           ...
           ...
           // set the priority back to normal
           setpriority(PRIO_PROCESS, 0, 0);
           pthread_exit(NULL);
       }
       // Time Critical Realtime Thread function
       static void * CriticalThread (void *d) {
           // Increase the priority
            struct sched_param param;  // scheduling priority
            int policy = SCHED_RR;     // scheduling policy
            // Get the current thread id
            pthread_t thread_id = pthread_self();
            // To set the scheduling priority of the thread
            param.sched_priority = 90;
            pthread_setschedparam(thread_id, policy, &param);
            // Perform time critical task
            ...
            ...
            // set the priority back to normal
            param.sched_priority = 0;
            policy = 0;             // for normal threads
            pthread_setschedparam(thread_id, policy, &param);
            ....
            ....
            pthread_exit(NULL);
       }
    





    回页首


    结束语

    本系列文章的第 1 部分已经给出了一个指南,能够帮助您将 Windows 进程和线程映射到 Linux 中的对应物。系列的第 2 部分介绍了同步对象和原语,首先是信号量和事件。第 3 部分介绍了互斥体、关键区域和等待函数。



     

    参考资料



     

    作者简介

    作者照片

    Srinivasan S. Muthuswamy 是 IBM Global Services Group 的一位软件工程师。他于 2000 年加入 IBM,他所精通的编程语言涵盖了多种平台上(Linux、Windows、WebSphere、Lotus 等等)的脚本语言以及面向对象和面向过程的语言。他已经开发的解决方案包括 Linux 和 Windows 上的系统编程以及用于 J2EE 的 Web 解决方案。他在印度的 Coimbatore 国立科技大学(Government College of Technology)获得计算机工程学士学位,主要致力于集成和迁移。您可以通过 smuthusw@in.ibm.com 与他联系。


    作者照片

    从 2000 年 12 月起,Kavitha Varadarajan 一直在 IBM India Software Lab 担任软件工程师。她的工作经验包括 host-access 客户机产品(比如 PCOMM)和网络软件(比如通信服务器)的开发与支持。Varadarajan 拥有迁移项目的实践经验,其中涉及到了面向对象 IPC Windows 应用程序向 Linux 的移植。她拥有印度的 Tanjore 山姆哈工程大学(Shanmugha College of Engineering)的计算机科学与工程硕士学位。您可以通过 vkavitha@in.ibm.com 与她联系。


    将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 3 部分: 信号

    级别: 初级

    Nam Keung (mailto:namkeung@us.ibm.com), 高级程序员, IBM 

    2005 年 4 月 21 日

    将您的 Win32 C/C++ 应用程序迁移到 POWER™ 上的 Linux™,并从信号(semaphore)应用程序接口(application program interface,API)的角度理解 Win32 到 Linux 的映射。Nam Keung 将通过详细的代码示例来为您描述这一过程。

    介绍

    本系列第三篇文章从信号的角度阐述了 Win32 C/++ 应用程序向 POWER 上的 Linux 的迁移。本系列的第 1 部分介绍了 Win32 API 映射,第 2 部分从互斥(mutex)API 的角度集中阐述了如何将 Win32 映射到 Linux。在继续阅读之前,建议您先去阅读本系列的第 1 部分和第 2 部分。





    回页首


    信号

    信号是包含有一个正数的资源。信号允许进程通过一个单一的原子操作来测试和设置那个整数的值,以此实现同步。通常,信号的主要用途是同步某个线程与其他线程的动作。在多个进程竞争访问同一操作系统资源时,这也是协调或者同步那些行为的一种实用技术。

    Linux 支持 Portable Operating System Interface(POSIX)信号以及 pthread 条件变量,以此来映射 Win32 信号 API。它们各有其优缺点。您应该基于应用程序的逻辑来判断使用哪种方法。在映射事件信号的过程中需要考虑的方面包括:

    • 信号的类型:Win32 既支持有名称的事件信号,也支持无名称的事件信号。有名称的信号是在多个进程间共享的。Linux 不支持这种方案。本文中列出的一个进程间通信(Inter-Process Communication,IPC)消息队列示例代码将向您展示如何来解决此问题。
    • 初始状态:在 Win32 中,信号可能会有初始值。在 Linux 中,POSIX 信号支持此功能,但 pthreads 不支持。在使用 pthreads 时您需要考虑到这一点。
    • 超时:Win32 事件信号支持定时等待。在 Linux 中,POSIX 信号实现只支持不确定的等待(阻塞)。pthreads 实现既支持阻塞也支持超时。pthread_cond_timedwait() 调用能给出等待期间的超时的值,pthread_cond_wait() 则用于不确定的等待。
    • 发信号:在 Win32 中,发出信号会唤醒等待那个信号的所有线程。在 Linux 中,POSIX 线程实现一次只唤醒一个线程。pthreads 实现的 pthread_cond_signal() 调用会唤醒一个线程,pthread_cond_broadcast() 调用会向所有等待那个信号的线程发出信号。


    表 1. 信号映射表

    Win32 pthread Linux POSIX
    CreateSemaphore pthread_mutex_init(&(token)->mutex, NULL))
    pthread_cond_init(&(token)->condition, NULL))
    sem_init
    CloseHandle (semHandle) pthread_mutex_destroy(&(token->mutex))
    pthread_cond_destroy(&(token->condition))
    sem_destroy
    ReleaseSemaphore(semHandle, 1, NULL) pthread_cond_signal(&(token->condition)) sem_post
    WaitForSingleObject(semHandle,
    INFINITE)
    WaitForSingleObject(semHandle,
    timelimit)
    pthread_cond_wait(&(token->condition),
    &(token->mutex))
    pthread_cond_timedwait(&(token
    ->condition), &(token->mutex))
    sem_wait
    sem_trywait




    回页首


    条件变量

    条件变量让开发者能够实现一个条件,在这个条件下线程执行然后被阻塞。Microsoft® Win32 接口本身不支持条件变量。为解决此缺憾,我使用 POSIX 条件变量模拟同步原语,并在一系列文章中对此进行了概述。在 Linux 中,它可以确保因某条件被阻塞的线程,当那个条件改变时,会被解除阻塞。它还允许您原子地(atomically)解除互斥的锁定,并等待条件变量,而不会有干涉其他线程的可能。不过,每个条件变量都应该伴有一个互斥。前面的表 1 给出了用于线程间同步的 pthread 条件变量。





    回页首


    创建信号

    在 Win32 中,CreateSemaphore 函数可以创建一个有名称的或者无名称的信号对象。Linux 不支持有名称的信号。


    清单 1. 创建信号

    HANDLE CreateSemaphore (
    	LPSECURITY_ATTRIBUTES	lpSemaphoreAttributes,
    	LONG			lInitialCount,
    	LONG			lMaximunCount,
    	LPCTSTR			lpName
    ); 
    

    在 Linux 中,sem_init() 调用会创建一个 POSIX 信号:


    清单 2. POSIX 信号

                        
    int sem_init(sem_t *sem, int pshared, unsigned int value
    

    Linux 使用 pthread_condition_init 调用在当前进程内创建信号对象,在其中维持一个在零与最大值之间的计数值。每次有某个线程完成对信号的等待,这个计数值会减小,而每次当某个线程释放这个信号时,计数值增加。当计数值成为零时,信号对象的状态成为 non-signaled。


    清单 3. 创建信号对象的 pthread_condition_init 调用

                        
    int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
    


    清单 4. Win32 示例代码

                    
    HANDLE semHandle;
    semHandle = CreateSemaphore(NULL, 0, 256000, NULL); 
         /* Default security descriptor     */
    if( semHandle == (HANDLE) NULL)                     
         /* Semaphore object without a name */
    {
         return RC_OBJECT_NOT_CREATED;
    }
    



    清单 5. 相应的 Linux 代码
                    
    typedef struct 
    {
    	pthread_mutex_t	mutex;
    	pthread_cond_t		condition;
    	int			semCount;	
    }sem_private_struct, *sem_private;
    sem_private    token;
    token = (sem_private) malloc(sizeof(sem_private_struct));
    if(rc = pthread_mutex_init(&(token->mutex), NULL))
    {
    	free(token);
    	return RC_OBJECT_NOT_CREATED;
    }
    if(rc = pthread_cond_init(&(token->condition), NULL))
    {
    	pthread_mutex_destroy( &(token->mutex) );
    	free(token);
    	return RC_OBJECT_NOT_CREATED;
    }
    token->semCount = 0;
    





    回页首


    销毁事件信号

    Win32 使用 CloseHandle 来删除由 CreateSemaphore 所创建的信号对象。


    清单 6. 销毁事件信号

    BOOL CloseHandle (HANDLE hObject);
    

    Linux POSIX 信号使用 sem_destroy() 来销毁无名称的信号。


    清单 7. sem_destroy()

                    
    int sem_destroy(sem_t *sem);

    在 Linux pthreads 中,使用 pthread_cond_destroy() 来销毁条件变量。


    清单 8. pthread_cond_destroy()

                        
    int pthread_cond_destroy(pthread_cond_t *cond);


    清单 9. Win32 代码和相应的 Linux 代码

    Win32 代码 相应的 Linux 代码
    CloseHandle(semHandle); pthread_mutex_destroy(&(token->mutex));

    pthread_cond_destroy(&(token->condition));

    free (token);




    回页首


    发布事件信号

    在 Win32 中,ReleaseSemaphore 函数会令指定的信号对象的计数值增加指定数量。


    清单 10. ReleaseSemaphore 函数

                        
    BOOL ReleaseSemaphore(
    	HANDLE hSemaphore,
    	LONG 	lReleaseCount,
    	LPLONG	lpPreviousCount
    );
    

    Linux POSIX 信号使用 sem_post() 来发布事件信号。这将唤醒阻塞于此信号的所有线程。


    清单 11. sem_post()

                        
    int sem_post(sem_t * sem);
    

    在 Linux 中,pthread_cond_signal 会唤醒等待某个条件变更的某个线程。Linux 调用这个函数来为此对象所标识的信号发布一个事件完成信号。调用的线程增加那个信号的值。如果信号的值从零开始增加,而且 pthread_cond 中有任何线程被阻塞,那么请等待这个信号,因为其中一个会被唤醒。默认情况下,实现可以选择任意的正在等待的线程。


    清单 12. pthread_cond_signal

                        
    int pthread_cond_signal(pthread_cond_t *cond);
    


    清单 13. Win32 代码和相应的 Linux 代码

    Win32 代码 相应的 Linux 代码
    ReleaseSemaphore(semHandle, 1, NULL) if (rc = pthread_mutex_lock(&(token->mutex)))
    return RC_SEM_POST_ERROR;

    token->semCount ++;

    if (rc = pthread_mutex_unlock(&(token->mutex)))
    return RC_SEM_POST_ERROR;

    if (rc = pthread_cond_signal(&(token->condition)))
    return RC_SEM_POST_ERROR;




    回页首


    等待事件信号

    Win32 调用 WaitForSingleObject 函数来等待所需要的信号上事件的完成。当等待单一线程同步对象时,可以使用此方法。当对象被设置发出信号或者超时时间段结束时,这个方法会得到通知。如果时间间隔是 INFINITE,那么它就会无止境地等待下去。


    清单 14. WaitForSingleObject 函数

                        
    DWORD WaitForSingleObject(
    	HANDLE hHANDLE,
    	DWORD	dwMilliseconds
    );
    

    使用 WaitForMultipleObjects 函数来等待多个被通知的对象。在信号线程同步对象中,当计数器变为零时,对象是 non-signaled。


    清单 15. WaitForMultipleObjects 函数

                        
    DWORD WaitForMultipleObjects(
    	DWORD	nCount,
    	Const	HANDLE* lpHandles,
    	BOOL	bWaitAll,
    	DWORD	dwMilliseconds
    );
    

    Linux POSIX 信号使用 sem_wait() 来挂起发出调用的线程,直到信号拥有了非零的计数值。然后它自动地减少信号的计数值。


    清单 16. sem_wait() 函数

                        
    int sem_wait(sem_t * sem);
    

    在 POSIX 信号中不能使用超时选项。不过,您可以通过在某个循环中执行非阻塞的 sem_trywait() 来完成此功能,它会计算超时的值。


    清单 17. sem_trywait() 函数

                        
    int sem_trywait(sem_t  * sem);
    

    在 Linux 中,pthread_cond_wait() 会阻塞发出调用的线程。发出调用的线程会减小那个信号。如果当 pthread_cond_wait 被调用时信号是零,则 pthread_cond_wait() 就会阻塞,直到另一个线程增加了那个信号的值。


    清单 18. pthread_cond_wait() 函数

                        
    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t  *mutex);
    

    pthread_cond_wait 函数首先释放相关联的 external_mutex of type pthread_mutex_t,当调用者检查条件表达式时必须持有它。


    清单 19. Win32 代码和相应的 Linux 代码

    Win32 代码 相应的 Linux 代码
    DWORD retVal;

    retVal = WaitForSingleObject(semHandle, INFINITE);

    if (retVal == WAIT_FAILED) return RC_SEM_WAIT_ERROR
    if (rc = pthread_mutex_lock(&(token->mutex)))
    return RC_SEM_WAIT_ERROR;

    while (token->semCount <= 0)
    {
    rc = pthread_cond_wait(&(token->condition), &(token->mutex));
    if (rc &&errno != EINTR )
    break;
    }
    token->semCount--;

    if (rc = pthread_mutex_unlock(&(token->mutex)))
    return RC_SEM_WAIT_ERROR;

    如果您需要在指定的一段时间内阻塞发出调用的线程,那么请使用 pthread_cond_timewait 来阻塞它。调用这个方法来等待所需要信号上某个事件的完成,等待指定的一段时间。


    清单 20. pthread_cond_timewait

                    
    	int pthread_cond_timewait(
    	pthread_cond_t		*cond,
    	pthread_mutex_t		*mutex,
    	timespec		*tm
    );			




    清单 21. Win32 代码和相应的 Linux 代码
    Win32 代码 相应的 Linux 代码
    retVal = WaitForSingleObject(SemHandle, timelimit);

    if (retVal == WAIT_FAILED)
    return RC_SEM_WAIT_ERROR;

    if (retVal == WAIT_TIMEOUT)
    return RC_TIMEOUT;
    int rc;
    struct timespec tm;
    struct timeb tp;
    long sec, millisec;

    if (rc = pthread_mutex_lock(&(token->mutex)))
    return RC_SEM_WAIT_ERROR;

    sec = timelimit / 1000;
    millisec = timelimit % 1000;
    ftime( &tp );
    tp.time += sec;
    tp.millitm += millisec;
    if( tp.millitm > 999 )
    {
    tp.millitm -= 1000;
    tp.time++;
    }
    tm.tv_sec = tp.time;
    tm.tv_nsec = tp.millitm * 1000000 ;

    while (token->semCount <= 0)
    {
    rc = pthread_cond_timedwait(&(token->condition), &(token->mutex), &tm);
    if (rc && (errno != EINTR) )
    break;
    }
    if ( rc )
    {
    if ( pthread_mutex_unlock(&(token->mutex)) )
    return RC_SEM_WAIT_ERROR );

    if ( rc == ETIMEDOUT) /* we have a time out */
    return RC_TIMEOUT );

    return RC_SEM_WAIT_ERROR );

    }
    token->semCount--;

    if (rc = pthread_mutex_unlock(&(token->mutex)))
    return RC_SEM_WAIT_ERROR;




    回页首


    POSIX 信号示例代码

    清单 22 使用 POSIX 信号来实现线程 A 和 B 之间的同步:


    清单 22. POSIX 信号示例代码

                        
    sem_t sem; /* semaphore object */
    int irc;   /* return code */
    /* Initialize the semaphore - count is set to 1*/
    irc = sem_init (sem, 0,1)
    ...
    /* In Thread A */
    /* Wait for event to be posted */
    sem_wait (&sem);
    /* Unblocks immediately as semaphore initial count was set to 1 */
     .......
    /* Wait again for event to be posted */
    sem_wait (&sem);
    /* Blocks till event is posted */
    /* In Thread B */
    /* Post the semaphore */
    ...
    irc = sem_post (&sem);
    /* Destroy the semaphore */
    irc = sem_destroy(&sem);
    





    回页首


    进程内信号示例代码


    清单 23. Win32 进程内信号示例代码

                    
    #include <stdio.h>
    #include <stdlib.h>
    #include <windows.h>
    void thrdproc      (void   *data);  // the thread procedure (function) 
         to be executed
    HANDLE   semHandle;
    int
    main( int argc, char **argv )
    {
            HANDLE    *threadId1;
            HANDLE    *threadId2;
            int        hThrd;
            unsigned   stacksize;
            int	    arg1;
    	if( argc < 2 )
    		arg1 = 7;
    	else
    		arg1 = atoi( argv[1] );
    	printf( "Intra Process Semaphor test.\n" );
    	printf( "Start.\n" );
    	semHandle = CreateSemaphore(NULL, 1, 65536, NULL);
            if( semHandle == (HANDLE) NULL)
    	{
    		printf("CreateSemaphore error: %d\n", GetLastError());
    	}
    	printf( "Semaphor created.\n" );
    	
            if( stacksize < 8192 )
                stacksize = 8192;
            else
                stacksize = (stacksize/4096+1)*4096;
         
            hThrd = _beginthread( thrdproc, // Definition of a thread entry
                                      NULL,
                                 stacksize,
                                "Thread 1");
            if (hThrd == -1)
                return RC_THREAD_NOT_CREATED);
            *threadId1 = (HANDLE) hThrd;
             hThrd = _beginthread( thrdproc, // Definition of a thread entry
                                       NULL,
                                  stacksize,
                                  "Thread 2");
             if (hThrd == -1)
                return RC_THREAD_NOT_CREATED);
             *threadId2 = (HANDLE) hThrd;
    	 printf( "Main thread sleeps 5 sec.\n" );
             sleep(5);
             if( ! ReleaseSemaphore(semHandle, 1, NULL) )
    		printf("ReleaseSemaphore error: %d\n", GetLastError());
             printf( "Semaphor released.\n" );
             printf( "Main thread sleeps %d sec.\n", arg1 );
    	
             sleep (arg1);
           if( ! ReleaseSemaphore(semHandle, 1, NULL) )
    		printf("ReleaseSemaphore error: %d\n", GetLastError());
     
    	printf( "Semaphor released.\n" );
    	printf( "Main thread sleeps %d sec.\n", arg1 );
    	sleep (arg1);
            CloseHandle(semHandle);
    	
    	printf( "Semaphor deleted.\n" );
    	printf( "Main thread sleeps 5 sec.\n" );
            
            sleep (5);
            printf( "Stop.\n" );
    	return OK;
    }
    void
    thread_proc( void *pParam )
    {
     
     DWORD  retVal;
    	printf( "\t%s created.\n", pParam );
    	       
            retVal = WaitForSingleObject(semHandle, INFINITE);
            if (retVal == WAIT_FAILED)
                   return RC_SEM_WAIT_ERROR;
    	printf( "\tSemaphor blocked by %s. (%lx)\n", pParam, retVal);
    	printf( "\t%s sleeps for 5 sec.\n", pParam );
    	sleep(5);
    	if( ! ReleaseSemaphore(semHandle, 1, NULL) )
                    printf("ReleaseSemaphore error: %d\n", GetLastError());
    	printf( "\tSemaphor released by %s.)\n", pParam);
    }
    




    清单 24. 相应的 Linux 进程内信号示例代码
                    
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <errno.h>
    void  thread_proc (void * data);
    pthread_mutexattr_t     attr;
    pthread_mutex_t         mutex;
    typedef struct
    {
            pthread_mutex_t         mutex;
            pthread_cond_t          condition;
            int                     semCount;
    }sem_private_struct, *sem_private;
    sem_private     token;
    int main( int argc, char **argv )
    {
            pthread_t             threadId1;
            pthread_t             threadId2;
            pthread_attr_t        pthread_attr;
            pthread_attr_t        pthread_attr2;
            int			arg1;
            int                   rc;
    	if( argc < 2 )
    		arg1 = 7;
    	else
    		arg1 = atoi( argv[1] );
          printf( "Intra Process Semaphor test.\n" );
          printf( "Start.\n" );
          token =(sem_private)  malloc (sizeof (sem_private_struct));        
         
          if(rc = pthread_mutex_init( &(token->mutex), NULL))
          {
                    free(token);
                    return 1;
          }
          if(rc = pthread_cond_init(&(token->condition), NULL))
          {
               printf( "pthread_condition ERROR.\n" );
               pthread_mutex_destroy( &(token->mutex) );
               free(token);
               return 1;
          }
          token->semCount = 0;
          printf( "Semaphor created.\n" );
          if (rc = pthread_attr_init(&pthread_attr))
          {
               printf( "pthread_attr_init ERROR.\n" );
               exit;
          }
          if (rc = pthread_attr_setstacksize(&pthread_attr, 120*1024))
          {
               printf( "pthread_attr_setstacksize ERROR.\n" );
               exit;
          }
          if (rc = pthread_create(&threadId1,
                               &pthread_attr,
                (void*(*)(void*))thread_proc, 
                                   "Thread 1" ))
          {
               printf( "pthread_create ERROR.\n" );
               exit;
          }
          if (rc = pthread_attr_init(&pthread_attr2))
          {
               printf( "pthread_attr_init2 ERROR.\n" );
               exit;
          }
          if (rc = pthread_attr_setstacksize(&pthread_attr2, 120*1024))
          {
               printf( "pthread_attr_setstacksize2 ERROR.\n" );
               exit;
          }
          if (rc = pthread_create(&threadId2, 
                              &pthread_attr2,
                (void*(*)(void*))thread_proc,
                                   "Thread 2" ))
          {
               printf( "pthread_CREATE ERROR2.\n" );
               exit ;  // EINVAL, ENOMEM
          }
    	
          printf( "Main thread sleeps 5 sec.\n" );
          sleep( 5 );
          if (rc =  pthread_mutex_lock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR 1.\n" );
               return 1; 
          }
          token->semCount ++;
          if (rc = pthread_mutex_unlock&(token->mutex)))
          {
               printf( "pthread_mutex_unlock ERROR 1.\n" );
               return 1; 
          }
          if (rc = pthread_cond_signal(&(token->condition)))
          {
               printf( "pthread_cond_signal ERROR1.\n" );
               return 1; 
          }
          printf( "Semaphor released.\n" );
          printf( "Main thread sleeps %d sec.\n", arg1 );
          sleep( arg1 );
          if (rc =  pthread_mutex_lock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR.\n" );
               return 1; 
          }
          token->semCount ++;
          if (rc = pthread_mutex_unlock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR.\n" );
               return 1; 
          }
          if (rc = pthread_cond_signal(&(token->condition)))
          {
               printf( "pthread_cond_signal ERROR.\n" );
               return 1; 
          }
    	
          printf( "Semaphor released.\n" );
          printf( "Main thread sleeps %d sec.\n", arg1 );
          sleep( arg1 );
          pthread_mutex_destroy(&(token->mutex));
          pthread_cond_destroy(&(token->condition));	
          printf( "Semaphor deleted.\n" );
          printf( "Main thread sleeps 5 sec.\n" );
          sleep( 5 );
          printf( "Stop.\n" );
          return 0;
    }
    void
    thread_proc( void *pParam )
    {
     int	rc;
          printf( "\t%s created.\n", pParam );
          if (token == (sem_private) NULL)
              return ;
          if (rc =  pthread_mutex_lock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR2.\n" );
               return ; 
          }
          while (token->semCount <= 0)
          {
                    rc = pthread_cond_wait(&(token->condition), &(token->mutex));
                    if (rc && errno != EINTR )
                            break;
          }
          if( rc )
          {
                    pthread_mutex_unlock(&(token->mutex));
                    printf( "pthread_mutex_unlock ERROR3.\n" );
                    return; 
          }
          token->semCount--;
          if (rc = pthread_mutex_unlock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR.\n" );
               return ; 
          }
          printf( "\tSemaphor blocked by %s. (%lx)\n", pParam, rc );
          printf( "\t%s sleeps for 5 sec.\n", pParam );
          sleep( 5 );
          if (rc =  pthread_mutex_lock(&(token->mutex)))
          {
               printf( "pthread_mutex_lock ERROR.\n" );
               return ; 
          }
          token->semCount ++;
          if (rc = pthread_mutex_unlock(&(token->mutex)))
          {
               printf( "pthread_mutex_unlock ERROR.\n" );
               return ; 
          }
          if (rc = pthread_cond_signal(&(token->condition)))
          {
               printf( "pthread_cond_signal ERROR.\n" );
               return ; 
          }
          printf( "\tSemaphor released by %s. (%lx)\n", pParam, rc );
    





    回页首


    进程间信号示例代码


    清单 25. Win32 进程间信号示例代码,进程 1

                    
    #include <stdio.h>
    #include <windows.h>
    #define WAIT_FOR_ENTER  printf( "Press ENTER\n" );getchar()
    int main()
    {
           HANDLE		semaphore;
           int		nRet;
           DWORD          retVal;
           SECURITY_ATTRIBUTES		sec_attr;
    	printf( "Inter Process Semaphore test - Process 1.\n" );
    	printf( "Start.\n" );
    	
    	sec_attr.nLength              = sizeof( SECURITY_ATTRIBUTES );
    	sec_attr.lpSecurityDescriptor = NULL;
    	sec_attr.bInheritHandle       = TRUE;
    	semaphore = CreateSemaphore( &sec_attr, 1, 65536, "456789" );
    	if( semaphore == (HANDLE) NULL )
    		return RC_OBJECT_NOT_CREATED;
           printf( "Semaphore created. (%lx)\n", nRet );
    	
    	WAIT_FOR_ENTER;
    	
    	if( ! ReleaseSemaphore(semaphore, 1, NULL) )
    		return SEM_POST_ERROR;
          
           printf( "Semaphore Posted. \n");
    	
    	WAIT_FOR_ENTER;
    	retVal = WaitForSingleObject (semaphore, INFINITE );
            if (retVal == WAIT_FAILED)
                  return SEM_WAIT_ERROR;
      
    	printf( "Wait for Semaphore. \n");
    	
           WAIT_FOR_ENTER;
    	       
           CloseHandle (semaphore);
    	printf( "Semaphore deleted.\n" );
    	printf( "Stop.\n" );
    	return 0;
    }
    

    清单 26 给出了作为支持进程间共享的有名称信号示例的消息 IPC 代码。


    清单 26. 相应的 Linux 进程间信号示例代码,进程 1

                    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/sem.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <unistd.h>
    #define WAIT_FOR_ENTER  printf( "Press ENTER\n" );getchar()
    struct msgbuf {
            long mtype;         /* type of message */
            char mtext[1];      /* message text */
    };
    int main()
    {
            key_t           msgKey;
            int             flag;
            struct msgbuf   buff;
            int             sem;
            int             nRet =0;
    	printf( "Inter Process Semaphore test - Process 1.\n" );
    	printf( "Start.\n" );
            
    	flag = IPC_CREAT|IPC_EXCL;
          if( ( msgKey = (key_t) atol( "456789" ) ) <= 0 )
                    return 1;
          flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
          sem  = (int) msgget( msgKey, flag );
          
          if (sem == -1)
                 if( errno == EEXIST )
                 {
                        flag &= ~IPC_EXCL;
                        sem = (int) msgget( msgKey, flag );
                        if (msgctl(sem, IPC_RMID, NULL ) != 0)
                                return 1;
                        sem = (int) msgget( msgKey, flag );
                        if (sem == -1)
                                return 1;
                 }
                 else
                        return 1;
          printf( "Semaphore created. \n" );
          WAIT_FOR_ENTER;
          buff.mtype = 123;
          if( msgsnd( sem, &buff, 1, 0 ) < 0 )
              return 1;
          printf( "Semaphore Posted. \n" );
    	
          WAIT_FOR_ENTER;
          if( msgrcv( sem, &buff, 1, 0, 0 ) < 0 )
              return 1;
          printf( "Wait for Semaphore. \n" );
          WAIT_FOR_ENTER;
          msgctl(sem, 0, IPC_RMID );
          printf( "Semaphore deleted.\n" );
    	printf( "Stop.\n" );
    	return 0;
    }
    




    清单 27. Win32 进程间信号示例代码,进程 2
                    
    #include <stdio.h>
    #include <windows.h>
    int main()
    {
    	HANDLE	semaphore;
    	DWORD   retVal;
    	printf( "Inter Process Semaphore test - Process 2.\n" );
    	printf( "Start.\n" );
    	SECURITY_ATTRIBUTES		sec_attr;
           
           sec_attr.nLength              = sizeof( SECURITY_ATTRIBUTES );
           sec_attr.lpSecurityDescriptor = NULL;
           sec_attr.bInheritHandle       = TRUE;
    	semaphore = CreateSemaphore( &sec_attr, 0, 65536, "456789" );
    	if( semaphore == (HANDLE) NULL )
    		return RC_OBJECT_NOT_CREATED;
    	printf( "Semaphore opened. (%lx)\n", nRet );
    	
    	printf( "Try to wait for semaphore.\n" );
    	
           while( ( retVal = WaitForSingleObject( semaphore, 250 ) ) == WAIT_TIMEOUT)
    		printf( "Timeout. \n");
    	
           printf( "Semaphore acquired. \n");
           printf( "Try to post the semaphore.\n" );
    	       
           if( ! ReleaseSemaphore(semaphore, 1, NULL) )
    	     return RC_SEM_POST_ERROR;
    	
           printf( "Semaphore posted. \n");
    	
    	CloseHandle(semaphore);
    	printf( "Semaphore closed. \n");
    	printf( "Stop.\n" );
    	return 0;
    }
    




    清单 28. 相应的 Linux 进程间信号示例代码,进程 2
                    
    #include <stdio.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/sem.h>
    #include <sys/stat.h>
    #include <errno.h>
    #include <unistd.h>
    #define RC_TIMEOUT = 3
    struct msgbuf {
            long mtype;         /* type of message */
            char mtext[1];      /* message text */
    };
    int main()
    {
            key_t           msgKey;
            int             flag=0;
            struct msgbuf   buff;
            int             sem;
            int             nRet =0;
    	printf( "Inter Process Semaphore test - Process 2.\n" );
    	printf( "Start.\n" );
          if( ( msgKey = (key_t) atol( "456789" ) ) <= 0 )
                return 1;
          flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
          sem = (int) msgget( msgKey, flag );
          if (sem == -1)
                if( errno == EEXIST )
                {
                       flag &= ~IPC_EXCL;
                       sem = (int) msgget( msgKey, flag );
                       if (msgctl(sem, IPC_RMID, NULL ) != 0)
                              return 1;
                       sem = (int) msgget( msgKey, flag );
                       if (sem == -1)
                               return 1;
                }
                else
                       return 1;
    	printf( "Semaphore opened. (%lx)\n", nRet );
    	if( nRet != 0 )
              return 0;
    	printf( "Try to wait for semaphore.\n" );
    	while( ( nRet = sem_shared_wait_timed( sem, 250 ) ) == 3) 
    	
    	printf( "Timeout. (%lx)\n", nRet );
    	printf( "Semaphore acquired. (%lx)\n", nRet );
    	printf( "Try to post the semaphore.\n" );
           buff.mtype = 123;
           if( msgsnd( sem, &buff, 1, 0 ) < 0 )
              return 1;
    	printf( "Semaphore posted. (%lx)\n", nRet );
    	if( nRet != 0 )
    		return 0;
    	printf( "Semaphore closed. (%lx)\n", nRet );
    	printf( "Stop.\n" );
    	return 0;
    }
    int sem_shared_wait_timed( int sem, unsigned long timelimit)
    {
            struct msgbuf           buff;
            struct timeval          timeOut;
            int                     msg[1];
            int                     nRet=0;
            timeOut.tv_sec  = timelimit / 1000;
            timeOut.tv_usec = (timelimit % 1000) * 1000;
            msg[0] = sem;
            nRet = select( 0x1000, (fd_set *)msg, NULL, NULL, &timeOut );
            if(nRet == 0)
               return 3;
            if( msgrcv( sem, &buff, 1, 0, 0 ) < 0 )
                  return 1;
    }
    





    回页首


    结束语

    本系列的第三篇文章从信号 API 的角度讲述了从 Win32 到 Linux 的映射,并给出了您可以引用的信号示例代码。线程化的、同步的系统所提出的挑战不仅是设计与实现,也包括了质量保证的所有阶段。当进行从 Win32 到 Linux 的迁移时,可以将这些文章做为参考。一定要去阅读本系列中以前的文章。

    补充声明

    Microsoft、Windows、Windows NT 和 Windows 徽标是 Microsoft Corporation 在美国和/或其他国家或地区的商标或注册商标。

    Intel、Intel Inside(logos)、MMX 和 Pentium 是 Intel 公司在美国和/或其他国家或地区的商标。

    UNIX 是 The Open Group 在美国和其他国家或地区的注册商标。

    Linux 是 Linus Torvalds 在美国和/或其他国家或地区的商标。



     

    参考资料



     

    关于作者

    Nam Keung 是 IBM 的一名高级程序员,他曾致力于 AIX 通信开发、AIX 多媒体、SOM/DSOM 开发和 Java 性能方面的工作。他目前的工作包括帮助独立软件提供商(Independent Software Vendors,ISV)进行应用程序设计、部署应用程序、性能调优和关于 pSeries 平台的教育。您可以通过 namkeung@us.ibm.com 与 Nam 联系。

    Port Windows IPC apps to Linux, Part 3: Mutexes, critical sections, and wait functions


    Level: Advanced

    Srinivasan S. Muthuswamy (smuthusw@in.ibm.com), Software Engineer, IBM
    Kavitha Varadarajan (vkavitha@in.ibm.com), Software Engineer, IBM

    25 Aug 2005

    The wave of migration to open source in business has the potential to cause a tremendous porting traffic jam as developers move the pervasive Windows® applications to the Linux™ platform. In this three-part series, you get a mapping guide, complete with examples, to ease your transition from Windows to Linux. This part takes a look at mutexes, critical sections, and wait functions.


    Today, many global businesses and services are going open source -- all the major corporate players in the industry are pushing for it. This trend has spurred a major migration exercise in which lots of existing products maintained for various platforms (Windows, OS2, Solaris, etc.) will be ported to open source Linux platforms.

    Many applications are designed without considering the need to port them to Linux. This has the potential to be a porting nightmare, but it doesn't have to be. The goal of this series of articles is to help you migrate complex applications involving IPC and threading primitives from Windows to Linux. We will share our experiences in moving these critical Windows IPC applications, applications that include multithreaded apps that require thread syncronization and multiprocess apps that require interprocess syncronization.

    In short, this series can be called a mapping document -- it provides mapping of various Windows calls to Linux calls related to threads, processes, and interprocess communication elements (mutexes, semaphores, etc.). To create easily digestible chunks, we've divided the series into three articles:

    • Part 1 dealt with processes and threads.
    • Part 2 handled semaphores and events.
    • This part covers mutexes, critical sections, and wait functions.

    Let's finish our Windows-to-Linux mapping guide by starting with mutexes.

    Mutexes

    A mutex (which stands for "mutual exclusion" lock) is a locking or synchronization object that allows multiple threads to synchronize access to shared resources. It is often used to ensure that shared variables are always seen by other threads in a consistent state.

    In Windows, the mutexes are both named and un-named. The named mutex is shared between the threads of different process.

    In Linux, the mutexes are shared only between the threads of the same process. To achieve the same functionality in Linux, a System V semaphore can be used (see Resources for a link to Part 2 of this series).

    In Windows, wait functions are used to request the ownership of the mutex. There are different types of wait functions available -- the one we're using as an example is WaitForSingleObject().

    The following points should be considered in the mapping process of a mutex:

    • In Windows, a mutex can be named and un-named. A named mutex is shared across the process. In Linux, mutexes are shared only among the threads. System V semaphores can be used to provide the named mutex functionality in Linux.
    • In Windows, a mutex can be owned during creation; this support is not available in Linux. To achieve the same in Linux, a mutex should be locked explicitly after creation.
    • In Windows, timeout can be specified in the wait functions. In Linux, the timeout option is not available. This is handled in application logic.
    • A Windows mutex is recursive by default. A Linux mutex needs to have recursion explicitly set. System V semaphores are not recursive.
    Table 1. Mutex mapping
    Windows Linux threads Linux process Classification
    CreateMutex pthreads_mutex_init semget
    semctl
    context specific
    OpenMutex not applicable semget context specific
    WaitForSingleObject pthread_mutex_lock
    pthread_mutex_trylock
    semop context specific
    ReleaseMutex pthread_mutex_unlock semop context specific
    CloseHandle pthread_mutex_destroy semctl context specific

    Creating a mutex

    In Windows, CreateMutex() is used to create or open a named or un-named mutex object. Named mutexes are mainly used to provide synchronization between processess: HANDLE CreateMutex (LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName). In this code:

    • lpMutexAttributes is a pointer to the structure that determines whether the handle can be inherited by the child process or not. If this attribute is null, the handle cannot be inherited.
    • bInitialOwner is a boolean value and if this value is TRUE then the calling thread initially owns the mutex.
    • lpName is a pointer to the name of the semaphore. If null, then un-named semaphore is created.

    In Windows, OpenMutex() is used to open the named mutex. This function returns the handle of the mutex.

    HANDLE OpenMutex(
      DWORD dwDesiredAccess,
      BOOL bInheritHandle,
      LPCTSTR lpName
    )
    

    In the code:

    • dwDesiredAccess is the desired access for the user requesting for the mutex object.
    • bInheritHandle is a flag and if true, related process can inherit the handle.
    • lpName is the name of the mutex (and is case sensitive).

    Notice in this code that the named mutex should have been created already.

    In Linux, the pthread library call pthread_mutex_init() is used to create the mutex: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr).

    There are three kinds of mutexes in Linux, each type determined by what happens if a thread attempts to lock a mutex it already owns with pthread_mutex_lock():

    • A fast mutex. While trying to lock the mutex using pthread_mutex_lock() the calling thread suspends forever.
    • A recursive mutex. pthread_mutex_lock() returns immediately with a success return code. This is used as equivalent for a Windows mutex since it is recursive in nature.
    • An error check mutex. pthread_mutex_lock() returns immediately with the error code EDEADLK.

    The mutex kind can be set in two ways. The static way of setting is as follows:

    /* Fast */
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;                    
    /* Recursive */
    pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;    
    /* Errorcheck */
    pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
    

    Another way of setting mutex kind is by using a mutex attribute object. To do this, pthread_mutexattr_init() is called to initialize the object followed by pthread_mutexattr_settype() which sets the kind of the mutex.

    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind);
    

    The parameter kind takes the following values:

    • PTHREAD_MUTEX_FAST_NP
    • PTHREAD_MUTEX_RECURSIVE_NP
    • PTHREAD_MUTEX_ERRORCHECK_NP

    The attribute can be destroyed using pthread_mutexattr_destroy(): int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);.

    Setting the initial state of the mutex

    In Linux the initial state of the mutex cannot be set using the pthread_mutex_init() call. This can be achieved by following steps:

    1. Create a mutex using pthread_mutex_init().
    2. Lock/acquire the mutex using pthread_mutex_lock().

    Acquiring a mutex

    In Windows wait funtions provide the facility to acquire the synchronization objects. There are different types of wait functions -- in this section we're using WaitForSingleObject(). This function takes the handle to the mutex object and waits until it is signaled or timeout occurs.

    DWORD WaitForSingleObject(
      HANDLE hHandle,
      DWORD dwMilliseconds
    );
    

    In this code:

    • hHandle is the pointer to the mutex handle.
    • dwMilliseconds is the timeout value in milliseconds. If the value is INFINITE then it blocks the calling thread/process indefinitely.

    In Linux, the pthread library call pthread_mutex_lock() / pthread_mutex_trylock() is used to acquire the mutex.

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    

    pthread_mutex_lock() is a blocking call which means that if the mutex is already locked by another thread, pthread_mutex_lock() suspends the calling thread until the mutex is unlocked. On the other hand, pthread_mutex_trylock() returns immediately if the mutex is already locked by another thread. Remember that in Linux, the timeout option is not available. This can be achieved by issuing a non-blocking pthread_mutex_trylock() call along with a delay in a loop which counts the timeout value.

    Releasing a mutex

    In Windows the function ReleaseMutex() releases the ownership of the mutex and sets the mutex to the signaled state: BOOL ReleaseMutex(HANDLE hMutex). In this code, hMutex is the handle of the mutex.

    Notice that mutexes in Windows are basically recursive mutexes -- if the thread which already owns the mutex tries to acquire ownership again, the wait function returns without blocking and deadlock is avoided.

    Linux uses pthread_mutex_unlock() to release/unlock the mutex: int pthread_mutex_unlock(pthread_mutex_t *mutex);.

    The mutex functions are not asynchronous signal-safe and should not be called from a signal handler. In particular, calling pthread_mutex_lock or pthread_mutex_unlock from a signal handler may deadlock the calling thread.

    Closing/destroying a mutex

    In Windows, CloseHandle() is used to close or destroy the mutex object.

    BOOL CloseHandle(
      HANDLE hObject
    );
    

    In the code, hObject is the pointer to the handle to the synchronization object.

    In Linux, pthread_mutex_destroy() destroys a mutex object, freeing the resources it might hold. It also checks to determine whether the mutex is unlocked at that time: int pthread_mutex_destroy(pthread_mutex_t *mutex).

    Named mutex

    In Windows, named mutexes are mainly used between processes to achieve synchronization (to access the shared resource in a mutually exclusive manner). The mutex provided by the Linux threads libraries are limited to the threads of the same process. To achieve the same functionality between processes in Linux, a System V semaphore can be used.

    System V semaphores are count variables. To achieve the same function as the Windows named mutex, the initial count of the semaphore is set to 0 using semctl() function. To acquire mutually exclusive access to the shared resource, semop() is used with sem_op value as -1. The calling process is blocked until mutually exclusive access is released. The creating process can acquire the mutually exclusive access by creating a semaphore with the initial count as 0 using semctl() function. After using the shared resource, the semaphore count can be set to 1 by using semop() function, allowing the other processes to access the shared resource. (See Resources for a link to the Part 2 section on semaphores.)

    Examples

    Following is code samples dealing with mutexes. Listings 13 and 14 show two scenarios each. In the first, a mutex is used without a specified timeout value. In the second, a mutex is used with a timeout value of two seconds.
    Listing 1. Windows example for un-named mutex

    HANDLE     hMutexWithNoTimeOut, hMutexWithTimeOut;
    DWORD     dwRetCode;
    
    // create a mutex
    hMutexWithNoTimeOut = CreateMutex(
                         NULL,  // no security attriutes
                         FALSE, // initially not owned
                         NULL); // un named mutex so NULL
    
    hMutexWithTimeOut = CreateMutex(
                         NULL,  // no security attriutes
                         FALSE, // initially not owned
                         NULL); // un named mutex so NULL
    
    // acquire a mutex
    dwRetCode = WaitForSingleObject(
                                    hMutexWithNoTimeOut,  // Mutex handle
                                    INFINITE);            // Infinite wait
    
    if (dwRetCode == WAIT_OBJECT_0) {
    
        // success
        // access the shared resource
        .....
    
       // release mutex
       ReleaseMutex(hMutexWithNoTimeOut); // Mutex Handle
    
    }
    
    // case 2, using mutex with timeout specified.
    dwRetCode = WaitForSingleObject(
                                    hMutexWithTimeOut,
                                    2000L); // 2 secs timeout
    
    switch(dwRetCode) {
        case WAIT_OBJECT_0 :
            // success
            // access the shared resource
            .....
    
            // After using the shared resource, release the semaphore
            ReleaseMutex(hMutexWithTimeOut);
            ....
    
        break;
    
        case WAIT_TIMEOUT :
            // Handle the timeout case
            ...
        break;
    
        case WAIT_ABANDONED :
            // probe for abandoned mutex
    
        break;
    }
    
    ....
    
    // close all the mutex
    CloseHandle(hMutexWithTimeout);
    CloseHandle(hMutexWithNoTimeout);
    


    Listing 2. Equivalent Linux code
    #define TIMEOUT 200  // 2 Secs delay time
    struct timespec delay;  // structure for providing timeout
    
    pthread_mutexattr_t mutexattr;  // Mutex Attribute
    pthread_mutex_t mutexWithNoTimeOut, mutexWithTimeOut; // Mutex variables
    
    // Set the mutex as a recursive mutex
    pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);
    
    // Create the mutex with the attributes set
    pthread_mutex_init(&mutexWithNoTimeOut, &mutexattr);
    pthread_mutex_init(&mutexWithTimeOut, &mutexattr);
    
    // destroy the attribute
    pthread_mutexattr_destroy(&mutexattr)
    
    
    // Lock/Acquire the mutex and access the shared resource
    pthread_mutex_lock (&mutexWithNoTimeOut);
    
    // access the shared resource
     .. ...
    
    // Unlock the mutex
    pthread_mutex_unlock (&mutexWithNoTimeOut);
     ...
    
    
    // Case 2, Accessing share resource with time out value specified in the
    // Mutex call
    
    while (timeout < TIMEOUT ) {
    
       delay.tv_sec = 0;
       delay.tv_nsec = 1000000;  // 1 milli sec delay
    
        // Tries to acquire the mutex and access the shared resource,
        // if success, access the shared resource,
        // if the shared reosurce already in use, it tries every 1 milli sec
        // to acquire the resource
        // if it does not acquire the mutex within 2 secs delay,
        // then it is considered to be failed
    
        irc = pthread_mutex_trylock(&mutexWithTimeOut);
        if (!irc)  {
    
            // Acquire mutex success
            // Access the shared resource
    
    
            // Unlock the mutex and release the shared resource
            pthread_mutex_unlock (&mutexWithTimeOut);
    
    
          break;
        }
        else {
            // check whether somebody else has the mutex
            if (irc == EPERM ) {
                // Yes, Resource already in use so sleep
                nanosleep(&delay, NULL);
                timeout++ ;
            }
            else{
                // Handle error condition
            }
        }
    }
    
    // Close all the mutex
    pthread_mutex_destroy (&mutexWithNoTimeOut);
    pthread_mutex_destroy (&mutexWithTimeOut);
    


    Listing 3. Windows example for named mutex
    // Process 1
    HANDLE     hMutex;
    DWORD     dwRetCode;
    
    // create a mutex
    hMutex = CreateMutex(
                         NULL,  // no security attriutes
                         FALSE, // initially not owned
                         NULL); // un named mutex so NULL
    
    // acquire a mutex
    dwRetCode = WaitForSingleObject(
                                    hMutex,  // Mutex handle
                                    INFINITE);  // Infinite wait
    
    if (dwRetCode == WAIT_OBJECT_0) {
    
        // success
        // access the shared resource
        .....
    
       // release mutex
       ReleaseMutex(hMutex); // Mutex Handle
    
    }
    
    
    // close the mutex
    CloseHandle(hMutex);
    
    // Process 2
    HANDLE     hMutex;
    DWORD     dwRetCode;
    
    // Open the mutex created by the Process 1
    hMutex = OpenMutex(
                       NULL,        // no security attriutes
                       NULL,        // handle cannot be inhereited
                       "testMuex"); // named mutex
    
    // acquire a mutex
    dwRetCode = WaitForSingleObject(
                                    hMutex,     // Mutex handle
                                    INFINITE);  // Infinite wait
    
    if (dwRetCode == WAIT_OBJECT_0) {
    
        // success
        // access the shared resource
        .....
    
       // release mutex
       ReleaseMutex(hMutex); // Mutex Handle
    
    }
    
    
    // close the mutex
    CloseHandle(hMutex);
    


    Listing 4. Linux equivalent code
    // Process 1
    #define TIMEOUT 200
    
    
    
    int main()
    {
        //Definition of variables
        key_t key;
        int semid;
        int Ret;
        int timeout = 0;
        struct sembuf operation[1] ;
    
        union semun
        {
            int val;
            struct semid_ds *buf;
            USHORT *array;
        } semctl_arg,ignored_argument;
    
    
        key = ftok(); //Generate a unique key or supply a value
    
        semid = semget(key, // a unique identifier to identify semaphore set
                 1,  // number of semaphore in the semaphore set
            0666 | IPC_CREAT // permissions (rwxrwxrwx) on the new
                     //semaphore set and creation flag
                        );
        if(semid < 0)
        {
            printf("Create semaphore set failed ");
            Exit(1);
        }
    
        //Set Initial value for the resource
        semctl_arg.val = 1; //Setting semval to 1
    
        semctl(semid, 0, SETVAL, semctl_arg);
    
    
        //Wait for Zero
    
        while(timeout < TIMEOUT)
        {
            delay.tv_sec = 0;
            delay.tv_nsec = 1000000;  /* 1 milli sec */
    
            //Call Wait for Zero with IPC_NOWAIT option,so it will be
            // non blocking
    
            operation[0].sem_op = -1; //Wait
            operation[0].sem_num = subset;
            operation[0].sem_flg = IPC_NOWAIT;
    
            ret = semop(semid, operation,1);
            if(ret < 0)
            {
                /* check whether somebody else has the mutex */
                if (retCode == EPERM )
                {
                    /* sleep for delay time */
                    nanosleep(&delay, NULL);
                    timeout++ ;
                }
                else
                {
                    printf("ERROR while wait ");
                    break;
                }
            }
            else
            {
                /*semaphore got triggered */
                break;
            }
    
        }
    
        //Close semaphore
        iRc = semctl(semid, 1, IPC_RMID , ignored_argument);
    
    }
    
    // Process 2
    int main()
    {
        int key = 0x20;   //Process 2 shd know key value in order to open the
                         // existing semaphore set
        struct sembuf operation[1] ;
    
        //Open semaphore
        semid = semget(key, 1, 0);
    
        operation[0].sem_op = 1; //Release the resource so Wait in process
                                 // 1 will be triggered
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
    
        //Release semaphore
        semop(semid, operation,1);
    }
    



    Back to top


    Critical sections

    In Windows, critical sections are synchronization objects that are similar to mutexes but with some limitations. The critical sections can only be used between threads of same process. A mutex uses timeout when requesting access to the mutex, but a critical section does not provide such feature -- it waits indefinitely.

    The critical section uses the spin count. In the single-processor system, the spin count is ignored and initialized to 0, but in the multiprocessor system, the calling thread will spin dwSpinCount times before it actually waits for the critical section so that if the critical section becomes free during that time, the calling thread does not wait. Since the critical sections are used only between the threads of the same process, a pthreads mutex is used to achieve the same results on Linux systems.

    Table 2. Critical section mapping
    Windows Linux Classification
    InitializeCriticalSection
    InitializeCriticalSectionAndSpinCount
    pthread_mutex_init mappable
    EnterCriticalSection
    TryEnterCriticalSection
    pthread_mutex_lock
    pthread_mutex_trylock
    mappable
    LeaveCriticalSection pthread_mutex_trylock mappable
    DeleteCriticalSection pthread_mutex_destroy mappable

    Creating/initializing a critical section

    In Windows, critical sections need to be initialized before the threads of same process can actually be used. InitializeCriticalSection() or InitializeCriticalSectionAndSpinCount() can be used to initialize the critical section.

    void InitializeCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    )
    

    In this code, lpCriticalSection is a pointer to the handle to the critical section. In low memory situations, this function raises the STATUS_NO_MEMORY exception.

    InitializeCriticalSectionAndSpinCount() is used to initialize and set the spin count.

    BOOL InitializeCriticalSectionAndSpinCount(
      LPCRITICAL_SECTION lpCriticalSection,
      DWORD dwSpinCount
    )
    

    In this code:

    • lpCriticalSection is a pointer to the handle of the critical section.
    • dwSpinCount is the spin count for the critical section.

    In Linux, pthread_mutex_init() is used to create or initialize the mutex object.

    Entering/acquiring a critical section

    In Windows, EnterCriticalSection() or TryEnterCriticalSection() is used to request the ownership of the critical section. If the critical section is already in use, EnterCriticalSection() will block the calling thread, but TryEnterCriticalSection() will attempt to enter the critical section without blocking the thread.

    EnterCriticalSection() is used to enter the critical section:

    void EnterCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    );
    

    In this code, lpCriticalSection is a pointer to the handle of the critical section.

    The TryEnterCriticalSection() function attempts to enter a critical section without blocking. If the call is successful, the calling thread takes ownership of the critical section:

    BOOL TryEnterCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    )
    

    In this code, lpCriticalSection is the pointer to the handle of the critical section.

    In Linux, the same can be achieved using pthread_mutex_lock() which blocks the calling thread. pthread_mutex_trylock attempts to acquire the mutex without blocking the thread.

    Leaving/releasing a critical section

    In Windows, LeaveCriticalSection() is used to release the ownership of the critical section. LeaveCriticalSection() needs to be called as many times as the critical section is entered.void LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection )

    In this code, lpCriticalSection is the pointer to the handle of the critical section.

    In Linux, pthread_mutex_unlock() is used to release the ownership of the mutex object.

    Deleting a critical section

    In Windows, DeleteCriticalSection() is used to delete the critical section; once used, this critical section can no longer be used for synchronization.

    void DeleteCriticalSection(
      LPCRITICAL_SECTION lpCriticalSection
    )
    

    In this code, lpCriticalSection is a pointer to the handle of the critical section.

    In Linux, pthread_mutex_destroy() is used to delete the mutex object.

    Example

    The following examples are very simple -- they present code snippets for accessing a shared resource using a critical section for mutual exclusion.
    Listing 5. Windows critical section example

    CRITICAL_SECTION csCriticalSection;
    
    // Initialize a Critical Section
    InitializeCriticalSection(
                &csCriticalSection); // Critical Section Object
    
    // Enter a critical Section
    EnterCriticalSection(
                &csCriticalSection); // Critical Section Object
    
    // Access a shared resource
    
    
    
    // Leave a Critical Section
    LeaveCriticalSection(
                &csCriticalSection); // Critical Section Object
    
    
    // Delete a Critical Section
    DeleteCriticalSection(
                &csCriticalSection); // Critical Section Object
    


    Listing 6. Equivalent Linux code
    pthread_mutex_t mutex;           // Mutex
    pthread_mutexattr_t mutexattr;   // Mutex attribute variable
    
    // Set the mutex as a recursive mutex
    pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE_NP);
    
    // create the mutex with the attributes set
    pthread_mutex_init(&mutex, &mutexattr);
    
    //After initializing the mutex, the thread attribute can be destroyed
    pthread_mutexattr_destroy(&mutexattr)
    
    
    // Acquire the mutex to access the shared resource
    pthread_mutex_lock (&mutex);
    
    // access the shared resource
     ..
    
    // Release the mutex  and release the access to shared resource
    pthread_mutex_unlock (&mutex);
     ...
    
    // Destroy / close the mutex
    irc = pthread_mutex_destroy (&mutex);
    



    Back to top


    Wait functions

    In Windows, the wait functions block the calling thread/process until the specified criteria is met -- in other words, they allow threads to block their own executions. The type of the wait function determines the set of wait criteria used. There are four types of wait functions:

    • Single object (requires a handle to one synchronization object; returns when either the specified object is in the signaled state or the timeout interval elapses).
    • Multiple object (enables the calling thread to specify an array containing one or more synchronization object handles; returns when either the state of any one of the specified objects is set to signaled or the states of all objects have been set to signaled or the timeout interval elapses).
    • Alertable (the function can return when the specified conditions are met, but it can also return if the system queues an I/O completion routine or an APC for execution by the waiting thread).
    • Registered (a multiple wait operation, when the specified conditions are met, the callback function is executed by a worker thread from the thread pool).

    We won't be addressing alertable or registered wait functions in this series.

    In Linux, wait functions are provided in the respective synchronization library itself (mutexes and semaphores have their own wait functions.)

    The following points should be considered when mapping wait functions:

    • Windows supports multiple object wait functionality. It allows passing in of multiple synchronization in the same wait function. In Linux, this functionality is not available. This logic needs to be implemented in the application logic.
    • Windows supports alertable and registered waits; Linux provides only basic wait functionality. These features can be handled in the application logic for Linux.
    Table 3. Wait function mapping
    Windows Linux threads Linux process Classification
    SignalObjectAndWait semop semop context specific
    WaitForMultipleObjects sem_wait
    sem_trywait
    semop context specific

    Signaling and waiting

    SignalObjectAndWait() is also an alertable wait function and it is different as it signals an object and waits on another object in an atomic manner.

    DWORD SignalObjectAndWait(
      HANDLE hObjectToSignal,
      HANDLE hObjectToWaitOn,
      DWORD dwMilliseconds,
      BOOL bAlertable
    )
    

    In this code:

    • hObjectToSignal is a pointer to the handle to the object to be signaled.
    • hObjectToWaitOn is a pointer handle to the object for which the thread has to wait.
    • dwMilliseconds is the time out specified in milliseconds.
    • bAlertable is a flag and if this parameter is TRUE, then the wait function returns when the system queues an I/O completion routine or APC function and the thread calls the function. For this article we can ignore this flag

    In Linux, the same functionality can be achieved by using System V semaphores. These semaphores provide function in which we can specify operation sets. To signal an object and wait for another synchronization object, we can create two operations sets -- one for signaling the object and other to wait on the specified object. The operations sets are performed in an atomic manner, meaning the semop() call succeeds if both the operations succeed; otherwise, it fails.

    System V semaphores do not provide timeout functionality. This can be implemented in application logic as we discussed in Part 2 of this series by making semop() call with flag IPC_NOWAIT. By doing it this way, the calling thread or process is not blocked.

    Examples

    The following examples should illustrated the wait functions we've discussed.
    Listing 7. Windows example for SignalObjectAndWait()

    // Main thread
    HANDLE hEventOne; // Global Variable
    HANDLE hEventTwo; // Global Variable
    
    
    // Thread 1
    DWORD  dwRetCode;
    
    // Create Event One
    hEventOne = CreateEvent(
                         NULL,   // no security attributes
                         TRUE,   // Auto reset event
                         FALSE,  // initially set to non signaled state
                         NULL);  // un named event
    
    // Create Event Two
    hEventTwo = CreateEvent(
                         NULL,   // no security attributes
                         TRUE,   // Auto reset event
                         FALSE,  // initially set to non signaled state
                         NULL);  // un named event
    
    // Signal hEventOne and Wait for the hEventTwo to be signaled
    dwRetCode = SignalObjectAndWait(
                               hEventOne, // Object to be signaled
                               hEventTwo, // Object to wait on
                               INFINITE,  // Infinite wait
                               FALSE);    // Not alertable
    
      switch(dwRetCode) {
              case WAIT_OBJECT_O :
                       // Event is signaled
                       // go ahead and proceed the work
    
    
             default :
                       // Probe for error
      }
    
    
    
    
    // Completed the job,
    // now close the event handle
    CloseHandle(hEventOne);
    CloseHandle(hEventTwo);
    
    
    // Thread 2
    // Condition met for the event hEventTwo
    // now set the event
    SetEvent(
             hEventTwo);    // Event Handle
    


    Listing 8. Linux equivalent using System V semaphores
    // Main thread
    int key = 0x20;  // Semaphore key
    
    // Thread 1
        struct sembuf operation[2] ;
    
        // Create 2 semaphores
        semid = semget(key, 2, 0666 | IPC_CREAT);
    
    
        operation[0].sem_op  = 1; //Release first resource
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
    
        operation[0].sem_op  = -1; // Wait on the second resource
        operation[0].sem_num = 1;
        operation[0].sem_flg = SEM_UNDO;
    
        //Release semaphore 1 and wait on semaphore 2
        // note : thread is suspended until the semaphore 2 is released.
        semop(semid, operation, 2);
    
        // thread is released
        // delete the semaphore
        semctl(semid, 0, IPC_RMID , 0)
    
    
    // Thread 2
        struct sembuf operation[1] ;
    
        // open semaphore
        mysemid = semget(key, 2, 0);
    
        operation[0].sem_op  = 1; // Release on the second resource
        operation[0].sem_num = 1;
        operation[0].sem_flg = SEM_UNDO;
    
        //Release semaphore 2
        semop(semid, operation, 1);
    

    Waiting on an array

    WaitForMultipleObjects() is the simplest function available in this type. This function takes an array on one or more synchronized objects as input and blocks the calling thread until any of the following criteria is met:

    • Either any one or all of the specified objects are in the signaled state.
    • The timeout interval elapses.
    DWORD WaitForMultipleObjects(
      DWORD nCount,
      const HANDLE* lpHandles,
      BOOL bWaitAll,
      DWORD dwMilliseconds
    )
    

    In this code:

    • nCount is the number of object handles in the array pointed to by lpHandles.
    • lpHandles is a pointer to array of object handles.
    • bWaitAll is a flag and if this parameter is TRUE, the function waits until all the objects are in signaled state.
    • dwMilliseconds is the timeout value in milliseconds.

    In Linux, the same functionality can be achieved by using additional logic in the code. In the context of threads, POSIX semaphores are used and in the context of processes, System V semaphores are used. In Windows, if the bWaitAll flag is FALSE, the thread/process is released if any one of the synchronization objects are signaled. Linux does not provide this functionality. This logic needs to be handled in the application logic.

    Examples

    Following are examples for multiple objects wait functions.
    Listing 9. Windows example for any single object to be signaled

    HANDLE     hEvents[2];
    DWORD     i, dwRetCode;
    
    // Create two event objects.
    
    for (i = 0; i < 2; i++)
    {
        hEvents[i] = CreateEvent(
            NULL,   // no security attributes
            FALSE,  // auto-reset event object
            FALSE,  // initial state is nonsignaled
            NULL);  // unnamed object
    
    }
    
    // The creating thread waits for other threads or processes
    // to signal the event objects.
    
    dwRetCode  = WaitForMultipleObjects(
                           2,           // number of objects in array
                           hEvents,     // array of objects
                           FALSE,       // wait for any
                           INFINITE);   // indefinite wait
    
    // Return value indicates which event is signaled.
    
    switch (dwEvent)
    {
        // hEvent[0] was signaled.
        case WAIT_OBJECT_0 + 0:
            // Perform tasks required by this event.
            break;
    
        // hEvent[1] was signaled.
        case WAIT_OBJECT_0 + 1:
            // Perform tasks required by this event.
            break;
    
        // Return value is invalid.
        default:
            // probe for error
    }
    


    Listing 10. Linux equivalent code using POSIX
    // Semaphore
    sem_t semOne  ;
    sem_t semTwo  ;
    sem_t semMain ;
    
    // Main thread
    sem_init(semOne,0,0) ;
    sem_init(semTwo,0,0) ;
    sem_init(semMain,0,0) ;
    
    // create 2 threads each one waits on one semaphore
    // if signaled signals the main semaphore
    
    sem_wait(&semMain);
    
    
    // Thread 1
    sem_wait(&semOne);
    sem_post(&semMain);
    
    
    // Thread 2
    sem_wait(&semTwo);
    sem_post(&semMain);
    


    Listing 11. Windows example for all objects to be signaled
    HANDLE     hEvents[2];
    DWORD     i, dwRetCode;
    
    // Create two event objects.
    
    for (i = 0; i < 2; i++)
    {
        hEvents[i] = CreateEvent(
            NULL,   // no security attributes
            FALSE,  // auto-reset event object
            FALSE,  // initial state is nonsignaled
            NULL);  // unnamed object
    
    }
    
    // The creating thread waits for other threads or processes
    // to signal the event objects.
    
    dwRetCode  = WaitForMultipleObjects(
      2,           // number of objects in array
      hEvents,     // array of objects
      TRUE,        // wait for both the objects to be signaled
      INFINITE);   // indefinite wait
    
    // Return value indicates which event is signaled.
    
    switch (dwEvent)
    {
        // hEvent[0] and hEvent[1] were signaled.
        case WAIT_OBJECT_0 :
            // Perform tasks required by this event.
    
            break;
    
        // Return value is invalid.
        default:
            // probe for error
    }
    


    Listing 12. Linux equivalent code using POSIX
    // Semaphore
    sem_t semOne  ;
    sem_t semTwo  ;
    sem_t semMain ;
    pthread_mutex_t mutMain = PTHREAD_MUTEX_INITIALIZER;
    
    // Main thread
    sem_init(semOne,0,0) ;
    sem_init(semTwo,0,0) ;
    sem_init(semMain,0,0) ;
    
    // create 2 threads each one waits on one semaphore
    // if signaled signals the main semaphore
    
    sem_wait(&semMain);
    
    
    // Thread 1
    sem_wait(&semOne);
    
    // lock the Mutex
    pthread_mutex_lock(&mutMain);
    count ++;
    if(count == 2) {
         // semaphore semTwo is already signaled
         // so post the main semaphore
         sem_post(&semMain);
    }
    pthread_mutex_unlock(&mutMain);
    
    
    // Thread 2
    sem_wait(&semTwo);
    // lock the Mutex
    pthread_mutex_lock(&mutMain);
    count ++;
    if(count == 2) {
         // semaphore semOne is already signaled
         // so post the main semaphore
         sem_post(&semMain);
    }
    pthread_mutex_unlock(&mutMain);
     


    Listing 13. Linux equivalent code using System V semaphores (wait until all semaphores are signaled)
    // Main thread
    int key = 0x20;  // Semaphore key
    
    // Thread 1
        struct sembuf operation[2] ;
    
        // Create 2 semaphores
        semid = semget(key, 2, 0666 | IPC_CREAT);
    
    
        operation[0].sem_op  = -1; // Wait on first resource
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
    
        operation[0].sem_op  = -1; // Wait on the second resource
        operation[0].sem_num = 1;
        operation[0].sem_flg = SEM_UNDO;
    
        // Wait on both the semaphores
        // Note : thread is suspended until both the semaphores are released.
        semop(semid, operation, 2);
    
        // thread is released
        // delete the semaphore
        semctl(semid, 0, IPC_RMID , 0)
    
    
    // Thread 2
        struct sembuf operation[1] ;
    
        // open semaphore
        mysemid = semget(key, 2, 0);
    
        operation[0].sem_op  = 1; // Release on the second resource
        operation[0].sem_num = 1;
        operation[0].sem_flg = SEM_UNDO;
    
        //Release semaphore 2
        semop(semid, operation, 1);
    
    // Thread 3
        struct sembuf operation[1] ;
    
        // open semaphore
        mysemid = semget(key, 2, 0);
    
        operation[0].sem_op  = 1; // Release on the first resource
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
    
        //Release semaphore 1
        semop(semid, operation, 1);
     



    Back to top


    In conclusion

    In this series, we've provided a guide to help map Windows processes to their functional counterparts in Linux.

    In the first article we covered creating, terminating, and exiting a process; we've introduced wait functions (more about them in Part Three); and we've discussed environment variables. On the threads side, we've highlighted creation, parameter passing, specifying the function, setting the stack size, exiting, thread states, and changing priorities. And we addressed the differences in Windows and Linux of normal and time-critical threads and processes.

    In the second article, we introduced synchronization objects, discussing semaphores -- creating, opening, acquiring, releasing, closing, and destroying them -- and event objects -- creating, opening, waiting on, signaling, resetting, closing, destroying, and named and un-named. In each section, we illustrated the difference between the functionality of each in Windows and in Linux.

    In the last article of the series, we've defined and provided a mapping guide for mutexes, critical sections, and wait functions.

    We hope this extensive mapping guide can lay the groundwork for your moving to Linux systems.

    ResourcesLearn


    Get products and technologies
    • Order the SEK for Linux, a two-DVD set containing the latest IBM trial software for Linux from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

    • Build your next development project on Linux with IBM trial software, available for download directly from developerWorks.



    Discuss


    About the authors

    Srinivasan S. Muthuswamy

    Srinivasan S. Muthuswamy works as a Software Engineer for IBM Global Services Group. He joined IBM in 2000, and his expertise in programming reaches from scripting languages to object- and procedure-oriented languages on multiple platforms (Linux, Windows, WebSphere, Lotus, and so on). Muthuswamy has developed solutions ranging from system programming on Linux and Windows to Web solutions for J2EE. His primary focus is on integration and porting, and he holds a B.Eng. in Computer Engineering from the Government College of Technology, Coimbatore, India. You can contact him at <a href="mailto:smuthusw@in.ibm.com">smuthusw@in.ibm.com</a>.


    Kavitha Varadarajan

    Kavitha Varadarajan has worked as a software Engineer in the IBM India Software Lab from December 2000. Her work experience involves development and support of host-access client products such as PCOMM and networking software like the communication server. Varadarajan has experience with a migration project that involves porting object-oriented IPC Windows applications to Linux. She holds a B.Eng. in Computer Science and Engineering from Shanmugha College of Engineering, Tanjore, India. She can be contacted at <a href="mailto:vkavitha@in.ibm.com">vkavitha@in.ibm.com</a>.

    将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 1 部分: 进程、线程和共享内存服务


    级别: 初级

    Nam Keung (namkeung@us.ibm.com), 高级程序员
    Chakarat Skawratananond (chakarat@us.ibm.com), pSeries Linux 技术顾问

    2005 年 1 月 06 日

    本文的内容是 Win32 API(特别是进程、线程和共享内存服务)到 POWER 上 Linux 的映射。本文可以帮助您确定哪种映射服务最适合您的需要。作者向您详细介绍了他在移植 Win32 C/C++ 应用程序时遇到的 API 映射。

    概述

    有很多方式可以将 Win32 C/C++ 应用程序移植和迁移到 pSeries 平台。您可以使用免费软件或者第三方工具来将 Win32 应用程序代码移到 Linux。在我们的方案中,我们决定使用一个可移植层来抽象系统 API 调用。可移植层将使我们的应用程序具有以下优势:

    • 与硬件无关。
      • 与操作系统无关。
      • 与操作系统上版本与版本间的变化无关。
    • 与操作系统 API 风格及错误代码无关。
    • 能够统一地在对 OS 的调用中置入性能和 RAS 钩子(hook)。

    由于 Windows 环境与 pSeries Linux 环境有很大区别,所以进行跨 UNIX 平台的移植比进行从 Win32 平台到 UNIX 平台的移植要容易得多。这是可以想到的,因为很多 UNIX 系统都使用共同的设计理念,在应用程序层有非常多的类似之处。不过,Win32 API 在移植到 Linux 时是受限的。本文剖析了由于 Linux 和 Win32 之间设计的不同而引发的问题。




    回页首


    初始化和终止

    在 Win2K/NT 上,DLL 的初始化和终止入口点是 _DLL_InitTerm 函数。当每个新的进程获得对 DLL 的访问时,这个函数初始化 DLL 所必需的环境。当每个新的进程释放其对 DLL 的访问时,这个函数为那个环境终止 DLL。当您链接到那个 DLL 时,这个函数会自动地被调用。对应用程序而言,_DLL_InitTerm 函数中包含了另外一个初始化和终止例程。

    在 Linux 上,GCC 有一个扩展,允许指定当可执行文件或者包含它的共享对象启动或停止时应该调用某个函数。语法是 __attribute__((constructor))__attribute__((destructor))。这些基本上与构造函数及析构函数相同,可以替代 glibc 库中的 _init 和 _fini 函数。

    这些函数的 C 原型是:

    void __attribute__ ((constructor)) app_init(void);
    void __attribute__ ((destructor)) app_fini(void);

    				Win32 sample
    _DLL_InitTerm(HMODULE modhandle, DWORD fdwReason, LPVOID lpvReserved)
    {	
        WSADATA			Data;	
        switch (fdwReason)	
        {		
            case DLL_PROCESS_ATTACH:		
            if (_CRT_init() == -1)			
               return 0L;               
              /* start with initialization code */		
              app_init();		
              break;	
        case DLL_PROCESS_DETACH:		
             /* Start with termination code*/               
             app_fini();		
             _CRT_term();		
             break;      
        …..	
        default:		
              /* unexpected flag value - return error indication */		
                return 0UL;	
        }	return 1UL;		/* success */
    }
          





    回页首


    进程服务

    Win32 进程模型没有与 fork()exec() 直接相当的函数。在 Linux 中使用 fork() 调用总是会继承所有内容,与此不同, CreateProcess() 接收用于控制进程创建方面的显式参数,比如文件句柄继承。

    CreateProcess API 创建一个包含有一个或多个在此进程的上下文中运行的线程的新进程,子进程与父进程之间没有关系。在 Windows NT/2000/XP 上,返回的进程 ID 是 Win32 进程 ID。在 Windows ME 上,返回的进程 ID 是除去了高位(high-order bit)的 Win32 进程 ID。当创建的进程终止时,所有与此进程相关的数据都从内存中删除。

    为了在 Linux 中创建一个新的进程, fork() 系统调用会复制那个进程。新进程创建后,父进程和子进程的关系就会自动建立,子进程默认继承父进程的所有属性。Linux 使用一个不带任何参数的调用创建新的进程。 fork() 将子进程的进程 ID 返回给父进程,而不返回给子进程任何内容。

    Win32 进程同时使用句柄和进程 ID 来标识,而 Linux 没有进程句柄。

    进程映射表

    Win32 Linux
    CreateProcess fork()
    execv()
    TerminateProcess kill
    ExitProcess() exit()
    GetCommandLine argv[]
    GetCurrentProcessId getpid
    KillTimer alarm(0)
    SetEnvironmentVariable putenv
    GetEnvironmentVariable getenv
    GetExitCodeProcess waitpid

    创建进程服务

    在 Win32 中, CreateProcess() 的第一个参数指定要运行的程序,第二个参数给出命令行参数。CreateProcess 将其他进程参数作为参数。倒数第二个参数是一个指向某个 STARTUPINFORMATION 结构体的指针,它为进程指定了标准的设备以及其他关于进程环境的启动信息。在将 STARTUPINFORMATION 结构体的地址传给 CreateProcess 以重定向进程的标准输入、标准输出和标准错误之前,您需要设置这个结构体的 hStdin、hStdout 和 hStderr 成员。最后一个参数是一个指向某个 PROCESSINFORMATION 结构体的指针,由被创建的进程为其添加内容。进程一旦启动,它将包含创建它的进程的句柄以及其他内容。

    				Win32 example
    PROCESS_INFORMATION    procInfo;
    STARTUPINFO	        startupInfo;
    typedef DWORD          processId;
    char                   *exec_path_name
    char                   *_cmd_line;
            GetStartupInfo( &startupInfo );     // You must fill in this structure
    if( CreateProcess( exec_path_name,  // specify the executable program
                           _cmd_line,   // the command line arguments
                           NULL,       // ignored in Linux
                           NULL,       // ignored in Linux
                           TRUE,       // ignored in Linux
                           DETACHED_PROCESS | HIGH_PRIORITY_CLASS,
                           NULL,       // ignored in Linux
                           NULL,       // ignored in Linux
                   &startupInfo,
                        &procInfo))
            *processId = procInfo.dwProcessId;
    else
    {
            *processId = 0;
              return RC_PROCESS_NOT_CREATED;
    }	
          

    在 Linux 中,进程 ID 是一个整数。Linux 中的搜索目录由 PATH 环境变量(exec_path_name)决定。 fork() 函数建立父进程的一个副本,包括父进程的数据空间、堆和栈。 execv() 子例程使用 exec_path_name 将调用进程当前环境传递给新的进程。

    这个函数用一个由 exec_path_name 指定的新的进程映像替换当前的进程映像。新的映像构造自一个由 exec_path_name 指定的正规的、可执行的文件。由于调用的进程映像被新的进程映像所替换,所以没有任何返回。

    				Equivalent Linux code
    #include <stdlib.h>
    #include <stdio.h>
    int    processId;
    char  *exec_path_name;
    char  *	cmd_line ;
    cmd_line = (char *) malloc(strlen(_cmd_line ) + 1 );
    if(cmd_line == NULL)
             return RC_NOT_ENOUGH_MEMORY;
             	
    strcpy(cmd_line, _cmd_line);
    if( ( *processId = 
            fork() ) == 0 )		// Create child
     {	
             char		*pArg, *pPtr;
             char		*argv[WR_MAX_ARG + 1];
             int		 argc;
             if( ( pArg = strrchr( exec_path_name, '/' ) ) != NULL )
                    pArg++;
             else
                    pArg = exec_path_name;
             argv[0] = pArg;
             argc = 1;
             
             if( cmd_line != NULL && *cmd_line != '\0' )
             {
                      
                   pArg = strtok_r(cmd_line, " ", &pPtr);
                   
                   while( pArg != NULL )
                   {
                                  argv[argc] = pArg;
                                  argc++;
                                  if( argc >= WR_MAX_ARG )
                                  break;
                                  pArg = strtok_r(NULL, " ", &pPtr);
                    }
             }
             argv[argc] = NULL;
             
             execv(exec_path_name, argv);
             free(cmd_line);
             exit( -1 );
    }
    else if( *processId == -1 )
    {
               *processId = 0;
               free(cmd_line);
               return RC_PROCESS_NOT_CREATED;
    }
          

    终止进程服务

    在 Win32 进程中,父进程和子进程可能需要单独访问子进程所继承的由某个句柄标识的对象。父进程可以创建一个可访问而且可继承的副本句柄。Win32 示例代码使用下面的方法终止进程:

    • 使用 OpenProcess 来获得指定进程的句柄。
    • 使用 GetCurrentProcess 获得其自己的句柄。
    • 使用 DuplicateHandle 来获得一个来自同一对象的句柄作为原始句柄。

    如果函数成功,则使用 TerminateThread 函数来释放同一进程上的主线程。然后使用 TerminateThread 函数来无条件地使一个进程退出。它启动终止并立即返回。

    				Win32 sample code
    if( thread != (HANDLE) NULL )
    {
        HANDLE	thread_dup;
        if( DuplicateHandle( OpenProcess(PROCESS_ALL_ACCESS, TRUE,  processId),
                                         thread,
                                         GetCurrentProcess(),
                                         &thread_dup,  //Output
                                         0,
                                         FALSE,
                                         DUPLICATE_SAME_ACCESS ))
          {
                TerminateThread( thread_dup, 0);
           }
    }
    TerminateProcess(
            OpenProcess(PROCESS_ALL_ACCESS, TRUE, 
            processId), 
       (UINT)0 );
          

    在 Linux 中,使用 kill 子例程发送 SIGTERM 信号来终止特定进程(processId)。然后调用设置 WNOHANG 位的 waitpid 子例程。这将检查特定的进程并终止。

    				Equivalent  Linux code
    pid_t	nRet;
    int	status;
    kill( processId, SIGTERM );
    nRet = waitpid( processId, &status, WNOHANG);  //Check specified
       process is terminated
          

    进程依然存在服务

    Win32 OpenProcess 返回特定进程(processId)的句柄。如果函数成功,则 GetExitCodeProcess 将获得特定进程的状态,并检查进程的状态是否是 STILL_ACTIVE。

    				Win 32 sample
    HANDLE      nProc;
    DWORD       dwExitCode;
    nProc = OpenProcess(PROCESS_ALL_ACCESS, TRUE, processId);
    if ( nProc != NULL)
    {
         GetExitCodeProcess( nProc, &dwExitCode );
         if (dwExitCode == STILL_ACTIVE )
                return RC_PROCESS_EXIST;
         else
                return RC_PROCESS_NOT_EXIST;
    }
    else
         return RC_PROCESS_NOT_EXIST;
          

    在 Linux 中,使用 kill 子例程发送通过 Signal 参数指定的信号给由 Process 参数(processId)指定的特定进程。Signal 参数是一个 null 值,会执行错误检查,但不发送信号。

    				Equivalent Linux code
    if ( kill ( processId, 0 ) == -1 && errno == ESRCH ) // No process can 
                                           be found
                                 // corresponding to processId
             return RC_PROCESS_NOT_EXIST;
    else
            return RC_PROCESS_EXIST;
          





    回页首


    线程模型

    线程 是系统分配 CPU 时间的基本单位;当等待调度时,每个线程保持信息来保存它的"上下文"。每个线程都可以执行程序代码的任何部分,并共享进程的全局变量。

    构建于 clone() 系统调用之上的 LinuxThreads 是一个 pthreads 兼容线程系统。因为线程由内核来调度,所以 LinuxThreads 支持阻塞的 I/O 操作和多处理器。不过,每个线程实际上是一个 Linux 进程,所以一个程序可以拥有的线程数目受内核所允许的进程总数的限制。Linux 内核没有为线程同步提供系统调用。Linux Threads 库提供了另外的代码来支持对互斥和条件变量的操作(使用管道来阻塞线程)。

    对有外加 LinuxThreads 的信号处理来说,每个线程都会继承信号处理器(如果派生这个线程的父进程注册了一个信号处理器的话。只有在 Linux Kernel 2.6 和更高版本中支持的新特性才会包含 POSIX 线程支持,比如 用于 Linux 的 Native POSIX Thread Library(NPTL)。

    线程同步、等待函数、线程本地存储以及初始化和终止抽象是线程模型的重要部分。在这些之下,线程服务只负责:

    • 新线程被创建,threadId 被返回。
    • 通过调用 pthread_exit 函数可以终止当前的新线程。

    线程映射表

    Win32 Linux
    _beginthread pthread_attr_init
    pthread_attr_setstacksize
    pthread_create
    _endthread pthread_exit
    TerminateThread pthread_cancel
    GetCurrentThreadId pthread_self

    线程创建

    Win32 应用程序使用 C 运行期库,而不使用 Create_Thread API。使用了 _beginthread 和 _endthread 例程。这些例程会考虑任何可重入性(reentrancy)和内存不足问题、线程本地存储、初始化和终止抽象。

    Linux 使用 pthread 库调用 pthread_create() 来派生一个线程。

    threadId 作为一个输出参数返回。为创建一个新线程,要传递一组参数。当新线程被创建时,这些参数会执行一个函数。stacksize 用作新线程的栈的大小(以字节为单位),当新线程开始执行时,实际的参数被传递给函数。

    指定线程程序(函数)

    进行创建的线程必须指定要执行的新线程的启动函数的代码。启动地址是 threadproc 函数(带有一个单独的参数,即 threadparam)的名称。如果调用成功地创建了一个新线程,则返回 threadId。Win32 threadId 的类型定义是 HANDLE。Linux threadId 的类型定义是 pthread_t。

    threadproc
    要执行的线程程序(函数)。它接收一个单独的 void 参数。
    threadparam
    线程开始执行时传递给它的参数。

    设置栈大小

    在 Win32 中,线程的栈由进程的内存空间自动分配。系统根据需要增加栈的大小,并在线程终止时释放它。在 Linux 中,栈的大小在 pthread 属性对象中设置;pthread_attr_t 传递给库调用 pthread_create()

    				Win32 sample
    int		hThrd;
    DWORD          dwIDThread;
    unsigned       stacksize;
    void           *thrdparam; //parameter to be passed to the thread when it 
                                      //begins execution
    HANDLE         *threadId;
    if( stacksize < 8192 )
         stacksize = 8192;
    else
         stacksize = (stacksize/4096+1)*4096;
         
         hThrd = _beginthread( thrdproc,    // Definition of a thread entry 
                                                                      //point
                                    NULL,
                                    stacksize,
                                    thrdparam);
    if (hThrd == -1)
         return RC_THREAD_NOT_CREATED);
         *threadId = (HANDLE) hThrd;
    __________________________________________________________________     
                              
            Equivalent Linux code
                                                                
    #include <pthread.h>
    pthread_t   *threadId;
    void         thrdproc  (void *data);  //the thread procedure (function) to 
                                                   //be executed. 
                          //It receives a single void parameter
    void        *thrdparam; //parameter to be passed to the thread when it 
                                   //begins execution
    pthread_attr_t  attr;
    int             rc = 0;
    if (thrdproc == NULL || threadId == NULL)
         	return RC_INVALID_PARAM);
         	
    if (rc = pthread_attr_init(&attr))	
         return RC_THREAD_NOT_CREATED);  // EINVAL, ENOMEM
          
    if (rc = pthread_attr_setstacksize(&attr, stacksize))
         return RC_THREAD_NOT_CREATED);   // EINVAL, ENOSYS
         
    if (rc = pthread_create(threadId, &attr, (void*(*)(void*))thrdproc, 
       thrdparam))
         return RC_THREAD_NOT_CREATED);      // EINVAL, EAGAIN
          

    终止线程服务

    在 Win32 中,一个线程可以使用 TerminateThread 函数终止另一个线程。不过,线程的栈和其他资源将不会被收回。如果线程终止自己,则这样是可取的。在 Linux 中,pthread_cancel 可以终止由具体的 threadId 所标识的线程的执行。

    Win32 Linux
    TerminateThread((HANDLE *) threadId, 0); pthread_cancel(threadId);

    线程状态

    在 Linux 中,线程默认创建为可合并(joinable)状态。另一个线程可以使用 pthread_join() 同步线程的终止并重新获得终止代码。可合并线程的线程资源只有在其被合并后才被释放。

    Win32 使用 WaitForSingleObject() 来等待线程终止。

    Linux 使用 pthread_join 完成同样的事情。

    Win32 Linux
    unsigned long rc;

    rc = (unsigned long) WaitForSingleObject (threadId, INIFITE);
    unsigned long rc=0;

    rc = pthread_join(threadId,
    void **status);

    结束当前线程服务的执行

    在 Win32 中,使用 _endthread() 来结束当前线程的执行。在 Linux 中,推荐使用 pthread_exit() 来退出一个线程,以避免显式地调用 exit 例程。在 Linux 中,线程的返回值是 retval,可以由另一个线程调用 pthread_join() 来获得它。

    Win32 Linux
    _endthread(); pthread_exit(0);

    获得当前线程 ID 服务

    在 Win32 进程中,GetCurrentThreadId 函数获得进行调用的线程的线程标识符。Linux 使用 pthread_self() 函数来返回进行调用的线程的 ID。

    Win32 Linux
    GetCurrentThreadId() pthread_self()

    sleep 服务用于 Win32 Sleep 函数的时间段的单位是毫秒,可以是 INFINITE,在这种情况下线程将永远不会再重新开始。 Linux sleep 函数类似于 Sleep,但是时间段以秒来计。要获得毫秒级的精度,则使用 nanosleep 函数来提供同样的服务。

    Win32 Equivalent Linux code
    Sleep (50) struct timespec timeOut,remains;

    timeOut.tv_sec = 0;
    timeOut.tv_nsec = 500000000; /* 50 milliseconds */

    nanosleep(&timeOut, &remains);

    Win32 SleepEx 函数挂起 当前线程,直到下面事件之一发生:

    • 一个 I/O 完成回调函数被调用。
    • 一个异步过程调用(asynchronous procedure call,APC)排队到此线程。
    • 最小超时时间间隔已经过去。

    Linux 使用 sched_yield 完成同样的事情。

    Win32 Linux
    SleepEx (0,0) sched_yield()




    回页首


    共享内存服务

    共享内存允许多个进程将它们的部分虚地址映射到一个公用的内存区域。任何进程都可以向共享内存区域写入数据,并且数据可以由其他进程读取或修改。共享内存用于实现进程间通信媒介。不过,共享内存不为使用它的进程提供任何访问控制。使用共享内存时通常会同时使用"锁"。

    一个典型的使用情形是:

    1. 某个服务器创建了一个共享内存区域,并建立了一个共享的锁对象。
    2. 某个客户机连接到服务器所创建的共享内存区域。
    3. 客户机和服务器双方都可以使用共享的锁对象来获得对共享内存区域的访问。
    4. 客户机和服务器可以查询共享内存区域的位置。

    共享内存映射表

    Win32 Linux
    CreateFileMaping,
    OpenFileMapping
    mmap
    shmget
    UnmapViewOfFile munmap
    shmdt
    MapViewOfFile mmap
    shmat

    创建共享内存资源

    Win32 通过共享的内存映射文件来创建共享内存资源。Linux 使用 shmget/mmap 函数通过直接将文件数据合并入内存来访问文件。内存区域是已知的作为共享内存的段。

    文件和数据也可以在多个进程和线程之间共享。不过,这需要进程或线程之间同步,由应用程序来处理。

    如果资源已经存在,则 CreateFileMapping() 重新初始化共享资源对于进程的约定。如果没有足够的空闲内存来处理错误的共享资源,此调用可能会失败。 OpenFileMapping() 需要共享资源必须已经存在;这个调用只是请求对它的访问。

    在 Win32 中,CreateFileMapping 不允许您增加文件大小,但是在 Linux 中不是这样。在 Linux 中,如果资源已经存在,它将被重新初始化。它可能被销毁并重新创建。Linux 创建可以通过名称访问的共享内存。 open() 系统调用确定映射是否可读或可写。传递给 mmap() 的参数必须不能与 open() 时请求的访问相冲突。 mmap() 需要为映射提供文件的大小(字节数)。

    对 32-位内核而言,有 4GB 虚地址空间。最前的 1 GB 用于设备驱动程序。最后 1 GB 用于内核数据结构。中间的 2GB 可以用于共享内存。当前,POWER 上的 Linux 允许内核使用 4GB 虚地址空间,允许用户应用程序使用最多 4GB 虚地址空间。

    映射内存访问保护位

    Win32 Linux
    PAGE_READONLY PROT_READ
    PAGE_READWRITE (PROT_READ | PROT_WRITE)
    PAGE_NOACCESS PROT_NONE
    PAGE_EXECUTE PROT_EXEC
    PAGE_EXECUTE_READ (PROT_EXEC |PROT_READ)
    PAGE_EXECUTE_READWRITE (PROT_EXEC | PROT_READ | PROT_WRITE)

    要获得 Linux 共享内存的分配,您可以查看 /proc/sys/kernel 目录下的 shmmax、shmmin 和 shmall。

    在 Linux 上增加共享内存的一个示例:

    echo 524288000 > /proc/sys/kernel/shmmax

    最大共享内存增加到 500 MB。

    下面是创建共享内存资源的 Win32 示例代码,以及相对应的 Linux nmap 实现。

    				Win32 sample code
    typedef struct
     {
       // This receives a pointer within the current process at which the 
       // shared memory is located.
       // The same shared memory may reside at different addresses in other
       // processes which share it.
           void *	location;
           HANDLE	hFileMapping;
    }mem_shared_struct, *mem_shared, *token;
    mem_shared_struct   *token;
    if ((*token = (mem_shared) malloc(sizeof(mem_shared_struct))) == NULL)     
         return RC_NOT_ENOUGH_MEMORY;
         
    if (newmode == new_shared_create)
       (*token)->hFileMapping = CreateFileMapping((HANDLE) 0xFFFFFFFF, NULL,
                     PAGE_READWRITE,
                     0,
                    (DWORD) size,
                     (LPSTR) name);
     else
          (*token)->hFileMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS,
                     FALSE,
                     (LPSTR) name);
    if ((*token)->hFileMapping == NULL)
    {
          free( *token );
          return RC_SHM_NOT_CREATED );
    }
    (*token)->location = MapViewOfFile((*token)->hFileMapping,
                                          FILE_MAP_READ | FILE_MAP_WRITE, 
                                          0, 0, 0);
    if ((*token)->location == NULL)
    {
          CloseHandle((*token)->hFileMapping);
                free(*token);
                return RC_OBJECT_NOT_CREATED;
    }
    ____________________________________________________________________
                         
            Equivalent Linux code
                                      
    typedef struct
    {    	
          void    *location;
          int	 nFileDes;	
          cs_size	 nSize;	
          char	 *pFileName;
    }mem_shared_struct, *mem_shared, token;
    mode_t	mode=0;
    int	flag=0;
    int	i, ch='\0';
    char   name_buff[128];
    if (newmode == new_shared_create)
                      flag = O_CREAT;
    else if (newmode != new_shared_attach)	
                      return RC_INVALID_PARAM;
                      
    if ((*token = (mem_shared) malloc(sizeof(mem_shared_struct))) == NULL)
                   return RC_NOT_ENOUGH_MEMORY;
                   
    strcpy(name_buff, "/tmp/" );
    strcat(name_buff, name );
    if(((*token)->pFileName = malloc(strlen(name_buff)+1)) == NULL )
    {     
          free(*token);
          return RC_NOT_ENOUGH_MEMORY;
    }
    mode |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
    flag |= O_RDWR;
    if(newmode == new_shared_create)    
          remove(name_buff);
          
    if(((*token)->nFileDes = open(name_buff, flag, mode)) < 0)
    {    
          free((*token)->pFileName);
          free(*token);
          return RC_OBJECT_NOT_CREATED;
    }
    if(newmode == new_shared_create)
    {    
          lseek((*token)->nFileDes, size - 1, SEEK_SET);
          write((*token)->nFileDes, &ch, 1);
    }
    if(lseek((*token)->nFileDes, 0, SEEK_END) < size)
    {	
             free((*token)->pFileName);
             free(*token);
             return RC_MEMSIZE_ERROR;
    }
    (*token)->location = mmap( 0, size,
                      PROT_READ | PROT_WRITE,
                      MAP_VARIABLE | MAP_SHARED, 
                          (*token)->nFileDes,
                                      0);
                                      
    if((int)((*token)->location) == -1)
    {   
           free((*token)->pFileName);
           free(*token);    
           return RC_OBJECT_NOT_CREATED;
    }
    (*token)->nSize = size;strcpy((*token)->pFileName, name_buff);
          

    删除共享内存资源

    为销毁共享内存资源,munmap 子例程要取消被映射文件区域的映射。munmap 子例程只是取消对 mmap 子例程的调用而创建的区域的映射。如果某个区域内的一个地址被 mmap 子例程取消映射,并且那个区域后来未被再次映射,那么任何对那个地址的引用将导致给进程发出一个 SIGSEGV 信号。

    Win32 等价的 Linux 代码
    UnmapViewOfFile(token->location);

    CloseHandle(token->hFileMapping);
    munmap(token->location, token->nSize);

    close(token->nFileDes);

    remove(token->pFileName);

    free(token->pFileName);




    回页首


    结束语

    本文介绍了关于初始化和终止、进程、线程及共享内存服务从 Win32 API 到 POWER 上 Linux 的映射。这绝对没有涵盖所有的 API 映射,而且读者只能将此信息用作将 Win32 C/C++ 应用程序迁移到 POWER Linux 的一个参考。

    特别声明

    IBM、eServer 和 pSeries 是 IBM Corporation 在美国和/或其它国家或地区的商标。

    UNIX 是 The Open Group 在美国和其它国家或地区的注册商标。

    Microsoft 和 Windows 是 Microsoft Corporation 在美国和/或其它国家或地区的商标或注册商标。

    所有其他商标和注册商标是它们相应公司的财产。

    此出版物/说明是在美国完成的。IBM 可能不在其他国家或地区提供在此讨论的产品、程序、服务或特性,而且信息可能会不加声明地加以修改。有关您当前所在区域的产品、程序、服务和特性的信息,请向您当地的 IBM 代表咨询。任何对 IBM 产品、程序、服务或者特性的引用并非意在明示或暗示只能使用 IBM 的产品、程序、服务或者特性。只要不侵犯 IBM 的知识产权,任何同等功能的产品、程序、服务或特性,都可以代替 IBM 产品、程序、服务或特性。

    涉及非 IBM 产品的信息可从这些产品的供应商、其出版说明或其他可公开获得的资料中获取,并不构成 IBM 对此产品的认可。非 IBM 价目及性能数字资源取自可公开获得的信息,包括供应商的声明和供应商的全球主页。 IBM 没有对这些产品进行测试,也无法确认其性能的精确性、兼容性或任何其他关于非 IBM 产品的声明。有关非 IBM 产品性能的问题应当向这些产品的供应商提出。

    有关非 IBM 产品性能的问题应当向这些产品的供应商提出。IBM 公司可能已拥有或正在申请与本说明中描述的内容有关的各项专利。提供本说明并未授予用户使用这些专利的任何许可。您可以用书面方式将许可查询寄往: IBM Director of Licensing IBM Corporation North Castle Drive Armonk, NY 10504-1785 U.S.A。所有关于 IBM 未来方向或意向的声明都可随时更改或收回,而不另行通知,它们仅仅表示了目标和意愿而已。联系您本地的 IBM 办公人员或者 IBM 授权的转销商,以获得特定的 Statement of General Direction 的全文。

    本说明中所包含的信息没有提交给任何正式的 IBM 测试,而只是"按原样"发布。虽然 IBM 可能为了其在特定条件下的精确性而已经对每个条目进行了检查,但不保证在其他地方可以获得相同的或者类似的结果。使用此信息或者实现这里所描述的任何技术是客户的责任,取决于客户评价并集成它们到客户的操作环境的能力。尝试为他们自己的环境而修改这些技术的客户,这样做所带来的风险由他们自行承担。

    参考资料

    1. 您可以参阅本文在 developerWorks 全球站点上的 英文原文


    作者简介

    Nam Keung 是一名高级程序员,他曾致力于 AIX 通信开发、AIX 多媒体、SOM/DSOM 开发和 Java 性能方面的工作。他目前的工作包括帮助 ISV 进行应用程序设计、部署应用程序、性能调优和关于 pSeries 平台的教育。他从 1987 年起就是 IBM 的程序员了。您可以通过 namkeung@us.ibm.com 与 Nam 联系。


    Chakarat Skawratananond 是 IBM eServer Solutions Enablement 组织的一名技术顾问,在那里,他帮助独立软件开发商在 IBM pSeries 平台上使用他们的用于 AIX 5L 和 Linux 的应用程序。您可以通过 chakarat@us.ibm.com 与他联系。

    将 Windows IPC 应用程序移植到 Linux,第 2 部分: 信号量和事件

    级别: 初级

    Srinivasan S. Muthuswamy (smuthusw@in.ibm.com), 软件工程师, IBM Global Services Group
    Kavitha Varadarajan (vkavitha@in.ibm.com), 软件工程师, IBM India Software Lab

    2005 年 6 月 27 日

    随着开发人员将一些普及的 Windows® 应用程序迁移到 Linux™ 平台,企业中正在进行的向开放源码迁移的浪潮有可能引发极大的移植问题。这个由三部分组成的系列文章提供了一个映射指南,并附有一些例子,以简化从 Windows 到 Linux 的转移。本文是系列文章的第 2 部分,将介绍两种同步对象类型:信号量和事件。

    当前,很多全球商务和服务都正在走向开源 —— 业界的所有主要参与者都在争取实现此目标。这一趋势催生了一个重要的迁移模式:为不同平台(Windows、OS2、Solaris 等)维持的许多现有产品都将被移植到开放源码的 Linux 平台。

    很多应用程序在设计时并未考虑到需要将它们移植到 Linux。这有可能使移植成为一件痛苦的事情,但并非绝对如此。本系列文章的目的是,帮助您将涉及到 IPC 和线程原语的复杂应用程序从 Windows 迁移到 Linux。我们与您分享迁移这些关键应用程序的经验,其中包括要求线程同步的多线程应用程序以及要求进程间同步的多进程应用程序。

    简言之,可以将此系列文章看作是一个映射文档 —— 它提供了与线程、进程和进程间通信元素(互斥、信号量等等)相关的各种 Windows 调用到 Linux 调用的映射。我们将那些映射分为三部分:

    • 第 1 部分 中,我们已经对进程和线程进行了讨论。
    • 本文将介绍信号量和事件。
    • 第 3 部分将介绍互斥、临界区和等待函数。

    在本文中,我们将从同步技术入手,继续从 Windows 到 Linux 的映射指导。

    同步

    在 Windows 上,同步是使用等待函数中的同步对象来实现的。同步对象可以有两种状态:有信号(signaled)状态和无信号(non-signaled)状态。当在一个等待函数中使用同步对象时,等待函数就会阻塞调用线程,直到同步对象的状态被设置为有信号为止。

    下面是在 Windows 上可以使用的一些同步对象:

    • 事件(Event)
    • 信号量(Semaphore)
    • 互斥(Mutexe)
    • 临界区(Critical section)

    在 Linux 中,可以使用不同的同步原语。Windows 与 Linux 的不同之处在于每个原语都有自己的等待函数(所谓等待函数就是用来修改同步原语状态的函数);在 Windows 中,有一些通用的等待函数来实现相同的目的。以下是 Linux 上可以使用的一些同步原语:

    • 信号量(Semaphore)
    • 条件变量(Conditional variable)
    • 互斥(Mutexe)

    通过使用上面列出的这些原语,各种库都可以用于 Linux 之上,以提供同步机制。

    表 1. 同步映射

    Windows Linux —— 线程 Linux —— 进程
    互斥 互斥 - pthread 库 System V 信号量
    临界区 互斥 - pthread 库 不适用,因为临界区只用于同一进程的不同线程之间
    信号量 具有互斥的条件变量 - pthreads
    POSIX 信号量
    System V 信号量
    事件 具有互斥的条件变量 - pthreads System V 信号量




    回页首


    信号量

    Windows 信号量是一些计数器变量,允许有限个线程/进程访问共享资源。Linux POSIX 信号量也是一些计数器变量,可以用来在 Linux 上实现 Windows 上的信号量功能。

    在对进程进行映射时,我们需要考虑以下问题:

    • 信号量的类型: Windows 提供了有名(named)信号量和无名(unnamed)信号量。有名信号量可以在进程之间进行同步。在 Linux 上,在相同进程的不同线程之间,则只使用 POSIX 信号量。在进程之间,可以使用 System V 信号量。
    • 等待函数中的超时: 当在一个等待函数中使用时,可以为 Windows 信号量对象指定超时值。在 Linux 中,并没有提供这种功能,只能通过应用程序逻辑处理超时的问题。

    表 2. 信号量映射

    Windows Linux 线程 Linux 进程 类别
    CreateSemaphore sem_init semget
    semctl
    与上下文相关
    OpenSemaphore 不适用 semget 与上下文相关
    WaitForSingleObject sem_wait
    sem_trywait
    semop 与上下文相关
    ReleaseSemaphore sem_post semop 与上下文相关
    CloseHandle sem_destroy semctl 与上下文相关

    创建信号量

    在 Windows 中,可以使用 CreateSemaphore() 创建或打开一个有名或无名的信号量。

    HANDLE CreateSemaphore(
      LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
      LONG lInitialCount,
      LONG lMaximumCount,
      LPCTSTR lpName
    );
    

    在这段代码中:

    • lpSemaphoreAttributes 是一个指向安全性属性的指针。如果这个指针为空,那么这个信号量就不能被继承。
    • lInitialCount 是该信号量的初始值。
    • lMaximumCount 是该信号量的最大值,该值必须大于 0。
    • lpName 是信号量的名称。如果该值为 NULL,那么这个信号量就只能在相同进程的不同线程之间共享。否则,就可以在不同的进程之间进行共享。

    这个函数创建信号量,并返回这个信号量的句柄。它还将初始值设置为调用中指定的值。这样就可以允许有限个线程来访问某个共享资源。

    在 Linux 中,可以使用 sem_init() 来创建一个无名的 POSIX 信号量,这个调用可以在相同进程的线程之间使用。它还会对信号量计数器进行初始化:int sem_init(sem_t *sem, int pshared, unsigned int value)。在这段代码中:

    • value(信号量计数器)是这个信号量的初始值。
    • pshared 可以忽略,因为在目前的实现中,POSIX 信号量还不能在进程之间进行共享。

    这里要注意的是,最大值基于 demaphore.h 头文件中定义的 SEM_VALUE_MAX。

    在 Linux 中,semget() 用于创建 System V 信号量,它可以在不同集成的线程之间使用。可以用它来实现与 Windows 中有名信号量相同的功能。这个函数返回一个信号量集标识符,它与一个参数的键值关联在一起。当创建一个新信号量集时,对于与 semid_ds 数据结构关联在一起的信号量,semget() 要负责将它们进行初始化,方法如下:

    • sem_perm.cuidsem_perm.uid 被设置为调用进程的有效用户 ID。
    • sem_perm.cgidsem_perm.gid 被设置为调用进程的有效组 ID。
    • sem_perm.mode 的低 9 位被设置为 semflg 的低 9 位。
    • sem_nsems 被设置为 nsems 的值。
    • sem_otime 被设置为 0。
    • sem_ctime 被设置为当前时间。

    用来创建 System V 信号量使用的代码是:int semget(key_t key, int nsems, int semflg)。下面是对这段代码的一些解释:

    • key 是一个惟一的标识符,不同的进程使用它来标识这个信号量集。我们可以使用 ftok() 生成一个惟一的键值。IPC_PRIVATE 是一个特殊的 key_t 值;当使用 IPC_PRIVATE 作为 key 时,这个系统调用就会只使用 semflg 的低 9 位,但却忽略其他内容,从而新创建一个信号量集(在成功时)。
    • nsems 是这个信号量集中信号量的数量。
    • semflg 是这个新信号量集的权限。要新创建一个信号量集,您可以将使用 IPC_CREAT 来设置位操作或访问权限。如果具有该 key 值的信号量集已经存在,那么 IPC_CREAT/IPC_EXCL 标记就会失败。

    注意,在 System V 信号量中,key 被用来惟一标识信号量;在 Windows 中,信号量是使用一个名称来标识的。

    为了对信号量集数据结构进行初始化,可以使用 IPC_SET 命令来调用 semctl() 系统调用。将 arg.buf 所指向的 semid_ds 数据结构的某些成员的值写入信号量集数据结构中,同时更新这个结构的 sem_ctime member 的值。用户提供的这个 arg.buf 所指向的 semid_ds 结构如下所示:

    • sem_perm.uid
    • sem_perm.gid
    • sem_perm.mode (只有最低 9 位有效)

    调用进程的有效用户 ID 应该是超级用户,或者至少应该与这个信号量集的创建者或所有者匹配: int semctl(int semid, int semnum, int cmd = IPC_SET, ...)。在这段代码中:

    • semid 是信号量集的标识符。
    • semnum 是信号量子集偏移量(从 0 到 nsems -1,其中 n 是这个信号量集中子集的个数)。这个命令会被忽略。
    • cmd 是命令;它使用 IPC_SET 来设置信号量的值。
    • args 是这个信号量集数据结构中要通过 IPC_SET 来更新的值(在这个例子中会有解释)。

    最大计数器的值是根据在头文件中定义的 SEMVMX 来决定的。

    打开信号量

    在 Windows 中,我们使用 OpenSemaphore() 来打开某个指定信号量。只有在两个进程之间共享信号量时,才需要使用信号量。在成功打开信号量之后,这个函数就会返回这个信号量的句柄,这样就可以在后续的调用中使用它了。

    HANDLE OpenSemaphore(
      DWORD dwDesiredAccess,
      BOOL bInheritHandle,
      LPCTSTR lpName
    )
    

    在这段代码中:

    • dwDesiredAccess 是针对该信号量对象所请求的访问权。
    • bInheritHandle 是用来控制这个信号量句柄是否可继承的标记。如果该值为 TRUE,那么这个句柄可以被继承。
    • lpName 是这个信号量的名称。

    在 Linux 中,可以调用相同的 semget() 来打开某个信号量,不过此时 semflg 的值为 0:int semget(key,nsems,0)。在这段代码中:

    • key 应该指向想要打开的信号量集的 key 值。
    • 为了打开一个已经存在的信号量,可以将 nsems 和标记设置为 0。semflg 值是在返回信号量集标识符之前对访问权限进行验证时设置的。

    获取信号量

    在 Windows 中,等待函数提供了获取同步对象的机制。可以使用的等待函数有多种类型;在这一节中,我们只考虑 WaitForSingleObject()(其他类型将会分别进行讨论)。这个函数使用一个信号量对象的句柄作为参数,并会一直等待下去,直到其状态变为有信号状态或超时为止。 DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );

    在这段代码中:

    • hHandle 是指向互斥句柄的指针。
    • dwMilliseconds 是超时时间,以毫秒为单位。如果该值是 INFINITE,那么它阻塞调用线程/进程的时间就是不确定的。

    在 Linux 中,sem_wait() 用来获取对信号量的访问。这个函数会挂起调用线程,直到这个信号量有一个非空计数为止。然后,它可以原子地减少这个信号量计数器的值:int sem_wait(sem_t * sem)

    在 POSIX 信号量中并没有超时操作。这可以通过在一个循环中执行一个非阻塞的 sem_trywait() 实现,该函数会对超时值进行计算:int sem_trywait(sem_t * sem)

    在使用 System V 信号量时,如果通过使用 IPC_SET 命令的 semctl() 调用设置初始的值,那么必须要使用 semop() 来获取信号量。semop() 执行操作集中指定的操作,并阻塞调用线程/进程,直到信号量值为 0 或更大为止:int semop(int semid, struct sembuf *sops, unsigned nsops)

    函数 semop() 原子地执行在 sops 中所包含的操作 —— 也就是说,只有在这些操作可以同时成功执行时,这些操作才会被同时执行。sops 所指向的数组中的每个 nsops 元素都使用 struct sembuf 指定了一个要对信号量执行的操作,这个结构包括以下成员:

    • unsigned short sem_num; (信号量个数)
    • short sem_op; (信号量操作)
    • short sem_flg; (操作标记)

    要获取信号量,可以通过将 sem_op 设置为 -1 来调用 semop();在使用完信号量之后,可以通过将 sem_op 设置为 1 来调用 semop() 释放信号量。通过将 sem_op 设置为 -1 来调用 semop(),信号量计数器将会减小 1,如果该值小于 0(信号量的值是不能小于 0 的),那么这个信号量就不能再减小,而是会让调用线程/进程阻塞,直到其状态变为有信号状态为止。

    sem_flg 中可以识别的标记是 IPC_NOWAITSEM_UNDO。如果某一个操作被设置了 SEM_UNDO 标记,那么在进程结束时,该操作将被取消。如果 sem_op 被设置为 0,那么 semop() 就会等待 semval 变成 0。这是一个"等待为 0" 的操作,可以用它来获取信号量。

    记住,超时操作在 System V 信号量中并不适用。这可以在一个循环中使用非阻塞的 semop()(通过将 sem_flg 设置为 IPC_NOWAIT)实现,这会计算超时的值。

    释放信号量

    在 Windows 中,ReleaseSemaphore() 用来释放信号量。

    BOOL ReleaseSemaphore(
      HANDLE hSemaphore,
      LONG lReleaseCount,
      LPLONG lpPreviousCount
    );
    

    在这段代码中:

    • hSemaphore 是一个指向信号量句柄的指针。
    • lReleaseCount 是信号量计数器,可以通过指定的数量来增加计数。
    • lpPreviousCount 是指向上一个信号量计数器返回时的变量的指针。如果并没有请求上一个信号量计数器的值,那么这个参数可以是 NULL。

    这个函数会将信号量计数器的值增加在 lReleaseCount 中指定的值上,然后将这个信号量的状态设置为有信号状态。

    在 Linux 中,我们使用 sem_post() 来释放信号量。这会唤醒对这个信号量进行阻塞的所有线程。信号量的计数器同时被增加 1。要为这个信号量的计数器添加指定的值(就像是 Windows 上一样),可以使用一个互斥变量多次调用以下函数:int sem_post(sem_t * sem)

    对于 System V 信号量来说,只能使用 semop() 来释放信号量:int semop(int semid, struct sembuf *sops, unsigned nsops)

    函数 semop() 原子地执行 sops 中包含的一组操作(只在所有操作都可以同时成功执行时,才会将所有的操作同时一次执行完)。sops 所指向的数组中的每个 nsops 元素都使用一个 struct sembuf 结构指定了一个要对这个信号量执行的操作,该结构包含以下元素:

    • unsigned short sem_num;(信号量个数)
    • short sem_op; (信号量操作)
    • short sem_flg; (操作标记)

    要释放信号量,可以通过将 sem_op 设置为 1 来调用 semop()。通过将 semop() 设置为 1 来调用 semop(),这个信号量的计数器会增加 1,同时用信号通知这个信号量。

    关闭/销毁信号量

    在 Windows 中,我们使用 CloseHandle() 来关闭或销毁信号量对象。

    BOOL CloseHandle(
      HANDLE hObject
    );
    

    hObject 是指向这个同步对象句柄的指针。

    在 Linux 中,sem_destroy() 负责销毁信号量对象,并释放它所持有的资源: int sem_destroy(sem_t *sem)。对于 System V 信号量来说,只能使用 semctl() 函数的 IPC_RMID 命令来关闭信号量集:int semctl(int semid, int semnum, int cmd = IPC_RMID, ...)

    这个命令将立即删除信号量集及其数据结构,并唤醒所有正在等待的进程(如果发生错误,则返回,并将 errno 设置为 EIDRM)。调用进程的有效用户 ID 必须是超级用户,或者可以与该信号量集的创建者或所有者匹配的用户。参数 semnum 会被忽略。

    例子

    下面是信号量的几个例子。
    清单 1. Windows 无名信号量的代码

    				
    HANDLE hSemaphore;
    LONG   lCountMax = 10;
    LONG   lPrevCount;
    DWORD  dwRetCode;
    // Create a semaphore with initial and max. counts of 10.
    hSemaphore = CreateSemaphore(
                          NULL,        // no security attributes
                          0,           // initial count
                          lCountMax,   // maximum count
                          NULL);       // unnamed semaphore
    // Try to enter the semaphore gate.
    dwRetCode = WaitForSingleObject(
                       hSemaphore,  // handle to semaphore
                       2000L);   // zero-second time-out interval
    switch (dwRetCode)
    {
        // The semaphore object was signaled.
        case WAIT_OBJECT_0:
            // Semaphore is signaled
            // go ahead and continue the work
            goto success:
            break;
        case WAIT_TIMEOUT:
            // Handle the time out case
            break;
    }
    Success:
    // Job done, release the semaphore
    ReleaseSemaphore(
            hSemaphore,  // handle to semaphore
            1,           // increase count by one
            NULL)        // not interested in previous count
    // Close the semaphore handle
    CloseHandle(hSemaphore);
    


    清单 2. Linux 使用 POSIX 信号量的等效代码

    				
    				// Main thread
    #define TIMEOUT 200  /* 2 secs */
    // Thread 1
    sem_t sem     ; // Global Variable
    int   retCode ;
    // Initialize event semaphore
    retCode = sem_init(
                       sem,   // handle to the event semaphore
                       0,     // not shared
                       0);    // initially set to non signaled state
    while (timeout < TIMEOUT ) {
       delay.tv_sec = 0;
       delay.tv_nsec = 1000000;  /* 1 milli sec */
       // Wait for the event be signaled
       retCode = sem_trywait(
                       &sem); // event semaphore handle
                              // non blocking call
       if (!retCode)  {
            /* Event is signaled */
            break;
       }
       else {
           /* check whether somebody else has the mutex */
           if (retCode == EPERM ) {
                /* sleep for delay time */
                nanosleep(&delay, NULL);
                timeout++ ;
           }
           else{
               /* error  */
           }
       }
    }
    // Completed the job,
    // now destroy the event semaphore
    retCode = sem_destroy(
                          &sem);   // Event semaphore handle
    // Thread 2
    // Condition met
    // now signal the event semaphore
    sem_post(
             &sem);    // Event semaphore Handle
     


    清单 3. Linux 使用 System V 信号量的等效代码

    				
    				// Process 1
    #define TIMEOUT 200
    //Definition of variables
        key_t key;
        int semid;
        int Ret;
        int timeout = 0;
        struct sembuf operation[1] ;
        union semun
        {
            int val;
            struct semid_ds *buf;
            USHORT *array;
        } semctl_arg,ignored_argument;
        key = ftok(); // Generate a unique key, U can also supply a value instead
        semid = semget(key,             // a unique identifier to identify semaphore set
                        1,              // number of semaphore in the semaphore set
                       0666 | IPC_CREAT // permissions (rwxrwxrwx) on the new
                                        //    semaphore set and creation flag
                        );
       //Set Initial value for the resource
        semctl_arg.val = 0; //Setting semval to 0
        semctl(semid, 0, SETVAL, semctl_arg);
        //Wait for Zero
        while(timeout < TIMEOUT)
        {
            delay.tv_sec = 0;
            delay.tv_nsec = 1000000;  /* 1 milli sec */
            //Call Wait for Zero with IPC_NOWAIT option,so it will be non blocking
            operation[0].sem_op = -1; // Wait until the semaphore count becomes 0
            operation[0].sem_num = 0;
            operation[0].sem_flg = IPC_NOWAIT;
            ret = semop(semid, operation,1);
            if(ret < 0)
            {
                /* check whether somebody else has the mutex */
                if (retCode == EPERM )
                {
                    /* sleep for delay time */
                    nanosleep(&delay, NULL);
                    timeout++ ;
                }
                else
                {
                    printf("ERROR while wait ");
                    break;
                }
            }
            else
            {
                /*semaphore got triggered */
                break;
            }
        }
        //Close semaphore
        iRc = semctl(semid, 1, IPC_RMID , ignored_argument);
    }
    // Process 2
    key_t key = KEY; // Process 2 should know key value in order to open the
                     //    existing semaphore set
        struct sembuf operation[1] ;
        //Open semaphore
        semid = semget(key, 1, 0);
        operation[0].sem_op = 1; // Release the resource so Wait in process 1 will
                                 //    be triggered
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
        //Release semaphore
        semop(semid, operation,0);
    }
     





    回页首


    事件

    在 Windows 中,事件对象是那些需要使用 SetEvent() 函数显式地将其状态设置为有信号状态的同步对象。事件对象来源有两种类型:

    • 手工重置事件(manual reset event) 中,对象的状态会一直维持为有信号状态,直到使用 ResetEvent() 函数显式地重新设置它为止。
    • 自动重置事件(auto reset event) 中,对象的状态会一直维持为有信号状态,直到单个正在等待的线程被释放为止。当正在等待的线程被释放时,其状态就被设置为无信号的状态。

    事件对象有两种状态,有信号(signaled)状态无信号(non-signaled)状态。对事件对象调用的等待函数会阻塞调用线程,直到其状态被设置为有信号状态为止。

    在进行平台的迁移时,需要考虑以下问题:

    • Windows 提供了 有名(named)无名(un-named) 的事件对象。有名事件对象用来在进程之间进行同步,而在 Linux 中, pthreads 和 POSIX 都提供了线程间的同步功能。为了在 Linux 实现与 Windows 中有名事件对象相同的功能,可以使用 System V 信号量或信号。
    • Windows 提供了两种类型的事件对象 —— 手工重置对象和自动重置对象。Linux 只提供了自动重置事件的特性。
    • 在 Windows 中,事件对象的初始状态被设置为有信号状态。在 Linux 中,pthreads 并没有提供初始状态,而 POSIX 信号量则提供了一个初始状态。
    • Windows 事件对象是异步的。在 Linux 中,POSIX 信号量和 System V 信号量也都是异步的,不过 pthreads 条件变量不是异步的。
    • 当在一个等待函数中使用事件对象时,可以指定 Windows 的事件对象的超时时间值。在 Linux 中,只有 pthreads 在等待函数中提供了超时的特性。

    还有几点非常重要,需要说明一下:

    • 尽管 POSIX 信号量是计数器信号量,但是当这个计数器被设置为 1 时,它们可以提供与 Windows 事件对象相似的功能。它们并不能在等待函数中提供超时时间。如果在进行移植时,超时并不是一个影响因素,那么建议您使用 POSIX 信号量。
    • 当与互斥一起使用时,pthreads 条件变量可以在线程之间提供基于事件的同步机制,不过这是同步的。根据应用程序的逻辑,这可以将此作为移植过程中在 Linux 上实现这种功能的一个选择。

    表 3. 事件对象映射

    Windows Linux 线程 Linux 进程 类别
    CreateEvent
    OpenEvent
    pthread_cond_init
    sem_init
    semget
    semctl
    与上下文相关
    SetEvent pthread_cond_signal
    sem_post
    semop 与上下文相关
    ResetEvent N/A N/A 与上下文相关
    WaitForSingleObject pthread_cond_wait
    pthread_cond_timedwait
    sem_wait
    sem_trywait
    semop 与上下文相关
    CloseHandle pthread_cond_destroy
    sem_destroy
    semctl 与上下文相关

    创建/打开事件对象

    在 Windows 中,我们使用 CreateEvent() 来创建事件对象。

    HANDLE CreateEvent(
      LPSECURITY_ATTRIBUTES lpEventAttributes,
      BOOL bManualReset,
      BOOL bInitialState,
      LPCTSTR lpName
    )
    

    在这段代码中:

    • lpEventAttributes 是一个指针,它指向一个决定这个句柄是否能够被继承的属性。如果这个指针为 NULL,那么这个对象就不能被初始化。
    • bManualReset 是一个标记,如果该值为 TRUE,就会创建一个手工重置的事件,应该显式地调用 ResetEvent(),将事件对象的状态设置为无信号状态。
    • bInitialState 是这个事件对象的初始状态。如果该值为 true,那么这个事件对象的初始状态就被设置为有信号状态。
    • lpName 是指向这个事件对象名的指针。对于无名的事件对象来说,该值是 NULL。

    这个函数创建一个手工重置或自动重置的事件对象,同时还要设置改对象的初始状态。这个函数返回事件对象的句柄,这样就可以在后续的调用中使用这个事件对象了。

    OpenEvent() 用来打开一个现有的有名事件对象。这个函数返回该事件对象的句柄。

    HANDLE OpenEvent(
      DWORD dwDesiredAccess,
      BOOL bInheritHandle,
      LPCTSTR lpName
    )
    

    在这段代码中:

    • dwDesiredAccess 是针对这个事件对象所请求的访问权。
    • bInheritHandle 是用来控制这个事件对象句柄是否可继承的标记。如果该值为 TRUE,那么这个句柄就可以被继承;否则就不能被继承。
    • lpName 是一个指向事件对象名的指针。

    在 Linux 中,可以调用 sem_init() 来创建一个 POSIX 信号量:int sem_init(sem_t *sem, int pshared, unsigned int value)(其中 value(即信号量计数值)被设置为这个信号量的初始状态)。

    Linux pthreads 使用 pthread_cond_init() 来创建一个条件变量:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)

    可以使用 PTHREAD_COND_INITIALIZER 常量静态地对 pthread_cond_t 类型的条件变量进行初始化,也可以使用 pthread_condattr_init() 对其进行初始化,这个函数会对与这个条件变量关联在一起的属性进行初始化。可以调用 pthread_condattr_destroy() 用来销毁属性:

    int pthread_condattr_init(pthread_condattr_t *attr)
    int pthread_condattr_destroy(pthread_condattr_t *attr)
    

    等待某个事件

    在 Windows 中,等待函数提供了获取同步对象的机制。我们可以使用不同类型的等待函数(此处我们只考虑 WaitForSingleObject())。这个函数会使用一个互斥对象的句柄,并一直等待,直到它变为有信号状态或超时为止。

    DWORD WaitForSingleObject(
      HANDLE hHandle,
      DWORD dwMilliseconds
    );
    

    在这段代码中:

    • hHandle 是指向互斥句柄的指针。
    • dwMilliseconds 是超时时间的值,单位是毫秒。如果该值为 INFINITE,那么它阻塞调用线程/进程的时间就是不确定的。

    Linux POSIX 信号量使用 sem_wait() 来挂起调用线程,直到信号量的计数器变成非零的值为止。然后它会自动减小信号量计数器的值:int sem_wait(sem_t * sem)

    在 POSIX 信号量中并没有提供超时操作。这可以通过在一个循环中执行非阻塞的 sem_trywait() 来实现,该函数会对超时时间进行计数:int sem_trywait(sem_t * sem).

    Linux pthreads 使用 pthread_cond_wait() 来阻塞调用线程,其时间是不确定的:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)。在另外一方面,如果调用线程需要被阻塞一段确定的时间,那么就可以使用 pthread_cond_timedwait() 来阻塞这个线程。如果在这段指定的时间内条件变量并没有出现,那么 pthread_cond_timedwait() 就会返回一个错误:int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime)。在这里,abstime 参数指定了一个绝对时间(具体来说,就是从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在所经过的时间。)

    改变事件对象的状态

    函数 SetEvent() 用来将事件对象的状态设置为有信号状态。对一个已经设置为有信号状态的事件对象再次执行该函数是无效的。

    BOOL SetEvent(
      HANDLE hEvent
    )
    

    Linux POSIX 信号量使用 sem_post() 来发出一个事件信号量。这会唤醒在该信号量上阻塞的所有线程:int sem_post(sem_t * sem)

    调用 pthread_cond_signal() 被用在 LinuxThreads 中,以唤醒在某个条件变量上等待的一个线程,而 pthread_cond_broadcast() 用来唤醒在某个条件变量上等待的所有线程。

    int pthread_cond_signal(pthread_cond_t *cond)
    int pthread_cond_broadcast(pthread_cond_t *cond)
    

    注意,条件函数并不是异步信号安全的,因此不能在信号处理函数中调用。具体地说,在信号处理函数中调用 pthread_cond_signal()pthread_cond_broadcast() 可能会导致调用线程的死锁。

    重置事件的状态

    在 Windows 中,ResetEvent() 用来将事件对象的状态重新设置为无信号状态。

    BOOL ResetEvent(
      HANDLE hEvent
    );
    

    在 Linux 中,条件变量和 POSIX 信号量都是自动重置类型的。

    关闭/销毁事件对象

    在 Windows 中,CloseHandle() 用来关闭或销毁事件对象。

    BOOL CloseHandle(
      HANDLE hObject
    );
    

    在这段代码中,hObject 是指向同步对象句柄的指针。

    在 Linux 中, sem_destroy()/ pthread_cond_destroy() 用来销毁信号量对象或条件变量,并释放它们所持有的资源:

    int sem_destroy(sem_t *sem)
    int pthread_cond_destroy(pthread_cond_t *cond)
    

    有名事件对象

    在 Linux 中,进程之间有名事件对象所实现的功能可以使用 System V 信号量实现。System V 信号量是计数器变量,因此可以实现 Windows 中事件对象的功能,信号量的计数器的初始值可以使用 semctl() 设置为 0。

    要将某个事件的状态修改为有信号状态,可以使用 semop(),并将 sem_op 的值设置为 1。要等待某个事件,则可以使用 semop() 函数,并将 sem_op 的值设置为 -1,这样就可以阻塞调用进程,直到它变为有信号状态为止。

    可以通过使用 semctl() 将信号量计数器的初始值设置为 0 来获得信号量。在使用完共享资源之后,可以使用 semop() 将信号量计数设置为 1。关于每个 System V 信号量的原型,请参阅本文中有关信号量一节的内容。

    例子

    下面几个例子可以帮助您理解我们在这一节中所讨论的内容。
    清单 4. Windows 无名事件对象的代码

    				
    				// Main thread
    HANDLE hEvent; // Global Variable
    // Thread 1
    DWORD  dwRetCode;
    // Create Event
    hEvent = CreateEvent(
                         NULL,    // no security attributes
                         FALSE,   // Auto reset event
                         FALSE,   // initially set to non signaled state
                         NULL);   // un named event
    // Wait for the event be signaled
    dwRetCode = WaitForSingleObject(
                                    hEvent,    // Mutex handle
                                  INFINITE);   // Infinite wait
    switch(dwRetCode) {
              case WAIT_OBJECT_O :
                     // Event is signaled
                     // go ahead and proceed the work
             default :
                       // Probe for error
    }
    // Completed the job,
    // now close the event handle
    CloseHandle(hEvent);
    // Thread 2
    // Condition met for the event hEvent
    // now set the event
    SetEvent(
             hEvent);    // Event Handle
    


    清单 5. Linux 使用 POSIX 信号量的等效代码

    				
    				// Main thread
    sem_t sem     ; // Global Variable
    // Thread 1
    int   retCode ;
    // Initialize event semaphore
    retCode = sem_init(
                       sem,   // handle to the event semaphore
                       0,     // not shared
                       0);    // initially set to non signaled state
    // Wait for the event be signaled
    retCode = sem_wait(
                       &sem); // event semaphore handle
                              // Indefinite wait
    // Event Signaled
    // a head and proceed the work
    // Completed the job,
    // now destroy the event semaphore
    retCode = sem_destroy(
                          &sem);   // Event semaphore handle
    // Thread 2
    // Condition met
    // now signal the event semaphore
    sem_post(
             &sem);    // Event semaphore Handle
    


    清单 6. Linux 中使用条件变量的等效代码

    				
    				// Main thread
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
    // Thread 1
     ...
    pthread_mutex_lock(&mutex);
    // signal one thread to wake up
    pthread_cond_signal(&condvar);
    pthread_mutex_unlock(&mutex);
    // this signal is lost as no one is waiting
    // Thread 1 now tries to take the mutex lock
    // to send the signal but gets blocked
         ...
        pthread_mutex_lock(&mutex);
    // Thread 1 now gets the lock and can
    // signal thread 2 to wake up
          pthread_cond_signal(&condvar);
          pthread_mutex_unlock(&mutex);
    // Thread 2
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&condvar, &mutex);
    pthread_mutex_unlock(&mutex);
    // Thread 2 blocks indefinitely
    // One way of avoiding losing the signal is as follows
    // In Thread 2 - Lock the mutex early to avoid losing signal
    pthread_mutex_lock (&mutex);
    // Do work
    .......
    // This work may lead other threads to send signal to thread 2
    // Thread 2 waits for indefinitely for the signal to be posted
    pthread_cond_wait (&condvar, &Mutex );
    // Thread 2 unblocks upon receipt of signal
    pthread_mutex_unlock (&mutex);
    


    清单 7. Windows 中使用有名事件的例子

    				
    				// Process 1
    DWORD  dwRetCode;
    HANDLE hEvent; // Local variable
    // Create Event
    hEvent = CreateEvent(
                         NULL,        // no security attributes
                         FALSE,       // Auto reset event
                         FALSE,       // initially set to non signaled state
                         "myEvent");  // un named event
    // Wait for the event be signaled
    dwRetCode = WaitForSingleObject(
                                    hEvent,    // Mutex handle
                                  INFINITE);   // Infinite wait
    switch(dwRetCode) {
              case WAIT_OBJECT_O :
                     // Event is signaled
                     // go ahead and proceed the work
             default :
                       // Probe for error
    }
    // Completed the job,
    // now close the event handle
    CloseHandle(hEvent);
    // Process 2
    HANDLE hEvent; // Local variable
    // Open the Event
    hEvent = CreateEvent(
                         NULL,        // no security attributes
                         FALSE,       // do not inherit handle
                         "myEvent");  // un named event
    // Condition met for the event hEvent
    // now set the event
    SetEvent(
             hEvent);    // Event Handle
    // completed the job, now close the event handle
    CloseHandle(hEvent);
    


    清单 8. Linux 中使用 System V 信号量的等效代码

    				
    				// Process 1
    int main()
    {
        //Definition of variables
        key_t key;
        int semid;
        int Ret;
        int timeout = 0;
        struct sembuf operation[1] ;
        union semun
        {
            int val;
            struct semid_ds *buf;
            USHORT *array;
        } semctl_arg,ignored_argument;
        key = ftok(); /Generate a unique key, U can also supply a value instead
        semid = semget(key,                // a unique identifier to identify semaphore set
                         1,                // number of semaphore in the semaphore set
                         0666 | IPC_CREAT  // permissions (rwxrwxrwx) on the new
                                           //     semaphore set and creation flag
                        );
        if(semid < 0)
        {
            printf("Create semaphore set failed ");
            Exit(1);
        }
        //Set Initial value for the resource - initially not owned
        semctl_arg.val = 0; //Setting semval to 0
        semctl(semid, 0, SETVAL, semctl_arg);
        // wait on the semaphore
        // blocked until it is signaled
        operation[0].sem_op = -1;
        operation[0].sem_num = 0;
        operation[0].sem_flg = IPC_WAIT;
        ret = semop(semid, operation,1);
        // access the shared resource
        ...
        ...
        //Close semaphore
        iRc = semctl(semid, 1, IPC_RMID , ignored_argument);
    }
    // Process 2
    int main()
    {
        key_t key = KEY; //Process 2 shd know key value in order to open the
                    // existing semaphore set
        struct sembuf operation[1] ;
        //Open semaphore
        semid = semget(key, 1, 0);
        // signal the semaphore by incrementing the semaphore count
        operation[0].sem_op = 1;
        operation[0].sem_num = 0;
        operation[0].sem_flg = SEM_UNDO;
        semop(semid, operation,0);
    }
     





    回页首


    本系列下一篇文章的内容

    本文是这一系列的第 2 部分,这篇文章从信号量和事件入手,介绍了有关同步对象和原语的内容。第 3 部分的内容将涉及互斥、临界区和等待函数。

    参考资料



    作者简介

    作者照片

    Srinivasan S. Muthuswamy 是 IBM Global Services Group 的一位软件工程师。他于 2000 年加入 IBM,他所精通的编程语言涵盖了多种平台上(Linux、Windows、WebSphere、Lotus,等等)的脚本语言以及面向对象和面向过程的语言。他已经开发的解决方案包括 Linux 和 Windows 上的系统编程以及用于 J2EE 的 Web 解决方案。他在印度的 Coimbatore 国立科技大学(Government College of Technology)获得计算机工程学士学位,主要致力于集成和迁移。您可以通过 smuthusw@in.ibm.com 与他联系。


    作者照片

    从 2000 年 12 月起,Kavitha Varadarajan 一直在 IBM India Software Lab 担任软件工程师。她的工作经验包括 host-access 客户机产品(比如 PCOMM)和网络软件(比如通信服务器)的开发与支持。Varadarajan 拥有迁移项目的实践经验,其中涉及到了面向对象 IPC Windows 应用程序向 Linux 的移植。她拥有印度的 Tanjore 山姆哈工程大学(Shanmugha College of Engineering)的计算机科学与工程硕士学位。您可以通过 vkavitha@in.ibm.com 与她联系。

    将 Win32 C/C++ 应用程序迁移到 POWER 上的 Linux,第 2 部分: 互斥

    级别: 初级

    Nam Keung (mailto:namkeung@us.ibm.com), 高级程序员, IBM 
    Chakarat Skawratananond (mailto:chakarat@us.ibm.com), pSeries Linux 技术顾问, IBM 

    2005 年 2 月 10 日
    2005 年 4 月 21 日 更新

    本系列文章可以帮助您将 Win32 C/C++ 应用程序移植到 POWER 上的 Linux。高级程序员 Nam Keung 和 pSeries® Linux 技术顾问 Chakarat Skawratananond 从互斥(mutex)应用程序接口(application program interface,API)的角度阐述了从 Win32 到 Linux 的映射。本系列的 第 1 部分 集中关注的是 Win32 API 的映射。

    介绍

    本文关注的是互斥原语(primitives)。建议您在继续阅读之前先回顾本系列 第 1 部分 中的下列章节:

    • 初始化
    • 进程
    • 线程
    • 共享内存





    回页首


    互斥

    如下面的 表 1 所示,互斥提供线程间资源的独占访问控制。它是一个简单的锁,只有持有它的线程才可以释放那个互斥。它确保了它们正在访问的共享资源的完整性(最常见的是共享数据),因为在同一时刻只允许一个线程访问它。


    表 1. 互斥

    Win32 Linux
    CreateMutex(0, FALSE, 0); pthread_mutex_init(&mutex, NULL))
    CloseHandle(mutex); pthread_mutex_destroy(&mutex)
    WaitForSingleObject(mutex, INFINITE)) pthread_mutex_lock(&mutex)
    ReleaseMutex(mutex); pthread_mutex_unlock(&mutex)




    回页首


    创建互斥

    在 Win NT/Win2K 中,所有互斥都是递归的。

    在 Win32 中,CreateMutex() 为当前进程中的线程提供资料的独占访问控制。此方法让线程可以串行化对进程内资源的访问。创建了互斥句柄(mutual exclusion handle)后,当前进程中的所有线程都可以使用它(见下面的 清单 1)。


    清单 1. 创建互斥

    HANDLE CreateMutex(
     LPSECURITY_ATTRIBUTES	lMutexAttributes,
     BOOL			lInitialOwner,
     LPCTSTR			lName
    )
    

    Linux 使用 pthread 库调用 pthread_mutex_init() 来创建互斥,如下面的 清单 2 所示。


    清单 2. pthread

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);

    Linux 有三种类型的互斥。互斥类型决定了在 pthread_mutex_lock 中线程尝试锁定一个它已经持有的互斥时所发生的情形:

    Fast mutex:
    当尝试使用 pthread_mutex_lock() 去锁定互斥时,进行调用的线程会永远挂起。
    Recursive mutex:
    pthread_mutex_lock() 立即返回成功返回代码。
    Error check mutex:
    pthread_mutex_lock() 立即返回错误代码 EDEADLK。

    可以以两种方式设置互斥的类型。清单 3 介绍了设置互斥的静态方法。


    清单 3. 设置互斥的静态方法

    /* For Fast mutexes */
    pthread_mutex_t 	mutex = PTHREAD_MUTEX_INITIALIZER;
    /* For recursive mutexes */
    

    您可以使用这个函数来锁定互斥:pthread_mutex_lock(pthread_mutex_t *mutex)。这个函数会获得一个指向它正在尝试锁定的互斥的指针。当互斥被锁定或者发生错误时,函数返回。那个错误不是归咎于被锁定的互斥。函数会等待互斥被解锁。

    设置互斥的另一种方式是使用互斥属性对象。为此,要调用 pthread_mutexattr_init() 来初始化对象,然后调用 pthread_mutexattr_settype() 来设置互斥的类型,如下面的 清单 4 所示。


    清单 4. 通过属性设置互斥

    int pthread_mutexattr_init(pthread_mutexattr_t *attr);
    int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int kind); 
    

    使用下面的函数解开对互斥的锁定(见 清单 5):

    这里是创建互斥的示例代码(见下面的 67)。


    清单 5. 解锁函数

    pthread_mutex_unlock(pthread_mutex_t *mutex))


    清单 6. Win32 示例代码

    HANDLE mutex;
    mutex = CreateMutex(0, FALSE, 0);
    if (!(mutex))
    {
        return RC_OBJECT_NOT_CREATED;
    }
    


    清单 7. 相应的 Linux 代码

    pthread_mutexattr_t  attr;
    pthread_mutex_t      mutex;
    pthread_mutexattr_init (&attr);
    if (rc = pthread_mutex_init(&mutex, &attr))
    {
        return RC_OBJECT_NOT_CREATED;
    }
    





    回页首


    销毁互斥

    在 Win32 中,CloseHandle() 方法(见 清单 8)可以删除为当前进程中资源提供独占访问控制的对象。删除那个对象后,那个互斥对象就会无效,直到 CloseHandle() 方法通过调用 CreateMutex 重新初始化它。

    当不再对资源进行独占访问后,您应该调用这个方法销毁它。如果您需要放弃那个对象的所有权,那么应该调用 ReleaseMutex() 方法。

    在 Linux 中,pthread_mutex_destroy() 会销毁互斥对象,这会释放它可能会持有的资源。它还会检查互斥在那个时刻是不是解除锁定的(见清单 9)。


    清单 8. Win32 示例代码

    if(WaitForSingleObject(mutex, (DWORD)0) == WAIT_TIMEOUT )
    return RC_NOT_OWNER;
    CloseHandle(mutex);
    


    清单 9. Linux 代码

    if (pthread_mutex_destroy(&mutex) == EBUSY)
    	return RC_NOT_OWNER;
    





    回页首


    锁定互斥

    在 Win32 中,WaitForSingleObject()(见 清单 10)会阻塞对当前进程内资源的独占访问的请求。进程可以通过下面的方式阻塞请求:

    1. 如果独占访问请求的资源没有被锁定,则这个方法锁定它。
    2. 如果独占访问的资源已经被锁定,则此方法阻塞那个调用线程,直到那个资源被解除锁定。

    Linux 使用 pthread_mutex_lock()(见 清单 11)。

    您还可以使用 pthread_mutex_trylock() 来测试某个互斥是否已经被锁定,而不需要真正地去锁定它。如果另一个线程锁定了那个互斥,则 pthread_mutex_trylock 将不会阻塞。它会立即返回错误代码 EBUSY。


    清单 10. Win32 示例代码

    if ((rc = WaitForSingleObject(mutex, INFINITE)) == WAIT_FAILED)
         return RC_LOCK_ERROR;
    


    清单 11. Linux 代码

    if (rc = pthread_mutex_lock(&mutex))
    return RC_LOCK_ERROR;
    





    回页首


    释放或者解锁互斥

    Win32 使用 ReleaseMutex()(见 清单 12)来释放对资源的独占访问。如果进行调用的线程并不拥有那个互斥对象,则这个调用可能会失败。

    Linux 使用 pthread_mutex_unlock() 来释放或者解锁互斥(见清单 13)。


    清单 12. Win32 示例代码

    If (! ReleaseMutex(mutex))
    {
    	rc = GetLastError();
    	return RC_UNLOCK_ERROR;
    }
    


    清单 13. Linux 示例代码

    if (rc = pthread_mutex_unlock(&mutex))
    return RC_UNLOCK_ERROR;





    回页首


    Mutex 示例代码

    这里是获得进程内互斥的 Win32 示例代码(见 Listing 14):


    清单 14. Win32 示例代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <windows.h>
    void  thrdproc  (void *data); //the thread procedure (function) to be executed
    HANDLE    mutex;
    int main( int argc, char **argv )
    {
          int        hThrd;
          unsigned   stacksize;
          HANDLE     *threadId1;
          HANDLE     *threadId2;
          int        arg1;
          DWORD	  rc;
           if( argc < 2 )
    		arg1 = 7;
    	else
    		arg1 = atoi( argv[1] );
    	printf( "Intra Process Mutex test.\n" );
    	printf( "Start.\n" );
    	      mutex = CreateMutex(0, FALSE, 0);
          if (mutex==NULL)
                return RC_OBJECT_NOT_CREATED;
    	printf( "Mutex created.\n" );
          if ((rc = WaitForSingleObject(mutex, INFINITE)) == WAIT_FAILED)
                 return RC_LOCK_ERROR ;
    	printf( "Mutex blocked.\n" );
          if( stacksize < 8192 )
                stacksize = 8192;
            else
                stacksize = (stacksize/4096+1)*4096;
         
          hThrd = _beginthread( thrdproc, // Definition of a thread entry
                                    NULL,
                               stacksize,
                              "Thread 1");
          if (hThrd == -1)
                return RC_THREAD_NOT_CREATED);
          *threadId1 = (HANDLE) hThrd;
          hThrd = _beginthread( thrdproc, // Definition of a thread entry
                                    NULL,
                               stacksize,
                               Thread 2");
          if (hThrd == -1)
                return RC_THREAD_NOT_CREATED);
          *threadId2 = (HANDLE) hThrd;
    	printf( "Main thread sleeps 5 sec.\n" );
    	Sleep( 5*1000 );
          if (! ReleaseMutex(mutex))
          {
              rc = GetLastError();
              return RC_UNLOCK_ERROR;
          }
    	printf( "Mutex released.\n" );
    	printf( "Main thread sleeps %d sec.\n", arg1 );
    	Sleep( arg1 * 1000 );
           if( WaitForSingleObject(mutex, (DWORD)0) == WAIT_TIMEOUT )
                 return RC_NOT_OWNER;
          
           CloseHandle(mutex);
    	printf( "Mutex deleted. (%lx)\n", rc );
    	printf( "Main thread sleeps 5 sec.\n" );
    	Sleep( 5*1000 );
    	printf( "Stop.\n" );
    	return 0;
    }
    void thread_proc( void *pParam )
    {
           DWORD	rc;
    	printf( "\t%s created.\n", pParam );
           if ((rc = WaitForSingleObject(mutex, INFINITE)) == WAIT_FAILED)
                  return RC_LOCK_ERROR;
    	printf( "\tMutex blocked by %s. (%lx)\n", pParam, rc );
    	printf( "\t%s sleeps for 5 sec.\n", pParam );
    	Sleep( 5* 1000 );
    	
           if (! ReleaseMutex(mutex))
           {
              rc = GetLastError();
              return RC_UNLOCK_ERROR;
           }
    	printf( "\tMutex released by %s. (%lx)\n", pParam, rc );
    }
    

    相应的获得进程内互斥的 Linux 示例代码(见 清单 15):


    清单 15. 相应的 Linux 示例代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <pthread.h>
    void  thread_proc (void * data);
    pthread_mutexattr_t     attr;
    pthread_mutex_t 	  mutex;
    int main( int argc, char **argv )
    {
          pthread_attr_t               pthread_attr;
          pthread_attr_t               pthread_attr2;
          pthread_t	            threadId1; 
          pthread_t                    threadId2;
          int	                    arg1;
          int	        	     rc = 0;
    	if( argc < 2 )
    		arg1 = 7;
    	else
    		arg1 = atoi( argv[1] );
    	printf( "Intra Process Mutex test.\n" );
    	printf( "Start.\n" );
    	pthread_mutexattr_init( &attr );
    	if ( rc = pthread_mutex_init( &mutex, NULL))
           {
    	   printf( "Mutex NOT created.\n" );
              return RC_OBJECT_NOT_CREATED;
           }
    	printf( "Mutex created.\n" );
           if (rc = pthread_mutex_lock (&mutex))
           {
    	   printf( "Mutex LOCK ERROR.\n" );
               return RC_LOCK_ERROR;
           }
    	printf( "Mutex blocked.\n" );
           if (rc = pthread_attr_init(&pthread_attr))
           {
    	   printf( "pthread_attr_init ERROR.\n" );
               return RC_THREAD_ATTR_ERROR;
           }
    	if (rc = pthread_attr_setstacksize(&pthread_attr, 120*1024))
           {
    	   printf( "pthread_attr_setstacksize ERROR.\n" );
               return RC_STACKSIZE_ERROR;
           }
    	if (rc = pthread_create(&threadId1,
                               &pthread_attr, 
                (void*(*)(void*))thread_proc, 
                                   "Thread 1" ))
           {
    	   printf( "pthread_create ERROR.\n" );
               return RC_THREAD_NOT_CREATED;
           }
     
           if (rc = pthread_attr_init(&pthread_attr2))
           {
    	   printf( "pthread_attr_init2 ERROR.\n" );
               return RC_THREAD_ATTR_ERROR;
           }
    	if (rc = pthread_attr_setstacksize(&pthread_attr2, 120*1024))
           {
    	   printf( "pthread_attr_setstacksize2 ERROR.\n" );
               return RC_STACKSIZE_ERROR;
           }
    	if (rc = pthread_create(&threadId2,
                              &pthread_attr2, 
                (void*(*)(void*))thread_proc, 
                                   "Thread 2" ))
           {
    	   printf( "pthread_CREATE ERROR2.\n" );
    	   return RC_THREAD_NOT_CREATED;  
           }
            
    	printf( "Main thread sleeps 5 sec.\n" );
    	sleep (5);
           if (rc = pthread_mutex_unlock(&mutex))
           {
    	   printf( "pthread_mutex_unlock ERROR.\n" );
    	   return RC_UNLOCK_ERROR;
           }
    	printf( "Mutex released.\n" );
    	printf( "Main thread sleeps %d sec.\n", arg1 );
    	sleep(arg1);
    	pthread_mutex_destroy(&mutex);
    	
           printf( "Main thread sleeps 5 sec.\n" );
    	sleep( 5 );
    	printf( "Stop.\n" );
    	return 0;
    }
    void thread_proc( void *pParam )
    {
           int	nRet;
    	printf( "\t%s created.\n", pParam );
    	if (nRet = pthread_mutex_lock(&mutex))
           {
    		printf( "thread_proc Mutex LOCK ERROR.\n" );
    		return RC_LOCK_ERROR;
           }
    	printf( "\tMutex blocked by %s. (%lx)\n", pParam, nRet );
    	printf( "\t%s sleeps for 5 sec.\n", pParam );
    	sleep(5);
           if (nRet = pthread_mutex_unlock(&mutex))
           {
    	   printf( " thread_proc :pthread_mutex_unlock ERROR.\n" );
    	   return RC_UNLOCK_ERROR;
           }
    	printf( "\tMutex released by %s. (%lx)\n", pParam, nRet );
    }
    

    这里是获得进程间互斥的另一 Win32 示例代码。

    互斥是系统范围内对象,可以由多个进程使用。如果程序 A 创建一个互斥,则程序 B 可以使用同一个互斥。互斥有名称,并且,一个给定名称的互斥在同一机器上同一时刻只能存在一个。如果您创建了一个名为"My Mutex" 的互斥,则任何其他程序都不能使用这个名称创建互斥,如下面的清单 1618 所示。


    清单 16. Win32 进程间互斥示例代码 Process 1

    #include <stdio.h>
    #include <windows.h>
    #define WAIT_FOR_ENTER  printf( "Press ENTER\n" );getchar()
    int main()
    {
          HANDLE	mutex;
          DWORD   rc;
          printf( "Inter Process Mutex test - Process 1.\n" );
          printf( "Start.\n" );
          SECURITY_ATTRIBUTES    sec_attr;
          sec_attr.nLength              = sizeof( SECURITY_ATTRIBUTES );
          sec_attr.lpSecurityDescriptor = NULL;
          sec_attr.bInheritHandle       = TRUE;
          mutex = CreateMutex(&sec_attr, FALSE, "My Mutex");
          if( mutex == (HANDLE) NULL )
              return RC_OBJECT_NOT_CREATED;
          printf( "Mutex created.\n" );
    	
          WAIT_FOR_ENTER;
          if ( WaitForSingleObject(mutex, INFINITE) == WAIT_FAILED)
               return RC_LOCK_ERROR;
    	printf( "Mutex blocked.\n" );
    	WAIT_FOR_ENTER;
    	
          if( ! ReleaseMutex(mutex) )
          {
                rc = GetLastError();
                return RC_UNLOCK_ERROR;
          }
           printf( "Mutex released.\n" );
    	
           WAIT_FOR_ENTER;
    	CloseHandle (mutex);
          
    	printf( "Mutex deleted.\n" );
    	printf( "Stop.\n" );
    	return OK;
    }
    

    在此,Linux 实现使用的是 System V Interprocess Communications(IPC)函数,如清单 1719 所示。


    清单 17. 相应的 Linux 示例代码 Process 1

    #include <sys/sem.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <unistd.h>
    #define WAIT_FOR_ENTER    printf( "Press ENTER\n" );getchar()
    union semun {
        int                 val;   /* value for SETVAL             */
        struct semid_ds    *buf;   /* buffer for IPC_STAT, IPC_SET */
        unsigned short     *array; /* array for GETALL, SETALL     */
        struct seminfo     __buf;  /* buffer for IPC info          */ 
    };
    main()
    {
          int	       shr_sem;
          key_t 	       semKey;
          struct sembuf   semBuf;
          int		flag;
          union semun      arg;
    	printf( "Inter Process Mutex test - Process 1.\n" );
    	printf( "Start.\n" );
    	flag = IPC_CREAT;
    	if( ( semKey = (key_t) atol( "My Mutex" ) ) == 0 )
    	      return RC_INVALID_PARAM;
    	flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
    	shr_sem  = (int) semget( semKey, 1, flag );
    	if (shr_sem < 0)
    		return RC_OBJECT_NOT_CREATED;
           arg.val = 1;
    	if (semctl(shr_sem, 0, SETVAL, arg) == -1)
    		return RC_OBJECT_NOT_CREATED;
    	printf( "Mutex created.\n" );
    	WAIT_FOR_ENTER;
           semBuf.sem_num = 0;
           semBuf.sem_op = -1;
           semBuf.sem_flg = SEM_UNDO;
           if (semop(shr_sem, &semBuf, 1) != 0)
                    return RC_LOCK_ERROR;
    	printf( "Mutex blocked.\n" );
    	
           WAIT_FOR_ENTER;
           semBuf.sem_num = 0;
           semBuf.sem_op  = 1;
           semBuf.sem_flg = SEM_UNDO;
           if (semop(shr_sem, &semBuf, 1) != 0)
               return RC_UNLOCK_ERROR;
    	printf( "Mutex released.\n" );
    	WAIT_FOR_ENTER;
           semctl( shr_sem, 0, IPC_RMID );
    	printf( "Mutex deleted.\n" );
    	printf( "Stop.\n" );
    	return 0;
    


    清单 18. Win32 进程间示例代码 Process 2

    #include <stdio.h>
    #include <windows.h>
    int main()
    {
          HANDLE      mutex;
          printf( "Inter Process Mutex test - Process 2.\n" );
          printf( "Start.\n" );
          SECURITY_ATTRIBUTES           sec_attr;
          sec_attr.nLength              = sizeof( SECURITY_ATTRIBUTES );
          sec_attr.lpSecurityDescriptor = NULL;
          sec_attr.bInheritHandle       = TRUE;
          mutex = OpenMutex(MUTEX_ALL_ACCESS, TRUE, "My Mutex");
          if( mutex == (HANDLE) NULL )
              return RC_OBJECT_NOT_CREATED;
    	printf( "Mutex opened. \n");
    	printf( "Try to block mutex.\n" );
          if ( WaitForSingleObject(mutex, INFINITE) == WAIT_FAILED)
             return RC_LOCK_ERROR;
    	printf( "Mutex blocked. \n" );
    	printf( "Try to release mutex.\n" );
          if( ! ReleaseMutex(mutex) )
                return RC_UNLOCK_ERROR;
    	
          printf( "Mutex released.\n" );
    	
          CloseHandle (mutex);
          printf( "Mutex closed. \n");
          printf( "Stop.\n" );
          return OK;
    }
    


    清单 19. 相应的 Linux 示例代码 Process 2

    #include <stdio.h>
    #include <sys/sem.h>
    #include <sys/stat.h>
    #include <sys/ipc.h>
    #include <unistd.h>
    int main()
    {
          int             mutex;
          key_t           semKey;
          struct sembuf   semBuf;
          int             flag;
          int	       nRet=0;
          printf( "Inter Process Mutex test - Process 2.\n" );
          printf( "Start.\n" );
            
          flag = 0;
          if( ( semKey = (key_t) atol( "My Mutex" ) ) == 0 )
                return RC_INVALID_PARAM;
          flag |= S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
          mutex = (int) semget( semKey, 1, flag );
          if (mutex == -1)
              return RC_OBJECT_NOT_CREATED;
          printf( "Mutex opened \n");
          printf( "Try to block mutex.\n" );
          semBuf.sem_num = 0;
          semBuf.sem_op = -1;
          semBuf.sem_flg = SEM_UNDO;
          if (semop(mutex, &semBuf, 1) != 0)
              return RC_LOCK_ERROR;
          printf( "Mutex blocked. \n");
          printf( "Try to release mutex.\n" );
          semBuf.sem_num = 0;
          semBuf.sem_op  = 1;
          semBuf.sem_flg = SEM_UNDO;
          if (semop(mutex, &semBuf, 1) != 0)
              return RC_UNLOCK_ERROR;
          printf( "Mutex released. \n");
            
          printf( "Mutex closed. \n");
          printf( "Stop.\n" );
          return 0;
    }
    





    回页首


    结束语

    在本文中,我们介绍了互斥 API 从 Win32 到 Linux 的映射。我们还引用了一系列互斥示例代码来帮助您进行从 Win32 到 Linux 的迁移行动。本系列的下一篇文章将阐述信号量。

    补充声明

    IBM Corporation 1994-2005。保留所有权利。

    本文档中对 IBM 产品或服务的引用并不表示 IBM 想要让它们在所有国家都可用。

    IBM、eServer 和 pSeries 是 IBM Corporation 在美国和/或其他国家或地区的商标。

    Microsoft、Windows、Windows NT 和 Windows 徽标是 Microsoft Corporation 在美国和/或其他国家或地区的商标或注册商标。

    Intel、Intel Inside(logos)、MMX 和 Pentium 是 Intel 公司在美国和/或其他国家或地区的商标。

    UNIX 是 The Open Group 在美国和其他国家或地区的注册商标。

    Linux 是 Linus Torvalds 在美国和/或其他国家或地区的商标。

    其他公司、产品或服务名称可能是其他公司的商标或服务标记。

    信息都是"按原样"发布,没有任何类型的保证。

    所描述的所有的客户示例只是为了说明那些客户如何使用 IBM 产品,以及它们可能获得的结果。不同客户所得到的实际环境代价和性能特性可能会不同。

    涉及非 IBM 产品的信息可从这些产品的供应商、其出版说明或其他可公开获得的资料中获取,并不构成 IBM 对此产品的认可。非 IBM 价目及性能数字资源取自可公开获得的信息,包括供应商的声明和供应商的全球主页。 IBM 没有对这些产品进行测试,也无法确认其性能的精确性、兼容性或任何其他关于非 IBM 产品的声明。有关非 IBM 产品性能的问题应当向这些产品的供应商提出。

    所有关于 IBM 未来方向或意向的声明都可随时更改或收回,而不另行通知,它们仅仅表示了目标和意愿而已。联系您本地的 IBM 办公人员或者 IBM 授权的转销商,以获得特定的 Statement of General Direction 的全文。

    这里所包含的信息可能陈述了预期的未来功能。上述信息并不打算作为对任何未来产品的特定性能级别、功能或交付时间表的明确承诺。这样的承诺只会在 IBM 产品中作出。这里出现的信息用于表明 IBM 当前的投资和发展活动,作为一种信任,来帮助我们的客户规划未来。

    性能是在受控环境中使用标准的 IBM 基准程序测试和估算的。任何用户实际的吞吐量或性能可能各不相同,这取决于需要考虑的事项,例如用户作业流中的多道程序设计总量、I/O 配置、存储器配置和处理的工作负载。因此,我们不能担保,个别用户所获得的吞吐量或性能改善等同于这里所列的值。



     

    参考资料



     

    作者简介

    Nam Keung 是 IBM 的一名高级程序员,他曾致力于 AIX 通信开发、AIX 多媒体、SOM/DSOM 开发和 Java 性能方面的工作。他目前的工作包括帮助独立软件提供商(Independent Software Vendors,ISV)进行应用程序设计、部署应用程序、性能调优和关于 pSeries 平台的教育。您可以通过 namkeung@us.ibm.com 与 Nam 联系。


    Chakarat Skawratananond 是 IBM eServer Solutions Enablement 组织的一名技术顾问,在那里,他帮助独立软件开发商在 IBM pSeries 平台上使用他们的用于 AIX 和 Linux 的应用程序。您可以通过 chakarat@us.ibm.com 与 Chakarat 联系。

    July 25

    软件工程师不可不知的10个概念

    "出色的软件工程师善用设计模式,勤于代码重构,编写单元测试,并对简单有宗教般的追求。除了这些,优秀的软件工程师还要通晓10个概念,这10个概念超越了编程语言与设计模式,软件工程师应当从更广的范围内明白这些道理(全文阅读):
    1. 界面 (Interfaces )
    2. 惯例与模板 (Conventions and Templates)
    3. 分层 (Layering )
    4. 算法的复杂性 (Algorithmic Complexity)
    5. 散列法 (Hashing )
    6. 缓存 (Caching )
    7. 并发 (Concurrency )
    8. 云计算(Cloud Computing )
    9. 安全(Security )
    10. 关系数据库 (Relational Databases )
    10. 关系数据库 (Relational Databases)

    关系数据库因为在大规模 Web 服务上缺乏可扩充性而颇受微词,然而,关系数据库仍然是近20年来计算机技术中最伟大的成就。关系数据库对处理订单,公司数据方面有着出色的表现。

    关系数据库的核心是以记录表示数据,记录存放在数据库表,数据库使用查询语言(SQL)对数据进行搜索与查询,同时,数据库对各个数据表进行关联。

    数据库的标准化技术(normalization)讲的是使用正确的方式对数据进行分存以降低冗余,并加快存取速度。

     9. 安全 (Security)

    随着黑客的崛起与数据敏感性的上升,安全变得非常重要。安全是个广义的概念,涉及验证,授权与信息传输。

    验证是对用户的身份进行检查,如要求用户输入密码。验证通常需要结合 SSL (secure socket layer)进行;授权在公司业务系统中非常重要,尤其是一些工作流系统。最近开发的 OAuth 协议可以帮助 Web 服务将相应信息向相应用户开放。Flickr 便使用这种方式管理私人照片和数据的访问权限。

    另外一个安全领域是网络设防,这关系到操作系统,配置与监控。不仅网络危险重重,任何软件都是。Firefox 被称为最安全的浏览器,仍然需要频频发布安全补丁。要为你的系统编写安全代码就需要明白各种潜在的问题。

     8. 云计算 (Cloud Computing)

    RWW 最近的关于云计算的文章 Reaching For The Sky Through Compute Clouds 讲到了云计算如何改变大规模 Web 应用的发布。大规模的并行,低成本,与快速投入市场。

    并行算法发明以来,首先迎来的是网格计算,网格计算是借助空闲的桌面计算机资源进行并行计算。最著名的例子是 Berkley 大学的 SETI@home 计划,该计划使用空闲的 CPU 资源分析太空数据。金融机构也大规模实施网格计算进行风险分析。空闲的资源,加上 J2EE 平台的崛起,迎来了云计算的概念:应用服务虚拟化。就是应用按需运行,并可以随着时间和用户规模而实时改变。

    云计算最生动的例子是 Amazon 的 Web 服务,一组可以通过 API 进行调用的应用,如云服务(EC2),一个用来存储大型媒体文件的数据库(S3),索引服务(SimpleDB),序列服务(SQS)。

     7. 并发 (Concurrency)

    并发是软件工程师最容易犯错的地方,这可以理解,因为我们一直遵从线形思维,然而并发在现代系统中非常重要。

    并发是程序中的并行处理,多数现代编程语言包含内置的并发能力,在 Java,指的是线程。关于并发,最经典的例子是"生产/消费"模式,生产方生产数据和任务,并放入工作线程消费或执行。并发的复杂性在于,线程需要经常访问共同数据,每个线程都有自己的执行顺序,但需要访问共同数据。Doug Lea 曾写过一个最复杂的并发类,现在是 core Java 的一部分。

    6. 缓存(Caching)

    缓存对现代 Web 程序不可或缺,缓存是从数据库取回,并存放在内存中的数据。因为数据库直接存取的代价非常高,将数据从数据库取回并放在缓存中访问就变得十分必要。比如,你有一个网站,要显示上周的畅销书,你可以从数据将畅销书榜一次性取回放在缓存中,而不必在每次访问时都去数据库读数据。

    缓存需要代价,只有最常用的内容才可以放入缓存。很多现代程序,包括 Facebook,依靠一种叫做 Memcached 的分布式缓存系统,该系统是 Brad Firzpatrick 在工作于 LiveJournal 项目时开发的,Memcached 使用网络中空闲的内存资源建立缓存机制,Memcached 类库在很多流行编程语言,包括 Java 和 PHP 中都有。

    5. 散列法(Hashing)

    Hashing 的目的是加速访问速度。如果数据是序列存储的,从中查询一个项的时间取决于数据列的大小。而散列法对每一个项计算一个数字作为索引,在一个好的 Hashing 算法下,数据查找的速度是一样的。

    除了存储数据,散列法对分布式系统也很重要。统一散列法(uniform hash )用来在云数据库环境下,在不同计算机之间分存数据。Google 的索引服务就是这种方法的体现,每一个 URL 都被散列分布到特定计算机。

    散列函数非常复杂,但现代类库中都有现成的类,重要的是,如何对散列法进行细调以获得最好的性能。

    4. 算法的复杂性 (Algorithmic Complexity)

    关于算法的复杂性,软件工程师需要理解这样几件事。第一,大O标记法(big O notation);第二,你永远都不应该使用嵌套式循环(循环里面套循环),你应该使用 Hash 表,数组或单一循环;第三,如今优秀类库比比皆是,我们不必过分纠缠于这些库的效能的差别,我们以后还有机会进行细调;最后,不要忽视算法的优雅及性能,编写紧凑的,可读的代码可以让你的算法更简单,更干净。

     3. 分层 (Layering)

    用分层来讨论软件架构是最容易的。John Lakos 曾出版过一本关于大型 C++ 系统的书。Lakos 认为软件包含了层,书中介绍了层的概念,方法是,对每个软件组件,数一下它所依赖的组件数目就可以知道它的复杂程度。

    Lakos 认为,一个好的软件拥有金字塔结构,就是说,软件组件拥有层层积累的复杂度,但每个组件本身必须简单,一个优秀的软件包含很多小的,可重复使用的模块,每个模块有自己的职责。一个好的系统中,组件之间的依赖性不可交叉,整个系统是各种各样的组件堆积起来,形成一个金字塔。

    Lakos 在软件工程的很多方面都是先驱,最著名的是 Refactoring (代码重构)。代码重构指的是,在编程过程中需要不断地对代码进行改造以保证其结构的健壮与灵活。

     2. 惯例与模板 (Conventions and Templates)

    命名惯例和基础模板在编程模式中常被忽视,然而它可能是最强大的方法。命名惯例使软件自动化成为可能,如,Java Beans 框架在 getter 和 setter 方法中,使用简单的命名惯例。del.icio.us 网站的 URL 命名也使用统一的格式,如 http://del.icio.us/tag/software 会将用户带到所有标签为 software 的页。

    很多社会网络均使用简单命名,如,你的名字是 johnsmith ,那你的头像可能命名为 johnsmith.jpg,而你的 rss 聚合文件的命名很可能是 johnsmith.xml 。

    命名惯例还用于单元测试,如,JUnit 单元测试工具会辨认所有以 test 开头的类。

    我们这里说的模板(templates )指的并不是 C++ 或 Java 语言中的 constructs,我们说的是一些包含变量的模板文件,用户可以替换变量并输出最终结果。

    Cold Fusion 是最先使用模板的程序之一,后来,Java 使用 JSP 实现模板功能。Apache 近来为 Java 开发了非常好用的通用模板, Velocity。PHP 本身就是基于模板的,因为它支持 eval 函数。

    1. 界面(Interfaces)

    软件工程中最重要的概念是界面。任何软件都是一个真实系统的模型。如何使用简单的用户界面进行模型化至关重要。很多软件系统走这样的极端,缺乏抽象的冗长代码,或者过分设计而导致无谓的复杂。

    在众多软件工程书籍中,Robert Martin 写的《敏捷编程》值得一读。

    关于模型化,以下方法对你会有帮助。首先,去掉那些只有在将来才可能用得着的方法,代码越精练越好。第二,不要总认为以前的东西是对的,要善于改变。第三,要有耐心并享受过程。