LMKD分享

背景

  1. Android是一个多任务系统,可以同时运行多个程序,一般来说,启动运行一个程序是有一定的时间开销的,因此为了加快运行速度,当你退出一个程序时,Android并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定会出现不足,这个时候Android系统杀进程的刽子手---Lowmemory Killer就起作用了。        
  2. Android 使用内核中的 lowmemorykiller驱动程序来监控系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。从内核 4.12 开始,lowmemorykiller 驱动程序已从上游内核中移除,用户空间 lmkd会执行内存监控以及进程终止任务。        
  3. 用户空间 lmkd 可实现与内核中的驱动程序相同的功能,但它使用现有的内核机制检测和估测内存压力。这些机制包括使用内核生成的 vmpressure 事件或压力失速信息 (PSI) 监视器来获取关于内存压力级别的通知,以及使用内存 cgroup 功能限制分配给每个进程的内存资源(根据每个进程的重要性)。
  4. 参考wiki:低内存终止守护程序  |  Android 开源项目  |  Android Open Source Project

LMKD什么时候启动?

在手机开机的时候,会调用lmkd.rc(system/memory/lmkd)初始化lmkd。

lmkd是系统一个非常重要的服务,开机是由init进程启动,如下所示:system/core/lmkd/lmkd.rc

system/memory/lmkd/lmkd.rc

LMKD的运行周期?

手机运行,LMKD全程都在。

水线

camera在后台

camera在后台,使用"sys.lmk.minfree_levels" 系统的水线

wj@wj:~/SSD_1T/M1_Stable$ adb shell getprop | grep -aEi "minfree"
[dalvik.vm.heapminfree]: [2m]
[sys.lmk.minfree_levels]: [18432:0,23040:100,27648:200,32256:250,55296:900,80640:950]

camera在前台

camera在前台,使用"persist.sys.lmk.camera_minfree_levels" 相机的水线

水线参数介绍

  • 水线中有六个level,每个level包含两个参数(minfree,min_score_adj)
  • minfree:代表内存大小,单位pages
  • min_score_adj:代表进程优先级,framework层的AMS获取并更新

当前系统剩余内存小于80640,会查杀oom_score_adj=950及以上的进程

LMKD 的基本工作原理

这张图简单地展示了 lmkd 的基本工作流程。

LMKD 的演变过程

lmkd(Low Memory Killer Daemon)是低内存终止守护进程,用来监控运行中android系统内存的状态,通过终止最不必要的进程来应对内存压力较高的问题,使系统以可接受的水平运行。

Android 版本

所处空间

杀进程时机

8.1 之前

kernel

监听 kswapd 触发的 shrink 回调

8.1 - 9.0

userspace

监听 vmpressure

10

userspace

监听 psi

相关配置属性

PSI

  1. Android 10 及更高版本支持新的 lmkd 模式,它使用内核压力失速信息 (PSI) 监视器来检测内存压力。上游内核中的 PSI 补丁程序集(反向移植到 4.9 和 4.14 内核)测量由于内存不足而导致任务延迟的时间。由于这些延迟会直接影响用户体验,因此它们代表了确定内存压力严重性的便捷指标。上游内核还包括 PSI 监视器,该监视器允许特权用户空间进程(例如 lmkd)指定这些延迟的阈值,并在突破阈值时从内核订阅事件。        
  2. PSI是Pressure stall information的简称,记录CPU/Memory/IO的压力信息,达到用户空间自定义的预值之后通知用户空间。
  3. 详细见官方文档:https://facebookmicrosites.github.io/psi/docs/overview.html
  4. PSI 是 Facebook 开源的一套解决重要计算集群管理问题的 Linux 内核组件和相关工具之一;是一种实时监测系统资源竞争程度的方法;以资源竞争等待时间的方式呈现memory、CPU 和 I/O 的资源短缺情况;PSI 统计数据为即将发生的资源短缺提供早期预警,从而实现更积极主动、细致的响应。
