hung 之 Android llkd

目录

1. llkd 简介

2. 原理

2.1 内核活锁

2.2 检测机制

2.3 为什么 persistent stack signature 检测机制不执行 ABA 检查?

2.4 为什么 kill 进程后,进程还存在就能判定发生了内核 live-lock?

3. 代码

3.1 内核 live-lock 检查

3.2 更新 state & count

3.3 persistent stack signature

3.3.1 开关

3.3.2 检测

4. 使能


1. llkd 简介

linux kernel 的 hungtaskd(由 CONFIG_DETECT_HUNG_TASK 使能)功能可以检测 hung tassk,即长时间处于 D 状态的进程。

lldk 是 hungtaskd 功能的用户空间平替(加强)。

llkd 在 debug、非 debug 版本上有不同的检测机制。

  • debug 版本,使用 persistent stack signature 检测机制

        目标是发现内核态调用栈长时间没有变化的进程。

  • 非 debug 版本,增加 persistent D or Z state 检测机制

        目标是发现长时间处于 D 或 Z 状态的进程。

llkd 的 AOSP 代码路径:system/core/llkd

llkd 官方介绍文档:https://source.android.google.cn/docs/core/architecture/kernel/llkd?hl=zh-cn

2. 原理

2.1 内核活锁

根据官方文档的描述,llkd 的作用是发现和减少内核死锁。

而由 llkd 的名字 live-lock daemon 可知,llkd 实际的作用是发现内核活锁。

活锁(live-lock)

活锁是一种情况:线程或进程虽然在不断运行,但实际上并未取得任何进展。

与死锁不同,活锁不会完全停止运行,而是陷入一个无效的循环。

即:死锁时,线程一般会一直处于 sleep 状态;活锁时,线程一般会不断运行。

广义的死锁包含活锁。

内核活锁:是指进程在内核态处于活锁状态。

2.2 检测机制

设置一个检测周期,每个检测周期进行一次内核活锁检测。

在非 debug 版本上使用 persistent D or Z state 检测机制,检测标准是进程长时间处于 D 或 Z 状态且状态没有发生变化。

以 T1 为周期 check 目标进程(所有进程都是目标进程)状态。

每次 check

  • 用变量 count 记录进程在当前状态下持续的时间;
  • 用变量 nrSwitches 记录进程状态切换次数
  • 用变量 schedUpdate 记录进程最近一次调度时的时间戳

check 时

  • 如果进程状态改变,则将 count 置为 0,否则将 count 值 + T1
  • 如果当前状态为 D 或 Z,且 count 值 >= D 或 Z 的 timeout 阈值时,则认为可能发生了 live-lock,执行 kill 进程的动作,将该进程标记为 killed。
  • 下一次 check 时,如果发现 killed 进程仍然存在(即没有 kill 掉),则判定的确发生了内核 live-lock,触发 kernel panic。

在 debug 版本上增加了 persistent stack signature 检测机制,检测标准是进程的内核态调用栈长时间没有发生变化。

首先,persistent stack signature 检测机制不考虑 Z 状态的进程。

其次,persistent stack signature 只匹配特定的栈帧符号,这些符号存放在变量 llkCheckStackSymbols 中,用 idx 表示符号在 llkCheckStackSymbols 中的存放顺序。

每次 check

  • 用变量 stack 记录进程的内核态调用栈匹配到的特定栈帧符号的 idx(按 idx 顺序匹配到第一个即止)
  • 用变量 count_stack 记录进程在当前调用栈下持续的时间

check 时

  • 如果匹配到的 idx 变化,则将 count_stack 置为 0;否则将 count_stack 值 + T1
  • 如果 count_stack 值 >= timeout 阈值,则认为可能发生了 live-lock,执行 kill 进程的动作,将该进程标记为 killed。
  • 如果下一次 check 时,发现 killed 进程仍然存在(即没有 kill 掉),则判定的确发生了内核 live-lock,触发 kernel panic。

2.3 为什么 persistent stack signature 检测机制不执行 ABA 检查?

llkd 在以周期 T1 进行 persistent stack signature 检查时,有可能两次 check 时内核调用栈相同,但是在周期 T1 内调用栈实际上发生了变化,这就产生了 ABA 问题。

