AndroidT(13) Log 系统 -- natvie LOG 输出的实现(三)

本文深入分析了Android系统中LOG接口的实现原理,包括C风格和C++风格的接口,并详细解释了不同接口间的调用关系及其底层实现。

1. 概览

  前两章涉及的是LOG系统的使用方法,本章则是深入的分析这些LOG接口的实现原理,如此在使用时能够更加的得心应手的。下面给出粗略的关系图
在这里插入图片描述  上面的概览图中,黄色的圈代表logd的一个socket客户端,他们属于不同的进程,因为在Android的Log子系统中一个进程只允许创建一个logd的客户端。
  至于logd,这里先将其理解为一个带网络功能的log储存器即可,它和它的客户端是通过socket进行通讯的。

2.C 风格的LOG接口实现

  此处就不对每一个接口进行说明了,以 RLOGD 接口为例子进行讲解,希望能起到抛砖引玉的作用。下面看看它的实现

//file:system\logging\liblog\include\log\log_radio.h
#define RLOGD(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, \
                                 __VA_ARGS__))

  对于C风格的log输出,它的级别以及所属的log buffer都由不同的宏决定。
    a) LOG_ID_RADIO 意味着该log最终被打入logd的radio buffer。
    b) ANDROID_LOG_DEBUG 代表该log的级别为调试级别,优先级是最低的。
    c) LOG_TAG为什么需要在头文件 log/log.h 之前定义,在此可见一斑,因为log_radio.h被 log/log.h所包含。
    d)至于 VA_ARGS 宏,则是代表可变参数,和printf中的一致。
  下面看看 __android_log_buf_print 的实现

2.1 __android_log_buf_print 的实现

//system\logging\liblog\logger_write.cpp
int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...) {
    ...
    va_start(ap, fmt);
    vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
    va_end(ap);
    __android_log_message log_message = {
        sizeof(__android_log_message), 
        bufID, 
        prio, 
        tag, 
        nullptr, 
        0, 
        buf
    };
    __android_log_write_log_message(&log_message);
    return 1;
}

  该函数则是来自于 logger_write.cpp 文件了,所有写log的操作都是经过它的,包括后面c++风格的log输出也不例外。它的入参类型为 __android_log_message ,这也是 __android_log_buf_print 存在的意义及根据RLOGD的入参组合出对应的 __android_log_message 类型的数据,下面来看看它的定义

2.2 __android_log_buf_print 定义

//system/logging/liblog/include/android/log.h
struct __android_log_message {
  /** Must be set to sizeof(__android_log_message) and is used for versioning. */
  size_t struct_size;

  /** {@link log_id_t} values. */
  int32_t buffer_id;

  /** {@link android_LogPriority} values. */
  int32_t priority;

  /** The tag for the log message. */
  const char* tag;

  /** Optional file name, may be set to nullptr. */
  const char* file;

  /** Optional line number, ignore if file is nullptr. */
  uint32_t line;

  /** The log message itself. */
  const char* message;
};

  注释的非常清楚,此处则不再赘述。
  在组装好了__android_log_message 数据后,就调用 __android_log_write_log_message 处理,

2.3 __android_log_write_log_message 实现

//system\logging\liblog\logger_write.cpp
void __android_log_write_log_message(__android_log_message* log_message) {
    ...
    logger_function(log_message);
}

2.4 logger_function 的选择

  此处的 logger_function 可能会有些疑惑它来自于哪,实际上它在C++风格的LOG接口中是支持配置的,但在C风格中则是使用默认值的,最终输出到logd中去

static __android_logger_function logger_function = __android_log_logd_logger;
void __android_log_logd_logger(const struct __android_log_message* log_message) {
    ...
    write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
}

2.5 SetLogger 的实现

  实际上这个函数决定着来自客户端的log信息的去除,在c++分格的log输出中提到过接口 SetLogger,它最终设置的就是这个值

//system\libbase\logging.cpp
LogFunction SetLogger(LogFunction&& logger)
    //system\logging\liblog\logger_write.cpp
    __android_log_set_logger(...)

  当然c风格的是不支持这种使用的,所以它的实现此处暂略。

