【ARM Coresight Debug 系列 16 -- Linux 断点 BRK 中断使用详细介绍】
文章目录
- 1.1 ARM BRK 指令
- 1.2 BRK 立即数宏定义介绍
- 1.3 断点异常处理流程
- 1.3.1 el1_sync_handler
- 1.3.2 el1_dbg 跟踪
- 1.4 debug 异常处理函数注册
- 1.4.1 brk 处理函数的注册
1.1 ARM BRK 指令
ARMv8 架构的 BRK
指令是用于生成一个软件断点的。当处理器执行到 BRK
指令时,会触发一个断点异常。
BRK 指令的格式如下:
BRK #<imm>
其中<imm>
是一个16
位的立即数,它可以在断点异常发生时将立即数保存到 ESR.ISS
域中,从可以用来区分不同目的的 BRK
断点指令。
下面是一个简单的例子:
MOV R0, #1
BRK #0x1234
MOV R0, #2
在这个例子中,当处理器执行到BRK #0x1234
这条指令时,并且可以在ESR.ISS
中看到BRK #0x1234
这条指令的立即数0x1234
。
需要注意的是,BRK指令只能在ARMv8及之后的ARM架构中使用。在早期的ARM架构中,生成软件断点通常使用的是SWI或BKPT指令。
1.2 BRK 立即数宏定义介绍
上节内容介绍了 BRK 后面跟的立即数会在断点中断发生时,保存到ESR.ISS
中,那么我们看下linux 中 BRK 后面的立即数宏定义种类有哪些并分别作用是什么?
ARM64中BRK 立即数的定义位于文件 linux/arch/arm64/include/asm/brk-imm.h
中:
/** #imm16 values used for BRK instruction generation* 0x004: for installing kprobes* 0x005: for installing uprobes* 0x006: for kprobe software single-step* Allowed values for kgdb are 0x400 - 0x7ff* 0x100: for triggering a fault on purpose (reserved)* 0x400: for dynamic BRK instruction* 0x401: for compile time BRK instruction* 0x800: kernel-mode BUG() and WARN() traps* 0x9xx: tag-based KASAN trap (allowed values 0x900 - 0x9ff)*/
#define KPROBES_BRK_IMM 0x004
#define UPROBES_BRK_IMM 0x005
#define KPROBES_BRK_SS_IMM 0x006
#define FAULT_BRK_IMM 0x100
#define KGDB_DYN_DBG_BRK_IMM 0x400
#define KGDB_COMPILED_DBG_BRK_IMM 0x401
#define BUG_BRK_IMM 0x800
#define KASAN_BRK_IMM 0x900
#define KASAN_BRK_MASK 0x0ff
-
KPROBES_BRK_IMM
:这是用于 Kprobes 的BRK指令的立即数值。Kprobes是Linux内核中的一个动态追踪工具,它允许你在运行时插入断点到内核代码中; -
UPROBES_BRK_IMM
:这是用于Uprobes的BRK指令的立即数值。Uprobes是Linux内核中的一个动态追踪工具,它允许你在运行时插入断点到用户空间程序中; -
KPROBES_BRK_SS_IMM
:这是用于Kprobes的单步执行模式的BRK指令的立即数值; -
FAULT_BRK_IMM
:这是用于处理页故障的BRK指令的立即数值; -
KGDB_DYN_DBG_BRK_IMM
:这是用于KGDB(内核调试器)的动态调试的BRK指令的立即数值; -
BUG_BRK_IMM
:这是用于BUG_ON宏的BRK指令的立即数值;BUG_ON是Linux内核中的一个宏,用于在满足某个条件时生成一个故障; -
KASAN_BRK_IMM
:这是用于KASAN(内核地址无效访问检测器)的BRK指令的立即数值。
1.3 断点异常处理流程
断点异常属于同步异常,所以我们需要从同步异常开始,ARMv8 的同步异常处理函数位于汇编文件linux/arch/arm64/kernel/entry.S
中,定义如下:
/** EL1 mode handlers.*/.align 6
SYM_CODE_START_LOCAL_NOALIGN(el1_sync)kernel_entry 1mov x0, spbl el1_sync_handlerkernel_exit 1
SYM_CODE_END(el1_sync)
从上面汇编代码可以看到将栈指针的值SP
赋值给X0
,然后跳转到函数el1_sync_handler
中,接下来继续跟踪该函数。
1.3.1 el1_sync_handler
el1_sync_handler
函数的定义位于linux/arch/arm64/kernel/entry-common.c
中:
asmlinkage void noinstr el1_sync_handler(struct pt_regs *regs)
{unsigned long esr = read_sysreg(esr_el1);switch (ESR_ELx_EC(esr)) {case ESR_ELx_EC_DABT_CUR:case ESR_ELx_EC_IABT_CUR:el1_abort(regs, esr);break;/** We don't handle ESR_ELx_EC_SP_ALIGN, since we will have hit a* recursive exception when trying to push the initial pt_regs.*/case ESR_ELx_EC_PC_ALIGN:el1_pc(regs, esr);break;case ESR_ELx_EC_SYS64:case ESR_ELx_EC_UNKNOWN:el1_undef(regs);break;case ESR_ELx_EC_BREAKPT_CUR:case ESR_ELx_EC_SOFTSTP_CUR:case ESR_ELx_EC_WATCHPT_CUR:case ESR_ELx_EC_BRK64:el1_dbg(regs, esr);break;case ESR_ELx_EC_FPAC:el1_fpac(regs, esr);break;default:el1_inv(regs, esr);}
}
首先读取异常状态寄存器 ESR_EL1
的 EC
域 判断当前异常类型,然后根据异常类型跳转到对应的处理函数,本篇文章组要介绍 ARMv8/ARMv9 debug 相关的内容,所先只关注 el1_dbg
这个异常处理函数。
当异常类型为 Breakpoint Instruction exceptions,Breakpoint exceptions,Watchpoint exceptions,Software Step exceptions 四种中的一种时就会跳转执行 el1_dbg
函数。
gcc 编译器在汇编过程中调用c语言函数时传递参数有两种方法:一种是通过堆栈,另一种是通过寄存器。缺省时采用寄存器,假如你要在你的汇编过程中调用 c 语言函数,并且想通过堆栈传递参数,你定义的 c 函数时要在函数前加上宏asmlinkage
- Breakpoint Instruction exceptions: 执行 BRK 指令时触发的异常;
- Breakpoint exceptions: 硬件断点异常,比如配置指令地址到硬件断点对应的寄存器中,当执行到该指令时就会触发硬件断点异常;
- Watchpoint exceptions:观察点异常,主要用来监控变量的,比如,将变量的地址写入到对应的寄存器中,当访问这个变量是就会触发该异常;
- Software Step exceptions:软件单步执行异常。
详细内容可以见 DDI0487_I_a_a-profile_architecture_reference_manual.pdf 中的 D2章节。
1.3.2 el1_dbg 跟踪
上节内容说到 当检查到异常类型为 debug 异常类型时就会执行el1_dbg 函数,该函数的实现如下:
static void noinstr el1_dbg(struct pt_regs *regs, unsigned long esr)
{unsigned long far = read_sysreg(far_el1);arm64_enter_el1_dbg(regs);do_debug_exception(far, esr, regs);arm64_exit_el1_dbg(regs);
}
该函数首先读取 far_el1
寄存器中产生导致异常发生的虚拟地址,然后再将虚拟地址,esr_el1的值,SP栈地址作为参数传给了 do_debug_exception
函数:
834 void do_debug_exception(unsigned long addr_if_watchpoint, unsigned int esr,
835 struct pt_regs *regs)
836 {
837 const struct fault_info *inf = esr_to_debug_fault_info(esr);
838 unsigned long pc = instruction_pointer(regs);
839
842 ...
843 debug_exception_enter(regs);...
848 if (inf->fn(addr_if_watchpoint, esr, regs)) {
849 arm64_notify_die(inf->name, regs,
850 inf->sig, inf->code, (void __user *)pc, esr);
851 }...
854 }
855 NOKPROBE_SYMBOL(do_debug_exception);
这里我们主要关注 837 行和 848行,这两行的作用是根据 ESR.EC
阈值判断当前异常类型,然后调佣该异常类型的处理函数。例如 BRK 软件断点异常的处理函数就是 linux/arch/arm64/kernel/debug-monitors.c
中的函数 brk_handler
。那么 brk_handler
异常的处理函数是如何注册的?
1.4 debug 异常处理函数注册
linux 对于类型相似的问题,比如许多类型相似 debug 异常,处理套路都是先定义一个全局的结构体数组(如 struct fault_info fault_info[]
, struct fault_info debug_fault_info[]
),然后将异常的处理函数,异常类型,异常描述等信息填入结构体数组中:
struct fault_info {int (*fn)(unsigned long addr, unsigned int esr,struct pt_regs *regs);int sig;int code;const char *name;
};/** __refdata because early_brk64 is __init, but the reference to it is* clobbered at arch_initcall time.* See traps.c and debug-monitors.c:debug_traps_init().*/
static struct fault_info __refdata debug_fault_info[] = {{ do_bad, SIGTRAP, TRAP_HWBKPT, "hardware breakpoint" },{ do_bad, SIGTRAP, TRAP_HWBKPT, "hardware single-step" },{ do_bad, SIGTRAP, TRAP_HWBKPT, "hardware watchpoint" },{ do_bad, SIGKILL, SI_KERNEL, "unknown 3" },{ do_bad, SIGTRAP, TRAP_BRKPT, "aarch32 BKPT" },{ do_bad, SIGKILL, SI_KERNEL, "aarch32 vector catch" },{ early_brk64, SIGTRAP, TRAP_BRKPT, "aarch64 BRK" },{ do_bad, SIGKILL, SI_KERNEL, "unknown 7" },
};
在异常发生的时候只要需要索引值,就可以直接调用到对应的异常处理函数。对于数组debug_fault_info[]
索引值的获取是根据 ESR.EC
的值计算来的:
static inline const struct fault_info *esr_to_debug_fault_info(unsigned int esr)
{return debug_fault_info + DBG_ESR_EVT(esr);
}#define DBG_ESR_EVT(x) (((x) >> 27) & 0x7)
宏 DBG_ESR_EVT
中右移27位是因为ESR_EL1
的bit26
开始时EC
域:
debug_fault_info
表中的内容是默认的一些异常的处理函数,对于 debug 异常的处理函数注册还需要在代码中调用 linux/arch/arm64/mm/fault.c
中的注册函数hook_debug_fault_code
来完成:
void __init hook_debug_fault_code(int nr,int (*fn)(unsigned long, unsigned int, struct pt_regs *),int sig, int code, const char *name)
{BUG_ON(nr < 0 || nr >= ARRAY_SIZE(debug_fault_info));debug_fault_info[nr].fn = fn;debug_fault_info[nr].sig = sig;debug_fault_info[nr].code = code;debug_fault_info[nr].name = name;
}
对于 BRK
和单步执行的异常处理函数的注册是在linux/arch/arm64/kernel/debug-monitors.c
中函数 debug_traps_init(void)
中完成的:
void __init debug_traps_init(void)
{hook_debug_fault_code(DBG_ESR_EVT_HWSS, single_step_handler, SIGTRAP,TRAP_TRACE, "single-step handler");hook_debug_fault_code(DBG_ESR_EVT_BRK, brk_handler, SIGTRAP,TRAP_BRKPT, "BRK handler");
}
对于 watchpoint 和 breakpoint 的异常处理函数的注册位于linux/arch/arm64/kernel/hw_breakpoint.c
中的arch_hw_breakpoint_init(void)
函数中:
/** One-time initialisation.*/
static int __init arch_hw_breakpoint_init(void)
{.../* Register debug fault handlers. */hook_debug_fault_code(DBG_ESR_EVT_HWBP, breakpoint_handler, SIGTRAP,TRAP_HWBKPT, "hw-breakpoint handler");hook_debug_fault_code(DBG_ESR_EVT_HWWP, watchpoint_handler, SIGTRAP,TRAP_HWBKPT, "hw-watchpoint handler");...
}
arch_initcall(arch_hw_breakpoint_init);
由于本篇文章主要介绍 BRK
指令异常,所以还需要继续跟踪器异常处理函数 brk_handler
:
326 static int brk_handler(unsigned long unused, unsigned int esr,
327 struct pt_regs *regs)
328 {
329 if (call_break_hook(regs, esr) == DBG_HOOK_HANDLED)
330 return 0;
331
332 if (user_mode(regs)) {
333 send_user_sigtrap(TRAP_BRKPT);
334 } else {
335 pr_warn("Unexpected kernel BRK exception at EL1\n");
336 return -EFAULT;
337 }
338
339 return 0;
340 }
341 NOKPROBE_SYMBOL(brk_handler);
这里我们只关注第329行,它的作用是遍历注册到链表 kernel_break_hook
上的所有node, 比较 node 节点上的的立即数 imm
是否和 异常症状寄存器 ESR.ISS
域中的值是否匹配, 如果匹配成功就会调用它的 handler。
static LIST_HEAD(kernel_break_hook);static int call_break_hook(struct pt_regs *regs, unsigned int esr)
{struct break_hook *hook;struct list_head *list;int (*fn)(struct pt_regs *regs, unsigned int esr) = NULL;list = user_mode(regs) ? &user_break_hook : &kernel_break_hook;/** Since brk exception disables interrupt, this function is* entirely not preemptible, and we can use rcu list safely here.*/list_for_each_entry_rcu(hook, list, node) {unsigned int comment = esr & ESR_ELx_BRK64_ISS_COMMENT_MASK;if ((comment & ~hook->mask) == hook->imm) // 比较 BRK 后面的立即数fn = hook->fn;}return fn ? fn(regs, esr) : DBG_HOOK_ERROR;
}
NOKPROBE_SYMBOL(call_break_hook);
1.4.1 brk 处理函数的注册
上文提到了当 debug 异常发生后,会遍历 kernel_break_hook
上的所有 node,那么我们看下有哪些类型的事件注册到这个链表上呢?
register_kernel_break_hook(&kgdb_brkpt_hook);
register_kernel_break_hook(&kgdb_compiled_brkpt_hook);
register_kernel_break_hook(&kprobes_break_hook);
register_kernel_break_hook(&kprobes_break_ss_hook);
register_kernel_break_hook(&bug_break_hook);
register_kernel_break_hook(&fault_break_hook);
register_kernel_break_hook(&kasan_break_hook);
我们在看下这些 BRK事件对应的处理函数:
static struct break_hook kgdb_brkpt_hook = {.fn = kgdb_brk_fn,.imm = KGDB_DYN_DBG_BRK_IMM,
};
static struct break_hook kgdb_compiled_brkpt_hook = {.fn = kgdb_compiled_brk_fn,.imm = KGDB_COMPILED_DBG_BRK_IMM,
};
static struct break_hook kprobes_break_hook = {.imm = KPROBES_BRK_IMM,.fn = kprobe_breakpoint_handler,
};
static struct break_hook kprobes_break_ss_hook = {.imm = KPROBES_BRK_SS_IMM,.fn = kprobe_breakpoint_ss_handler,
};
static struct break_hook bug_break_hook = {.fn = bug_handler,.imm = BUG_BRK_IMM,
};
static struct break_hook fault_break_hook = {.fn = reserved_fault_handler,.imm = FAULT_BRK_IMM,
};
static struct break_hook kasan_break_hook = {.fn = kasan_handler,.imm = KASAN_BRK_IMM,.mask = KASAN_BRK_MASK,
};
这里挑我们最常用到的处理函数 bug_handler
来介绍:
static int bug_handler(struct pt_regs *regs, unsigned int esr)
{switch (report_bug(regs->pc, regs)) {case BUG_TRAP_TYPE_BUG:die("Oops - BUG", regs, 0);break;case BUG_TRAP_TYPE_WARN:break;default:/* unknown/unrecognised bug trap type */return DBG_HOOK_ERROR;}/* If thread survives, skip over the BUG instruction and continue: */arm64_skip_faulting_instruction(regs, AARCH64_INSN_SIZE);return DBG_HOOK_HANDLED;
}
这里看到了我们经常遇到的 “Oops - BUG” 了。
到目前为止介绍了整个 BRK 点断指令的处理流程与对应的异常处理函数注册流程,那么我们什么时候会用到 BRK 指令呢?
在 linux中最常用的地方也就是 WARN
和 BUG
这两个地方,这里以BUG
为例进行介绍:
#define BUG() do { \__BUG_FLAGS(0); \unreachable(); \
} while (0)
#define __BUG_FLAGS(flags) \asm volatile (__stringify(ASM_BUG_FLAGS(flags)));
#define ASM_BUG() ASM_BUG_FLAGS(0)
#define ASM_BUG_FLAGS(flags) \__BUG_ENTRY(flags) \brk BUG_BRK_IMM
相关文章:

【ARM Coresight Debug 系列 16 -- Linux 断点 BRK 中断使用详细介绍】
文章目录 1.1 ARM BRK 指令1.2 BRK 立即数宏定义介绍1.3 断点异常处理流程1.3.1 el1_sync_handler1.3.2 el1_dbg 跟踪 1.4 debug 异常处理函数注册1.4.1 brk 处理函数的注册 1.1 ARM BRK 指令 ARMv8 架构的 BRK 指令是用于生成一个软件断点的。当处理器执行到 BRK 指令时&…...
Rust星号(*)的作用-基础篇
在Rust中,*符号具有多种不同的用途,具体取决于它的使用方式。以下是Rust中*常见的用法. 1.解引用指针 当作为一元运算符放在指针变量之前时,*用于解引用指针并访问它指向的值。在Rust中,通常更推荐使用引用而不是原始指针。引用…...

企业该如何选择数字化转型工具?
对于希望在当今快速发展的商业环境中增强运营、提高效率并保持竞争力的公司来说,选择正确的数字化转型工具是一项关键决策。以下是选择数字化转型工具的关键步骤和注意事项: 1.定义业务目标: 清楚地阐明您的业务目的和目标。了解您希望通过数…...

element ui 中 el-button重新渲染后disabled属性失效
调试发现:disabled绑定的值和显示没有保持一致,发现是disabled属性失效 解决方式: 给标签添加key 比如:key“isOldVersion” <el-form-item><el-button type"primary" style"margin-left: 100px;" click"…...
WebRTC AIMD算法用处
WebRTC使用AIMD(Additive Increase Multiplicative Decrease)算法来进行码率控制。 在WebRTC中,码率控制的目标是优化音视频传输的质量和稳定性,以适应网络状况的变化。具体而言,AIMD算法通过监测网络的拥塞情况&…...
迁移kubelet、docker和containerd工作目录
文章目录 问题背景迁移Docker停止 Docker 服务修改配置移动文件重新启动 Docker 服务 containerd停止服务修改配置移动文件重新启动服务 kubelet(遇到问题待解决)停止服务修改配置移动文件(遇到问题待解决)重新启动服务 使用的版本…...
Go 重构:尽量避免使用 else、break 和 continue
今天,我想谈谈相当简单的事情。我不会发明什么,但我在生产代码中经常看到这样的事情,所以我不能回避这个话题。 我经常要解开多个复杂的 if else 结构。多余的缩进、过多的逻辑只会加深理解。首先,这篇文章的主要目的是让代码更透…...

