调试系列1:bugreport源码篇

基于android 6.0, 分析bugreport过程

framework/native/cmds/bugreport/bugreport.cpp
framework/native/cmds/dumpstate/dumpstate.cpp
framework/native/cmds/dumpstate/utils.c

一、概述

通过adb命令可获取bugrepport信息,并输出到文件当前路径的bugreport.txt文件:

adb bugreport > bugreport.txt

对于Android系统调试分析,bugreport信息量非常之大,几乎涵盖整个系统各个层面内容,对于分析BUG是一大利器,本文先从从源码角度来分析一下Bugreport的实现原理。

二、原理分析

Android系统源码中framework/native/cmds/bugreport目录通过Android.mk定义了bugreport项目,在系统编译完成后会生成bugreport可执行文件,位于系统/system/bin/bugreport。当执行adb bugreport时,便会调用这个可执行文件,进入bugreport.cpp中的main()方法。

2.1 bugreport.main

[-> bugreport.cpp]

int main() {
  //启动dumpstate服务
  property_set("ctl.start", "dumpstate");
  //需要多次尝试,直到dumpstate服务启动完成,才能建立socket通信
  int s;
  for (int i = 0; i < 20; i++) {
    s = socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED,
                            SOCK_STREAM);
    if (s >= 0)
      break;
    //休眠1s后再次尝试连接
    sleep(1);
  }
  if (s == -1) {
    printf("Failed to connect to dumpstate service: %s\n", strerror(errno));
    return 1;
  }
  //当3分钟没有任何数据可读,则超时停止读取并退出。
  //dumpstate服务中不存在大于1分钟的timetout,因而不可预见的超时的情况下留有很大的回旋余地。
  struct timeval tv;
  tv.tv_sec = 3 * 60;
  tv.tv_usec = 0;
  if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
    printf("WARNING: Cannot set socket timeout: %s\n", strerror(errno));
  }
  while (1) {
    char buffer[65536];
    ssize_t bytes_read = TEMP_FAILURE_RETRY(read(s, buffer, sizeof(buffer)));
    if (bytes_read == 0) {
      break;
    } else if (bytes_read == -1) {
      // EAGAIN意味着timeout,Bugreport读异常终止
      if (errno == EAGAIN) {
        errno = ETIMEDOUT;
      }
      break;
    }
    ssize_t bytes_to_send = bytes_read;
    ssize_t bytes_written;
    //不断循环得将读取数据输出到stdout
    do {
      bytes_written = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
                       buffer + bytes_read - bytes_to_send, bytes_to_send));
      if (bytes_written == -1) {
        return 1; //将数据无法写入stdout
      }
      bytes_to_send -= bytes_written;
    } while (bytes_written != 0 && bytes_to_send > 0);
  }
  close(s);
  return 0;
}

property_set(“ctl.start”, “dumpstate”)会触发init进程,来fork进程/system/bin/dumpstate, 作为dumpstate服务的进程. Bugreport再通过socket建立于dumpstate的通信,这个过程会尝试20次socket连接建立直到成功连接。 在socket通道中如果持续3分钟没有任何数据可读,则超时停止读取并退出。由于dumpstate服务中不存在大于1分钟的timetout,因而不可预见的超时的情况下留有很大的回旋余地。

当从socket读取到数据后,写入到标准时输出或者重定向到文件。可见bugreport数据的来源都是dumpstate服务,那么接下来去看看dumpstate服务的工作。

2.2 dumpstate.main

[-> dumpstate.cpp]

int main(int argc, char *argv[]) {
    struct sigaction sigact;
    int do_add_date = 0;
    int do_vibrate = 1;
    char* use_outfile = 0;
    int use_socket = 0;
    int do_fb = 0;
    int do_broadcast = 0;
    if (getuid() != 0) {
        //兼容性考虑,旧版本支持直接调用dumpstate命令,新版本通过调用/system/bin/bugreport来替代。
        //当检测到直接调用,则强制执行bugreport命令。
        return execl("/system/bin/bugreport", "/system/bin/bugreport", NULL);
    }
    ALOGI("begin\n");
    //清空句柄SIGPIPE
    memset(&sigact, 0, sizeof(sigact));
    sigact.sa_handler = sigpipe_handler;
    sigaction(SIGPIPE, &sigact, NULL);
    //提高当前进程的优先级,防止被OOM Killer杀死
    setpriority(PRIO_PROCESS, 0, -20);
    FILE *oom_adj = fopen("/proc/self/oom_adj", "we");
    if (oom_adj) {
        fputs("-17", oom_adj);
        fclose(oom_adj);
    }
    //参数解析
    int c;
    while ((c = getopt(argc, argv, "dho:svqzpB")) != -1) {
        switch (c) {
            case 'd': do_add_date = 1;       break;
            case 'o': use_outfile = optarg;  break;
            case 's': use_socket = 1;        break;
            case 'v': break;  // compatibility no-op
            case 'q': do_vibrate = 0;        break;
            case 'p': do_fb = 1;             break;
            case 'B': do_broadcast = 1;      break;
            case '?': printf("\n");
            case 'h':
                usage();
                exit(1);
        }
    }
    //建立socket
    if (use_socket) {
        redirect_to_socket(stdout, "dumpstate");
    }
    //打开vibrator
    FILE *vibrator = 0;
    if (do_vibrate) {
        vibrator = fopen("/sys/class/timed_output/vibrator/enable", "we");
        if (vibrator) {
            vibrate(vibrator, 150);
        }
    }
    //读取/proc/cmdline
    FILE *cmdline = fopen("/proc/cmdline", "re");
    if (cmdline != NULL) {
        fgets(cmdline_buf, sizeof(cmdline_buf), cmdline);
        fclose(cmdline);
    }
    //收集虚拟机和native进程的stack traces(需要root权限)
    dump_traces_path = dump_traces();
    //获取tombstone文件描述符
    get_tombstone_fds(tombstone_data);
    //确保capabilities
    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
        ALOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
        return -1;
    }
    //切换到非root用户和组,在切换之前都是处于root权限
    gid_t groups[] = { AID_LOG, AID_SDCARD_R, AID_SDCARD_RW,
            AID_MOUNT, AID_INET, AID_NET_BW_STATS };
    if (setgroups(sizeof(groups)/sizeof(groups[0]), groups) != 0) {
        ALOGE("Unable to setgroups, aborting: %s\n", strerror(errno));
        return -1;
    }
    if (setgid(AID_SHELL) != 0) {
        ALOGE("Unable to setgid, aborting: %s\n", strerror(errno));
        return -1;
    }
    if (setuid(AID_SHELL) != 0) {
        ALOGE("Unable to setuid, aborting: %s\n", strerror(errno));
        return -1;
    }
    struct __user_cap_header_struct capheader;
    struct __user_cap_data_struct capdata[2];
    memset(&capheader, 0, sizeof(capheader));
    memset(&capdata, 0, sizeof(capdata));
    capheader.version = _LINUX_CAPABILITY_VERSION_3;
    capheader.pid = 0;
    capdata[CAP_TO_INDEX(CAP_SYSLOG)].permitted = CAP_TO_MASK(CAP_SYSLOG);
    capdata[CAP_TO_INDEX(CAP_SYSLOG)].effective = CAP_TO_MASK(CAP_SYSLOG);
    capdata[0].inheritable = 0;
    capdata[1].inheritable = 0;
    if (capset(&capheader, &capdata[0]) < 0) {
        ALOGE("capset failed: %s\n", strerror(errno));
        return -1;
    }
    //如果需要,则重定向输出
    char path[PATH_MAX], tmp_path[PATH_MAX];
    pid_t gzip_pid = -1;
    if (!use_socket && use_outfile) {
        strlcpy(path, use_outfile, sizeof(path));
        if (do_add_date) {
            char date[80];
            time_t now = time(NULL);
            strftime(date, sizeof(date), "-%Y-%m-%d-%H-%M-%S", localtime(&now));
            strlcat(path, date, sizeof(path));
        }
        if (do_fb) {
            strlcpy(screenshot_path, path, sizeof(screenshot_path));
            strlcat(screenshot_path, ".png", sizeof(screenshot_path));
        }
        strlcat(path, ".txt", sizeof(path));
        strlcpy(tmp_path, path, sizeof(tmp_path));
        strlcat(tmp_path, ".tmp", sizeof(tmp_path));
        redirect_to_file(stdout, tmp_path);
    }
    //这里是真正干活的地方 【见小节 2.3】
    dumpstate();
    //通过震动提醒已完成所有dump操作
    if (vibrator) {
        for (int i = 0; i < 3; i++) {
            vibrate(vibrator, 75);
            usleep((75 + 50) * 1000);
        }
        fclose(vibrator);
    }
    //等待gzip的完成,等进程退出时则会被杀
    if (gzip_pid > 0) {
        fclose(stdout);
        waitpid(gzip_pid, NULL, 0);
    }
    //重命名.tmp文件到最终位置
    if (use_outfile && rename(tmp_path, path)) {
        fprintf(stderr, "rename(%s, %s): %s\n", tmp_path, path, strerror(errno));
    }
    //通过发送广播告知ActivityManager已完成bugreport操作
    if (do_broadcast && use_outfile && do_fb) {
        run_command(NULL, 5, "/system/bin/am", "broadcast", "--user", "0",
                "-a", "android.intent.action.BUGREPORT_FINISHED",
                "--es", "android.intent.extra.BUGREPORT", path,
                "--es", "android.intent.extra.SCREENSHOT", screenshot_path,
                "--receiver-permission", "android.permission.DUMP", NULL);
    }
    ALOGI("done\n");
    return 0;
}

整个过程的工作流程:

  1. 提高执行dumpsate所在进程的优先级,防止被OOM Killer杀死;
  2. 参数解析,可通过命令adb shell dumpstate -h查看dumpstate命令所支持的参数;
  3. 打开vibrator,用于在执行bugreport时,手机会先震动一下用于提醒开始抓取系统信息;
  4. 通过dump_traces()来完成收集虚拟机和native进程的stack traces;
  5. 通过get_tombstone_fds来获取tombstone文件描述符;
  6. 开始执行切换到非root用户和组,在这之前的执行都处于root权限;
  7. 执行dumpstate(),这里是真正干活的地方
  8. 再次通过震动以提醒dump操作执行完成;
  9. 发送广播,告知ActivityManager已完成bugreport操作。

接下来就重点说说dumpstate()功能:

2.3 dumpstate()

该方法负责整个bugreport内容输出的最为核心的功能。

[-> dumpstate.cpp ]