但是 llkd 没有进行 ABA 检查,这是因为 llkd 的 persistent stack signature 检查机制允许进程前向调度。即,persistent stack signature 检查并不会使目标进程停止运行,在 persistent stack signature 检查包括 T1 周期内目标进程照常运行,所以没办法做 ABA 检查。

即,非不为也,实不能也~

ABA 问题以及 ABA 检测

ABA 问题发生在一个线程或进程在检查共享资源的状态时,该状态在检查过程中被其他线程或进程更改并恢复到原始状态。具体来说:

  1. 线程 A 读取一个共享变量,发现其值为 A

  2. 线程 A 在进行一些处理时,线程 B 修改了该共享变量的值,从 A 到 B,再从 B 回到 A

  3. 线程 A 再次检查共享变量的值,发现它仍然是 A,于是认为该值从未被修改过,继续执行

这种情况下,线程 A 会误认为共享变量的状态没有发生变化,可能会导致程序错误。为了防止这种情况,需要进行 ABA 检测,即在每次修改共享变量时附加一个版本号或其他标识符,以便检测到状态的变化。

前向调度(Forward Scheduling)

前向调度,是指系统允许线程或进程在未来的某个时间点被调度和执行。这意味着系统可以根据某些条件提前安排线程的执行顺序,而不是严格按照先来先服务的原则。

2.4 为什么 kill 进程后,进程还存在就能判定发生了内核 live-lock?

按照活锁的定义,发生活锁的目标进程是持续运行的,应该不会一直处于 D 或 Z 状态,那么就应该有机会执行到 kill -9 信号,从而被 killed 掉。

但是,内核活锁不一样,如果 live-lock 发生在内核态,进程陷入到内核态的循环中,是没有机会返回用户态的。在这种情况下,常规的信号处理机制(如SIGKILL)就无法生效,因为这些信号通常在用户态中处理,而不是内核态。因此,发生内核活锁的进程杀不掉。

3. 代码

3.1 内核 live-lock 检查

检查内核 live-lock 的关键函数是 llkCheck。

// system/core/llkd/libllkd.cppmilliseconds llkCheck(bool checkRunning) {
// 遍历进程...// 更新目标进程的 count、state 值// ABA mitigation watching last time schedule activity happenedllkCheckSchedUpdate(procp, piddir);// DEBUG 版本 __PTRACE_ENABLED__ 使能
#ifdef __PTRACE_ENABLED__// 执行 persistent stack signature 检查auto stuck = llkCheckStack(procp, piddir);// 执行 persistent state 检查if (llkIsMonitorState(state)) {if (procp->count >= llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {stuck = true;} else if (procp->count != 0ms) {LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"<< pid << "->" << tid << ' ' << process_comm;}}if (!stuck) continue;
#else// 执行 persistent state 检查if (procp->count >= llkStateTimeoutMs[(state == 'Z') ? llkStateZ : llkStateD]) {if (procp->count != 0ms) {LOG(VERBOSE) << state << ' ' << llkFormat(procp->count) << ' ' << ppid << "->"<< pid << "->" << tid << ' ' << process_comm;}continue;}
#endif
...// 代码执行到这里,说明目标进程可能发生了内核 live-lock。// 目标进程还没尝试 kill,则执行 kill。// 对于 Z 状态的进程,需要 kill 它的父进程。if (procp->killed == false) {procp->killed = true;// confirm: re-read uid before committing to a panic.procp->uid = -1;switch (state) {case 'Z':  // kill ppid to free up a Zombie// Killing init will kernel panic without diagnostics// so skip right to controlled kernel panic with// diagnostics.if (ppid == initPid) {break;}LOG(WARNING) << "Z " << llkFormat(procp->count) << ' ' << ppid << "->"<< pid << "->" << tid << ' ' << process_comm << " [kill]";if ((llkKillOneProcess(pprocp, procp) >= 0) ||(llkKillOneProcess(ppid, procp) >= 0)) {continue;}break;case 'D':  // kill tid to free up an uninterruptible D// If ABA is doing its job, we would not need or// want the following.  Test kill is a Hail Mary// to make absolutely sure there is no forward// scheduling progress.  The cost when ABA is// not working is we kill a process that likes to// stay in 'D' state, instead of panicing the// kernel (worse).default:LOG(WARNING) << state << ' ' << llkFormat(procp->count) << ' ' << pid<< "->" << tid << ' ' << process_comm << " [kill]";if ((llkKillOneProcess(llkTidLookup(pid), procp) >= 0) ||(llkKillOneProcess(pid, state, tid) >= 0) ||(llkKillOneProcess(procp, procp) >= 0) ||(llkKillOneProcess(tid, state, tid) >= 0)) {continue;}break;}}// 代码执行到这里,说明确认了内核活锁,触发 kernel panic// We are here because we have confirmed kernel live-lockstd::vector<std::string> threads;auto taskdir = procdir + std::to_string(tid) + "/task/";dir taskDirectory(taskdir);for (auto tp = taskDirectory.read(); tp != nullptr; tp = taskDirectory.read()) {std::string piddir;if (getValidTidDir(tp, &piddir))threads.push_back(android::base::Basename(piddir));}const auto message = state + " "s + llkFormat(procp->count) + " " +std::to_string(ppid) + "->" + std::to_string(pid) + "->" +std::to_string(tid) + " " + process_comm + " [panic]\n" +"  thread group: {" + android::base::Join(threads, ",") +"}";llkPanicKernel(dump, tid,(state == 'Z') ? "zombie" : (state == 'D') ? "driver" : "sleeping",message);dump = false;}LOG(VERBOSE) << "+closedir()";}

