2009年4月5日星期日

Linux下的调试工具

随着XP的流行,人们越来越注重软件的前期设计、后期的实现,以及贯穿于其中的测试工作,经过这个过程出来的自然是高质量的软件。甚至有人声称XP会淘汰调试器!这当然是有一定道理的,然而就目前的现实来看,这还是一种理想。在日常工作中,调试工具还是必不可少的。在Linux下,调试工具并非只有gdb,还有很多其它调试工具,它们都各有所长,侧重方面也有所不同。本文介绍几种笔者常用的调试工具:

1. mtrace
在linux下开发应用程序,用C/C++语言的居多。内存泄露和内存越界等内存错误,无疑是其中最头疼的问题之一。glibc为解决内存错误提供了两种方案:

一种是hook内存管理函数。hook内存管理函数后,你可以通过记下内存分配的历史记录,在程序终止时查看是否有内存泄露,这样就可以找出内存泄露的地方了。你也可以通过在所分配内存的首尾写入特殊的标志,在释放内存时检查该标志是否被破坏了,这样就可以达到检查内存越界问题的目的。

另外一种方法更简单,glibc已经为第一种方案提供了默认的实现,你要做的只是在特定的位置调用mtrace/muntrace两个函数,它们的函数原型如下:
#include <mcheck.h>
void mtrace(void);
void muntrace(void);
你可能会问,在哪里调这两种函数最好?这没有固定的答案,要视具体情况而定。对于小程序来说,在进入main时调用mtrace,在退出main函数时调用muntrace。对于大型软件,这样做可能会记录过多的信息,分析这些记录会比较慢,这时可以在你所怀疑代码的两端调用。

另外,还需要设置一个环境变量MALLOC_TRACE,它是一个文件名,要保证当前用户有权限创建和写入该文件。glibc的内存管理器会把内存分配的历史信息写入到MALLOC_TRACE指定的文件中。

程序运行完毕后,使用mtrace工具分析这些内存分配历史信息,可以查出内存错误的位置(mtrace在glibc-utils软件包里)。

2. strace
在编程时,检查函数的返回值是一种好习惯。对于像glibc等标准C的函数,光检查返回值是不够的,还需要检查errno的值。这样的程序往往显得冗长,不够简洁。同时也可能是出于偷懒的原因,大多数程序里并没有做这样的检查。

这样的程序,一旦出现错误,用调试器一步一步定位错误,然后想法查出错误的原因,也是可以的,不过比较麻烦,对调试器来说有些大材小用,不太可取。这时,用strace命令可能会更方便一点。它可以显示各个系统调用/信号的执行过程和结果。比如文件打开出错,一眼就看出来了,连错误的原因(errno)都知道。

3. binutil
binutil是一系列的工具,你可能根本不知道它们的存在,但是没有它们你却寸步难行。Binutil包括下列工具:
ld - the GNU linker.
as - the GNU assembler.
addr2line - Converts addresses into filenames and line numbers.
ar - A utility for creating, modifying and extracting from archives.
c++filt - Filter to demangle encoded C++ symbols.
gprof - Displays profiling information.
nlmconv - Converts object code into an NLM.
nm - Lists symbols from object files.
objcopy - Copys and translates object files.
objdump - Displays information from object files.
ranlib - Generates an index to the contents of an archive.
readelf - Displays information from any ELF format object file.
size - Lists the section sizes of an object or archive file.
strings - Lists printable strings from files.
strip - Discards symbols.
windres - A compiler for Windows resource files.
其中部分工具对调试极有帮助,如:
你可以用objdump反汇编,查看目标文件或可执行文件内部信息。
你可以用addr2line把机器地址转换到代码对应的位置。
你可以用nm查看目标文件或可执行文件中的各种符号。
你可以用gprof分析各个函数的使用情况,找出性能的瓶颈所在(这需要加编译选项)。

4. ld-linux
现在加载ELF可执行文件的工作,已经落到ld-linux.so.2头上了。你可能会问,这与有调试程序有关系吗?有的。比如,在linux中,共享库里所有非static的函数/全局变量都是export的,更糟的是C语言中没有名字空间这个概念,导致函数名极易冲突。在多个共享库中,名字冲突引起的BUG是比较难查的。这时,你可以通过设置LD_
DEBUG环境变量,来观察ld-linux.so加载可执行文件的过程,从中可以得到不少帮助信息。LD_
DEBUG的取值如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
5. gdb
对于真正意义的调试器来说,gdb在linux下是独一无二的。它有多种包装,有字符界面的,也有图形界面的,有单独运行的,也有集成到IDE中的。gdb功能强大,图形界面的gdb容易上手一点,但功能无疑受到了一些限制,相信大部分高手还是愿意使用字符界面的。Gdb太常用了,这里不再多说。