static void dumpstate() {
    ...
    property_get("ro.build.display.id", build, "(unknown)");
    property_get("ro.build.fingerprint", fingerprint, "(unknown)");
    property_get("ro.build.type", build_type, "(unknown)");
    property_get("ro.baseband", radio, "(unknown)");
    property_get("ro.bootloader", bootloader, "(unknown)");
    property_get("gsm.operator.alpha", network, "(unknown)");
    strftime(date, sizeof(date), "%Y-%m-%d %H:%M:%S", localtime(&now));
    //开头信息
    printf("========================================================\n");
    printf("== dumpstate: %s\n", date);
    printf("========================================================\n");
    printf("\n");
    printf("Build: %s\n", build);
    printf("Build fingerprint: '%s'\n", fingerprint);
    printf("Bootloader: %s\n", bootloader);
    printf("Radio: %s\n", radio);
    printf("Network: %s\n", network);
    printf("Kernel: "); dump_file(NULL, "/proc/version");
    printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
    printf("\n");
    //记录系统运行时长和休眠时长
    run_command("UPTIME", 10, "uptime", NULL);

    //输出mmcblk0设备信息
    dump_files("UPTIME MMC PERF", mmcblk0, skip_not_stat, dump_stat_from_fd);

    dump_file("MEMORY INFO", "/proc/meminfo");
    run_command("CPU INFO", 10, "top", "-n", "1", "-d", "1", "-m", "30", "-t", NULL);
    run_command("PROCRANK", 20, "procrank", NULL);
    dump_file("VIRTUAL MEMORY STATS", "/proc/vmstat");
    dump_file("VMALLOC INFO", "/proc/vmallocinfo");
    dump_file("SLAB INFO", "/proc/slabinfo");
    dump_file("ZONEINFO", "/proc/zoneinfo");
    dump_file("PAGETYPEINFO", "/proc/pagetypeinfo");
    dump_file("BUDDYINFO", "/proc/buddyinfo");
    dump_file("FRAGMENTATION INFO", "/d/extfrag/unusable_index");
    dump_file("KERNEL WAKELOCKS", "/proc/wakelocks");
    dump_file("KERNEL WAKE SOURCES", "/d/wakeup_sources");
    dump_file("KERNEL CPUFREQ", "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state");
    dump_file("KERNEL SYNC", "/d/sync");
    run_command("PROCESSES", 10, "ps", "-P", NULL);
    run_command("PROCESSES AND THREADS", 10, "ps", "-t", "-p", "-P", NULL);
    run_command("PROCESSES (SELINUX LABELS)", 10, "ps", "-Z", NULL);
    run_command("LIBRANK", 10, "librank", NULL);

    //输出kernel log
    do_dmesg();

    //所有已打开文件
    run_command("LIST OF OPEN FILES", 10, SU_PATH, "root", "lsof", NULL);
    //遍历所有进程的show map
    for_each_pid(do_showmap, "SMAPS OF ALL PROCESSES");
    //显示所有线程的blocked位置
    for_each_tid(show_wchan, "BLOCKED PROCESS WAIT-CHANNELS");

    //SYSTEM LOG
    timeout = logcat_timeout("main") + logcat_timeout("system") + logcat_timeout("crash");
    if (timeout < 20000) {
        timeout = 20000;
    }
    run_command("SYSTEM LOG", timeout / 1000, "logcat", "-v", "threadtime", "-d", "*:v", NULL);

    //EVENT LOG
    timeout = logcat_timeout("events");
    if (timeout < 20000) {
        timeout = 20000;
    }
    run_command("EVENT LOG", timeout / 1000, "logcat", "-b", "events", "-v", "threadtime", "-d", "*:v", NULL);

    //RADIO LOG
    timeout = logcat_timeout("radio");
    if (timeout < 20000) {
        timeout = 20000;
    }
    run_command("RADIO LOG", timeout / 1000, "logcat", "-b", "radio", "-v", "threadtime", "-d", "*:v", NULL);

    //Log统计信息
    run_command("LOG STATISTICS", 10, "logcat", "-b", "all", "-S", NULL);

    //输出当前虚拟机和native进程的vm traces
    if (dump_traces_path != NULL) {
        dump_file("VM TRACES JUST NOW", dump_traces_path);
    }

    //输出上次发生ANR时vm traces,即路径/data/anr/traces.txt
    struct stat st;
    char anr_traces_path[PATH_MAX];
    property_get("dalvik.vm.stack-trace-file", anr_traces_path, "");
    if (!anr_traces_path[0]) {
        printf("*** NO VM TRACES FILE DEFINED (dalvik.vm.stack-trace-file)\n\n");
    } else {
      int fd = TEMP_FAILURE_RETRY(open(anr_traces_path,
                                   O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK));
      if (fd < 0) {
          printf("*** NO ANR VM TRACES FILE (%s): %s\n\n", anr_traces_path, strerror(errno));
      } else {
          dump_file_from_fd("VM TRACES AT LAST ANR", anr_traces_path, fd);
      }
    }

    //输出慢操作的vm traces,例如/data/anr/slow1.txt
    if (anr_traces_path[0] != 0) {
        int tail = strlen(anr_traces_path)-1;
        while (tail > 0 && anr_traces_path[tail] != '/') {
            tail--;
        }
        int i = 0;
        while (1) {
            //例如trace文件为/data/anr/slow1.txt
            sprintf(anr_traces_path+tail+1, "slow%02d.txt", i);
            if (stat(anr_traces_path, &st)) {
                break;
            }
            dump_file("VM TRACES WHEN SLOW", anr_traces_path);
            i++;
        }
    }

    //输出tombstone信息,NUM_TOMBSTONES=10,例如/data/tombstones/tombstone_1
    int dumped = 0;
    for (size_t i = 0; i < NUM_TOMBSTONES; i++) {
        if (tombstone_data[i].fd != -1) {
            dumped = 1;
            dump_file_from_fd("TOMBSTONE", tombstone_data[i].name, tombstone_data[i].fd);
            tombstone_data[i].fd = -1;
        }
    }
    if (!dumped) {
        printf("*** NO TOMBSTONES to dump in %s\n\n", TOMBSTONE_DIR);
    }

    dump_file("NETWORK DEV INFO", "/proc/net/dev");
    dump_file("QTAGUID NETWORK INTERFACES INFO", "/proc/net/xt_qtaguid/iface_stat_all");
    dump_file("QTAGUID NETWORK INTERFACES INFO (xt)", "/proc/net/xt_qtaguid/iface_stat_fmt");
    dump_file("QTAGUID CTRL INFO", "/proc/net/xt_qtaguid/ctrl");
    dump_file("QTAGUID STATS INFO", "/proc/net/xt_qtaguid/stats");

    //输出上次的kernel log
    if (!stat(PSTORE_LAST_KMSG, &st)) {
        //文件为/sys/fs/pstore/console-ramoops
        dump_file("LAST KMSG", PSTORE_LAST_KMSG);
    } else {
        //文件为/proc/last_kmsg
        dump_file("LAST KMSG", "/proc/last_kmsg");
    }

    //输出上次 logcat,内核必须设置CONFIG_PSTORE_PMSG
    run_command("LAST LOGCAT", 10, "logcat", "-L", "-v", "threadtime",
                                             "-b", "all", "-d", "*:v", NULL);

    //wifi驱动/固件 以及ip相关信息
    run_command("NETWORK INTERFACES", 10, "ip", "link", NULL);
    run_command("IPv4 ADDRESSES", 10, "ip", "-4", "addr", "show", NULL);
    run_command("IPv6 ADDRESSES", 10, "ip", "-6", "addr", "show", NULL);
    run_command("IP RULES", 10, "ip", "rule", "show", NULL);
    run_command("IP RULES v6", 10, "ip", "-6", "rule", "show", NULL);
    dump_route_tables();
    run_command("ARP CACHE", 10, "ip", "-4", "neigh", "show", NULL);
    run_command("IPv6 ND CACHE", 10, "ip", "-6", "neigh", "show", NULL);
    run_command("IPTABLES", 10, SU_PATH, "root", "iptables", "-L", "-nvx", NULL);
    run_command("IP6TABLES", 10, SU_PATH, "root", "ip6tables", "-L", "-nvx", NULL);
    run_command("IPTABLE NAT", 10, SU_PATH, "root", "iptables", "-t", "nat", "-L", "-nvx", NULL);
    run_command("IPTABLE RAW", 10, SU_PATH, "root", "iptables", "-t", "raw", "-L", "-nvx", NULL);
    run_command("IP6TABLE RAW", 10, SU_PATH, "root", "ip6tables", "-t", "raw", "-L", "-nvx", NULL);
    run_command("WIFI NETWORKS", 20, SU_PATH, "root", "wpa_cli", "IFNAME=wlan0", "list_networks", NULL);

    //中断向量表
    dump_file("INTERRUPTS (1)", "/proc/interrupts");
    run_command("NETWORK DIAGNOSTICS", 10, "dumpsys", "connectivity", "--diag", NULL);
    //中断向量表(二次输出)
    dump_file("INTERRUPTS (2)", "/proc/interrupts");

    //获取properties属性值
    print_properties();
    run_command("VOLD DUMP", 10, "vdc", "dump", NULL);
    run_command("SECURE CONTAINERS", 10, "vdc", "asec", "list", NULL);
    //可用空间
    run_command("FILESYSTEMS & FREE SPACE", 10, "df", NULL);
    run_command("LAST RADIO LOG", 10, "parse_radio_log", "/proc/last_radio_log", NULL);

    //背光信息
    printf("------ BACKLIGHTS ------\n");
    printf("LCD brightness="); dump_file(NULL, "/sys/class/leds/lcd-backlight/brightness");
    printf("Button brightness="); dump_file(NULL, "/sys/class/leds/button-backlight/brightness");
    printf("Keyboard brightness="); dump_file(NULL, "/sys/class/leds/keyboard-backlight/brightness");
    printf("ALS mode="); dump_file(NULL, "/sys/class/leds/lcd-backlight/als");
    printf("LCD driver registers:\n"); dump_file(NULL, "/sys/class/leds/lcd-backlight/registers");
    printf("\n");

    //Binder相关
    dump_file("BINDER FAILED TRANSACTION LOG", "/sys/kernel/debug/binder/failed_transaction_log");
    dump_file("BINDER TRANSACTION LOG", "/sys/kernel/debug/binder/transaction_log");
    dump_file("BINDER TRANSACTIONS", "/sys/kernel/debug/binder/transactions");
    dump_file("BINDER STATS", "/sys/kernel/debug/binder/stats");
    dump_file("BINDER STATE", "/sys/kernel/debug/binder/state");

    printf("========================================================\n");
    printf("== Board\n");
    printf("========================================================\n");
    dumpstate_board(); printf("\n");

    //输出framework各种服务的dumpsys信息
    printf("========================================================\n");
    printf("== Android Framework Services\n");
    printf("========================================================\n");
    run_command("DUMPSYS", 60, "dumpsys", NULL); //很耗时则timeout=60s

    printf("========================================================\n");
    printf("== Checkins\n");
    printf("========================================================\n");
    run_command("CHECKIN BATTERYSTATS", 30, "dumpsys", "batterystats", "-c", NULL);
    run_command("CHECKIN MEMINFO", 30, "dumpsys", "meminfo", "--checkin", NULL);
    run_command("CHECKIN NETSTATS", 30, "dumpsys", "netstats", "--checkin", NULL);
    run_command("CHECKIN PROCSTATS", 30, "dumpsys", "procstats", "-c", NULL);
    run_command("CHECKIN USAGESTATS", 30, "dumpsys", "usagestats", "-c", NULL);
    run_command("CHECKIN PACKAGE", 30, "dumpsys", "package", "--checkin", NULL);

    //输出当前 运行中activity/service/provider信息
    printf("========================================================\n");
    printf("== Running Application Activities\n");
    printf("========================================================\n");
    run_command("APP ACTIVITIES", 30, "dumpsys", "activity", "all", NULL);
    printf("========================================================\n");
    printf("== Running Application Services\n");
    printf("========================================================\n");
    run_command("APP SERVICES", 30, "dumpsys", "activity", "service", "all", NULL);
    printf("========================================================\n");
    printf("== Running Application Providers\n");
    printf("========================================================\n");
    run_command("APP SERVICES", 30, "dumpsys", "activity", "provider", "all", NULL);
    printf("========================================================\n");
    printf("== dumpstate: done\n");
    printf("========================================================\n");
}