3.2 更新 state & count

state 的更新比较简单,只需要在 check 时记录下目标进程当前的 state 即可。

而 count 的更新则要考虑目标进程在 check 周期内是否发生了变化,即需要做 ABA 检查。

是否可以考虑用进程在用户态、内核态的运行时间来表示"持续时间"呢,即只有当用户态、内核态运行时间没有增加时我们才增加"持续时间"?

即,通过 /proc/<pid>/stat 节点可以获取目标进程在用户态、内核态的运行时间。

比如下面的示例进程在用户态的运行时长为 86,内核态运行时间为 77~~~ 单位是 jiffies。

一个jiffy表示CPU调度(软件时钟)的周期,是 CONFIG_HZ 的倒数,比如 CONFIG_HZ 为100,则一个jiffy为10s。

yudi:/ # cat /proc/2523/stat
2523 (binder:2523_2) S 1 2523 0 0 -1 1077936384 3575 4242 0 3 86 77 5 9 20 0 7 0 18048945 11271012352 2012 18446744073709551615 405566062592 405566180224 548682767680 0 0 0 0 0 1073775864 0 0 0 17 1 0 0 0 0 0 405566267184 405566267184 405863604224 548682771308 548682771336 548682771336 548682776540 0

首先,用进程的内核态运行时间不变来认定 "持续" 是不行的,因为内核活锁的主要表现就是进程会在内核态持续运行。内核态运行时间增加,不能说明进程没有内核活锁。

用进程的用户态运行时间不变来认定 "持续" 也不行。虽然用户态运行时间增加,可以说明进程在用户态执行了,不是内核活锁;但是用户态运行时间不增加,却不能说明进程发生了内核 live-lock。

llkd 使用了另外一种方式,确保只有在进程状态在周期内没有变化时,才会增加"持续时间"。

  • 通过 /proc/<pid>/sched 的 last_update_time 字段获取进程状态最近一时更新的时间戳;
  • 通过 /proc/<pid>/schedstat 获取进程状态切换次数。

只要两次 check 时进程状态的切换次数或者更新时间不同,就认为状态发生过变化,将 count 置 0!

1|yudi:/ # cat /proc/2523/sched|grep last_update_time
se.avg.last_update_time                      :      179663295832064yudi:/ # cat /proc/2523/schedstat
230446929 10297343 451

llkCheckSchedUpdate 方法更新 state & count 。

