当前位置: 首页 > news >正文

Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器

让崩溃成为历史:详解有效诊断与解决技巧

  • 引言
  • 崩溃信息的类型
  • 设置信号处理函数(Setting up signal handlers)
    • 信号来源和上下文信息
    • 使用 siginfo_t 结构体获取信号来源信息
    • 使用 ucontext 结构体获取上下文信息
    • 将崩溃信息写入日志
    • 标准的信号处理函数示例
    • 辅助信息(打印信号用途)
  • 获取堆栈跟踪
    • 使用 backtrace 函数(POSIX)
    • 使用 libunwind(跨平台)
  • 获取 CPU 使用情况
  • 获取操作系统和硬件信息
    • 操作系统:
    • 硬件信息:
    • 示例
  • 获取内存使用情况
  • 获取进程资源使用情况
    • getursage示例
    • getursage示例
  • 从 /proc 获取线程信息(Linux):
  • 优化信号处理函数
    • 避免信号处理函数中的不安全操作:
    • 避免在信号处理函数中使用 exit()失效
      • 使用 siglongjmp 和 sigsetjmp 函数
      • 使用 C++11 或更高版本的 std::thread 类
  • 获取线程信息
    • 使用线程库获取线程信息(跨平台)
    • 通过解析 /proc 目录中的信息来获取线程状态
    • 使用异步信号安全的函数
  • 总结

引言

在软件开发的过程中,进程崩溃是一个常见的问题。进程崩溃通常是由于程序中的错误或异常引起的,如内存泄漏、空指针解引用、数组越界访问等。这些问题可能会导致程序在运行时突然终止,给用户带来不便并影响软件的稳定性。

进程崩溃后,开发者需要对问题进行调查和诊断,以便找出问题的根源并修复它。在这种情况下,收集崩溃时的信息变得至关重要。这些信息可以帮助开发者了解崩溃发生的上下文,缩小问题范围,加快定位和解决问题的速度。

崩溃信息包括堆栈跟踪、系统信息、线程信息等,这些信息可以帮助开发者快速地找到问题所在。例如,堆栈跟踪可以显示函数调用的顺序,从而让开发者了解导致崩溃的函数调用路径。系统信息可以帮助开发者判断是否是由于某些特定的硬件或操作系统引起的问题。线程信息有助于了解多线程程序中可能存在的竞争条件或死锁等问题。

为了有效地收集崩溃信息,开发者可以使用信号处理函数来捕获进程中的异常信号。通过在信号处理函数中收集和记录崩溃时的各种信息,开发者可以更容易地分析和解决问题。

总之,进程崩溃后收集信息是软件开发和维护过程中的关键步骤。通过对崩溃信息的分析和理解,开发者可以更快地识别问题、修复错误并提高软件的稳定性和可靠性。


崩溃信息的类型

  • 堆栈跟踪(Stack trace)
    堆栈跟踪提供了在崩溃发生时函数调用的顺序,包括函数名、参数和返回地址。这有助于开发者了解导致崩溃的函数调用路径,以及问题可能发生的位置。

  • 系统信息(System information)
    系统信息包括操作系统版本、硬件配置、已安装的软件以及其他环境信息。这些信息有助于判断问题是否由特定的硬件、操作系统或软件引起,并有助于重现问题。

  • 线程信息(Thread information)
    线程信息可以帮助了解多线程程序中可能存在的竞争条件、死锁等问题。这些信息包括线程的状态、标识符、优先级以及线程栈等。

  • 内存使用情况(Memory usage)
    内存使用情况信息包括进程在崩溃时分配的内存大小、已使用内存、内存泄漏等。分析这些信息可以帮助开发者找到潜在的内存问题,如内存泄漏或分配失败。

  • 信号来源和上下文信息(Signal source and context information)
    当异常信号触发时,可以获取信号的来源和触发原因。此外,上下文信息包括寄存器值、指令地址等,有助于进一步分析崩溃原因。


设置信号处理函数(Setting up signal handlers)

为了有效地收集崩溃信息,开发者需要设置信号处理函数来捕获进程中的异常信号。信号处理函数可以在异常发生时被自动调用,并收集和记录崩溃时的各种信息。信号处理函数的设置通常涉及以下步骤:

a. 定义信号处理函数,用于在接收到异常信号时执行相应的操作,如收集崩溃信息。
b. 注册信号处理函数,将其与特定的异常信号关联起来。
c. 在程序运行过程中,信号处理函数会自动捕获异常信号并执行相应操作。

通过设置信号处理函数并收集崩溃信息,开发者可以更容易地分析和解决问题,从而提高软件的稳定性和可靠性。