wj@wj:~/SSD_1T/M1_Stable$ adb shell
ishtar:/ # cd /proc/pressure/                                                                                                                                                                                     
ishtar:/proc/pressure # cat memory                                                                                                                                                                                
some avg10=0.00 avg60=0.00 avg300=0.00 total=3083343
full avg10=0.00 avg60=0.00 avg300=0.00 total=2506968
ishtar:/proc/pressure # cat io
some avg10=0.00 avg60=0.01 avg300=0.04 total=38859160
full avg10=0.00 avg60=0.00 avg300=0.00 total=26192413
ishtar:/proc/pressure # cat cpu
some avg10=2.32 avg60=3.10 avg300=5.89 total=3776585362
full avg10=0.00 avg60=0.00 avg300=0.00 total=0

vg10 、avg60 、avg300分别代表 10s、60s、300s 的时间周期内的阻塞时间百分比。total 是总累计时间,以毫秒为单位。

some 这一行,代表至少有一个任务在某个资源上阻塞的时间占比,full 这一行,代表所有的非idle任务同时被阻塞的时间占比.

PSI 中的 full 与 some

some : 至少有一个任务在某个资源上阻塞时间占比 

full : 所有任务同时阻塞的时间占比

参考:纯干货,PSI 原理解析与应用_psi原理_内核工匠的博客-CSDN博客

进程的级别

在Android中,进程主要分为以下几种:

ADJ级别

取值

解释

UNKNOWN_ADJ

1001

一般指将要会缓存进程,无法获取确定值

CACHED_APP_MAX_ADJ 

999

不可见进程的adj最大值

CACHED_APP_MIN_ADJ 

900

不可见进程的adj最小值

SERVICE_B_ADJ

800

B List中的Service(较老的、使用可能性更小)

PREVIOUS_APP_ADJ

700

上一个App的进程(往往通过按返回键)

HOME_APP_ADJ

600

Home进程

SERVICE_ADJ

500

服务进程

HEAVY_WEIGHT_APP_ADJ

400

后台的重量级进程,system/rootdir/init.rc文件中设置

BACKUP_APP_ADJ

300

备份进程

PERCEPTIBLE_APP_ADJ

200

可感知进程,比如后台音乐播放

VISIBLE_APP_ADJ

100

可见进程

ADJ级别

取值

解释

FOREGROUND_APP_ADJ

0

前台进程

PERSISTENT_SERVICE_ADJ

-700

关联着系统或persistent进程

PERSISTENT_PROC_ADJ

-800

系统persistent进程,比如telephony

SYSTEM_ADJ

-900

系统进程

NATIVE_ADJ

-1000

native进程(不被系统管理)

相关进程级别的定义在文件:frameworks/base/services/core/java/com/android/server/am/ProcessList.java中。        

从上面定义的adj数值来看:adj越小表示进程类型就越重要,系统进程的默认oom_adj 为-900,这类进程被杀的概率很低。

AMS与LMKD交互

 在AMS初始化时,通过调用ProcessList.java中updateOomLevels方法,计算出阈值adj 和 minfree ,通过socket与lmkd进行通信,传送数据(LMK_TARGET、minfree、adj),在lmkd中将adj 和minfree写入sys.lmk.minfree_levels中保存。

 AMS调整进程的adj相关接口(OomAdjuster.java):

  1. computeOomAdjLocked:计算adj(对优先级高于cache和empty的进程进行adj的分配)。该方法执行是在updateOomAdjLocked中。
  2. updateOomAdjLocked:更新adj(分配computeOomAdjLocked没有处理的cache和empty优先级的进程adj)
  3. applyOomAdjLocked:应用adj,直接保存对应进程的adj:ProcessList执行setOomAdj方法,通过socket传送数据(LMK_PROCPRIO、pid、uid等)给lmkd.c,最终lmkd.c针对每一个进程创建单独文件并写入adj。该方法执行是在updateOomAdjLocked中,最终通过它把computeOomAdjLocked和updateOomAdjLocked计算好的adj更新并保存。

AMS(ActivityManagerService)

CameraBoost

LMKD流程

服务启动后,入口在system/memory/lmkd/lmkd.c文件的main函数中,主要做了如下几件事:

  1. 读取配置参数
  2. 初始化 epoll 事件监听
  3. 锁住内存页
  4. 设置进程调度器
  5. 循环处理事件

system/memory/lmkd/lmkd.cpp

