LMKD分享

news2025/1/18 20:05:07

背景

  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;

//未完待续....

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL优化】快速入门慢SQL优化

MySQL B树结构&#xff08;二叉排序树&#xff09; 索引 SQL优化&#xff0c;主要就是在优化索引 索引:相当于书的目录 索引:index是帮助MYSQL高效获取数据的数据结构。索引是数据结构&#xff08;树:B树(默认)、Hash树…) 索引的弊端: 1.索引本身很大&#xff0c;可以存…

Spring Cloud Config配置服务及那些你不知道的坑

目录 1、为什么选择Spring Cloud Config 1.1 集中式管理 1.2 动态修改配置 2、Spring Cloud Config 简介 3、服务端配置 3.1 添加依赖 3.2 开启服务注册 3.3 添加YML配置 3.4 创建远程分支及Profile配置文件 3.5 启动并测试服务 4、客户端配置 4.1 添加依赖 4.2 开…

2.3-6循环链表

原理的单链表和循环单链表的区别&#xff1a; 初始化循环单链表时&#xff0c;使头节点next指针指向头节点。 判断循环单链表是否为空。 对比&#xff1a; 单链表&#xff1a;if(L->next NULL) 双链表&#xff1a;if(L->nextL) 判断循环单链表的结点p是否为表尾结点…

汇聚音视频新能量 探索行业新蓝海

视频行业卷成红海&#xff0c;如何突破瓶颈&#xff0c;去探索行业的新蓝海&#xff0c;本次LiveVideoStackCon 2022北京站邀请到快手高级副总裁、研发线负责人于冰&#xff0c;以《汇聚音视频新能量&#xff0c;探索行业新蓝海》为题&#xff0c;从视频行业趋势和痛点出发&…

类ChatGPT模型ChatGLM-b6本地部署实践

国外ChatGPT火爆持续&#xff0c;前一段时间百度发布“文心一言”还没有全面放开测试&#xff0c;这不阿里“通义千问”又悄然而至&#xff0c;国内大模型AI产品渐渐浮出水面。早在2022年8月份时候清华大学的对话语言模型ChatGLM-6B就发布并开源&#xff0c;本文简要介绍ChatGL…

flex弹性布局详细介绍

这里提供一个可以边学习边玩的flex学习网站&#xff1a;弹性盒青蛙 目录一、Flex布局是什么&#xff1f;二、属性1. justify-content 属性2. align-items属性3. flex-direction属性4. order属性5. align-self属性6. flex-wrap 属性7. flex-flow属性8. align-content属性三、综合…

测试新人必看,软件测试测试流程

不同类型的软件产品测试的方式和重点不一样&#xff0c;测试流程也会不一样。同样类型的软件产品&#xff0c;不同的公司所制定的测试流程也会不一样。虽然不同软件的详细测试步骤不同&#xff0c;但它们所遵循的最基本的测试流程是一样的。 1分析测试需求 2制定测试计划 3设…

Python opencv 实现图像平移及旋转

Python opencv 实现图像平移及旋转 仿射变换是一种仅在二维平面中发生的几何变形&#xff0c;变换之后的图像仍然可以保持直线的 “平直性” 和 “平行性”&#xff0c;也就是说原来的直线变换之后还是直线&#xff0c;平行线变换之后还是平行线。图像平移和图像旋转是常见的放…

IMX6ULL-IRQ中断之添加中断向量表

一. 中断向量表 中断向量表是存放中断向量的表。中断服务程序的入口地址或存放中断服务程序的首地址成为中断向量&#xff0c;因此中断向量表是一系列中断服务程序入口地址组成的表。当有中断事件发生时&#xff0c;处理器通过中断向量表进入相关的中断服务程序处理事件。 二.…

自动化测试(二):QTP验证点

1 、程序自带验证点 自带验证点&#xff1a;图形界面insert -> checkpoint Standard Checkpoint 标准验证&#xff1a;用于检查测试对象的属性 Text Checkpoint 文本验证&#xff1a;用于检查文本字符串是否在应用程序中的适当位置出现 Text Area Checkpoint文本区域验…