信号来源和上下文信息

使用 siginfo_t 结构体获取信号来源信息

当信号处理函数被调用时,操作系统会将一个 siginfo_t 结构体传递给处理函数。siginfo_t 结构体包含了关于信号的详细信息,如信号类型、发送信号的进程或线程 ID、触发信号的原因等。通过分析 siginfo_t 结构体,可以了解信号的来源和触发原因。

使用 ucontext 结构体获取上下文信息

信号处理函数除了可以接收 siginfo_t 结构体外,还可以接收一个 ucontext 结构体。ucontext 结构体包含了信号发生时的上下文信息,如寄存器值、指令地址等。通过分析 ucontext 结构体,可以了解崩溃发生时的程序状态,从而更准确地定位问题。

将崩溃信息写入日志

收集到信号来源和上下文信息后,将这些信息写入日志文件或发送给开发者是一种有效的诊断方法。可以使用以下方法将崩溃信息写入日志:

使用标准 I/O 函数,如 fprintf,将信号来源和上下文信息写入文件。需要确保文件 I/O 操作在信号处理函数中是安全的。
使用 syslog(POSIX)或 Event Log(Windows)将崩溃信息记录到系统日志中,以便在远程或集中式日志系统中查看。
使用网络通信库,如 libcurl,将崩溃信息发送给开发者或崩溃报告服务器,以便实时收集和分析崩溃数据。
通过收集信号来源和上下文信息,并将这些信息写入日志或发送给开发者,可以更快地分析和解决崩溃问题,从而提高软件的稳定性和可靠性。


标准的信号处理函数示例

关于信号的相关资料可以看这几篇文章
Linux之信号介绍/列表:列举Linux系统中常见的信号及其含义和用途
Linux系统编程之信号使用:介绍信号的基本概念、用法和实现方式
Linux系统编程之信号集:介绍信号集的基本概念、用法和实现方式

static void staticFailureSignalHandler(int signum, siginfo_t *signal_info, void *ucontext) {std::cout << "Signal number: " << signum << std::endl;// 使用 siginfo_t 结构体中的信息if (signal_info) {std::cout << "Signal code: " << signal_info->si_code << std::endl;std::cout << "Fault address: " << signal_info->si_addr << std::endl;}// 使用 ucontext 结构体中的信息if (ucontext) {ucontext_t *uc = static_cast<ucontext_t *>(ucontext);#ifdef __linux__std::cout << "Instruction pointer: 0x" << std::hex << uc->uc_mcontext.gregs[REG_RIP] << std::endl;
#elif __APPLE__std::cout << "Instruction pointer: 0x" << std::hex << uc->uc_mcontext->__ss.__rip << std::endl;
#endif}_Exit(EXIT_FAILURE);
}

辅助信息(打印信号用途)

#include <string>std::string getSignalDescription(int signal) {std::string signalDescription;switch (signal) {case SIGSEGV:signalDescription = "Segmentation fault";break;case SIGABRT:signalDescription = "Aborted";break;case SIGFPE:signalDescription = "Floating point exception";break;// Add other signal cases heredefault:signalDescription = "Unknown signal";break;}return signalDescription;
}

获取堆栈跟踪

关于堆栈的作用: 此文章有介绍 堆栈的作用

获取堆栈跟踪是分析崩溃信息的关键步骤。下面介绍两种获取堆栈跟踪的方法:使用 POSIX 的 backtrace 函数和使用跨平台的 libunwind 库。

使用 backtrace 函数(POSIX)

在 POSIX 兼容的系统(如 Linux 和 macOS)上,可以使用 backtrace 函数来获取当前线程的堆栈跟踪。首先,需要包含头文件 <execinfo.h>,然后调用 backtrace 函数:

#include <execinfo.h>
#include <stdio.h>void print_stack_trace() {void *buffer[100];int size = backtrace(buffer, sizeof(buffer) / sizeof(void *));char **symbols = backtrace_symbols(buffer, size);for (int i = 0; i < size; i++) {printf("%d: %s\n", i, symbols[i]);}free(symbols);
}

使用 libunwind(跨平台)

libunwind 是一个跨平台的库,用于获取堆栈跟踪。首先,需要安装 libunwind,然后包含头文件 <libunwind.h>,并使用相应的 API

#include <libunwind.h>
#include <stdio.h>void print_stack_trace() {unw_cursor_t cursor;unw_context_t context;unw_getcontext(&context);unw_init_local(&cursor, &context);while (unw_step(&cursor) > 0) {unw_word_t offset;char symbol[256] = {"<unknown>"};unw_word_t ip;unw_get_reg(&cursor, UNW_REG_IP, &ip);if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {printf("0x%lx: %s (0x%lx)\n", ip, symbol, offset);} else {printf("0x%lx: %s\n", ip, symbol);}}
}