该方法涉及run_command其他几个方法见下方:

2.3.1 run_command()

[-> utils.c]

int run_command(const char *title, int timeout_seconds, const char *command, ...) {
    fflush(stdout);
    uint64_t start = nanotime();
    //通过fork创建子进程
    pid_t pid = fork();
    if (pid < 0) {
        printf("*** fork: %s\n", strerror(errno));
        return pid;
    }

    //子进程执行
    if (pid == 0) {
        const char *args[1024] = {command};
        size_t arg;
        //确保dumpstate结束后能关闭子进程
        prctl(PR_SET_PDEATHSIG, SIGKILL);
        struct sigaction sigact;
        memset(&sigact, 0, sizeof(sigact));
        sigact.sa_handler = SIG_IGN;
        //忽略SIGPIPE
        sigaction(SIGPIPE, &sigact, NULL);
        va_list ap;
        va_start(ap, command);

        if (title) printf("------ %s (%s", title, command);
        for (arg = 1; arg < sizeof(args) / sizeof(args[0]); ++arg) {
            args[arg] = va_arg(ap, const char *);
            if (args[arg] == NULL) break;
            if (title) printf(" %s", args[arg]);
        }
        if (title) printf(") ------\n");
        fflush(stdout);
        //执行命令
        execvp(command, (char**) args);
        printf("*** exec(%s): %s\n", command, strerror(errno));
        fflush(stdout);
        _exit(-1); //进程退出
    }
    //父进程执行,主要处理子进程退出
    int status;
    bool ret = waitpid_with_timeout(pid, timeout_seconds, &status);
    uint64_t elapsed = nanotime() - start;
    if (!ret) {
        if (errno == ETIMEDOUT) {
            printf("*** %s: Timed out after %.3fs (killing pid %d)\n", command,
                   (float) elapsed / NANOS_PER_SEC, pid);
        } else {
            printf("*** %s: Error after %.4fs (killing pid %d)\n", command,
                   (float) elapsed / NANOS_PER_SEC, pid);
        }
        kill(pid, SIGTERM);
        if (!waitpid_with_timeout(pid, 5, NULL)) {
            kill(pid, SIGKILL);
            if (!waitpid_with_timeout(pid, 5, NULL)) {
                printf("*** %s: Cannot kill %d even with SIGKILL.\n", command, pid);
            }
        }
        return -1;
    }
    if (WIFSIGNALED(status)) {
        printf("*** %s: Killed by signal %d\n", command, WTERMSIG(status));
    } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
        printf("*** %s: Exit code %d\n", command, WEXITSTATUS(status));
    }
    if (title) printf("[%s: %.3fs elapsed]\n\n", command, (float)elapsed / NANOS_PER_SEC);
    return status;
}

功能是fork子进程并等待它执行完成,或者超时退出。当命令title不为空时,每次输出结果,都分别以下面作为开头和结尾:

------  (<command>) ------
[<command>: <执行时长> elapsed]

</code></pre> 
 <h4>2.3.2 dump_file() </h4> 
 <p>[-> utils.c]</p> 
 <pre><code>int dump_file(const char *title, const char *path) {
    //尝试打开文件
    int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
    if (fd < 0) {
        //无法打开文件时,则输出如下信息
        int err = errno;
        if (title) printf("------ %s (%s) ------\n", title, path);
        printf("*** %s: %s\n", path, strerror(err));
        if (title) printf("\n");
        return -1;
    }
    //输出文件内容
    return _dump_file_from_fd(title, path, fd);
}

</code></pre> 
 <p>当可以正确打开文件时,则执行_dump_file_from_fd,输出文件内容</p> 
 <pre><code>static int _dump_file_from_fd(const char *title, const char *path, int fd) {
    if (title) printf("------ %s (%s", title, path);
    if (title) {
        struct stat st;
        //文件路径为/proc/或者/sys/
        if (memcmp(path, "/proc/", 6) && memcmp(path, "/sys/", 5) && !fstat(fd, &st)) {
            char stamp[80];
            time_t mtime = st.st_mtime; //文件上次修改时间
            strftime(stamp, sizeof(stamp), "%Y-%m-%d %H:%M:%S", localtime(&mtime));
            printf(": %s", stamp);
        }
        printf(") ------\n");
    }
    bool newline = false;
    fd_set read_set;
    struct timeval tm;
    while (1) {
        FD_ZERO(&read_set);
        FD_SET(fd, &read_set);
        //30s无数据可读则超时
        tm.tv_sec = 30;
        tm.tv_usec = 0;
        uint64_t elapsed = nanotime();
        int ret = TEMP_FAILURE_RETRY(select(fd + 1, &read_set, NULL, NULL, &tm));
        if (ret == -1) {
            printf("*** %s: select failed: %s\n", path, strerror(errno));
            newline = true;
            break;
        } else if (ret == 0) {
            elapsed = nanotime() - elapsed;
            printf("*** %s: Timed out after %.3fs\n", path,
                   (float) elapsed / NANOS_PER_SEC);
            newline = true;
            break;
        } else {
            char buffer[65536];
            // 读取数据
            ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
            if (bytes_read > 0) {
                fwrite(buffer, bytes_read, 1, stdout);
                newline = (buffer[bytes_read-1] == '\n');
            } else {
                if (bytes_read == -1) {
                    printf("*** %s: Failed to read from fd: %s", path, strerror(errno));
                    newline = true;
                }
                break;
            }
        }
    }
    close(fd);
    if (!newline) printf("\n");
    if (title) printf("\n");
    return 0;
}

</code></pre> 
 <p>当打不开文件或者出错则输出:</p> 
 <pre><code>------ <title> (<path>) ------
*** <path>: <err>

</code></pre> 
 <p>当文件路径为/proc/或者/sys/,则输出时间/文件上次修改时间:</p> 
 <pre><code>------ <title> (<path>: <文件修改时间>) ------

</code></pre> 
 <h4>2.3.3 dump_files() </h4> 
 <p>dump_files(“UPTIME MMC PERF”, mmcblk0, skip_not_stat, dump_stat_from_fd);</p> 
 <p>其中skip_not_stat是指忽略mmcblk0目录下的非stat文件,dump_files该方法遍历输出mmcblk0(即”/sys/block/mmcblk0/”)目录下所有stat文件,具体的输出调用dump_stat_from_fd方法来完成,该方法输出每个分区的读写速度:</p> 
 <pre><code>static int dump_stat_from_fd(const char *title __unused, const char *path, int fd) {
    unsigned long fields[11], read_perf, write_perf;
    bool z;
    char *cp, *buffer = NULL;
    size_t i = 0;
    FILE *fp = fdopen(fd, "rb"); //打开文件
    getline(&buffer, &i, fp);
    fclose(fp);
    if (!buffer) {
        return -errno;
    }
    i = strlen(buffer);
    while ((i > 0) && (buffer[i - 1] == '\n')) {
        buffer[--i] = '\0';
    }
    if (!*buffer) {
        free(buffer);
        return 0;
    }
    z = true;
    for (cp = buffer, i = 0; i < (sizeof(fields) / sizeof(fields[0])); ++i) {
        fields[i] = strtol(cp, &cp, 0);
        if (fields[i] != 0) {
            z = false;
        }
    }
    if (z) { /* never accessed */
        free(buffer);
        return 0;
    }
    if (!strncmp(path, mmcblk0, sizeof(mmcblk0) - 1)) {
        path += sizeof(mmcblk0) - 1;
    }
    //例如输出/sys/block/mmcblk0/mmcblk0p13/stat内容
    printf("%s: %s\n", path, buffer);
    free(buffer);
    read_perf = 0;
    if (fields[3]) {
        //计算读的性能
        read_perf = 512 * fields[2] / fields[3];
    }
    write_perf = 0;
    if (fields[7]) {
        //计算写的性能
        write_perf = 512 * fields[6] / fields[7];
    }
    printf("%s: read: %luKB/s write: %luKB/s\n", path, read_perf, write_perf);
    //worst_write_perf默认值为20000kb/s
    if ((write_perf > 1) && (write_perf < worst_write_perf)) {
        worst_write_perf = write_perf;
    }
    return 0;
}

</code></pre> 
 <p>例如:stat文件共有11个数据:</p> 
 <pre><code>mmcblk0p13/stat:  15  369  100  10  57  7239  5000  250  0  900  2610