int main(int argc, char **argv) {
    if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        if (property_set(LMKD_REINIT_PROP, "")) {
            ALOGE("Failed to reset " LMKD_REINIT_PROP " property");
        }
        return issue_reinit();
    }

    update_props();

    ctx = create_android_logger(KILLINFO_LOG_TAG);

    if (!init()) {
        if (!use_inkernel_interface) {
            /*
             * MCL_ONFAULT pins pages as they fault instead of loading
             * everything immediately all at once. (Which would be bad,
             * because as of this writing, we have a lot of mapped pages we
             * never use.) Old kernels will see MCL_ONFAULT and fail with
             * EINVAL; we ignore this failure.
             *
             * N.B. read the man page for mlockall. MCL_CURRENT | MCL_ONFAULT
             * pins ⊆ MCL_CURRENT, converging to just MCL_CURRENT as we fault
             * in pages.
             */
            /* CAP_IPC_LOCK required */
            if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
                ALOGW("mlockall failed %s", strerror(errno));
            }

            /* CAP_NICE required */
            struct sched_param param = {
                    .sched_priority = 1,
            };
            if (sched_setscheduler(0, SCHED_FIFO | SCHED_RESET_ON_FORK, &param)) {
                ALOGW("set SCHED_FIFO failed %s", strerror(errno));
            }
        }

        if (init_reaper()) {
            ALOGI("Process reaper initialized with %d threads in the pool",
                reaper.thread_cnt());
        }

        if (!watchdog.init()) {
            ALOGE("Failed to initialize the watchdog");
        }

        if(!low_free_kill_init()) {
            ALOGE("Failed to initialize the low memory kill");
        }

        mainloop();
    }

    android_log_destroy(&ctx);
    close_handle_for_perf_iop();
    ALOGI("exiting");
    return 0;
}

update_props

static void update_props() {
    // step 1 :设置vmpressure level对应的oom_adj,这部分应该是mp_event_common用,目前弃用
    // low level vmpressure events : low 1001 ; medium 800 ; critical 0 ;super_critical 606
    // 现调用mp_event_psi
    
    level_oomadj[VMPRESS_LEVEL_LOW] =
        GET_LMK_PROPERTY(int32, "low", OOM_SCORE_ADJ_MAX + 1);
    level_oomadj[VMPRESS_LEVEL_MEDIUM] =
        GET_LMK_PROPERTY(int32, "medium", 800);
    level_oomadj[VMPRESS_LEVEL_CRITICAL] =
        GET_LMK_PROPERTY(int32, "critical", 0);
#ifdef QCOM_FEATURE_ENABLE
    /* This will gets updated through perf_wait_get_prop. */
    level_oomadj[VMPRESS_LEVEL_SUPER_CRITICAL] = 606;
#endif

.....

#ifdef QCOM_FEATURE_ENABLE
    // step 2 : Update Perf Properties LmkdImpl::update_perf_props 
    // 更新很多信息:
    LmkdStub::update_perf_props();
#endif

...

#if defined(QCOM_FEATURE_ENABLE) && defined(MI_PERF_FEATURE)
    //step 3 : XM_update props  LmkdImpl::mi_update_props 
    // AndoridS后,目前会走这部分逻辑
    LmkdStub::mi_update_props();
#endif
}

static int init(void)

