Linux错误(2)程序触发SIGBUS信号分析

news2025/3/18 7:18:58

Linux错误(2)之SIGBUS错误分析


Author: Once Day Date: 2025年3月12日

一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…

漫漫长路,有人对你微笑过嘛…

全系列文章可参考专栏: Linux实践记录_Once_day的博客-CSDN博客

参考文章:

  • libunwind] Unwind through aarch64/Linux sigreturn frame (llvm.org)
  • Aarch64 crash (SIGBUS) due to atomic instructions on under-aligned memory (gnu.org)
  • Arm A64 Instruction Set Architecture
  • JVM coredump分析系列(4):常见的SIGBUS案例分析 | HeapDump性能社区
  • Logging and debugging unaligned accesses on Linux / aarch64 - Stack Overflow
  • singal 7 SIGBUS(Bus error)_program received signal sigbus, bus error. memset
    -CSDN博客
  • Bus error的调试解决方法-CSDN博客
  • 段错误(SIGSEGV)与总线错误(SIGBUS)-CSDN博客
  • SIGSEGV 和 SIGBUS & gdb看汇编 - blcblc - 博客园 (cnblogs.com)
  • c - What is a bus error? Is it different from a segmentation fault? - Stack Overflow
  • X86 Linux 下 SIGBUS 总结 - twoon - 博客园 (cnblogs.com)
  • 关于SIGBUS 信号_received signal sigbus, bus error-CSDN博客
  • access_mem in aaarch64 coredump · Issue #260 · libunwind/libunwind · GitHub
  • libunwind(3) (nongnu.org)

文章目录

  • Linux错误(2)之SIGBUS错误分析
        • 1. 问题分析
          • 1.1【现象介绍】
          • 1.2【分析原因】
          • 1.3【解决思路】
        • 2. 实例验证
          • 2.1 对齐问题触发SIGBUS信号
          • 2.2 映射内存大小改变触发SIGBUS信号
        • 3. 总结

1. 问题分析
1.1【现象介绍】

SIGBUS是BUS error的缩写,中文称为总线错误信号。它是Unix/Linux系统中的一种异常信号,通常在访问内存时发生某些类型的错误时产生。产生SIGBUS的典型原因包括:

  • 地址对齐错误(Alignment Fault):当访问的内存地址不满足硬件的对齐要求时触发。例如在需要4字节对齐的系统上访问一个地址不是4的倍数的int变量。
  • 未映射的物理地址访问:试图访问未映射到任何设备的物理地址空间时会触发SIGBUS。这可能是由错误的指针运算或直接访问物理内存导致的。
  • 特定于设备的硬件错误:一些硬件相关的错误,如访问未初始化的内存控制器、总线奇偶校验错误等也会触发SIGBUS。

SIGBUS在不同的硬件架构和字长下会有一些差异:

  • X86架构:在x86上,SIGBUS主要出现在对齐错误的情况下。x86 CPU允许未对齐的内存访问,但通常会有性能损失。只有使用了SIMD指令(如SSE)时要求更严格的对齐,否则会产生SIGBUS。
  • ARM架构:相比x86,ARM CPU通常要求更严格的内存对齐,未对齐访问会直接触发SIGBUS而不只是性能问题。尤其是在ARM64上,对齐要求更高。
  • 大/小端序:在一些场景下,访问不同字节序的数据也可能触发SIGBUS。但这个问题通常在编译器层面处理。

SIGBUS和SIGSEGV(Segmentation Fault,段错误)信号有一些相似之处,它们都和非法内存访问有关,有时会被混淆。它们主要区别在于:

  • 产生原因:SIGSEGV由访问未映射或者权限不足的虚拟内存地址触发,而SIGBUS则主要由访问映射的物理内存但其他硬件相关错误触发。

  • 处理难度:SIGSEGV相对容易判断和修复,通常检查程序的地址映射即可。而SIGBUS可能和具体硬件相关,排查难度大一些。

  • 可恢复性:SIGSEGV异常通过修改地址映射表可以在一定程度上恢复执行,而SIGBUS异常通常不可恢复,因为可能对应着物理硬件错误。