6. gcc/boundschecker
相信很多人用过win32下的BoundsChecker(Compuware公司)和Purify(IBM公司)两个工具吧。它们的功能实在太强大了,绝非能通过重载内存管理函数就可以做到,它们在编译时插入了自己的调试代码。

gcc也有个扩展,通过在编译时插入调试代码,来实现更强大的检查功能。当然这要求重新编译gcc,你可以到http://sourceforge.net/projects/boundschecking/
下载gcc的补丁。它的可移植性非常好,笔者曾一个ARM
平台项目里使用过,效果不错。

7. valgrind
最好的东西往往最后才见到。Valgrind是我的最爱,用习惯了,写的程序不在valgrind下跑一遍,就像没有写单元测试程序一样,有点放心不下。它有BoundsChecker/Purify的功能,而且速度更快。
有点遗憾的是valgrind目前只支持x86平台,当然,这对大多数情况已经足够了。
你可以到http://valgrind.org/ 下载最新版本。

Linux下共享库(SO)有关的几个环境变量

Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)
共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:

LD_LIBRARY_PATH
这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。

LD_PRELOAD
这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。

LD_ DEBUG
这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW
这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。

LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。
Linux下共享库(SO)有关的几个环境变量收藏
Linux支持共享库已经有悠久的历史了,不再是什么新概念了。大家都知道如何编译、连接以及动态加载(dlopen/dlsym/dlclose)
共享库。但是,可能很多人,甚至包括一些高手,对共享库相关的一些环境变量认识模糊。当然,不知道这些环境变量,也可以用共享库,但是,若知道它们,可能就会用得更好。下面介绍一些常用的环境变量,希望对家有所帮助:

LD_LIBRARY_PATH
这个环境变量是大家最为熟悉的,它告诉loader:在哪些目录中可以找到共享库。可以设置多个搜索目录,这些目录之间用冒号分隔开。在linux下,还提供了另外一种方式来完成同样的功能,你可以把这些目录加到/etc/ld.so.conf中,或则在/etc/ld.so.conf.d里创建一个文件,把目录加到这个文件里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。按照惯例,除非你用上述方式指明,loader是不会在当前目录下去找共享库的,正如shell不会在当前目前找可执行文件一样。

LD_PRELOAD
这个环境变量对于程序员来说,也是特别有用的。它告诉loader:在解析函数地址时,优先使用LD_PRELOAD里指定的共享库中的函数。这为调试提供了方便,比如,对于C/C++程序来说,内存错误最难解决了。常见的做法就是重载malloc系列函数,但那样做要求重新编译程序,比较麻烦。使用LD_PRELOAD机制,就不用重新编译了,把包装函数库编译成共享库,并在LD_PRELOAD加入该共享库的名称,这些包装函数就会自动被调用了。在linux下,还提供了另外一种方式来完成同样的功能,你可以把要优先加载的共享库的文件名写在/etc/ld.so.preload里。当然,这是系统范围内全局有效的,而环境变量只对当前shell有效。

LD_ DEBUG
这个环境变量比较好玩,有时使用它,可以帮助你查找出一些共享库的疑难杂症(比如同名函数引起的问题)。同时,利用它,你也可以学到一些共享库加载过程的知识。它的参数如下:
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
BIND_NOW
这个环境变量与dlopen中的flag的意义是一致,只是dlopen中的flag适用于显示加载的情况,而BIND_NOW/BIND_NOT适用于隐式加载。

LD_PROFILE/LD_PROFILE_OUTPUT:为指定的共享库产生profile数据,LD_PROFILE指定共享库的名称,LD_PROFILE_OUTPUT指定输出profile文件的位置,是一个目录,且必须存在,默认的目录为/var/tmp/或/var/profile。通过profile数据,你可以得到一些该共享库中函数的使用统计信息。

Linux下文件关联的实现原理

From:http://blog.csdn.net/absurd/

我们知道在Windows下,双击一个可执行文件,文件管理器会自动运行这个应用程序。而双击一个数据文件时,文件管理器会用与之关联的应用程序打开它。数据文件与应用程序之间的关联是通过注册表来实现的:文件管理器查询注册表,找到数据文件对应的应用程序,然后运行这个应用程序,并把数据文件的文件名作为命令行参数传给它。

这种文件关联的方式非常好用,省去了先起动应用程序再打开文件的麻烦。Linux下的桌面环境也有类似的功能,而且实现方式更合理。最近负责开发一个资源管理器,本来GNOME有一个功能强大的资源管理器Nautilus,只是它过于庞大,不但有超过10万行的代码,还依赖于libgnome、gnome-vfs和CORBA等,故不适合于嵌入式应用。最终我们决定自己开发一个简化的资源管理器,但又要尽量兼容现有的应用程序,这要了解相关标准,文件关联方式是其中之一。把这几天学到的知识做个笔记吧,供有兴趣的朋友参考:

首先让我们看看文件关联要做些什么。
1.
数据文件与应用程序的关联。一个应用程序通常只能打开一些特定的数据文件,比如图片浏览工具可以打开PNG、BMP和JPEG等图片文件。打开一词的意义比较宽泛,这里包括:打开、播放、安装、编辑和打印等等。

2.
文件类型信息。资源管理器把数据文件列出来时,通常会用一个图标来标识这类文件,同时也会加上一个简短的名称,以便用户可以很容易把它与其它类型的文件区分开来。

下面我们看看linux下是如何实现的。
1.
判断文件类型。文件的数量是无限的,我们只能按文件类型来处理。如何判断一个文件所属的文件类型呢?可能有人会说,很简单,用扩展名区分就行了。没错,用扩展名可以做到,但这种方法有两个缺陷:一方面它不是很精确,相同扩展名的文件的类型可能完全不同,比如dat文件,可能是一个视频文件,也可能是一个普通数据文件。另一方面它不是很准确,扩展名可以任何改动,为了某种目的,完全可以把exe扩展名改为htm扩展名。

而且在Linux下扩展名只是一个可选项,很多文件根本没有扩展名,所以纯粹采用文件扩展名的方式来判断肯定是不行的。为了更好的判断文件类型,在linux下同时采用两种方式:优先采用magic方式,其次才采用文件扩展名方式。所谓magic方式,就是根据文件内容来判断。绝大多数文件,内部都有一些特定的标记,这些标记称为magic,比如BMP图片文件以BM两个字符开头,BM就是一个magic。虽然即使采用了双保险机制也有误判的可能,但概率已经大大降低了。

2. 文件类型的表示。
文件类型如何表示呢?我们说JPEG是图片文件,说txt是文本文件,WML是XML文件。这种分类很直观,但也有几个问题:对JPEG文件来说,称它图片文件太笼统了。有的图片浏览工具虽然能够打开大部分图片文件,但不一定能打开所有图片文件,它需要更详细的文件类型信息。对txt和WML来说,它们其实都是文本文件,有的编辑器可能以同样的方式处理它们。为了避免分类太细或者太粗,linux采用了MIME(可以参考相关RFC)规范,它用一种层次型的方式来分类,如:
JPEG文件:image/jpeg
文本文件:text/plain
XML文件:text/xml
这种分类方式就可以粗细兼顾了。

3. 文件类型的数据信息。
在linux下,关于文件类型的信息通常放在/usr/share/mime、/usr/local/share/mime和用户目录下,所有应用程序可以共享这些信息。在该目录下,一般会有以下这些文件:
l aliases:文件类型的别名。比如application/pdf
有时也称为application/x-pdf 。
l
magic:各种文件的内部标识,用于从文件内容来判断文件类型。如BMP图片文件以BM开头。
l
globs:扩展名与文件类型的对应关系。如*.cpp文件是text/x-c++src类型的。
l packages目录:用于安装新文件类型用。
l
其它子目录及其下的文件:更详细的描述各种文件类型。比如image下的jpeg.xml文件描述了jpeg文件类型。为了方便国际化,这些描述信息有各种语言版本。

4. 图标文件与数据文件的关联。
在资源管理器中,通常用不同的图标来区分不同的文件类型。同时图标也是桌面主题相关的,主题不同,图标的大小和外观也不一样。图标文件通常存放在/usr/share/icons/主题/大小/mimetypes目录下。

文件类型与图标文件的对应关系是通过文件名来实现的。比如,JPEG文件对应的图标文件为gnome-mime-image-jpeg.png。
(这块不是很确定,有待进一步研究)

5. 应用程序与数据文件的关联。
应用程序与数据文件的关联是通过.desktop文件来实现的。应用程序要出现在开始菜单中或者桌面上,它要提供一个desktop文件才行。应用程序安装之后,desktop文件通常安装到/usr/share/applications下。

可以在desktop文件中,指明其可以操作的文件类型。如,软件包安装程序可以操作rpm文件,它的desktop文件(system-install-packages.desktop)内容为:
[Desktop Entry]
Name=Install Packages
GenericName=Install Packages
Comment=Install new packages on the system
MimeType=application/x-rpm;
Exec=/usr/bin/system-install-packages %F
Terminal=false
Type=Application
Icon=system-config-packages.png
Encoding=UTF-8
NoDisplay=true

MimeType项指明它可以操作rpm类型的文件。

Linux比windows的做法科学之处。
1. Linux采用了双保险机制,对文件类型的判断更正确,出错的概率更小。

2.
Linux分离文件类型判断信息和文件关联方式,这样文件类型信息可以被重用。比如file命令可以用这些信息来判断文件类型,而不必打开它。

参考资料:
http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-0.13.html
http://www.freedesktop.org/wiki/Standards_2fAddingMIMETutor

