新闻详情

新闻详情

首页 / 资讯中心 / 详情

Keil MDK 变量与函数固定地址实战:从属性到分散加载文件

发布时间:2026/10/1 13:52:46来源:尧图网络
Keil MDK 变量与函数固定地址实战:从属性到分散加载文件
搞嵌入式的迟早有一天会碰到这么一个问题。手里的传感器校准参数要写在 Flash 末尾固定地址断电不能丢App 升级还得躲开它Bootloader 要跟 App 通信必须在 RAM 里留一块固定地址的共享区更常见的是明明定义了全局变量调试的时候却怎么都找不到不是被优化器干掉就是被链接器丢掉了。这些问题本质上都指向同一个主题在 Keil MDK 里学会控制变量和函数的属性并把变量、函数甚至整个段放到我们自己指定的地址上。这篇文章不聊 Keil 怎么安装也不扯玄学就是把编译器、链接器和分散加载文件这几个层面的东西讲透。适合正在做 Bootloader、OTA、掉电保存参数或者被 AC5 切 AC6 之后地址布局乱掉折腾过的人看。我会尽量把变量地址定位、段地址控制、函数属性这些知识点掰开揉碎后面还带一个完整的“校准参数固定到 Flash 末尾”的例子可以直接照着改。1. 为什么非要把变量和函数钉在指定地址1.1 变量固定地址背后是真需求很多人第一次接触“指定地址”是想给片外 SRAM、外部设备寄存器或者双核共享内存定义一个变量。比如两个主控之间用普通 GPIO 模拟总线太慢干脆把一段 RAM 拿来做共享邮箱A 芯片往 0x20001000 写一包数据B 芯片从同一个地址读这时地址就必须定死。如果你只写一个普通全局变量链接器今天把变量放在 0x20000100明天改了代码又挪到 0x20000420两边固件永远对不上通信自然没法做。另一个高频场景是掉电保存数据。很多 MCU 有一小段电池后备 RAM或者常规 RAM 在软件复位后不清零但你要是没告诉启动代码“这块区域不要动”复位后里面的数据就被清掉了。说得直白点这不是变量定义的问题是“这个变量放在哪个段、段放在哪块内存、启动时怎么处理”的问题三者缺一不可。还有寄存器映射。Cortex-M 访问片上外设本质就是读写某一段固定地址空间。最常见的写法是宏加指针强转#define REG_CTRL (*(volatile uint32_t *)0x40021000)但有些人也喜欢用变量定位来访问外设volatile uint32_t reg_ctrl __attribute__((at(0x40021000)));这两种方式都能达到目的区别在于宏更轻变量定位更直观尤其在调试时可以直接把变量拖进 Watch 窗口看。需要注意的是变量定位会被编译器当成普通全局变量处理如果加了初值必定会占用 Flash 存放初值启动时再复制到对应地址对寄存器来说这往往是多余的所以要么不初始化要么用volatile防止编译器产生意外拷贝。1.2 函数固定地址和属性也没那么简单函数需要固定地址的场景通常比变量更隐蔽。最典型的是 Bootloader 和 App 分区。比如你把 Bootloader 放在 0x08000000 到 0x08003FFFApp 从 0x08004000 开始这本身是通过分散加载文件完成的但如果你希望 App 把某个关键函数放在 0x08010000供 Bootloader 直接调用或者希望两个固件通过一张固定地址的函数表来交互那就要对函数本身做段定位。还有一类情况是函数被优化掉了。链接器的默认行为是只要没有任何地方引用这个函数它就不会被放到最终镜像里。你辛苦写了一段用于测试的裸函数结果编译完连地址都找不到原因就是它没被“used”标记保留下来。另外在调试阶段如果某个函数内部变量全是optimized out你也可能想单独把它的优化等级降下来这就涉及函数级优化属性。讲到这你大概能看出函数属性不只是“地址控制”还包含“是否保留”“是否内联”“是否弱符号”“是否特殊中断属性”等。这跟变量属性是一套逻辑只是作用对象不同。1.3 地址由链接器决定属性是写给编译器和链接器的“合同”我见过不少新人以为在源码里写了一句__attribute__((at(0x20000000)))地址就真的固定在 0x20000000 了。其实这句话只是给编译器一个指令让这个符号被安排到一个特殊的段里真正的“地址裁决权”在链接器手上。链接器拿到所有目标文件后会按照链接脚本在 MDK 里叫 Scatter File也就是分散加载文件把各个段分配进对应的内存区域。所以你可以这样理解编译器负责把变量和函数归类、贴标签比如这是只读数据、这是可读写数据、这是代码链接器负责把带标签的内容安放到内存里。我们写属性相当于在编译阶段给某个变量或函数偷偷贴上一张写着“注意我要去专门的地方”的小纸条而段地址能不能生效最终取决于分散加载文件里有没有给这个段留位置。这也解释了为什么实际项目里大家更推荐“自定义 section 分散加载文件”而不是一股脑用__attribute__((at()))。前者把控制权真正握在手里后者是用编译器提供的便捷接口去“暗示”链接器ersion两者在简单场景下都可以但工程一复杂界线就出来了。2. 变量和函数属性先看懂这些最常用的关键字2.1 一张表看清常用属性Keil MDK 里常见的属性关键字说多不多说少不少我把日常最常用的列出来你在工程里见到也不会慌。属性/关键字作用对象作用说明__attribute__((at(addr)))变量、常量把符号定位到绝对地址__attribute__((section(name)))变量、常量、函数把符号放进指定名称的段__attribute__((used))变量、函数强制保留符号防止被链接器丢弃__attribute__((unused))变量、函数告诉编译器“我没用它别报警告”__attribute__((noinline))函数禁止函数被内联保证函数入口地址稳定__attribute__((weak))变量、函数声明弱符号允许被其他强符号覆盖__attribute__((aligned(n)))变量、类型指定对齐字节数__attribute__((packed))结构体取消结构体成员自动对齐紧凑排列__attribute__((zero_init))变量告诉编译器这是一个 ZI 段变量不生成初值数据__attribute__((noreturn))函数告诉编译器函数不会返回优化循环/中断__attribute__((interrupt(IRQ)))函数AC6 下声明中断函数__irq函数AC5 下的中断函数关键字这里特别提醒一下zero_init这个名字很容易误导人。它真正的意思是“这个变量属于 ZI 段”也就是启动代码不会往 Flash 里放初始化数据而是靠启动阶段“清零”来准备内存。如果这个变量所在的执行区被标记为 UNINIT那么启动代码连清零都会跳过数据才能做到真正的掉电保持。后面第 4 章会细讲。2.2 AC5 和 AC6同一个关键字两套脾气Keil MDK 默认的编译器从 armccAC5逐渐过渡到 armclangAC6之后很多人遇到的第一个坑就是“同样代码在 AC5 编译好好的切到 AC6 就报错或者变了味道”。这不是优化等级的问题是两套编译器对属性的支持程度不一样。举个例子#pragma arm section在 AC5 里可以很自然地给后续变量指定段但在 AC6 里基本是无效的。另一个例子是中断函数AC5 用__irq很常见AC6 更推荐__attribute__((interrupt(IRQ)))。如果老工程从 AC5 升到 AC6最好把编译输出窗口的所有 warning 都翻一遍很多地址相关函数就是在这时候悄悄失效的。我给你的建议是新工程直接用__attribute__((section()))这种标准写法不要在 AC6 下依赖#pragma arm section。对于__attribute__((at()))AC5 支持AC6 也有兼容处理但从可维护性角度还是优先用 section 分散加载文件的方式。如果你必须维护 AC5 老工程暂时保留#pragma arm section也说得过去但要意识到它没法平滑迁移。3. 指定变量地址的三种落地方法3.1 用attribute((at())) 快速定位变量最直接的写法是这样的volatile uint32_t boot_flag __attribute__((at(0x20004000)));这句代码的意思是把boot_flag这个变量放进一个特殊段段名由编译器自动生成最终链接到 0x20004000。编译后你可以在 map 文件里看到它也能在调试模式 Memory 窗口直接看 0x20004000 的内容。用这种方式有几个点要注意。第一一定要加volatile尤其是这个变量会被中断、DMA 或者其他核心修改的时候不加 volatile 可能出现读取到的值被编译器缓存导致调试和实际行为对不上。第二at指定的地址不能与别的变量重叠否则链接器会报 AddressSizingError 或者 overlapping 之类的错误它不会智能避开因为这就是你强行占的地盘。第三如果变量定义在函数内部最好加static不然它的作用域和生命周期会让人一脸懵。第四绝对地址不等于“自然对齐”如果你写__attribute__((at(0x20000001)))这个地址就是非对齐的对 Cortex-M 来说容易出现 HardFault所以尽量用 4 字节对齐地址必要时加__attribute__((aligned(4)))。另外补充一个小技巧。如果你只是想读一个寄存器的值很多时候根本不需要定义变量直接写宏更清爽#define REG_STATUS (*(volatile uint32_t *)0x40021000)这个宏定义其实是“读地址 0x40021000 的值”逻辑上比定义一个全局变量更轻也避免了链接器参与。具体用宏还是用变量看个人习惯但不要混用太多。3.2 用自定义 section 配合分散加载文件控制变量段at简单但不够灵活。一旦你需要把 4KB、16KB 的连续内存整体放到指定区域或者要让启动代码不清零这块区域at就不好使了。这时候推荐用自定义 section。先给变量贴段标签uint8_t shared_pool[4096] __attribute__((section(.shared_pool), zero_init));这一步只是告诉编译器把shared_pool放到名为.shared_pool的段里具体放到哪个内存地址由分散加载文件决定。如果你不加后面的 scatter 配置链接器会按照默认的 RW/ZI 规则把它分配到普通 RAM 区域地址就不是你预期的了。所以 custom section 就像点名册上先写上名字真正“分座位”的是链接器。这种做法的优势是可以整块规划一段内存、可以配合UNINIT实现掉电保持、可以在链接阶段精确控制多个模块的布局比at这种“一人一座”要强壮得多。第 4 章我会给出具体的 scatter 写法。3.3 AC5 老工程的 #pragma arm section 写法如果你手里有早期用 armcc 写的工程可能会看见这种写法#pragma arm section rwdata ext_ram, zidata ext_ram uint16_t adc_buf[256]; uint8_t status_flags; #pragma arm section rwdata 0, zidata 0它的意思是从这条 pragma 开始后续定义的带初值和清零变量的变量都归入名为ext_ram的段直到恢复成默认段。麻烦在于这种写法是 AC5 特有的。AC6 下这个 pragma 基本不生效但编译器也不会直接报错导致你以为是自定义段实际上变量又回到了默认 RAM 区。这类“静默失效”比报错还难受排查半天才发现是编译器版本的问题。所以我强烈建议老代码迁移到 AC6 时把#pragma arm section批量替换成变量上的__attribute__((section()))写法。3.4 三种方式怎么选简单总结一下选型思路。临时调试、就几个变量要放到指定地址直接用at最快也最直观。正式产品里涉及 Flash 分区、内存布局、Bootloader 通信区老老实实走自定义 section 加分散加载文件。老工程还在用 armcc并且暂时不打算升级编译器#pragma arm section可以继续用但也要做好未来迁移的准备。核心原则是不要用“变量定义时的写法”去替代“链接时期的全局布局”。地址控制本质是全局规划不是给某个变量开小灶。4. 分散加载文件段地址控制的真正主角4.1 先搞懂 RO、RW、ZI 和段的关系编译完一个 C 文件目标文件里其实不是一块光秃秃的二进制而是分好类的“乐高积木”。代码和只读常量属于 RORead-Only带初值的全局变量属于 RWRead-Write没初值或者初始化为 0 的变量属于 ZIZero-Init。启动代码的职责之一就是把 RW 段从 Flash 复制到 RAM然后把 ZI 段清零。我们自定义一个 section等于额外给一部分积木贴了特殊标签。如果不告诉链接器这个标签放在哪里链接器只会根据默认规则尽量塞进 RO、RW 或 ZI 里去如果告诉它了链接器就会把这块积木放到一个独立的“房间”也就是执行区域Execution Region。所以理解分散加载文件之前先要把“段”的概念装进脑子符号属于段段根据属性进入不同执行区域执行区域再落在具体的内存地址上。搞懂这句话80% 的地址问题都能解决。4.2 scatter 文件长什么样怎么在 MDK 里开启MDK 默认是让链接器根据 Target 对话框里填的 ROM/RAM 地址自动生成散列文件但这样你就看不到细节。要手动控制需要打开散列文件模式。看一下典型的 scatter 文件结构LR_IROM1 0x08000000 0x00080000 { ER_IROM1 0x08000000 0x0007F000 { startup_stm32f103xe.o (RESET, .vectors) * (RO) } RW_IRAM1 0x20000000 0x00010000 { * (RW ZI) } }这里LR_IROM1是加载区域描述整个镜像在 Flash 里的分布ER_IROM1是执行区域描述运行时这段内存/Flash 的布局。对大多数单片机来说代码在 Flash 里原地执行加载区域和执行区域地址相同。ER_IROM1里第一行是启动文件和向量表* (RO)表示剩下的只读代码和常量都放进这个区RW_IRAM1则用来放所有数据和零初始化变量。在 MDK 里开启方式很简单打开 Options for Target切到 Linker 页把原来的 “Use Memory Layout from Target Dialog” 勾掉然后勾选或手动填写 Scatter File 路径。第一次做这件事之前我建议你在勾掉之前先编译一次到工程目录的 Listings 文件夹里找到自动生成的.sct文件把它当成学习的起点再复制出来改成自己的版本。4.3 把自定义段放进指定 RAM 和 Flash 地址假设你定义了一个变量段.shared_pool希望它固定落在 0x20007000并且复位不清零。scatter 文件里可以单独加一个执行区域RW_SHARED 0x20007000 UNINIT 0x00001000 { shared.o (.shared_pool) }关键点是UNINIT关键字。它告诉链接器这块区域启动的时候不需要清 ZI也不需要从 Flash 复制初值。配合源码里的zero_init数据才能在复位后被保留下来。如果是 Flash 上的只读常量比如校准表可以这样安排ER_CALI 0x0807F000 0x00001000 { cali.o (.cali_section) }这一步等于是说把cali.o里所有放在.cali_section段的内容强制锁定到 0x0807F000 这一段 Flash。只要你的const变量写了__attribute__((section(.cali_section)))链接后它就会出现在这个地址。注意Flash 区域通常要按扇区对齐不同芯片扇区大小不一样别随手一填就完事。4.4 链接器放在哪个区* 和 .ANY 的意义很多第一次改 scatter 文件的人会被*和.ANY搞晕。*是一个通配符*(RW ZI)表示把“所有目标文件中的所有 RW/ZI 段”都放进这个执行区域。.ANY是另一种选择器它主要用于多个执行区域同时匹配时的分配链接器会优先使用普通选择器再用.ANY去填补剩下的位置。如果你写完ER_CALI 0x0807F000 0x1000 { cali.o (.cali_section) }发现链接器报 L6314W 之类的 warning说没有匹配到任何内容那十有八九是变量定义里的段名和 scatter 文件里写的段名不一致或者目标文件名写错了。这种情况下变量不会消失而是按默认规则被塞进其他 region你在 map 文件里找它往往能发现它跑到了完全不同的地址。5. 函数属性与函数地址控制的实践细节5.1 和函数相关的常用属性函数常用的属性组合大概是这样的__attribute__((section(.app_func), used, noinline)) void sys_jump(void) { // 关键跳转代码 }section决定函数存放在哪个段used告诉链接器不要因为“没人调用”就把它丢掉noinline保证函数的机器码不会内联到别的函数里入口地址稳定调试断点和函数指针调用都更可靠。weak属性也值得提一下。它是用来定义“默认实现”的。比如库里给某个回调函数提供了弱定义你可以在自己的文件里再写一个同名强定义链接器会自动用你的强定义替换库里的弱定义。这在驱动移植和提供默认回调时非常实用写法是__attribute__((weak)) void HAL_DefaultHandler(void) { while (1); }5.2 固定函数入口地址的两种方案方案一把函数直接放进某个固定地址的段。代码写法已经在上面给了只要在 scatter 文件里安排一个独立执行区比如把boot.o里的.app_func锁定到 0x08010000函数启动地址就算被“钉”住了。这个方法对单函数比较简单但函数若依赖其他外部符号链接器不会把依赖关系一起搬过来所以只适合那种完全自包含的“跳转”“复位”“校准”类短函数。方案二更通用的是固定一张函数表。把一组函数指针放进一个自定义段再把这个段定到固定地址typedef struct { uint32_t magic; void (*init)(void); void (*run)(void); } app_api_t; __attribute__((section(.app_api), used)) const app_api_t app_api { 0xA5A5A5A5, app_init, app_run };然后 Bootloader 里直接通过地址访问这张结构体app_api_t *api (app_api_t *)0x0807F800; if (api-magic 0xA5A5A5A5) { api-run(); }函数表的好处是具体函数体内部怎么优化、地址怎么变都无所谓只要表的地址固定即可。这是实际工程里更稳的做法比追求“某个函数地址必须怎样”要优雅得多。5.3 中断函数与优化等级的单独控制中断函数在 AC5 里用__irqAC6 里更推荐__attribute__((interrupt(IRQ)))。对 Cortex-M 而言只要函数名和启动文件里的向量表名字一致编译器会自动处理中断上下文不一定非要加属性。但你如果写的不是标准中断向量名而是想手动声明一个中断入口属性就很有用了。比中断更常被问到的是“怎么让某个函数优化等级低一点方便调试”。AC5 里可以在函数前后加#pragma O0 void debug_func(void) { int x 3; x 4; } #pragma O2这样就只有这个函数是 O0其他区域依旧按全局优化等级编译。AC6 下的对应思路是用__attribute__((optnone))或者直接在函数上指定优化属性不过这个特性在不同编译器版本里表现不完全一致所以调试时我也经常直接把整个工程临时降到 O0定位完再改回来。6. 实战把校准参数固定到 Flash 末尾的指定段6.1 需求与整体设计拿一个很常见的场景来收尾前五章的思路设备出厂前需要对传感器做校准校准结果 12 字节要断电保存并且将来 App 升级的时候绝对不能把这块参数擦掉。假设用的 MCU 是 256KB Flash地址范围 0x08000000 到 0x0807FFFF那么最后 4KB 就是 0x0807F000 到 0x0807FFFF。我们就把校准参数固定放在这里。设计上要做的动作有三个第一定义带__attribute__((section(.cali_section)))的结构体常量第二在 scatter 文件里为这个段单独划一个 Flash 执行区第三编译后确认链接地址并在代码里通过符号或地址访问。这只是“定位”真正擦写 Flash 时还需要走 Flash 驱动这里不展开。6.2 落地步骤第一步定义结构体和常量。如果厂家提供默认参数可以写成带初值的const结构体typedef struct { uint32_t magic; int16_t offset_x; int16_t offset_y; uint16_t year; uint32_t crc; } cali_param_t; __attribute__((section(.cali_section), used)) const cali_param_t cali_params { 0xC0FFEE11, 12, -5, 2025, 0 };used是为了避免链接器因为当前代码还没引用它而把它优化掉。后面真正读取时直接访问cali_params符号即可。第二步修改 scatter 文件。把主代码区放在 0x08000000 开始的地方但提前结束在 0x0807F000 前最后 4KB 单独留给参数区LR_IROM1 0x08000000 0x00080000 { ER_IROM1 0x08000000 0x0007F000 { startup_stm32f103xe.o (RESET, .vectors) * (RO) } ER_CALI 0x0807F000 0x00001000 { cali.o (.cali_section) } RW_IRAM1 0x20000000 0x00010000 { * (RW ZI) } }注意这里的ER_IROM1大小是 0x0007F000代码和只读数据只能用到 0x0807EFFF再大就会报 region size 超限。如果你的固件代码已经很大那就得把参数区放到更往后的位置或者换更大 Flash 的芯片这都取决于项目。第三步编译后打开 map 文件搜索cali_params确认它的地址确实落在 0x0807F000。这一步一定要做不能用“我以为”代替事实。调试模式下也可以在 Memory 窗口输入 0x0807F000看到结构体的原始字节数。第四步业务代码里直接引用这个符号。如果将来要修改参数按 Flash 页擦除再写入写入前应该先把整页内容备份到 RAM修改后再整页写回。同时擦写范围要控制在参数区内绝对不能越界到代码区。6.3 验证与注意验证这件事千万别只靠肉眼。我建议加一层 CRC 校验。出厂时算好整张参数表的 CRC 写入结构体运行时先校验校验不通过再决定是用默认参数还是进入“参数丢失”告警状态。这样即使 Flash 被意外擦成 0xFF程序也不至于拿着垃圾参数跑飞。还有两个容易踩的坑。第一量产后如果有人用调试器全片擦除校准参数一样会没这属于生产流程问题不是在代码里加属性就能解决的。第二芯片 Flash 扇区大小不一定是 4KB比如某些型号最后 4KB 页和别的页尺寸不同代码里计算地址时不要写死“偏移多少字节”尽量根据芯片手册按扇区边界计算。切记链接器做的是“把段放到你指定的地址”它不会替你做硬件层面的扇区对齐校验这些得由写代码的人自己负责。7. 常见问题与排查实录7.1 链接报错到底在说什么分散加载和地址控制最常见的报错就那么几种列个速查表报错关键词含义最常见的解决办法L6220E某个 Region 大小超限把该 Region 的 size 调大或把部分内容移到别的区L6282E绝对地址段重叠地址被占用换个地址或减小其他区占用L6314Wscatter 文件里的段没有匹配到任何内容检查段名、目标文件名是否写错L6907Escatter 文件地址空间重叠检查多个 exec region 的地址和长度是否打架遇到这些报错第一步永远是看完整输出不要只看最下面那一行。链接器一般会提示具体是哪个 Region、哪个 Object 出的问题。比如 L6314W 出现时它会把匹配不到的 section 名字打印出来这时候对比源码里__attribute__((section(.xxx)))和 scatter 文件里写的.xxx是否一字不差通常就能定位。7.2 地址不对或变量消失怎么办变量“不见了”先检查它有没有被used保留。如果没被任何代码引用编译器或链接器完全可以把它从镜像里删掉这在 Keil 里非常常见。其次是查 scatter 文件确认这个符号所在的段确实被某个 exec region 匹配到而不是被规则默认扔到了别的地址。然后打开 map 文件。map 文件里会列出所有全局符号和段分布搜变量名看地址是否符合预期。如果地址和我预期不一样再回头看变量定义有没有被宏、条件编译切掉。很多你以为“没生效”的属性其实是代码根本没走到那个定义分支。还有一点容易被忽略在 AC6 下某些 AC5 的编译参数会被自动忽略但不会报错。比如--no_autoinline、--no_remove这些选项在 AC5 和 AC6 里行为不同若工程是从老版本升级上来的最好对照 MDK 的编译器迁移文档检查一遍编译选项。7.3 调试结构体变量与内存查看调试模式下想看到定义在固定地址的结构体操作不复杂。打开 Watch 窗口添加变量名比如cali_params如果编译器没有把它优化掉View 里会直接展开显示各成员。加cali_params可以看到这个结构体的起止地址用于核对是否落在预期位置。如果 Watch 窗口显示cannot evaluate或optimized out说明变量访问路径被优化了。最粗暴的办法是临时把这个变量定义成volatile或者把读它的函数加#pragma O0后重新编译。要是只想看你内存里到底存了什么东西更直接的方法是打开 Memory 窗口输入地址如0x0807F000选字节格式就能看到原始字节流这个方法在排问题的时候比 Watch 更直观因为没有 C 语言类型的“包装”。7.4 汇总避坑清单最后整理一条实操避坑清单这些基本都是从真实项目里踩出来的绝对地址不要随便越过芯片地址边界特别是 RAM 和 Flash 容量要心里有数。先看分散加载文件再改源码。改源码段名而不改 scatter等于改了宿舍门牌但没告诉管理员结果就是睡楼道。AC6 工程不要再依赖#pragma arm section迁移时统一改成__attribute__((section()))。掉电保存的数据区scatter 里必须标UNINIT源码里配合zero_init才能真正不清零。Flash 参数区按扇区对齐别把 12 字节参数硬塞进 2KB 页中间容易把相邻固件代码擦掉。提交工程到 Git 时记得把.sct文件一起提交不然别人拉下来本地地址布局全乱。给参数区加魔数和 CRC否则 Flash 被意外擦除后你根本不知道数据是坏的。最后再分享一点个人习惯。我现在接手带 Bootloader 的项目第一件事就是打开分散加载文件把内存和 Flash 分区图画出来再回头看业务代码。不要等问题出现才去补地址控制。Keil 的地址布局其实就两层符号进段段进区。把这一句话吃透80% 的地址问题都能自己解决。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Oracle 查看指标 calls (User Calls Per Sec) 与 execs (Executions Per Sec):用 TaoToken 统一 Key 打通 AWR 与 v$sys 2026/10/1 14:37:39

