AUPE 输出致标准错误的出错函数分析与实现 err_sys, err_quit, err_doit etc.
1. 出错函数汇总
AUPE 输出至标准错误的出错函数, 分为以下几个:
函数名 | 何时调用 | 功能 |
---|---|---|
err_ret | 系统调用相关的非致命错误 | 打印消息并且返回 |
err_sys | 系统调用相关的非致命错误 | 打印消息并且终止程序 |
err_cont | 不与系统调用相关的非致命错误 | 错误码通过显式参数error传递, 打印消息并返回 |
err_exit | 不与系统调用相关的致命错误 | 错误码通过显式参数error传递, 打印消息并返回 |
err_dump | 系统调用相关的致命错误 | 打印消息, 核心转储, 并且终止程序 |
err_msg | 不与系统调用相关的非致命错误 | 打印消息, 并且返回 |
err_quit | 不与系统调用相关的致命错误 | 打印消息, 并且终止程序 |
2. 简要分析
-
详细错误开关
通过一个标志errnorflag, 控制错误详细信息的打印. -
错误号
如果要打印除传入错误信息外, 更加详细错误信息:
除了要打开errnorflag, 另外,
① 对于系统调用相关错误, 可以不用调用者传递错误号(参数error), 可直接利用全局变量errno, 检查对象要求调用出错时会设置errno;
② 对于非系统调用相关错误, 如果要打印错误号对应的详细信息, 就需要调用者传递错误号. 常见多线程环境, 或者其他不会设置errno, 但会返回错误号的调用, i.g. pthread_create; -
是否终止程序
只有致命错误, 不论系统调用, 还是非系统调用, 才会终止程序. 非必要情况, 不用终止程序. 终止程序用exit(). -
核心转储(core dump)
调用abort异常终止当前进程, 触发核心转储. 输出core文件的前提是, Core file size > 0, 可以用ulimit -c命令设置, ulimit -a命令查看, 单位如下图.
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 30702
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 30702
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
$ ulimit -c
0
$ ulimit -c 1024
$ ulimit -c
1024
- 可变参数及如何实现打印
对于可变参数"...", 可以将其转换为va_list, 然后利用vsnprintf, 将要打印的内容写至缓冲区中. 最后将打印内容输出到stderr.
3. 实现源码
先来看公共的, 可变参数、错误号及提示信息打印函数err_doit
errnoflag : 打印错误详细信息开关;
error: 错误号, 通过strerror将其转化为字符串;
fmt: 格式化字符串, 可由用户传入, 包含了用户错误提示信息;
ap: 可变参数列表, 由用户跟fmt一起传入;
err_doit的主要任务是:
- 根据参数设置, 先将用户设置错误提示信息fmt & 参数列表ap转存到缓冲区buf中;
- 判断是否要打印错误号error对应的错误消息, 如果要打印, 就用strerror将error转化成字符串信息, 并追加填入缓冲区;
- 缓冲区字符串末尾追加\n, 因为调用库IO函数时, 会自动冲刷库缓存;
- 冲刷IO库缓存, 然后将缓冲区字符串, 通过fputs输出至IO库缓存, 这样就打印了错误信息;
/**
* 打印消息, 并且返回调用者
* 调用者指定参数errnoflag
* @param errnoflag 错误标识, 值非0时, 才打印错误号转化成的错误详细信息. 值为0时, 不打印错误号对应错误详细信息.
* @param error 错误号. 系统调用相关错误, 不用传错误号, 直接用errno作为错误号; 非系统调用错误(不设置errno的), 手动传入错误号
* @param fmt 格式化字符串
* @param ap 变参列表, fmt中有多少个转义字符, ap就需要提供同样个数参数值
*/
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
{
int errno_save;
char buf[MAXLINE];
errno_save = error; /* value caller might want printed */
vsprintf(buf, fmt, ap); /* format string fmt write out to buf */
if (errnoflag)
sprintf(buf+strlen(buf), ": %s", strerror(errno_save)); /* append strerror(error) to buf */
strcat(buf, "\n"); /* append "\n" to buf */
fflush(stdout); /* in case stdout and stderr are the same */
fputs(buf, stderr); /* write out buf to stderr */
fflush(stderr); /* SunOS 4.1.* doesn't grok NULL argument */
}
出错函数的用户接口实现:
主要需要注意区分 是否为系统调用相关, 是否需要用户输入错误号, 是否打印详细错误信息, 是否终止进程, 是否需要core file
#include <errno.h> /* for definition of errno */
#include <stdarg.h> /* ANSI C header file */
#include <stdlib.h>
#include "ourhdr.h"
static void err_doit(int, int, const char *, va_list);
/*
* 系统调用相关的非致命错误
* 打印消息并且返回
*/
void err_ret(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
return;
}
/*
* 系统调用相关的非致命错误
* 打印消息并且终止程序
*/
void err_sys(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
exit(1);
}
/**
* 不与系统调用相关的非致命错误
* 错误码通过显式参数error传递
* 打印消息并返回
*/
void err_cont(int error, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, error, fmt, ap);
va_end(ap);
}
/**
* 不与系统调用相关的致命错误
* 错误码通过显式参数error传递
* 打印消息并终止程序
*/
void err_exit(int error, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, error, fmt, ap);
va_end(ap);
exit(1);
}
/*
* 系统调用相关的致命错误
* 打印消息, 核心转储, 并且终止程序
*/
void err_dump(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(1, errno, fmt, ap);
va_end(ap);
abort(); /* dump core and terminate */
exit(1); /* shouldn't get here */
}
/*
* 不与系统调用相关的非致命错误
* 打印消息, 并且返回
*/
void err_msg(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
}
/*
* 不与系统调用相关的致命错误
* 打印消息, 并且终止程序
*/
void err_quit(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
err_doit(0, 0, fmt, ap);
va_end(ap);
exit(1);
}