Source Insight的Rebuild project功能

当使用Source Insight时,有可能会出现有些定义的macro或function找不到(显示为黑),原因通常是source insight的数据库文件被破坏了,这时可以用“Rebuild Project”这个选项去重新生成数据库文件:选择“project->Rebuild Project->Re-Create the whole project from scratch”。详细介绍可参考:http://www.sourceinsight.com/docs35/ad920311.htm

Solaris搭建64位C语言开发环境

刚来公司时,公司的C程序还是32位的。后来我阅读了一些资料,觉得64位的程序才是真正的趋势,所以就开始尝试着开发64位的程序。这篇文章介绍如何在Solaris下搭建64位C语言开发环境,希望给需要的朋友一点帮助。

(1)gcc

Solaris的/usr/sfw/bin/gcc可以用来编译64位C程序,但是需要加-m64编译选项,否则默认编译出来的是32位程序。此外也可以从gcc的官网下载gcc源代码,自行编译安装,但是要注意编译出来的gcc需要是64位的。

(2)gdb

调试64位C程序需要64位的gdb,gdb的安装步骤如下(以7.6版本为例):

1)gunzip gdb-7.6.tar.gz
2)tar xvf gdb-7.6.tar
3)export CC=”/usr/sfw/bin/gcc -m64″
4)mkdir build_gdb
5)cd build_gdb
6)../gdb-7.6/configure –prefix=“/…/…(a folder path)”
7)make
8)make install

需要注意以下两点:

a)Solaris下的tar程序不支持”-z”选项,所以只能先调用gunzip,再调用tar,不能一步搞定:tar -xzf gdb-7.6.tar.gz。

b)目前gdb的最新版本是7.7,在Solaris下编译会有错误。解决办法也很简单,可以参考这篇文章

(3)参考资料

个人认为Oracle的这本《Solaris 64-bit Developer’s Guide》,是在Solaris下开发64位C程序最好的资料。每一位C语言开发者都应该看一下,相信都能受益匪浅。

libumem使用和源码分析系列文章(二)

在使用libumem时,大家可能都有一点好奇,为什么系统会使用libumem封装的malloc,free这些内存管理函数去替换libc中的内存管理函数?其实答案就在malloc.c中的如下代码:

#ifdef __GLIBC__
static void __attribute__((constructor)) umem_malloc_init_hook(void)
{
	if (__malloc_hook != umem_malloc_hook) {
		umem_startup(NULL, 0, 0, NULL, NULL);
		__malloc_hook = umem_malloc_hook;
		__free_hook = umem_free_hook;
		__realloc_hook = umem_realloc_hook;
		__memalign_hook = umem_memalign_hook;
	}
}

void (*__malloc_initialize_hook)(void) = umem_malloc_init_hook;

#else
void __attribute__((constructor))
__malloc_umem_init (void)
{
	umem_startup(NULL, 0, 0, NULL, NULL);
}
#endif

针对上述代码,我们只需关注#ifdef __GLIBC__……#else这个部分,因为替换libc中的内存管理函数的魔法就在这个部分:

(1)__attribute__((constructor))

__attribute__是gcc的关键字,它允许用户声明一些属性。而constructor属性的含义则是被修饰的函数要在main()函数执行前执行。相对应地,还有一个destructor属性,则是在main()函数结束时执行。这对这个属性的讨论,大家可以参考stackoverflow上的一篇帖子

(2)__malloc_hook,__free_hook,__realloc_hook,__memalign_hook

这些其实是GNU C库提供的一些变量,程序通过修改这些变量,可以达到使用自己定义的malloc,free这些函数的目的。详细的解释可以参考:http://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html

一个变量越界导致破坏堆栈的bug

前一段时间在商用系统上出现了core dump,原因是由于一个局部变量写越界导致堆栈被破坏了。在这里,我把这个bug分享一下,希望给需要的朋友一点帮助。简化的代码如下:

typedef struct
{
    ......
} A;

void func1(char *p)
{
    ......
}

void main(void)
{
    A a;
    A *p = &a;
    char b[10];

    ......
    /* 
     * 这个程序运行在SPARC处理器上, 而SPARC处理器的堆栈生长
     * 方向是从高地址向低地址生长的.
     * 这个函数里会把b这个数组写越界,导致p这个变量被破坏, 
     * 结果在下面访问p时,是一个无效的值,导致core dump.
     */
    func1(b);
    printf("%p\n", p);
    ......

    return;
}