Oracle 查看指标 calls (User Calls Per Sec) 与 execs (Executions Per Sec):用 TaoToken 统一 Key 打通 AWR 与 v$sys

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
【VS Code】Windows10下VS Code配置LaTeX语言环境:settings.json 与 TaoToken 统一 Key 接入 2026/10/1 14:37:39

【VS Code】Windows10下VS Code配置LaTeX语言环境:settings.json 与 TaoToken 统一 Key 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
12项性能位居第一,中国最接近GPT-4的大模型来了!TaoToken 统一 Key 接入 InternLM-123B 实战 2026/10/1 14:37:39

12项性能位居第一,中国最接近GPT-4的大模型来了!TaoToken 统一 Key 接入 InternLM-123B 实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
米米商聊动态可见范围拆解:受众规则、置顶与访问校验 2026/10/1 14:37:39

米米商聊动态可见范围拆解:受众规则、置顶与访问校验

一条动态设为“公开”,是否意味着陌生人也能看到?把一条私密动态置顶,是否会让原本没有权限的人看到? 这些问题涉及两个不同维度:谁可以查看,以及有权限的人看到怎样的展示顺序。本文以米米商聊的动态可见…

阅读更多 →
AI编程实践指南:用TaoToken统一API通道打通自动化代码生成与低代码工作流 2026/10/1 14:37:39

AI编程实践指南:用TaoToken统一API通道打通自动化代码生成与低代码工作流

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
使用 MCP 自定义编写 MCP Tool:conda 启动 + Cline 配置全流程 2026/10/1 14:37:32

使用 MCP 自定义编写 MCP Tool:conda 启动 + Cline 配置全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