</code></pre> 
 <p>则mmcblk0p13/stat的read_perf = 512* 100/10 = 5120KB/s, write_perf= 512* 5000/250 = 10240KB/s</p> 
 <h4>2.3.4 dump_traces() </h4> 
 <p>dump虚拟机和native的stack traces,并返回trace文件位置</p> 
 <pre><code>const char *dump_traces() {
    const char* result = NULL;
    char traces_path[PROPERTY_VALUE_MAX] = "";

    //traces_path等于/data/anr/traces.txt
    property_get("dalvik.vm.stack-trace-file", traces_path, "");
    if (!traces_path[0]) return NULL;

    char anr_traces_path[PATH_MAX];
    将traces_path文件名拷贝到anr_traces_path
    strlcpy(anr_traces_path, traces_path, sizeof(anr_traces_path));
    //连接后,anr_traces_path变成了/data/anr/traces.txt.anr
    strlcat(anr_traces_path, ".anr", sizeof(anr_traces_path));

    //文件重命名, 将/data/anr/traces.txt文件重命名为/data/anr/traces.txt.anr
    if (rename(traces_path, anr_traces_path) && errno != ENOENT) {
        return NULL; //没有权限重命令
    }

    char anr_traces_dir[PATH_MAX];
    strlcpy(anr_traces_dir, traces_path, sizeof(anr_traces_dir));
    // *slash为/traces.txt
    char *slash = strrchr(anr_traces_dir, '/');
    if (slash != NULL) {
        *slash = '\0';
        //创建文件夹/data/anr/traces.txt/
        if (!mkdir(anr_traces_dir, 0775)) {
            chown(anr_traces_dir, AID_SYSTEM, AID_SYSTEM);
            chmod(anr_traces_dir, 0775);
            if (selinux_android_restorecon(anr_traces_dir, 0) == -1) {
                fprintf(stderr, "restorecon failed for %s: %s\n", anr_traces_dir, strerror(errno));
            }
        } 
    }

    //创建一个新的空文件/data/anr/traces.txt
    int fd = TEMP_FAILURE_RETRY(open(traces_path, O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
                                     0666));  /* -rw-rw-rw- */

    int chmod_ret = fchmod(fd, 0666);
    DIR *proc = opendir("/proc");

    //当进程完成dump操作时,通过inotify来通知
    int ifd = inotify_init();
    int wfd = inotify_add_watch(ifd, traces_path, IN_CLOSE_WRITE);

    struct dirent *d;
    int dalvik_found = 0;
    while ((d = readdir(proc))) {
        int pid = atoi(d->d_name);
        if (pid <= 0) continue;

        char path[PATH_MAX];
        char data[PATH_MAX];
        snprintf(path, sizeof(path), "/proc/%d/exe", pid);
        ssize_t len = readlink(path, data, sizeof(data) - 1);
        if (len <= 0) {
            continue;
        }
        data[len] = '\0';

        if (!strncmp(data, "/system/bin/app_process", strlen("/system/bin/app_process"))) {
            snprintf(path, sizeof(path), "/proc/%d/cmdline", pid);
            int cfd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_CLOEXEC));
            len = read(cfd, data, sizeof(data) - 1);
            close(cfd);
            if (len <= 0) {
                continue;
            }
            data[len] = '\0';
            //略过zygote,并不输出它的栈信息
            if (!strncmp(data, "zygote", strlen("zygote"))) {
                continue;
            }

            ++dalvik_found;
            uint64_t start = nanotime();
            if (kill(pid, SIGQUIT)) {
                fprintf(stderr, "kill(%d, SIGQUIT): %s\n", pid, strerror(errno));
                continue;
            }

            //等待来自inotify的可写关闭的通知
            struct pollfd pfd = { ifd, POLLIN, 0 };
            int ret = poll(&pfd, 1, 5000);  /* 5s超时*/
            if (ret < 0) {
                fprintf(stderr, "poll: %s\n", strerror(errno));
            } else if (ret == 0) {
                fprintf(stderr, "warning: timed out dumping pid %d\n", pid);
            } else {
                struct inotify_event ie;
                read(ifd, &ie, sizeof(ie));
            }

            if (lseek(fd, 0, SEEK_END) < 0) {
                fprintf(stderr, "lseek: %s\n", strerror(errno));
            } else {
                dprintf(fd, "[dump dalvik stack %d: %.3fs elapsed]\n",
                        pid, (float)(nanotime() - start) / NANOS_PER_SEC);
            }
        } else if (should_dump_native_traces(data)) {
            //native进程trace
            if (lseek(fd, 0, SEEK_END) < 0) {
                fprintf(stderr, "lseek: %s\n", strerror(errno));
            } else {
                static uint16_t timeout_failures = 0;
                uint64_t start = nanotime();

                /* If 3 backtrace dumps fail in a row, consider debuggerd dead. */
                if (timeout_failures == 3) {
                    dprintf(fd, "too many stack dump failures, skipping...\n");
                // 超时时长为20s
                } else if (dump_backtrace_to_file_timeout(pid, fd, 20) == -1) {
                    dprintf(fd, "dumping failed, likely due to a timeout\n");
                    timeout_failures++;
                } else {
                    timeout_failures = 0;
                }
                dprintf(fd, "[dump native stack %d: %.3fs elapsed]\n",
                        pid, (float)(nanotime() - start) / NANOS_PER_SEC);
            }
        }
    }

    static char dump_traces_path[PATH_MAX];
    //将/data/anr/tracex.txt字节拷贝到dump_traces_path
    strlcpy(dump_traces_path, traces_path, sizeof(dump_traces_path));
    //此时dump_traces_path就变成了/data/anr/tracex.txt.bugreport
    strlcat(dump_traces_path, ".bugreport", sizeof(dump_traces_path));
    if (rename(traces_path, dump_traces_path)) {
        goto error_close_ifd;
    }
    result = dump_traces_path;

    //再将/data/anr/traces.txt.anr 重命名回到/data/anr/traces.txt
    rename(anr_traces_path, traces_path);

    ...
    return result;
}

</code></pre> 
 <p>此处有多次文件名的拷贝/连接/重命名操作, 主要逻辑如下:</p> 
 <ol> 
  <li>首先,/data/anr/tracex.txt文件重命名为/data/anr/traces.txt.anr, 这样可以保护上次anr信息;</li> 
  <li>然后,bugreport输出的trace内容输出到/data/anr/tracex.txt文件, 然后再把该文件重命名为/data/anr/tracex.txt.bugreport;</li> 
  <li>最后,将/data/anr/traces.txt.anr文件名改为/data/anr/tracex.txt.</li> 
 </ol> 
 <p>其中,整个过程的效果就等价于将bugreport过程抓取的traces输出到/data/anr/tracex.txt.bugreport文件.</p> 
 <p>dump_traces主要完成如下两个功能的输出:</p> 
 <ul> 
  <li>输出Java进程的trace是通过发送signal 3来dump相应信息。</li> 
  <li>输出native进程的trace是通过dump_backtrace_to_file_timeout,并且超时时长为20s;</li> 
 </ul> 
 <h4>2.3.5 do_dmesg() </h4> 
 <pre><code>void do_dmesg() {
    printf("------ KERNEL LOG (dmesg) ------\n");
    //获取kernel buffer的大小
    int size = klogctl(KLOG_SIZE_BUFFER, NULL, 0);
    if (size <= 0) {
        printf("Unexpected klogctl return value: %d\n\n", size);
        return;
    }
    char *buf = (char *) malloc(size + 1);
    if (buf == NULL) {
        printf("memory allocation failed\n\n");
        return;
    }
    //获取kernel log
    int retval = klogctl(KLOG_READ_ALL, buf, size);
    if (retval < 0) {
        printf("klogctl failure\n\n");
        free(buf);
        return;
    }
    buf[retval] = '\0';
    printf("%s\n\n", buf);
    free(buf);
    return;
}

</code></pre> 
 <h3>2.4 总结 </h3> 
 <p>bugreport通过socket与dumpstate服务建立通信,在dumpstate.cpp中的dumpstate()方法完成核心功能,该功能依次输出内容项, 主要分为5大类:</p> 
 <ol> 
  <li> <strong>current log</strong>: kernel,system, event, radio;</li> 
  <li> <strong>last log</strong>: kernel, system, radio;</li> 
  <li> <strong>vm traces</strong>: just now, last ANR, tombstones</li> 
  <li> <strong>dumpsys</strong>: all, checkin, app</li> 
  <li> <strong>system info</strong>:cpu, memory, io等</li> 
 </ol> 
 <p>从bugreport内容的输出顺序的角度,再详细列举其内容:</p> 
 <ol> 
  <li>系统build以及运行时长等相关信息;</li> 
  <li>内存/CPU/进程等信息;</li> 
  <li> <code>kernel log</code>;</li> 
  <li>lsof、map及Wait-Channels;</li> 
  <li> <code>system log</code>;</li> 
  <li> <code>event log</code>;</li> 
  <li>radio log;</li> 
  <li> <code>vm traces</code>: 
   <ol> 
    <li>VM TRACES JUST NOW (/data/anr/traces.txt.bugreport) (抓bugreport时主动触发)</li> 
    <li>VM TRACES AT LAST ANR (/data/anr/traces.txt) (存在则输出)</li> 
    <li>TOMBSTONE (/data/tombstones/tombstone_xx) (存在这输出)</li> 
   </ol> </li> 
  <li>network相关信息;</li> 
  <li> <code>last kernel log</code>;</li> 
  <li> <code>last system log</code>;</li> 
  <li>ip相关信息;</li> 
  <li>中断向量表</li> 
  <li>property以及fs等信息</li> 
  <li>last radio log;</li> 
  <li>Binder相关信息;</li> 
  <li>dumpsys all:</li> 
  <li>dumpsys checkin相关:</li> 
 </ol> 
 <pre><code>*   dumpsys batterystats电池统计;
*   dumpsys meminfo内存
*   dumpsys netstats网络统计;
*   dumpsys procstats进程统计;
*   dumpsys usagestats使用情况;
*   dumpsys package.
</code></pre> 
 <ol start="19"> 
  <li>dumpsys app相关</li> 
 </ol> 
 <pre><code>*   dumpsys activity;
*   dumpsys activity service all;
*   dumpsys activity provider all.
</code></pre> 
 <p><strong>Tips</strong>: bugreport几乎涵盖整个系统信息,内容非常长,每一个子项都以<code>------ xxx ------</code>开头。 例如APP ACTIVITIES的开头便是 <code>------ APP ACTIVITIES (dumpsys activity all) ------</code>,其中括号内的便是输出该信息指令,即<code>dumpsys activity all</code>,还有可能是内容所在节点,各个子项目类似的规律,看完前面的源码分析过程,相信你肯定能明白。下面一篇文章再进一步从bugreport内容的角度来说明其寓意。</p> 
</article>
                            </div>
                        </div>
                    </div>
                    <!--PC和WAP自适应版-->
                    <div id="SOHUCS" sid="1493609632113909760"></div>
                    <script type="text/javascript" src="/views/front/js/chanyan.js"></script>
                    <!-- 文章页-底部 动态广告位 -->
                    <div class="youdao-fixed-ad" id="detail_ad_bottom"></div>
                </div>
                <div class="col-md-3">
                    <div class="row" id="ad">
                        <!-- 文章页-右侧1 动态广告位 -->
                        <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_1"> </div>
                        </div>
                        <!-- 文章页-右侧2 动态广告位 -->
                        <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_2"></div>
                        </div>
                        <!-- 文章页-右侧3 动态广告位 -->
                        <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad">
                            <div class="youdao-fixed-ad" id="detail_ad_3"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div class="container">
        <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(调试系列1:bugreport源码篇)</h4>
        <div id="paradigm-article-related">
            <div class="recommend-post mb30">
                <ul class="widget-links">
                    <li><a href="/article/1947960908932247552.htm"
                           title="ubuntu20.04.2上安装wordpress+nginx+php+fpm+mysql" target="_blank">ubuntu20.04.2上安装wordpress+nginx+php+fpm+mysql</a>
                        <span class="text-muted">crayon-shin-chan</span>