848  // Primary ABA mitigation watching last time schedule activity happened
849  void llkCheckSchedUpdate(proc* procp, const std::string& piddir) {
850      // Audit finds /proc/<tid>/sched is just over 1K, and
851      // is rarely larger than 2K, even less on Android.
852      // For example, the "se.avg.lastUpdateTime" field we are
853      // interested in typically within the primary set in
854      // the first 1K.
855      //
856      // Proc entries can not be read >1K atomically via libbase,
857      // but if there are problems we assume at least a few
858      // samples of reads occur before we take any real action.
859      std::string schedString = ReadFile(piddir + "/sched");
860      if (schedString.empty()) {
861          // /schedstat is not as standardized, but in 3.1+
862          // Android devices, the third field is nr_switches
863          // from /sched:
864          schedString = ReadFile(piddir + "/schedstat");
865          if (schedString.empty()) {
866              return;
867          }
868          auto val = static_cast<unsigned long long>(-1);
869          if (((::sscanf(schedString.c_str(), "%*d %*d %llu", &val)) == 1) &&
870              (val != static_cast<unsigned long long>(-1)) && (val != 0) &&
871              (val != procp->nrSwitches)) {
872              procp->nrSwitches = val;
873              procp->count = 0ms;
874              procp->killed = !llkTestWithKill;
875          }
876          return;
877      }
878  
879      auto val = getSchedValue(schedString, "\nse.avg.lastUpdateTime");
880      if (val == -1) {
881          val = getSchedValue(schedString, "\nse.svg.last_update_time");
882      }
883      if (val != -1) {
884          auto schedUpdate = nanoseconds(val);
885          if (schedUpdate != procp->schedUpdate) {
886              procp->schedUpdate = schedUpdate;
887              procp->count = 0ms;
888              procp->killed = !llkTestWithKill;
889          }
890      }
891  
892      val = getSchedValue(schedString, "\nnr_switches");
893      if (val != -1) {
894          if (static_cast<uint64_t>(val) != procp->nrSwitches) {
895              procp->nrSwitches = val;
896              procp->count = 0ms;
897              procp->killed = !llkTestWithKill;
898          }
899      }
900  }

3.3 persistent stack signature

3.3.1 开关

llkd 代码中通过宏 __PTRACE_ENABLED__ 控制 persistent stack signature 是否使能。

debug 版本编译时默认开启宏 __PTRACE_ENABLED__,因此 debug 版本才会使能 persistent stack signature。

// system/core/llkd/Android.bpcc_library_static {name: "libllkd",srcs: ["libllkd.cpp",],shared_libs: ["libbase","libcutils","liblog",],export_include_dirs: ["include"],cflags: ["-Werror"],product_variables: {debuggable: {cppflags: ["-D__PTRACE_ENABLED__"],},},
}

这里有个疑问,persistent stack signature 功能并不需要 ptrace 目标进程,为什么功能开关要用 __PTRACE_ENABLED__ 这样一个看似与 ptrace 有关的宏控制?

persistent stack signature 功能需要读 /proc/<pid>/stack 节点,该节点返回进程(线程)的内核态调用栈。读 /proc/<pid>/stack 节点时,内核方法会检查 caller 是否有 ptrace 目标进程的权限,然后通过 unwind 获取调用栈。也就是说,如果要使能 persistent stack signature,llkd 需要设置 ptrace 权限(SYS_PTRACE capabilitiy)。

yudi:/ # cat /proc/7187/stack
[<0>] __switch_to+0x244/0x4e4
[<0>] binder_wait_for_work+0x1ac/0x77c
[<0>] binder_thread_read+0x3c8/0x35b0
[<0>] binder_ioctl_write_read+0x120/0x854
[<0>] binder_ioctl+0x294/0x1dc4
[<0>] __arm64_sys_ioctl+0x174/0x1f8
[<0>] el0_svc_common+0xd4/0x270
[<0>] el0_svc+0x28/0x88
[<0>] el0_sync_handler+0x8c/0xf0
[<0>] el0_sync+0x1b4/0x1c0

但是,SYS_PTRACE capabilitiy 权限只有在 debug 版本上才能获取到(sepolicy 限制)。

