Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]_安卓logd.rc-程序员宅基地

技术标签: logd  logcat  Android取经之路  # 6.系统服务  Android10.0  日志系统  liblog  

摘要:本节主要来讲解Android10.0 日志系统的架构分析,以及logd、logcat的初始化操作

阅读本文大约需要花费15分钟。

文章首发微信公众号:IngresGe

专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!

欢迎关注我的公众号!

[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析

[Android取经之路]系列文章:

《系统启动篇》

  1. Android系统架构
  2. Android是怎么启动的
  3. Android 10.0系统启动之init进程
  4. Android10.0系统启动之Zygote进程
  5. Android 10.0 系统启动之SystemServer进程
  6. Android 10.0 系统服务之ActivityMnagerService
  7. Android10.0系统启动之Launcher(桌面)启动流程
  8. Android10.0应用进程创建过程以及Zygote的fork流程
  9. Android 10.0 PackageManagerService(一)工作原理及启动流程
  10. Android 10.0 PackageManagerService(二)权限扫描
  11. Android 10.0 PackageManagerService(三)APK扫描
  12. Android 10.0 PackageManagerService(四)APK安装流程

《日志系统篇》

  1. Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性
  2. Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化
  3. Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析
  4. Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​

《Binder通信原理》

  1. Android10.0 Binder通信原理(一)Binder、HwBinder、VndBinder概要
  2. Android10.0 Binder通信原理(二)-Binder入门篇
  3. Android10.0 Binder通信原理(三)-ServiceManager篇
  4. Android10.0 Binder通信原理(四)-Native-C\C++实例分析
  5. Android10.0 Binder通信原理(五)-Binder驱动分析
  6. Android10.0 Binder通信原理(六)-Binder数据如何完成定向打击
  7. Android10.0 Binder通信原理(七)-Framework binder示例
  8. Android10.0 Binder通信原理(八)-Framework层分析
  9. Android10.0 Binder通信原理(九)-AIDL Binder示例
  10. Android10.0 Binder通信原理(十)-AIDL原理分析-Proxy-Stub设计模式
  11. Android10.0 Binder通信原理(十一)-Binder总结

  《HwBinder通信原理》

  1. HwBinder入门篇-Android10.0 HwBinder通信原理(一)
  2.  HIDL详解-Android10.0 HwBinder通信原理(二)
  3. HIDL示例-C++服务创建Client验证-Android10.0 HwBinder通信原理(三)
  4. HIDL示例-JAVA服务创建-Client验证-Android10.0 HwBinder通信原理(四)
  5. HwServiceManager篇-Android10.0 HwBinder通信原理(五)
  6. Native层HIDL服务的注册原理-Android10.0 HwBinder通信原理(六)
  7. Native层HIDL服务的获取原理-Android10.0 HwBinder通信原理(七)
  8. JAVA层HIDL服务的注册原理-Android10.0 HwBinder通信原理(八)
  9. JAVA层HIDL服务的获取原理-Android10.0 HwBinder通信原理(九)
  10. HwBinder驱动篇-Android10.0 HwBinder通信原理(十)
  11. HwBinder原理总结-Android10.0 HwBinder通信原理(十一)

《编译原理》

  1. 编译系统入门篇-Android10.0编译系统(一)
  2. 编译环境初始化-Android10.0编译系统(二)
  3. make编译过程-Android10.0编译系统(三)
  4. Image打包流程-Android10.0编译系统(四)
  5. Kati详解-Android10.0编译系统(五)

上一节我们看了Logd、logcat的指令说明,这一节我们来看看Android的日志系统架构,以及logd\logcat的初始化操作

 

6.架构

6.1 读写日志架构

    在Android5.0(Android-L)之前,log由kernel的环形 buffer 保存,在Android5.0 之后,log保存在用户空间,通过Socket进行访问。

在Android5.0之后,引入了Logd的守护进程用来进行日志的读写操作。

不管是应用层,还是Native层,读写日志都是通过liblog提供的接口,访问logd的两个socket buffer:logdr、logdw来实现读写。

图片来自于CSDN-私房菜:

 

6.2 写日志流程

    在应用层可以通过android.util.Log,android.util.SLog,android.util.EventLog接口,把日志写入到main,system,event的不同缓冲区中去。

    在JAVA中想调用日志,就需要import下面的内容:

    import android.util.Log;

    import android.util.SLog;

    import android.util.EventLog;

应用层写日志方法如下:

在Native C/C++中,进程通过加载liblog.so,调用ALOGD()、ALOGI()来进行日志的写入,最终也是通过logd写入到logdw的socket中。

    如果在Native中想要调用liblog的内容,需要在Android.mk 或者Android.bp中加入liblog,并引入头文件:#include <android/log.h>

    Native 层写日志方法如下:

 

6.3 读日志流程

    Android中主要通过logcat进程来读取日志,logcat属于native-C的进程,通过加载liblog,从而调用logd的read接口读取 logdr socket的日志内容。

 

7. 源码分析

Android系统日志主要有三个部分需要关注:

  • logd守护进程:日志系统的大管家,管理三个日志的socket:logd、logdr、logdw。

  • logcat进程:日志读取工具。

  • liblog:提供日志读写、过滤等接口,供logcat、JAVA、Native等程序使用

 

7.1 logd启动及初始化

7.1.1启动logd

    在Android 系统启动后,init进程加载,会解析logd.rc启动logd service如下:

service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd
    group logd system package_info readproc
    capabilities SYSLOG AUDIT_CONTROL
    priority 10
    writepid /dev/cpuset/system-background/tasks

从上面的service可以看出,启动了一个守护进程为logd,存放在手机的/system/bin中,同时创建并启动三个socket:

  • logd 接收logcat 传递的指令然后处理 ,比如logcat -g, logcat -wrap等

  • logdr logcat从此buffer中读取buffer

  • logdw 日志写入的buffer

logd初始化调用栈如下:

logd的初始化流程:

  1. 打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储

  2. 如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志

  3. 设置运行时优先级、权限

  4. 启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次

  5. 启动各个 log 监听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit和LogKlog

源码:

int main(int argc, char* argv[]) {
  //logd是在假设时区是UTC的情况下编写的。
  //如果未设置TZ,则在某些时间实用程序libc函数(包括mktime)中查找persist.sys.timezone。
  //它混淆了logd时间处理,因此这里显式地将TZ设置为UTC,这将重写属性。
    setenv("TZ", "UTC", 1);
    // issue reinit command. KISS argument parsing.
    if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        return issueReinit();
    }

    //1.打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储
    static const char dev_kmsg[] = "/dev/kmsg";
    fdDmesg = android_get_control_file(dev_kmsg);
    if (fdDmesg < 0) {
        fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
    }

    //2.如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志
    int fdPmesg = -1;
    bool klogd = __android_logger_property_get_bool(
        "ro.logd.kernel",
        BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
    if (klogd) {
        static const char proc_kmsg[] = "/proc/kmsg";
        fdPmesg = android_get_control_file(proc_kmsg);
        if (fdPmesg < 0) {
            fdPmesg = TEMP_FAILURE_RETRY(
                open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
        }
        if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
    }

    //3.设置运行时优先级、权限
    bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
    if (drop_privs(klogd, auditd) != 0) {
        return EXIT_FAILURE;
    }

    //4.启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次
    sem_init(&reinit, 0, 0);
    pthread_attr_t attr;
    if (!pthread_attr_init(&attr)) {
        struct sched_param param;

        memset(&param, 0, sizeof(param));
        pthread_attr_setschedparam(&attr, &param);
        pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
        if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
            pthread_t thread;
            reinit_running = true;
            if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
                reinit_running = false;
            }
        }
        pthread_attr_destroy(&attr);
    }

  //用于管理在SOCKET连接上读取的最后日志时间,以及作为一个对一系列日志项的读卡器锁。
    LastLogTimes* times = new LastLogTimes();

    //5.启动各个 log 监听器
    //5.1先创建一个LogBuffer的对象,LogBuffer是负责保存所有日志项的对象
    logBuf = new LogBuffer(times);

    signal(SIGHUP, reinit_signal_handler);

    if (__android_logger_property_get_bool(
            "logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
                                   BOOL_DEFAULT_FLAG_ENG |
                                   BOOL_DEFAULT_FLAG_SVELTE)) {
        logBuf->enableStatistics();
    }

    //5.2 LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。
    LogReader* reader = new LogReader(logBuf);
    if (reader->startListener()) {
        return EXIT_FAILURE;
    }

    //5.3 LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入
    LogListener* swl = new LogListener(logBuf, reader);
    // Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
    if (swl->startListener(600)) {
        return EXIT_FAILURE;
    }

     //5.4 CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd
    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {
        return EXIT_FAILURE;
    }

    //5.5 如果配置了属性"ro.logd.auditd",则启动LogAudit,LogAudit 在NETLINK_AUDIT的socket上侦听selinux启动的日志消息
    LogAudit* al = nullptr;
    if (auditd) {
        al = new LogAudit(logBuf, reader,
                          __android_logger_property_get_bool(
                              "ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
                              ? fdDmesg
                              : -1);
    }

    //5.6如果配置了属性"ro.logd.kernel",则启动LogKlog,用来存储内核日志
    LogKlog* kl = nullptr;
    if (klogd) {
        kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
    }

    //5.7通过 LogAudit和 LogKlog来分别读取selinux和kernel的日志
    readDmesg(al, kl);
    // failure is an option ... messages are in dmesg (required by standard)
    if (kl && kl->startListener()) {
        delete kl;
    }

    if (al && al->startListener()) {
        delete al;
    }

    TEMP_FAILURE_RETRY(pause());
    return EXIT_SUCCESS;
}

 在system/core/lodgd/main.cpp文件的main函数中,默认创建了LogBuffer、LogReader、LogListener和CommandListener四个对象:

  • LogBuffer:LogBuffer是负责保存所有日志项的对象

  • LogReader:LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。

  • LogListener:LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入

  • CommandListener:CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd

另外,还有两个对象-LogAudit 和LogKlog,受属性控制:

  • LogAudit:受属性"ro.logd.auditd"控制,在NETLINK_AUDIT的socket上侦听selinux启动的日志消息,新的日志条目将添加到LogBuffer中,并通知LogReader向连接的客户端发送更新

  • LogKlog:受属性"ro.logd.kernel"控制,用来存储内核日志,内核日志通过"/dev/kmsg", "/proc/kmsg" 获得

7.1.2 启动 logd-reinit

    logd.rc中启动logd-reinit 如下:

service logd-reinit /system/bin/logd --reinit
    oneshot
    disabled
    user logd
    group logd
    writepid /dev/cpuset/system-background/tasks

   启动logd-reinit的服务,主要工作是重新初始化logd的LogBuffer,在上面的启动脚本中,配置为oneshot,即开机只执行一次。

    通过上面logd的初始化,可以看到,logd启动后,创建了一个线程reinit_thread_start(),当logd-reinit 传入参数 reinit后,进行功能执行。

    logd-reinit两个步骤:

  1. 如果reinit启动后,并且/deg/kmsg打开成功,把 logd.daemon: renit写入kmsg

  2. 重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象

源码:


static void* reinit_thread_start(void* /*obj*/) {
    prctl(PR_SET_NAME, "logd.daemon");

    while (reinit_running && !sem_wait(&reinit) && reinit_running) {

        if (fdDmesg >= 0) {
            static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
                                                   'l',
                                                   'o',
                                                   'g',
                                                   'd',
                                                   '.',
                                                   'd',
                                                   'a',
                                                   'e',
                                                   'm',
                                                   'o',
                                                   'n',
                                                   ':',
                                                   ' ',
                                                   'r',
                                                   'e',
                                                   'i',
                                                   'n',
                                                   'i',
                                                   't',
                                                   '\n' };
            write(fdDmesg, reinit_message, sizeof(reinit_message));
        }

        // Anything that reads persist.<property>
  //重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象
        if (logBuf) {
            logBuf->init();
            logBuf->initPrune(nullptr);
        }
        android::ReReadEventLogTags();
    }

    return nullptr;
}