static int init(void) {
    static struct event_handler_info kernel_poll_hinfo = { 0, kernel_event_handler };
    struct reread_data file_data = {
        .filename = ZONEINFO_PATH,
        .fd = -1,
    };
    struct epoll_event epev;
    int pidfd;
#ifdef QCOM_FEATURE_ENABLE
    union meminfo info;
#endif
    int i;
    int ret;

    page_k = sysconf(_SC_PAGESIZE);
    if (page_k == -1)
        page_k = PAGE_SIZE;
    page_k /= 1024;

    update_psi_window_size();

#if defined(QCOM_FEATURE_ENABLE) && defined(MI_PERF_FEATURE)
    if (!meminfo_parse(&info)) {
        LmkdStub::mi_init(page_k, info);
    } else {
        ULMK_LOG(E, "Failed to parse the meminfo\n");
    }
#endif
    /*
     * Ensure min polling period for supercritical event is no less than
     * PSI_POLL_PERIOD_SHORT_MS.
     */
#ifdef QCOM_FEATURE_ENABLE
    if (psi_poll_period_scrit_ms < PSI_POLL_PERIOD_SHORT_MS) {
           psi_poll_period_scrit_ms = PSI_POLL_PERIOD_SHORT_MS;
    }
#endif
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        ALOGE("epoll_create failed (errno=%d)", errno);
        return -1;
    }

    // mark data connections as not connected
    for (int i = 0; i < MAX_DATA_CONN; i++) {
        data_sock[i].sock = -1;
    }

    ctrl_sock.sock = android_get_control_socket("lmkd");
    if (ctrl_sock.sock < 0) {
        ALOGE("get lmkd control socket failed");
        return -1;
    }

    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
    if (ret < 0) {
        ALOGE("lmkd control socket listen failed (errno=%d)", errno);
        return -1;
    }

    epev.events = EPOLLIN;
    ctrl_sock.handler_info.handler = ctrl_connect_handler;
    epev.data.ptr = (void *)&(ctrl_sock.handler_info);
    if (epoll_ctl(epollfd, EPOLL_CTL_ADD, ctrl_sock.sock, &epev) == -1) {
        ALOGE("epoll_ctl for lmkd control socket failed (errno=%d)", errno);
        return -1;
    }
    maxevents++;

    has_inkernel_module = !access(INKERNEL_MINFREE_PATH, W_OK);
    use_inkernel_interface = has_inkernel_module && !enable_userspace_lmk;

    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
        if (init_poll_kernel()) {
            epev.events = EPOLLIN;
            epev.data.ptr = (void*)&kernel_poll_hinfo;
            if (epoll_ctl(epollfd, EPOLL_CTL_ADD, kpoll_fd, &epev) != 0) {
                ALOGE("epoll_ctl for lmk events failed (errno=%d)", errno);
                close(kpoll_fd);
                kpoll_fd = -1;
            } else {
                maxevents++;
                /* let the others know it does support reporting kills */
                property_set("sys.lmk.reportkills", "1");
            }
        }
    } else {
        if (!init_monitors()) {
            return -1;
        }
        /* let the others know it does support reporting kills */
        property_set("sys.lmk.reportkills", "1");
    }

    for (i = 0; i <= ADJTOSLOT(OOM_SCORE_ADJ_MAX); i++) {
        procadjslot_list[i].next = &procadjslot_list[i];
        procadjslot_list[i].prev = &procadjslot_list[i];
    }

    memset(killcnt_idx, KILLCNT_INVALID_IDX, sizeof(killcnt_idx));

    /*
     * Read zoneinfo as the biggest file we read to create and size the initial
     * read buffer and avoid memory re-allocations during memory pressure
     */
    if (reread_file(&file_data) == NULL) {
        ALOGE("Failed to read %s: %s", file_data.filename, strerror(errno));
    }

    /* check if kernel supports pidfd_open syscall */
    pidfd = TEMP_FAILURE_RETRY(pidfd_open(getpid(), 0));
    if (pidfd < 0) {
        pidfd_supported = (errno != ENOSYS);
    } else {
        pidfd_supported = true;
        close(pidfd);
    }
    ALOGI("Process polling is %s", pidfd_supported ? "supported" : "not supported" );

    return 0;
}
  • 创建epoll,用以监听 9 个event;
  • 初始化socket /dev/socket/lmkd,并将其添加到epoll 中;
  • 根据prop ro.lmk.use_psi 确认是否使用PSI 还是vmpressure;
  • 根据prop ro.lmk.use_new_strategy 或者通过 prop ro.lmk.use_minfree_levels 和 prop ro.config.low_ram 使用PSI 时的新策略还是旧策略;
  • 新、旧策略主要体现在mp_event_psi 和mp_event_common 的选择, AndroidS /proc/pressure/memory 获取内存压力是否达到some/full 指定来确认是否触发event;
  • 后期epoll 的触发主要的处理函数是mp_event_psi 或 mp_event_common;
  • extend_reclaim_init

epoll_create

    /* 1个socket 监听 lmkd fd dev/socket/lmkd 
       3个client下发的socket ctrl_connect_handle添加到epoll中
       3个pressure init_mp_psi/init_mo_common添加到epoll中
       1个监听lmkd事件 但是现在弃用
       1个wait for process death,start_wait_for_proc_kill添加到epoll
       */
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
    if (epollfd == -1) {
        ALOGE("epoll_create failed (errno=%d)", errno);
        return -1;
    }

    // mark data connections as not connected
    for (int i = 0; i < MAX_DATA_CONN; i++) {
        data_sock[i].sock = -1;
    }