Unity3D 程序员常用的核心类及方法详解
Unity3D是一款强大的游戏引擎,广泛应用于游戏开发领域。作为Unity3D程序员,掌握常用的核心类及方法是非常重要的。本文将详细介绍Unity3D中程序员常用的核心类及方法,并给出代码实现。 对惹,这里有一个游戏开发交流小组ÿ…...

76.C++ STL list容器
目录 1.什么是list容器 2.list构造函数 3. 元素插⼊和删除操作 4.大小操作 5.赋值操作 6.数据存取操作 7.反转、排序 1.什么是list容器 list 是 C 标准库提供的双向链表容器。它与 vector 和 deque 不同,不是连续的内存块,而是由节点组成的链表结…...

使用FreeMarker导出word文档(支持循环导出实时多张图片)
续上一期的更新内容 ,导出的是单张图片,直接在路径的src 里面填写对应的占位符,就可以了,随着需求的变化,那么今天我们继续往下写一个循环导出多张图片到word里面。 使用FreeMarker导出word文档(支持导出单张图片) …...
Evaluating Open-Domain Question Answering in the Era of Large Language Models
本文是LLM系列文章,针对《Evaluating Open-Domain Question Answering in the Era of Large Language Models》的翻译。 大语言模型时代的开放域问答评价 摘要1 引言2 相关工作3 开放域QA评估4 评估开放域QA模型的策略5 正确答案的语言分析6 CuratedTREC上的正则表…...

