上接:如何在嵌入式Linux产品中做立体、覆盖产品生命期的调试 ( 4)
这篇谈谈error code的使用和做法:
程序不出错则大家都好,出错了,首先找谁,error code!
大家知道,嵌入式Linux产品的软件一般有个分层的:
从上到下依次为:
application层
middleware层/Enabler层
driver层
kernel层
每一层都会有error code, 下层的error code提供给上层调用时用;
所以说,上面一层的代码调用下面一层的API时,如果该API提供了error code,
则说明这个API还是较好的,如果没有,对于你纠错帮助不大。
下面我举几个常用的:
application调用glib的函数:
application调用sqlite的函数:
//sqlite提供error 的手段比较多
1. 直接返回错误原因
2. 返回错误码,有个函数可以根据错误码提取出错原因:
这个函数是sqlite里面比较亲切的函数:const char *sqlite_error_string (int rc);
多用这个函数,你会发现sqlite原来这么简单;
sqlite算是在嵌入式Linux产品中用的较广泛的数据库了,这个开源项目提供的error code/error string是继承自Glib, 并发扬广大了;
application调用gconf:
//原型
gboolean gconf_init (GError **error);
GError *error = NULL;
if (!gconf_init (&error))
{
fprintf (stderr, “Failed to initialize gconf, reason: %s/n”, error->message);
g_error_free (error);
error = NULL;
}
//在gconf系统里面,主要复制了GError的一套内容,并且把出错信息填充到GError中,
这样调用者就可以很好的获取出错信息;
gconf也是嵌入式Linux产品中使用的较多的用于记录setting的开源项目,对于出错的处理,继承自Glib,并没有向Sqlite那样做了扩充,应该说是中规中矩吧,也可以了。
上面的sqlite/gconf/dbus/gstreamre等都是使用比较多的开源软件,本省提供的error code、error string也是比较好的。
如果我们自己写程序,比如写一些middleware这一层的代码,就需要给上层的application提供足够好的error code的支持。
下面就看看如何在middleware层增加对error code的很好支持,首先研究4个开源项目的error code的做法:
A) sqlite,
B) gconf
C) gstreamer
D) dbus
A) sqlite的做法
==================sqlite 的error code 做法=============
Sqlite的error code的做法比较简单、直白!没有什么花花肠子。
就是error code + error string.
1 定义error code(也称return code, rc)的枚举
/*
** Return values for sqlite3_exec() and sqlite3_step()
*/
#define SQLITE_OK 0 /* Successful result */
/* beginning-of-error-codes */
#define SQLITE_ERROR 1 /* SQL error or missing database */
#define SQLITE_INTERNAL 2 /* NOT USED. Internal logic error in SQLite */
// … 参考sqlite.h.in文件,编译后放在sqlite.h中
#define SQLITE_DONE 101 /* sqlite3_step() has finished executing */
/* end-of-error-codes */
2 定义与error code对应的字串
const char *sqlite3ErrStr(int rc){
const char *z;
switch( rc & 0xff ){
case SQLITE_ROW:
case SQLITE_DONE:
case SQLITE_OK: z = "not an error"; break;
case SQLITE_ERROR: z = "SQL logic error or missing database"; break;
// 参考sqlite/src/main.c中
default: z = "unknown error"; break;
}
return z;
}
3 sqlite和GError的关系不大,另外sqlite还有一个很有个性的东西:
提取“最近”发生的错误码或者错误信息!
这三个可是sqlite调试的大功臣啊!当调用sqlite的函数发生错误时,不要客气,调用这几个函数去查查错误原因,事半功倍!
const char *sqlite3_errmsg(sqlite3 *db);
const void *sqlite3_errmsg16(sqlite3 *db);
int sqlite3_errcode(sqlite3 *db)
B) gconf的做法
==================gconf 的error code 做法=============
Gconf的error code最没有个性,就是想方设法满足GError! 往GError中填充信息。
1 定义gconf中可能出现的错误的种类,这里使用的是枚举
/* Sync with ConfigErrorType in GConf.idl, and some switch statements in the code */
typedef enum { /*< prefix=GCONF_ERROR >*/
GCONF_ERROR_SUCCESS = 0,
GCONF_ERROR_FAILED = 1, /* Something didn't work, don't know why, probably unrecoverable
// … 参考gconf-error.h
GCONF_ERROR_IN_SHUTDOWN = 16 /* server is shutting down */
} GConfError;
2 定义与上面枚举对应的错误信息字符串数组:
static const gchar* err_msgs[] = {
N_("Success"),
N_("Failed"),
// … 参考gconf-error.c
N_("No database available to save your configuration")
};
这点和GStreamer的差距:没有GSteamer做的巧妙
和 DBus的差距:不能像dbus那样灵活修改;
3 定义gconf的身份证,就是声明一个quark,和GStreamer类似
#define GCONF_ERROR gconf_error_quark ()
GQuark
gconf_error_quark (void)
{
static GQuark err_q = 0;
if (err_q == 0)
err_q = g_quark_from_static_string ("gconf-error-quark");
return err_q;
}
4 把错误信息填充到GError中
static GError*
gconf_error_new_valist(GConfError en, const gchar* fmt, va_list args);
GError*
gconf_error_new(GConfError en, const gchar* fmt, ...);
void
gconf_set_error (GError** err, GConfError en, const gchar* fmt, ...);
// … 具体参考gconf-error.c
从上面可以看出,GConf的error是委身于GError的,没有独立性。
5 外部调用GConf的接口API,大部分接口API都有一个GError** err参数,就是用这个参数接收Gconf返回的错误信息的;
C) gstreamer的做法
==================Gstreamer 的error code 做法=============
Gstreamer中如何做error code/error string的?
在GStreamer中有4大类error:
1 GST_CORE_ERROR
2 GST_LIBRARY_ERROR
3 GST_RESOURCE_ERROR
4 GST_STREAM_ERROR
每一大类都有一些细分的error:
1, core类error,主要是gstreamer核心概念方面的错误
typedef enum
{
GST_CORE_ERROR_FAILED = 1,
GST_CORE_ERROR_TOO_LAZY,
GST_CORE_ERROR_NOT_IMPLEMENTED,
GST_CORE_ERROR_STATE_CHANGE,
GST_CORE_ERROR_PAD,
GST_CORE_ERROR_THREAD,
GST_CORE_ERROR_NEGOTIATION,
GST_CORE_ERROR_EVENT,
GST_CORE_ERROR_SEEK,
GST_CORE_ERROR_CAPS,
GST_CORE_ERROR_TAG,
GST_CORE_ERROR_MISSING_PLUGIN,
GST_CORE_ERROR_CLOCK,
GST_CORE_ERROR_NUM_ERRORS
} GstCoreError;
2, 库操作方面的error
typedef enum
{
GST_LIBRARY_ERROR_FAILED = 1,
GST_LIBRARY_ERROR_TOO_LAZY,
GST_LIBRARY_ERROR_INIT,
GST_LIBRARY_ERROR_SHUTDOWN,
GST_LIBRARY_ERROR_SETTINGS,
GST_LIBRARY_ERROR_ENCODE,
GST_LIBRARY_ERROR_NUM_ERRORS
} GstLibraryError;
3,资源方面的error
typedef enum
{
GST_RESOURCE_ERROR_FAILED = 1,
GST_RESOURCE_ERROR_TOO_LAZY,
GST_RESOURCE_ERROR_NOT_FOUND,
GST_RESOURCE_ERROR_BUSY,
GST_RESOURCE_ERROR_OPEN_READ,
GST_RESOURCE_ERROR_OPEN_WRITE,
GST_RESOURCE_ERROR_OPEN_READ_WRITE,
GST_RESOURCE_ERROR_CLOSE,
GST_RESOURCE_ERROR_READ,
GST_RESOURCE_ERROR_WRITE,
GST_RESOURCE_ERROR_SEEK,
GST_RESOURCE_ERROR_SYNC,
GST_RESOURCE_ERROR_SETTINGS,
GST_RESOURCE_ERROR_NO_SPACE_LEFT,
GST_RESOURCE_ERROR_NUM_ERRORS
} GstResourceError;
4 编解码方面的error
typedef enum
{
GST_STREAM_ERROR_FAILED = 1,
GST_STREAM_ERROR_TOO_LAZY,
GST_STREAM_ERROR_NOT_IMPLEMENTED,
GST_STREAM_ERROR_TYPE_NOT_FOUND,
GST_STREAM_ERROR_WRONG_TYPE,
GST_STREAM_ERROR_CODEC_NOT_FOUND,
GST_STREAM_ERROR_DECODE,
GST_STREAM_ERROR_ENCODE,
GST_STREAM_ERROR_DEMUX,
GST_STREAM_ERROR_MUX,
GST_STREAM_ERROR_FORMAT,
GST_STREAM_ERROR_NUM_ERRORS
} GstStreamError;
上面定义这么多的枚举量,很多人会直接去返回这个错误,没错,可以这么做。但是不直观,不能充分表达错误的原因!
为了充分把错误的原因传达给调用者,GStreamer把上面的error枚举都做了一个对应的字符串,作的比较巧妙。
做法如下:
1 定义一个quark宏
#define QUARK_FUNC(string) /
GQuark gst_ ## string ## _error_quark (void) { /
static GQuark quark; /
if (!quark) /
quark = g_quark_from_static_string ("gst-" # string "-error-quark"); /
return quark; }
大家知道:夸克是比原子还小的单位。在glib里面quark是用来区分唯一性的;
一般是根据一个字符串得出一个唯一的quark, 这个产出的quark就用来唯一标识那个字符串;
2 为每种error颁发一个身份证明:
QUARK_FUNC (core);
QUARK_FUNC (library);
QUARK_FUNC (resource);
QUARK_FUNC (stream);
上面这4句话就是4个函数的implementation了:
GQuark gst_core_error_quark (void){ //…}
GQuark gst_library_error_quark (void) { //…}
GQuark gst_ resource _error_quark (void) { //…}
GQuark gst_stream_error_quark (void) { //…}
//为了方便起见,再定义二代身份证
#define GST_LIBRARY_ERROR gst_library_error_quark ()
#define GST_RESOURCE_ERROR gst_resource_error_quark ()
#define GST_CORE_ERROR gst_core_error_quark ()
#define GST_STREAM_ERROR gst_stream_error_quark ()
有了表示4类error的身份证了。
3 定义一个做字符串数组的宏
#define TABLE(t, d, a, b) t[GST_ ## d ## _ERROR_ ## a] = g_strdup (b)
这里面
t 是字符串数组的名字;
d 是domain,上述枚举量的中间部分(蓝 {MOD}标出);
a 是上述枚举量的后面半截(红 {MOD}标出);
b 是错误字符串,这里可以给出详细的信息;
4 把上面定义的4类枚举变量对应的错误信息做4个字符串数组
4-1: core方面的详细错误信息
static gchar **
_gst_core_errors_init (void)
{
gchar **t = NULL;
t = g_new0 (gchar *, GST_CORE_ERROR_NUM_ERRORS);
TABLE (t, CORE, FAILED,
N_("GStreamer encountered a general core library error."));
TABLE (t, CORE, TOO_LAZY,
N_("GStreamer developers were too lazy to assign an error code "
"to this error." FILE_A_BUG));
TABLE (t, CORE, NOT_IMPLEMENTED,
N_("Internal GStreamer error: code not implemented." FILE_A_BUG));
TABLE (t, CORE, STATE_CHANGE,
N_("Internal GStreamer error: state change failed." FILE_A_BUG));
TABLE (t, CORE, PAD, N_("Internal GStreamer error: pad problem." FILE_A_BUG));
TABLE (t, CORE, THREAD,
N_("Internal GStreamer error: thread problem." FILE_A_BUG));
TABLE (t, CORE, NEGOTIATION,
N_("Internal GStreamer error: negotiation problem." FILE_A_BUG));
TABLE (t, CORE, EVENT,
N_("Internal GStreamer error: event problem." FILE_A_BUG));
TABLE (t, CORE, SEEK,
N_("Internal GStreamer error: seek problem." FILE_A_BUG));
TABLE (t, CORE, CAPS,
N_("Internal GStreamer error: caps problem." FILE_A_BUG));
TABLE (t, CORE, TAG, N_("Internal GStreamer error: tag problem." FILE_A_BUG));
TABLE (t, CORE, MISSING_PLUGIN,
N_("Your GStreamer installation is missing a plug-in."));
TABLE (t, CORE, CLOCK,
N_("Internal GStreamer error: clock problem." FILE_A_BUG));
return t;
}
//上面的函数的执行后的结果
gchar **gst_core_errors = _gst_core_errors_init ();
gst_core_errors [GST_CORE_ERROR_FAILED] = "GStreamer encountered a general core library error."
gst_core_errors [GST_CORE_ERROR_TOO_LAZY] = "GStreamer developers were too lazy to assign an error code to this error."
//…
这样,用户就可以根据error code得出对应的error string了。
static gchar **
_gst_library_errors_init (void)
{
// …. 省略
return t;
}
/* initialize the dynamic table of translated resource errors */
static gchar **
_gst_resource_errors_init (void)
{
// …. 省略
}
static gchar **
_gst_stream_errors_init (void)
{
// …. 省略
}
具体可以参考gsterror.h/.c
这样就有了4个字符串数组。怎么来区分呢,就是用quark和error code:
gchar *
gst_error_get_message (GQuark domain, gint code)
{
static gchar **gst_core_errors = NULL;
static gchar **gst_library_errors = NULL;
static gchar **gst_resource_errors = NULL;
static gchar **gst_stream_errors = NULL;
gchar *message = NULL;
/* initialize error message tables if necessary */
if (gst_core_errors == NULL)
gst_core_errors = _gst_core_errors_init ();
if (gst_library_errors == NULL)
gst_library_errors = _gst_library_errors_init ();
if (gst_resource_errors == NULL)
gst_resource_errors = _gst_resource_errors_init ();
if (gst_stream_errors == NULL)
gst_stream_errors = _gst_stream_errors_init ();
// 根据身份证和error code , 提取error string.
if (domain == GST_CORE_ERROR)
message = gst_core_errors[code];
else if (domain == GST_LIBRARY_ERROR)
message = gst_library_errors[code];
else if (domain == GST_RESOURCE_ERROR)
message = gst_resource_errors[code];
else if (domain == GST_STREAM_ERROR)
message = gst_stream_errors[code];
else {
g_warning ("No error messages for domain %s", g_quark_to_string (domain));
return g_strdup_printf (_("No error message for domain %s."),
g_quark_to_string (domain));
}
if (message)
return g_strdup (_(message));
else
return
g_strdup_printf (_
("No standard error message for domain %s and code %d."),
g_quark_to_string (domain), code);
}
如果开发middleware的话,GStreamer的error code的做法就非常值得参考;如果做application可以直接return error code, 或者也这么做,好像application很少有人这么干过。
D) dbus的做法
==================dbus 的error code 做法=============
Dbus的error code的做法有另外一种风格,可以接收可变参数。
1 定义一个数据结构DBusError
typedef struct DBusError DBusError;
/**
* Object representing an exception.
*/
struct DBusError
{
const char *name; /**< error name */ // 主要的
const char *message; /**< error message */ // 主要的
unsigned int dummy1 : 1; /**< placeholder */
unsigned int dummy2 : 1; /**< placeholder */
unsigned int dummy3 : 1; /**< placeholder */
unsigned int dummy4 : 1; /**< placeholder */
unsigned int dummy5 : 1; /**< placeholder */
void *padding1; /**< placeholder */
};
2 然后dbus提供几个函数对这个数据结构进行分配内存、赋值、释放等的操作;
// 分配内存
void dbus_error_init (DBusError *error);
// 释放
void dbus_error_free (DBusError *error);
// 这是dbus的特 {MOD}菜肴,能灵活的对DBusError填充想填充的内容,不必事先定死
void dbus_set_error (DBusError *error,
const char *name,
const char *message,
...);
// 这个是上面那个函数的简化版本
void dbus_set_error_const (DBusError *error,
const char *name,
const char *message);
void dbus_move_error (DBusError *src,
DBusError *dest);
// 查询之用
dbus_bool_t dbus_error_has_name (const DBusError *error,
const char *name);
dbus_bool_t dbus_error_is_set (const DBusError *error);
具体实现,大家可以参考dbus-error.c
如何使用上面的Error呢?分为外部和内部使用
1先看dbus内部如何使用的:
1-1 定义一些常见的error string, 这是准备填充到DBusError name字段上去的;
#define DBUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed"
#define DBUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory"
//……., 参考dbus-protocol.h
#define DBUS_ERROR_SELINUX_SECURITY_CONTEXT_UNKNOWN "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown"
1-2 初始化一个DBusError,并且填充错误信息给它
DBusError error;
dbus_error_init (&error);
比如说
dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
或者://这个就是往DBusError的message字段填充可变参数,这样信息可以灵活点
dbus_set_error (error, DBUS_ERROR_SPAWN_CHILD_SIGNALED,
"Process %s received signal %d",
sitter->executable, WTERMSIG (sitter->status));
2 外部如果调用dbus的API,并且该API有个参数DBusError *error的话,可以这样调用
//同样也是初始化一个error
DBusError error;
dbus_error_init (&error);
//然后调用dbus API
If (!dbus_message_get_args (msg, &error, …)
{
printf (“Can’t get arguments: %s”, error.message);
dbus_error_free (&error); //别忘记释放
}
大家可以看到,外部调用非常简单,其实我在这里聊dbus的error code,主要是想借鉴dbus内部调用的方式,以及DBusError的实现方式;
我们知道,dbus一般是作为middleware一层的代表,这种灵活多变的error code的做法也是继承自Glib的。在middleware一层,可以多考虑这种做法,比较专业,能传递更多的信息。
总结:
比较而言:
1 GconfError就是GError的升级版本;
2 GStreamer的Error分类思想很好,特别对于提供复杂功能的middleware,可以借鉴;
3 DBusError的可变填充比较好;
4 Sqlite 完全就是个一对一的关系,比较有特 {MOD}的就是可以提取“最新”的错误,很有用;