<a class="tag" taget="_blank" href="/search/surprise/1.htm">surprise</a><a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/wordpress/1.htm">wordpress</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/php/1.htm">php</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/wp/1.htm">wp</a>
                        <div>1.更新安装源sudoapt-getupdate2.安装nginxsudoaptinstallnginx检查状态servicenginxstatus●nginx.service-AhighperformancewebserverandareverseproxyserverLoaded:loaded(/lib/systemd/system/nginx.service;enabled;vendorpr</div>
                    </li>
                    <li><a href="/article/1947960548943523840.htm"
                           title="spring之事务管理" target="_blank">spring之事务管理</a>
                        <span class="text-muted">writeanewworld</span>

                        <div>1.spring简介spring中认为一切java类都是资源,而资源都是Bean,容纳这些Bean的是spring提供的Ioc容器,所以Spring是一种基于bean的编程。spring的作用主要是整合框架。2.spring中的事务管理,首先事务的基本概念就是一处报错,全部回滚。这也是spring事务管理的基本作用。3.spring事务管理分为xml跟注解案例:(1)实体类Employee.jav</div>
                    </li>
                    <li><a href="/article/1947960531029651456.htm"
                           title="全链路跟踪关键技术-ThreadLocal" target="_blank">全链路跟踪关键技术-ThreadLocal</a>
                        <span class="text-muted">txxs</span>
<a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a>
                        <div>转自:https://github.com/alibaba/transmittable-thread-local/issues/123应用场景的文章Java多线程上下文传递在复杂场景下的实践byvivo互联网技术(海外商城租户区分)2021-02-01SpringSecurityOAuth2.0认证授权五:用户信息扩展到jwt2021-01-14再谈Token认证,如何快速方便获取用户信息By尹吉</div>
                    </li>
                    <li><a href="/article/1947960293384581120.htm"
                           title="荆门地区亲子鉴定收费费用标准大全(附2024年最新报价)" target="_blank">荆门地区亲子鉴定收费费用标准大全(附2024年最新报价)</a>
                        <span class="text-muted">国医基因陈主任</span>

                        <div>荆门地区亲子鉴定多少钱费用?荆门地区dna亲子鉴定收费费用标准在2000元到5000元之间不等,亲子鉴定按(鉴定用途、鉴定对象)大致可分为:司法亲子鉴定、个人亲子鉴定、无创胎儿亲子鉴定;荆门地区亲子鉴定中心收费标准如下:荆门国医基因亲子鉴定中心地址:荆门市象山大道67号1、荆门地区个人亲子鉴定费用:2000元-2600元元,一般3-7天出结果;2、荆门地区司法亲子鉴定费用:2600元-3200元元</div>
                    </li>
                    <li><a href="/article/1947960294697398272.htm"
                           title="今天最开心的事" target="_blank">今天最开心的事</a>
                        <span class="text-muted">吴奕豪</span>

                        <div>今天,是放假的第二天。我很高兴,因为,今天我在奶奶家吃小鸡炖蘑菇了,蘑菇很好吃,鸡肉也很好吃。今天下午我在家吃西瓜啦!西瓜很凉快,又很甜。结果我忘了,我不能吃西瓜,我很难受,最后我把西瓜吐了。我妈妈说,你有病,别吃西瓜,我说,妈妈,我忘了,妈妈说有啥事不能忘,我说妈妈,我知道了。今天是2020年10月2日星期五</div>
                    </li>
                    <li><a href="/article/1947960027142746112.htm"
                           title="Agent架构与工作原理:理解智能体的核心机制" target="_blank">Agent架构与工作原理:理解智能体的核心机制</a>
                        <span class="text-muted">hdzw20</span>
<a class="tag" taget="_blank" href="/search/agent%E5%AD%A6%E4%B9%A0/1.htm">agent学习</a><a class="tag" taget="_blank" href="/search/ai/1.htm">ai</a><a class="tag" taget="_blank" href="/search/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/1.htm">机器学习</a><a class="tag" taget="_blank" href="/search/agent/1.htm">agent</a><a class="tag" taget="_blank" href="/search/%E6%99%BA%E8%83%BD%E4%BD%93/1.htm">智能体</a>
                        <div>Agent架构与工作原理:深入理解智能体的核心机制AIAgent的核心组成部分一个完整的AIAgent通常由以下几个核心模块组成:1.规划模块(PlanningModule)规划模块是Agent的"大脑",负责制定行动策略。它接收目标任务,分析当前状态,并制定一系列行动计划。规划可以是:短期规划:针对当前步骤的即时决策长期规划:面向整体目标的战略性规划动态规划:根据执行结果实时调整计划2.记忆模块</div>
                    </li>
                    <li><a href="/article/1947959900059529216.htm"
                           title="MySQL索引机制解析:B+树、索引类型与优化策略" target="_blank">MySQL索引机制解析:B+树、索引类型与优化策略</a>
                        <span class="text-muted">hdzw20</span>
<a class="tag" taget="_blank" href="/search/mysql%E5%A4%8D%E4%B9%A0/1.htm">mysql复习</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/b%E6%A0%91/1.htm">b树</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a>
                        <div>MySQL索引机制解析:B+树、索引类型与优化策略索引是MySQL数据库中提高查询效率的关键。深入理解索引的底层机制、不同类型及其优化策略,对于数据库性能调优和面试准备都至关重要。本文将围绕B+树、聚簇索引与非聚簇索引、索引下推、覆盖索引以及自适应哈希索引等核心概念进行阐述。1.B+树vsB树:为何MySQL选择B+树?B树(B-tree)和B+树(B±tree)都是常用的多路平衡查找树,它们旨在</div>
                    </li>
                    <li><a href="/article/1947959900759977984.htm"
                           title="MySQL存储引擎核心:了解Buffer Pool与Page管理机制" target="_blank">MySQL存储引擎核心:了解Buffer Pool与Page管理机制</a>
                        <span class="text-muted">hdzw20</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a>
                        <div>MySQL存储引擎核心:了解BufferPool与Page管理机制1.BufferPool:数据库的高速缓存1.1基本概念作用:缓存表数据与索引数据,减少磁盘IO组成:缓存数据页(Page,默认16KB)控制块(约800字节,记录表空间、页号、缓存页地址等)默认大小:128MB(控制块额外占用约5%内存)1.2工作流程查询过程:通过哈希表(Key=表空间号+页号)判断页是否在BufferPool缓</div>
                    </li>
                    <li><a href="/article/1947959521594896384.htm"
                           title="PyQt6基础_pyqtgraph_横向柱状图" target="_blank">PyQt6基础_pyqtgraph_横向柱状图</a>
                        <span class="text-muted">程序猿与金融与科技</span>
<a class="tag" taget="_blank" href="/search/PyQt6%E5%9F%BA%E7%A1%80/1.htm">PyQt6基础</a><a class="tag" taget="_blank" href="/search/PyQt6/1.htm">PyQt6</a><a class="tag" taget="_blank" href="/search/pyqtgraph/1.htm">pyqtgraph</a>
                        <div>效果:效果图显示的是2025Q1申万行业1,各行业的总资产柱状图代码:#-*-coding:utf-8-*-importnumpyasnpfromPyQt6.QtGuiimport(QColor)fromPyQt6.QtWidgetsimport(QApplication)importpyqtgraphaspgclassGraphHorizonalBarWidget(pg.PlotWidget):</div>
                    </li>
                    <li><a href="/article/1947959407434330112.htm"
                           title="适合女生的五大高薪职业 哪些工作前景好" target="_blank">适合女生的五大高薪职业 哪些工作前景好</a>
                        <span class="text-muted">氧惠购物达人</span>

                        <div>这几年每一位女性消费者们也希望个人的经济独立,这样就能够有花钱的自由,而且也能够展现出自己的魅力。那么女人在家干点啥最挣钱?1、开展一家小型的加工厂虽然说女人的体力不如男的高,但是现在很多的女性消费者们在做事情的时候并不比男性差,经营者们就可以在家经营一家小型的加工厂,比如说服装加工厂。2、制作手工工艺品总共一品这几年得到了迅速的发展,很多的消费者们对于手工艺品是非常的喜欢,不少的手工艺品进入到市</div>
                    </li>
                    <li><a href="/article/1947959013257834496.htm"
                           title="高省的邀请码怎么获取小心坑码获得高省邀请码步骤!" target="_blank">高省的邀请码怎么获取小心坑码获得高省邀请码步骤!</a>
                        <span class="text-muted">凌风导师</span>

                        <div>写点什么..推荐填联合创始人邀请码500888直升2皇冠,佣金更高,升级无忧,送万元推广大礼包,教授百度霸屏、强势引流技术!高省-各大应用商城下载即可-购物领劵返利高,邀请码切记填500888,凌风高省邀请码500888,全网唯一教你技术的老师码填对码直送2皇冠总裁等级,《凌风导师V:125130414》送价值百万引流技术推广绝密大礼包,也可以后台联系老师进高省官方群。星巴克说:每分享保举位新主顾</div>
                    </li>
                    <li><a href="/article/1947958757740834816.htm"
                           title="哪种粉底液好用?最好用的粉底液排行榜前十名" target="_blank">哪种粉底液好用?最好用的粉底液排行榜前十名</a>
                        <span class="text-muted">测评君高省</span>

                        <div>粉底液是我们日常化妆必不可少的产品,好的粉底液不仅对皮肤比较好,而且能够让我们的妆容更加精致持久,那么哪些粉底液是公认最好用的呢?今天小编为大家盘点了最好用的粉底液排行榜前十名,一起来看看吧!从哪里买便宜呢?通过高省APP(官方邀请码518518)购物,领券还能返佣!超级便宜~!分享赚钱,自用省钱!!高省APP佣金更高,模式更好,终端用户不流失。【高省】是一个自用省钱佣金高,分享推广赚钱多的平台,</div>
                    </li>
                    <li><a href="/article/1947958626853384192.htm"
                           title="手机兼职平台正规app有哪些?用手机做的正规兼职" target="_blank">手机兼职平台正规app有哪些?用手机做的正规兼职</a>
                        <span class="text-muted">古楼</span>

                        <div>很多用户想在自己空闲的时候找一份兼职的工作来赚一些零花钱,今天小编就来介绍一下找兼职哪个app靠谱2022,以方便用户们更快的找到一款合适的找兼职工作app。用户可以根据自己的需求下载不同的兼职app,以下是最新能找靠谱兼职的app前十名。1、高省app使用【高省app】网购,更便宜更划算!高省app上每天都有大额内部优惠券,还有返利佣金,而且高省的返利佣金在全网超高的!手机应用商城搜索【高省】直</div>
                    </li>
                    <li><a href="/article/1947958628141035520.htm"
                           title="《度五行》生活报报壬子140:浪的好可以引领新潮流,创造新时代" target="_blank">《度五行》生活报报壬子140:浪的好可以引领新潮流,创造新时代</a>
                        <span class="text-muted">YangduSam2021</span>

                        <div>221026壬寅庚戌壬子,20221026,周三,兴大上海六班2590天,西交大2013上海班3290天,后TA15410天,度生活700天,《度.生活五行》:天干金生水,壬壬反吟。地支寅戌合。浪,什么叫做浪?你觉得什么是浪?浪一般用来形容人表现出玩世不恭、自信膨胀、不按常理出牌的行为和心理状态,带有无所谓、我行我素、行为随意、举止轻佻的意味。浪用做动词的时候,是玩的意思。一般被他人用“浪”字形容</div>
                    </li>
                    <li><a href="/article/1947958624340996096.htm"
                           title="大脑也需要休息,需要保养" target="_blank">大脑也需要休息,需要保养</a>
                        <span class="text-muted">剑飞在思考</span>

                        <div>image1有没有这种感觉,就是在晚上10点以后,如果有事情没有立刻去做,就会拖拖拉拉。为什么不鼓励熬夜呢?因为晚上这段时间,人的整体意志力不是特别强。从时间安排角度来说,强度和质量都不够。早一点睡,早一点起,反而会更好一些。每天衡量效率高不高的一个标准,不是看睡之前多么疲劳,而是多么不疲劳。在时间记录体系里,有一个标签是『健康休闲』。“休息一下”,是指发呆的情况,或者是大脑不知道做了什么,不知道</div>
                    </li>
                    <li><a href="/article/1947958513519095808.htm"
                           title="Java注解家族--`@ResponseBody`" target="_blank">Java注解家族--`@ResponseBody`</a>
                        <span class="text-muted"></span>

                        <div>@ResponseBody@ResponseBody是Spring框架中的一个注解,在基于Spring的Web开发中扮演着重要角色,以下是对它的详细总结:1.定义与基本功能定义:@ResponseBody注解用于将Controller方法的返回值,通过适当的HttpMessageConverter转换为指定格式后写入HTTP响应体中返回给客户端。它可以应用在方法上,也可以用在类上(当用在类上时,表</div>
                    </li>
                    <li><a href="/article/1947958494644727808.htm"
                           title="2022-10-14" target="_blank">2022-10-14</a>
                        <span class="text-muted">巴赫与赋格</span>

                        <div>我最近心情不算很好。坐在咖啡厅里,每一次能有时间想一想最近发生的事,都是在这样的环境里。我不喜欢我现在的状态,不喜欢听我之前听过的音乐,不想把自己刻意浸润在某一个状态里了。那些情绪化的片段仿佛来自另一个世界,我同样不喜欢听之前两三个月一直播放的音乐,但我最近也没有精力去听新歌。大多数时候像我们这样的普通人忙忙碌碌只为了碎银几两。看着同龄人参与工作,其实我也算参与了一半。即便是没有把全部的时间花在上</div>
                    </li>
                    <li><a href="/article/1947958495903019008.htm"
                           title="2023-01-05" target="_blank">2023-01-05</a>
                        <span class="text-muted">短发娃</span>

                        <div>在办公室的下午,我觉得有很严重的缺氧,头部,有些难受。我觉得在工作上有些受阻的感觉,是那种不能挑起看看大局的感觉,因为对后面流程的不了解,加上有些心急,就成了这个样子。看到自己的局限:着急想了解全部,心太大导致够不着,这样给自己的压力挺大的。静下来告诉自己:你已经在努力了,别着急,慢慢来!</div>
                    </li>
                    <li><a href="/article/1947958387106967552.htm"
                           title="JVM基础篇1 - Class的加载" target="_blank">JVM基础篇1 - Class的加载</a>
                        <span class="text-muted">The小可</span>
<a class="tag" taget="_blank" href="/search/JVM/1.htm">JVM</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/ide/1.htm">ide</a>
                        <div>JVM基础篇1-Class的加载JVM基础篇2-指令集JVM进阶篇1-内存模型JVM进阶篇2-GC垃圾回收JVM总览-JVM架构引入jvm面试题:谈谈对jvm的理解?java8虚拟机和之前的变化更新?什么是OOM,什么是栈溢出StackOverFlowError?怎么分析JVM常用的调优参数有哪些?内存快照如何抓取?怎么分析Dump文件?知道吗?谈谈jVM,类加载器的认识?1.什么是JVM?jvm</div>
                    </li>
                    <li><a href="/article/1947958260204105728.htm"
                           title="nginx 配置 https http" target="_blank">nginx 配置 https http</a>
                        <span class="text-muted">R_miss</span>
<a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/https/1.htm">https</a><a class="tag" taget="_blank" href="/search/http/1.htm">http</a>
                        <div>nginx配置https域名访问参考文档https://blog.csdn.net/heng_yan/article/details/938740351.域名已经分配好这时需要和项目进行关联,能通过域名访问连接调通分配的域名:https://xxx.com2个证书文件:xxx.key文件,xxx.pem证书放在服务器上的使用本地命令把文件放在服务器上2.进入liunx服务器先查询服务器是否配置过n</div>
                    </li>
                    <li><a href="/article/1947958260749365248.htm"
                           title="python排序算法之桶排序" target="_blank">python排序算法之桶排序</a>
                        <span class="text-muted">华强笔记</span>
<a class="tag" taget="_blank" href="/search/python%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95/1.htm">python数据结构和算法</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a>
                        <div>桶排序主要适用于全是数字的列表排序代码如下:defbuckrt_sort(li,n=100,max_num=10000):bucket=[[]for_inrange(n)]</div>
                    </li>
                    <li><a href="/article/1947958241740779520.htm"
                           title="20180722【剽悍行动营8】DAY1 嘉宾分享——赵周《碎片化时代你最缺的知识管理五招》" target="_blank">20180722【剽悍行动营8】DAY1 嘉宾分享——赵周《碎片化时代你最缺的知识管理五招》</a>
                        <span class="text-muted">英娟儿</span>

                        <div>补课五、自己学习后的五个收获:1.区分两类知识管理:追求知识本身;追求致用与成长。2.便签学习法的三个维度:A用自己的语言重述信息(理解)A1描述自己相关经验(内化)A2规划自己的目标与行动3.一切不改变行动的知识管理都是浪费。也就是说,不管是何种知识管理,都要以行动为目的。4.信息和知识的区别,又一次听到这两个概念的区别。5.构建知识体系是知识管理的最高境界。三、自己需要改善的(三个方面):1.</div>
                    </li>
                    <li><a href="/article/1947958007551815680.htm"
                           title="【PGCCC】PostgreSQL 18 Beta 1发布,有哪些亮点?" target="_blank">【PGCCC】PostgreSQL 18 Beta 1发布,有哪些亮点?</a>
                        <span class="text-muted">PGCCC-PostgeSQL培训认证</span>
<a class="tag" taget="_blank" href="/search/postgresql/1.htm">postgresql</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a>
                        <div>PostgreSQL全球开发组于2025年5月8日发布了第一个PostgreSQL18Beta版本,现已开放下载。虽然细节可能会有所改变,但是该版本包含了PostgreSQL18最终正式版中所有新功能的预览。以下是PostgreSQL18引入的部分关键功能亮点。性能优化异步I/O(AIO),通过全新的异步I/O子系统提升I/O吞吐量并隐藏延迟。Linux系统可使用io_uring,其他平台则提供基</div>
                    </li>
                    <li><a href="/article/1947958008021577728.htm"
                           title="【PGCCC】Postgres 18 Beta 版发布:您应该了解的 7 个功能" target="_blank">【PGCCC】Postgres 18 Beta 版发布:您应该了解的 7 个功能</a>
                        <span class="text-muted">PGCCC-PostgeSQL培训认证</span>
<a class="tag" taget="_blank" href="/search/postgresql/1.htm">postgresql</a>
                        <div>Postgres18Beta1刚刚发布。与之前的主要版本一样,此Beta版本包含所有计划正式发布的功能的预览版。您可以阅读发行说明来了解完整的更新列表,但我们将在本文中重点介绍一些更新亮点。Postgres18中的新功能异步I/OPostgres18最令人兴奋的功能之一是引入了全新的异步I/O子系统。此前,Postgres中的所有I/O都是同步的:每次读取都会阻塞查询执行,直到数据到达。而使用异步</div>
                    </li>
                    <li><a href="/article/1947957984927739904.htm"
                           title="MySQL新建用户与授权" target="_blank">MySQL新建用户与授权</a>
                        <span class="text-muted">守优</span>

                        <div>方法一:mysql>insertintomysql.user(Host,User,Password)values("localhost","zhangs",password("123456"));mysql>flushprivileges;解释:这样就创建了一个用户名为zhangs,密码为123456的数据库用户;此处的"localhost",是指该用户只能在本地登录,不能在另外一台机器上远程登录</div>
                    </li>
                    <li><a href="/article/1947957983673643008.htm"
                           title="在淘宝上买月饼怎么领优惠券?" target="_blank">在淘宝上买月饼怎么领优惠券?</a>
                        <span class="text-muted">氧惠帮朋友一起省</span>

                        <div>1.通过淘宝APP领券中心领取打开淘宝APP,在首页顶部导航栏找到“领券中心”或类似栏目,点击进入。在领券中心页面,可以通过搜索关键词(如“月饼”)来查找相关的优惠券,或者直接浏览推荐的优惠券列表。找到心仪的月饼优惠券后,点击“领取”按钮即可。领取成功后,在购买月饼时会自动使用优惠券,享受优惠价格。2.在商品详情页领取在淘宝搜索框中输入月饼相关的关键词,找到想要购买的月饼商品。进入商品详情页后,留</div>
                    </li>
                    <li><a href="/article/1947957730723557376.htm"
                           title="成功日记(Day806)" target="_blank">成功日记(Day806)</a>
                        <span class="text-muted">狮子座的兔子姑娘</span>

                        <div>1、跟l斌约好周一见。2、摘豆角。~0.25h。3、给爸买水泵遥控器。4、和y月简单聊天。~0.25h。5、和谢哥微信闲聊。~0.25h。6、和g夙微信闲唠嗑。~0.5h。7、淘宝协商退掉了买的梨膏糖。~0.25h。8、淘宝买一丢丢吃的。~0.25h。9、帮妈妈串苏子叶。~0.5h。心情:尚可。</div>
                    </li>
                    <li><a href="/article/1947957601031483392.htm"
                           title="抖音极速版邀请码是多少,2024新版抖音极速版邀请码有哪些?" target="_blank">抖音极速版邀请码是多少,2024新版抖音极速版邀请码有哪些?</a>
                        <span class="text-muted">熊熊福利</span>

                        <div>抖音极速版邀请码是多少呢?拉到文章末尾就可以看到!在文章的结尾可以看到抖音极速版邀请码是多少。抖音极速版邀请码怎么填写第1步首先打开【抖音极速版】客户端,然后点击底部【来赚钱】第2步接着点击【填写邀请码】右侧的【去填写】第3步再输入邀请码,最后点击【立即领取】即可。2024年抖音极速版为了回馈广大用户朋友的支持和厚爱,特推出邀请好友填写邀请码来获得一系列特权的活动。只要您是首次下载抖音极速版,并且</div>
                    </li>
                    <li><a href="/article/1947957602356883456.htm"
                           title="华为P30系列海外已发布,OPPO Reno更稳了!" target="_blank">华为P30系列海外已发布,OPPO Reno更稳了!</a>
                        <span class="text-muted">灵感科技</span>

                        <div>华为P30在巴黎正式发布,作为年度的摄影旗舰,特别是P30Pro可以说是目前手机影像的天花板机型,特别是在暗光拍照上以及长焦拍摄上的表现着实让人感觉到惊艳。在长焦拍摄上,华为P30Pro在硬件上采用了潜望式的长焦镜头,10倍长焦焦段为125mm,结合华为自己的技术可以获得大约10倍混合变焦以及50倍的数码变焦,单看参数华为P30Pro已经比大部分的数码相机要强上一些。但是毕竟还是无损数码变焦,即便</div>
                    </li>
                    <li><a href="/article/1947957503320977408.htm"
                           title="/etc/profile 和 ~/.bashrc" target="_blank">/etc/profile 和 ~/.bashrc</a>
                        <span class="text-muted"></span>

                        <div>/etc/profile和~/.bashrc是Linux系统中用于配置BashShell环境的两个重要文件,它们在作用范围、加载时机和使用场景上有显著区别。以下是详细对比:1.作用范围对比文件作用范围影响对象/etc/profile系统全局对所有用户生效~/.bashrc用户级仅对当前用户生效/etc/profile是系统级配置文件,适合设置所有用户共享的环境(如全局PATH、语言设置等)~/.b</div>
                    </li>
                                <li><a href="/article/102.htm"
                                       title="xml解析" target="_blank">xml解析</a>
                                    <span class="text-muted">小猪猪08</span>
<a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a>
                                    <div>1、DOM解析的步奏 
准备工作: 
   1.创建DocumentBuilderFactory的对象 
   2.创建DocumentBuilder对象 
   3.通过DocumentBuilder对象的parse(String fileName)方法解析xml文件 
   4.通过Document的getElem</div>
                                </li>
                                <li><a href="/article/229.htm"
                                       title="每个开发人员都需要了解的一个SQL技巧" target="_blank">每个开发人员都需要了解的一个SQL技巧</a>
                                    <span class="text-muted">brotherlamp</span>
<a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/linux%E8%A7%86%E9%A2%91/1.htm">linux视频</a><a class="tag" taget="_blank" href="/search/linux%E6%95%99%E7%A8%8B/1.htm">linux教程</a><a class="tag" taget="_blank" href="/search/linux%E8%87%AA%E5%AD%A6/1.htm">linux自学</a><a class="tag" taget="_blank" href="/search/linux%E8%B5%84%E6%96%99/1.htm">linux资料</a>
                                    <div>  
对于数据过滤而言CHECK约束已经算是相当不错了。然而它仍存在一些缺陷,比如说它们是应用到表上面的,但有的时候你可能希望指定一条约束,而它只在特定条件下才生效。 
使用SQL标准的WITH CHECK OPTION子句就能完成这点,至少Oracle和SQL Server都实现了这个功能。下面是实现方式: 
CREATE TABLE books ( 
  id &</div>
                                </li>
                                <li><a href="/article/356.htm"
                                       title="Quartz——CronTrigger触发器" target="_blank">Quartz——CronTrigger触发器</a>
                                    <span class="text-muted">eksliang</span>
<a class="tag" taget="_blank" href="/search/quartz/1.htm">quartz</a><a class="tag" taget="_blank" href="/search/CronTrigger/1.htm">CronTrigger</a>
                                    <div>转载请出自出处:http://eksliang.iteye.com/blog/2208295 一.概述 
CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。 二.Cron表达式介绍 1)Cron表达式规则表 
Quartz</div>
                                </li>
                                <li><a href="/article/483.htm"
                                       title="Informatica基础" target="_blank">Informatica基础</a>
                                    <span class="text-muted">18289753290</span>
<a class="tag" taget="_blank" href="/search/Informatica/1.htm">Informatica</a><a class="tag" taget="_blank" href="/search/Monitor/1.htm">Monitor</a><a class="tag" taget="_blank" href="/search/manager/1.htm">manager</a><a class="tag" taget="_blank" href="/search/workflow/1.htm">workflow</a><a class="tag" taget="_blank" href="/search/Designer/1.htm">Designer</a>
                                    <div>1. 
1)PowerCenter Designer:设计开发环境,定义源及目标数据结构;设计转换规则,生成ETL映射。 
2)Workflow  Manager:合理地实现复杂的ETL工作流,基于时间,事件的作业调度 
3)Workflow  Monitor:监控Workflow和Session运行情况,生成日志和报告 
4)Repository  Manager:</div>
                                </li>
                                <li><a href="/article/610.htm"
                                       title="linux下为程序创建启动和关闭的的sh文件,scrapyd为例" target="_blank">linux下为程序创建启动和关闭的的sh文件,scrapyd为例</a>
                                    <span class="text-muted">酷的飞上天空</span>