获取 CPU 使用情况

获取 CPU 使用情况:记录崩溃时的 CPU 使用情况,以确定是否存在性能问题。

#include <string>
#include <fstream>
#include <unistd.h>
//获取 CPU 使用情况的函数(仅适用于 Linux)
bool getCPUUsage(std::string &cpuUsageInfo) {std::ifstream stat("/proc/self/stat");if (!stat.is_open()) {return false;}std::string line;std::getline(stat, line);stat.close();std::istringstream iss(line);std::vector<std::string> tokens{std::istream_iterator<std::string>{iss},std::istream_iterator<std::string>{}};if (tokens.size() >= 17) {long utime = std::stol(tokens[13]);long stime = std::stol(tokens[14]);long cutime = std::stol(tokens[15]);long cstime = std::stol(tokens[16]);cpuUsageInfo = "User time: " + std::to_string(utime) + " ticks\n";cpuUsageInfo += "System time: " + std::to_string(stime) + " ticks\n";cpuUsageInfo += "Children user time: " + std::to_string(cutime) + " ticks\n";cpuUsageInfo += "Children system time: " + std::as_string(cstime) + " ticks\n";} else {return false;}return true;
}

获取操作系统和硬件信息

获取操作系统和硬件信息对于诊断和解决问题非常重要,因为某些问题可能与特定的操作系统或硬件有关。可以通过以下方法收集操作系统和硬件信息:

操作系统:

使用 uname 函数(POSIX)或 GetVersionEx 函数(Windows)获取操作系统名称、版本、内核版本等信息。

硬件信息:

使用 sysctl 函数(POSIX)或 GetSystemInfo 函数(Windows)获取处理器类型、内存大小、虚拟内存等硬件信息。

示例

#include <string>
#include <fstream>
#include <unistd.h>
#include <sys/utsname.h>
//获取硬件和操作系统信息的函数(仅适用于 Linux):
bool getSystemInfo(std::string &systemInfo) {struct utsname sysinfo;if (uname(&sysinfo) == -1) {return false;}systemInfo = "Operating System: ";systemInfo += sysinfo.sysname;systemInfo += "\n";systemInfo += "Node name: ";systemInfo += sysinfo.nodename;systemInfo += "\n";systemInfo += "Release: ";systemInfo += sysinfo.release;systemInfo += "\n";systemInfo += "Version: ";systemInfo += sysinfo.version;systemInfo += "\n";systemInfo += "Machine: ";systemInfo += sysinfo.machine;systemInfo += "\n";return true;
}

获取内存使用情况

获取崩溃时的内存使用情况,可以帮助确定是否存在内存泄漏或内存不足的问题。

#include <string>
#include <fstream>
#include <unistd.h>
//获取内存使用情况的函数(仅适用于 Linux)
bool getMemoryUsage(std::string &memoryUsageInfo) {std::ifstream meminfo("/proc/self/status");if (!meminfo.is_open()) {return false;}std::string line;while (std::getline(meminfo, line)) {if (line.find("VmRSS") != std::string::npos || line.find("VmSize") != std::string::npos) {memoryUsageInfo += line + "\n";}}meminfo.close();return true;
}

获取进程资源使用情况

收集进程的资源使用情况可以帮助开发者找到潜在的性能问题或资源泄漏。可以通过以下方法获取进程资源使用情况:

使用 getrusage 函数(POSIX)或 GetProcessMemoryInfo 函数(Windows)获取进程的 CPU 时间、内存使用、页面错误等资源使用情况。
使用 getrlimit 函数(POSIX)或 GetProcessWorkingSetSize 函数(Windows)获取进程的资源限制,如最大内存使用量、最大文件描述符数量等。

getursage示例