饮用水中的六价铬去除工艺详解

铬是人体必需的微量元素&#xff0c;天然水不含铬&#xff0c;海水中铬的平均浓度为0.05μg/L&#xff0c;饮用水中铬含量更低。 铬在水中主要以三价和六价形式存在&#xff0c;三价的铬是对人体有益的元素&#xff0c;而六价铬是有毒的。由于其毒性之高&#xff0c;已被国家列…

Smith预估器

Smith预估器主要针对存在大滞后的系统,作用延迟和反馈延迟环节的控制,Smith预估器的另一篇文章,请参看下面的博客文章: 博途1200/1500PLC Smith预估器(补偿器)算法实现(FB)_RXXW_Dor的博客-CSDN博客在写这篇文章之前写过一篇"大林控制算法",大家可以参看下面这…

免费的包噪音网站分享

免费的包噪音网站分享 现代生活中&#xff0c;噪音扰人&#xff0c;影响健康和情绪。白噪音可以为人们提供放松心情、提高睡眠质量和专注力的帮助。 现在有很多免费的白噪音网站可以任意使用和分享&#xff0c;包括海浪声、雨声、蝉鸣声等等&#xff0c;非常适合在办公室、家里…

windows 下安装 ffmpeg

介绍一下我的环境及开发软件版本 windows phpstudy php7.3 进入安装步骤 1.下载windows系统的FFMpeg 下载链接&#xff1a; http://ffmpeg.org/download.html ps: 这里有各种版本了 &#xff08;未使用这个版本&#xff09;git地址&#xff1a;https://github.com/BtbN/…

数据结构初阶 - 汇总

-0- 数据结构前言 什么是数据结构 什么是算法 数据结构和算法的重要性-1- 时间复杂度和空间复杂度 &#x1f449;数据结构 -1- 时间复杂度和空间复杂度 | C 算法效率 时间复杂度大O的渐进表示法eg 空间复杂度 常见复杂度对比OJ 消失的数组 轮转数组-2- 顺序表 与 链表 &am…

反射之构造方法和成员变量

什么是反射 反射允许对成员变量&#xff0c;成员方法和构造方法的信息进行编程访问。 Class类中获取构造方法的方法 Constructor getConstructors&#xff08;&#xff09; Constructor getDeclaredConstructors&#xff08;&#xff09; Constructor…

[STM32F103C8T6]看门狗

看门狗&#xff1a; 在由单片机构成的微型计算机系统中&#xff0c;由于单片机的工作常常会受到来自外界电磁场的干扰&#xff0c;造 成程序的跑飞&#xff0c;而陷入死循环&#xff0c;程序的正常运行被打断&#xff0c;由单片机控制的系统无法继续工作&#xff0c;会 造成整个…

LeetCode 1206. 设计跳表

LeetCode 1206. 设计跳表 难度&#xff1a;hard\color{red}{hard}hard 题目描述 不使用任何库函数&#xff0c;设计一个 跳表 。 跳表 是在 O(log(n))O(log(n))O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树&#xff0c;其功能与性能相当&am…

【区块链】【FISCO】WeIdentity

什么是 WeIdentity&#xff1f; 官方的说法&#xff1a;去中心化身份标识解决方案。其实说白了就是互联网上每个人都拥有自己数字身份&#xff0c;并且这个身份是唯一且不可篡改的。 WeIdentity要解决的问题就是用来解决数字身份验证的问题。传统互联网身份验证的方式通常用账…

将ip地址中的每一个字符串按照分隔符提取

1、算法思想 该题采用 c 中的 string 完成比较方便 对于字符串 string str1“hehehe:hahaha:xixixi:lalala” 定义 int pos 0&#xff0c;记录子串的初始位置 在循环语句中重复执行以下操作&#xff1a; &#xff08;1&#xff09;、定义 int ret str1.find(":",…