<a class="tag" taget="_blank" href="/search/scrapy/1.htm">scrapy</a>
                                    <div>对于一些未提供service管理的程序  每次启动和关闭都要加上全部路径,想到可以做一个简单的启动和关闭控制的文件 
  
下面以scrapy启动server为例,文件名为run.sh: 
  
  
#端口号,根据此端口号确定PID
PORT=6800
#启动命令所在目录
HOME='/home/jmscra/scrapy/'

#查询出监听了PORT端口</div>
                                </li>
                                <li><a href="/article/737.htm"
                                       title="人--自私与无私" target="_blank">人--自私与无私</a>
                                    <span class="text-muted">永夜-极光</span>

                                    <div>            今天上毛概课,老师提出一个问题--人是自私的还是无私的,根源是什么? 
  
            从客观的角度来看,人有自私的行为,也有无私的</div>
                                </li>
                                <li><a href="/article/864.htm"
                                       title="Ubuntu安装NS-3 环境脚本" target="_blank">Ubuntu安装NS-3 环境脚本</a>
                                    <span class="text-muted">随便小屋</span>
<a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a>
                                    <div>  
将附件下载下来之后解压,将解压后的文件ns3environment.sh复制到下载目录下(其实放在哪里都可以,就是为了和我下面的命令相统一)。输入命令: 
  