这四年来(以前写的,有点乱)

原帖:http://blog.csdn.net/absurd/archive/2006/02/17/601586.aspx

(刚到深圳时写的)
转眼就毕业快四年了,在一研究所呆了半年,在北京工作了三年,在深圳流浪了近三个月。
  
呆在研究所时,同从武汉过去毕业生共九个,无一不是感觉上当了。他们大多数人,要么一起谈论离职,与单位谈条件,要么就是怨天尤人。而我却只能把失望压在心里,老爸是中建七局公司的工人,凭着老爸高超的手艺和勤奋的工作,家里还算过得殷实。然而在我高考后几天,他为救一个同事,自己从三楼摔到二楼,腰部受伤,被迫病退了,加上弟弟同时上学,家里日趋困难。
  
去上班的路费还是向表哥借的,我还得等第一个月工资,以资助弟弟。再说我主修的是机电,辅修的计算机应用,没有工作经验,重新找份工作也很困难,下几个月的生活更无着落。
  
这时,女友打电话来说,大家工作在两地,生活在一起是不太可能的,你现在一个月几百块钱,自己生活都困难,还要资助你弟弟,你即使想来看我一次都不容易,还是分了吧。我握着电话,没有出声,任眼泪静静的流下。最后说了一句,等我一年,好吗?我发誓,一年后,月薪会超过三千的。她犹豫了一会儿,说,大家年龄都大了,家里人也着急,还是不用了吧。
  
性格本来就内行,我更加远离人群了,在宿舍里拼命的学英语,在办公室拼命的学编程。内心股强大的力量,不断的告诉我,一定要努力,一定离开这里。从不参与同事的抗议活动,也不参与他们的抱怨研讨会,只是默默的学习。有同事问我,不想离开吗?我笑着说,单位不倒,我是不走的,其实我比谁都想走。
  
渐渐的,凭着以前的数据库功底和PB的知识,不但可以轻松的应付工作,还可以指导同事。上司也是个爱学习的人,比我大两届,他见我如此爱学习,就让我专心的研究新技术,而不工作了。当时所谓的新技术无非是加密、反拷贝之类的。值得一提的是,他让我还要学习COM原理和OpenGL等知识,对此,他也仅懂一点,而我以前都没有听说过,他只是告诉我说,很有用的,同时给我买了十多本书(至今对他极为感谢,当时我根本没钱买书)。
  
现在想来真是搞笑,由于是全新的东西,很简单的概念都要想半天,翻译的也很烂,学习了半个月,还不清楚类工厂是做什么用的。直到看潘爱民老师的《COM原理》,才真正搞懂了。
  
终于到年底了,想到拿到那一千块的年终奖,就可以跑路了,不禁有些激动。为了谨慎起见,我装出一副平静的样子,只有在走了那一天,同室的同事才知道。
  
去北京之前,联系了在北航读研的好友老胡,同时发了五六封求职信。那时恰逢北京最冷的时候,零下十六度,又下很大的雪,只好花了三百块去买了羽绒服,又花了二百块去买了双鞋,还花了一百块买了个钱包,钱就用得差不多了,还好另外一个同学资助了一千块。
  
去了四家公司面试,都通过了。之所以后来选择那家公司,一是因待遇比较高,另一方面,在我发过简后,不到半小时就收到了他们的回复,真让我感动。老板让我去拿offer时,说,给你三天时间考虑,没问题的话,二月一号来上班,我当时兴奋的说,不用考虑了,一定来。
  
开始为了让我熟悉项目,让我写Linux平台的测试程序。旁边的同事,特别好,不厌其烦给我讲解(很想念他的,不知他现在过得好不好)。上司低估了我的能力,几个任务,我都只用比预期时间少得多的时间完成了,上司由此对我刮目相看。我完成了一套测试程序框架,工作量一下缩小到原来的五分之一,RD的人都无不惊异。
  
记得,给RD一个Linux高手讲了我的测试程序框架后,他看着我了老半天,才说,你太聪明了。我笑了笑了,说,惭愧,刚刚接触C++。其实心里高兴得不行了。
  
然而,后来的工作,却不是我想要的,前人留下的代码,写得也很差,本想成为编程高手的我,结果成了一个调试高手。工资也在长,职位也在升,然而我却日趋倦怠。感到在工作中学不到什么东西,有时你费尽心思找到一个严重的错误,不过是个简单的内存溢出,或者文件关闭后继续在使用。在上百万行代码中,而这种错误潜在的,不知有多少。我建议大家学习代码重构,设计模式,由于诸多原因无法实行。
  
真的累了,感觉就是在原地不断的奔跑,累了,然而还在原地。渐渐的萌生退意。后来公司组织一次活动去爬山,一件事让我很郁闷。
  