获取socket并且监听

//socket lmkd
    ctrl_sock.sock = android_get_control_socket("lmkd");
    if (ctrl_sock.sock < 0) {
        ALOGE("get lmkd control socket failed");
        return -1;
    }

    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);
    if (ret < 0) {
        ALOGE("lmkd control socket listen failed (errno=%d)", errno);
        return -1;
    }

    epev.events = EPOLLIN;

//未完待续....

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/11964.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【论文阅读】3D-LaneNet

【论文阅读】3D-LaneNet 主要要做的事情就是 lane detection。这里提一下 BEV&#xff08;Bird‘s Eye View&#xff09; 感知算法&#xff0c;为了将 2D 图像映射到 3D 空间中&#xff0c;能够更准确的检测物体位置&#xff0c;需要 BEV 感知的结果。后续还会继续了解这方面内…

企业数据安全能力建设思路

在现代社会&#xff0c;企业数据安全已经成为一个非常重要的话题。企业数据安全能力的建设是每个企业都必须面对和解决的问题。企业数据安全能力建设思路包括以下几个方面&#xff1a; 1. 建立完善的安全管理制度 企业要建立完善的安全管理制度&#xff0c;包括信息安全政策、…

注册claude AI账号 slack工作区账号

Claude 是建立在 slack工作区的一个AI人工助手&#xff0c;更像是将chatgpt集成到了会议模式&#xff0c;一个账号实际上拥有了你的会议室和你的AI助手&#xff0c;你可以让你的朋友和同事进入你的房间体验。 Claude是不是openai的产物&#xff1f;目前还不知道&#xff0c;不…

phpstudy本地环境搭建图文教程

作者&#xff1a;Eason_LYC 悲观者预言失败&#xff0c;十言九中。 乐观者创造奇迹&#xff0c;一次即可。 一个人的价值&#xff0c;在于他所拥有的。可以不学无术&#xff0c;但不能一无所有&#xff01; 技术领域&#xff1a;WEB安全、网络攻防 关注WEB安全、网络攻防。我的…

价值迭代求解马尔可夫决策过程

Value Iteration Algorithm 其算法思想是: 在每一个状态s下&#xff0c; 之迭代算法流程如下&#xff1a; 初始化状态价值state value&#xff0c;即对每个状态的价值都赋一个初始值&#xff0c;一般是0 计算每一个状态-动作对的 动作价值函数&#xff0c;通常通过创建一个二维…

迎难而上,阿里高频考点2023Java岗面试突击手册

上周我接到一位粉丝的私信说目前互联网形势实在对他太不友好&#xff0c;感觉自己每个技术栈都会一点&#xff0c;但不是完全精通。基本二面三面的时候就挂了&#xff0c;已经完全不知道该朝哪个方向努力了&#xff0c;希望可以给他一些建议和方法指导。那么&#xff0c;本次就…

