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

Posted 泡沫o0

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器相关的知识,希望对你有一定的参考价值。

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

引言

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

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

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

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

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


崩溃信息的类型

  • 堆栈跟踪(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 here
        default:
            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> tokensstd::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)获取进程的资源限制,如最大内存使用量、最大文件描述符数量等。


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

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

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


优化信号处理函数

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

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

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


获取线程信息

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

线程列表:使用 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)获取可用的处理器数量,以评估线程并发能力。

示例

#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;


使用异步信号安全的函数

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

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


总结

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

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

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

[转]linux 系统监控诊断工具之 IO wait

1、问题:

最近在做日志的实时同步,上线之前是做过单份线上日志压力测试的,消息队列和客户端、本机都没问题,但是没想到上了第二份日志之后,问题来了:

集群中的某台机器 top 看到负载巨高,集群中的机器硬件配置一样,部署的软件都一样,却单单这一台负载有问题,初步猜测可能硬件有问题了。

同时,我们还需要把负载有异常的罪魁祸首揪出来,到时候从软件、硬件层面分别寻找解决方案。

2、排查:

从 top 中可以看到 load average 偏高,%wa 很高,%us 偏低:

技术分享

从上图我们大致可以推断 IO 遇到了瓶颈,下面我们可以再用相关的 IO 诊断工具,具体的验证排查下。

 

PS:如果你对 top 的用法不了解,请参考我去年写的一篇博文:

linux 系统监控、诊断工具之 top 详解

常用组合方式有如下几种:

  • 用vmstat、sar、iostat检测是否是CPU瓶颈

  • 用free、vmstat检测是否是内存瓶颈

  • 用iostat、dmesg 检测是否是磁盘I/O瓶颈

  • 用netstat检测是否是网络带宽瓶颈

2.1 vmstat

vmstat命令的含义为显示虚拟内存状态(“Viryual Memor Statics”),但是它可以报告关于进程、内存、I/O等系统整体运行状态。

技术分享
它的相关字段说明如下:

Procs(进程)
? r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)
? b: 等待IO的进程数量,也就是处在非中断睡眠状态的进程数,展示了正在执行和等待CPU资源的任务个数。当这个值超过了CPU数目,就会出现CPU瓶颈了

Memory(内存)
? swpd: 使用虚拟内存大小,如果swpd的值不为0,但是SI,SO的值长期为0,这种情况不会影响系统性能。
? free: 空闲物理内存大小。
? buff: 用作缓冲的内存大小。
? cache: 用作缓存的内存大小,如果cache的值大的时候,说明cache处的文件数多,如果频繁访问到的文件都能被cache处,那么磁盘的读IO bi会非常小。

Swap
? si: 每秒从交换区写到内存的大小,由磁盘调入内存。
? so: 每秒写入交换区的内存大小,由内存调入磁盘。
注意:内存够用的时候,这2个值都是0,如果这2个值长期大于0时,系统性能会受到影响,磁盘IO和CPU资源都会被消耗。有些朋友看到空闲内存(free)很少的或接近于0时,就认为内存不够用了,不能光看这一点,还要结合si和so,如果free很少,但是si和so也很少(大多时候是0),那么不用担心,系统性能这时不会受到影响的。

IO(现在的Linux版本块的大小为1kb)
? bi: 每秒读取的块数
? bo: 每秒写入的块数
注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。

system(系统)
? in: 每秒中断数,包括时钟中断。
? cs: 每秒上下文切换数。
注意:上面2个值越大,会看到由内核消耗的CPU时间会越大。

CPU(以百分比表示)
? us: 用户进程执行时间百分比(user time)
us的值比较高时,说明用户进程消耗的CPU时间多,但是如果长期超50%的使用,那么我们就该考虑优化程序算法或者进行加速。
? sy: 内核系统进程执行时间百分比(system time)
sy的值高时,说明系统内核消耗的CPU资源多,这并不是良性表现,我们应该检查原因。
? wa: IO等待时间百分比
wa的值高时,说明IO等待比较严重,这可能由于磁盘大量作随机访问造成,也有可能磁盘出现瓶颈(块操作)。
? id: 空闲时间百分比

从 vmstat 中可以看到,CPU大部分的时间浪费在等待IO上面,可能是由于大量的磁盘随机访问或者磁盘的带宽所造成的,bi、bo 也都超过 1024k,应该是遇到了IO瓶颈。