#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>/*** @brief 获取当前进程的资源使用情况* * @param result 存储结果的vector,每条消息用string存储* @return bool 成功获取返回true,否则返回false*/
bool getProcessResourceUsage(std::vector<std::string>& result)
{// 获取进程资源使用情况struct rusage usage;if (getrusage(RUSAGE_SELF, &usage) != 0){std::cerr << "Error: Failed to get resource usage." << std::endl;return false;}std::stringstream ss;// 用户态CPU时间ss << "User CPU time: " << usage.ru_utime.tv_sec << "s " << usage.ru_utime.tv_usec << "us";result.push_back(ss.str());ss.str("");// 系统态CPU时间ss << "System CPU time: " << usage.ru_stime.tv_sec << "s " << usage.ru_stime.tv_usec << "us";result.push_back(ss.str());ss.str("");// 页面重映射次数ss << "Page reclaims (soft page faults): " << usage.ru_minflt;result.push_back(ss.str());ss.str("");// 页面错误次数ss << "Page faults (hard page faults): " << usage.ru_majflt;result.push_back(ss.str());ss.str("");// 发出的阻塞I/O操作次数ss << "Block input operations: " << usage.ru_inblock;result.push_back(ss.str());ss.str("");// 已完成的阻塞I/O操作次数ss << "Block output operations: " << usage.ru_oublock;result.push_back(ss.str());ss.str("");// IPC消息发送次数ss << "IPC messages sent: " << usage.ru_msgsnd;result.push_back(ss.str());ss.str("");// IPC消息接收次数ss << "IPC messages received: " << usage.ru_msgrcv;result.push_back(ss.str());ss.str("");// 信号量操作次数ss << "Semaphore operations: " << usage.ru_nvcsw + usage.ru_nivcsw;result.push_back(ss.str());ss.str("");// 上下文切换次数ss << "Context switches: " << usage.ru_nswap;result.push_back(ss.str());return true;
}int main()
{std::vector<std::string> resource_usage_info;if (getProcessResourceUsage(resource_usage_info)){for (const auto& info : resource_usage_info){std::cout << info << std::endl;}}return 0;
}

getursage示例

#include <sys/resource.h>
#include <unistd.h>
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <limits>/*** @brief 获取当前进程的资源限制* * @param result 存储结果的vector,每条消息用string存储* @return bool 成功获取返回true,否则返回false*/
bool getProcessResourceLimits(std::vector<std::string>& result)
{// 定义资源类型数组const std::vector<int> resource_types = {RLIMIT_AS, RLIMIT_CORE, RLIMIT_CPU, RLIMIT_DATA, RLIMIT_FSIZE,RLIMIT_LOCKS, RLIMIT_MEMLOCK, RLIMIT_MSGQUEUE, RLIMIT_NICE,RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_RSS, RLIMIT_RTPRIO, RLIMIT_RTTIME,RLIMIT_SIGPENDING, RLIMIT_STACK};// 定义资源类型名称数组const std::vector<std::string> resource_names = {"Address space limit", "Core file size limit", "CPU time limit","Data segment limit", "File size limit", "File lock limit","Locked memory limit", "Message queue limit", "Nice priority limit","File descriptor limit", "Number of processes limit", "Resident set size limit","Real-time priority limit", "Real-time timeout limit", "Pending signals limit","Stack size limit"};struct rlimit limit;std::stringstream ss;for (size_t i = 0; i < resource_types.size(); ++i){if (getrlimit(resource_types[i], &limit) == 0){ss << resource_names[i] << ": ";// 获取软限制if (limit.rlim_cur == RLIM_INFINITY){ss << "soft limit: unlimited, ";}else{ss << "soft limit: " << limit.rlim_cur << ", ";}// 获取硬限制if (limit.rlim_max == RLIM_INFINITY){ss << "hard limit: unlimited";}else{ss << "hard limit: " << limit.rlim_max;}result.push_back(ss.str());ss.str("");}else{std::cerr << "Error: Failed to get resource limit for " << resource_names[i] << std::endl;return false;}}return true;
}int main()
{std::vector<std::string> resource_limits_info;if (getProcessResourceLimits(resource_limits_info)){for (const auto& info : resource_limits_info){std::cout << info << std::endl;}}return 0;
}

从 /proc 获取线程信息(Linux):

在 Linux 系统中,/proc 文件系统提供了丰富的进程和线程信息。通过读取 /proc 文件系统中的相关文件,可以收集到线程的详细信息。例如:

/proc/[pid]/task:该目录包含进程中每个线程的详细信息。可以遍历此目录以获取进程中所有线程的标识符。
/proc/[pid]/task/[tid]/status:该文件提供了特定线程的状态信息,如线程状态、优先级等。
/proc/[pid]/task/[tid]/stat:该文件包含了特定线程的详细统计信息,如 CPU 使用时间、上下文切换次数等。
关于更加详细的介绍,可以看我这篇文章:
深度剖析Linux进程的内部机制:一探/proc/pid的奥秘


优化信号处理函数

避免信号处理函数中的不安全操作:

信号处理函数可能在程序的任何时刻被调用,因此在编写信号处理函数时应避免使用可能导致竞争条件、死锁或其他未定义行为的操作。以下是一些建议:

避免在信号处理函数中使用全局变量或共享资源,因为这可能导致竞争条件。如果必须使用全局变量,请确保使用原子操作或其他同步机制来保护数据。
避免在信号处理函数中调用可能阻塞的函数,如文件 I/O、内存分配等,因为这可能导致死锁或崩溃。
避免在信号处理函数中修改信号掩码或重新注册信号处理函数,因为这可能导致信号丢失或循环调用。

避免在信号处理函数中使用 exit()失效

使用 siglongjmp 和 sigsetjmp 函数

设置一个非局部跳转标记,然后在信号处理函数中执行 siglongjmp,以便从信号处理函数返回到设置跳转标记的地方。

#include <csetjmp>
#include <csignal>
#include <iostream>std::sigjmp_buf jumpBuffer;void signalHandler(int signal) {// Call existing functions: getStackTrace, getSysinfo, etc.// ...// Perform a non-local jump to the point where the jump buffer was setstd::siglongjmp(jumpBuffer, 1);
}int main() {std::signal(SIGABRT, signalHandler);if (std::sigsetjmp(jumpBuffer, 1) == 0) {// This block is executed when the program is first run// or when returning from the signal handler using siglongjmpstd::cout << "Running before assertion..." << std::endl;assert(false);} else {// This block is executed after returning from the signal handlerstd::cout << "Recovered from assertion failure." << std::endl;}return 0;
}

使用 C++11 或更高版本的 std::thread 类

std::thread 类来处理异常不需要使用信号处理函数。可以通过捕获线程抛出的异常来处理断言失败。

#include <cassert>
#include <iostream>
#include <thread>void worker() {std::cout << "Running before assertion..." << std::endl;assert(false);
}int main() {try {std::thread t(worker);t.join();} catch (const std::exception& e) {// Handle the exception herestd::cerr << "Caught an exception: " << e.what() << std::endl;}return 0;
}

获取线程信息

线程信息有助于了解多线程程序中可能存在的竞争条件、死锁等问题。
可以通过以下方法获取线程信息:

线程列表:使用 pthread 库(POSIX)或 CreateToolhelp32Snapshot 函数(Windows)获取进程中所有线程的列表。
线程状态:使用 pthread_attr_getschedparam 函数(POSIX)或 GetThreadPriority 函数(Windows)获取线程的状态、优先级等信息。
线程栈:使用 backtrace 函数(POSIX)或 CaptureStackBackTrace 函数(Windows)获取线程的堆栈跟踪。
通过收集系统信息、进程资源使用情况和线程信息,开发者可以更全面地了解崩溃发生的环境和上下文,从而更快地定位和解决问题。

使用线程库获取线程信息(跨平台)

跨平台的线程库,如 Boost.Thread 或 C++11标准库,提供了一些函数和类来获取线程信息。使用这些库可以在不同操作系统上以统一的方式收集线程信息。例如:

使用 std::thread::get_id 函数(C++11)或 boost::thread::get_id 函数(Boost.Thread)获取线程标识符。
使用 std::thread::hardware_concurrency 函数(C++11)或 boost::thread::hardware_concurrency 函数(Boost.Thread)获取可用的处理器数量,以评估线程并发能力。

通过解析 /proc 目录中的信息来获取线程状态

#include <dirent.h>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>struct ThreadInfo {int tid;char state;std::string comm;
};bool getThreadStatus(std::vector<ThreadInfo>& threadStatus) {const std::string proc_path = "/proc";DIR* dir = opendir(proc_path.c_str());if (dir == nullptr) {std::cerr << "Failed to open /proc directory." << std::endl;return false;}struct dirent* entry;while ((entry = readdir(dir)) != nullptr) {if (entry->d_type == DT_DIR) {int tid = -1;try {tid = std::stoi(entry->d_name);} catch (const std::invalid_argument&) {continue;} catch (const std::out_of_range&) {continue;}if (tid > 0) {std::string status_file = proc_path + "/" + entry->d_name + "/status";std::ifstream in(status_file);if (in.is_open()) {ThreadInfo thread_info;thread_info.tid = tid;std::string line;while (std::getline(in, line)) {if (line.substr(0, 5) == "Name:") {thread_info.comm = line.substr(6);} else if (line.substr(0, 6) == "State:") {thread_info.state = line[7];break;}}threadStatus.push_back(thread_info);}}}}closedir(dir);return true;
}

示例用法:

void signalHandler(int signal) {// Call existing functions: getStackTrace, getSysinfo, etc.// ...std::vector<ThreadInfo> threadStatus;if (getThreadStatus(threadStatus)) {for (const auto& thread_info : threadStatus) {std::cout << "TID: " << thread_info.tid<< ", Name: " << thread_info.comm<< ", State: " << thread_info.state << std::endl;}} else {std::cerr << "Failed to get thread status." << std::endl;}// Save or print the collected information// ...
}