7.1.3 启动 logd-auditctl

    logd.rc中启动logd-auditctl

# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
    oneshot
    disabled
    user logd
    group logd
    capabilities AUDIT_CONTROL

    logd-auditctl 的主体是 /system/bin/auditctl,在logd的android.bp中,通过编译 auditctl.cpp得来,并加载了liblogd的 库。

    logd-auditctl是Android 10.0中引入的新功能,目的是让selinux denia的日志打印限制为5秒一次。

   Android.bp 中auditctl展示如下:

cc_binary {
    name: "auditctl",
    srcs: ["auditctl.cpp"],
    static_libs: [
        "liblogd",
    ],
    shared_libs: ["libbase"],
    cflags: [
        "-Wall",
        "-Wextra",
        "-Werror",
        "-Wconversion"
    ],
}

logd-auditctl 初始化调用栈如下:

logd-auditctl的主要作用是 让selinux denia的日志打印限制为5秒一次

说明:在logd.rc中配置了logd-auditctl,传入参数为-r5,即限制selinux日志写入频率更新为5秒

源码:


[auditctl.cpp] main()
int main(int argc, char* argv[]) {
    uint32_t rate = 0;
    bool update_rate = false;
    int opt;
    //如果logd-auditctl传入了-r的参数,获取参数的值
    //即这里的rate为5,并标记update_rate 为启动
    while ((opt = getopt(argc, argv, "r:")) != -1) {
        switch (opt) {
            case 'r':
                if (!android::base::ParseUint<uint32_t>(optarg, &rate)) {
                    error(EXIT_FAILURE, errno, "Invalid Rate");
                }
                update_rate = true;
                break;
            default: /* '?' */
                usage(argv[0]);
                exit(EXIT_FAILURE);
        }
    }

    // In the future, we may add other options to auditctl
    // so this if statement will expand.
    // if (!update_rate && !update_backlog && !update_whatever) ...
    if (!update_rate) {
        fprintf(stderr, "Nothing to do\n");
        usage(argv[0]);
        exit(EXIT_FAILURE);
    }

    //如果传入了-r参数,更新rate
    if (update_rate) {
        do_update_rate(rate);
    }

    return 0;
}

