Tony Bai
来源: BlogBus 原始链接: http://bigwhite.blogbus.com:80/logs/2005/12/ 存档链接: https://web.archive.org/web/20060118105312id_/http://bigwhite.blogbus.com:80/logs/2005/12/
Tony Bai 一个程序员的心路历程 ANSI C; C++; Java; Linux 首页 分页 2005-12-29 11:22 2005腊月靓乐 我喜欢音乐,所以我也来说说音乐。 最近晚上休息时一直在听中央人民广播电台的音乐之声(Music Radio)栏目,听到心仪的歌曲马上就下载到本本中,慢慢欣赏。下面是一些听后感^^: 激情澎湃 - 汪峰 之 ' 怒放的生命' 最开始知道汪峰是通过其作品“花火”,其高亢澎湃的嗓音让我想起了演唱'超越梦想'的汪正正。'怒放的生命'是在其'飞得更高'之后的又一表现其强烈心声的歌曲,经历过挫折、痛苦、迷茫和彷徨的汪峰终于开始觉醒了,并用这首'怒放的生命'毫无保留的向人们展示他对未来生活的愿景。相信有着和汪峰同样经历的人听到这首歌后必定会产生心灵的共振。另外汪峰那带有强烈穿透力的嗓音也必然会给你带来与众不同的音乐体验。 摆正舌头 - 周杰伦 之 ' 珊瑚海' 虽然对以前周杰伦的某些歌曲颇有微词,但我也不得不承认周杰伦在音乐方面的天赋和造诣。不久前周杰伦推出的其新专辑'十一月的肖邦'又在歌迷中引起轰动,其中多首歌曲都在各大排行榜上名列前茅,如'夜曲'、'发如雪'等,但是最让我欣赏的一首歌却是'珊瑚海',这是一首周杰伦与梁心颐合作的男女对唱的歌曲,之所以欣赏这首歌一是因为其是对唱歌曲(在KTV中偶最喜欢此类歌曲了^^),再有一点就是在这首歌中周杰伦一贯打卷的舌头终于伸直了 -- 吐字清晰了,那就意味着这首歌是可以被大众化的。另外这首歌的旋律优美,歌词细品起来也颇有味道。 初出茅庐 - 华少弈 之 ' 爱还在' 在最近的Music Radio中总听到一首男女对唱的歌,叫'爱还在'。初听该歌的时候我一直以为那个男生是孙楠,因为其声音真的与孙楠的声音有多分神似,特别是在后面的歌曲高潮时。到Baidu去搜索后才发现这是一位新人华少弈的作品,而另外的那个女生居然是一个韩国人,看来我的听力需要提高提高了。以前很喜欢孙楠的歌,每到KTV必唱。华少弈的嗓音是那么的纯净和富有磁性,这也正是我最爱的风格,而且在这首歌中无论是高潮还是低吟,华少弈的演唱都是那么完美。该首歌曲的另外一个特点就是那个韩国歌手略带沙哑的嗓音和华少弈纯净的嗓音相得益彰,恰到好处。唯一的一点缺陷是在结尾处那个女生的一次高音爆音给整首歌曲带来那么一丝缺憾。 [注]: 农历月份的别称 一月:正月 二月:杏月 三月:桃月 四月:梅月 五月:榴月 六月:荷月 七月:兰月 八月:桂月 九月:菊月 十月:良月 十一月:畅月 十二月:腊月 bigwhite @ 11:22 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-28 13:24 圣诞后说 圣诞节没来得及“说”,俺就来个“圣诞后说”^^。 又是一年圣诞节,又是考验国民消费能力的日子,不过从统计数据来看,没有让大家失望,以沈阳为例,著名商业街“中街”几个大商场一夜的销售额就过亿。看来大家都比我有钱,呵呵。 圣诞节也是一个考验肚子的日子,和GF从早到晚吃个不停。中午烤鸭,晚上南美烤肉。这回儿可往肚子里灌满了“油水”。 圣诞节也是个“短信爆炸”的日子,我的手机真是忙个不停,鉴于词语表达上的不过关,只能将好友A发来的“美辞”,转发给好友B、C、D...^^。 圣诞节也是个考验公共交通系统能力的日子,看着一辆辆挤满了乘客的公交车从面前驶过,心里真是“胆战心惊”亚,可是过不多久,又在一辆挤满了乘客的公交车上发现了我的身影。 圣诞节还是个大片云集的日子,各大影院争相上映叫什么“无聊之极”的大片,实在对不起,没记住该部片名,只记住网上对该片的评论题目了。还好公司发的团体电影票圣诞节无效,要不我也有可能成为“无聊之极”看片大军中的一员了。 圣诞节总是有好消息的,这里念上一条:“我的一位大学兄弟于近日开通了他的blog-" 岁岁年年 ",还希望大家多多捧场”。 圣诞节也有让人郁闷的消息,这里也说说。最近特想买的两本书《深入理解Linux内核》和《Linux内核源代码情景分析(上)》在 互动出版网 和 华储网 都已经划归为“绝版”行列。看来一段时间只能看电子版了。 再说一条与圣诞节无关的消息。一直想弄到一份Linux发行版的安装光盘,Redhat已经太老,Fedora由于是开源,少有安装盘。经过一番努力还是发现了另一种很有名的发行版 Ubuntu 。Ubuntu Linux可以免费为你寄送安装光盘,我已经订购了一些,相关信息可以在其官方网站上找到。 bigwhite @ 13:24 | 阅读全文 | 评论(1) | 引用Trackback(0) | 编辑 2005-12-17 10:36 失望的“火焰杯” 英国女作家“罗琳”的小说着实让我“着迷”一番。从第一部“魔法石”到第五部“凤凰令”,每部小说我都会认真拜读,至于同名电影我也“同步”地看了3部,这不刚刚把第4部电影“火焰杯”看完。 至今为止最吸引我的两部是“魔法石”和“火焰杯”。历来畅销小说的开山之作总是被誉为经典,“哈利波特”也不例外,“魔法石”一部让我们认识了善良、正义和勇敢的小哈利,也大致知道了整部小说的线索是“哈利与伏地魔之间的斗争”。在出版了略微沉闷的“密室”和“阿兹卡班”囚徒之后,“火焰杯”则再次向我们展示了魔法世界的精彩,如“魁地奇”世界杯、“三巫师斗法”以及在前3部都未提到的另外的两个魔法学校。书中的精彩让我很是期盼其同名电影的出版。就在昨天我看完了这部电影,谈起感受,用两个字来形容就是“失望”。不得不承认“火焰杯”是一部“大部头”的作品,要在短暂的区区不到两个半小时的时间里完全展现出小说中的“亮点”绝对是很有挑战的,不过“火焰杯”的导演还是让我很失望。 “一笔带过”的“魁地奇”世界杯 可以说“魁地奇”世界杯是魔法世界中水平最高的“魁地奇”比赛,以往我们都只看到小哈利在学校里参加的院际联赛,虽说精彩但那毕竟不能代表最高水平,又有幸看一眼世界杯这么高水平的比赛我想也是哈利波特迷的一个愿望,可是导演没给我们这个机会,在短暂不乏精彩的出场仪式后,“魁地奇”世界杯便迅速从我们的眼前消失了。 “告别经典罗恩” 罗恩在我印象中一贯是“傻头傻脑、资质平庸但又不乏笑料”的人物角色,但是在这部电影中我们却几乎看不到他那“经典”的笑料了(除了假魔眼给学生们上黑魔法课一段),相反长大后的罗恩却多了份儿女情长,真是大煞风景。 “平淡的伏地魔复活” 书中的伏地魔复活是整个哈利波特故事的一个转折点,从此哈利波特故事便开始走向“黑色”。对于这一情节书中自然是浓重渲染,而片中伏地魔的复活显然过于迅速,如果没有读过小说的人可能很是迷惑。另外哈利与伏地魔的决斗也显然没有书中的那份精彩,当哈利的父母从魔仗中出来时有点过于朦胧,让我很难看清那些人是谁。 影片拍到现在我也不对后面的几部抱有太大希望了,还是抱着那几本罗琳的“大部头”细细体味魔法世界的精彩吧。 bigwhite @ 10:36 | 阅读全文 | 评论(2) | 引用Trackback(0) | 编辑 2005-12-16 10:07 说说电影 我是个不折不扣的电影迷,特别喜欢看欧美的科幻片、喜剧片和中国的剧情片,以前看电影都仅仅停留在看看乐乐就完了的阶段,现在有了blog这个工具,遂想把自己的一些看电影后的体会写出来,不吐不快么^_^,另外注意哦这不是影评。 让我产生上面想法是索尼的“A Sound Of Thunder”这部2005年科幻片,其中文名为“雷霆万钧”或“一声惊雷”。知道这部片子源于同事的介绍,在他的描述中这部片子很精彩。科幻片我的最爱,怎能放过。科幻片最大的吸引力就在它让你的想象力得到了充分的满足,越是能激发我想象力的片子我就越喜欢。显然“A Sound Of Thunder”做到了这一点。尤其是其“时间波”理论让我产生了浓厚的兴趣,对其的不是十分理解甚至让我产生重新学习大学物理的想法(还有行动:早上起来后到MIT OCW上下载了一些物理课程)。下面具体谈一下我的一些观感: 影片阵容:不得不承认里面的演员我一个都不认识,也许我还不是“骨灰级”电影Fans,不过在网上查了一下,其中男主角爱德华·伯恩斯还是好莱坞的一个知名演员,那个女科学家似乎有些眼熟,但就是不知道她在那部电影中出镜过。 影片剧情:上面也提到过这是这部电影最吸引我的地方,仅从第一印象来说剧情还算合理,当然百密一疏,在好看的电影都有其纰漏之处,呵呵我没找到罢了(仅看了一遍,火候还不到)。在剧情上还是有些地方让我不甚满意的,比如女科学家再给大家讲解如何利用下一次时间波而到达杀死蝴蝶的前一刻那段,女科学家的讲解我想很多人都很糊涂,我想导演是为了表现出“时间的紧迫性”而加快这部分的进度,再者“时间波”这么高深的理论,没有一定的“功底”是很难明白的,导演的想法也许就是让大家模模糊糊这样才会激发观众的想象力和继续观赏的动力,这让我想起了影片“骇客帝国”,虽然三部看完,又有多少人能够完全理解其深刻含义呢,不过其票房已经说明了其拍摄的成功。 技术手段:科幻片总是离不开电脑特技的,遗憾的是这部片子在这方面做的让人很是失望。明眼人一眼就可以看出电脑特技的痕迹,特别是那段男主角在车水马龙的芝加哥大街上走的时候,其身体明显和背景难以融合。至于其他场景也并不是那么精致,只能用“马马虎虎”来形容了。 总之“A Sound Of Thunder”是2005年唯一一部能强烈激发我的想象力的科幻影片,用一句话评价它就是“继经典的影片“时间机器”后又一部在“时空”理论上给人们带来想象空间的影片。 bigwhite @ 10:07 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-15 11:00 在Linux上工作 在Linux上学习Linux内核我想应该是最好的方法了。Linux对我来说绝对是一个新鲜环境,搭建在Linux上的工作环境就是我的首要工作,这篇blog记录的就是我在Linux上的工作环境,也希望对大家有些借鉴意义。 我的Linux是在一个多月以前安装的[注1],安装的版本是Fedora Core 4。我使用的是本地磁盘映像安装,磁盘映像文件很大,总共4个,大约2.4G体积。安装过程倒是没有像网上很多人说得那样不顺利,包括修改、合并分区在内大约用了3个小时就看到Linux的桌面了。 进入Linux首先映入眼帘的的就是Linux桌面,我选择了GNOME(GNU Network Object Model Environment)桌面,不为什么,就是因为它流行。下一步就是熟悉这个新环境了,如基本的系统设置、网络设置以及个性化定制等,这些不详述。 工作环境是一个常用软件的集合,在Windows下自不必说了,那些软件都是耳熟能详了。但是在Linux下又有哪些软件可以作为替代品呢?带着这样的目的,我开始了搭建Linux工作环境的历程。另外王垠( http://learn.tsinghua.edu.cn:8080/2001315450 )曾在其主页上介绍过不少好用的工具软件,这里很多软件也都是源于王垠的介绍。 Linux下的软件安装一般有两种方法: (1) 通过rpm方式 安装:rpm -i your-package.rpm 卸载:rpm -e your-package (2) 通过源代码编译方式 源代码编译三部曲:configure --> make --> make install 我的Linux工作环境 (1) 强大的Bash 以前在Solaris上开发使用的都是C shell,而Linux默认的Shell却是Bash Shell。我初始感觉Bash Shell与C Shell不同之处包括可以自动匹配补齐命令行、支持UP和DOWN ARROW来选择前一个和后一个命令行。对于一个非系统工程师的开发人员来说有一份得心应手的Shell配置文件足矣。下面是我的一份配置文件,简单而灵活,关键一点是它完全能够满足我的需求: /* .bashrc */
Tony Bai's .bashrc
Source global definitions
if [ -f /etc/bashrc ]; then . /etc/bashrc # --> Read /etc/bashrc, if present. fi
Greetings
echo "" echo " This is Tony Bai " echo " Welcome to my linux world " echo "" function _exit() # function to run upon exit of shell { echo "" echo " Bye Bye! " echo " Welcome Back " echo "" } trap _exit EXIT
Export environment variables
CVSROOT=:pserver:tony@127.0.0.1:/export/home/cvs/CVS-ROOT PROJDIR=/home/administrator/proj/example PATH=.:$PATH:$HOME/bin:.local/bin export CVSROOT export PROJDIR
User specific aliases and functions
System command set
alias rm='rm -i' alias mv='mv -i' alias mkdir='mkdir -p' alias h='history' alias which='type -all' alias ..='cd ..' alias path='echo -e ${PATH//:/\n}' alias du='du -kh' alias df='df -kTh' alias la='ls -Al' # show hidden files alias ls='ls -hF --color' # add colors for filetype recognition alias lx='ls -lXB' # sort by extension alias lk='ls -lSr' # sort by size alias lc='ls -lcr' # sort by change time alias lu='ls -lur' # sort by access time alias lr='ls -lR' # recursive ls alias lt='ls -ltr' # sort by date alias lm='ls -al |more' # pipe through 'more'
Compile
alias gcc='gcc -Wall'
System info Viewer
alias cpu='cat /proc/cpuinfo' alias mem='cat /proc/meminfo' alias version='cat /proc/version' alias ipconfig='/sbin/ifconfig'
Project info
alias cdinc='cd $PROJDIR/include' alias cdsrc='cd $PROJDIR/src' 另外修改.bashrc后别忘了执行'bash'使配置修改生效。 (2) 输入法 毕竟是开发中文程序,中文输入发必不可少。虽觉得Fedora自带的“智能拼音”不错,但是“小企鹅输入法(free Chinese Input Toy for X)”的定制功能却让我更加垂涎。遂在小企鹅输入法网站 上下载了专门为Fedora Core 4制作的rpm。安装后我们就可以修改~/.fcitx/config文件来订制你个性化的输入法了,如果你在Windows上使用微软输入法习惯了,我们完全可以把“小企鹅输入法”变成Linux上的“微软输入法”。 (3) 浏览器 无论在任何平台上我们都不能忽略网络世界的存在,在Windows上有IE,在Linux上我们有Mozilla Firefox这一新宠儿。关于Firefox的资料太多太多,我想这里就毋庸讳言了。 (4) 邮件工具 Evolution, 一款在使用习惯上颇为接近于Microsoft Outlook的邮件客户端及个人信息管理程序,如果你是用惯了Outlook的用户,那么Evolution将是你在Linux上的一个不错的选择。Evolution是Linux自带的程序,无需你下载安装了。 (5) 编辑器 对于一名程序员来说获得一得心应手的编辑器就好比如虎添翼一般。Linux给你提供了多种选择,既有图形界面的,又有基于终端的。不过VI/VIM仍然是我的最爱。 (6) 开发工具 由于做后台服务端开发,所以必不可少的需要Gcc, make等工具, Linux上还默认提供automake, autoconf等工具,免去了你手工编写Makefile的烦恼,不过要掌握这些工具也需要一个过程,自己权衡吧^_^。 (7) 词典工具 王垠在其文章中提到了WordNet,对该软件的新颖的概念很是感兴趣,遂down了一个,不过遗憾的是没有编译通过,至今未找到原因。 (8) 娱乐工具 程序员在工作之余都喜欢看看电影,而 MPlayer 又是被公认在Linux下最好的媒体播放软件。遗憾的是我的机器上没有声卡,不能听到MPlayer输出的优美音乐。 (9) 办公工具 由于公司的文档都是由微软的工具产生的,要想在Linux下阅读和修改可不是件容易事。试过了Linux自带的OpenOffice,PPT文档还可以,Word文档简直就不堪入目了。王垠推荐将Word等先转换为html网页再查看,我很懒嫌麻烦。想起金山最新推出的WPS2005在Windows下的效果还不错,希望金山也能尽快推出Linux下的WPS版本。来解决这一使用Linux办公的最大难题。 (10) 通讯工具 对于使用QQ的人,LumaQQ相信是最好的选择;而Gaim是一个支持多种IM协议的工具,只是上手不是很容易罢了。 初接触Linux,试用了上面的一些工具,还处于经验积累阶段。 [注1] 我是参考 http://fedora.Linuxsir.org 上的安装说明一步一步做的,感觉还不错。 bigwhite @ 11:00 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-13 14:49 差异学习 看了dreamhead的那篇“差异程序员”,又恰逢在今天dreamhead在一封邮件中谈到其继续深入“向下学习”的想法,心里突然有了本篇题目这样的一个话题。 “差异程序员”( http://dreamhead.blogbus.com/logs/2005/12/1676755.html ) 最近自己也在“向下走”,这和dreamhead的想法和做法不谋而合。dreamhead在其blog和邮件中都谈了其在“向下走”过程中的体会,“差异程序员”一篇dreamhead也是在告诉大家其不满足于只做应用一层的程序员,这也同样是我的一些想法。 想法有了,那我们是如何做的呢?mail中dreamhead谈到了他下一步的学习目标和计划,在我的回复mail中我也谈了我将在linux内核方面和编译技术方面作些努力的初步想法。dreamhead也马上谈了他关于这两方面的看法,这里引用少许,目的在对比: “编译器的前端相对来说都是简单的,技术基本都是现成的,而且有现成的工具供人使用,当然,为了学习,还是自己尝试写一遍比较好。后端的东西才是真正复杂的,不同的平台不同的OS都会有不同的要求,之后,还有优化的技术,复杂度直线上升。... ... 如果想了解OS运作,Linux内核显然过于庞大了,那本《自己动手写操作系统》则更为简单。一些基础的东西是类似的,只要理解了那个小内核,理解Linux只是更好的去体会更好的设计而已,因为所有的知识已经都各就其位了,一旦架构形成,剩下的只是不断完善了。” 看到这样的回复,我马上意识到:虽然都是向下走,但是我和dreamhead的目的不同,形成的想法就有了些差异。 我之所以想啃下编译,原因起初有二:一是在前不久的工作中想采用编译中的技术来完成项目中一个模块的功能;二是填补大学时没有学懂编译的遗憾,就是想弄懂那些三元式、四元式,让自己更专业一些。上星期看了“dragon book[注1]”作者的一席话又为我学习编译增加了一枚筹码,其大致意思就是:大多数学习编译的人一生都可能没有机会去创造一门语言,那么我们为什么还学习编译呢?其中一个原因就是编译技术中某些原理可以适用于一般应用软件的设计。比如词法分析器中的字符串匹配技术可用于文本编辑器、信息获取系统或者模式识别程序中。 对于学习linux内核我有以下几点考虑:首先一直在用户层使用内核提供的系统调用,比如fork,在很多Unix编程书中会讲到调用fork后子进程与父进程的异同,这些几乎就是应用程序员必须牢记的东西,一直很讨厌强记,遂想刨根问底的去看看fork的一些实现,这样弄清了其来龙去脉就再无须强记了,而弄清了这些后反过来又会让你更好的使用这些系统调用;其次,想在用户层程序中借鉴内核的优秀设计思想,比如缓冲技术,在内核中有在应用层也有,应用层完全可以参考内核中的某些优秀设计来实现;最后,了解操作系统内核会让你对计算机的体系的理解有一个质的飞跃。即使是计算机本科毕业的人又有多少敢说自己完全理解了计算机呢。 dreamhead以了解OS运作为目的向我们推荐《自己动手写操作系统》一书自然没错,带着这样的目的来学习这本书效果肯定也不错;但是对于我上面所说的那些目的,这本书也仅能满足一小部分,该书可以作为整个学习过程中的一个参考资料。这就是由于学习目的不同带来的一些差异性的东西。 差异学习没有对错之分,也没有好坏之分,只是因目的不同而已罢了。目的不同,学习的关注点和着重点就会不同,这样即使学习同一样的技术效果也不同。另外学习不是孤立的,沿着学习的主线方向会有很多旁支,如学习linux内核,你将会了解到CPU体系结构、存储器管理和算法理论等多方面的知识。 最近看到某一电视台播放的央视版“笑傲江湖”,情节中提到华山剑宗与气宗之争,当时自己就考虑“为何两派不二者兼修以达到前所未有之境界呢”,在后来的令狐冲无心插柳却达到了这一境界,想必那些在两宗相争中冤死的华山前辈们看到这一结局都后悔之极了吧。(开个玩笑,其实从古自今大凡争斗之事多源自“权势之争”)。令狐冲成为了绝顶高手又让我想到了“差异程序员”,要想成为“程序界”的令狐冲又何尝不需要“上下”兼修呢?起码dreamhead已经为我们作出了表率。 [注1] “dragon book” -- 《Compilers: Principles, Techniques and Tools》by Aho, Sethi and Ullman bigwhite @ 14:49 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-11 18:29 APR分析-线程同步篇 在线程同步方面,Posix标准定义了3种同步模型,分别为互斥量、条件变量和读写锁。APR也“浅”封装了这3种模型,只是在“读写锁”一块儿还没有全部完成。 线程同步的源代码的位置在$(APR_HOME)/locks目录下,本篇blog着重分析unix子目录下的thread_mutex.c、thread_rwlock.c和thread_cond.c文件的内容,其相应头文件为(APR_HOME)/include/apr_thread_mutex.h、apr_thread_rwlock.h和apr_thread_cond.h。 由于APR的封装过于“浅显”,实际上也并没有多少值得分析的“靓点”。所以本篇实际上是在讨论线程同步的3种运行模型。 一、互斥量 互斥量是线程同步中最基本的同步方式。互斥量用于保护代码中的临界区,以保证在任一时刻只有一个线程或进程访问临界区。 1、互斥量的初始化 在POSIX Thread中提供两种互斥量的初始化方式,如下: (1) 静态初始化 互斥量首先是一个变量,Pthread提供预定义的值来支持互斥量的静态初始化。举例如下: static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 既然是静态初始化,那么必然要求上面的mutex变量需要静态分配。在APR中并不支持apr_thread_mutex_t的使用预定值的静态初始化(但可以变通的利用下面的方式进行静态分配的mutex的初始化)。 (2) 动态初始化 除了上面的情况,如果mutex变量在堆上或在共享内存中分配的话,我们就需要调用一个初始化函数来动态初始化该变量了。在Pthread中的对应接口为pthread_mutex_init。APR封装了这一接口,我们可以使用下面方式在APR中初始化一个apr_thread_mutex_t变量。 apr_thread_mutex_t *mutex = NULL; apr_pool_t *pool = NULL; apr_status_t stat; stat = apr_pool_create(&pool, NULL); if (stat != APR_SUCCESS) { printf("error in pool %d\n", stat); } else { printf("ok in pool\n"); } stat = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool); if (stat != APR_SUCCESS) { printf("error %d in mutex\n", stat); } else { printf("ok in mutex\n"); } 2、互斥锁的软弱性所在 互斥锁之软弱性在于其是一种协作性锁,其运作时对各线程有一定的要求,即“所有要访问临界区的线程必须首先获取这个互斥锁,离开临界区后释放该锁”,一旦某一线程不遵循该要求,那么这个互斥锁就形同虚设了。如下面的例子: 举例:我们有两个线程,一个线程A遵循要求,每次访问临界区均先获取锁,然后将临界区的变量x按偶数值递增,另一个线程B不遵循要求直接修改x值,这样即使在线程A获取锁的情况下仍能修改临界区的变量x。 static apr_thread_mutex_t *mutex = NULL; static int x = 0; static apr_thread_t *t1 = NULL; static apr_thread_t *t2 = NULL; static void * APR_THREAD_FUNC thread_func1(apr_thread_t *thd, void *data) { apr_time_t now; apr_time_exp_t xt; while (1) { apr_thread_mutex_lock(mutex); now = apr_time_now(); apr_time_exp_lt(&xt, now); printf("[threadA]: own the lock, time[%02d:%02d:%02d]\n", xt.tm_hour, xt.tm_min, xt.tm_sec); printf("[threadA]: x = %d\n", x); if (x % 2 || x == 0) { x += 2; } else { printf("[threadA]: Warning: x变量值被破坏,现重新修正之\n"); x += 1; } apr_thread_mutex_unlock(mutex); now = apr_time_now(); apr_time_exp_lt(&xt, now); printf("[threadA]: release the lock, time[%02d:%02d:%02d]\n", xt.tm_hour, xt.tm_min, xt.tm_sec); sleep(2); } return NULL; } static void * APR_THREAD_FUNC thread_func2(apr_thread_t *thd, void *data) { apr_time_t now; apr_time_exp_t xt; while (1) { x ++; now = apr_time_now(); apr_time_exp_lt(&xt, now); printf("[threadB]: modify the var, time[%02d:%02d:%02d]\n", xt.tm_hour, xt.tm_min, xt.tm_sec); sleep(2); } return NULL; } int main(int argc, const char * const * argv, const char * const env) { apr_app_initialize(&argc, &argv, &env); apr_status_t stat; //... /
- 创建线程 / stat = apr_thread_create(&t1, NULL, thread_func1, NULL, pool); stat = apr_thread_create(&t2, NULL, thread_func2, NULL, pool); //... apr_terminate(); return 0; } //output ... ... [threadA]: own the lock, time[10:10:15] [threadB]: modify the var, time[10:10:15] [threadA]: x = 10 [threadA]: Warning: x变量值被破坏,现重新修正之 [threadA]: release the lock, time[10:10:15] 当然这个例子不一定很精确的表明threadB在threadA拥有互斥量的时候修改了x值。 二、条件变量 互斥量一般用于被设计被短时间持有的锁,一旦我们不能确定等待输入的时间时,我们可以使用条件变量来完成同步。我们曾经说过I/O复用,在我们调用poll或者select的时候实际上就是在内核与用户进程之间达成了一个协议,即当某个I/O描述符事件发生的时候内核通知用户进程并且将处于挂起状态的用户进程唤醒。而这里我们所说的条件变量让对等的线程间达成协议,即“某一线程发现某一条件满足时必须发信号给阻塞在该条件上的线程,将后者唤醒”。这样我们就有了两种角色的线程,分别为 (1) 给条件变量发送信号的线程 其流程大致为: { 获取条件变量关联锁; 修改条件为真; 调用apr_thread_cond_signal通知阻塞线程条件满足了;------ (a) 释放变量关联锁; } (2) 在条件变量上等待的线程 其流程大致为: { 获取条件变量关联锁; while (条件为假) { --------------------- (c) 调用apr_thread_cond_wait阻塞在条件变量上等待;------ (b) } 修改条件; 释放变量关联锁; } 上面两个流程中,理解三点最关键: a) apr_thread_cond_signal中调用的pthread_cond_signal保证至少有一个阻塞在条件变量上的线程恢复;在《Unix网络编程 Vol2》中也谈过这里存在着一个race。即在发送cond信号的同时,该发送线程仍然持有条件变量关联锁,那么那个恢复线程的apr_thread_cond_wait返回时仍然拿不到这把锁就会再次挂起。这里的这个race要看各个平台实现是如何处理的了。 b) apr_thread_cond_wait中调用的pthread_cond_wait原子的将调用线程挂起,并释放其持有的条件变量关联锁; c) 这里之所以使用while反复测试条件,是防止“伪唤醒”的存在,即条件并未满足就被唤醒。所以无论怎样,唤醒后我都需要重新测试一下条件,保证该条件的的确确满足了。 条件变量在解决“生产者-消费者”问题中有很好的应用,在我以前的一篇blog中也说过这个问题。 三、读写锁 前面说过,互斥量把想进入临界区而又试图获取互斥量的所有线程都阻塞住了。读写锁则改进了互斥量的这种霸道行为,它区分读临界区数据和修改临界区数据两种情况。这样如果有线程持有读锁的话,这时再有线程想读临界区的数据也是可以再获取读锁的。读锁和写锁的分配规则在《Unix网络编程 Vol2》中有详细说明,这里不详述。 四、小结 三种同步方式如何选择?场合不同选择也不同。互斥量在于完全同步的临界区访问;条件变量在解决“生产者-消费者”模型问题上有独到之处;读写锁则在区分对临界区读写的时候使用。 bigwhite @ 18:29 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-08 10:53 APR分析-线程篇 并行一直是程序设计领域的难点,而线程是并行的一种重要的手段,而且线程的一些特性也能在进程并行时发挥很好的作用(在“线程同步篇”中详细阐述)。 APR线程的源代码的位置在$(APR_HOME)/threadproc目录下,本篇blog着重分析unix子目录下的thread.c文件内容,其相应头文件为$(APR_HOME)/include/apr_threadproc.h。 一、线程基础 《深入理解计算机系统》(以下称CS.APP)一书中对线程基础概念的讲解让我眼前豁然开朗,这里不妨引述一下: (1) 在传统观点中,进程是由存储于用户虚拟内存中的代码、数据和栈,以及由内核维护的“进程上下文”组成的,其中“进程上下文”又可以看成“程序上下文”和“内核上下文”组成,可参见下面图示: 进程-- |- 进程上下文 |- 程序上下文 |- 数据寄存器 |- 条件码 |- 栈指针 |- 程序计数器 |- 内核上下文 |- 进程ID |- VM结构 |- Open files |- 已设置的信号处理函数 |- brk pointer |- 代码、数据和栈(在虚存中) |- 栈区 <-- SP |- 共享库区 |- 运行时堆区 <-- brk |- 可读/写数据区 |- 只读代码/数据区 <-- PC (2) 另种观点中,进程是由线程、代码和数据以及内核上下文组成的,下图更能直观的展示出两种观点的异同: 进程 --+ |- 线程 |- 栈区 <-- SP |- 线程上下文 |- 线程ID |- 数据寄存器 |- 条件码 |- 栈指针 |- 程序计数器 |- 内核上下文 |- 进程ID |- VM结构 |- Open files |- 已设置的信号处理函数 |- brk pointer |- 代码、数据(在虚存中) |- 共享库区 |- 运行时堆区 <-- brk |- 可读/写数据区 |- 只读代码/数据区 <-- PC 对比两种观点我们可以得出以下几点结论: (a) 从观点(2)可以看出进程内的多个线程共享进程的内核上下文和代码、数据(当然不包括栈区); (b) 线程上下文比进程上下文小,且切换代价小; (c) 线程不像进程那样有着“父-子”体系,同一个进程内的线程都是“对等的”,主线程与其他线程不同之处就在于其是进程创建的第一个线程。 二、APR线程管理接口 如今应用最广泛的线程包就是Posix Thread了。APR对线程的封装也是基于Posix thread的。 APR线程管理接口针对apr_thread_t这个基本的数据结构进行操作,apr_thread_t的定义很简单: / apr_arch_threadproc.h */ struct apr_thread_t { apr_pool_t *pool; pthread_t *td; void data; apr_thread_start_t func; apr_status_t exitval; }; 这个结构中包含了线程ID、线程函数以及该函数的参数数据。不过APR的线程函数定义与Pthread的有不同,“Pthread线程函数”是这样的: typedef void (start_routine)(void); 而“APR线程函数”如下: typedef void (APR_THREAD_FUNC apr_thread_start_t)(apr_thread_t, void); 1、apr_thread_create apr_thread_create内部定义了一个dummy_worker的“Pthread线程函数”,并将apr_thread_t结构作为参数传入,然后在dummy_worker中启动“APR的线程函数”。在该函数的参数列表中有一项类型为apr_threadattr_t: struct apr_threadattr_t { apr_pool_t pool; pthread_attr_t attr; }; 这个类型封装了线程的属性,不同的线程属性会导致线程的行为有所不同。Pthread提供多种线程属性设置接口,可是APR并未全部提供,必要时我觉得可以自己来调用Pthread接口。APR提供的属性设置接口包括设置线程的可分离性、线程栈大小和栈Guard区域属性。 2、apr_thread_exit 进程退出我们可以直接调用exit函数,而线程退出也有几种方式: (1) 隐式退出 - 可以理解为线程main routine代码结束返回; (2) 显式退出 - 调用线程包提供的显式退出接口,在apr中就是apr_thread_exit; (3) 另类显式退出 - 调用exit函数,不仅自己退出,其所在线程也跟着退出了; (4) 被“黑”退出 - 被别的“对等”线程调用pthread_cancel而被迫退出。 apr_thread_exit属于种类(2),该种类退出应该算是线程的优雅退出了。apr_thread_exit做了3个工作,分别为设置线程返回值、释放pool中资源和调用pthread_exit退出。 3、apr_thread_join和apr_thread_detach 进程有waitpid,线程有join。线程在调用apr_thread_exit后,只是其执行停止了,其占有的“资源”并不一定释放,这里的“资源”我想就是“另种观点”中的“线程上下文”,线程有两种方式来释放该“资源”,这主要由线程的“可分离”属性决定的。如果线程是“可分离的”,当线程退出后就会自动释放其“资源”,如果线程为“非可分离的”,则必须由“对等线程”调用join接口来释放其资源。apr_thread_detach用来将其调用线程转化为“可分离”线程,而apr_thread_join用来等待某个线程结束并释放其资源。 三、小结 基本的线程管理接口相对较简单,关键是对线程概念的理解。接下来的“线程同步”则是件比较有趣的话题。 bigwhite @ 10:53 | 阅读全文 | 评论(0) | 引用Trackback(0) | 编辑 2005-12-05 20:06 APR分析-网络IO篇 “这个世界如果没有了网络就好比没有了石油、没有了电一样,是多么的可怕呀。”相信世界上已经有很多很多的人能够同意这种观点了,通过这个观点也可以看出网络在现代人们心中的地位。而运行在网络节点上的网络应用程序则是在幕后默默地为人们提供着服务。Apache Server就是其中一个典型的代表。而APR网络I/O库则像磐石一样支撑着Apache Server的运行。 APR网络I/O的源代码的位置在$(APR_HOME)/network_io目录下,本篇blog着重分析unix子目录下的各.c文件内容,其相应头文件为$(APR_HOME)/include/apr_network_io.h。 以程序员的视角来看待网络,这样我们可以忽略一些网络的基础概念。下面将循序渐进地接触网络,并说明APR是如何支持这些网络概念的。 一、IP地址 -- 主机通信 我们熟知的并且每天工作于其上的因特网是一个世界范围的主机的集合,这个主机集合被映射为一个32位(目前)或者64位(将来)IP地址;而IP地址又被映射为一组因特网域名;一个网络中的主机上的进程能通过一个连接(connection)和任何其他网络中的主机上的进程通信。 1、IP地址存储 在如今的IPV4协议中我们一般使用一个unsigned int来存储IP地址,在UNIX平台下,使用如下结构来存储一个IP地址的值: / Internet address structure / struct in_addr { unsigned int s_addr; / network byte order (big-endian) / }; 这里值得一提的是APR关于IP地址存储的做法,看如下代码: #if (!APR_HAVE_IN_ADDR) /
- We need to make sure we always have an in_addr type, so APR will just
- define it ourselves, if the platform doesn't provide it. */ struct in_addr { apr_uint32_t s_addr; }; #endif APR保证了其所在平台上in_addr的存在。还有一点儿需要注意的是在in_addr中,s_addr是以网络字节序存储的。如果你的IP地址不符合条件,可通过调用一些辅助接口来做转换,这些接口包括: htonl : host to network long ; htons : host to network short ; ntohl : network to host long ; ntohs : network to host short. 2、IP地址表示 我们平时看到的IP地址都是类似“xxx.xxx.xxx.xxx”这样的点分十进制的。上面说过IP地址使用的是一个unsigned int整形数来表示。这样就存在着一个IP地址表示和IP地址存储之间的一个转换过程。APR提供这一转换支持,我们用一个例子来说明: #include <apr.h> #include <apr_general.h> #include "apr_network_io.h" #include "apr_arch_networkio.h" int main(int argc, const char * const * argv, const char * const env) { apr_app_initialize(&argc, &argv, &env); char presentation[100]; int networkfmt; memset(presentation, 0, sizeof(presentation)); apr_inet_pton(AF_INET, "255.255.255.255", &networkfmt); printf("0x%x\n", networkfmt); apr_inet_ntop(AF_INET, &networkfmt, presentation, sizeof(presentation)); printf("presentation is %s\n", presentation); apr_terminate(); return 0; } APR提供apr_inet_pton将我们熟悉的点分十进制形式转换成一个整型数存储的IP地址;而apr_inet_ntop则将一个存整型数存储的IP地址转换为我们可读的点分十进制形式。这两个接口的功能类似于系统调用inet_pton和inet_ntop,至于使用哪个就看你的喜好了^_^。 二、SOCKET -- 进程通信 前面提到过通过一个连接(connection)可以连接两个internet不同或相同主机上的不同进程,这个连接是点对点的。而从Unix内核角度来看,SOCKET则是连接的一个端点。每个SOCKET都有一个地址,其地址由主机IP地址和通讯端口号组成。一个连接有两个端点,这样一个连接就可以由一个SOCKET对唯一表示了。这个SOCKET对是这个样子的(cliaddr:cliport, servaddr:servport)。 那么在应用程序中我们如何获取和使用这一互联网上的进程通讯利器呢?每个平台都为应用程序提供了一套SOCKET编程接口,APR又在不同平台提供的接口之上进行了封装,使代码可以在不同平台上编译运行,而且易用性也有所提高。 1、SOCKET描述符 SOCKET属于系统资源,我们必须通过系统调用来申请该资源。SOCKET资源的申请类似于FILE,在使用文件时我们通过调用open函数获取文件描述符,类似我们也可通过调用下面的接口来获取SOCKET描述符: int socket(int domain, int type, int protocol); 从Unix程序的角度来看,SOCKET就是一个有相应描述符的打开的文件。在APR中我们可以通过调用apr_socket_create来创建一个APR自定义的SOCKET对象,该SOCKET结构如下: / apr_arch_networkio.h */ struct apr_socket_t { apr_pool_t *cntxt; int socketdes; int type; int protocol; apr_sockaddr_t *local_addr; apr_sockaddr_t *remote_addr; apr_interval_time_t timeout; #ifndef HAVE_POLL int connected; #endif int local_port_unknown; int local_interface_unknown; int remote_addr_unknown; apr_int32_t options; apr_int32_t inherit; sock_userdata_t userdata; #ifndef WAITIO_USES_POLL / if there is a timeout set, then this pollset is used */ apr_pollset_t *pollset; #endif }; 该结构中的socketdes字段其实是真正存储由socket函数返回的SOCKET描述符的,其他字段都是为APR自己所使用的,这些字段在Bind、Connect等过程中使用。另外需要提及的就是要分清SOCKET描述符和SOCKET地址(IP地址,端口号),前者是系统资源,而后者用来描述一个连接的一个端点的地址。SOCKET描述符可以代表任意的SOCKET地址,也可以绑定到某个固定的SOCKET地址上(在后面有说明)。我们如果不显式将SOCKET描述符绑定到某SOCKET地址上,系统内核就会自动为该SOCKET描述符分配一个SOCKET地址。 2、SOCKET属性 还是与文件对比,在文件系统调用中有一个fcntl接口可以用来获取或设置已分配的文件描述符的属性,如是否Block、是否Buffer等。SOCKET也提供类似的接口调用setsockopt和getsockopt。在APR中等价于该功能的接口是apr_socket_opt_set和apr_socket_opt_get。APR在apr_network_io.h中提供如下SOCKET的参数属性: #define APR_SO_LINGER 1 /< Linger */ #define APR_SO_KEEPALIVE 2 /< Keepalive */ #define APR_SO_DEBUG 4 /< Debug */ #define APR_SO_NONBLOCK 8 /< Non-blocking IO */ #define APR_SO_REUSEADDR 16 /< Reuse addresses */ #define APR_SO_SNDBUF 64 /< Send buffer */ #define APR_SO_RCVBUF 128 /< Receive buffer */ #define APR_SO_DISCONNECTED 256 /< Disconnected */ ... ... 另外从上面这些属性值(都是2的n次方)可以看出SOCKET也是使用一个属性控制字段中的“位”来控制SOCKET属性的。 再有APR提供一个宏apr_is_option_set来判断一个SOCKET是否拥有某个属性。 3、Connect、Bind、Listen、Accept -- 建立连接 这里不详述C/S模型了,只是说说APR支持C/S模型的一些接口。 (1) apr_socket_connect 客户端连接服务器端的唯一调用就是connect,connect试图建立一个客户端进程与服务器端进程的连接。apr_socket_connect的参数分别为客户端已经打开的一个SOCKET以及指定的服务器端的SOCKET地址(IP ADDR : PORT)。apr_socket_connect内部实现的流程大致如以下代码: apr_socket_connect { do { rc = connect(sock->socketdes, (const struct sockaddr *)&sa->sa.sin, sa->salen); } while (rc == -1 && errno == EINTR); -------- (a) if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY) && (sock->timeout > 0)) { rc = apr_wait_for_io_or_timeout(NULL, sock, 0); --------- (b) 注[1] if (rc != APR_SUCCESS) { return rc; } if (rc == -1 && errno != EISCONN) { return errno; --------- (c) } 初始化sock->remote_addr; ... ... } 对上述代码进行若干说明: (a) 执行系统调用connect连接服务器端,注意这里做了防止信号中断的处理,这个技巧在以前的文章中提到过,这里不详述; (b) 如果系统操作正在进行中,调用apr_wait_for_io_or_timeout进行超时等待; (c) 错误返回,前提errno不是表示已连接上。 一旦apr_socket_connect成功返回,我们就已经成功建立了一个SOCKET对,即一个连接。 (2) apr_socket_bind Bind、Listen和Accept这三个过程是服务器端用于接收“连接”的必经之路。其中Bind就是告诉操作系统内核显式地为该SOCKET描述符分配一个SOCKET地址,这个SOCKET地址就不能被其他SOCKET描述符占用了。在服务器编程中Bind几乎成为了“必选”之调用,因为一般服务器程序都有自己的“名气很大”的SOCKET地址,如TELNET服务端口号23等。apr_socket_bind也并未做太多的工作,只是简单的调用了bind系统接口,并设置了apr_socket_t结构的几个local_addr字段。 (3) apr_socket_listen 按照《Unix网络编程 Vol1》的说法,SOCKET描述符在初始分配时都处于“主动连接”状态,Listen过程将该SOCKET描述符从“主动连接”转换为“被动状态”,并告诉内核接受该SOCKET描述符的连接请求。apr_socket_listen的背后直接就是listen接口调用。 (4) apr_socket_accept Accept过程在“被动状态”SOCKET描述符上接受一个客户端的连接,这时系统内核会自动分配一个新的SOCKET描述符,内核为该描述符自动分配一个SOCKET地址,来代表这条连接的服务器端。注意在SOCKET编程接口中除了socket函数能分配新的SOCKET描述符之外,accept也是另外的一个也是唯一的一个能分配新的SOCKET描述符的系统调用了。apr_socket_accept首先在pool中分配一个新的apr_socket_t结构变量,然后调用accept,并设置新变量的各个字段。 4、Send/Recv -- 数据传输 网络通信最重要的还是数据传输,在SOCKET编程接口中最常见的两个接口就是recv和send。在APR中分别有apr_socket_recv和apr_socket_send与前面二者对应。下面逐一分析。 (1) apr_socket_recv 首先来看看apr_socket_recv的实现过程: apr_socket_recv { if (上次调用apr_socket_recv没有读完所要求的字节数) { ----------(a) 设置sock->options; goto do_select; } do { rv = read(sock->socketdes, buf, (*len)); ------ (b) } while (rv == -1 && errno == EINTR); if ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK) && (sock->timeout > 0)) { do_select: arv = apr_wait_for_io_or_timeout(NULL, sock, 1); if (arv != APR_SUCCESS) { *len = 0; return arv; } else { do { rv = read(sock->socketdes, buf, (*len)); } while (rv == -1 && errno == EINTR); } } ------------ (c) 设置(*len)和sock->options; -------------(d) ... ... } 针对上面代码进行简单说明: (a) 一次apr_socket_recv调用完全有可能没有读完所要求的字节数,这里做个判断以决定是否继续读完剩下的数据; (b) 调用read读取SOCKET缓冲区数据,注意这里做了防止信号中断的处理,这个技巧在以前的文章中提到过,这里不详述; (c) 如果SOCKET操作正在忙,我们调用apr_wait_for_io_or_timeout等待,直到SOCKET可用。这里我觉得好像有个问题,想象一下如果上一次SOCKET的状态为APR_INCOMPLETE_READ,那么重新调用apr_socket_read后在SOCKET属性中去掉APR_INCOMPLETE_READ,然后进入apr_wait_for_io_or_timeout过程,一旦apr_wait_for_io_or_timeout失败,那么就直接返回了。而实际上SOCKET仍然应该处于APR_INCOMPLETE_READ状态,而下次再调用apr_socket_read就直接进入一轮完整数据的读取过程了,不知道这种情形是否能否发生。 (d) 将(len)设置为实际从SOCKET Buffer中读取的字节数,并根据这一实际数据与要求数据作比较来设置sock->options。 (2) apr_socket_send apr_socket_send负责发送数据到SOCKET Buffer,其实现的方式与apr_socket_recv大同小异,这里就不分析了。 三、小结 APR Network I/O中还有对Multicast的支持,由于平时不常接触,这里不分析了。 注[1]: / in errno.h / #define EISCONN 133 / Socket is already connected / #define EALREADY 149 / operation already in progress / #define EINPROGRESS 150 / operation now in progress / bigwhite @ 20:06 | 阅读全文 | 评论(1) | 引用Trackback(0) | 编辑 2005-12-02 10:12 APR分析-进程同步篇 最新的统计数据显示Apache服务器在全世界仍然占据着Web服务器龙头老大的位置,而且市场占有率遥遥领先,所以学习Apache相关知识是完全正确的方向,这里我们继续分析APR进程同步相关内容。 进程同步的源代码的位置在$(APR_HOME)/locks目录下,本篇blog着重分析unix子目录下的proc_mutex.c、global_mutex文件内容,其相应头文件为$(APR_HOME)/include/apr_proc_mutex.h、apr_global_mutex.h。其用于不同进程之间的同步以及多进程多线程中的同步问题。 APR提供三种同步措施,分别为: apr_thread_mutex_t - 支持单个进程内的多线程同步; apr_proc_mutex_t - 支持多个进程间的同步; apr_global_mutex_t - 支持不同进程内的不同线程间同步。 在本篇中着重分析apr_proc_mutex_t。 1、同步机制 APR提供多种进程同步的机制供选择使用。在apr_proc_mutex.h中列举了究竟有哪些同步机制: typedef enum { APR_LOCK_FCNTL, / 记录上锁 / APR_LOCK_FLOCK, / 文件上锁 / APR_LOCK_SYSVSEM, / 系统V信号量 / APR_LOCK_PROC_PTHREAD, / 利用pthread线程锁特性 / APR_LOCK_POSIXSEM, / POSIX信号量 / APR_LOCK_DEFAULT / 默认进程间锁 / } apr_lockmech_e; 这几种锁机制,随便拿出哪一种都很复杂。APR的代码注释中强调了一点就是“只有APR_LOCK_DEFAULT”是可移植的。这样一来用户若要使用APR进程同步机制接口,就必须显式指定一种同步机制。 2、实现点滴 APR提供每种同步机制的实现,每种机制体现为一组函数接口,这些接口被封装在一个结构体类型中: / in apr_arch_proc_mutex.h */ struct apr_proc_mutex_unix_lock_methods_t { unsigned int flags; apr_status_t (*create)(apr_proc_mutex_t *, const char *); apr_status_t (*acquire)(apr_proc_mutex_t *); apr_status_t (*tryacquire)(apr_proc_mutex_t *); apr_status_t (*release)(apr_proc_mutex_t *); apr_status_t (*cleanup)(void *); apr_status_t (*child_init)(apr_proc_mutex_t **, apr_pool_t *, const char *); const char name; }; 之后在apr_proc_mutex_t类型中,apr_proc_mutex_unix_lock_methods_t的出现也就在情理之中了:) / in apr_arch_proc_mutex.h */ struct apr_proc_mutex_t { apr_pool_t *pool; const apr_proc_mutex_unix_lock_methods_t *meth; const apr_proc_mutex_unix_lock_methods_t *inter_meth; int curr_locked; char *fname; ... ... #if APR_HAS_PROC_PTHREAD_SERIALIZE pthread_mutex_t *pthread_interproc; #endif }; 这样APR提供的用户接口其实就是对mech各个“成员函数”功能的“薄封装”,而真正干活的其实是apr_proc_mutex_t中的meth字段的“成员函数”,它们的工作包括mutex的创建、获取(加锁)和清除(解锁)等。以“获取锁”为例APR的实现如下: APR_DECLARE(apr_status_t) apr_proc_mutex_lock(apr_proc_mutex_t *mutex) { return mutex->meth->acquire(mutex); } 3、同步机制 按照枚举类型apr_lockmech_e的声明,我们知道APR为我们提供了5种同步机制,下面分别简单说说: (1) 记录锁 记录锁是一种建议性锁,它不能防止一个进程写已由另一个进程上了读锁的文件,它主要利用fcntl系统调用来完成锁功能的,记得在以前的一篇关于APR 文件I/O的Blog中谈过记录锁,这里不再详细叙述了。 (2) 文件锁 文件锁是记录锁的一个特例,其功能由函数接口flock支持。值得说明的是它仅仅提供“写入锁”(独占锁),而不提供“读入锁”(共享锁)。 (3) System V信号量 System V信号量是一种内核维护的信号量,所以我们只需调用semget获取一个System V信号量的描述符即可。值得注意的是与POSIX的单个“计数信号量”不同的是System V信号量是一个“计数信号量集”。所以我们在注意的是在初始化时设定好信号量集的属性以及在调用semop时正确选择信号量集中的信号量。在APR的System V信号量集中只是申请了一个信号量。 (4) 利用线程互斥锁机制 APR使用pthread提供的互斥锁机制。原本pthread互斥锁是用来互斥一个进程内的各个线程的,但APR在共享内存中创建了pthread_mutex_t,这样使得不同进程的主线程实现互斥,从而达到进程间互斥的目的。截取部分代码如下: new_mutex->pthread_interproc = (pthread_mutex_t )mmap( (caddr_t) 0, sizeof(pthread_mutex_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); (5) POSIX信号量 APR使用了POSIX有名信号量机制,从下面的代码中我们可以看出这一点: / in proc_mutex.c / apr_snprintf(semname, sizeof(semname), "/ApR.%lxZ%lx", sec, usec); / APR自定义了一种POSIX信号量命名规则,在源代码中有说明 */ psem = sem_open(semname, O_CREAT, 0644, 1); 4、如何使用 我们知道父进程的锁其子进程并不继承。APR进程同步机制的一个典型使用方法就是:“Create the mutex in the Parent, Attach to it in the Child”。APR提供接口apr_proc_mutex_child_init在子进程中re-open the mutex。 5、小结 APR提供多种锁机制,所以使用的时候要根据具体应用情况细心选择。 bigwhite @ 10:12 | 阅读全文 | 评论(1) | 引用Trackback(0) | 编辑 日历 最近更新 最新评论 存档 我的链接