基于安卓Android的掌上酒店预订APP
项目介绍 网络的广泛应用给生活带来了十分的便利。所以把掌上酒店预订与现在网络相结合,利用java技术建设掌上酒店预订APP,实现掌上酒店预订的信息化。则对于进一步提高掌上酒店预订发展,丰富掌上酒店预订经验能起到不少的促进作用。 掌上酒…...
搭建CNFS文件系统
1.概念: CNFS (Cluster Network File System)是 GPFS 中的一种模式,用于配置和管理多台服务器(节点)之间的文件共享和数据访问 它允许多个节点同时访问和共享文件系统的数据,以实现高性能、高可…...

网络工程师知识点7
111、IS-IS路由器的三种类型? Level-1路由器(只能创建level-1的LSDB) Level-2路由器(只能创建level-2的LSDB) Level-1-2路由器(路由器默认的类型,能同时创建level-1和level-2的LSDB)…...
C++ 八股文:类析构
继承层次中,为什么基类析构函数是虚函数? 在继承层次中,将基类的析构函数声明为虚函数的主要原因是为了支持多态和安全的资源释放。以下是为什么基类的析构函数通常应该是虚函数的原因: 多态析构: 当使用基类指针&a…...

第三章 内存管理 八、两级页表
目录 一、定义 二、如何实现地址变换 三、注意 四、总结 一、定义 二级页表是一种分层的虚拟内存管理机制。在二级页表中,虚拟地址被分成两个层次,第一层是页目录,第二层是页表。通过这种方式,二级页表可以管理更大的虚拟内存…...