说明:创建一个netlink的socket,协议号为NETLINK_AUDIT,并通过audit_rate_limit发送selinux频率

源码:

[auditctl.cpp] do_update_rate()
static void do_update_rate(uint32_t rate) {
    //创建socket PF_NETLINK
    int fd = audit_open();
    if (fd == -1) {
        error(EXIT_FAILURE, errno, "Unable to open audit socket");
    }
    int result = audit_rate_limit(fd, rate);
    close(fd);
    if (result < 0) {
        fprintf(stderr, "Can't update audit rate limit: %d\n", result);
        exit(EXIT_FAILURE);
    }
}

说明:组装结构体audit_status,传入频率为5秒,最终通过sendto()发送message到内核,由用户态切入到内核态

源码:

[libaudit.c] audit_rate_limit()
int audit_rate_limit(int fd, uint32_t limit) {
    struct audit_status status;
    memset(&status, 0, sizeof(status));
    status.mask = AUDIT_STATUS_RATE_LIMIT;
    status.rate_limit = limit; /* audit entries per second */
    return audit_send(fd, AUDIT_SET, &status, sizeof(status));
}

 

7.2 logcat启动

    logcat编译时,会编译两个进程/system/bin/logcat 和/system/bin/logcatd。

    和logd一样,logcat进程启动,是init进程解析了logcatd.rc来进行加载。

    logcatd.rc 如下所示:

service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-1024} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
    class late_start
    disabled
    # logd for write to /data/misc/logd, log group for read from log daemon
    user logd
    group log
    writepid /dev/cpuset/system-background/tasks
    oom_score_adjust -600

    从上面的service可以看出,启动了一个守护进程为logcatd,存放在手机的/system/bin中。

    启动logcatd时,传入了-b\-v\-f等参数。

 

说明:logcat启动后,先创建一个context,设置信号量,再启动一个while死循环,用来接收logcat的command

源码:

int main(int argc, char** argv, char** envp) {
    android_logcat_context ctx = create_android_logcat();
    if (!ctx) return -1;
    signal(SIGPIPE, exit);
    int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
    int ret = android_logcat_destroy(&ctx);
    if (!ret) ret = retval;
    return ret;
}

说明:android_logcat_run_command()用来解析logcat传入的command,最终通过函数__logcat()中启动一个while死循环,来执行logcat传入的各种命令。

源码:

int android_logcat_run_command(android_logcat_context ctx,
                               int output, int error,
                               int argc, char* const* argv,
                               char* const* envp) {
    android_logcat_context_internal* context = ctx;

    context->output_fd = output;
    context->error_fd = error;
    context->argc = argc;
    context->argv = argv;
    context->envp = envp;
    context->stop = false;
    context->thread_stopped = false;
    return __logcat(context);
}