2.6 接口实现的文件分布及调用关系

  下面给出接口实现的文件分布及调用关系,用于理解接口实现的软件分层
在这里插入图片描述

3.C++ 风格的LOG接口实现

  同样的,对于C++风格的LOG接口也不一一个说明,以 LOG(INFO) 接口为例子进行讲解,希望能起到抛砖引玉的作用。下面看看它的实现

//system\libbase\include\android-base\logging.h
#define LOG(severity) LOGGING_PREAMBLE(severity) && LOG_STREAM(severity)

  LOGGING_PREAMBLE(severity)则是用于展示log前的条件检查,例如其log级别是否规范,下面则是它的展开

3.1 LOGGING_PREAMBLE(severity)

//system\libbase\include\android-base\logging.h
LOGGING_PREAMBLE(severity)
(
    WOULD_LOG(severity)//code 1
    (
        UNLIKELY(
            ::android::base::ShouldLog(
                SEVERITY_LAMBDA(severity), 
                (//code 1-1
                    [&]() {    \
                        using ::android::base::VERBOSE;             \
                        using ::android::base::DEBUG;               \
                        using ::android::base::INFO;                \
                        using ::android::base::WARNING;             \
                        using ::android::base::ERROR;               \
                        using ::android::base::FATAL_WITHOUT_ABORT; \
                        using ::android::base::FATAL;               \
                        return (severity); 
                    }()
                )
                _LOG_TAG_INTERNAL
            )
        )
        ||
        MUST_LOG_MESSAGE(severity)
            (SEVERITY_LAMBDA(severity) == ::android::base::FATAL)
    )
    &&
    ABORT_AFTER_LOG_EXPR_IF((SEVERITY_LAMBDA(severity)) == ::android::base::FATAL, true)
    (//code 2
        (
            (c)//code 2-1
            &&
            ::android::base::LogAbortAfterFullExpr()
        ) 
        ||
        (x)
    )
    &&
    ::android::base::ErrnoRestorer()//code 3
)

  其实LOGGING_PREAMBLE 就做一件事,只不过把一件事分成了2个时间做,一个是在编译阶段,另一个则是在代码运行阶段
  前者对应code1-1代码,如果传入非using的宏,那么在编译阶段就直接报错了。
  后者对应code2-1代码,如果c条件满足那么就直接运行后面的LogAbortAfterFullExpr使程序异常退出。所以也算巧妙吧,如果是fatal级别的就直接走abort分支。
  ABORT_AFTER_LOG_EXPR_IF的描述如下,意思就是说前后都为true的话就直接使程序异常退出

    Provides an expression that evaluates to the truthiness of `x`, automatically aborting if `c` is true.

3.2 LOG_STREAM(severity)

LOG_STREAM(severity)
    //system\libbase\logging.cpp
    ::android::base::LogMessage(__FILE__, __LINE__, SEVERITY_LAMBDA(severity), _LOG_TAG_INTERNAL, -1).stream()
        data_(new LogMessageData(file, line, severity, tag, error))
    this->stream()//this ->LogMessage
        return data_->GetBuffer()
            return buffer_;

  LOG_STREAM 则是LOG显示的实现的部分,一个log在使用C++风格的log接口到被现实结果如下几个步骤
    a) 实例化 LogMessage 对象,其内部的 LogMessageData 用于描述log数据本身。
    b) LoMessage::stream会返回 stringiostream供用户往里添加string 日志。
    c) 待 LogMessage 析构时调用对应的接口来显示log。

//file:system\libbase\logging.cpp
LogMessage::~LogMessage() {
    std::string msg(data_->ToString());
    ...
    LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(),
            msg.c_str());

    // Abort if necessary.
    if (data_->GetSeverity() == FATAL) {
        if (__builtin_available(android 30, *)) {
        __android_log_call_aborter(msg.c_str());
        } else {
        Aborter()(msg.c_str());
        }
    }
}

  从代码上能看出 LogMessage 析构方法中只做两件事
    1) Log内容的处理。
    2) 如果时FATAL级别的LOG,那么在打印完LOG内容出后调用abort让程序异常退出。很简单在Andorid30及以上的系统使用 __android_log_call_aborter , 而小于android30的就走Aborter接口,反正最终都会调用我们熟知的 abort 接口的。

