Android 中malloc_debug 原理详解
版本基于:Android R
0. 前言
最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针对整个系统,运行代价还是很高的。而笔者遇到的问题大致有所方向,能指定到某一个进程,针对单个进程是否有检测的功能呢?答案是肯定的,也就是本文需要分析的 malloc_debug。
1. malloc_debug简介
malloc_debug 是调试native 内存问题的一个工具,能够帮助我们检测内存损坏、内存泄漏、释放再使用等问题。详细的细节可以查看:bionic/libc/malloc_debug/README.md 文件,该文件主要总结Android N 及之后版本中的使用方法,而Android N 之前的malloc_debug 使用方法可以查看README_marshmallow_and_earlier.md 这个文件。
2. malloc_debug 初始化
在分析 malloc_debug 初始化之前,需要记住 malloc_debug 在libc 中的几个重要常量:
bionic/libc/bionic/malloc_common_dynamic.cppstatic constexpr char kDebugSharedLib[] = "libc_malloc_debug.so";
static constexpr char kDebugPrefix[] = "debug";
static constexpr char kDebugPropertyOptions[] = "libc.debug.malloc.options";
static constexpr char kDebugPropertyProgram[] = "libc.debug.malloc.program";
static constexpr char kDebugEnvOptions[] = "LIBC_DEBUG_MALLOC_OPTIONS";
这里,指定了动态加载malloc_debug 的so 名称,也指定了 malloc_debug 配置的prop 名称和环境变量名称。
2.1 加载lib.so 时进行preinit
在程序加载 libc.so 的时候会调用 __libc_preinit():
bionic/libc/bionic/libc_init_dynamic.cpp// We flag the __libc_preinit function as a constructor to ensure that
// its address is listed in libc.so's .init_array section.
// This ensures that the function is called by the dynamic linker as
// soon as the shared library is loaded.
// We give this constructor priority 1 because we want libc's constructor
// to run before any others (such as the jemalloc constructor), and lower
// is better (http://b/68046352).
__attribute__((constructor(1))) static void __libc_preinit() {// The linker has initialized its copy of the global stack_chk_guard, and filled in the main// thread's TLS slot with that value. Initialize the local global stack guard with its value.__stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);__libc_preinit_impl();
}
__libc_preinit() 在main 函数执行前执行,因为它有 __attribute__((constructor(1))),通过这个constructor 对此程序所连接的 libc.so 进行 preinit。
该函数的核心处理函数是 __libc_preinit_impl():
bionic/libc/bionic/libc_init_dynamic.cpp// We need a helper function for __libc_preinit because compiling with LTO may
// inline functions requiring a stack protector check, but __stack_chk_guard is
// not initialized at the start of __libc_preinit. __libc_preinit_impl will run
// after __stack_chk_guard is initialized and therefore can safely have a stack
// protector.
__attribute__((noinline))
static void __libc_preinit_impl() {
#if defined(__i386__)__libc_init_sysinfo();
#endif// Register libc.so's copy of the TLS generation variable so the linker can// update it when it loads or unloads a shared object.TlsModules& tls_modules = __libc_shared_globals()->tls_modules;tls_modules.generation_libc_so = &__libc_tls_generation_copy;__libc_tls_generation_copy = tls_modules.generation;__libc_init_globals();__libc_init_common();// Hooks for various libraries to let them know that we're starting up.__libc_globals.mutate(__libc_init_malloc);// Install reserved signal handlers for assisting the platform's profilers.__libc_init_profiling_handlers();__libc_init_fork_handler();#if __has_feature(hwaddress_sanitizer)// Notify the HWASan runtime library whenever a library is loaded or unloaded// so that it can update its shadow memory.__libc_shared_globals()->load_hook = __hwasan_library_loaded;__libc_shared_globals()->unload_hook = __hwasan_library_unloaded;
#endifnetdClientInit();
}
这里需要注意三点:
- 全局变量 __libc_globals:
bionic/libc/private/bionic_globals.h__LIBC_HIDDEN__ extern WriteProtected<libc_globals> __libc_globals;
- __libc_init_globals() 对__libc_globals 初始化:
- __libc_globals.mutate(__libc_init_malloc); 为每个进程注册通知
2.2 入口函数 __libc_init_malloc()
bioni/libc/bionic/malloc_common_dynamic.cpp// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__BIONIC_WEAK_FOR_NATIVE_BRIDGE
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {MallocInitImpl(globals);
}
参数 globals 指向的是 __libc_globals 对象的 contents 里的 value,即 libc_globals 对象。这里顺便来看下 libc_globals:
bionic/libc/private/bionic_globals.hstruct libc_globals {vdso_entry vdso[VDSO_END];long setjmp_cookie;uintptr_t heap_pointer_tag;_Atomic(const MallocDispatch*) current_dispatch_table;_Atomic(const MallocDispatch*) default_dispatch_table;MallocDispatch malloc_dispatch_table;
};
malloc_dispatch_table 就是后来用来存放,libc_malloc_debug.so 中的函数 symbol,详细看第 2.4.1 节。
回到函数,该函数最终调用的 MallocInitImpl(),该函数详细看第 2.3 节。
2.3 MallocInitImpl()
bionic/libc/bionic/malloc_common_dynamic.cppstatic void MallocInitImpl(libc_globals* globals) {char prop[PROP_VALUE_MAX];char* options = prop;MaybeInitGwpAsanFromLibc(globals);// Prefer malloc debug since it existed first and is a more complete// malloc interceptor than the hooks.bool hook_installed = false;if (CheckLoadMallocDebug(&options)) {hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);} else if (CheckLoadMallocHooks(&options)) {hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);}if (!hook_installed) {if (HeapprofdShouldLoad()) {HeapprofdInstallHooksAtInit(globals);}} else {// Record the fact that incompatible hooks are active, to skip any later// heapprofd signal handler invocations.HeapprofdRememberHookConflict();}
}
针对 malloc_debug() 会通过 CheckLoadMallocDebug() 来确认该工具是否 enabled:
bionic/libc/bionic/malloc_common_dynamic.cppstatic bool CheckLoadMallocDebug(char** options) {// If kDebugMallocEnvOptions is set then it overrides the system properties.char* env = getenv(kDebugEnvOptions);if (env == nullptr || env[0] == '\0') {if (__system_property_get(kDebugPropertyOptions, *options) == 0 || *options[0] == '\0') {return false;}// Check to see if only a specific program should have debug malloc enabled.char program[PROP_VALUE_MAX];if (__system_property_get(kDebugPropertyProgram, program) != 0 &&strstr(getprogname(), program) == nullptr) {return false;}} else {*options = env;}return true;
}
通过该函数主要有两种方式使能 malloc_debug:
- 通过环境变量 LIBC_DEBUG_MALLOC_OPTIONS 指定options
- 通过prop libc.debug.malloc.options 指定 options
回到 MallocInitImpl() 函数,当确认 malloc_debug 使能时,会调用 InstallHooks() 进行动态加载 libc_malloc_debug.so,该函数的参数除了传入 globals 和 options,还指定了库的使用前缀和库名,这里分别是“debug” 和 libc_malloc_debug.so。函数 InstallHooks() 详细看 2.4 节。
2.4 InstallHooks()
bionic/libc/bionic/malloc_common_dynamic.cppstatic bool InstallHooks(libc_globals* globals, const char* options, const char* prefix,const char* shared_lib) {void* impl_handle = LoadSharedLibrary(shared_lib, prefix, &globals->malloc_dispatch_table);if (impl_handle == nullptr) {return false;}if (!FinishInstallHooks(globals, options, prefix)) {dlclose(impl_handle);return false;}return true;
}
这里主要做了两件事情:
- LoadSharedLibrary(),注意这里的第三个参数 malloc_dispatch_table;
- FinishInstallHooks(),进行malloc_debug 初始化工作,并注册回调函数 MallocFiniImpl() 进行收尾;
2.4.1 LoadSharedLibrary()
在 LoadSharedLibrary() 中通过 dlopen() 动态加载 libc_malloc_debug.so,接着通过 InitSharedLibrary() 函数查找如下names 数组中的 symbol,将查找到的 symbol 保存在全局数组变量 gfunctions 中。注意查找的函数都会加上 debug_ 前缀。
bionic/libc/bionic/malloc_common_dynamic.cppbool InitSharedLibrary(void* impl_handle, const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {static constexpr const char* names[] = {"initialize","finalize","get_malloc_leak_info","free_malloc_leak_info","malloc_backtrace","write_malloc_leak_info",};for (size_t i = 0; i < FUNC_LAST; i++) {char symbol[128];snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);gFunctions[i] = dlsym(impl_handle, symbol);if (gFunctions[i] == nullptr) {error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);ClearGlobalFunctions();return false;}}if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {ClearGlobalFunctions();return false;}return true;
}
然后通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数:
bionic/libc/malloc_debug/malloc_debug.cppdebug_free()
debug_calloc()
debug_mallinfo()
debug_malloc()
debug_realloc()
...
并将查找到的 symbol 都存放到 MallocDispatch 对应的函数指针,这个 MallocDispatch 就是最开始 LoadSharedLibrary() 的第三个参数,也就是 globals->malloc_dispatch_table:
bionic/libc/private/bionic_malloc_dispatch.hstruct MallocDispatch {MallocCalloc calloc;MallocFree free;MallocMallinfo mallinfo;MallocMalloc malloc;MallocMallocUsableSize malloc_usable_size;MallocMemalign memalign;MallocPosixMemalign posix_memalign;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)MallocPvalloc pvalloc;
#endifMallocRealloc realloc;
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)MallocValloc valloc;
#endifMallocIterate malloc_iterate;MallocMallocDisable malloc_disable;MallocMallocEnable malloc_enable;MallocMallopt mallopt;MallocAlignedAlloc aligned_alloc;MallocMallocInfo malloc_info;
} __attribute__((aligned(32)));
指定这些函数的目的是什么呢?
详细可以查看第 3 节的malloc() 函数调用。
2.4.2 FinishInstallHooks()
bionic/libc/bionic/malloc_common_dynamic.cppbool FinishInstallHooks(libc_globals* globals, const char* options, const char* prefix) {init_func_t init_func = reinterpret_cast<init_func_t>(gFunctions[FUNC_INITIALIZE]);// If GWP-ASan was initialised, we should use it as the dispatch table for// heapprofd/malloc_debug/malloc_debug.const MallocDispatch* prev_dispatch = GetDefaultDispatchTable();if (prev_dispatch == nullptr) {prev_dispatch = NativeAllocatorDispatch();}if (!init_func(prev_dispatch, &gZygoteChild, options)) {error_log("%s: failed to enable malloc %s", getprogname(), prefix);ClearGlobalFunctions();return false;}// Do a pointer swap so that all of the functions become valid at once to// avoid any initialization order problems.atomic_store(&globals->default_dispatch_table, &globals->malloc_dispatch_table);if (!MallocLimitInstalled()) {atomic_store(&globals->current_dispatch_table, &globals->malloc_dispatch_table);}// Use atexit to trigger the cleanup function. This avoids a problem// where another atexit function is used to cleanup allocated memory,// but the finalize function was already called. This particular error// seems to be triggered by a zygote spawned process calling exit.int ret_value = __cxa_atexit(MallocFiniImpl, nullptr, nullptr);if (ret_value != 0) {// We don't consider this a fatal error.warning_log("failed to set atexit cleanup function: %d", ret_value);}return true;
}
该函数大致做了三件事情:
- 调用 malloc_debug 中的 debug_initialize(),对malloc_debug 内存进行初始化工作,例如其中的关键变量 g_dispatch 和 g_debug,注意参数prev_dispatch 是默认dispatch,最开始默认为NULL,用 NativeAllocatorDispatch() 进行创建;
- 设置 __libc_globals 对象中的 libc_globals.default_dispatch_table 和 current_dispatch_table 指向 malloc_dispatch_table,以后在 malloc 库函数里都会通过 GetDispatchTable(),这个函数就是返回的 current_dispatch_table 指针;
- 通过 __cxa_atexit() 调用,注册 MallocFiniImpl(),注册的此函数将在进程 exit 时(例如调用 exit()函数) 进行回调:
bionic/libc/bionic/malloc_common_dynamic.cppstatic void MallocFiniImpl(void*) {// Our BSD stdio implementation doesn't close the standard streams,// it only flushes them. Other unclosed FILE*s will show up as// malloc leaks, but to avoid the standard streams showing up in// leak reports, close them here.fclose(stdin);fclose(stdout);fclose(stderr);reinterpret_cast<finalize_func_t>(gFunctions[FUNC_FINALIZE])();
}
其实最终调用的是 malloc_debug 中的debug_finalize() 进行检查,例如是否有内存泄漏,如果有,会将 callstack 打印出来:
bionic/libc/malloc_debug/malloc_debug.cppvoid debug_finalize() {if (g_debug == nullptr) {return;}// Make sure that there are no other threads doing debug allocations// before we kill everything.ScopedConcurrentLock::BlockAllOperations();// Turn off capturing allocations calls.DebugDisableSet(true);if (g_debug->config().options() & FREE_TRACK) {PointerData::VerifyAllFreed();}if (g_debug->config().options() & LEAK_TRACK) {PointerData::LogLeaks();}if ((g_debug->config().options() & BACKTRACE) && g_debug->config().backtrace_dump_on_exit()) {debug_dump_heap(android::base::StringPrintf("%s.%d.exit.txt",g_debug->config().backtrace_dump_prefix().c_str(),getpid()).c_str());}backtrace_shutdown();delete g_debug;g_debug = nullptr;DebugDisableFinalize();
}
例如options 里指定的 FREE_TRACK、LEAK_TRACK、BACKTRACE,都是在这里进行检查。
注意:
__cxa_atexit() 注册的 MallocFiniImpl(),是需要进程主动调用 exit() 才会调用 debug_finalize() 去检查,如果进程收到 fatal signal 而导致被 kernel 强制 exit,此时进程不会调用 exit(),也就不会调用 debug_finalize() 进行检查了。
3. malloc()
在上面一节将 malloc_debug 初始化的过程基本是分析完了,其中关键点总结有如下几个方面:
- 通过 dlopen() 动态加载 libc_malloc_debug.so,并将几个重要接口函数保存在 gfunctions 数组中;
- 通过 InitMallocFuntions() 在这个so里继续查找 malloc_debug 其他系列函数,并保存在全局变量 __libc_globals 对象里 libc_globals.malloc_dispatch_table 变量指定的函数指针;
- 注册 exit() 的回调函数 MallocFiniImpl(),最终调用 malloc_debug 中的 debug_finalize() 接口进行检查;
本节来看下 malloc() 接口,通过该接口进一步了解 malloc_debug 的使用:
bionic/libc/bionic/malloc_common.cppextern "C" void* malloc(size_t bytes) {auto dispatch_table = GetDispatchTable();void *result;if (__predict_false(dispatch_table != nullptr)) {result = dispatch_table->malloc(bytes);} else {result = Malloc(malloc)(bytes);}if (__predict_false(result == nullptr)) {warning_log("malloc(%zu) failed: returning null pointer", bytes);return nullptr;}return MaybeTagPointer(result);
}
如果没有使能 malloc_debug 时,dispatch_table 为 nullptr,则会进入 Malloc() 调用,即原生的 malloc 函数。这里的 dispatch_table 就是加载 libc_malloc_debug.so 之后初始化全局变量 __libc_globals 中的 current_dispatch_table,详细看上面第 2.4.2 节。
如果使能了 malloc_debug时,就会调用 dispatch_table->malloc(),这里的malloc 函数就是之前第 2.4.1 节中所解析的 MallocDispatch 里面的函数指针。这里最终调用的就是 malloc_debug 中的 debug_malloc() 函数:
bionic/libc/malloc_debug/malloc_debug.cppvoid* debug_malloc(size_t size) {if (DebugCallsDisabled()) {return g_dispatch->malloc(size);}ScopedConcurrentLock lock;ScopedDisableDebugCalls disable;ScopedBacktraceSignalBlocker blocked;void* pointer = InternalMalloc(size);if (g_debug->config().options() & RECORD_ALLOCS) {g_debug->record->AddEntry(new MallocEntry(pointer, size));}return pointer;
}
我们在之前的第 2.4.2 节中 FinishInstallHooks() 函数会对 malloc_debug 进行初始化,其中就包括 malloc_debug 中的关键变量 g_debug,options 的解析也是在 g_debug的 Initialize() 中完成。
介绍完 g_debug,再来看下 debug_malloc() 的核心处理函数在 InternalMalloc(),参数为需要申请的内存大小。
3.1 InternalMalloc()
bionic/libc/malloc_debug/malloc_debug.cppstatic void* InternalMalloc(size_t size) {if ((g_debug->config().options() & BACKTRACE) && g_debug->pointer->ShouldDumpAndReset()) {debug_dump_heap(android::base::StringPrintf("%s.%d.txt", g_debug->config().backtrace_dump_prefix().c_str(), getpid()).c_str());}if (size == 0) {size = 1;}//g_debug在初始化的时候,会根据options解析 extra_bytes_size_t real_size = size + g_debug->extra_bytes();if (real_size < size) {// Overflow.errno = ENOMEM;return nullptr;}if (size > PointerInfoType::MaxSize()) {errno = ENOMEM;return nullptr;}void* pointer;//创建 header,real_size 按照16字节或8字节对齐,64位系统按16字节if (g_debug->HeaderEnabled()) {Header* header =reinterpret_cast<Header*>(g_dispatch->memalign(MINIMUM_ALIGNMENT_BYTES, real_size));if (header == nullptr) {return nullptr;}pointer = InitHeader(header, header, size);} else {pointer = g_dispatch->malloc(real_size);}if (pointer != nullptr) {if (g_debug->TrackPointers()) {PointerData::Add(pointer, size);}if (g_debug->config().options() & FILL_ON_ALLOC) {size_t bytes = InternalMallocUsableSize(pointer);size_t fill_bytes = g_debug->config().fill_on_alloc_bytes();bytes = (bytes < fill_bytes) ? bytes : fill_bytes;memset(pointer, g_debug->config().fill_alloc_value(), bytes);}}return pointer;
}
来看下 real_size 是在size 基础上又加上了 g_debug->extra_bytes(),在g_debug 在初始化时根据 options 指定计算出增加的空间保存在 g_debug->extra_bytes_ 中,详细可以查看 g_debug->Initialize():
bionic/libc/malloc_debug/DebugData.cppbool DebugData::Initialize(const char* options) {if (config_.options() & HEADER_OPTIONS) {// Initialize all of the static header offsets.pointer_offset_ = __BIONIC_ALIGN(sizeof(Header), MINIMUM_ALIGNMENT_BYTES);if (config_.options() & FRONT_GUARD) {front_guard.reset(new FrontGuardData(this, config_, &pointer_offset_));}extra_bytes_ = pointer_offset_;// Initialize all of the non-header data.if (config_.options() & REAR_GUARD) {rear_guard.reset(new RearGuardData(this, config_));extra_bytes_ += config_.rear_guard_bytes();}}...if (config_.options() & EXPAND_ALLOC) {extra_bytes_ += config_.expand_alloc_bytes();}return true;
}
回到 InternalMalloc(), 当 options 设置 HEADER_OPTIONS 时,HeaderEnabled() 返回true,此时创建 header 对象,以real_size 申请空间,并调用 InitHeader() 对header 进行初始化。
否则,通过 g_dispatch->malloc() 通过原生的 malloc() 申请 real_size 空间:
bionic/libc/bionic/malloc_common.cppstatic constexpr MallocDispatch __libc_malloc_default_dispatch __attribute__((unused)) = {Malloc(calloc),Malloc(free),Malloc(mallinfo),Malloc(malloc),Malloc(malloc_usable_size),Malloc(memalign),Malloc(posix_memalign),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)Malloc(pvalloc),
#endifMalloc(realloc),
#if defined(HAVE_DEPRECATED_MALLOC_FUNCS)Malloc(valloc),
#endifMalloc(malloc_iterate),Malloc(malloc_disable),Malloc(malloc_enable),Malloc(mallopt),Malloc(aligned_alloc),Malloc(malloc_info),
};
说到底,最终会使用原生的 malloc() 进行创建,只不过在原来的 size 基础上多了一些东西,而这些东西就是 malloc_debug 的核心。
至此,malloc_debug 原理已经基本分析完。
有什么遗漏的地方,欢迎留言提醒,笔者会在后面尽快更新!!
相关文章:
Android 中malloc_debug 原理详解
版本基于:Android R 0. 前言 最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针对整个系统,运行代价还是很高的。而笔者遇到的问题大致有所方向,能指定到某一个进程,针对单…...
D. Triangle Coloring【组合数学,乘法逆元】
链接 分析 题目要求我们去求出最优的染色的方法数。首先什么时候是最优的,这里只有两种颜色,不可能取到三条边,即蓝色为B,红色为R,有BBB,RRR,BBR,RRB四种组合,显然最多的就是取两条边,我们想取到…...
【读论文】AttentionFGAN
【读论文】AttentionFGAN介绍网络架构提取红外图像目标信息的网络辨别器损失函数生成器损失函数辨别器损失函数总结参考论文: https://ieeexplore.ieee.org/document/9103116/如有侵权请联系博主介绍 好久没有读过使用GAN来实现图像融合的论文了,正好看…...
ClickHouse 配置文件使用说明
本文主要介绍 ClickHouse 的配置文件。在 ClickHouse 中配置主要分为两类,一类是负责 server 端配置的,另一类是负责用户端配置的。负责 server 端配置的一般会放在 config.xml 文件中,负责用户端配置的一般会放在 users.xml 文件中。当然如果…...
如果不是互联网人,谁会找到这些神器?
一、上线啦 你肯定该问了,这个是什么鬼东西。它本来是一个创建自己网站的网站。 现在使用它可以创建自己的小程序,又不是有点小厉害了。 而且功能强大,还支持微信支付,分销,优惠券,营销等多种功能。 还有多…...
Neo4j优化
使用参数 查询参数 :params设置参数 :param actorName: Tom Hanks参数的冒号后要用空格使用参数用 $ MATCH (p:Person)-[:ACTED_IN]->(m:Movie) WHERE p.name $actorName RETURN m.released AS releaseDate,m.title AS title ORDER BY m.released DESC多个参数 MATCH (p:Pe…...
CF1692G 2^Sort 题解
CF1692G 2^Sort 题解题目链接字面描述题面翻译题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1提示思路代码实现题目 链接 https://www.luogu.com.cn/problem/CF1692G 字面描述 题面翻译 给你一个长度为 n(∑n<2⋅105)n \ (\sum n < 2\cdot 10^5)n (∑n<…...
关于物理像素,逻辑像素,像素比
关于物理像素、逻辑像素(css像素)、分辨率、像素比的超详细讲解 在日常生活中,有这样一个问题。同样的图片为什么在不同的设备上显示的大小是不一样的。🤒带着这个问题来说明一下。 一、物理像素 设备刚生产出来就已经固定了&a…...
JavaSE基础部分总结
JavaSe基础部分 文章目录JavaSe基础部分1.命名规范2.基本的数据类型3.方法3.1方法的基本格式3.2 方法的分类3.3 方法的注释4.数组4.1 数组的命名格式4.2 数组中存在的址交换的操作4.3数组Arrays常用的方法1. Arrays.asList(数组作为参数或者数据作为参数):2.Arrays.…...
C++基础知识
目录类和对象C static_cast、dynamic_cast、const_cast和reinterpret_cast1、为什么要引入这四种类型转化?2、应用场景。C/C类型转换的本质struct和class的区别为什么会诞生面向对象的编程思想析构函数的执行时机初始化 const 成员变量C const对象(常对象…...
2023/2/24 图数据库Neo4j的理解与应用
1 什么是图数据库(graph database) 十大应用案例:https://go.neo4j.com/rs/710-RRC-335/images/Neo4j-Top-Use-Cases-ZH.pdf “大数据”每年都在增长,但如今的企业领导者不仅需要管理更大规模的数据,还迫切需要从现有…...
适合视力障碍者的Linux
导读有哪些最适合视障用户的 Linux 发行版?让我们一起来看看。 如果有人视力障碍或失明,他们可能会依赖声音提示或其他交互方式(如盲文)来阅读和交流。 他们怎样才能使用 Linux 发行版? 嗯,一般来说&…...
Tina Linux 存储开发指南
Tina Linux 存储开发指南 1 概述 1.1 编写目的 介绍TinaLinux Flash,分区,文件系统等存储相关信息,指导方案的开发定制。 1.2 适用范围 Tina V3.0 及其后续版本。 1.3 相关人员 适用于TinaLinux 平台的客户及相关技术人员。 2 分区管…...
【洛谷 P2670】[NOIP2015 普及组] 扫雷游戏 题解(模拟)
[NOIP2015 普及组] 扫雷游戏 题目背景 NOIP2015 普及组 T2 题目描述 扫雷游戏是一款十分经典的单机小游戏。在 nnn 行 mmm 列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。玩家翻…...
【nohup引发磁盘读写高】nohup命令导致服务器磁盘读写占满该如何修复?
【写在前面】自己在跑一个项目的时候,猛然发现服务器挂了,直接访问不了,呈现出一种卡死现象,我当时都懵了,难道阿里在后端升级,也不会选择在工作日的时间升级吧,于是乎就咨询了一下客服。才有下…...
MySQL(二)索引和SQL优化
MySQL进阶MySQL体系结构存储引擎存储引擎特点InnoDB逻辑存储结构MyISAMMemory存储引擎选择索引索引结构二叉树B-TreeBTreeHash索引分类索引语法SQL性能分析工具SQL执行频率慢查询日志profile详情explain索引使用联合索引索引失效情况SQL提示覆盖索引前缀索引单列索引与联合索引…...
Java常用日期类(包含三代)_Date类及Calendar类等
一.java.util.Date类概述从JDK 1.0出现。表示一个日期和时间,精确到毫秒,内部getTime()从1970年1月1号开始算。1. java.util.Date类构造部份构造已经过时,重点看以下两个构造。public Date()从运行程序的此时此刻到时间原点经历的毫秒值&…...
计算机网络你都懂了吗
文章目录一、计算机网络的定义简单定义通用定义二、计算机网络通信过程三、什么是网络协议(Protocol)四、网络协议组成及功能一、计算机网络的定义 简单定义 计算机网络是一些相互连接的、自治的计算机系统的集合。 通用定义 将处于不同位置并具有独…...
3.4 Spring Boot 日志配置
第3章 Spring Boot 的系统配置 3.1 Spring Boot 系统配置文件 3.2 Spring Boot 自定义配置项 3.3 Spring Boot 其他配置 3.4 Spring Boot 日志配置 3.5 实战:Spring Boot 实现系统多环境配置 3.4 Spring Boot 日志配置 日志对于系统监控、故障定位非常重要…...
3款百里挑一的国产软件,逆天好用,装了就舍不得卸载
推荐3款让你偷懒,让你上头的提效电脑软件,个个功能强大,让你远离加班! 很多几个小时才能做好的事情,用上它们,只需要5分钟就行!! 1、JNPF快速开发平台 JNPF 是一款精巧耐用的软件…...
label-studio的使用教程(导入本地路径)
文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
【机器视觉】单目测距——运动结构恢复
ps:图是随便找的,为了凑个封面 前言 在前面对光流法进行进一步改进,希望将2D光流推广至3D场景流时,发现2D转3D过程中存在尺度歧义问题,需要补全摄像头拍摄图像中缺失的深度信息,否则解空间不收敛…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
