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

智能推荐

没有U盘Win10电脑下如何使用本地硬盘安装Ubuntu20.04(单双硬盘都行)_没有u盘怎么装ubuntu-程序员宅基地

文章浏览阅读3.6k次,点赞2次,收藏2次。DELL7080台式机两块硬盘。_没有u盘怎么装ubuntu

【POJ 3401】Asteroids-程序员宅基地

文章浏览阅读32次。题面Bessie wants to navigate her spaceship through a dangerous asteroid field in the shape of an N x N grid (1 <= N <= 500). The grid contains K asteroids (1 <= K <= 10,000), which are conv...

工业机器视觉系统的构成与开发过程(理论篇—1)_工业机器视觉系统的构成与开发过程(理论篇—1-程序员宅基地

文章浏览阅读2.6w次,点赞21次,收藏112次。机器视觉则主要是指工业领域视觉的应用研究,例如自主机器人的视觉,用于检测和测量的视觉系统等。它通过在工业领域将图像感知、图像处理、控制理论与软件、硬件紧密结合,并研究解决图像处理和计算机视觉理论在实际应用过程中的问题,以实现高效的运动控制或各种实时操作。_工业机器视觉系统的构成与开发过程(理论篇—1

plt.legend的用法-程序员宅基地

文章浏览阅读5.9w次,点赞32次,收藏58次。legend 传奇、图例。plt.legend()的作用:在plt.plot() 定义后plt.legend() 会显示该 label 的内容,否则会报error: No handles with labels found to put in legend.plt.plot(result_price, color = 'red', label = 'Training Loss') legend作用位置:下图红圈处。..._plt.legend

深入理解 C# .NET Core 中 async await 异步编程思想_netcore async await-程序员宅基地

文章浏览阅读2.2k次,点赞3次,收藏11次。深入理解 C# .NET Core 中 async await 异步编程思想引言一、什么是异步?1.1 简单实例(WatchTV并行CookCoffee)二、深入理解(异步)2.1 当我需要异步返回值时,怎么处理?2.2 充分利用异步并行的高效性async await的秘密引言很久没来CSDN了,快小半年了一直在闲置,也写不出一些带有思想和深度的文章;之前就写过一篇关于async await 的异步理解 ,现在回顾,真的不要太浅和太陋,让人不忍直视!好了,废话不再啰嗦,直入主题:一、什么是异步?_netcore async await

IntelliJ IDEA设置类注释和方法注释带作者和日期_idea作者和日期等注释-程序员宅基地

文章浏览阅读6.5w次,点赞166次,收藏309次。当我看到别人的类上面的多行注释是是这样的:这样的:这样的:好装X啊!我也想要!怎么办呢?往下瞅:跟着我左手右手一个慢动作~~~File--->Settings---->Editor---->File and Code Templates --->Includes--->File Header:之后点applay--..._idea作者和日期等注释

随便推点

发行版Linux和麒麟操作系统下netperf 网络性能测试-程序员宅基地

文章浏览阅读175次。Netperf是一种网络性能的测量工具,主要针对基于TCP或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/reponse)模式。工作原理Netperf工具以client/server方式工作。server端是netserver,用来侦听来自client端的连接,c..._netperf 麒麟

万字长文详解 Go 程序是怎样跑起来的?| CSDN 博文精选-程序员宅基地

文章浏览阅读1.1k次,点赞2次,收藏3次。作者| qcrao责编 | 屠敏出品 | 程序员宅基地刚开始写这篇文章的时候,目标非常大,想要探索 Go 程序的一生:编码、编译、汇编、链接、运行、退出。它的每一步具体如何进行,力图弄清 Go 程序的这一生。在这个过程中,我又复习了一遍《程序员的自我修养》。这是一本讲编译、链接的书,非常详细,值得一看!数年前,我第一次看到这本书的书名,就非常喜欢。因为它模仿了周星驰喜剧..._go run 每次都要编译吗

C++之istringstream、ostringstream、stringstream 类详解_c++ istringstream a >> string-程序员宅基地

文章浏览阅读1.4k次,点赞4次,收藏2次。0、C++的输入输出分为三种:(1)基于控制台的I/O (2)基于文件的I/O (3)基于字符串的I/O 1、头文件[cpp] view plaincopyprint?#include 2、作用istringstream类用于执行C++风格的字符串流的输入操作。 ostringstream类用_c++ istringstream a >> string

MySQL 的 binglog、redolog、undolog-程序员宅基地

文章浏览阅读2k次,点赞3次,收藏14次。我们在每个修改的地方都记录一条对应的 redo 日志显然是不现实的,因此实现方式是用时间换空间,我们在数据库崩了之后用日志还原数据时,在执行这条日志之前,数据库应该是一个一致性状态,我们用对应的参数,执行固定的步骤,修改对应的数据。1,MySQL 就是通过 undolog 回滚日志来保证事务原子性的,在异常发生时,对已经执行的操作进行回滚,回滚日志会先于数据持久化到磁盘上(因为它记录的数据比较少,所以持久化的速度快),当用户再次启动数据库的时候,数据库能够通过查询回滚日志来回滚将之前未完成的事务。_binglog

我的第一个Chrome小插件-基于vue开发的flexbox布局CSS拷贝工具_chrome css布局插件-程序员宅基地

文章浏览阅读3k次。概述之前介绍过 移动Web开发基础-flex弹性布局(兼容写法) 里面有提到过想做一个Chrome插件,来生成flexbox布局的css代码直接拷贝出来用。最近把这个想法实现了,给大家分享下。play-flexbox插件介绍play-flexbox一秒搞定flexbox布局,可直接预览效果,拷贝CSS代码快速用于页面重构。 你也可以通过点击以下链接(codepen示例)查_chrome css布局插件

win10下安装TensorFlow-gpu的流程(包括cuda、cuDnn下载以及安装问题)-程序员宅基地

文章浏览阅读308次。我自己的配置是GeForce GTX 1660 +CUDA10.0+CUDNN7.6.0 + TensorFlow-GPU 1.14.0Win10系统安装tensorflow-gpu(按照步骤一次成功)https://blog.csdn.net/zqxdsy/article/details/103152190环境配置——win10下TensorFlow-GPU安装(GTX1660 SUPER+CUDA10+CUDNN7.4)https://blog.csdn.net/jiDxiaohuo/arti

推荐文章

热门文章

相关标签