NAME
       abort - cause abnormal process termination

SYNOPSIS
       #include <stdlib.h>

       void abort(void);

DESCRIPTION
       The  abort()  function  first unblocks the SIGABRT signal, and then raises that signal for the calling process (as though raise(3) was called).  This
       results in the abnormal termination of the process unless the SIGABRT signal is caught and the signal handler does not return (see longjmp(3)).

       If the SIGABRT signal is ignored, or caught by a handler that returns, the abort() function will still  terminate  the  process.   It  does  this  by
       restoring the default disposition for SIGABRT and then raising the signal for a second time.

RETURN VALUE
       The abort() function never returns.

  下面来看看 LogLine是怎么处理Log内容的

//file:system\libbase\logging.cpp
void LogMessage::LogLine(...)
    __android_log_message log_message = {sizeof(__android_log_message), LOG_ID_DEFAULT, priority, tag, file, line, message};
    __android_log_write_log_message(&log_message);

  很熟悉吧,和上面C格式的LOG输出接口实现是一致的,最终还是调用了 __android_log_write_log_message,它属于 logger_write.cpp。另外还需要注意一个细节

__android_log_message log_message = {...,LOG_ID_DEFAULT,...};

  在使用C++风格的LOG接口是不支持 logd buffer类型选择的,它是代码写死为 LOG_ID_DEFAULT 了

//system\logging\liblog\include\android\log.h
typedef enum log_id {
  LOG_ID_MIN = 0,
  /** The main log buffer. This is the only log buffer available to apps. */
  LOG_ID_MAIN = 0,
  /** The radio log buffer. */
  LOG_ID_RADIO = 1,
  /** The event log buffer. */
  LOG_ID_EVENTS = 2,
  /** The system log buffer. */
  LOG_ID_SYSTEM = 3,
  /** The crash log buffer. */
  LOG_ID_CRASH = 4,
  /** The statistics log buffer. */
  LOG_ID_STATS = 5,
  /** The security log buffer. */
  LOG_ID_SECURITY = 6,
  /** The kernel log buffer. */
  LOG_ID_KERNEL = 7,
  LOG_ID_MAX,
  /** Let the logging function choose the best log target. */
  LOG_ID_DEFAULT = 0x7FFFFFFF
} log_id_t;

  从注释上看,是让log系统自己找到最合适的buffer进行打印,但是理想很美好显示却很骨感,在logd中就是使用 LOG_ID_MAIN 的,即 main buffer。这也就是为什么C++风格的LOG不像C语言风格LOG那样可以自选logd 中的buffer类型。

3.3 接口实现的文件分布及调用关系

  下面给出接口实现的文件分布及调用关系,用于理解接口实现的软件分层
在这里插入图片描述

4.总结

  经过上面的分析可见,不论是那种风格的LOG接口实现,最终调用的都是logger_write.cpp中过提供的一些列接口,对于这部分接口实现请见下一章。

