AndroidT(13) Log 系统 -- logd 服务的初始化(七)

1. 概览

  经过上一章的分析,现在也是时候讨论下logd的初始化了,虽然 logd 在代码量上来说并不大,但是还是分模块进行分析比较合适。所以这里就不贴整体代码了,这部分代码也被包含在AOSP t 的代码中,有兴趣的读者可以自己下载查看。

2. 屏蔽 SIGPIPE 信号

  下面先看看这部分代码长什么样

main
    signal(SIGPIPE, SIG_IGN);

  对于 signal 可以查看 man 手册,

NAME
       signal - ANSI C signal handling

SYNOPSIS
       #include <signal.h>

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);

DESCRIPTION
       The  behavior  of signal() varies across UNIX versions, and has also varied historically across different versions of Linux.  Avoid its use: use sigaction(2) instead.  See Portability below.

       signal() sets the disposition of the signal signum to handler, which is either SIG_IGN, SIG_DFL, or the address of  a  programmer-defined  function  (a
       "signal handler").

       If the signal signum is delivered to the process, then one of the following happens:

       *  If the disposition is set to SIG_IGN, then the signal is ignored.

       *  If the disposition is set to SIG_DFL, then the default action associated with the signal (see signal(7)) occurs.

       *  If  the  disposition  is set to a function, then first either the disposition is reset to SIG_DFL, or the signal is blocked (see Portability below),
          and then handler is called with argument signum.  If invocation of the handler caused the signal to be blocked, then the signal  is  unblocked  upon
          return from the handler.

       The signals SIGKILL and SIGSTOP cannot be caught or ignored.

RETURN VALUE
       signal() returns the previous value of the signal handler, or SIG_ERR on error.  In the event of an error, errno is set to indicate the cause.

   signal 的作用就是为调用进程注册一个对于信号量的处理函数,例如在logd中这句代码的含义则是,logd 在接收到信号 SIGPIPE 时调用函数 SIG_IGN 进行处理。而 SIG_IGN 则是什么也不做,即忽略了该信号。对于 SIGPIPE 信号,系统默认的操作则是让该进程异常退出。
  那么系统是在什么场景下会发送 SIGPIPE 到进程中?描述如下

当往一个写端关闭的管道或socket连接中连续写入数据时会引发SIGPIPE信号,引发SIGPIPE信号的写操作将设置errno为EPIPE。在TCP通信中,当通信的双方中的一方close一个连接时,若另一方接着发数据,根据TCP协议的规定,会收到一个RST响应报文,若再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不能再写入数据。 

  因此 logd 作为一个socket的服务端,以上场景一样会发生在它身上,例如一个 logcat 何时退出是不会告知 logd 的。所以在 logd 中是需要忽略掉信号 SIGPIPE 的。

3. log 时间戳时区设置

  log中的时间戳非常的重要,时间戳代表着何时这部分log被打印,这一般也意味着此时程序想告诉开发人员它想表达的事项。因此 logd 为了统一,直接设置时区为UTC了。

main
    setenv("TZ", "UTC", 1);

  下面是 Google 对其的解释

    logd is written under the assumption that the timezone is UTC. If TZ is not set, persist.sys.timezone is looked up in some time utility
    libc functions, including mktime. It confuses the logd time handling, so here explicitly set TZ to UTC, which overrides the property.

4. logd 内部 log 的输出位置设置

  Andorid中在 logd 之后启动的组件可以将 log 传递给 logd 去处理,但是对于 logd 来说就没这么好运了。但是对于 logd 也是需要开发调试的,因此它的 log 也是很重要的。下面则是用于设置 logd 内部log的输出

android::base::InitLogging(
        argv, [](android::base::LogId log_id, android::base::LogSeverity severity,
                    const char* tag, const char* file, unsigned int line, const char* message) {
            if (tag && strcmp(tag, "logd") != 0) {//code 1
                auto prefixed_message = android::base::StringPrintf("%s: %s", tag, message);
                android::base::KernelLogger(log_id, severity, "logd", file, line,
                                            prefixed_message.c_str());
            } else {//code 2
                android::base::KernelLogger(log_id, severity, "logd", file, line, message);
            }
        });

  logd 的输出又分两种,如果 log 的 tag 为 logd 那么就使用 code 2 进行打印,否则使用 code 1 的格式。 code 1 的方式多了 log 来源的 tag,但还是由 logd 打印所以 log 过滤时还是用 logd 标签。最终这部分 log 会被打印到 kernel log 中去,即可以使用 dmesg 来获取。

5. logd buffer

   buffer 顾名思义,用来存放需要 logd 处理的 log 信息。在 logd 中,我们是可以通过设置属性值 logd.buffer_type 来决定使用不同的buffer的。

//system/logging/logd/README.property
logd.buffer_type           string (empty) Set the log buffer type.  Current choices are 'simple',
                                          'chatty', or 'serialized'.  Defaults to 'chatty' if empty.

  从上面可知,目前支持 3 中buffer类型simple、chatty 和 serialized。但是属性 logd.buffer_type 为空的情况在 AndroidT 中可不是 chatty 而是 serialized

//file:system\logging\logd\main.cpp
main
    std::string buffer_type = GetProperty("logd.buffer_type", "serialized");

  因此我们也就以 serialized 类型的 log buffer 作为例子进行讲解。

main
    LogBuffer* log_buffer = nullptr;
    if (buffer_type == "chatty") {
        ...
    } else if (buffer_type == "serialized") {
        log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);
    } else if (buffer_type == "simple") {
        ...
    } else {
        LOG(FATAL) << "buffer_type must be one of 'chatty', 'serialized', or 'simple'";
    }

5.1 SerializedLogBuffer 类图

  类的结构对了解类的作用及工作原理还是至关重要的,下面给出 SerializedLogBuffer 对应的类图。
在这里插入图片描述

5.2 SerializedLogBuffer 初始化解析

  LogBuffer 仅仅是个接口类,并没有对应的构造方法。因此只要看看 SerializedLogBuffer 的构造方法就可以剖析初始化了。

log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);
    reader_list_(reader_list), tags_(tags), stats_(stats)
    Init();
        log_id_for_each(i) {//code 1
            if (!SetSize(i, GetBufferSizeFromProperties(i))) {
                SetSize(i, kLogBufferMinSize);
            }
        }
        //code 2
        for (const auto& reader_thread : reader_list_->running_reader_threads()) {
            reader_thread->TriggerReader();
        }

  代码很简单,说下设计意图吧。 reader_list 里面会维护所有读者信息,在buffer来了数据后就会通知 reader_list 里面所有的读者来读取 log 数据,至于各个读者施展什么手段 buffer 并不关心,使用 reader_list_ 隔离掉了。
  在logd初始化阶段,显然是没有读者的,所以 SerializedLogBuffer 的构造方法也就剩两个事
    1)记录传进来的 reader_list、log_tag等变量。
    2)设置各个类型buffer的大小,如system/main/radio等,log_id_for_each 定义如下

//system\logging\logd\LogStatistics.h
#define log_id_for_each(i) \
    for (log_id_t i = LOG_ID_MIN; (i) < LOG_ID_MAX; (i) = (log_id_t)((i) + 1))

    从这里就可以看出 i 就是LOG_ID,现在来看看 SetSize

//system\logging\logd\SerializedLogBuffer.cpp
SetSize(i, GetBufferSizeFromProperties(i))
    //system\logging\logd\LogSize.cpp
    size = GetBufferSizeFromProperties(i)//code 1

    max_size_[id] = size;//code 2
    // If this buffer has been compressed, we only consider its compressed size when accounting for
    // memory consumption for pruning.  This is since the uncompressed log is only by used by
    // readers, and thus not a representation of how much these logs cost to keep in memory.
    MaybePrune(id);//code 3

  buffer 大小的设置大致分成如下几个步骤
    1) 在Android T 中已经不支持灵活的通过属性来配置对应各个buffer的大小了,描述如下

    We've been seeing timeouts from logcat in bugreports for years, but the
rate has gone way up lately. The suspicion is that this is because we
have a lot of dogfooders who still have custom (large) log sizes but
the new compressed logging is cramming way more in. The bugreports I've seen
have had 1,000,000+ lines, so taking 10s to collect that much logging seems
plausible. Of course, it's also possible that logcat is timing out because
the log is being *spammed* as it's being read. But temporarily disabling
custom log sizes like this should help us confirm (or deny) whether the
problem really is this simple.

  只根据设备是不是低内存设备来决定 各个buffer的大小

//Xsystem\logging\logd\LogSize.h
static constexpr size_t kDefaultLogBufferSize = 256 * 1024;
static constexpr size_t kLogBufferMinSize = 64 * 1024;
//system\logging\logd\LogSize.cpp
    if (android::base::GetBoolProperty("ro.config.low_ram", false)) {
        return kLogBufferMinSize;
    }
    return kDefaultLogBufferSize;

  如果是低内存设备那么就返回 kLogBufferMinSize 的值也就是 64KB,否则返回 kDefaultLogBufferSize 为 256KB。
     2) 在 SerializedLogBuffer 内部使用变量 max_size_ 记录下各个log buffer的能力大小。

6. kernel log 处理器 – klogd

6.1 klogd 功能选择

  从 logcat 工具也是能看出,它是支持 kernel log 的显示的。klogd也就是 logd 实现 kernel 显示的关键

erd8535:/ # logcat -h
Usage: logcat [options] [filterspecs]

General options:
  -b, --buffer=<buffer>       Request alternate ring buffer(s):
                                main system radio events crash default all
                              Additionally, 'kernel' for userdebug and eng builds, and
                                                        'security' for Device Owner installations.

  Additionally 这个副词也说明了 kernel log 在 logd 中的地位有些不一样,有点像孤儿、送的、没人要的。实际上 kernel log 的打印确实和 logd 中 main/system 这些 log 不一样。logd 是通过读取节点 /proc/kmsg 来获取 kernel log 的。这里值得注意的是 /proc/kmsg 是不支持多读者的,即一个读者读走内容后,下一个读者就获取不到 log 信息了。所以例如在使用 logcat -b kernel 后,不要再类似手动 cat 这个节点了。下面就来看看实现吧。

main
    //code 1
    static const char dev_kmsg[] = "/dev/kmsg";
    int fdDmesg = android_get_control_file(dev_kmsg);
    //code 2
    bool klogd = GetBoolPropertyEngSvelteDefault("ro.logd.kernel");
    if (klogd) {//code 3
        SetProperty("ro.logd.kernel", "true");
        static const char proc_kmsg[] = "/proc/kmsg";
        fdPmesg = android_get_control_file(proc_kmsg);
    }

  code 1 用于获取 kmsg 节点的 fd,它在init启动lod得时候就被以仅写的权限打开了

//system\logging\logd\logd.rc
service logd /system/bin/logd
    file /dev/kmsg w

  用于确认是否开启 klogd,即 logd 是否支持 kernel log 的处理。下面来看看 code 2 的代码实现

//system\logging\logd\main.cpp
bool klogd = GetBoolPropertyEngSvelteDefault("ro.logd.kernel");
    bool default_value =
        GetBoolProperty("ro.debuggable", false) && !GetBoolProperty("ro.config.low_ram", false);

    return GetBoolProperty(name, default_value);

  ro.debuggable 只有在 eng 和 userdebug 两个版本中为 1,下面是 Android 团队的官方解释,供参考。android build-variants
  同样的 ro.config.low_ram 在低内存设备上为false。
  综上,只要满足任意一个条件,logd 对 kernel log 的功能默认就是关闭的,毕竟 kernel log 一般包含系统关键信息。
    a) 低内存设备
    b) user 版本的Android 系统
  当然用户可以明确通过属性 ro.logd.kernel 来开启 klog 的功能。

6.2 klog – LogKlog 的初始化

  最终 klogd 如果被设置为 true,那么 logd 就支持处理 kernel log 的特性。LogKlog 类就是被用于 logd 处理kernel log的。下面先来看看它的类图
在这里插入图片描述

  可见它的父类就是 SocketListener,对于它的讲解见 《AndroidT(13) Log 系统 – SocketListener 帮助类详解(六)》 章节。下面先来看看它的初始化

//system\logging\logd\main.cpp
main
    ...
    kl = new LogKlog(log_buffer/*buf*/, fdDmesg/*fdWrite*/, fdPmesg/*fdRead*/, al != nullptr/*auditd*/, &log_statisticsstats/**/);
        SocketListener(fdRead, false),//code 1
        logbuf(buf),
        ...
        //code 2
        static const char klogd_message[] = "%s%s%" PRIu64 "\n";
        char buffer[strlen(priority_message) + strlen(klogdStr) +
                    strlen(klogd_message) + 20];
        snprintf(buffer, sizeof(buffer), klogd_message, priority_message, klogdStr,
                signature.nsec());
        write(fdWrite, buffer, strlen(buffer));

  值得注意的是,LogKlog 中使用 SocketListener 来监听文件句柄(fdPmesg 为 /proc/kmsg ),而非 socket的连接i请求,因为/proc/kmsg 并不是 socket 句柄,如果有客户端想获取kernel log的话,那也是通过 logr 的。所以此处 SocketListener 传入的 listen 为 false。
  code 2 用于设置当前进程所读取到的kernel log的格式,下面是对应的格式

//system\logging\logd\LogKlog.cpp
static const char klogdStr[] = "logd.klogd: ";

"priority_message""klogdStr""signature(CLOCK_MONOTONIC)"

  的拆解过程如下,最终就是去掉"<44>"后缀’\0’的内容

//bionic\libc\include\syslog.h
#define LOG_SYSLOG   (5<<3)
#define LOG_INFO 6

#define KMSG_PRIORITY(LOG_INFO) \
    '<', 
    '0' + ((5<<3) | 6) / 10,
        '0' + (10 1000b | 1100b) / 10,
            '0' + 44 / 10,
                '4',
    '0' + ((5<<3) | (6)) % 10,
        '4',
    '>'
    
static const char priority_message[] = { KMSG_PRIORITY(LOG_INFO), '\0' };
    {
        '<',
        '4',
        '4',
        '>'
    }

6.3 LogKlog 的监听启动

  从 LogKlog 的类图可知它的父类为 SocketListener ,所以启动监听则是通过它的 startListener 接口实现,启动成功后新线程会被创建用于监听所给句柄是否有数据可读,并且调用子类也就是 LogKlog 中的 onDataAvailable 方法进行处理。

//system\logging\logd\main.cpp
bool LogKlog::onDataAvailable(SocketClient* cli){
    prctl(PR_SET_NAME, "logd.klogd");
    for (;;) {
        read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
    }
}

  对于 LogKlog 是如何处理kmsg的log即它重写的 onDataAvailable 实现细节放到后面章节讲解,本章只关注 logd 的初始化。

7. logd 监听听注册器 – logdr

   logdr 这个 socket 节点用于客户端获取 logd 中所管理的 log,例如典型的 logcat 工具就是通过和该 socket 节点进行通信从而获取 log的。

7.1 LogReader 的初始化

  logd 中则是使用类 LogReader 来实现上面提到的部分功能的。实际上 LogReader 并不参与数据的传输,它只负责处理客户端连接 logdr 的请求。下面先看看它的类图
在这里插入图片描述

   从类图可知它的结构很简单,就一个父类 SocketListener,下面就看看它的构造

main
    LogReader* reader = new LogReader(log_buffer, &reader_list);
        //code 1
        SocketListener(getLogSocket(), true)
        //code 2
        log_buffer_(logbuf)
        //code 3
        reader_list_(reader_list)

  code 1,又见老朋友 SocketListener 了,不过这里的 mListen LogKlog 中的不一样,前者是支持作为服务端功能的,即可以处理客户端发过来的连接请求。这里的客户端也就是对 logd 所管理的 log 感兴趣的,例如前面提到过的 logcat。对于 SocketListener 的初始化,这里就不再赘述了,请参考相关章节的解析。

7.2 LogReader 的启动

  从 LogReader 的类图可知它的父类为 SocketListener ,所以启动监听则是通过它的 startListener 接口实现,对于客户端的连接请求处理在 SocketListener 章节中也有详细的说明。对于 mListen 为true的情况,SocketListener 的子类只要在 onDataAvailable 中处理已经连接上的客户端的数据即可。

bool LogReader::onDataAvailable(SocketClient* cli) {
    //code 1
    prctl(PR_SET_NAME, "logd.reader");
    //code 2
    int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
    ...
    //code 3
    auto entry = std::make_unique<LogReaderThread>(log_buffer_, reader_list_,
                                                   std::move(socket_log_writer), nonBlock, tail,
                                                   logMask, pid, start, sequence, deadline);
    //code 4
    reader_list_->AddPendingThread(std::move(entry));
}

  code 1,之前提到过 onDataAvailable 是跑在新建立的线程的,所以此处设置下线程名,方便区分。
  code 2,既然是客户端来了数据,那么总得先把数据读出来。
  code 3,在 LogReader 使用类 LogReaderThread 来管理每一个读者,实际上其内部也会创建一个线程进行log数据的传输。下面先给出它的类图供欣赏。
在这里插入图片描述

  code 4,此处将被 LogReaderThread 封装的新的 log reader 加到 reader list中去,以便统一管理。
  至于细节,会放到对应章节中去,此处就不赘述了。

8. log 接收器 – logdw

  logd 进程相当于一个 log 收集器,因此需要被显示的 log 最终也会被发送到 logd 进程来。logdw socket节点就是用来接受来自系统各个进程的log的。在 logd 中使用 LogListener 来实现这块功能。它就简单很多了,因为只要处理 socket 本身的数据即可。
在这里插入图片描述

8.1 logdw 的初始化 – LogListener

  下面来看看它的初始化

LogListener::LogListener(LogBuffer* buf)
    socket_(GetLogSocket())
    logbuf_(buf)

  打开并记录下 logdw socket,然后记录下buf共后面来 log 数据后的存储及处理,这个 buffer 实际上就是 SerializedLogBuffer 的一个实例。

8.2 logdw 的监听启动

  虽然没有使用 startListener ,但是启动的接口名却保持了一致

//system\logging\logd\main.cpp
main
    LogListener* swl = new LogListener(log_buffer);
    swl->StartListener()
        auto thread = std::thread(&LogListener::ThreadFunction, this);
        thread.detach();
        return true;
}

  可见此处又会启动一个新的线程来处理,它的处理方法为 LogListener::ThreadFunction , 入参为 LogListener 的当前实例。最终 ThreadFunction 会被调用

//system\logging\logd\LogListener.cpp
ThreadFunction
    prctl(PR_SET_NAME, "logd.writer");
    while (true) {
        HandleData();
    }

9. logd 的控制器 – logd

   logd 节点用于接收对 logd 本身的控制请求,例如获取/设置 logd 中维护 buffer 的大小。在 logd 中使用类 CommandListener 来实现该功能,下面是对应的类图。

9.1 CommandListener 类图

在这里插入图片描述

  它的顶层基类还是老朋友 SocketListener,邻近父类为 FrameworkListener,所以在看初始化的时候不能拉下这两个基类对应的构造方法了。

9.2 CommandListener 的初始化