使用异步信号安全的函数

异步信号安全(Async-signal-safe)函数是指可以在信号处理函数中安全调用的函数,因为它们不会被中断或产生竞争条件。以下是一些建议:

使用 write 函数(POSIX)或 WriteFile 函数(Windows)将崩溃信息写入文件,而不是使用标准 I/O 函数,如 fprintf。
使用 sig_atomic_t 类型来存储信号处理函数中使用的变量,以确保对这些变量的操作是原子的。
使用 sigprocmask 函数(POSIX)或 SetConsoleCtrlHandler 函数(Windows)来阻塞或解除阻塞信号,而不是在信号处理函数中直接修改信号掩码。
通过优化信号处理函数,避免不安全操作并使用异步信号安全的函数,可以降低崩溃时产生的风险,提高信号处理函数的可靠性。这将有助于更准确地收集和分析崩溃信息,从而加快问题定位和解决的速度。


总结

在本文中,我们探讨了如何在软件崩溃时收集关键信息,以便在之后进行诊断和问题解决。我们介绍了信号处理函数的概念,并讨论了如何使用 sigaction 函数注册信号处理函数。我们还详细了解了如何获取堆栈跟踪、收集系统信息、获取线程信息,以及如何获取信号来源和上下文信息。

为了确保有效地记录这些信息,我们讨论了如何选择一个日志库,并在信号处理函数中将收集到的信息写入日志。此外,我们还强调了在编写信号处理函数时遵循的最佳实践,包括避免不安全操作和使用异步信号安全的函数。

收集崩溃信息的重要性不言而喻,它能帮助开发人员更快地定位问题,从而加快修复速度。掌握本文中介绍的方法和技巧,将有助于您更好地应对软件崩溃问题,提高软件的稳定性和可靠性。

相关文章:

Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器

让崩溃成为历史&#xff1a;详解有效诊断与解决技巧引言崩溃信息的类型设置信号处理函数&#xff08;Setting up signal handlers&#xff09;信号来源和上下文信息使用 siginfo_t 结构体获取信号来源信息使用 ucontext 结构体获取上下文信息将崩溃信息写入日志标准的信号处理函…...

ChatGPT能代替Oracle DBA吗?用Oracle OCP(1z0-083)的真题测试一下。

让我们来看看ChatGPT不能通过Oracle OCP的考试&#xff1f; 文章目录引言测试过程总结和分析关于博主&#xff0c;姚远&#xff1a;Oracle ACE&#xff08;Oracle和MySQL数据库方向&#xff09;。Oracle MAA 大师。华为云MVP。《MySQL 8.0运维与优化》的作者。拥有 Oracle 10g和…...

《扬帆优配》二季度投资策略出炉 机构调仓换股露踪迹

随着多家上市公司公告发布&#xff0c;其发表的股东数据使得基金的最新持仓浮出水面。与此同时&#xff0c;组织也在密集调研中寻觅出资时机。站在二季度的起点&#xff0c;基金公司二季度出资策略渐次发表。多家基金公司以为&#xff0c;宏观经济将延续修正态势&#xff0c;仍…...

【SpringMVC】2—传统方式实现增删改查

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 如果文章对你有所帮助&#xff0c;可以点赞&#x1f44d;…...

图像阈值化

图像阈值化 图像阈值化简介 ⚫ 图像阈值化是图像处理的重要基础部分, 应用很广泛, 可以根据灰度差异来分割图像不同部分 ⚫ 阈值化处理的图像一般为单通道图像(灰度图) ⚫ 阈值化参数的设置可以使用滑动条来debug ⚫ 阈值化处理易光照影响, 处理时应注意 ⚫ 本节主要介绍…...

1.5 极限运算法则

思维导图&#xff1a; 我的理解&#xff1a; 如果一个数列{a_n}是一个无穷小&#xff0c;那么它的极限为0&#xff0c;即lim(n→∞)a_n0。同样地&#xff0c;如果另一个数列{b_n}也是一个无穷小&#xff0c;那么它的极限为0&#xff0c;即lim(n→∞)b_n0。 当我们考虑这两个无…...

首批因AI失业的人出现-某游戏公司裁掉半数原画师

如今各种AI爆火&#xff0c;不可避免的的会与某些功能撞车职业发生冲突&#xff0c;每一次生产力的变革&#xff0c;在带来技术进步与更高效率的同时&#xff0c;也都无可避免的会带来一波失业浪潮&#xff0c;当下的人工智能浪潮自然也不例外。 现在&#xff0c;第一批因为AI…...