新时代高效记账:自动化智能如何进行财务管理
随着科技的不断发展,自动化智能已经逐渐渗透到我们生活的各个领域。在财务管理中,自动化智能的应用显得尤为重要。它不仅可以提高财务管理的效率和精度,还能帮助我们更好地规划和掌控公司的财务状况 晨曦记账本提供了多种高效财务管理工具。…...

Linux小程序---进度条
一:\r 和 \n \r --- 回车 --- 使光标回到这一行的开头 \n --- 换行 --- 会来到下一行与之平行的位置 缓冲区的问题: <1>: \n 的示例 正常输出 hehehehe 。 <2>: \r 的示例 为了方便观察,加入一个 sleep (休眠函数…...

【Java笔试强训】Day1(100449-组队竞赛 、OR63 删除公共字符)
100449-组队竞赛 链接:组队竞赛 题目: 牛牛举办了一次编程比赛,参加比赛的有3*n个选手,每个选手都有一个水平值a_i.现在要将这些选手进行组队,一共组成n个队伍,即每个队伍3人.牛牛发现队伍的水平值等于该队伍队员中第二高水平值。 例如: 一个队伍三个…...

C语言进行实验:通过程序实现线算图取值【支持VC++ 6.0编辑器环境运行】
背景: 一、实验目的和要求 1、能描述数据基本类型及其常量的表示方法; 2、会对变量进行定义及初始化; 3、能使用运算符与表达式对变量赋值; 4、会描述C语句的概念及种类、C语言常用的输入/出方式; 5、会设计顺序…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...

图解JavaScript原型:原型链及其分析 | JavaScript图解
忽略该图的细节(如内存地址值没有用二进制) 以下是对该图进一步的理解和总结 1. JS 对象概念的辨析 对象是什么:保存在堆中一块区域,同时在栈中有一块区域保存其在堆中的地址(也就是我们通常说的该变量指向谁&…...