2.2 iostat

下面再用更加专业的磁盘 IO 诊断工具来看下相关统计数据。

技术分享
它的相关字段说明如下:

rrqm/s:    每秒进行 merge 的读操作数目。即 delta(rmerge)/s
wrqm/s:    每秒进行 merge 的写操作数目。即 delta(wmerge)/s
r/s:       每秒完成的读 I/O 设备次数。即 delta(rio)/s
w/s:       每秒完成的写 I/O 设备次数。即 delta(wio)/s
rsec/s:    每秒读扇区数。即 delta(rsect)/s
wsec/s:    每秒写扇区数。即 delta(wsect)/s
rkB/s:     每秒读K字节数。是 rsect/s 的一半,因为每扇区大小为512字节。(需要计算)
wkB/s:     每秒写K字节数。是 wsect/s 的一半。(需要计算)
avgrq-sz:  平均每次设备I/O操作的数据大小 (扇区)。delta(rsect+wsect)/delta(rio+wio)
avgqu-sz:  平均I/O队列长度。即 delta(aveq)/s/1000 (因为aveq的单位为毫秒)。
await:     平均每次设备I/O操作的等待时间 (毫秒)。即 delta(ruse+wuse)/delta(rio+wio)
svctm:     平均每次设备I/O操作的服务时间 (毫秒)。即 delta(use)/delta(rio+wio)
%util:     一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000 (因为use的单位为毫秒)

可以看到两块硬盘中的 sdb 的利用率已经 100%,存在严重的 IO 瓶颈,下一步我们就是要找出哪个进程在往这块硬盘读写数据。 

2.3 iotop

技术分享

根据 iotop 的结果,我们迅速的定位到是 flume 进程的问题,造成了大量的 IO wait。

但是在开头我已经说了,集群中的机器配置一样,部署的程序也都 rsync 过去的一模一样,难道是硬盘坏了?

这得找运维同学来查证了,最后的结论是:

Sdb为双盘raid1,使用raid卡为“LSI Logic / Symbios Logic SAS1068E”,无cache。近400的IOPS压力已经达到了硬件极限。而其它机器使用的raid卡是“LSI Logic / Symbios Logic MegaRAID SAS 1078”,有256MB  cache,并未达到硬件瓶颈,解决办法是更换能提供更大IOPS的机器,比如最后我们换了一台带 PERC6/i 集成RAID控制器卡的机器。需要说明的是,raid信息是在raid卡和磁盘固件里面各存一份,磁盘上的raid信息和raid卡上面的信息格式要是匹配的,否则raid卡识别不了就需要格式化磁盘。
IOPS本质上取决于磁盘本身,但是又很多提升IOPS的方法,加硬件cache、采用RAID阵列是常用的办法。如果是DB那种IOPS很高的场景,现在流行用SSD来取代传统的机械硬盘。
不过前面也说了,我们从软硬件两方面着手的目的就是看能否分别寻求代价最小的解决方案:

知道硬件的原因了,我们可以尝试把读写操作移到另一块盘,然后再看看效果:

技术分享

3、最后的话:另辟蹊径

其实,除了用上述专业的工具定位这个问题外,我们可以直接利用进程状态来找到相关的进程。

我们知道进程有如下几种状态:

PROCESS STATE CODES
 D uninterruptible sleep (usually IO)
 R running or runnable (on run queue)
 S interruptible sleep (waiting for an event to complete)
 T stopped, either by a job control signal or because it is being traced.
 W paging (not valid since the 2.6.xx kernel)
 X dead (should never be seen)
 Z defunct ("zombie") process, terminated but not reaped by its parent.

其中状态为 D 的一般就是由于 wait IO 而造成所谓的”非中断睡眠“,我们可以从这点入手然后一步步的定位问题:

for x in `seq 10`; do ps -eo state,pid,cmd | grep "^D"; echo "----"; sleep 5; done
 D 248 [jbd2/dm-0-8]
 D 16528 bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
 ----
 D 22 [kdmflush]
 D 16528 bonnie++ -n 0 -u 0 -r 239 -s 478 -f -b -d /tmp
 ----