字符串转换整数(atoi)

请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格 检查下一个字符&#xff08;假设还未到字符…...

Servlet练习

练习准备 编写Student和StudentDao package beans;public class Student{private String num;private String name;public Student(){}public String getNum() {return num;}public String getName() {return name;}public void setNum(String num) {this.num num;}public v…...

美国高速公路信号灯控制项目的大致逻辑和步骤 智慧公路设计

美国高速公路信号灯控制项目的大致逻辑和步骤&#xff1a; 美国那边先提供一个关于具体做什么需求、那边的设备&#xff08;信号灯&#xff09;有什么参数&#xff0c;什么接口&#xff0c;分别是什么属性等等的详细设计文档&#xff0c;开发人员拿到这个文档以后把它看懂&…...

数字电源专用IC,国产C2000, QX320F280049

一、特性参数 1、独立双核&#xff0c;32位CPU&#xff0c;单核主频400MHz 2、IEEE 754 单精度浮点单元 &#xff08;FPU&#xff09; 3、三角函数单元 &#xff08;TMU&#xff09; 4、1MB 的 FLASH &#xff08;ECC保护&#xff09; 5、1MB 的 SRAM &#xff08;ECC保护&…...

第六章 ARM汇编语言程序设计【嵌入式系统】

第六章 ARM汇编语言程序设计【嵌入式系统】前言推荐第六章 ARM汇编语言程序设计6.1 概述6.2 ARM汇编语言指示符6.3 ARM编程举例6.4 ARM过程调用6.5 典型举例6.6 内嵌汇编C与汇编相互调用最后前言 以下内容源自《【嵌入式系统】》 仅供学习交流使用 推荐 无 第六章 ARM汇编语…...

详细讲讲Java线程的状态

TERMINATED状态 是什么状态&#xff1f; 在Java线程的生命周期中&#xff0c;TERMINATED状态是线程的最终状态&#xff0c;表示线程已经执行完毕并已经退出。当一个线程完成了它的工作&#xff0c;或者因为异常而提前结束时&#xff0c;它会进入TERMINATED状态。此时线程不再执…...

企业月结快递管理教程

回答这个问题的之前&#xff0c;我们先来看看什么是企业月结快递管理...... 经济的发展&#xff0c;技术的进步&#xff0c;电商行业的加持之下&#xff0c;这几年快递行业的发展有目共睹。不仅是我们的生活离不开快递&#xff0c;很多企业的运作多多少少也离不开“快递”二字…...

cm cdp告警 Swap Memory Usage Suppress...

原因&#xff1a;服务器没有关swap&#xff0c;服务使用了swap 在cdp集群中&#xff0c;一般要关掉swap&#xff0c;如果没有关。可以使用 下面命令设置程序尽可能不使用swap&#xff0c;使用swap会影响性能 修改后&#xff0c;重启服务就不会使用swap了 sysctl -w vm.swappi…...

3.8——友元

类的主要特点之一是信息隐藏和封装&#xff0c;即类的私有成员和保护成员只能在定义的范围内使用&#xff0c;也就是说私有成员和保护成员只能通过类的成员函数来访问。但是&#xff0c;有时候我们在类外也需要访问私有成员数据或保护成员数据怎么办。这时我们就要通过友元函数…...

C++ OOP Feature Conclusion (更新中)

目录 1.类与对象 1.1 基本概念&#xff08;继承、封装、抽象、多态&#xff09; 1.2类的声明 1.3成员函数&#xff08;对象所占空间取决于数据成员&#xff0c;和成员函数无关&#xff09; 1.4数据成员&#xff08;不能在类中初始化&#xff09; 1.5构造与析构函数&#xff08…...

【HTTP】Cookie、Session、Token以及Cookie优化

Cookie、Session、TokenCookie优化Cookie、Session、Token 在开始介绍Cookie安全之前&#xff0c;我们先来了解一下实现授权的方式。 在登录功能中&#xff0c;为了记住登录成功后的信息&#xff0c;在客户端&#xff0c;我们通常会使用Cookie来记录&#xff0c;但是&#xf…...

npm之报错:Error: EACCES: permission denied, access ‘/usr/local/lib/node_modules‘

1.报错 363 error [Error: EACCES: permission denied, rename ‘/usr/local/lib/node_modules/tldr’ -> ‘/usr/local/lib/node_modules/.tldr-8nq4AGAt’] { 363 error errno: -13, 363 error code: ‘EACCES’, 363 error syscall: ‘rename’, 363 error path: ‘/usr/…...

「SQL面试题库」 No_30 超过5名学生的课

&#x1f345; 1、专栏介绍 「SQL面试题库」是由 不是西红柿 发起&#xff0c;全员免费参与的SQL学习活动。我每天发布1道SQL面试真题&#xff0c;从简单到困难&#xff0c;涵盖所有SQL知识点&#xff0c;我敢保证只要做完这100道题&#xff0c;不仅能轻松搞定面试&#xff0…...

从Django后台到Celery Worker:一个完整用户注册邮件异步发送的部署实录

从Django后台到Celery Worker&#xff1a;一个完整用户注册邮件异步发送的部署实录 在Web应用开发中&#xff0c;用户注册流程是每个系统必备的基础功能。当新用户完成注册表单提交后&#xff0c;系统通常需要发送欢迎邮件或激活链接。如果直接在请求响应周期内执行邮件发送&am…...

‌失落大陆建模:亚特兰蒂斯数字重建的结构验证‌

一、项目背景与目标设定在数字孪生与虚拟考古技术飞速发展的当下&#xff0c;亚特兰蒂斯这一传说中失落大陆的数字重建&#xff0c;不仅是对古老神话的技术致敬&#xff0c;更是对复杂场景建模与结构验证能力的极致考验。本项目旨在依托Blender等3D建模工具&#xff0c;结合最新…...

《从GIS前端到AIGC大厂:WebGIS、WebGL、Three.js技术栈的底层能力拆解与岗位适配指南》

前端GIS技术栈&#xff1a;从图形学底层到AIGC营销增长的全链路实战指南 &#xff08;附大厂AI前端JD精准匹配与可落地项目&#xff09; &#x1f516; 目录理论篇&#xff1a;GIS中必学的图形学、WebGL、Three.js核心内容&#xff08;含GIS实战细节&#xff09; 1.1 计算机图形…...

在自动化部署流程中集成 TaoToken 大模型 API 调用

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 在自动化部署流程中集成 TaoToken 大模型 API 调用 将大模型能力融入自动化部署流程&#xff0c;正成为提升 DevOps 效率的新范式。…...

大语言模型驱动SVG代码生成:原理、实践与应用前景

1. 项目概述&#xff1a;当大语言模型遇上SVG图形生成最近在开源社区里&#xff0c;一个名为“ximinng/LLM4SVG”的项目引起了我的注意。这个项目名字直译过来就是“用于SVG的大语言模型”&#xff0c;它瞄准了一个非常具体且有趣的交叉领域&#xff1a;利用大语言模型来生成或…...

linux内核源码内存管理(7)

一、 引言&#xff1a;冲破冯诺依曼瓶颈的壁障在传统的单处理器&#xff08;UMA&#xff0c;Uniform Memory Access&#xff09;架构中&#xff0c;所有CPU核心通过同一条总线平等地访问所有内存。这种对称性带来了编程模型的简洁&#xff0c;但也埋下了致命的可扩展性陷阱&…...

机器人遥测系统设计:从数据采集到可视化监控的工程实践

1. 项目概述&#xff1a;从开源代码仓库到可观测性实践最近在梳理一些开源机器人项目时&#xff0c;遇到了一个名为jizb880/openclaw_telemetry的仓库。乍一看&#xff0c;这个标题由两部分组成&#xff1a;一个可能是作者的用户名jizb880&#xff0c;以及一个极具指向性的项目…...

开源笔记Memos与AI助手Copaw集成:打造自动化知识管理工作流

1. 项目概述&#xff1a;当开源笔记遇上AI助手最近在折腾个人知识管理工具&#xff0c;发现一个挺有意思的组合&#xff1a;Hailpeng的copaw-memos-integration。简单来说&#xff0c;它把两个独立但都很棒的工具给“焊”在了一起。一边是Memos&#xff0c;一个极简、开源、自部…...

5.【Python】Python3 运算符

第一步&#xff1a;分析与整理 运算符1. 什么是运算符&#xff1f; 运算符用于执行算术、比较、逻辑等操作。操作数是参与运算的值。例如 4 5 9 中&#xff0c;4 和 5 是操作数&#xff0c; 是运算符。 Python 支持以下运算符类型&#xff1a; 算术运算符比较&#xff08;关系…...

TouchGFX SPI屏移植避坑全记录:从下载算法到分散加载.sct文件

TouchGFX SPI屏移植实战&#xff1a;破解下载算法与分散加载的三大技术难点 当一块240x320的SPI接口屏幕在STM32F412RET6上流畅渲染出60帧的TouchGFX界面时&#xff0c;我盯着示波器上稳定的时序信号长舒一口气——这已经是本周第三次重写W25Q64的下载算法。与官方文档描述的&…...