您的位置:首页 > 其它

HPC组点滴记忆—记录我的成长(技术方面)

2012-02-03 16:40 211 查看
HPC组给我带来了一笔宝贵的有关C语言开发大型软件的技术财富,下面是这些点滴财富记忆

1 指针和指向指针的指针两个不同类型变量的命名

if ((ret = set_execunit2pcc(job_p, job_p->jrset, &recvjob_p->execunit.rsv_vn)) < 0) {

中间这个多个&的错误避免办法:

以后注意指针变量尽量写成:char * str_p,不要写成 str,避免指针误操作

指针的指针变量写成:char ** str_pp, 不要写成 str_p,避免指针误操作

指针变量最好命名为*_p,指针的指针变量最好命名为*_pp,如果不这样命名,有可能

导致错误隐患。所以关注不这样命名的指针变量,就有可能及早地发现类似今天下午那

个对job_p->jrset又多加个取地址符的错误。

2:长度变量命名:ssize_t data_sz; ssize_t rsv_node_sz = 0;

3:PJSD目录下面架构特色

定义的外部函数,声明统一放到了pjsd_prt.h文件中

定义的内部函数,当然声明在其内部文件里

定义的头文件要用

#ifndef _PJS_DEF_H

#define _PJS_DEF_H

...........

...........

#endif // _PJS_DEF_H

来包裹

main.c文件中没有自己再定义结构体,反而定义了几个全局变量

各个C源码文件(非头文件)都include 了pjsd_include 头文件,而这个头文件里也include 了 pjsd_prt.h文件,还有pjsd_struct.h文件,后者为公共结构体的定义

各个C源码文件里声明的结构体当然只为该文件内引用

4:GCC警告选项理解

../SRC/libpjmacl/pjmacl_pjm.c:332: 警告: assignment from incompatible pointer type

data_p = (PjmAclPrioAddr_t *)calloc(1, sizeof(PjmAclPrioAddr_t))

uint64_t *data_p;

../SRC/libpjmacl/pjmacl_jaccmd.c:1661: 警告: dereferencing type-punned pointer will break strict-aliasing rules

pjmacl_free((void**)&cmn_ofs_rg_p);

PjmAclComnAndOfsList_t *cmn_ofs_rg_p = NULL;

../SRC/libpjmacl/pjmacl_pjm.c:2867: 警告: no previous declaration for ‘pjmacl_get_node_pri_U’

pjmacl_get_node_pri_U如果为外部函数的话,就应该有个声明加到类似pjsd_prt.h中

../SRC/libpjmfep/pjmfep_exec.c:406: 警告: implicit declaration of function ‘pjmfep_evc_queue_init’

../SRC/libpjmfep/pjmfep_exec.c:406: 警告: nested extern declaration of ‘pjmfep_evc_queue_init’

‘pjmfep_evc_queue_init’函数就没有被声明过,而且如果定义文件和引用该函数文件不一样时,引用该函数的文件也没有提前用extern 声明该函数

警告选项中通常关注那些引用未初始化值的语句

变量定义时,就最好初始化一下,省得到GCC 警告信息里刷数据

GCC警告信息过于严格,有些警告信息就不是BUG,而是误扰信息了。比如调用函数中实参是&aa,这时这个指针就失去了具体的类型

dereferencing type-punned pointer will break strict-aliasing rules

call_ret = cmdlib_pjmcall(PJMCMD_CMD_PADLINE, &reqbufinf, buf_num, NULL, NULL, NULL, &exitval, &retval, (char **)&padlresp_p, &respsize, &resptype, clst_p);

中的(char **)&padlresp_p 就会产生上述警告信息

而&exitval 会产生

类似passing argument 1 of 'pjmd_pjsutil_rscunit_name2id' discards qualifiers from pointer target type的警告信息提示

dereferencing type-punned pointer will break strict-aliasing rules警告信息跟优化选项-fstrict-aliasing 有关。当开启这个优化选项时,可能优化会导致源代码中部分语句缺失,而造成系统工作不正常,所以就所有的违反strict-aliasing rules原则的地方加了条这个警告信息,提醒读者检查这个地方的代码,看看是不是如果优化后会导致部分语句工作不正常

同时这个信息提示还有另外一个目的,两个不同类型的指针指向同一个地址时,极有可能会导致代码编写出错(比如犯大小端的错误,还有地址字节对齐的错误),所以这个警告信息还有这一层的目的。

5:统一编码方法

面对同一问题时,不要在代码中用两种以上的方法实现,最好统一一下,这样增强了程序的可维护性

今天发现那个两个不同的寻找CPUIDX 方法,让我觉得编码时:

最好统一成一种方法,不能有时图省事,换成另外一种简单的方法,这样以后遇到BUG时,调试起来会有一些疑惑的

6:超级进程的理解

今天明白了什么叫超级进程,Damon进程,原来脱离终端设备后,以后这个终端关闭后,原先在那个终端里启动的超级进程还会继续执行的,但是在那个终端里启动的其他进程则要全部终结了

7:错误和调试消息的分级别显示

这样有利于以后调试和更好的阅读大型软件的工作流程

EMERG = 0, // システムが利用できないような緊急事態

// ノード自体の停止を引き起こす様な重大なエラー。

// pjms_loglevel == 1 で内部ログ、個別ログ、シスログに出力。

CRIT = 1, // 致命的な状態

// 異常であり、デーモン等が継続処理不可能なもの。

// pjms_loglevel == 1 で内部ログ、個別ログ、シスログに出力。

ERROR = 2, // エラー状態

// 異常ではあるが、想定されているエラーでデーモン等は継続処理可能なもの。

// pjms_loglevel == 1 で内部ログ、個別ログに出力。

WARN = 3, // 警告状態

// pjms_loglevel == 1 で内部ログ、個別ログに出力。

INFO = 5, // 情報メッセージ

// pjms_loglevel == 1 で内部ログ、個別ログに出力。

DEBUG1 = 6, // デバッグレベルメッセージ(1)

// システム負荷を考慮し、可能な限り出力を抑えたデバッグ情報。

// 共通ライブラリ内のエラーメッセージ用に使用する。

// 他コンポとのI/F確認に使用する。

// pjms_loglevel == 2 で内部ログ、個別ログに出力。

DEBUG2 = 7, // デバッグレベルメッセージ(2)

// システム負荷を考慮し、出力を抑えたデバッグ情報。

// 重要な関数トレースに使用する。

// pjms_loglevel == 3 で内部ログ、個別ログに出力。

好像error显示的错误比较重要;

DEBUG1 显示的错误还可以继续执行,不是很严重;

DEBUG2 用于显示一些更详细

8:野指针的注意事项

如果一个指针不需要再使用时,及时把它置为NULL

原因:

pjmd_pjsrsp_getjoblist函数中

job_p指针只在 for (tid = 0; tid <= maxtid; tid++) {

}中出现,在该函数的下面代码中并没有出现,那么在下面代码执行时,这个指针就是

个野指针,最好在跳出这个循环时,把job_p置为NULL,这样关于我今天下午犯的复制

错误就可以在第一次疏通时通过让Damon当掉的方式及时检测出来(因为这个复制错误

就是在该函数下面代码中继续引用了这个野指针)

9:一个C语言BUG追踪记录(学习如何调查C语言内存错误)

BUG现象:在配置文件指定ResourceGroup后,PJMD无法启动

用GDB调试core文件,及查看LOG文件后,发现函数pjmd_conff_getconf的以下代码在free的时候发生了问题

张SAN的调查结论:

通过查阅在FREE函数之前的代码,找到在开始申请这块后来FREE出错的内存代码地方,结果发现

copy_rscgrp的调用多余,在该函数中试图向一个没有申请空间的指针拷贝内容。该问题本该在编译阶段就被发现,凑巧的是,这个空指针前面多了个取地址符&。于是,一个空指针神奇般的变成了“合法”指针,从而逃出了编译器的手掌心。

修改方法就是把这个调用删掉。

我的调查结论1:

好像也可以用valgrind 内存检测工具直接定位到copy_rscgrp

的非法内存访问,如果这样可行的话,就可以又缩短BUG调查时间了。

我的调查结论2:

RE 张SAN的结论: copy_rscgrp的调用多余,在该函数中试图向一个没有申请空间的指针拷贝内容。该问题本该在编译阶段就被发现,凑巧的是,这个空指针前面多了个取地址符&。于是,一个空指针神奇般的变成了“合法”指针,从而逃出了编译器的手掌心。

我觉得这个虽然多加个取地址符&,应该还是逃不脱GCC的手掌心的

我发现的PJSD下面的set_execunit2pcc实参job_p->jrset多个取地址符的BUG,就是在GCC警告信息中:

../SRC/pjsd/pjsd_jobctl.c:2717: warning: passing argument 2 of 'set_execunit2pcc' from incompatible pointer type

发现的。看来要注意GCC警告信息了。

项目经理的结论:

肯定我的意见,让我对类似的编译错误进行一次全面检查。

10:调试PJM软件内存错误的若干方法(我在组内wiki上的技术留言)

当时我写的这个技术总结,貌似并没有引起组内人的注意,可能是我们客户是个大好人,很能容忍我们的BUG不断出现

方法1:

可通过LIBPJMS目录下的内存分配检查工具来调试

需要通过以下步骤使内存检测工具生效:

修改MAKEFILE文件,使DEBUG 赋值为-DMDEBUG -DMDEBUG_CMD=4
重新编译源代码
把生成的可执行文件和库文件分别拷到/usr/sbin,/usr/bin和/lib64目录下

该内存检测工具有三个作用:

检查内存读写是否越界,可通过log文件中的underflow和overflow信息看出具体是哪个函数对已分配内存读写时造成了破坏
检查内存是否泄漏,系统结束时会调用pjms_malloc_report函数,可以通过该函数输出的"alloctbl_p%d...."信息,查明系统中未释放的内存块信息
如果连续两次free同一内存区域时,系统可以通过调用pjms_free函数,输出ptr_p=NULL错误提示信息,而不至于崩溃。

方法2:

通过进程崩溃时生成的core文件来调试

找到系统崩溃时对应生成的core文件,该core文件名包含有崩溃进程ID号
执行命令gdb 崩溃进程对应的可执行文件 core文件
执行bt命令,得到进程崩溃时保存的调用栈信息,通过该信息的前面几条语句,可以找出导致进程崩溃的代码块语句

方法1和方法2可以结合着使用,另外也可以用valgrind等内存检测工具来调试内存错误。内存错误调试是C语言开发大型软件时比较费时头疼的工作。在debug阶段通过内存分配方面的严格检查让软件及时崩溃,及时发现这些内存错误,就可以在以后软件运行时避免对内存的恶意对写破坏,从而改进软件质量

11:软件版本升级时的LOG信息改善(一个说明版本维护意思在逐渐增强的提案)

我写的这个提案基于之前调查的一个BUG,当时调查了好长时间,结果发现是版本没好好维护带来的错误

[现状]:

SVN中保留的版本升级LOG信息比较杂乱,格式不统一,缺乏详细的版本升级环境描述。

[存在问题]

软件出现BUG时,比较难以恢复到之前版本上,进行更有效的调试

[解决方法]

1. 将版本升级时对应的LOG信息记录格式规定为:

[开发阶段]:[修改级别]:[升级模块名称]:[升级信息描述]

Ø 开发阶段记录了版本升级时对应的项目进展情况,可以为FD,SD,merge,式样变更等NQS组常见开发阶段。

Ø LOG信息级别可以选择为CRIT,USUAL,INFO,DEBUG级别之一,CRIT代表比较重要的,影响到软件整体功能的改动,USUAL代表一般的,只影响到局部枝节功能的改动,INFO和DEBUG则表明所作的改动仅仅是增加了一些调试信息。

Ø 升级模块名称指修改代码所处的模块名称

Ø 升级信息描述可以理解为升级LOG信息备注,负责版本升级的人员可以在此再详细描述此次升级原因及过程

2. 通过这种统一详细的LOG信息记录,以后软件在某版本上出现BUG时,可以直接查看升级LOG信息记录,在理解了最近的升级记录后,就能比较方便迅速地找出BUG产生原因。

PS:我们组有相当一部分BUG是这种版本维护不当造成的。

12:还要关注程序中微小错误

{"pjsub-fs ", PJSUB_OPT_FS}, fs和其后的双引号之间多个空格、

13:加深对C语言常见库函数缺陷的认识

C语言库函数的设计也沿袭了C语言的简单之特色

但是过于简单的风格,让我们觉得这是终极大牛程序员的语言,不适合新手来进行大型软件开发

比如 strcpy,memcpy等函数,感觉就做的不是很好,如果复制源和复制目的地内存区域有重叠现象发生时,就出现错误了

所以我们有时候调试程序时,记住可能不是我们自己代码的错误,可能是调用库函数的错误。

14:断言的有效使用,可以更快速地定位内存越界BUG

比如在代码中,可能的会导致指针越界访问动态分配内存区域外的地方加上断言。拿该指针的值和该动态内存区域边界地址相比较,如果大于该边界地址值,说明已经越界了,那在这个地方就得赶紧崩溃,从而方便开发者快速定位导致破坏内存区域的代码位置,方便调试内存越界BUG。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: