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

news2024/11/15 5:03:57

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监听。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/78940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode102. 二叉树的层序遍历】——层序遍历

102. 二叉树的层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3],[9,20],[15,7]]示…

[附源码]Python计算机毕业设计SSM基于的优质房源房租管理系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

LeetCode HOT 100 —— 207 .课程表

题目 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习课程 ai 则 必须 先学习课程 bi 。 例…

软件测试的类型

在本节中&#xff0c;我们将了解各种类型的软件测试&#xff0c;这些测试可以在软件开发生命周期时使用。 众所周知&#xff0c; 软件测试 是根据客户的先决条件分析应用程序功能的过程。 如果我们想确保我们的软件没有错误或稳定&#xff0c;我们必须执行各种类型的软件测试…

神经辐射场NeRF

NeRF: Representing Scenes as Neural Radiance Fields for Novel View Synthesis 文章目录NeRF: Representing Scenes as Neural Radiance Fields for Novel View SynthesisPipelineStepsVolume RenderingOptimizationPositional EncodingHierarchical Volume Samplingview-de…

从零学习gitHub (一)

一、账户注册 1.1、打开 GitHub 官方网址 GitHub 1.2、填入用户名「Username」、邮箱「Email」l、密码「Password 注意事项 Username&#xff1a;用户名不能重名&#xff0c;可包含字母数字字符和单行连字符&#xff0c;且不能以单行连字符开头或结尾&#xff1b; Email&…

Linux | 线程同步 | 条件变量 | 生产消费模型 | 阻塞队列实现生产消费模型

文章目录线程饥饿条件变量接口的使用生产者和消费者模型使用阻塞队列实现生产消费模型代码中存在的问题关于pthread_cond_wait的原子性生产消费模型中的并发体现线程饥饿 在多线程并发执行的场景中&#xff0c;会不会出现这样的情况&#xff0c;一些线程由于优先级更高&#x…

分布式锁4-Redisson分布式锁实现与看门狗原理

文章目录一.Redisson介绍二.分布式锁的运用1.引入依赖.2.增加配置类.3.简单代码实现1.不指定加锁时间,会默认启动看门狗.自动帮你的锁进行续期.2.指定加锁时间,不启用看门狗续期,到期自动解锁.三.分布式锁实现原理加锁过程看门狗续期过程一.Redisson介绍 Redisson 是架设在 Red…

读《冯诺依曼传》

关于冯诺依曼 冯诺依曼是20世纪的全才,原名约翰尼,匈牙利美籍科学家,被称为计算机之父和博弈论之父。计算机和博弈论都深刻改变人类的生活工作和思维方式,极大地促进了社会和人类文明的进步发展。在六个哲学领域他都做出了相当大的贡献,把模糊的问题用数学精确地表述出来…

扩散模型的迁移和应用(DiffusionDet,AR-LDM,MagicVideo,RenderDiffusion,AltDiffusion,VD)

在上一篇博文中&#xff0c;博主已经整理了扩散模型&#xff08;Diffusion Model&#xff0c;DDPM&#xff0c;GLIDE&#xff0c;DALLE2&#xff0c;Stable Diffusion&#xff09;的基本原理&#xff0c;后续不再赘述其细节。作为一个最近被讨论热烈的方向&#xff0c;很自然地…

干Java开发快30岁了,没有核心竞争力,该怎么跳槽面试?

今年互联网大环境的确不怎么好&#xff0c;互联网公司纷纷陷入裁员风波并缩减规模&#xff0c;这就导致更多程序员需要去竞争更少的就业岗位&#xff0c;搞的整个IT 行业越来越卷。作为Java程序员的我们就更不用说了&#xff0c;除去加班的可能性&#xff0c;每天上班8小时需要…

Java项目:SSM药品进货销售仓储信息管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 管理员角色包含以下功能&#xff1a; 管理员登录,用户信息管理,个人信息管理,药品信息管理,药品类别信息管理,选购药品管理,订单管理,订单记录管…

余生很短,及时止损--爱摸鱼的美工(九)

-----------作者&#xff1a;天涯小Y 余生很短&#xff0c;及时止损 今日份垃圾清理完毕 有的人就像垃圾车 他们装满了垃圾四处奔走 充满懊悔、愤怒.失望的情绪 随着垃圾越堆越高 就需要找地方倾倒&#xff0c;释放出来 他们会逮着一切机会 到处碰瓷、找茬、泄愤 如果你给他们…

Js逆向教程22-AST 抽象语法树babel安装

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; AST 抽象语法树babel安装 一、什么是AST js代码&#xff1a; var a1;json标识&#xff1a; 赋值语句左边为定义定义的名称为a(作用域…

python——面向对象

1、概念 ①OOP&#xff1a;Object Oriented Programming ②面向对象&#xff1a;只关心哪个对象完成这个功能&#xff0c;它关注的是完成功能的结果&#xff0c;以结果为导向 python、Java、js、c... ③面向过程&面向对象 ④面向对象三大特征&#xff1a;封装、继承、多…

springcloud(配置中心)

目录 1. 为何需要配置中心2. nacos的配置中心 2.1 配置示例2.2 多环境配置2.3 GROUP2.4 namespace 1. 为何需要配置中心 没有配置中心的情况下会出现什么情况&#xff1a; 如果没有配置中心&#xff0c;则各个服务的配置文件分散在各个项目中&#xff0c;不方便维护出于安全考…

代码审计学习 : xhcms

前言 从大佬那里听说&#xff0c;xhcms 很适合代码审计的新手。 环境 php 5.4.45 Apache 2.4.39 Mysql 5.7.26 文件上传配合文件包含 /index.php 和 /admin/index.php error_reporting(0); //关闭错误显示 $fileaddslashes($_GET[r]); //接收文件名 $action$file?index:…

FH30502输入3.7V升5V电流3A-5A同步整流升压芯片

3.3V升5V电流3A-5A同步整流升压芯片&#xff0c;2.7V到18V的输入电压支持供电系统和电池的较宽范围应用。FH30502根据负载情况的变化自动切换工作模式&#xff0c;在轻载Burst模式下静态电流处于低状态。FH30502使用自适应常数断开时间峰值电流模式控制。FH30502有一个内部特性…

C 语言实现经典贪吃蛇游戏

原文链接&#xff1a;C语言 贪吃蛇游戏 文章目录一、说明二、效果2.1 欢迎界面2.2 游戏规则2.3 得分排行2.4 退出游戏2.5 游戏界面2.6 游戏结束三、源码3.1 cmd.h3.2 cmd.c3.3 io.h3.4 io.c3.5 model.h3.6 service.h3.7 service.c3.8 ui.h3.9 ui.c3.10 utils.h3.11 utils.c3.12…

m蜂窝移动通信系统中越区切换的matlab仿真分析

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 用Matlab仿真一个蜂窝模型&#xff0c;用户在打电话时产生切换的过程。建立蜂窝越区切换&#xff0c;假设有16个蜂窝&#xff0c;实现两个小区之间的硬切换&#xff0c;每个小区假设能容纳30个用…