2012年6月7日星期四

Gcc编译可变参数函数之__attribute__属性

    这篇文章又是为了纪念我碰到的无数坑中的一个。话说在其他部门做项目的时候,还是全新的代码,某天就碰到一个程序崩溃问题,通过代码走查和屏蔽代码的方法,发现是程序里面的log函数造成的,log函数有可变参数,由于在写参数的时候没注意参数匹配,会出现类型不匹配或者数目不匹配的情况,然后makefile又没有编译warning,这些可好,编译出来的都正常,结果一跑就异常。还好现在的代码不多,如果是几万,几十万行的代码,找这样一个错误非要找死人啊。我一直不太理解,为什么代码编译时都不编译warning呢,要知道,经常可以在看warning的过程中发现代码的问题啊。
    话说回来,如果仅仅是在gcc编译的时候加上-Wall选项,编译时是检查不出来可变参数不匹配的问题的。我也是google了好久才发现gcc一个特性__attribute__,可以用来解决此种问题。
    首先:

__attribute__语法格式为
__attribute__ ((attribute-list))
其位置约束为:
放于声明的尾部“;”之前。attribute-list可以有很多种,这里主要说format这个属性。
 
    具体的含义如下:

__attribute__ format
该__attribute__属性可以给被声明的函数加上类似printf 或者scanf 的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。该功能十分有用,尤其是处理一些很难发现的bug。

format 的语法格式为
format (archetype, string-index, first-to-check)
format 属性告诉编译器,按照printf, scanf, strftime 或strfmon 的参数表格式规则对该函数的参数进行检查。“archetype”指定是哪种风格;“string-index”指定传入函数的第几个参数是格式化字符串;“first-to-check”指定从函数的第几个参数开始按上述规则进行检查。

具体使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中参数m 与n 的含义为:
m:第几个参数为格式化字符串(format string)
n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢。


m和n的含义一不小心很容易搞错,我就是在这错了很多次,编译一直通不过。
比如说我自己写的一个函数:extern void WriteLog(const char *LogStr,...);
在使用时就是这样:WriteLog("info: %s %d",string info,int num);
那么传入函数的格式化字符串就是:"info: %s %d",即m为1。我们要检查是否匹配的第一个参数就是string info,这个参数在整个函数的参数集合里面位于第二位,所以n就是2。因此就要写成
extern void WriteLog(const char *LogStr,...) __attribute__((format(printf,1,2)));
如果这个函数是一个类的成员函数,由于成员函数默认带this指针,所以m和n的值就要加1,写法就变成:
void WriteLog(const char *LogStr,...) __attribute__((format(printf,2,3)));
由于是类的成员函数,所以上面的写法放在h文件里面就可以了。

    如果m和n的值写错,gcc会提示你n不能小于m啊,或者m的值不是对应的格式化字符串等等。把函数写好后,再加上-Wall选项,这个就会提示warning了。不过我现在还不知道可以让出现这种warning的时候把它当error来处理,虽然有选项可以让所有的warning视为error,但是有些warning是允许存在的,怎么区分这类warning呢?这个留待以后吧。

    另:gcc __attribute__属性的文档放到google drive里面了。嗯,我还真是个google控,但是google+ 界面太烂了。

Crontab 碰到的一些问题(环境变量,cmd)

    之前在一台机器上配置得好好的,昨天换了一台机器,然后配置的时候怎么都不行,郁闷。现象:配置后crontab后,程序没有运行。
    尝试解决之:首先是怀疑crontab的服务没有启动,因为没有root权限,不能用命令看crontab状态,问同事,同事说crontab是默认启动的,然后我ps -ef|grep cron一把,发现cron是在跑的。这就不懂了,因为程序里面是有写log的,但是log文件可以看出来程序一直没跑。再问同事,然后他加了一条cmd在crontab里面:echo "1" 1>/home/temp.log,过了一会这个log文件就有内容了,可以看出来cron服务是正常运行的。这就怪了,我的程序是一个很简单的程序啊,怎么就跑不起来。当时同事说一般这种问题都是环境变量引起的。我的程序是使用了mysql库,但是我在shell里面直接运行都可以,应该不存在环境变量的问题吧。(就是这个想法导致我后来绕了一个大弯)。
    到这个时候,我开始怀疑是不是在cron里面程序的标准输出被重定向了啊,其实程序是运行了的呢(这里我没有先想办法确定程序是否运行)。所以我就写了个简单的测试程序,就是main函数里面只有一个printf("test"),然后在crontab里面配置cmd为:/home/test 1>/home/test.log,稍等了一下,log文件马上就有内容了,这下看来是程序有问题了。我把自己的程序main函数改成只有一个打印语句,居然还是不行,怒了。难道是编译的时候有的库有问题(这里我已经走远了。。),为了证实这个,我改makefile,一个个库来编译(当时都居然没想到二分法!),最后终于发现是只要编译了mysqlclient库就有问题(我已经走远到天涯海角了),然后上网google之,还是没发现类似的问题啊。
    百思不得其解,然后突然想到看别人的crontab配置里面有这样一句:2>&1,意思就是说标准错误信息和标准输出一样可以被重定向到一个文件,也许这个可以让我看看有没有出错信息呢。加上,马上发现程序运行时提示找不到mysql库,这下我终于想到是环境变量的问题了。由于我没有root权限,不能直接在/etc/crontab里面加上环境变量。所以就要写一个运行脚本,在脚本里面export LD_LIBRARY_PATH=/data/lib,然后在脚本里面调用程序。cron配置的时候就运行这个脚本就可以了。
    修改完,过一会居然还有错,提示找不到程序文件。嗯,我在脚本里面调用程序用的是"./"这个相对路径来调用程序,但是crontab里面用的是用绝对路径来找运行脚本,比如:/home/auto_run.sh,这样就导致找不到程序文件了。修改成:cd /home;./auto_run.sh,有cd /home这句命令后,就可以改变当前目录,然后可以找到程序文件了。这下终于可以了。
    回想这个问题,还真是绕了不少弯啊,主要是没有利用1>/home/temp.log 2>&1这种来查看输出。因为cron是一个守护进程,它设计成为捕捉所有的stdout和stderr然后mail给用户(这个没看cron的源代码,估计也是这样),所以在crontab里面只配置echo是不行的,必须把stdout重定向到文件,这样就可以了。

2012年6月6日星期三

linux crontab 设置定时运行

因为这段时间做的程序都是要设置成为按固定时间运行,所以必须要对crontab进行一些配置。下面这篇文章说得不错,记录之。原帖地址:crontab 简介


命令简介
crontab命令常见于Unix和类Unix的操作系统之中,用于设置周期性被执行的指令。该命令从标准输入设备读取指令,并将其存放于“crontab”文件中,以供之后读取和执行。该词来源于希腊语 chronos(χρόνος),原意是时间。
通常,crontab储存的指令被守护进程激活, crond常常在后台运行,每一分钟检查是否有预定的作业需要执行。这类作业一般称为cron jobs。
crontab文件
crontab文件包含送交cron守护进程的一系列作业和指令。每个用户可以拥有自己的crontab文件;同时,操作系统保存一个针对整个系统的crontab文件,该文件通常存放于/etc或者/etc之下的子目录中,而这个文件只能由系统管理员来修改。
crontab文件的每一行均遵守特定的格式,由空格或tab分隔为数个领域,每个领域可以放置单一或多个数值。
使用说明
语法介绍
使用权限: root用户和crontab文件的所有者
 crontab格式语法:
crontab [-e [UserName]|-l [UserName]|-r [UserName]|-v [UserName]|File ]
说明:
crontab 是用来让使用者在固定时间或固定间隔执行程序之用,换句话说,也就是类似使用者的时程表。-u user 是指设定指定 user 的时程表,这个前提是你必须要有其权限(比如说是 root)才能够指定他人的时程表。如果不使用 -u user 的话,就是表示设定自己的时程表。
参数:
-e [UserName]: 执行文字编辑器来设定时程表,内定的文字编辑器是 VI,如果你想用别的文字编辑器,则请先设定 VISUAL 环境变数来指定使用那个文字编辑器(比如说 setenv VISUAL joe)
-r [UserName]: 删除目前的时程表
-l [UserName]: 列出目前的时程表
-v [UserName]:列出用户cron作业的状态
时程表的格式如下:
f1 f2 f3 f4 f5 program
其中 f1 是表示分钟,f2 表示小时,f3 表示一个月份中的第几日,f4 表示月份,f5 表示一个星期中的第几天。program 表示要执行的程式。
当 f1 为 * 时表示每分钟都要执行 program,f2 为 * 时表示每小时都要执行程式,其余类推
当 f1 为 a-b 时表示从第 a 分钟到第 b 分钟这段时间内要执行,f2 为 a-b 时表示从第 a 到第 b 小时都要执行,其余类推
当 f1 为 */n 时表示每 n 分钟个时间间隔执行一次,f2 为 */n 表示每 n 小时个时间间隔执行一次,其余类推
当 f1 为 a, b, c,... 时表示第 a, b, c,... 分钟要执行,f2 为 a, b, c,... 时表示第 a, b, c...个小时要执行,其余类推
使用者也可以将所有的设定先存放在档案 file 中,用 crontab file 的方式来设定时程表。
由于unix版本不一样,所以部分语法有差别,例如在hp unix aix 中设定间隔执行如果采用*/n 方式将出现语法错误,在这类unix中 ,间隔执行只能以列举方式,详请见例子。
使用方法:
用VI编辑一个文件 cronfile,然后在这个文件中输入格式良好的时程表。编辑完成后,保存并退出。
在命令行输入
$: crontab cronfile
这样就将cronfile文件提交给c r o n进程,同时,新创建cronfile的一个副本已经被放在/ v a r / s p o o l / c r o n目录中,文件名就是用户名。
例子:
每月每天每小时的第 0 分钟执行一次 /bin/ls :
0 * * * * /bin/ls
在 12 月内, 每天的早上 6 点到 12 点中,每隔 20 分钟执行一次 /usr/bin/backup :
*/20 6-11 * 12 * /usr/bin/backup
周一到周五每天下午 5:00 寄一封信给 alex_mail_name :
0 17 * * 1-5 mail -s "hi" alex_mail_name < /tmp/maildata
每月每天的午夜 0 点 20 分, 2 点 20 分, 4 点 20 分....执行 echo "haha"
20 0-23/2 * * * echo "haha"
晚上11点到早上8点之间每两个小时,早上8点
0 23-7/2,8 * * * date
在hp unix,中,每20分钟执行一次,表示为:0,20,40 * * * * 而不能采用*/n方式,否则出现语法错误
注意:
1. 当程式在你所指定的时间执行后,系统会寄一封信给你,显示该程式执行的内容,若是你不希望收到这样的信,请在每一行空一格之后加上 > /dev/null 2>&1 即可。
2. %在crontab中被认为是newline,要用\来escape才行。比如crontab执行行中,如果有"date +%Y%m%d",必须替换为:"date +\%Y\%m\%d"
创建crontab?
在考虑向cron进程提交一个crontab文件之前,首先要做的一件事情就是设置环境变量EDITOR。cron进程根据它来确定使用哪个编辑器编辑crontab文件。99 %的UNIX和LINUX用户都使用vi,如果你也是这样,那么你就编辑$HOME目录下的.profile文件,在其中加入这样一行:
EDITOR=vi; export EDITOR
然后保存并退出。
不妨创建一个名为<user>cron的文件,其中<user>是用户名,为了提交你刚刚创建的crontab文件,可以把这个新创建的文件作为cron命令的参数:
$ crontab davecron
现在该文件已经提交给cron进程,同时,新创建文件的一个副本已经被放在/var/spool/cron目录中,文件名就是用户名(即,dave)。
列出crontab文件
为了列出crontab文件,可以用:
$crontab -l
编辑crontab文件
如果希望添加、删除或编辑crontab文件中的条目,而EDITOR环境变量又设置为vi,那么就可以用vi来编辑crontab文件,相应的命令为:
$ crontab -e
可以像使用vi编辑其他任何文件那样修改crontab文件并退出。
删除crontab文件
为了删除crontab文件,可以用:
$ crontab -r
恢复丢失的crontab文件
如果不小心误删了crontab文件,假设你在自己的$HOME目录下还有一个备份,那么可以将其拷贝到/var/spool/cron/<username>,其中<username >是用户名。如果由于权限问题无法完成拷贝,可以用:
$ crontab <filename>
其中,<filename>是你在$HOME目录中副本的文件名。
crontab中的输出配置
crontab中经常配置运行脚本输出为:>/dev/null 2>&1,来避免crontab运行中有内容输出。
shell命令的结果可以通过‘> ’的形式来定义输出
/dev/null 代表空设备文件
> 代表重定向到哪里,例如:echo "123" > /home/123.txt
1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于"1>/dev/null"
2 表示stderr标准错误
& 表示等同于的意思,2>&1,表示2的输出重定向等同于1
那么重定向输出语句的含义:
1>/dev/null 首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,不显示任何信息。
2>&1 表示标准错误输出重定向等同于标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。