sudo ./ns3environment.sh >>result 
  
这样系统就自动安装ns3的环境,运行的结果在result文件中,如果提示 
  
  
com</div>
                                </li>
                                <li><a href="/article/991.htm"
                                       title="创业的简单感受" target="_blank">创业的简单感受</a>
                                    <span class="text-muted">aijuans</span>
<a class="tag" taget="_blank" href="/search/%E5%88%9B%E4%B8%9A%E7%9A%84%E7%AE%80%E5%8D%95%E6%84%9F%E5%8F%97/1.htm">创业的简单感受</a>
                                    <div>  
       2009年11月9日我进入a公司实习,2012年4月26日,我离开a公司,开始自己的创业之旅。 
     今天是2012年5月30日,我忽然很想谈谈自己创业一个月的感受。 
当初离开边锋时,我就对自己说:“自己选择的路,就是跪着也要把他走完”,我也做好了心理准备,准备迎接一次次的困难。我这次走出来,不管成败</div>
                                </li>
                                <li><a href="/article/1118.htm"
                                       title="如何经营自己的独立人脉" target="_blank">如何经营自己的独立人脉</a>
                                    <span class="text-muted">aoyouzi</span>
<a class="tag" taget="_blank" href="/search/%E5%A6%82%E4%BD%95%E7%BB%8F%E8%90%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E7%8B%AC%E7%AB%8B%E4%BA%BA%E8%84%89/1.htm">如何经营自己的独立人脉</a>
                                    <div>独立人脉不是父母、亲戚的人脉,而是自己主动投入构造的人脉圈。“放长线,钓大鱼”,先行投入才能产生后续产出。       现在几乎做所有的事情都需要人脉。以银行柜员为例,需要拉储户,而其本质就是社会人脉,就是社交!很多人都说,人脉我不行,因为我爸不行、我妈不行、我姨不行、我舅不行……我谁谁谁都不行,怎么能建立人脉?我这里说的人脉,是你的独立人脉。       以一个普通的银行柜员</div>
                                </li>
                                <li><a href="/article/1245.htm"
                                       title="JSP基础" target="_blank">JSP基础</a>
                                    <span class="text-muted">百合不是茶</span>
<a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a><a class="tag" taget="_blank" href="/search/%E6%B3%A8%E9%87%8A/1.htm">注释</a><a class="tag" taget="_blank" href="/search/%E9%9A%90%E5%BC%8F%E5%AF%B9%E8%B1%A1/1.htm">隐式对象</a>
                                    <div>  
1,JSP语句的声明 
<%! 声明 %>       声明:这个就是提供java代码声明变量、方法等的场所。

表达式 <%= 表达式 %>      这个相当于赋值,可以在页面上显示表达式的结果,

程序代码段/小型指令 <% 程序代码片段 %>  
  
2,JSP的注释 
  
 <!-- --> </div>
                                </li>
                                <li><a href="/article/1372.htm"
                                       title="web.xml之session-config、mime-mapping" target="_blank">web.xml之session-config、mime-mapping</a>
                                    <span class="text-muted">bijian1013</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/web.xml/1.htm">web.xml</a><a class="tag" taget="_blank" href="/search/servlet/1.htm">servlet</a><a class="tag" taget="_blank" href="/search/session-config/1.htm">session-config</a><a class="tag" taget="_blank" href="/search/mime-mapping/1.htm">mime-mapping</a>
                                    <div>session-config 
1.定义: 
<session-config>
 <session-timeout>20</session-timeout>
</session-config> 
2.作用:用于定义整个WEB站点session的有效期限,单位是分钟。 
  
mime-mapping 
1.定义: 
<mime-m</div>
                                </li>
                                <li><a href="/article/1499.htm"
                                       title="互联网开放平台(1)" target="_blank">互联网开放平台(1)</a>
                                    <span class="text-muted">Bill_chen</span>
<a class="tag" taget="_blank" href="/search/%E4%BA%92%E8%81%94%E7%BD%91/1.htm">互联网</a><a class="tag" taget="_blank" href="/search/qq/1.htm">qq</a><a class="tag" taget="_blank" href="/search/%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A/1.htm">新浪微博</a><a class="tag" taget="_blank" href="/search/%E7%99%BE%E5%BA%A6/1.htm">百度</a><a class="tag" taget="_blank" href="/search/%E8%85%BE%E8%AE%AF/1.htm">腾讯</a>
                                    <div>现在各互联网公司都推出了自己的开放平台供用户创造自己的应用,互联网的开放技术欣欣向荣,自己总结如下: 
1.淘宝开放平台(TOP) 
网址:http://open.taobao.com/ 
依赖淘宝强大的电子商务数据,将淘宝内部业务数据作为API开放出去,同时将外部ISV的应用引入进来。 
目前TOP的三条主线: 
TOP访问网站:open.taobao.com 
ISV后台:my.open.ta</div>
                                </li>
                                <li><a href="/article/1626.htm"
                                       title="【MongoDB学习笔记九】MongoDB索引" target="_blank">【MongoDB学习笔记九】MongoDB索引</a>
                                    <span class="text-muted">bit1129</span>
<a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a>
                                    <div>索引 
 
 可以在任意列上建立索引 
 索引的构造和使用与传统关系型数据库几乎一样,适用于Oracle的索引优化技巧也适用于Mongodb 
 使用索引可以加快查询,但同时会降低修改,插入等的性能 
 内嵌文档照样可以建立使用索引 
 测试数据 
  
  