Android 日志系统架构及初始化讲完了,下一节我们来分析logd、logcat读写日志的源码分析。

微信公众号:IngresGe

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yiranfeng/article/details/104244900

智能推荐

Python 入门的60个基础练习_练习python基础语法-程序员宅基地

文章浏览阅读4.2w次,点赞329次,收藏2.7k次。Python 入门的60个基础练习_练习python基础语法

iOS6和iOS7代码的适配(2)——status bar_ios7 statusbar-程序员宅基地

文章浏览阅读1w次。用Xcode5运行一下应用,第一个看到的就是status bar的变化。在iOS6中,status bar是系统在处理,应用_ios7 statusbar

gdb调试时No symbol "var" defined in current context && No Register_no registers调试显示-程序员宅基地

文章浏览阅读2.1k次。问题描述:,在gdb调试程序输出变量:p var,会提示No symbol "var" in current context.原因:程序编译时开启了优化选项,那么在用GDB调试被优化过的程序时,可能会发生某些变量不能访问,或是取值错误码的情况。这个是很正常的,因为优化程序会删改程序,整理程序的语句顺序,剔除一些无意义的变量等,所以在GDB调试这种程序时,运行时的指令和你所编写指_no registers调试显示

IDGeneratorUtil 主键id生成工具类_idgeneratorutils.generateid()-程序员宅基地

