调试系列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/1950233451282100224.htm"
                           title="python 读excel每行替换_Python脚本操作Excel实现批量替换功能" target="_blank">python 读excel每行替换_Python脚本操作Excel实现批量替换功能</a>
                        <span class="text-muted">weixin_39646695</span>
<a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E8%AF%BBexcel%E6%AF%8F%E8%A1%8C%E6%9B%BF%E6%8D%A2/1.htm">读excel每行替换</a>
                        <div>Python脚本操作Excel实现批量替换功能大家好,给大家分享下如何使用Python脚本操作Excel实现批量替换。使用的工具Openpyxl,一个处理excel的python库,处理excel,其实针对的就是WorkBook,Sheet,Cell这三个最根本的元素~明确需求原始excel如下我们的目标是把下面excel工作表的sheet1表页A列的内容“替换我吧”批量替换为B列的“我用来替换的</div>
                    </li>
                    <li><a href="/article/1950233199242178560.htm"
                           title="x86-64汇编语言训练程序与实战" target="_blank">x86-64汇编语言训练程序与实战</a>
                        <span class="text-muted">十除以十等于一</span>

                        <div>本文还有配套的精品资源,点击获取简介:汇编语言是一种低级语言,与机器代码紧密相关,特别适用于编写系统级代码及性能要求高的应用。nasm编译器是针对x86和x86-64架构的汇编语言编译器,支持多种语法风格和指令集。项目Euler提供数学和计算机科学问题,鼓励编程技巧应用,前100个问题的答案可共享。x86-64架构扩展了寄存器数量并引入新指令,提升了数据处理效率。学习汇编语言能够深入理解计算机底层</div>
                    </li>
                    <li><a href="/article/1950233167633903616.htm"
                           title="男士护肤品哪个牌子好?十大男士护肤品排行榜" target="_blank">男士护肤品哪个牌子好?十大男士护肤品排行榜</a>
                        <span class="text-muted">高省APP珊珊</span>

                        <div>很多男生意识到护肤的必要性,开始着手护肤,但不知道该选哪个男士护肤品品牌使用好。目前市面上很多男士护肤品品牌,可谓琳琅满目,让人眼花缭乱。男士挑选护肤品时,根据自己皮肤需求去正规渠道挑选合适的知名护肤品比较放心靠谱。高省APP,是2021年推出的平台,0投资,0风险、高省APP佣金更高,模式更好,终端用户不流失。【高省】是一个自用省钱佣金高,分享推广赚钱多的平台,百度有几百万篇报道,也期待你的加入</div>
                    </li>
                    <li><a href="/article/1950233072825856000.htm"
                           title="三菱PLC全套学习资料及应用手册" target="_blank">三菱PLC全套学习资料及应用手册</a>
                        <span class="text-muted">good2know</span>

                        <div>本文还有配套的精品资源,点击获取简介:三菱PLC作为工业自动化领域的核心设备,其系列产品的学习和应用需要全面深入的知识。本次资料包为学习者提供从基础到进阶的全方位学习资源,包括各种型号PLC的操作手册、编程指南、软件操作教程以及实际案例分析,旨在帮助用户系统掌握PLC的编程语言、指令系统及在各类工业应用中的实施。1.三菱PLC基础知识入门1.1PLC的基本概念可编程逻辑控制器(PLC)是工业自动化</div>
                    </li>
                    <li><a href="/article/1950233040592629760.htm"
                           title="2022-10-20" target="_blank">2022-10-20</a>
                        <span class="text-muted">体力劳动者</span>

                        <div>不因感觉稍纵即逝就不加记录。在女儿睡觉后我记下今天的小故事。接手新班级后,今天是第二次收到家长的感谢信(微信)。是我表扬次数最多的两位学生家长致来的感谢,他们明显感受到孩子自信、阳光了不少,写作业由被动变为了主动,家庭氛围也由鸡飞狗跳变成了其乐融融。在被顽皮的学生气得头晕之后,我感到了久违的价值感,责任感甚至使命感,我回复家长这样一句话:我们也需要家长的反馈好让我们的教育工作更有劲头。我也认识到,</div>
                    </li>
                    <li><a href="/article/1950232912192401408.htm"
                           title="程翔授《评价一篇记叙文》" target="_blank">程翔授《评价一篇记叙文》</a>
                        <span class="text-muted">行吟斯基</span>

                        <div>桂林十一中高一2中学生自读程老师学生文章板书课题师巡看。看完举手。问:它是记叙文。不商量。独立打分。学生评价打分。师:高低都正常,不受干扰。师巡,略评。打完举手。调查:分层次举手——高分先举手。最低分。最高95分。最低45分。女:差距太大!师:同一篇,相差55分。若是你的文章,愿落谁手?男:身临其境感觉。师:你有此经历?没也没关系。女:不优美……,结尾无升华……无感悟……师:辞藻不美?(师追问)男</div>
                    </li>
                    <li><a href="/article/1950232910862807040.htm"
                           title="《玉骨遥》:大司命为什么不杀朱颜?原因没那么简单" target="_blank">《玉骨遥》:大司命为什么不杀朱颜?原因没那么简单</a>
                        <span class="text-muted">windy天意晚晴</span>

                        <div>《玉骨遥》里,朱颜就是时影的命劫之人。重明与时影早就知道,他们一直瞒着大司命,如今大司命也知道了真相。可是大司命却没有杀朱颜,而是给朱颜下了诛心咒,还说时影的命劫已经破了,真的如此吗?1、计划总是赶不上变化的大司命从目前剧情来说,大司命还不如时影,他信心十足的事情总会有纰漏。他不让时影见命劫之女,结果时影还是遇上了。他想让时影走火入魔,一心复仇,结果时影在朱颜的劝说下放下了仇恨。大司命让时影开山收</div>
                    </li>
                    <li><a href="/article/1950232820773351424.htm"
                           title="移动端城市区县二级联动选择功能实现包" target="_blank">移动端城市区县二级联动选择功能实现包</a>
                        <span class="text-muted">good2know</span>

                        <div>本文还有配套的精品资源,点击获取简介:本项目是一套为移动端设计的jQuery实现方案,用于简化用户在选择城市和区县时的流程。它包括所有必需文件:HTML、JavaScript、CSS及图片资源。通过动态更新下拉菜单选项,实现城市到区县的联动效果,支持数据异步加载。开发者可以轻松集成此功能到移动网站或应用,并可基于需求进行扩展和优化。1.jQuery移动端解决方案概述jQuery技术简介jQuery</div>
                    </li>
                    <li><a href="/article/1950232783670538240.htm"
                           title="自律打卡第四天:比昨天进步一点点" target="_blank">自律打卡第四天:比昨天进步一点点</a>
                        <span class="text-muted">花儿的念想</span>

                        <div>今天新闻我们县城又确诊了一例,截止目前已经确诊的三例了,打开,看了一篇简友写的武汉的真实情况,有病住不了院,还没等到床位已经去世的消息,心里更加的难受,武汉尚且这样,如果是我们这没有高速没有火车的十八线的小县城发生这种情况,那情况将是更加的不堪设想,不敢想,唯有祈求灾难早点快去,平安才是最大的福气。突然觉得我的自律打卡,比昨天进步一点点。更希望疫情战争每一天都要比昨天好一点,希望一觉醒来听到的是好</div>
                    </li>
                    <li><a href="/article/1950232781174927360.htm"
                           title="15个小技巧,让我的Windows电脑更好用了!" target="_blank">15个小技巧,让我的Windows电脑更好用了!</a>
                        <span class="text-muted">曹元_</span>

                        <div>01.桌面及文档处理第一部分的技巧,主要是围绕桌面的一些基本操作,包括主题设置、常用文档文件快捷打开的多种方式等等。主题换色默认情况下,我们的Win界面可能就是白色的文档界面,天蓝色的图表背景,说不出哪里不好看,但是就是觉得不够高级。imageimage说到高级感,本能第一反应就会和暗色模式联想起来,如果我们将整个界面换成黑夜模式的话,它会是这样的。imageimage更改主题颜色及暗色模式,我们</div>
                    </li>
                    <li><a href="/article/1950232316974526464.htm"
                           title="(二)SAP Group Reporting (GR) 核心子模块功能及数据流向架构解析" target="_blank">(二)SAP Group Reporting (GR) 核心子模块功能及数据流向架构解析</a>
                        <span class="text-muted"></span>

                        <div>数据如何从子公司流转到合并报表的全过程,即数据采集→合并引擎→报表输出,特别是HANA内存计算如何优化传统ETL瓶颈。SAPGroupReporting(GR)核心模块功能及数据流向的架构解析,涵盖核心组件、数据处理流程和关键集成点,适用于S/4HANA1809+版本:一、核心功能模块概览模块功能关键事务码/FioriApp数据采集(DataCollection)整合子公司财务数据(SAP/非SA</div>
                    </li>
                    <li><a href="/article/1950232316408295424.htm"
                           title="9、汇编语言编程入门:从环境搭建到简单程序实现" target="_blank">9、汇编语言编程入门:从环境搭建到简单程序实现</a>
                        <span class="text-muted">神经网络酱</span>
<a class="tag" taget="_blank" href="/search/%E6%B1%87%E7%BC%96%E8%AF%AD%E8%A8%80/1.htm">汇编语言</a><a class="tag" taget="_blank" href="/search/MEPIS/1.htm">MEPIS</a><a class="tag" taget="_blank" href="/search/GNU%E5%B7%A5%E5%85%B7%E9%93%BE/1.htm">GNU工具链</a>
                        <div>汇编语言编程入门:从环境搭建到简单程序实现1.数据存储介质问题解决在处理数据存储时,若要使用MEPIS系统,需确保有其可访问的存储介质。目前,MEPIS无法向采用NTFS格式(常用于Windows2000和XP工作站)的硬盘写入数据。不过,若硬盘采用FAT32格式,MEPIS就能进行写入操作。此外,MEPIS还能将文件写入软盘和大多数USB闪存驱动器。若工作站连接到局域网,还可通过FTP协议或挂载</div>
                    </li>
                    <li><a href="/article/1950232190038110208.htm"
                           title="day15|前端框架学习和算法" target="_blank">day15|前端框架学习和算法</a>
                        <span class="text-muted">universe_01</span>
<a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a>
                        <div>T22括号生成先把所有情况都画出来,然后(在满足什么情况下)把不符合条件的删除。T78子集要画树状图,把思路清晰。可以用暴力法、回溯法和DFS做这个题DFS深度搜索:每个边都走完,再回溯应用:二叉树搜索,图搜索回溯算法=DFS+剪枝T200岛屿数量(非常经典BFS宽度把树状转化成队列形式,lambda匿名函数“一次性的小函数,没有名字”setup语法糖:让代码更简洁好写的语法ref创建:基本类型的</div>
                    </li>
                    <li><a href="/article/1950231640819167232.htm"
                           title="贝多芬诞辰250周年纪念" target="_blank">贝多芬诞辰250周年纪念</a>
                        <span class="text-muted">万千星河赴远方</span>

                        <div>就算不是古典音乐爱好者,你也一定听说过贝多芬。作为古典音乐史上最伟大的音乐家之一,他不仅是古典主义风格的集大成者,同时也是浪漫主义风格的开创者。贝多芬肖像画(1813年)贝多芬的一生共创作了9部交响曲、36首钢琴奏鸣曲、10部小提琴奏鸣曲、16首弦乐四重奏、1部歌剧及2部弥撒曲等等。数量虽然不及前辈海顿、莫扎特多,但他几乎改造了当时所有的音乐表达形式,赋予了它们全新的价值,对后世音乐的发展产生了极</div>
                    </li>
                    <li><a href="/article/1950231513744338944.htm"
                           title="IK分词" target="_blank">IK分词</a>
                        <span class="text-muted">初心myp</span>

                        <div>实现简单的分词功能,智能化分词添加依赖配置:4.10.4org.apache.lucenelucene-core${lucene.version}org.apache.lucenelucene-analyzers-common${lucene.version}org.apache.lucenelucene-queryparser${lucene.version}org.apache.lucenel</div>
                    </li>
                    <li><a href="/article/1950231508648259584.htm"
                           title="三件事—小白猫·雨天·八段锦" target="_blank">三件事—小白猫·雨天·八段锦</a>
                        <span class="text-muted">咸鱼月亮</span>

                        <div>1.最近楼下出现一只非常漂亮的粘人小白猫,看着不像是流浪猫,非常亲人。眼睛比蓝球的还大,而且是绿色的,很漂亮。第一次遇到它,它就跟我到电梯口,如果我稍微招招手,肯定就跟我进电梯了。后来我喂过它几次,好可惜不能养它,一只蓝球就是我的极限了。2.下雨天就心烦,好奇怪。明明以前我超爱看窗外的雨和听雨声,看来近来的心情不够宁静了。3.最近在练八段锦,从第一次就爱上了这个运动,很轻松缓慢,但是却出汗。感觉可</div>
                    </li>
                    <li><a href="/article/1950231509906550784.htm"
                           title="25-1-2019" target="_blank">25-1-2019</a>
                        <span class="text-muted">树藤与海岛呢</span>

                        <div>hello八月来报道了今天看到了一篇文章就只想记下那两句话:良田千顷不过一日三餐广夏万间只睡卧榻三尺大概的意思就是要珍惜当下不要等来不及的时候才珍惜分享今天的两餐最近没有时间运动呢下个月补回好了说完了哈哈goodnight图片发自App图片发自App</div>
                    </li>
                    <li><a href="/article/1950231308781285376.htm"
                           title="力扣热题100-------54. 螺旋矩阵" target="_blank">力扣热题100-------54. 螺旋矩阵</a>
                        <span class="text-muted">海航Java之路</span>
<a class="tag" taget="_blank" href="/search/%E5%8A%9B%E6%89%A3/1.htm">力扣</a><a class="tag" taget="_blank" href="/search/leetcode/1.htm">leetcode</a><a class="tag" taget="_blank" href="/search/%E7%9F%A9%E9%98%B5/1.htm">矩阵</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a>
                        <div>给你一个m行n列的矩阵matrix,请按照顺时针螺旋顺序,返回矩阵中的所有元素。示例1:输入:matrix=[[1,2,3],[4,5,6],[7,8,9]]输出:[1,2,3,6,9,8,7,4,5]示例2:输入:matrix=[[1,2,3,4],[5,6,7,8],[9,10,11,12]]输出:[1,2,3,4,8,12,11,10,9,5,6,7]提示:m==matrix.lengthn</div>
                    </li>
                    <li><a href="/article/1950230873060208640.htm"
                           title="你要记住,最重要的是:随时做好准备,为了你可能成为更好的自己,放弃现在的自己。" target="_blank">你要记住,最重要的是:随时做好准备,为了你可能成为更好的自己,放弃现在的自己。</a>
                        <span class="text-muted">霖霖z</span>

                        <div>打卡人:周云日期:2018年11月09日【日精进打卡第180天】【知~学习】《六项精进》0遍共214遍《通篇》1遍共106遍《大学》2遍共347遍《坚强工作,温柔生活》ok《不抱怨的世界》104-108页《经典名句》你要记住,最重要的是:随时做好准备,为了你可能成为更好的自己,放弃现在的自己。【行~实践】一、修身:(对自己个人)1、坚持打卡二、齐家:(对家庭和家人)打扫卫生,接送孩子,洗衣做饭,陪</div>
                    </li>
                    <li><a href="/article/1950230804957294592.htm"
                           title="SpringMVC执行流程(原理),通俗易懂" target="_blank">SpringMVC执行流程(原理),通俗易懂</a>
                        <span class="text-muted">国服冰</span>
<a class="tag" taget="_blank" href="/search/SpringMVC/1.htm">SpringMVC</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a>
                        <div>SpringMVC执行流程(原理),通俗易懂一、图解SpringMVC流程二、进一步理解Springmvc的执行流程1、导入依赖2、建立展示的视图3、web.xml4、spring配置文件springmvc-servlet5、Controller6、tomcat配置7、访问的url8、视图页面一、图解SpringMVC流程图为SpringMVC的一个较完整的流程图,实线表示SpringMVC框架提</div>
                    </li>
                    <li><a href="/article/1950230678696161280.htm"
                           title="C++ 计数排序、归并排序、快速排序" target="_blank">C++ 计数排序、归并排序、快速排序</a>
                        <span class="text-muted">每天搬一点点砖</span>
<a class="tag" taget="_blank" href="/search/c%2B%2B/1.htm">c++</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a>
                        <div>计数排序:是一种基于哈希的排序算法。他的基本思想是通过统计每个元素的出现次数,然后根据统计结果将元素依次放入排序后的序列中。这种排序算法适用于范围较小的情况,例如整数范围在0到k之间计数排序步骤:1初始化一个长度为最大元素值加1的计数数组,所有元素初始化为02遍历原始数组,将每个元素值作为索引,在计数数组中对应位置加13将数组清空4遍历计数器数组,按照数组中的元素个数放回到元数组中计数排序的优点和</div>
                    </li>
                    <li><a href="/article/1950229985633562624.htm"
                           title="2023-11-02" target="_blank">2023-11-02</a>
                        <span class="text-muted">一帆f</span>

                        <div>发现浸润心田的感觉:今天一个机缘之下突然想分享我的婆媳关系,我一边分享一边回忆我之前和儿媳妇关系的微妙变化,特别是分享到我能感受到儿媳妇的各种美好,现在也能心平气和的和老公平等对话,看到自己看到老公,以己推人以人推己自然而然的换位思考,心中有一种美好的能量在涌动,一种浸润心田的感觉从心胸向全身扩散,美好极了……我很想记住这种感觉,赶紧把它写下来以留纪念,也就是当我看见他人的美好,美好的美妙的浸润心</div>
                    </li>
                    <li><a href="/article/1950229418706268160.htm"
                           title="密码正则验证:大小写字母、数字、特殊字符至少8位" target="_blank">密码正则验证:大小写字母、数字、特殊字符至少8位</a>
                        <span class="text-muted">qq_21875331</span>
<a class="tag" taget="_blank" href="/search/%E6%B8%90%E8%BF%9B%E5%BC%8F%E7%9A%84%E6%88%90%E9%95%BF/1.htm">渐进式的成长</a>
                        <div>正则表达式:密码必须包含大写字母、数字、特殊字符(四种里至少三种,且至少8位)写法一:/((^(?=.*[a-z])(?=.*[A-Z])(?=.*\W)[\da-zA-Z\W]{8,16}$)|(^(?=.*\d)(?=.*[A-Z])(?=.*\W)[\da-zA-Z\W]{8,16}$)|(^(?=.*\d)(?=.*[a-z])(?=.*\W)[\da-zA-Z\W]{8,16}$)|(^</div>
                    </li>
                    <li><a href="/article/1950229040682037248.htm"
                           title="48. 旋转图像 - 力扣(LeetCode)" target="_blank">48. 旋转图像 - 力扣(LeetCode)</a>
                        <span class="text-muted">Fiee-77</span>
<a class="tag" taget="_blank" href="/search/%23/1.htm">#</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E7%BB%84/1.htm">数组</a><a class="tag" taget="_blank" href="/search/leetcode/1.htm">leetcode</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E7%BB%84/1.htm">数组</a>
                        <div>题目:给定一个n×n的二维矩阵matrix表示一个图像。请你将图像顺时针旋转90度。你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。示例1:输入:matrix=[[1,2,3],[4,5,6],[7,8,9]]输出:[[7,4,1],[8,5,2],[9,6,3]]示例2:输入:matrix=[[5,1,9,11],[2,4,8,10],[13,3,6,</div>
                    </li>
                    <li><a href="/article/1950228468897738752.htm"
                           title="别再讲道理啦,对方听不进去的" target="_blank">别再讲道理啦,对方听不进去的</a>
                        <span class="text-muted">方所</span>

                        <div>我之前写过一篇叫做《你总妄想改变他人》,然后就有朋友跟我说,有一些方法可以改变他人之类的。嗯,是这样,但是任何具体的问题,都要限定好语境,描述清楚前提条件,然后再表达观点,我的这位朋友的说法就犯了一刀切的错误,这样并不能让讨论正常展开(这篇我得先给她看看,不然可能会挨揍)。好了,hhhh,谁让她不能写文章呢,我就来再说一说吧。我前面说过,我们在学到一个道理、学会一种方法之后,总是迫不及待地想要去与</div>
                    </li>
                    <li><a href="/article/1950228285266915328.htm"
                           title="Git 与 GitHub 的对比与使用指南" target="_blank">Git 与 GitHub 的对比与使用指南</a>
                        <span class="text-muted">一念&</span>
<a class="tag" taget="_blank" href="/search/%E5%85%B6%E5%AE%83/1.htm">其它</a><a class="tag" taget="_blank" href="/search/git/1.htm">git</a><a class="tag" taget="_blank" href="/search/github/1.htm">github</a>
                        <div>Git与GitHub的对比与使用指南在软件开发中,Git和GitHub是两个密切相关但本质不同的工具。下面我将逐步解释它们的定义、区别、核心概念以及如何协同使用,确保内容真实可靠,基于广泛的技术实践。1.什么是Git?Git是一个分布式版本控制系统,由LinusTorvalds于2005年创建。它的核心功能是跟踪代码文件的变化,帮助开发者管理项目历史记录、协作和回滚错误。Git是开源的,可以在本地</div>
                    </li>
                    <li><a href="/article/1950228158863175680.htm"
                           title="英伟达靠什么支撑起了4万亿?AI泡沫还能撑多久?" target="_blank">英伟达靠什么支撑起了4万亿?AI泡沫还能撑多久?</a>
                        <span class="text-muted"></span>

                        <div>英伟达市值突破4万亿美元,既是AI算力需求爆发的直接体现,也暗含市场对未来的狂热预期。其支撑逻辑与潜在风险并存,而AI泡沫的可持续性则取决于技术、商业与地缘政治的复杂博弈。⚙️一、英伟达4万亿市值的核心支撑因素技术垄断与生态壁垒硬件优势:英伟达GPU在AI训练市场占有率超87%,H100芯片的FP16算力达1979TFLOPS,领先竞品3-5倍。CUDA生态:400万开发者构建的软件护城河,成为A</div>
                    </li>
                    <li><a href="/article/1950228031117258752.htm"
                           title="深入解析JVM工作原理:从字节码到机器指令的全过程" target="_blank">深入解析JVM工作原理:从字节码到机器指令的全过程</a>
                        <span class="text-muted"></span>

                        <div>一、JVM概述Java虚拟机(JVM)是Java平台的核心组件,它实现了Java"一次编写,到处运行"的理念。JVM是一个抽象的计算机器,它有自己的指令集和运行时内存管理机制。JVM的主要职责:加载:读取.class文件并验证其正确性存储:管理内存分配和垃圾回收执行:解释或编译字节码为机器指令安全:提供沙箱环境限制恶意代码二、JVM架构详解JVM由三个主要子系统组成:1.类加载子系统类加载过程分为</div>
                    </li>
                    <li><a href="/article/1950228031524106240.htm"
                           title="Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程" target="_blank">Spring进阶 - SpringMVC实现原理之DispatcherServlet处理请求的过程</a>
                        <span class="text-muted">倾听铃的声</span>
<a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a>
                        <div>前文我们有了IOC的源码基础以及SpringMVC的基础,我们便可以进一步深入理解SpringMVC主要实现原理,包含DispatcherServlet的初始化过程和DispatcherServlet处理请求的过程的源码解析。本文是第二篇:DispatcherServlet处理请求的过程的源码解析。@pdaiSpring进阶-SpringMVC实现原理之DispatcherServlet处理请求的</div>
                    </li>
                    <li><a href="/article/1950227573938122752.htm"
                           title="2019-06-05" target="_blank">2019-06-05</a>
                        <span class="text-muted">第十七把巴鲁克</span>

                        <div>今天去实验田里实习,见到了福寿螺真的可怕且牛皮,六级也快来了,说实话还是害怕。我昨天考了环工原理,真的太难了,太烦了,理工科真的难,烦。实验报告还是没写,要抓紧速度抓紧时间,还是应该学会努力学习,远离一些不上进的事物。</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>