本文主要是介绍SylixOS里的打印【7】--- 内核日志信息打印接口printk,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
概述
内核日志信息打印接口printk, 用于内核信息输出,可以在中断函数或者信号句柄中运行,不能在应用层中调用。
内核日志信息打印接口printk本质是API_LogPrintk函数的宏别名。
#ifndef printk
#define printk API_LogPrintk
#endif
更多printk的详细信息可以查看SylixOS日志系统。
信息等级
printk输出具备信息等级,信息等级用来控制printk打印的这条信息是否在终端上显示的,当日志级别的数值小于控制台级别时,printk要打印的信息才会在控制台打印出来,否则不会显示在控制台!
信息等级通过在字符串开头添加“<n>”
(n为0~7)这三个字符来设置,如果不设置则使用默认等级。也可以用宏来设置,可读性更强。
系统定义了8个内核信息等级,和POSIX 标准兼容。
#define KERN_EMERG "<0>" /* system is unusable */
#define KERN_ALERT "<1>" /* action must be taken immediately */
#define KERN_CRIT "<2>" /* critical conditions */
#define KERN_ERR "<3>" /* error conditions */
#define KERN_WARNING "<4>" /* warning conditions */
#define KERN_NOTICE "<5>" /* normal but significant condition */
#define KERN_INFO "<6>" /* informational */
#define KERN_DEBUG "<7>" /* debug-level messages */
- KERN_EMERG:会导致主机系统不可用的情况;
- KERN_ALERT:必须马上采取措施解决的问题;
- KERN_CRIT:比较严重的情况;
- KERN_ERR:运行出现错误;
- KERN_WARNING:可能会影响系统功能的事件;
- KERN_NOTICE:不会影响系统但值得注意;
- KERN_INFO:一般信息;
- KERN_DEBUG:程序或系统调试信息等。
日志等级从上到下依次变低,通常对于系统来说,如果发现等级KERN_EMERG的日志,则代表发生了严重的问题导致系统不可以再运行。等级KERN_DEBUG通常被用于一些调试信息的打印,在SylixOS驱动的开发中,经常使用等级KERN_ERR来打印一些错误信息,使用等级KERN_INFO来打印一些普通信息。
系统还定义了一些等级变量,如终端级别,printk默认级别, 让用户使用的最小级别,默认终端级别。
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
#define MINIMUM_CONSOLE_LOGLEVEL 0 /* 让用户使用的最小级别 */
#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than *//* KERN_DEBUG */
int console_printk[4] = {DEFAULT_CONSOLE_LOGLEVEL, /* 终端级别 */DEFAULT_MESSAGE_LOGLEVEL, /* 默认级别 */MINIMUM_CONSOLE_LOGLEVEL, /* 让用户使用的最小级别 */DEFAULT_CONSOLE_LOGLEVEL, /* 默认终端级别 */
};#define console_loglevel (console_printk[0])
#define default_message_loglevel (console_printk[1])
#define minimum_console_loglevel (console_printk[2])
#define default_console_loglevel (console_printk[3])
系统初始化时(bspInit.c中)会将终端级别初始化为默认终端级别,代码如下:
console_loglevel = default_message_loglevel;
系统还提供一个loglevel命令,用于查看和设置终端级别。用法如下:
输出通道
在日志系统初始化完成前,printk也是通过bspDebugMsg函数输出的。使用 bspDebugMsg() 输出时, 需要将 \n 变为 \r\n 序列。
在日志系统初始化完成后,printk输出通道改为一个文件描述符的集合,会向文件集中的所有文件输出。这些文件可以是设备文件(如口/dev/ttyS0),普通文件,socket通道等各种有效文件类型。
系统初始化时向日志文件集添加的第一个文件就是内核标准输出文件(STD_OUT ),所以一般内核打印只能在内核终端看到,在进程终端(如Telnet)中是看不到的。
/*********************************************************************************************************
** 函数名称: halLogInit
** 功能描述: 初始化目标系统日志系统
** 输 入 : NONE
** 输 出 : NONE
*********************************************************************************************************/
#if LW_CFG_LOG_LIB_EN > 0static VOID halLogInit (VOID)
{fd_set fdLog;FD_ZERO(&fdLog);FD_SET(STD_OUT, &fdLog);API_LogFdSet(STD_OUT + 1, &fdLog); /* 初始化日志 */
}#endif
系统提供logfiles命令用于查看日志文件集中有哪些文件。
实现原理
API_LogPrintk 函数先借助vsnprintf函数将可变参数格式化为固定字符串,将该字符串拷贝到一个定长的缓存中,然后通过输出接口将字符串输出。
在日志系统初始化完成前API_LogPrintk 使用的是静态缓存,并通过bspDebugMsg函数输出。
在日志系统初始化完成后API_LogPrintk 使用缓存池中动态分配出的缓存,并先发往消息队列,后由日志服务的线程再转发至日志文件集。
API_LogPrintk 的详细实现原理请查看SylixOS日志系统,这里只列了API_LogPrintk 函数的源码,里面对主要过程有详细注释。
/*********************************************************************************************************
** 函数名称: API_LogPrintk
** 功能描述: 记录格式化日志信息
** 输 入 : pcFormat 格式化字串
** ... 变长字串
** 输 出 : 打印长度
*********************************************************************************************************/
INT API_LogPrintk (CPCHAR pcFormat, ...)
{static CHAR cBspMsgBuf[__MAX_MSG_LEN]; /* 没有初始化之前暂时使用 *//* 线程不安全! */va_list varlist;LW_LOG_MSG logmsg;REGISTER INT iRet;REGISTER PCHAR pcBuffer;REGISTER ULONG ulError;BOOL bHaveLevel = LW_FALSE;BOOL bBspMsg = LW_FALSE;/** 获取打印缓存* 根据日志消息队列是否已初始化,选择使用静态数组或内存池分配空间* 总之使用一个固定大小的内存,LW_CFG_LOG_MSG_LEN_MAX一般为1024*/if (_G_hLogMsgHandle == LW_OBJECT_HANDLE_INVALID) { /* log 还没有初始化 */pcBuffer = cBspMsgBuf;bBspMsg = LW_TRUE;} else {pcBuffer = (PCHAR)__LOG_PRINTK_GET_BUFFER();if (pcBuffer == LW_NULL) {_ErrorHandle(ERROR_SYSTEM_LOW_MEMORY);return (PX_ERROR);}}/** 获取打印等级* 先读取默认打印等级* 如果参数中设置了打印等级则解析并覆盖*/logmsg.LOGMSG_iLevel = default_message_loglevel;if (lib_strnlen(pcFormat, 3) >= 3) {if ((pcFormat[0] == '<') && (pcFormat[2] == '>')) {if ((pcFormat[1] <= '9') && (pcFormat[1] >= '0')) {logmsg.LOGMSG_iLevel = pcFormat[1] - '0';bHaveLevel = LW_TRUE;}}}/** 检查本次打印等级是否大于控制台级别* 当日志级别的数值小于控制台级别时,printk要打印的信息才会在控* 制台打印出来,否则不会显示在控制台!*/if (logmsg.LOGMSG_iLevel > console_loglevel) { /* 至少应该为 7 */if (bBspMsg == LW_FALSE) {__LOG_PRINTK_FREE_BUFFER(pcBuffer);}return (ERROR_NONE); /* 等级太低, 无法打印 */}/** 格式化输出到缓存中* 如果显示设置了打印等级,则跳过设置,即不输出打印等级字串* 通过vsnprintf函数解析打印格式* 超出__MAX_MSG_LEN长度的内容会被丢弃*/if (bHaveLevel) {va_start(varlist, pcFormat);iRet = vsnprintf(pcBuffer, __MAX_MSG_LEN, &pcFormat[3], varlist);va_end(varlist);} else {va_start(varlist, pcFormat);iRet = vsnprintf(pcBuffer, __MAX_MSG_LEN, pcFormat, varlist);va_end(varlist);}logmsg.LOGMSG_pcPrintk = pcBuffer;logmsg.LOGMSG_pcFormat = pcFormat;logmsg.LOGMSG_bIsNeedHeader = LW_FALSE; /* 不需要打印头部 */logmsg.LOGMSG_ulThreadId = LW_OBJECT_HANDLE_INVALID;/** 输出日志信息* 根据日志消息队列是否已初始化,选择通过内核调试接口输出还是* 先发送至消息队列,后由日志服务的线程再转发至日志文件集* 消息队列发送操作可能存在失败,失败时打印输出丢弃*/if (bBspMsg) { /* log 还没有初始化 *///通过内核调试接口输出__logBspMsg(pcBuffer);} else {//先发送至消息队列,后由日志服务的线程再转发至日志文件集ulError = API_MsgQueueSend(_G_hLogMsgHandle, &logmsg, sizeof(LW_LOG_MSG));if (ulError) {__LOG_PRINTK_FREE_BUFFER(pcBuffer);_G_iLogMsgsLost++;_DebugHandle(__ERRORMESSAGE_LEVEL, "log message lost.\r\n");_ErrorHandle(ERROR_LOG_LOST);return (PX_ERROR);}}return (iRet);
}
这篇关于SylixOS里的打印【7】--- 内核日志信息打印接口printk的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!