llkd 在 debug 版本上使用的 rc 配置文件是 llkd-debuggable.rc,在 llkd-debuggable.rc 文件中给 lldk 服务设置了 SYS_PTRACE capabilitiy 权限。

//system/core/llkd/llkd-debuggable.rcservice llkd-1 /system/bin/llkdclass late_startdisableduser llkdgroup llkd readproccapabilities KILL IPC_LOCK SYS_PTRACE DAC_OVERRIDE SYS_ADMINfile /dev/kmsg wfile /proc/sysrq-trigger wtask_profiles ServiceCapacityLow

3.3.2 检测

检测时,首先过滤掉 Z 状态的进程。

忽略掉 llkIgnorelistStack 中定义的进程。

然后检查目标进程的内核态调用栈是否包含 llkCheckStackSymbols 中定义的符号。

// system/core/llkd/libllkd.cpp#ifdef __PTRACE_ENABLED__
bool llkCheckStack(proc* procp, const std::string& piddir) {if (llkCheckStackSymbols.empty()) return false;if (procp->state == 'Z') {  // No brains for Zombiesprocp->stack = -1;procp->count_stack = 0ms;return false;}// Don't check process that are known to block ptrace, save sepolicy noise.// 忽略掉 llkIgnorelistStack 中定义的进程if (llkSkipProc(procp, llkIgnorelistStack)) return false;auto kernel_stack = ReadFile(piddir + "/stack");if (kernel_stack.empty()) {LOG(VERBOSE) << piddir << "/stack empty comm=" << procp->getComm()<< " cmdline=" << procp->getCmdline();return false;}// A scheduling incident that should not reset count_stackif (kernel_stack.find(" cpu_worker_pools+0x") != std::string::npos) return false;char idx = -1;char match = -1;std::string matched_stack_symbol = "<unknown>";// 检查目标进程的内核态调用栈是否包含 llkCheckStackSymbols 中定义的符号for (const auto& stack : llkCheckStackSymbols) {if (++idx < 0) break;if ((kernel_stack.find(" "s + stack + "+0x") != std::string::npos) ||(kernel_stack.find(" "s + stack + ".cfi+0x") != std::string::npos)) {match = idx;matched_stack_symbol = stack;break;}}if (procp->stack != match) {procp->stack = match;procp->count_stack = 0ms;return false;}if (match == char(-1)) return false;procp->count_stack += llkCycle;if (procp->count_stack < llkStateTimeoutMs[llkStateStack]) return false;LOG(WARNING) << "Found " << matched_stack_symbol << " in stack for pid " << procp->pid;return true;
}
#endif

llkCheckStackSymbols 包含目标 symbols。

#define LLK_CHECK_STACK_DEFAULT         \"cma_alloc,__get_user_pages,bit_wait_io,wait_on_page_bit_killable"

llkIgnorelistStack 包含要忽略的进程。原因是这个名单中的进程不能被 ptrace(ptrace 时会 block),所以不能 check 这些进程的 stack,这个名单包含 init、llkd 等进程...

// system/core/llkd/include/llkd.h#define LLK_IGNORELIST_STACK_DEFAULT    "init,lmkd.llkd,llkd,keystore,keystore2,ueventd,apexd"

4. 使能

本地的 Android 设备没有使能 lldk。

yudi:/ # ps -A|grep llkd
yudi:/ #

llkd 服务默认 disable,通过 prop llk.enable 启动。

在非 debug 版本上,启动 llkd-0 服务,

在 debug 版本上,启动 llkd-1 服务(定义在 llkd-debuggable.rc 中)。

可以通过手动设置 llk.enable 为 1 或 true 来启动服务。

// system/core/llkd/llkd.rcon property:llk.enable=truestart llkd-${ro.debuggable:-0}service llkd-0 /system/bin/llkdclass late_startdisableduser llkdgroup llkd readproccapabilities KILL IPC_LOCKfile /dev/kmsg wfile /proc/sysrq-trigger wtask_profiles ServiceCapacityLow

debug 版本如果设置了 ro.llk.enable 为 1,则开机时会自动设置 llk.enable 为 true 并启动 llkd 服务。

// system/core/llkd/llkd.rc# eng default for ro.llk.enable and ro.khungtask.enable
on property:ro.debuggable=*setprop llk.enable ${ro.llk.enable:-0}setprop khungtask.enable ${ro.khungtask.enable:-0}on property:ro.llk.enable=truesetprop llk.enable trueon property:llk.enable=1setprop llk.enable trueon property:llk.enable=0setprop llk.enable false

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

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

相关文章

verilog刷题笔记

1、选择器实现方式 &#xff08;1&#xff09;case语句&#xff0c;注意default &#xff08;2&#xff09;if-else语言&#xff0c;注意else&#xff0c;有优先级 &#xff08;3&#xff09;三元运算符 &#xff1f; &#xff1a; 2、阻塞赋值/非阻塞赋值都是过程性赋值&a…

使用崖山YMP 迁移 Oracle/MySQL 至YashanDB 23.2 验证测试

前言 首届YashanDB「迁移体验官」开放后&#xff0c;陆续收到「体验官」们的投稿&#xff0c;小崖在此把优秀的投稿文章分享给大家~今天分享的用户文章是《使用崖山YMP 迁移 Oracle/MySQL 至YashanDB 23.2 验证测试》&#xff08;作者&#xff1a;尚雷&#xff09;&#xff0c…

提交(git-add git-commit git-push)

当修改好一个功能的时候&#xff0c;可以提交到远程仓库&#xff0c;这样就等于更新了一次版本&#xff0c;那么下次改修了文件的话&#xff0c;是跟这个版本做对比的 git status&#xff0c; 查看文件修改情况&#xff0c;git add 假如你只想提交1个文件&#xff0c;那么直接…

硬件开发——SD/MMC/eMMC

硬件开发——SD/MMC/eMMC 小狼http://blog.csdn.net/xiaolangyangyang 1、引脚定义 2、时钟频率 eMMC的速度模式有&#xff1a; SDR50&#xff1a;26M/52MHS200&#xff1a;200MHS400&#xff1a;400M 详细信息见如下表格&#xff1a; 3、协议 Read Data Write Data No Data …

beego框架_golang web框架_使用介绍

beego简介 beego是一个用于快速开发Go应用的http框架&#xff0c;由Go语言方面的技术大牛设计。beego可以用来快速开发API、Web、后端服务等各种应用&#xff0c;是一个RESTful的框架&#xff0c;主要设计灵感来源于tornado、sinatra、flask这三个框架&#xff0c;但结合了Go本…

Linux 安装多个jdk,切换使用

一、下载jdk 先去oracle官网下载jdk安装文件(我需要下载旧版本的jdk) https://www.oracle.com/ 二、安装jdk 1、将下载的jdk安装文件解压到想要安装的目录下 tar -xzvf jdk-8u202-linux-x64.tar.gz2、修改环境变量 vim /etc/profile export JAVA_HOME/usr/java/jdk1.…

Java代码批量处理sql语句

背景&#xff1a;数据源迁移&#xff0c;目标数据源和原始数据源的语法不同&#xff0c;要把建表语句全都改成新的语法。 一个个sql文件去替换实在是麻烦&#xff0c;可以把原始的sql文件放在一个文件夹&#xff0c;然后用程序一跑&#xff0c;改完语法的sql语句就放在新的文件…

基于Llama Index构建RAG应用

前言 Hello&#xff0c;大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者&#xff0c;本文参与活动是2024 DataWhale AI夏令营&#xff1b;&#x1f632; 在本文中作者将通过&#xff1a; Gradio、Streamlit和LlamaIndex介绍 LlamaIndex 构…

只用 CSS 能玩出什么花样?

在前端开发领域&#xff0c;CSS 不仅仅是一种样式语言&#xff0c;它更像是一位多才多艺的艺术家&#xff0c;能够创造出令人惊叹的视觉效果。本文将带你探索 CSS 的无限可能&#xff0c;从基本形状到动态动画&#xff0c;从几何艺术到仿生设计&#xff0c;只用 CSS 就能玩出令…

MySQL的索引、事务

MySQL的索引 索引的概念 索引是一个排序的列表&#xff0c;在列表当中存储索引的值以及索引值对应数据所在的物理行。 索引值和数据是一一映射的关系。 索引的作用 使用索引之后就不需要扫描全表来定位某行的数据 加快数据库查询的速度。 索引可以是表中的一列也可以是多…

WsgiDAV:强大的 WebDAV 解决方案

一、软件介绍 WsgiDAV 是一款卓越的开源 WebDAV 服务器和客户端库&#xff0c;由 Python 精心编写而成。它为用户搭建了一座通过 HTTP/HTTPS 协议访问和管理远程文件系统的便捷桥梁。 WsgiDAV 的核心是一个严格遵循 WebDAV 标准的中间件&#xff0c;能够与任何 WSGI 兼容的 W…

Python语法大全

#前言&#xff1a; 关于此篇博客主要是关于对Python语法的总结&#xff0c;有过C语言学习经历的人看的会更加通俗易懂&#xff01;&#xff08;当然没学过C的理解起来也是很容易&#xff09;知识点言简意赅&#xff0c;内容全面&#xff0c;多有代码解释&#xff01;且有多处实…

【数据结构】--- 栈和队列

前言 前面学习了数据结构的顺序表、单链表、双向循环链表这些结构&#xff1b;现在就来学习栈和队列&#xff0c;这里可以简单的说栈和队列是具有特殊化的线性表 一、栈 1.1、栈的概念和结构 栈是一种遵循先入后出逻辑的线性数据结构。 栈是一种特殊的线性表&#xff0c;它只允…

表格竖向展示

最近在做手机端web页面&#xff0c;页面中需要有个表格来显示数据&#xff0c;但是由于数据太多页面太窄&#xff0c;table展示横向滑动的话感觉很丑。所以让表格竖向显示了 具体页面如下: 实现代码&#xff1a;当然代码里面绑定的数据啊什么的你都可以修改为自己的内容&#…

PyTorch高级特性与性能优化

PyTorch高级特性与性能优化 引言&#xff1a; 在深度学习项目中&#xff0c;使用正确的工具和优化策略对于实现高效和有效的模型训练至关重要。PyTorch&#xff0c;作为一个流行的深度学习框架&#xff0c;提供了一系列的高级特性和性能优化方法&#xff0c;以帮助开发者充分利…

TDC 5.0:多集群统一纳管,构建一体化大数据云平台

近期&#xff0c;星环科技数据云平台Transwarp Data Cloud&#xff08;简称TDC&#xff09;5.0版本正式发布&#xff0c;TDC5.0架构屏蔽底层多个TDH集群的差异&#xff0c;采用统一操作模式&#xff0c;新增一个多集群抽象与管理层&#xff0c;能够实现多集群网络互通、跨集群资…

驱动框架——CMSIS第一部分 RTE驱动框架介绍

一、介绍CMISIS 什么是CMSIS&#xff08;cortex microcontrol software interface standard一种软件标准接口&#xff09;&#xff0c;官网地址&#xff1a;https://arm-software.github.io/CMSIS_6/latest/General/index.html 包含的core、driver、RTOS、dsp、nn等部分&…

【MySQL】11.使用 C 语言访问 MySQL

使用C语言访问MySQL 一.检查第三方库是否配置成功二.MySQL 常用接口1.创建&#xff0c;销毁操作句柄2.使用句柄连接数据库3.向 mysqld 发送指令4.查询相关函数 三.使用示例 一.检查第三方库是否配置成功 想要使用代码连接数据库&#xff0c;必须使用 MySQL 官方提供的第三方库。…

redis服务器同 redis 集群

搭建redis服务器 修改服务运行参数 常用命令常用命令 创建redis集群 准备做集群的主机&#xff0c;不允许存储数据、不允许设置连接密码 配置服务器&#xff1a; 1、在任意一台redis服务器上都可以执行创建集群的命令。 2、--cluster-replicas 1 给每个master服务器分配1台…

Java之反射和枚举及lambda表达式

1.反射 1 定义 Java 的反射&#xff08; reflflection &#xff09;机制是在 运行 状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的 所有属性和方法 &#xff1b;对于任 意一个对象&#xff0c;都能够调用它的任意方法和属性&#xff0c;既然能拿到那么&…