我一向很守时,说好早上九点半集合,我准时到了,而此时到的人不到五分之一,等到十点多,老板才过来。上山前,说好下午五点半集合,我计划好时间,估计五点可以下来,就和绿野的几个同伴一起走了,而最终五点十分回到集合地点,发现他们等了我一个小时间了,原来,他们只在半山腰转了一下,就回来了。晚上被老板训了一顿,说因为我而耽搁大家一个小时。真是郁闷极了,守时反而挨训,不守时反有理,真是天理何在。
  
每天在上地三街和七街间往返,早上在五街时,看见那班飞机从头顶飞过,在六街遇到几个中东的老外,还有一个总戴着红眼镜的女孩子。听着云飞的幽默集装箱或者CRI的Music。时间就这样溜走了。
  
和女友分手后,渐渐的我对周围的事充耳不闻,除了工作学习,个人外出旅游,偶尔和喜欢的网友聚一下。在公司里,我的脾气还是那样好,大家都喜欢和我来往,然而仅仅限于工作上的来往罢了。记得当时QA有个女孩,真的很喜欢她,看得出她对我也好感。然而另一个同事捷先登了,结果那女孩被吓跑了,一直很后悔没有早点行动。
  
年前给老妈打电话,老妈问起那个老问题,找对象没有。我说,没有。她说,二十六了,你小学同学的儿子都上小学了,该想想了。二十六了,我真的不敢面对时间流逝,年龄的增长。
  
二十六了,我还是一无所成!生活还是那样单调!我决定走了,决定去南方,我拒绝了亲友的忠告,拒绝了老板的加薪和升职,拒绝了同事的挽留(美国那边两个同事,一再劝我不要走,最后又劝我在外不好,就再回来)。
  
到了深圳,见到好友时,他第一句话就是,你还是像刚毕业时那样年轻。我苦涩的笑了笑,其实内心感觉好老了。他看了看我在背上的小包,说,就这么点东西,你是来玩的吧?我说就这点行李。他又说有钱就行。我又笑了,我存折上总共还不到八千块钱。
  
自信自己的能力,不在任何同龄人之下,尽管这几年IT不景气,还是相信可以找到一份新的工作。然而发出的数十信简历竟如沉大海。
  
我有些沮丧,这时一个好友拉我入伙,和他们一起做嵌入式系统,他们几个清华毕业的校友,软硬都很厉害。我答应做兼职,尽管没做过,凭着在学校时基础,很快上手了,开发出一些网络组件和图形组件。
  
渐渐的,觉得一个人在家里做,还是不如到公司做有感觉。这时另一个好友,介绍我去华为。记得,在面试那天,旁边一个小孩和我说话,他说,你也是去年毕业的吧,我笑了笑说,2000年毕业的。他惊讶了半天,说,不会吧,看起来这么年轻,怎么会那么老呢?呵,我只好苦笑。
  
面试很顺利,得到各方面的评价都比较高,不久就收到华为的offer了。一直羡慕华为的规范,所以铁了心要去了。然而,在腾讯的好友又介绍我去他们公司,说腾讯的待遇比华为高得多。今天又接到步步高和恒伟业面试的通知。我想凭自己的能力,都可以通过吧。
  
真是有些郁闷,想找工作时,竟没有人理我,找到工作时,又有很多选择了。我的性格内向,向管理方面发展,空间很小,做技术吧,空间本就不大,和朋友创业吧,自己没有创业头脑,也只能跟着他们混。

何去何从,真是有让人为难。

如何面对单调重复的任务

转载时请注明出处:http://blog.csdn.net/absurd/

我们每个人都喜欢做有挑战的,能学到新东西的任务,而不愿意去那些单调重复的,没有什么新意的事情。然而常常事与愿违,在软件开发中,前者并非主流,而后者占了大多数。前者未必每次都能轮到你,而后者也总是要人完成的。

面对后者,你可以选择拒绝接受任务,但那会让人觉得你工作态度不好,以后好的差事可能就轮流不到你了;你可以选择走人,换个地方去做,但那也只能祷告,祈求上帝保佑你在别的地方遇到好任务。其实这都不是好的做法,相反我们应该接受并搞掂它,应该想法转换它们,完成任务但又不必自虐,从中还能学到东西。下面是我的一些经验:

1.
让电脑去做单调重复的工作。Unix文化有一个原则:宁愿花机器一分,不花程序员一秒。单调重复的工作多数都是有规律可循的,有规律可循就可以让电脑来做。

实例一:在早些年代,那时还没有听说autobuild这个概念,发布版本是一件痛苦事情。你要从CVS上取出源代码,编译各个版本((英文版,
日文版) x(大企业版, 小企业版,
试用版)),再制定各个版本的安装包,最后上传到FTP服务器上。如果整个过程顺利,四个小时差不多了,但事实是从来没有顺利过,结果通常要花费两三天才能完成。编译出错,手工拷贝文件出错,上传时放错目录如此等等。那位负责做版本的大姐还算有耐心,坚持做了大半年时间,后来这事没费多大劲就推到我头上了(呵,大家都认为我好欺负)。我接手后,第一件事是花了两天用bash写了个脚本,把整个过程自动化了,在接下来一个版本中进行了验证,并修正几个脚本里的问题,后来发布版本时几乎不用人干预了。

