1. 程式人生 > 其它 >AUPE 輸出致標準錯誤的出錯函式分析與實現 err_sys, err_quit, err_doit etc.

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. 簡要分析

  1. 詳細錯誤開關
    通過一個標誌errnorflag, 控制錯誤詳細資訊的列印.

  2. 錯誤號
    如果要列印除傳入錯誤資訊外, 更加詳細錯誤資訊:
    除了要開啟errnorflag, 另外,
    ① 對於系統呼叫相關錯誤, 可以不用呼叫者傳遞錯誤號(引數error), 可直接利用全域性變數errno, 檢查物件要求調用出錯時會設定errno;
    ② 對於非系統呼叫相關錯誤, 如果要列印錯誤號對應的詳細資訊, 就需要呼叫者傳遞錯誤號. 常見多執行緒環境, 或者其他不會設定errno, 但會返回錯誤號的呼叫, i.g. pthread_create;

  3. 是否終止程式
    只有致命錯誤, 不論系統呼叫, 還是非系統呼叫, 才會終止程式. 非必要情況, 不用終止程式. 終止程式用exit().

  4. 核心轉儲(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
  1. 可變引數及如何實現列印
    對於可變引數"...", 可以將其轉換為va_list, 然後利用vsnprintf, 將要列印的內容寫至緩衝區中. 最後將列印內容輸出到stderr.

3. 實現原始碼

先來看公共的, 可變引數、錯誤號及提示資訊列印函式err_doit
errnoflag : 列印錯誤詳細資訊開關;
error: 錯誤號, 通過strerror將其轉化為字串;
fmt: 格式化字串, 可由使用者傳入, 包含了使用者錯誤提示資訊;
ap: 可變引數列表, 由使用者跟fmt一起傳入;

err_doit的主要任務是:

  1. 根據引數設定, 先將使用者設定錯誤提示資訊fmt & 引數列表ap轉存到緩衝區buf中;
  2. 判斷是否要列印錯誤號error對應的錯誤訊息, 如果要列印, 就用strerror將error轉化成字串資訊, 並追加填入緩衝區;
  3. 緩衝區字串末尾追加\n, 因為呼叫庫IO函式時, 會自動沖刷庫快取;
  4. 沖刷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);
}