通过分析这个bug,分享一下我的心得体会:
(1)SPARC处理器是当年SUN公司开发的一款RISC处理器,如果有朋友从事Solaris软件开发,应该还是会接触到的。我一直觉得作为底层软件工程师,还是应该对处理器结构(包括汇编语言)有尽可能多的了解,这样对我们分析和解决问题都会有很大帮助。比方说,在分析这个bug时,如果知道SPARC处理器的堆栈生长方向是从高地址向低地址生长的,就会让我们把注意力集中在变量定义在p后面的变量上,这样就有了分析问题的方向。在这里,我推荐一篇介绍SPARC处理器的文章(http://web.cecs.pdx.edu/~apt/cs577_2008/sparc_s.pdf),有兴趣的朋友可以看一下。
(2)当遇到堆栈上的变量被莫名其妙改变时,首先还是检查那些地址比被破坏变量地址低的变量。因为一般有可能引起内存越界的代码,像memcpy,都会破坏高地址的值。以这个bug为例。变量p在一开始就赋了值,以后对p都是读操作,没有写操作。现在p变成了一个无效的值,那么首先就应该怀疑访问定义在p后面的变量出了问题。

Chinaunix论坛“Linux/UNIX系统编程,系统程序员成长的基石?”话题讨论的回复

以下是我参与Chinaunix论坛关于“Linux/UNIX系统编程,系统程序员成长的基石?”(http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=4118545&fromuid=28864581)话题讨论的回复,整理出来发到这里,给需要的人做个参考:

(1) 您在Linux/UNIX系统编程行业的经历及经验
我是2006年研究生一年级时开始接触Linux系统编程的,当时是因为我实习的公司用到Linux。后来自己在业余时间看了一些系统编程方面的书,还实现了一个简单的网络协议。通过研究生这两年的经历,算是对Linux系统编程有了一个入门。2008年研究生毕业后,并没有从事Linux/UNIX系统编程的相关工作,而是做手机MMI程序的开发。2010年来到现在这家公司,才又重新开始Linux/UNIX系统编程的工作,目前我主要在Solaris/Linux系统上从事通信网关和服务器程序的开发。通过这些年的经历,我觉得从事Linux/UNIX系统编程最大的益处就是可以获得很多计算机底层系统的知识。举几个例子来说:通常我们都使用gcc编译器编译程序,我们需要了解一些常用编译选项的含义,因为有可能一个编译选项就会导致程序不按你的想法运行;程序不可能没有bug,也许我们需要gdb帮助我们详细了解程序的内存布局,去找到原因;有时程序在一个处理器上运行的好好的,而在另一个上面就不行,此时我们可能又要去了解处理器结构和汇编语言。总之,作为一个底层软件工程师,需要不断地学习计算机最底层最核心的技术,这是件很快乐的事情。

(2) Linux/UNIX系统编程职业生涯的发展探讨
最近几年,随着移动互联网的兴起,前端技术越来越受到大家的重视,而底层技术似乎现在的关注度并不高。我个人觉得Linux/UNIX系统编程知识是程序员的重要基础,无论你从事什么方面的开发,了解系统编程知识和原理,对你是有百益而无一害的,Linux/UNIX系统编程也永远不会被淘汰。所以,我们这些Linux/UNIX系统编程工程师需要做的就是不断学习,提高自己的技术能力。

(3)对于新入门的Linux/UNIX系统编程人员来说,您有什么话对他们说?
我并不是一个资深的工程师,以下是我个人的一点经验和体会,希望给大家一点启示吧:
首先要调整好心态,现在是个浮躁的社会,所以要尽量着让自己静下心来,踏踏实实地去学习技术。
第二就是多实践,经典的技术书籍很多,可是光是阅读,效果并不好。要试着自己写一些代码,这样会加深印象。
第三就是多读好的开源代码。现在github上有很多的优秀代码值得我们学习。拿著名的NoSQL数据库Redis来说,看完Redis的main函数,我们就可以了解到如何创建一个deamon进程了,此外我们也会从中学到如何写个好的高并发服务器程序,如何访问文件系统等等。

以上是我的一孔之见,希望能给大家一点帮助吧。

libumem使用和源码分析系列文章(一)

libumem是2001年由SUN的工程师Jeff Bonwick和Jonathan Adams开发的一个对应用程序进行内存管理的动态链接库。简单地讲,libumem通过提供一组替换C标准库的内存分配函数(malloc,free,realloc等等),从而可以使用户更容易发现和解决关于内存管理的bug。从Solaris 9(Update 3)开始,libumem作为Solaris操作系统的一个标准库提供给应用程序使用。现在libumem已经被移植到其它的平台上,这个网站(https://labs.omniti.com/labs/portableumem)不仅介绍了关于libumem的移植,并且还提供了源代码。从今天起,我打算写一组系列文章,通过对libumem的源代码分析,介绍如何使用libumem以及它的工作原理。

首先,我介绍一下如何使用libumem。这里有两个方法:
(1)通过使用LD_PRELOAD环境变量:
LD_PRELOAD=libumem.so;export LD_PRELOAD
这样就使用户程序使用的是libumem中提供的内存管理函数,而不是C标准库的函数。关于LD_PRELOAD,感兴趣的读者可以参考stackoverflow上的这篇帖子:http://stackoverflow.com/questions/426230/what-is-the-ld-preload-trick
(2)在链接程序时加上 -lumem选项。

当应用程序运行后,我们可通过以下简单的Dtrace脚本去检查程序是否使用的是libumem:

#!/usr/sbin/dtrace -qs

pid$1::malloc:entry
{
    trace(probemod);
}

这个脚本会输出使用哪个模块的malloc函数,如果输出的模块名是类似于“libumem.so.1”,那么就证明程序已经使用libumem了,否则就需要检查一下,看看问题在哪里。

2013年终总结

2013年转眼就要走完了,这是我第一次真正地感觉到时间流逝如此之快。回想这一年,似乎又是碌碌无为,什么也没做。不管怎么样,还是写一篇文章,纪念一下吧。

这一年生活上过得像个机器人:周一至周五上班,周六上午去超市买东西,下午看书或者运动,周日上午打扫卫生,下午休息。每一周几乎都是这样的日子,千篇一律。

工作上表现中规中矩,所有的项目都按时完成了,也没出什么问题,就不细说了。

英语学习也断断续续地坚持下来了,虽然自己并没有觉得有什么质的飞跃,还是会继续坚持下去的。因为英语水平已经到谷底了,坚持下去肯定只会一天比一天好吧。

技术方面上半年主要学习Lua,也就刚入门。下半年开始学习Golang,目前能写简单的程序了,但是要达到自己C语言的水平,还需多下功夫。另外最近一个月也开始看Dtrace,目前已经利用Dtrace解决了两个工作中的问题了,这也给了自己一点小小的成就感和坚持下去的信心。

展望2014年,还是希望自己能够在技术方面能更上一层楼吧,同时自己也已经30岁了,古人云:“三十而立”,自己也在考虑新的职业发展方向。好了,就写到这里吧。

使用Dtrace检查recv()的返回值

这周在新的产品版本上线后,发现监控日志总是会报recv()返回error,并且errno是131(ECONNRESET)。查了一下man手册,发现并没有说recv()会返回ECONNRESET,于是自己便打算一探究竟。想到最近正在学习Dtrace,于是便写了下面这个简单脚本(check_recv.d):

#!/usr/sbin/dtrace -qs

syscall::recv:return
/(int)arg0 <=0 && pid == $1/
{
    printf("recv return: tid=%d, arg0=%d, errno=%d\n", tid, arg0, errno);
}

第四行是触发探针条件:当recv返回0或者-1并且进程号等于输入监控的进程号。
第六行是输出:线程ID,recv()返回值,errno。
使用方法 :check_recv.d 19771(监控进程号)
通过运行脚本,我发现其实recv()返回的是0,而errno也是0,那么为什么监控日志会输出errno是131呢?我又查了一下这个版本新加的代码,发现了下面的逻辑:

if (recv() <= 0)
{
    log(errno)
}

原来在recv()返回0时,也会输出errno。而在recv()返回0时,是不会更新errno的值,只有在recv()返回-1时,才会更新errno的值。所以现在监控日志里的errno其实是以前某个系统调用错误时设置的errno。所以代码应该改为:

if (recv() < 0)
{
    log(errno)
}

问题解决!

客户端—服务器通信模型浅析

客户端—服务器(Client-Server)是我们平时最常见的通信方式,本文就对这一通信模型做个简单介绍。

(一)TCP连接方式:短连接方式?长连接方式

目前,多数的客户端—服务器选择TCP做为传输层协议,也有少数选择UDP或SCTP协议的。而TCP连接有两种工作方式:短连接方式(Short-Live Connection)和长连接方式(Long-Live Connection)。

(1)短连接方式:

当客户端有请求时,会建立一个TCP连接,接收到服务器响应后,就断开连接。下次有请求时,再建立连接,收到响应后,再断开。如此循环。这种方式主要有两个缺点:

a)建立TCP连接需要3次“握手”,拆除TCP连接需要4次“挥手”,这就需要7个数据包。如果请求和响应各占1个数据包,那么一次短连接的交互过程,有效的传输仅占2/9,这个利用率太低了。

b)主动断开TCP连接的一端,TCP状态机会进入TIME_WAIT状态。如果频繁地使用短连接方式,就有可能使客户端的机器产生大量的处于TIME_WAIT状态TCP连接(UNIX系统下,可以使用netstat命令来查看)。

(2)长连接方式:

客户端和服务器建立TCP连接后,会一直使用这条连接进行数据交互,直到没有数据传输或异常断开。在空闲期间,通常会使用心跳数据包(Keep-Alive)保持链路不断开。目前长连接方式应用范围比较广泛。

(二)消息交互方式

(1)一个请求,一个响应

这种消息交互方式如下图所示:


当客户端发出请求后,程序就阻塞在那里,直到收到服务器的响应或者超时。很多对数据库的访问方式都是这样的(像Redis的C程序客户端hiredis)。

(2)多个请求,多个响应

这种消息交互方式如下图所示:


这种方式下,客户端一次可以发送多个请求,而每个请求会带有一个消息标示(Message ID)。这样在客户端收到响应后,就可以根据响应的消息标示,和请求对应起来。