实例二:如果有人问我写得最多的程序是什么。我一定会回答是代码产生器,前前后后、大大小小至少写过十几个代码产生器,小的可能是用bash+awk来做的,也就上百行代码,大的用C/C++来写,动则数千行代码,最大的竟达8000多行C++代码。大部分代码产生器都为我节省了不少时间,或者至少把单调重复的事情变得有趣一点了,而且得到的代码也更稳定可靠。

2.
换种思路,看有没有捷径。有些事情单调重复,本来也是可以让电脑去做的,但是开发相应的工具要费更多的时间,得不偿失,这时不换一种思路,或许别有洞天。

实例一:曾接到一个任务,要求找出一个公共函数库里的所有全局变量。那个库是个大杂烩,里面什么东西都有,凌乱而庞大。时间期限是一周,时间比较充足,即使一个文件一个文件的去找,时间也来得及,但那太痛苦了。更麻烦的是这个库是变化的,可能刚刚完成任务,又有人加了一个全局变量,这样就很难拿到一个最新的结果。怎么呢,我想,编译器肯定是知道哪些是全局变量的,所以第一反应是拿一个开源的编译器修改一下,让编译器告诉我结果。修改编译器可能也要一周时间,但利用它随时可以得到最新结果。有没有更简便的办法呢?猜想VC输出的map文件或许有些帮助,打开map文件一看,果然如此。让VC编译该库并输出map文件,取出全局变量列表,搞掂了。从接受任务到完成任务前后不到一个小时。

实例二:前几天,同事拿一个第三方库,编译时发现那个库是用带硬件浮点数的toolchain编译,而我们的toolchain用的是软浮点数。尽管反汇编出来,没有发现浮点指令,但编译器就是不让链接该库。我们的方案是,反汇编它再重新编译它。但反汇编出来的格式与as的输入格式有些差异,要花不少时间去修改,修改之后发现还是编译不过去。最后,这个任务又落到我头上了。我想既然没有用浮点指令,编译器不让编译可能是因为一个标志引起的,于是花了点时间去研究ELF(linux下的可执行文件格式)文件格式。果然是文件头中一个标志引起的,写了个小程序为该库加上这个标志位,编译就通过了。

3.
换种心情,坦然接受。如果面对一项任务,你别无选择时,那就坦然接受它吧。然后想法说服自己,让自己有个好的心情,这样的任务总是要有人做才行。还可以告诉自己,一定要从中学到点东西,即使从中学不到技术,也要从中学会忍耐。

当我从QA组进入RD组时,我任务是把Win32程序移植到linux下。这个任务比较重要,但绝不是什么好任务,工作本身单调不说,别人还瞧不起。他们认为这都是很简单的,不用动脑子的体力活。同组的同事很多都走了,新来的同事也呆不了多久。但我坚持下去了,移植的同时去研究那些代码,去研究Win32和linux在编程上的差异,
一年之后我成了少数几个了解整个系统架构的人,编程能力大有提高,对软件的可移植性也有了较深的理解。

~~end~~
如何面对单调重复的任务

转载时请注明出处:http://blog.csdn.net/absurd/

我们每个人都喜欢做有挑战的,能学到新东西的任务,而不愿意去那些单调重复的,没有什么新意的事情。然而常常事与愿违,在软件开发中,前者并非主流,而后者占了大多数。前者未必每次都能轮到你,而后者也总是要人完成的。

面对后者,你可以选择拒绝接受任务,但那会让人觉得你工作态度不好,以后好的差事可能就轮流不到你了;你可以选择走人,换个地方去做,但那也只能祷告,祈求上帝保佑你在别的地方遇到好任务。其实这都不是好的做法,相反我们应该接受并搞掂它,应该想法转换它们,完成任务但又不必自虐,从中还能学到东西。下面是我的一些经验:

1.
让电脑去做单调重复的工作。Unix文化有一个原则:宁愿花机器一分,不花程序员一秒。单调重复的工作多数都是有规律可循的,有规律可循就可以让电脑来做。