需要注意SIGBUS更多的和硬件体系结构相关,不同系统和编译器下可能有细微差异。在编写底层程序时,彻底了解目标平台的内存模型和对齐要求是很有必要的。

现代CPU一般不会因为地址非对齐出现SIGBUS错误,更常见原因是映射的内存地址出现了问题,如SO库被更新、共享内存大小变化、映射的页面属性异常等。

1.2【分析原因】

SIGBUS触发场景常见有如下两种:

(1)文件映射访问异常:进程通过mmap系统调用将文件映射到内存中,建立文件和内存页面之间的映射关系。这种方式可以实现高效的文件IO,避免了繁琐的read/write系统调用。然而,这种映射是基于文件大小的,如果文件大小发生变化,已经映射的内存页面可能会出现问题。

如果进程A对文件进行了mmap,而此时另一个进程B对该文件进行了truncate操作,将文件截断到更小的大小。这时,进程A中已经映射的超出文件实际大小的那部分内存页就处于一种不一致的状态。当进程A试图访问这些无效的内存页时,就会触发SIGBUS信号。

另一个常见场景是,某个进程直接用一个新文件覆盖了旧的动态库或可执行文件。由于系统出于性能考虑,通常采用copy-on-write的策略,并不会立即使已经加载的旧版本失效。只有当进程真正执行到新文件中不存在的部分(例如新版本删减了一些代码)时,才会触发SIGBUS。这种场景在升级更新软件时容易遇到。

(2)访问不对齐的内存:现代处理器对内存的访问通常要求地址按照特定的字节数对齐,例如32位的整数要求地址是4的倍数,64位的整数要求地址是8的倍数。这种要求和硬件的设计有关,可以简化电路并提高访问效率。

在x86平台上,CPU一般允许访问未对齐的内存,但会带来一定的性能损失。而在ARM等RISC架构的处理器上,未对齐访问通常会直接触发异常(ARM64位CPU会自行处理对齐问题,一般不会触发异常)。程序员在编写代码时,尤其是在处理网络数据或磁盘数据时,需要特别注意字节对齐问题。

有趣的是,x86平台也提供了一种机制,可以主动禁止未对齐访问,即通过修改EFLAGS寄存器的AC(Alignment Check)标志位。当设置AC位为1时,CPU会在每次内存访问时检查地址的对齐情况,如果发现未对齐访问就会抛出SIGBUS异常。

这个特性对于调试和测试代码很有帮助。它可以帮助程序员及早发现代码中隐藏的对齐问题,提高软件的可移植性和稳定性。但在生产环境中一般不会启用这个特性,因为它会带来额外的性能开销。

1.3【解决思路】

(1)文件映射访问异常的解决方案:这类问题的根源在于多个进程对同一个文件进行了不同步的操作。因此,解决方案的核心思路是加强进程间的协调和同步。具体可以采取以下措施:

  • 文件锁:在对文件进行映射或者修改大小之前,先获取文件锁。这样可以防止其他进程同时修改文件,导致不一致。Linux提供了flock和fcntl两种文件锁机制,可以根据需要选用。

  • 避免直接覆盖文件:如果需要更新动态库或可执行文件,不要直接用新文件覆盖旧文件,而是先写入一个新文件,然后用rename系统调用进行原子性的替换。这样可以确保任何时刻文件系统中只存在一个完整的版本。

  • 异常处理:在访问mmap的内存时,用try/catch等机制捕获SIGBUS异常,并进行适当的错误处理,如重新加载文件、通知用户等,提高程序的健壮性。

  • 定期检查文件大小:如果进程长时间持有一个mmap,可以定期检查文件的实际大小是否发生变化,如果变小了就及时解除不一致的映射,重新进行mmap。