# 或者:
while true; do date; ps auxf | awk {if($8=="D") print $0;}; sleep 1; done
 Tue Aug 23 20:03:54 CLT 2011
 root       302  0.0  0.0      0     0 ?        D    May22   2:58  \_ [kdmflush]
 root       321  0.0  0.0      0     0 ?        D    May22   4:11  \_ [jbd2/dm-0-8]
 Tue Aug 23 20:03:55 CLT 2011
 Tue Aug 23 20:03:56 CLT 2011

cat /proc/16528/io
 rchar: 48752567
 wchar: 549961789
 syscr: 5967
 syscw: 67138
 read_bytes: 49020928
 write_bytes: 549961728
 cancelled_write_bytes: 0
 
lsof -p 16528
 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
 bonnie++ 16528 root cwd DIR 252,0 4096 130597 /tmp
 <truncated>
 bonnie++ 16528 root 8u REG 252,0 501219328 131869 /tmp/Bonnie.16528
 bonnie++ 16528 root 9u REG 252,0 501219328 131869 /tmp/Bonnie.16528
 bonnie++ 16528 root 10u REG 252,0 501219328 131869 /tmp/Bonnie.16528
 bonnie++ 16528 root 11u REG 252,0 501219328 131869 /tmp/Bonnie.16528
 bonnie++ 16528 root 12u REG 252,0 501219328 131869 <strong>/tmp/Bonnie.16528</strong>
 
df /tmp
 Filesystem 1K-blocks Used Available Use% Mounted on
 /dev/mapper/workstation-root 7667140 2628608 4653920 37% /
 
fuser -vm /tmp
        USER        PID ACCESS COMMAND
 /tmp:  db2fenc1   1067 ....m db2fmp
        db2fenc1   1071 ....m db2fmp
        db2fenc1   2560 ....m db2fmp
        db2fenc1   5221 ....m db2fmp

4、Refer:

[1] Troubleshooting High I/O Wait in Linux
        ——A walkthrough on how to find processes that are causing high I/O Wait on Linux Systems
http://bencane.com/2012/08/06/troubleshooting-high-io-wait-in-linux/

[2] 理解Linux系统负荷

http://www.ruanyifeng.com/blog/2011/07/linux_load_average_explained.html

[3] 24 iostat, vmstat and mpstat Examples for Linux Performance Monitoring

http://www.thegeekstuff.com/2011/07/iostat-vmstat-mpstat-examples/

[4] vmstat vmstat命令
http://man.linuxde.net/vmstat

[5] Linux vmstat命令实战详解

http://www.cnblogs.com/ggjucheng/archive/2012/01/05/2312625.html
[6] 影响Linux服务器性能的因素

http://www.rocklv.net/2004/news/article_284.html

[7] linux磁盘IO查看iostat,vmstat

http://blog.csdn.net/qiudakun/article/details/4699587

[8] What Process is using all of my disk IO

http://stackoverflow.com/questions/488826/what-process-is-using-all-of-my-disk-io

[9] Linux Wait IO Problem

http://www.chileoffshore.com/en/interesting-articles/126-linux-wait-io-problem

[10] Tracking Down High IO Wait in Linux

http://ostatic.com/blog/tracking-down-high-io-wait-in-linux

[11] 磁盘IOPS计算与测量

http://blog.csdn.net/liuaigui/article/details/6168186

[12] [DOC]磁盘性能指标—IOPS - Huawei

http://www.huawei.com/ecommunity/3msimage/download-10053641-10023111-fbcd1a056196d26a1a30032a222a5ec3.bin?type=bbs

[13] RAID卡

http://baike.baidu.com/view/95439.htm

[14] Linux下的一些I/O统计工具

http://blogread.cn/it/article/5716?f=wb

[15] 一次性能优化,tps从400+到4k+

http://bit.ly/29WaL5F

 

转自:https://my.oschina.net/leejun2005/blog/355915









以上是关于Linux C/C++ 崩溃诊断大师:解锁软件问题定位与修复的秘密武器的主要内容,如果未能解决你的问题,请参考以下文章

Linux 上的崩溃分析

杂谈 个人感想 方便以后回味

诊断卡跑过99到A2停留在启动菜单上就不动了..哪位大师帮忙哦!!

Intellij 诊断崩溃问题

如何诊断日食崩溃?

诊断 ADL 崩溃?