文章浏览阅读3.4k次。import java.util.Random;import org.drools.util.UUIDGenerator;/** * * * 类名称:GenerateIdUtil * 类描述: 主键生成工具类 * @author chenly * 创建时间:Jul 10, 2012 8:10:43 AM * 修改人: * 修改时间:Jul 10, 2012 8..._idgeneratorutils.generateid()

关于汇编 BX 和 BLX 跳转指令_汇编blx-程序员宅基地

文章浏览阅读5k次。BX:跳转到寄存器reg给出的目的地址处,如:BX R2BLX:跳转到寄存区reg给出的目的地址处并将返回地址存储到LR(R14)使用这两个指令时有一点特别需要注意:跳转的目的地址必须是奇数,若不是奇数则在后面加1,如某函数的起始地址是0x80000f00,则要跳转到此函数则应该跳转到0x80000f01处!否则会进入硬件错误中断!..._汇编blx

前端vue,打包整合进后端springboot的resources里面后,运行只要刷新就报404_前端项目放入resource-程序员宅基地

文章浏览阅读2.6k次,点赞2次,收藏4次。vue打包后,其实就剩index.html和一堆静态资源,页面的加载和替换都是通过刷新index.html种的dom来实现的(应该是这样,可能表述不是很好),所以做个重定向就可以了。(博主是这么解决的,网上还有很多人是各种路径错误,大家可以尝试下自己是哪个原因)import org.springframework.boot.web.server.ConfigurableWebServerFa..._前端项目放入resource

随便推点

添加远程github仓库时报错 Warning: Permanently added the RSA host key for IP address 52.74.223.119_cmd warning: permanently added-程序员宅基地

文章浏览阅读9.7k次。1.问题展示2.解决方案1.任意窗口, 打开git bash2.命令行界面, 输入cd C:3.cat ~/.ssh/id_rsa.pub正常下面应该显示一大串公钥如果没有,显示如下图, 则进行下一步, 创建公钥4.创建公钥, 输入 ssh-keygen5.然后一直下一步, 直到出现6.再次输入cat ~/.ssh/id_rsa.pub下面一大串数字便是公钥,复制这些字符串, 打开github, 点击头像, 打开settings, 打开SSH and GPG Keys_cmd warning: permanently added

SQL*Plus 使用技巧1-程序员宅基地

文章浏览阅读154次。[code="java"]1. SQL/Plus 常用命令 a. help [topic] 查看命令的使用方法,topic表示需要查看的命令名称。 如: help desc; b. host 该命令可以从SQL*Plus环境切换到操作系统环境,以便执行操作系统命名。 c. host [command] 在sql*plus环境中执行操作系统命令,如:host notepad.exe..._sql+plus的使用方法

域控服务器搭建与管理论文,校园网络服务器的配置与管理 毕业论文.doc-程序员宅基地

文章浏览阅读441次。该文档均来自互联网,如果侵犯了您的个人权益,请联系我们将立即删除!**学校毕 业 论 文**学校园网络服务器的配置与管理姓 名: **学 号: **指导老师:系 名:专 业: 计算机网络技术班 级:二0一一年十二月十五日摘 要随着网络技术的不断发展和Internet的日益普及,许多学校都建立了校园网络并投入使用,这无疑对加快信息处理,提高工作效..._服务器配置与应用论文

mysql单实例多库与多实例单库_数据库单实例和多实例-程序员宅基地

文章浏览阅读1k次。一、单实例多库:一个mysql实例,创建多个数据目录。规划:实例路径:/usr/local/mysql数据目录路径:(1)/usr/local/mysql/data(2)/usr/local/mysql/data2步骤:安装mysql。配置my.cnf文件。初始化各个数据库。用mysqld_multi启动。1、安装mysql。平常安装。2、m..._数据库单实例和多实例

MFC解决找不到MFC90.DLL的问题_microsoft v90.debugmfc-程序员宅基地

文章浏览阅读6.3k次。今天装了第三方的MFC软件库Xtreme ToolkitPro v15.0.1,听说搞MFC的人都知道它的强大,我刚学习,所以装了一个,然后想运行一下它自带的例子看看。出现一个“找不到mfc90.dll“的问题,百度一下,记录如下:vs2008已经打过sp1补丁,编译C++程序会提示找不到mfc90.dll文件的错误,但是如果是release版的话就能正常运行csdn看到解决方案,粘贴_microsoft v90.debugmfc

XeLaTeX-中文排版解决方案_latex 中文排版 texlive-程序员宅基地

文章浏览阅读2.1k次。以前使用CJK进行中文的排版,需要自己生成字体库,近日,出现了XeTeX,可以比较好的解决中文字体问题,不需要额外生成LaTeX字体库,直接使用计算机系统里的字体,本文以在Linux下为例说明XeTeX的使用。操作系统: UbuntuTeX:除了texlive包外,还需要安装的包是texlive-xetex。字体:可以使用fc-list查看你自己的字体库,注意字体的完整名称,在XeTe..._latex 中文排版 texlive