var p1 = {
"name":"Jack",
"age&q</div>
                                </li>
                                <li><a href="/article/1753.htm"
                                       title="JDBC常用API之外的总结" target="_blank">JDBC常用API之外的总结</a>
                                    <span class="text-muted">白糖_</span>
<a class="tag" taget="_blank" href="/search/jdbc/1.htm">jdbc</a>
                                    <div> 做JAVA的人玩JDBC肯定已经很熟练了,像DriverManager、Connection、ResultSet、Statement这些基本类大家肯定很常用啦,我不赘述那些诸如注册JDBC驱动、创建连接、获取数据集的API了,在这我介绍一些写框架时常用的API,大家共同学习吧。 
  
  
 
 ResultSetMetaData获取ResultSet对象的元数据信息 
 </div>
                                </li>
                                <li><a href="/article/1880.htm"
                                       title="apache VelocityEngine使用记录" target="_blank">apache VelocityEngine使用记录</a>
                                    <span class="text-muted">bozch</span>
<a class="tag" taget="_blank" href="/search/VelocityEngine/1.htm">VelocityEngine</a>
                                    <div>VelocityEngine是一个模板引擎,能够基于模板生成指定的文件代码。 
  
使用方法如下: 
    VelocityEngine engine = new VelocityEngine();// 定义模板引擎 
    Properties properties = new Properties();// 模板引擎属</div>
                                </li>
                                <li><a href="/article/2007.htm"
                                       title="编程之美-快速找出故障机器" target="_blank">编程之美-快速找出故障机器</a>
                                    <span class="text-muted">bylijinnan</span>
<a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BE%8E/1.htm">编程之美</a>
                                    <div>
package beautyOfCoding;

import java.util.Arrays;

public class TheLostID {

	/*编程之美 
	 假设一个机器仅存储一个标号为ID的记录,假设机器总量在10亿以下且ID是小于10亿的整数,假设每份数据保存两个备份,这样就有两个机器存储了同样的数据。
		1.假设在某个时间得到一个数据文件ID的列表,是</div>
                                </li>
                                <li><a href="/article/2134.htm"
                                       title="关于Java中redirect与forward的区别" target="_blank">关于Java中redirect与forward的区别</a>
                                    <span class="text-muted">chenbowen00</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/servlet/1.htm">servlet</a>
                                    <div>在Servlet中两种实现: 
 
forward方式:request.getRequestDispatcher(“/somePage.jsp”).forward(request, response); 
 
redirect方式:response.sendRedirect(“/somePage.jsp”); 
 
forward是服务器内部重定向,程序收到请求后重新定向到另一个程序,客户机并不知</div>
                                </li>
                                <li><a href="/article/2261.htm"
                                       title="[信号与系统]人体最关键的两个信号节点" target="_blank">[信号与系统]人体最关键的两个信号节点</a>
                                    <span class="text-muted">comsci</span>
<a class="tag" taget="_blank" href="/search/%E7%B3%BB%E7%BB%9F/1.htm">系统</a>
                                    <div> 
 
 
        如果把人体看做是一个带生物磁场的导体,那么这个导体有两个很重要的节点,第一个在头部,中医的名称叫做 百汇穴, 另外一个节点在腰部,中医的名称叫做 命门 
 
        如果要保护自己的脑部磁场不受到外界有害信号的攻击,最简单的</div>
                                </li>
                                <li><a href="/article/2388.htm"
                                       title="oracle 存储过程执行权限" target="_blank">oracle 存储过程执行权限</a>
                                    <span class="text-muted">daizj</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E5%AD%98%E5%82%A8%E8%BF%87%E7%A8%8B/1.htm">存储过程</a><a class="tag" taget="_blank" href="/search/%E6%9D%83%E9%99%90/1.htm">权限</a><a class="tag" taget="_blank" href="/search/%E6%89%A7%E8%A1%8C%E8%80%85/1.htm">执行者</a><a class="tag" taget="_blank" href="/search/%E8%B0%83%E7%94%A8%E8%80%85/1.htm">调用者</a>
                                    <div>在数据库系统中存储过程是必不可少的利器,存储过程是预先编译好的为实现一个复杂功能的一段Sql语句集合。它的优点我就不多说了,说一下我碰到的问题吧。我在项目开发的过程中需要用存储过程来实现一个功能,其中涉及到判断一张表是否已经建立,没有建立就由存储过程来建立这张表。 
 
CREATE OR REPLACE PROCEDURE TestProc  
IS  
  fla</div>
                                </li>
                                <li><a href="/article/2515.htm"
                                       title="为mysql数据库建立索引" target="_blank">为mysql数据库建立索引</a>
                                    <span class="text-muted">dengkane</span>
<a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E6%80%A7%E8%83%BD/1.htm">性能</a><a class="tag" taget="_blank" href="/search/%E7%B4%A2%E5%BC%95/1.htm">索引</a>
                                    <div>前些时候,一位颇高级的程序员居然问我什么叫做索引,令我感到十分的惊奇,我想这绝不会是沧海一粟,因为有成千上万的开发者(可能大部分是使用MySQL的)都没有受过有关数据库的正规培训,尽管他们都为客户做过一些开发,但却对如何为数据库建立适当的索引所知较少,因此我起了写一篇相关文章的念头。  最普通的情况,是为出现在where子句的字段建一个索引。为方便讲述,我们先建立一个如下的表。</div>
                                </li>
                                <li><a href="/article/2642.htm"
                                       title="学习C语言常见误区 如何看懂一个程序 如何掌握一个程序以及几个小题目示例" target="_blank">学习C语言常见误区 如何看懂一个程序 如何掌握一个程序以及几个小题目示例</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a>
                                    <div>如果看懂一个程序,分三步 
  
1、流程 
  
2、每个语句的功能 
  
3、试数 
  
如何学习一些小算法的程序 
尝试自己去编程解决它,大部分人都自己无法解决 
如果解决不了就看答案 
关键是把答案看懂,这个是要花很大的精力,也是我们学习的重点 
看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义 
照着答案去敲 
调试错误 
</div>
                                </li>
                                <li><a href="/article/2769.htm"
                                       title="centos6.3安装php5.4报错" target="_blank">centos6.3安装php5.4报错</a>
                                    <span class="text-muted">dcj3sjt126com</span>
<a class="tag" taget="_blank" href="/search/centos6/1.htm">centos6</a>
                                    <div>报错内容如下: 
Resolving Dependencies 
--> Running transaction check 
---> Package php54w.x86_64 0:5.4.38-1.w6 will be installed 
--> Processing Dependency: php54w-common(x86-64) = 5.4.38-1.w6 for </div>
                                </li>
                                <li><a href="/article/2896.htm"
                                       title="JSONP请求" target="_blank">JSONP请求</a>
                                    <span class="text-muted">flyer0126</span>
<a class="tag" taget="_blank" href="/search/jsonp/1.htm">jsonp</a>
                                    <div>  
    使用jsonp不能发起POST请求。 
It is not possible to make a JSONP POST request.
JSONP works by creating a <script> tag that executes Javascript from a different domain; it is not pos</div>
                                </li>
                                <li><a href="/article/3023.htm"
                                       title="Spring Security(03)——核心类简介" target="_blank">Spring Security(03)——核心类简介</a>
                                    <span class="text-muted">234390216</span>
<a class="tag" taget="_blank" href="/search/Authentication/1.htm">Authentication</a>
                                    <div>核心类简介 
目录 
1.1     Authentication 
1.2     SecurityContextHolder 
1.3     AuthenticationManager和AuthenticationProvider 
1.3.1  &nb</div>
                                </li>
                                <li><a href="/article/3150.htm"
                                       title="在CentOS上部署JAVA服务" target="_blank">在CentOS上部署JAVA服务</a>
                                    <span class="text-muted">java--hhf</span>
<a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a><a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a><a class="tag" taget="_blank" href="/search/Java%E6%9C%8D%E5%8A%A1/1.htm">Java服务</a>
                                    <div>    本文将介绍如何在CentOS上运行Java Web服务,其中将包括如何搭建JAVA运行环境、如何开启端口号、如何使得服务在命令执行窗口关闭后依旧运行 
    
第一步:卸载旧Linux自带的JDK 
①查看本机JDK版本 
java -version 
   结果如下 
java version "1.6.0"</div>
                                </li>
                                <li><a href="/article/3277.htm"
                                       title="oracle、sqlserver、mysql常用函数对比[to_char、to_number、to_date]" target="_blank">oracle、sqlserver、mysql常用函数对比[to_char、to_number、to_date]</a>
                                    <span class="text-muted">ldzyz007</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/SQL+Server/1.htm">SQL Server</a>
                                    <div>oracle                                &n</div>
                                </li>
                                <li><a href="/article/3404.htm"
                                       title="记Protocol Oriented Programming in Swift of WWDC 2015" target="_blank">记Protocol Oriented Programming in Swift of WWDC 2015</a>
                                    <span class="text-muted">ningandjin</span>
<a class="tag" taget="_blank" href="/search/protocol/1.htm">protocol</a><a class="tag" taget="_blank" href="/search/WWDC+2015/1.htm">WWDC 2015</a><a class="tag" taget="_blank" href="/search/Swift2.0/1.htm">Swift2.0</a>
                                    <div>其实最先朋友让我就这个题目写篇文章的时候,我是拒绝的,因为觉得苹果就是在炒冷饭, 把已经流行了数十年的OOP中的“面向接口编程”还拿来讲,看完整个Session之后呢,虽然还是觉得在炒冷饭,但是毕竟还是加了蛋的,有些东西还是值得说说的。 
 
通常谈到面向接口编程,其主要作用是把系统设计和具体实现分离开,让系统的每个部分都可以在不影响别的部分的情况下,改变自身的具体实现。接口的设计就反映了系统</div>
                                </li>
                                <li><a href="/article/3531.htm"
                                       title="搭建 CentOS 6 服务器(15) - Keepalived、HAProxy、LVS" target="_blank">搭建 CentOS 6 服务器(15) - Keepalived、HAProxy、LVS</a>
                                    <span class="text-muted">rensanning</span>
<a class="tag" taget="_blank" href="/search/keepalived/1.htm">keepalived</a>
                                    <div>(一)Keepalived 
 
(1)安装 
 
# cd /usr/local/src
# wget http://www.keepalived.org/software/keepalived-1.2.15.tar.gz
# tar zxvf keepalived-1.2.15.tar.gz
# cd keepalived-1.2.15
# ./configure
# make &a</div>
                                </li>
                                <li><a href="/article/3658.htm"
                                       title="ORACLE数据库SCN和时间的互相转换" target="_blank">ORACLE数据库SCN和时间的互相转换</a>
                                    <span class="text-muted">tomcat_oracle</span>
<a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a>
                                    <div>SCN(System Change Number 简称 SCN)是当Oracle数据库更新后,由DBMS自动维护去累积递增的一个数字,可以理解成ORACLE数据库的时间戳,从ORACLE 10G开始,提供了函数可以实现SCN和时间进行相互转换;     
用途:在进行数据库的还原和利用数据库的闪回功能时,进行SCN和时间的转换就变的非常必要了;     
 操作方法:     1、通过dbms_f</div>
                                </li>
                                <li><a href="/article/3785.htm"
                                       title="Spring MVC 方法注解拦截器" target="_blank">Spring MVC 方法注解拦截器</a>
                                    <span class="text-muted">xp9802</span>
<a class="tag" taget="_blank" href="/search/spring+mvc/1.htm">spring mvc</a>
                                    <div>应用场景,在方法级别对本次调用进行鉴权,如api接口中有个用户唯一标示accessToken,对于有accessToken的每次请求可以在方法加一个拦截器,获得本次请求的用户,存放到request或者session域。 
python中,之前在python flask中可以使用装饰器来对方法进行预处理,进行权限处理 
先看一个实例,使用@access_required拦截:    
?      </div>
                                </li>
                </ul>
            </div>
        </div>
    </div>

<div>
    <div class="container">
        <div class="indexes">
            <strong>按字母分类:</strong>
            <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a
                href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a
                href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a
                href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a
                href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a
                href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a
                href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a
                href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a
                href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a>
        </div>
    </div>
</div>
<footer id="footer" class="mb30 mt30">
    <div class="container">
        <div class="footBglm">
            <a target="_blank" href="/">首页</a> -
            <a target="_blank" href="/custom/about.htm">关于我们</a> -
            <a target="_blank" href="/search/Java/1.htm">站内搜索</a> -
            <a target="_blank" href="/sitemap.txt">Sitemap</a> -
            <a target="_blank" href="/custom/delete.htm">侵权投诉</a>
        </div>
        <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved.
<!--            <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>-->
        </div>
    </div>
</footer>
<!-- 代码高亮 -->
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script>
<script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script>
<link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/>
<script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script>





</body>

</html>