【两阶段鲁棒优化】利用列-约束生成方法求解两阶段鲁棒优化问题(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

MySQL--表的使用--0409

目录 1.表的基本操作 1.1 创建表 2. 查看表结构 3.修改表 3.1 新增一列 3.2 修改列属性 3.3 修改名字 3.3.1 修改表名字 3.3.2 修改表内列名字 3.4删除 3.4.1 删除列 3.4.2 删除表 1.表的基本操作 查看自己目前在哪个数据库里面 mysql> select database(); 1.1 创…

基于PCA与LDA的数据降维实践

基于PCA与LDA的数据降维实践 描述 数据降维&#xff08;Dimension Reduction&#xff09;是降低数据冗余、消除噪音数据的干扰、提取有效特征、提升模型的效率和准确性的有效途径&#xff0c; PCA&#xff08;主成分分析&#xff09;和LDA&#xff08;线性判别分析&#xff0…

状态错误 MSB8040,此项目需要缓解了 Spectre 漏洞的库。从 Visual Studio 安装程序(单个组件选项卡)为正在使用的任何工具集和体

“Spectre Mitigation”缓解错误 如果出现“Spectre Mitigation”这种错误&#xff0c;就要了解下PIPE技术&#xff1a;流水线技术&#xff0c;比如3级流水线&#xff0c;避免CPU空闲&#xff0c;不浪费时间&#xff0c;但是前提是没有跳转&#xff0c;指令都是顺序执行的&…

3.9、互斥锁(互斥量)

3.9、互斥锁&#xff08;互斥量&#xff09;1.互斥锁&#xff08;互斥量&#xff09;的介绍2. 互斥量相关操作函数3.互斥量函数的使用介绍①pthread_mutex_init②pthread_mutex_destroy③pthread_mutex_lock④pthread_mutex_trylock⑤pthread_mutex_unlock3.利用互斥锁实现线程…

王小川,才是深「爱」李彦宏的那个人?

在推出中国首个类ChatGPT产品「文心一言」后&#xff0c;李彦宏在接受专访时断言&#xff0c;中国基本不会再出一个OpenAI了&#xff0c;「创业公司重新做一个ChatGPT其实没有多大意义&#xff0c;基于大语言模型开发应用机会很大&#xff0c;没有必要再重新发明一遍轮子。」 听…

SPARQL endpoint with Ontop CLI部署,python使用SPARQLWrapper

Ontop CLI部署&#xff0c;避免踩坑0.前言1.提示2.详细部署流程3.python操作4.碎碎念0.前言 教程&#xff1a;Setting up an Ontop SPARQL endpoint with Ontop CLI照着教程来&#xff0c;不知道为啥&#xff0c;总是报错&#xff0c;后来发现&#xff0c;手机搜到的跟电脑不一…

pytorch 数据类型

文章目录一、tensor如何表示字符串数据类型类型判断Dimension 0Dimension 1Dimension 2Dimension 3Dimension 4mixed二、创建Tensorimport from numpyimport from listuninitialized 未初始化set default typerand/rand_like, randintfulllinspaceindex切片三、维度变换总结一、…

尚硅谷大数据技术Scala教程-笔记04【集合】

视频地址&#xff1a;尚硅谷大数据技术之Scala入门到精通教程&#xff08;小白快速上手scala&#xff09;_哔哩哔哩_bilibili 尚硅谷大数据技术Scala教程-笔记01【Scala课程简介、Scala入门、变量和数据类型、运算符、流程控制】尚硅谷大数据技术Scala教程-笔记02【函数式编程】…

交换机Access模式和Trunk模式配置演示

一.Access配置 1.创建VLAN 2.设置为接口模式&#xff0c;将接口划入不同VLAN 3.测试 二.Trunk配置 1. 接口VLAN配置 2.设置允许VLAN流量通过&#xff0c;可写all 3.测试 一.Access配置 实现VLAN10 和 VLAN20之间通信隔离 1.创建VLAN [s1]vlan 10 [s1]vlan 20[s1]vlan…

Android中的AsyncTask

近期写了一个项目&#xff0c;在前台刷新界面的时候需要操作数据库&#xff0c;进行数据操作&#xff0c;在UI线程更新数据会导致ANR&#xff0c;程序十分卡&#xff0c;因此用了AsyncTask进行后台数据处理。 介绍 AsyncTask是一个用于在后台线程执行异步任务并在主线程更新U…

set/multiset容器

1、set/multiset容器简介 但是 set 容器只有键值&#xff0c;在插入数据的时候会自动根据 键值 进行排序&#xff0c;所以不允许有相同的键值存在&#xff0c;也不能修改 set 容器元素值&#xff0c;会破坏 set 的数据结构。set 容器的迭代器是只读迭代器 2、set容器 API 操作…

读懂AUTOSAR :DiagnosticLogAndTrace DLT(四)-- API解析

一、周期调用的函数&#xff1a;Dlt_TxFunction 根据参数DltGeneralTrafficShapingSupport&#xff0c;决定如何去发送DLT消息。如果为TRUE&#xff0c;那需要参考参数DltLogChannelTrafficShapingBandwidth为每个Log通道设置发送带宽&#xff1b;如果为FALSE&#xff0c;那么…

纯虚函数和抽象类

什么时候使用纯虚函数: 某些类,在现实角度和项目实现角度,都不需要实例化(不需要创建它的对象),这个类中定义的某些成员函数,只是为了提供一个形式上的借口,准备让子类来做具体化的实现,此时,这个方法就可以定义为"纯虚函数",包含纯虚函数的类,就称为抽象类. 纯虚函…