(2)访问不对齐内存的解决方案:对于这类问题,首要原则是尽量避免在代码中产生非对齐访问。具体措施包括:

  • 使用对齐的数据结构:在定义结构体时,确保每个字段都按照其大小要求进行对齐。可以使用编译器提供的对齐属性,如GNU C的__attribute__((aligned(n)))

  • 内存分配时考虑对齐:使用malloc、mmap等分配内存时,确保返回的内存地址是对齐的。可以使用posix_memalign、aligned_alloc等对齐版本的内存分配函数。

  • 网络/磁盘IO时注意对齐:在处理网络数据或磁盘数据时,注意数据的边界可能不对齐。需要使用memcpy等函数进行中间缓冲,不要直接将IO缓冲区强制转换为结构体指针。

  • 使用对齐版本的内存访问指令:现代CPU提供了一些对齐版本的内存访问指令,如movaps、lddqu等。在进行大块数据操作时,使用这些指令可以避免对齐异常。

  • 必要时进行填充:如果某些数据结构无法完美对齐,可以在末尾添加填充字节,使其对齐。虽然这会浪费一些内存空间,但可以避免更严重的异常。

2. 实例验证
2.1 对齐问题触发SIGBUS信号

测试代码如下:

/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 2025 Once Day <once_day@qq.com>, All rights reserved.
 *
 * @FilePath: /tools/sigbus_alignment.c
 * @Author: Once Day <once_day@qq.com>.
 * @Date: 2025-03-17 21:42
 * @info: Encoder=utf-8,Tabsize=4,Eol=\n.
 *
 * @Description:
 *  测试SIGBUS信号
 *
 */

#include <stdint.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

unsigned long int original_eflags;
int               temp_value;

void sigbus_handler(int sig)
{
    printf("Caught SIGBUS: Alignment Fault!\n");
    // 恢复原始的EFLAGS值
    __asm__ volatile("push %0 \n\t popf" : : "r"(original_eflags));
    exit(1);
}

int main()
{
    // 安装SIGBUS信号处理器
    signal(SIGBUS, sigbus_handler);

    // 分配一个字符数组(确保未对齐)
    char data[64] = {0};

    // 将字符数组的起始地址+3转换为int指针
    int *misaligned = (int *)((((uint64_t)(data + 8)) & ~0x7) + 1);

    // 手动设置CPU对齐检查标识
    __asm__ volatile("pushf \n\t pop %0" : "=r"(original_eflags));

    // 打印原始EFLAGS值
    printf("Original EFLAGS: 0x%lx\n", original_eflags);
    // 打印对齐检查位
    printf("Alignment Check Bit: %s\n", (original_eflags & 0x40000) ? "Enabled" : "Disabled");
    printf("Misaligned pointer: %p\n", misaligned);

    // 设置对齐检查位
    __asm__ volatile("pushf \n\t orl $0x40000, (%rsp) \n\t popf");

    // 尝试通过misaligned指针写入数据
    *misaligned = 0x12345678;

    // 如果没有触发SIGBUS,这行代码会执行
    printf("No SIGBUS, data written: %d\n", temp_value);

    // 恢复原始的EFLAGS值
    __asm__ volatile("push %0 \n\t popf" : : "r"(original_eflags));
    return temp_value;
}

这段 C 语言代码的主要目的是演示在 x86 架构下处理未对齐内存访问时触发的SIGBUS信号。具体步骤如下:

  1. 包含必要的头文件,并定义全局变量用于存储原始的 EFLAGS 寄存器值和一个临时变量。
  2. 编写信号处理函数,当捕获到SIGBUS信号时,输出提示信息,恢复原始 EFLAGS 值,然后终止程序。
  3. main函数中,首先安装SIGBUS信号处理器,接着准备一个未对齐的内存地址。
  4. 保存并打印原始的 EFLAGS 值和对齐检查位的状态,然后设置对齐检查位。
  5. 尝试通过未对齐的指针写入数据,若触发SIGBUS信号则进入信号处理函数;若未触发,则输出提示信息。
  6. 最后恢复原始的 EFLAGS 值,并返回临时变量。

编译执行,可以出现SIGBUS信号,解决也很简单,只有将地址对齐即可:

ubuntu->tools:$ gcc sigbus_alignment.c -O0 -o sigbus_alignment.out 
ubuntu->tools:$ ./sigbus_alignment.out 
Original EFLAGS: 0x206
Alignment Check Bit: Disabled
Misaligned pointer: 0x7ffc32e491f9
Caught SIGBUS: Alignment Fault!
2.2 映射内存大小改变触发SIGBUS信号

测试代码如下:

/*
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (c) 2025 Once Day <once_day@qq.com>, All rights reserved.
 *
 * @FilePath: /tools/sigbus_mmap.c
 * @Author: Once Day <once_day@qq.com>.
 * @Date: 2025-03-17 22:16
 * @info: Encoder=utf-8,Tabsize=4,Eol=\n.
 *
 * @Description:
 * 测试SIGBUS信号
 *
 */

#define _GNU_SOURCE
#define __USE_GNU

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <setjmp.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mman.h>

static uint64_t mapped_size        = 8192;
static uint64_t mapped_size_latest = 4096;
static char    *mapped_mem;
static int      mapped_fd;

// 使用signal longjmp跳转到sigbus_handler
static sigjmp_buf jmpbuf;

void sigbus_handler(int sig)
{
    printf("Caught SIGBUS: Invalid memory access!\n");
    // 首先取消原有内存映射
    munmap(mapped_mem, mapped_size);
    // 重新映射内存
    mapped_mem =
        mmap(mapped_mem, mapped_size_latest, PROT_READ | PROT_WRITE, MAP_SHARED, mapped_fd, 0);
    if (mapped_mem == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    printf("Remapped address: %p\n", mapped_mem);
    // 继续执行
    siglongjmp(jmpbuf, 1);
}

int main()
{
    // 安装SIGBUS信号处理器
    signal(SIGBUS, sigbus_handler);

    // 创建一个共享内存文件映射
    mapped_fd = shm_open("test-sigbus", O_CREAT | O_RDWR, 0666);
    if (mapped_fd == -1) {
        perror("shm_open");
        exit(1);
    }

    // 删除共享内存文件(确保下次重新映射)
    shm_unlink("test-sigbus");

    // 设置文件大小
    ftruncate(mapped_fd, mapped_size);

    // 映射共享内存
    mapped_mem = mmap(NULL, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, mapped_fd, 0);

    printf("Mapped address: %p\n", mapped_mem);
    // 写入数据
    for (int i = 0; i < mapped_size; i++) {
        mapped_mem[i] = 'A' + i % 26;
    }

    printf("Data written\n");

    // 重新设置文件大小
    printf("Truncated file size\n");
    ftruncate(mapped_fd, mapped_size_latest);

    // 尝试访问超出映射内存的地址
    printf("Accessing out-of-bound memory...\n");

    // 设置长跳转点
    if (sigsetjmp(jmpbuf, 1) == 0) {
        // 尝试访问超出映射内存的地址
        mapped_mem[mapped_size_latest] = 0;
    } else {
        // 如果发生SIGBUS,长跳转到这里
        printf("Check mapped file length has been changed\n");
        printf("Read data under new size: %c\n", mapped_mem[mapped_size_latest - 1]);
    }

    // 如果没有触发SIGBUS,这行代码会执行
    printf("End of program\n");
    return 0;
}

这段 C 语言代码的主要目的是测试SIGBUS信号,模拟内存映射文件大小改变后,访问超出新文件大小的内存区域触发SIGBUS信号的情况,并对该信号进行处理。具体步骤如下:

  1. 头文件和全局变量:包含必要的系统头文件,定义全局变量用于记录映射内存的大小、文件描述符和映射内存地址,同时使用sigjmp_buf用于长跳转。
  2. 信号处理函数:定义sigbus_handler函数,当捕获到SIGBUS信号时,先取消原有内存映射,再重新映射内存,若映射失败则输出错误信息并退出程序。最后使用siglongjmp跳转到之前设置的跳转点继续执行。
  3. main函数,安装SIGBUS信号处理器,创建一个共享内存文件映射,删除该文件以确保下次重新映射,设置文件大小并进行内存映射。向映射内存写入数据。重新设置文件大小,尝试访问超出新文件大小的内存地址。使用sigsetjmp设置长跳转点,若触发SIGBUS信号,跳转到相应代码块,输出检查信息并读取新大小下的最后一个字符。若未触发SIGBUS信号,输出程序结束信息并返回。

编译执行,可以出现SIGBUS信号,解决方法可以是重新映射共享内存,并且同步修改访问内存的变量值。

ubuntu->tools:$ gcc sigbus_mmap.c -O0 -g -o sigbus_mmap.out
ubuntu->tools:$ ./sigbus_mmap.out 
Mapped address: 0x7f7738469000
Data written
Truncated file size
Accessing out-of-bound memory...
Caught SIGBUS: Invalid memory access!
Remapped address: 0x7f7738469000
Check mapped file length has been changed
Read data under new size: N
End of program
3. 总结

一般而已,对齐问题主要影响性能,所以在面对热点代码时,需要保证核心数据结构对齐,特别是对于原子操作,需要尽可能在一个cache line内。

对于文件映射内存后,文件大小发生改变,这种情况下需要重新映射共享内存,直接重启应用是非常不错的选项,如果无法重启应用,可尝试在信号处理函数里面重新映射共享内存,并且修改相关的变量信息。

对于存在异常捕获的编程语言,如C++和Python,这一步很好操作,但是对于C语言,需要借助setjmp来完成退栈操作。

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

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

相关文章

[贪心算法]-最大数(lambda 表达式的补充)

1.解析 我们一般使用的排序比较大小都是 a>b 那么a在b的前面 ab 无所谓 a<b a在b的后面 本题的排序则是 ab>ba 那么a在b的前面 abba 无所谓 ab<ba a在b的后面 2.代码 class Solution { public:string largestNumber(vector<int>& nums) {//1.先把所有…

C语言 —— 此去经年梦浪荡魂音 - 深入理解指针(卷二)

目录 1. 数组名与地址 2. 指针访问数组 3.一维数组传参本质 4.二级指针 5. 指针数组 6. 指针数组模拟二维数组 1. 数组名与地址 我们先看下面这个代码&#xff1a; int arr[10] { 1,2,3,4,5,6,7,8,9,10 };int* p &arr[0]; 这里我们使用 &arr[0] 的方式拿到了数…

python实现简单的图片去水印工具

python实现简单的图片去水印工具 使用说明&#xff1a; 点击"打开图片"选择需要处理的图片 在图片上拖拽鼠标选择水印区域&#xff08;红色矩形框&#xff09; 点击"去除水印"执行处理 点击"保存结果"保存处理后的图片 运行效果 先简要说明…

使用dify+deepseek部署本地知识库

使用difydeepseek部署本地知识库 一、概述二、安装windows docker desktop1、确认系统的Hyper-v功能正常启用2、docker官网下载安装windows客户端3、安装完成后的界面如下所示 三、下载安装ollama四、部署本地deepseek五、本地下载部署dify5.1 下载dify的安装包5.2 将dify解压到…

【算法day13】最长公共前缀

最长公共前缀 https://leetcode.cn/problems/longest-common-prefix/submissions/612055945/ 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 class Solution { public:string longestCommonPrefix(vector<string&g…

Java高频面试之集合-13

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本baby今天来报道了&#xff01;哈哈哈哈哈嗝&#x1f436; 面试官&#xff1a;为什么 hash 函数能降哈希碰撞&#xff1f; 哈希函数通过以下核心机制有效降低碰撞概率&#xff0c;确保不同输入尽可能映…

RGV调度算法(三)--遗传算法

1、基于时间窗 https://wenku.baidu.com/view/470e9fd8b4360b4c2e3f5727a5e9856a57122693.html?_wkts_1741880736197&bdQuery%E7%8E%AF%E7%A9%BF%E8%B0%83%E5%BA%A6%E7%AE%97%E6%B3%95 2.2019年MathorCup高校数学建模挑战赛B题 2019-mathorcupB题-环形穿梭机调度模型&a…

YOLOv8轻量化改进——Coordinate Attention注意力机制

现在针对YOLOv8的架构改进越来越多&#xff0c;今天尝试引入了Coordinate Attention注意力机制以改进对小目标物体的检测效率。 yolov8的下载和安装参考我这篇博客&#xff1a; 基于SeaShips数据集的yolov8训练教程_seaships处理成yolov8-CSDN博客 首先我们可以去官网找到CA注…

基于SpringBoot+Vue的驾校预约管理系统+LW示例参考

1.项目介绍 系统角色&#xff1a;管理员、普通用户、教练功能模块&#xff1a;用户管理、管理员管理、教练管理、教练预约管理、车辆管理、车辆预约管理、论坛管理、基础数据管理等技术选型&#xff1a;SpringBoot&#xff0c;Vue等测试环境&#xff1a;idea2024&#xff0c;j…

ONNX:统一深度学习工作流的关键枢纽

引言 在深度学习领域&#xff0c;模型创建与部署的割裂曾是核心挑战。不同框架训练的模型难以在多样环境部署&#xff0c;而 ONNX&#xff08;Open Neural Network Exchange&#xff09;作为开放式神经网络交换格式&#xff0c;搭建起从模型创建到部署的统一桥梁&#xff0c;完…

蓝桥杯————23年省赛 ——————平方差

3.平方差 - 蓝桥云课 一开始看题我还没有意识到问题的严重性 我丢&#xff0c;我想 的是用两层循环来做&#xff0c;后来我试了一下最坏情况&#xff0c;也就是l1 r 1000000000 结果运行半天没运行出来&#xff0c;我就知道坏了&#xff0c;孩子们&#xff0c;要出事&#…

一、串行通信基础知识

一、串行通信基础知识 1.处理器与外部设备通信有两种方式 并行通信&#xff1a;数据的各个位用多条数据线同时传输。&#xff08;传输速度快&#xff0c;但占用引脚资源多。&#xff09; 串行通信&#xff1a;将数据分成一位一位的形式在一条数据线上逐个传输。&#xff08;线路…

自带多个接口,完全免费使用!

做自媒体的小伙伴们&#xff0c;是不是经常为语音转文字的事儿头疼&#xff1f; 今天给大家推荐一款超实用的语音转文字软件——AsrTools&#xff0c;它绝对是你的得力助手&#xff01; AsrTools 免费的语音转文字软件 这款软件特别贴心&#xff0c;完全免费&#xff0c;而且操…

Qt QML解决SVG图片显示模糊的问题

前言 在QML中直接使用SVG图片&#xff0c;使用Image控件加载资源&#xff0c;显示出来图片是模糊的&#xff0c;很影响使用体验。本文介绍重新绘制SVG图片&#xff0c;然后注册到QML中使用。 效果图&#xff1a; 左边是直接使用Image加载资源显示的效果 右边是重绘后的效果 …

【Linux我做主】基础命令完全指南上篇

Linux基础命令完全指南【上篇】 Linux基础命令完全指南github地址前言命令行操作的引入Linux文件系统树形结构的根文件系统绝对路径和相对路径适用场景Linux目录下的隐藏文件 基本指令目录和文件相关1. ls2. cd和pwdcdpwd 3. touch4. mkdir5. cp6. mv移动目录时覆盖写入的两种特…

Designing Dashboards with SAP Analytics Cloud

Designing Dashboards with SAP Analytics Cloud

项目实战系列:基于瑞萨RA6M5构建多节点OTA升级-系统设计<一>

项目背景 原嵌入式控制系统采用分布式模块化架构&#xff0c;由12个功能板卡&#xff08;通信控制、信号采集、驱动执行等&#xff09;组成。系统维护阶段存在以下痛点&#xff1a; 低效的本地烧录机制&#xff1a;各板卡固件升级需通过JTAG接口逐一手动连接JLINK仿真器&#x…

《AI大模型趣味实战》 No3:快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色/博客/聊天室/AI管家(下)

《AI大模型趣味实战》 No3&#xff1a;快速搭建一个漂亮的AI家庭网站-相册/时间线/日历/多用户/个性化配色/博客/聊天室/AI管家(下) 摘要 本文介绍了家庭网站V1.3版本的更新内容&#xff0c;主要聚焦于AI管家功能的优化与完善。V1.3版本对AI管家模块进行了全面升级&#xff0…

c++基础知识-图论进阶

一、拓扑排序 1、基础知识 1&#xff09;什么是拓扑排序 对一个有向无环图G进行拓扑排序&#xff0c;是将G中所有顶点排成一个线性序列&#xff0c;使得图中任意一对顶点u和v&#xff0c;若&#xff0c;则u在线性序列中出现在v之前。 2&#xff09;拓扑排序的操作方法 重复执行…