实例一:在早些年代,那时还没有听说autobuild这个概念,发布版本是一件痛苦事情。你要从CVS上取出源代码,编译各个版本((英文版,
日文版) x(大企业版, 小企业版,
试用版)),再制定各个版本的安装包,最后上传到FTP服务器上。如果整个过程顺利,四个小时差不多了,但事实是从来没有顺利过,结果通常要花费两三天才能完成。编译出错,手工拷贝文件出错,上传时放错目录如此等等。那位负责做版本的大姐还算有耐心,坚持做了大半年时间,后来这事没费多大劲就推到我头上了(呵,大家都认为我好欺负)。我接手后,第一件事是花了两天用bash写了个脚本,把整个过程自动化了,在接下来一个版本中进行了验证,并修正几个脚本里的问题,后来发布版本时几乎不用人干预了。

实例二:如果有人问我写得最多的程序是什么。我一定会回答是代码产生器,前前后后、大大小小至少写过十几个代码产生器,小的可能是用bash+awk来做的,也就上百行代码,大的用C/C++来写,动则数千行代码,最大的竟达8000多行C++代码。大部分代码产生器都为我节省了不少时间,或者至少把单调重复的事情变得有趣一点了,而且得到的代码也更稳定可靠。

2.
换种思路,看有没有捷径。有些事情单调重复,本来也是可以让电脑去做的,但是开发相应的工具要费更多的时间,得不偿失,这时不换一种思路,或许别有洞天。

实例一:曾接到一个任务,要求找出一个公共函数库里的所有全局变量。那个库是个大杂烩,里面什么东西都有,凌乱而庞大。时间期限是一周,时间比较充足,即使一个文件一个文件的去找,时间也来得及,但那太痛苦了。更麻烦的是这个库是变化的,可能刚刚完成任务,又有人加了一个全局变量,这样就很难拿到一个最新的结果。怎么呢,我想,编译器肯定是知道哪些是全局变量的,所以第一反应是拿一个开源的编译器修改一下,让编译器告诉我结果。修改编译器可能也要一周时间,但利用它随时可以得到最新结果。有没有更简便的办法呢?猜想VC输出的map文件或许有些帮助,打开map文件一看,果然如此。让VC编译该库并输出map文件,取出全局变量列表,搞掂了。从接受任务到完成任务前后不到一个小时。

实例二:前几天,同事拿一个第三方库,编译时发现那个库是用带硬件浮点数的toolchain编译,而我们的toolchain用的是软浮点数。尽管反汇编出来,没有发现浮点指令,但编译器就是不让链接该库。我们的方案是,反汇编它再重新编译它。但反汇编出来的格式与as的输入格式有些差异,要花不少时间去修改,修改之后发现还是编译不过去。最后,这个任务又落到我头上了。我想既然没有用浮点指令,编译器不让编译可能是因为一个标志引起的,于是花了点时间去研究ELF(linux下的可执行文件格式)文件格式。果然是文件头中一个标志引起的,写了个小程序为该库加上这个标志位,编译就通过了。

3.
换种心情,坦然接受。如果面对一项任务,你别无选择时,那就坦然接受它吧。然后想法说服自己,让自己有个好的心情,这样的任务总是要有人做才行。还可以告诉自己,一定要从中学到点东西,即使从中学不到技术,也要从中学会忍耐。

当我从QA组进入RD组时,我任务是把Win32程序移植到linux下。这个任务比较重要,但绝不是什么好任务,工作本身单调不说,别人还瞧不起。他们认为这都是很简单的,不用动脑子的体力活。同组的同事很多都走了,新来的同事也呆不了多久。但我坚持下去了,移植的同时去研究那些代码,去研究Win32和linux在编程上的差异,
一年之后我成了少数几个了解整个系统架构的人,编程能力大有提高,对软件的可移植性也有了较深的理解。

~~end~~

三种Cache写入方式原理简介

  
在386以上档次的微机中,为了提高系统效率,普遍采用Cache(高速缓冲存储器),现在的系统甚至可以拥有多级Cache。Cache实际上是位于CPU与DRAM主存储器之间少量超高速的静态存储器(SRAM),通常的大小为8KB~512KB。

  
对Cache的工作原理可以进行如下描述:具有Cache的计算机,当CPU需要进行存储器存取时,首先检查所需数据是否在Cache中。如果存在,则可以直接存取其中的数据而不必插入任何等待状态,这是最佳情况,称为高速命中。当CPU所需信息不在Cache中时,则需切换存取主储器,由于速度较慢,需要插入等待,这种情况称高速未命中。在CPU存取主存储器的时候,按照最优化原则将存储信息同时写入到Cache中以保证下次可能的高速命中。因此,同一数据可能同时存储在主存储器和Cache中;同样,按照优化算法,可以淘汰Cache中的一些不常使用的数据。

  所以,提高高速命中率的最好方法是尽量使Cache存放CPU最近一直在使用的指令与数据,当
Cache 装满后,可将相对长期不用的数据删除,提高 Cache 的使用效率。为保持
Cache
中数据与主存储器中数据的一致性,避免CPU在读写过程中,将Cache中的新数据遗失,造成错误的读数据,确保Cache
中更新过程的数据不会因覆盖而消失,必须将 Cache
中的数据更新及时准确地反映到主存储器中,这是一个写入过程,通常采用的处理方法有:直写式、缓冲直写式与回写式三种。

  1.直写式系统:
CPU对Cache写入时,将数据同时写入到主存储器中,这样可保证Cache中的内容与主存储器的内容完全一致。这种方式比较直观,而且简单、可靠,但由于每次对Cache更新时都要对主存储器进行写操作,而这必须通过系统总线来完成,因此总线工作频繁,系统运行速度就会受到影响。

  2.缓冲直写式系统:为解决直写式系统对总线速度的影响问题,在主存储器的数据写入时增加缓冲器区。当要写入主存储器的数据被缓冲器锁存后,CPU
便可执行下一个周期的操作,不必等待数据写入主存储器 。
这相对于给主存储器增加了一个单向单次高速缓存。比如,在写入周期之后可以紧接着一个数据已存在于Cache中的读取周期,这样就可避免直写式系统造成的操作延时。但这个缓冲器只能存储一次写入的数据,当连续两次写操作发生时,CPU仍需等待。

  3.回写式系统:以前的两种写入方式系统,都是在写Cache的同时,对主存储器进行写操作。实际上这不仅是对总线带宽的占用,浪费了宝贵的执行时间,而且对于有的情况是不必要的,可以通过增加额外的标准来判断是否有必要更新数据。回写式系统就是通过在Cache中的每一数据块的标志字段中加入一更新位,解决主存储器不必要的写操作。比如,若Cache中的数据曾被CPU更新过但还未同时更新主存储器,则该更新位被置1。每次CPU将一块新内容写入Cache时,首先,检查Cache中该数据块的更新位,若更新位为0,则将数据直接写入Cache;反之,若更新位为1,则先将
Cache 中的该项内容写入到主存储器中相应的位置,再将新数据写回到Cache中。

  与直写式系统相比,回写式系统可省下一些不必要的立即回写操作,而在许多情况下这是很频繁出现的。即使一个Cache被更新,若未被新的数据所取代,则没有必要立刻进行主存储器的写操作。也就是说,实际写入主存储器的次数,可能少于CPU实际所执行的写入周期的次数,但回写式系统的结构较复杂,Cache也必须用额外的容量用来存储标志。

  由于回写系统的高效率,现代的Cache大多采取这种方式进行操作。

PC架构之CPU/RAM/IO总线的发展历史

1. 从 IBM PC XT 架构开始...

一开始PC的设计中,CPU/RAM/IO都是被一条总线(BUS)连接起来,所有的部件都必须在同步的模式下面工作,由CPU来决定的其他设备工作在什么频率(Frequency)上。这样就带来一个"互锁"(locked to each other)效应,即大家都被限定在一个被所有设备所能承受的通用时钟频率(Clock Frequency)上面,系统的整体性能不高。

2. 总线的第一次切分

1987年,康柏(Compaq)公司想到一个办法,将系统总线与I/O总线切分开来,可以使得2个不同的总线工作在不同的时钟频率上面。CPU和内存仍旧工作在自己的公用总线上(the System Bus),独立于所有的I/O设备,可以使得高速的CPU/RAM组件摆脱低速I/O设备的束缚。

这里的Bridge,就是我们现在所说的南桥(South Bridge)芯片的前身,而且此处可以看到Bridge实际起到了一个降频的作用(类似AMD K8 CPU的分频机制)。

3. CPU倍频的出现

从80486开始,CPU的发展迅猛,频率大幅攀升,内存开始变得跟不上CPU的发展步伐了!Intel 于是决定在80486中引入倍频(Clock Doubler)的概念!内存依旧工作在系统总线上,与系统总线保持同样的工作频率,而CPU实际的内部工作频率(就是我们常说的CPU主频)是:

CPU 主频 = 外频(系统总线频率/System Bus Frequency)* 倍频 (Clock doubler)
CPU 主频 = 33 MHz * 2 = 66 MHz

用Intel P4 2.8C为例子则是:200 MHz * 14 = 2800 MHz = 2.8 GHz

4. 北桥芯片/前端总线的出现


从前面几点可以看到,PC结构变化的趋势是把速率慢的设备与速率快的设备用切割总线的方式,进行隔离。而这发展到后来,就终于演变出来了北桥(North Bridge)芯片!内存与北桥间的总线称为内存总线,把CPU与北桥间连接的这段总线成为前端总线(Front Side Bus,FSB),也就是系统总线(System Bus)!

得益于Intel的QDR (Quad Data Rate)技术,Pentium 4的CPU可以在每个时钟周期传输4次数据,所以当FSB的工作频率在200MHz的时候,FSB的等效频率为200MHz * 4 = 800 MHz !

注:常说的FSB 800,实际是FSB的等效频率,并不是FSB实际的工作频率!