<think>我们正在处理一个复杂的命令序列,涉及多个步骤:切换目录、初始化环境、选择编译配置和编译内核组件。命令如下: cd /home/zhujianhua/work/AndroidU/sprd_vnd/bsp && source build/envsetup.sh && lunch ums9230_6h10_Natv_k515 - userdebug - androidt && make kernelcomponent -j3 我们将分解这个命令,并解释每个部分的作用,然后讨论可能遇到的问题及解决办法。 ## 命令分解 1. **cd /home/zhujianhua/work/AndroidU/sprd_vnd/bsp** 切换到指定目录,这是后续命令执行的工作目录。 2. **source build/envsetup.sh** 执行`build/envsetup.sh`脚本,该脚本用于初始化Android编译环境。它会定义一些有用的函数(如`lunch`、`m`、`mm`等)并设置环境变量。 3. **lunch ums9230_6h10_Natv_k515 - userdebug - androidt** 选择编译目标(target)。`lunch`命令用于从菜单中选择或直接指定一个目标。这里直接指定目标为`ums9230_6h10_Natv_k515 - userdebug - androidt`。 注意:目标字符串中包含了空格和连字符,这可能是笔误?通常,`lunch`的目标格式为`<product_name>-<build_variant>`,例如`aosp_arm-eng`。因此,这里的目标应该是一个整体字符串,可能应为`ums9230_6h10_Natv_k515-userdebug-androidt`(用连字符连接,无空格)。如果目标字符串包含空格,可能会导致`lunch`命令无法识别。 4. **make kernelcomponent -j3** 开始编译名为`kernelcomponent`的目标,使用3个并行任务(`-j3`)。这可能是特定于该平台的编译目标。 ## 可能遇到的问题及解决方法 ### 1. 目录不存在或路径错误 - **问题**:如果`/home/zhujianhua/work/AndroidU/sprd_vnd/bsp`目录不存在,`cd`命令会失败,整个命令链会终止(因为使用了`&&`,前一个命令失败则后续不执行)。 - **解决**:确保该目录存在,并且路径正确。可以使用`ls`命令检查目录是否存在。 ### 2. envsetup.sh 脚本不存在 - **问题**:在切换目录后,如果`build/envsetup.sh`文件不存在,`source`命令会失败。 - **解决**:确认该目录下存在`build`子目录,并且`envsetup.sh`位于其中。通常,在Android源代码树的根目录下会有这个文件。因此,请确认当前工作目录(`/home/zhujianhua/work/AndroidU/sprd_vnd/bsp`)是否是正确的Android源代码根目录(或包含`build`目录的目录)。 ### 3. lunch 目标无效 - **问题**:目标字符串`ums9230_6h10_Natv_k515 - userdebug - androidt`包含空格,这可能导致`lunch`命令无法识别。正确的目标格式通常不包含空格,而是用连字符连接各个部分。 - **解决**:检查目标名称的正确性。可能应为`ums9230_6h10_Natv_k515-userdebug-androidt`(去掉空格)。可以通过运行`lunch`命令然后按空格键列出所有可选目标,查看正确的目标名称。 ### 4. 编译目标 kernelcomponent 不存在 - **问题**:如果`kernelcomponent`不是一个有效的编译目标,`make`命令会失败。 - **解决**:确认该目标在当前的编译环境中存在。可以尝试先运行`make`命令查看所有目标,或者查阅项目文档。另外,有时目标名称可能有拼写错误,注意检查。 ### 5. 编译失败 - **问题**:编译过程中可能因为各种原因失败(如依赖缺失、代码错误、环境配置不正确等)。 - **解决**: - 检查错误日志,确定失败原因。 - 确保所有必要的依赖已经安装(如编译器、库文件等)。 - 尝试使用更少的并行任务(例如`-j1`)来避免并行编译中的问题。 - 清理编译环境(如运行`make clean`)然后重新编译。 ### 6. 环境变量未正确设置 - **问题**:在`source build/envsetup.sh`和`lunch`之后,环境变量(如`ANDROID_PRODUCT_OUT`等)可能没有正确设置,导致编译失败。 - **解决**:在运行`lunch`后,可以打印环境变量(如`echo $TARGET_PRODUCT`)来验证设置是否正确。 ### 7. 并行编译选项-j3的问题 - **问题**:使用`-j3`指定3个并行任务,但有时并行编译可能导致竞争条件或资源不足(内存不足等)。 - **解决**:根据机器配置调整并行任务数。通常建议设置为CPU核心数的1.5到2倍。如果编译失败,尝试减少并行任务数(如`-j1`)以排除并行引起的问题。 ## 建议的执行步骤 1. 首先,单独执行每个命令,检查每一步是否成功。 ```bash cd /home/zhujianhua/work/AndroidU/sprd_vnd/bsp source build/envsetup.sh lunch ums9230_6h10_Natv_k515-userdebug-androidt # 注意去掉空格 make kernelcomponent -j3 ``` 2. 如果`lunch`的目标格式有问题,请使用正确的目标字符串。 3. 如果编译失败,查看错误输出,并逐步排查。 ## 相关帮助命令 - 查看`lunch`的帮助:运行`lunch`后直接回车,会列出所有可选目标。 - 查看`make`的帮助:`make -h` 或 `make --help`,但注意在Android编译环境中,也可以使用`m -h`(在`envsetup.sh`之后可用)。 - 编译单个模块:`mmm <模块路径>` 或 `mm`(在模块目录下)可以在`envsetup.sh`之后使用。 希望这些信息能帮助你顺利执行命令! §§相关问题§§ 1. 如何验证Android编译环境的正确设置? 2. 在编译过程中遇到依赖错误应该如何解决? 3. 如何增加或减少并行编译任务数,以及如何确定最佳任务数? </think>我将针对您提供的命令序列,分析各个步骤的作用、可能遇到的问题及解决方案,并提供相关帮助信息。 ### 命令分解与作用 ```bash cd /home/zhujianhua/work/AndroidU/sprd_vnd/bsp && source build/envsetup.sh && lunch ums9230_6h10_Natv_k515-userdebug-androidt && make kernelcomponent -j3 ``` 1. **`cd .../bsp`** 切换到Android源码目录(展锐平台特定路径) 2. **`source build/envsetup.sh`** 初始化Android编译环境: - 添加`lunch`、`m`、`mm`等编译命令 - 设置环境变量(如`ANDROID_BUILD_PATHS`) - 注册产品配置信息[^1] 3. **`lunch ...`** 选择目标设备编译配置: - `ums9230_6h10_Natv_k515`:芯片平台型号 - `userdebug`:编译类型(可调试版本) - `androidt`:Android版本分支 4. **`make kernelcomponent -j3`** 编译内核组件: - `-j3`:使用3线程并行编译 ### 常见问题及解决方案 #### 1. 环境初始化失败(source命令) - **现象**:`envsetup.sh not found` - **原因**:路径错误或源码不完整 - **解决**: ```bash # 确认目录结构 ls build/envsetup.sh # 重新同步源码 repo sync -c -j4 ``` #### 2. lunch选择失败 - **现象**:`Invalid lunch combo` - **原因**:目标配置名称错误或未定义 - **解决**: ```bash # 查看可用配置列表 printconfig # 检查vendorsetup.sh是否注册 grep ums9230_6h10 vendor/*/vendorsetup.sh ``` #### 3. 内核编译错误 - **典型错误**: ```log Kernel configuration invalid: .config not found ``` - **解决步骤**: 1. 检查内核配置 ```bash cd kernel/sprd/ums9230_6h10 make ARCH=arm64 O=out ums9230_6h10_Natv_k515_defconfig ``` 2. 修复依赖缺失 ```bash sudo apt install flex bison libssl-dev ``` 3. 减少并行线程(内存不足时) ```bash make kernelcomponent -j1 # 单线程编译 ``` #### 4. 权限问题 - **现象**:`Permission denied` - **解决**: ```bash # 检查文件权限 ls -ld /home/zhujianhua/work # 递归修复所有权 sudo chown -R zhujianhua:zhujianhua AndroidU/ ``` #### 5. 编译终止(-j3问题) - **现象**:随机编译中止 - **原因**:并行任务过多导致资源竞争 - **优化方案**: ```bash # 根据CPU核心数调整-j参数 JOBS=$(($(nproc)/2)) make kernelcomponent -j$JOBS ``` ### 调试建议 1. **分步执行**: ```bash cd /home/zhujianhua/work/AndroidU/sprd_vnd/bsp source build/envsetup.sh lunch ums9230_6h10_Natv_k515-userdebug-androidt make kernelcomponent -j3 ``` 2. **日志记录**: ```bash make kernelcomponent -j3 2>&1 | tee build.log ``` 3. **环境验证**: ```bash # 检查关键变量 echo $TARGET_PRODUCT $TARGET_BUILD_VARIANT # 验证内核配置 make -C kernel/sprd/ums9230_6h10/ listconfigs ``` ### 关键文档参考 - 展锐编译手册:`bsp/vendor/sprd/docs/Kernel_Build_Guide.pdf` - Android官方编译指南:[Android Build System](https://source.android.com/docs/setup/build)[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值