//system\logging\logd\main.cpp
main
    CommandListener* cl = new CommandListener(log_buffer/*buf*/, &log_tags/*tags*/, &prune_list/*prune*/, &log_statistics/*stats*/);
        //code 1
        FrameworkListener(getLogSocket()/*int sock*/)//system\logging\logd\CommandListener.cpp
            //code 1-1
            SocketListener(sock, true)
            //code 1-2
            init(nullptr, false);
        //code 2
        registerCmd(new ClearCmd(this));
        ...
        registerCmd(new ExitCmd(this));

  code 1 部分为 CommandListener 父类的构造, code 1-1 用于监听 logd 这个socket 句柄,并且它是支持作为server的,即会处理客户端的连接请求的,这也意味这 FrameworkListener 要处理的内容是客户端连接上后通过新建立的 socket 句柄发送而来的。code 1-2 仅仅是变量初始化,没什么好说的。
  code 2 以及后面的都是一样的含义,用于注册命令,最终 CommandListener 会根据客户端要求执行的命令来调用对应的命令类。下面就看看 code 2 中的例子。

9.2.1 ClearCmd 类的由来

//system\logging\logd\CommandListener.h
#define LogCmd(name, command_string)                                \
    class name##Cmd : public FrameworkCommand {                     \
      public:                                                       \
        explicit name##Cmd(CommandListener* parent)                 \
            : FrameworkCommand(#command_string), parent_(parent) {} \
        virtual ~name##Cmd() {}                                     \
        int runCommand(SocketClient* c, int argc, char** argv);     \
                                                                    \
      private:                                                      \
        LogBuffer* buf() const { return parent_->buf_; }            \
        LogTags* tags() const { return parent_->tags_; }            \
        PruneList* prune() const { return parent_->prune_; }        \
        LogStatistics* stats() const { return parent_->stats_; }    \
        CommandListener* parent_;                                   \
    }

    LogCmd(Clear, clear);

  还是相当的简单的,通过宏 LogCmd 就可以创建出一个类,下面是预编译后加了换行符的类定义

LogCmd(Clear/*name*/, clear/*command_string*/);
#define LogCmd(name, command_string)                              
    class ClearCmd : public FrameworkCommand {                    
      public:                                                     
        explicit ClearCmd(CommandListener* parent)                
            : FrameworkCommand(clear), parent_(parent) {} 
        virtual ~ClearCmd() {}                                    
        int runCommand(SocketClient* c, int argc, char** argv);   
                                                                  
      private:                                                    
        LogBuffer* buf() const { return parent_->buf_; }          
        LogTags* tags() const { return parent_->tags_; }          
        PruneList* prune() const { return parent_->prune_; }      
        LogStatistics* stats() const { return parent_->stats_; }  
        CommandListener* parent_;                                 
    };

  ClearCmd 类的实现,其实只要实现 runCommand 就可以了

//system\logging\logd\CommandListener.cpp
int CommandListener::ClearCmd::runCommand(SocketClient* cli, int argc, char** argv) {
    ...
    return LogIdCommand(cli, argc, argv, [&](log_id_t id) {
        cli->sendMsg(buf()->Clear(id, uid) ? "success" : "busy");
    });
}
9.2.2 LogIdCommand

  其中 LogIdCommand 是一个模板方法,只要传入的处理方法不一致,那就是发生了函数重载,所以是可以支持多命令复用的

//system\logging\logd\CommandListener.cpp
template <typename F>
static int LogIdCommand(SocketClient* cli, int argc, char** argv, F&& function) {
    setname();
    function(static_cast<log_id_t>(log_id));
    return 0;
}

  可见最终还是调用传入的函数来处理对应的命令。

9.3 CommandListener 的启动

  CommandListener 也是调用 startListener 启动的,它继承自它的顶层基类 SocketListener

main
    CommandListener* cl = new CommandListener(log_buffer, &log_tags, &prune_list, &log_statistics);
    cl->startListener()
   
    // Notify that others can now interact with logd
    SetProperty("logd.ready", "true");

10.总结

  至此整个 logd 至此也准备完成了,其中的各种 unix domain socket 节点都可以使用了,例如客户端写 log 内容到 logdw,客户端同 logdr 注册log监听。

<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]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值