Low Memory Killer in Android

目录

低内存管理(Linux vs Android)

Linux内存回收

shrink_slab原理

shrink_zone原理

oom killer

oom killer设计原则

OOM killer具体实现

android的lmk(Low Memory Killer)

Android系统特点

oom killer在android中的不足

​​​​​​​LMK概述

LMK提供的接口

接口说明

​​​​​​​minfree与adj初始化

LMK实现

​​​​​​​LMK驱动实现

​​​​​​​android进程管理

​​​​​​​Android组件

​​​​​​​Android进程生命周期

进程adj调整

adj值

​​​​​​​adj调整原则

​​​​​​​adj调整时机

​​​​​​​adj调整算法

如何降低被kill概率


低内存管理(Linux vs Android)

Linux内存回收

低内存情况下,Linux内存回收有二种模式:一种是直接内存回收,通过伙伴系统分配一大块内存或者需要创建一个很大的缓冲区时,如果没有足够多的free pages,那么系统会尽快进行页面回收;另一种是定期内存回收,定期唤醒kswapd内核线程,当系统空闲内存小于阈值时则进行页面回收。具体的系统调用过程,请参考图1所示。

由图1可知,不管是直接页面回收,还是周期页面回收,最终都调用shrink_slab()和shrink_zone()。直接页面回收,在一定的约束下,如果最终没能释放所需page,则调用OOM(out of memory) killer。

shrink_slab原理

先向操作系统内核注册shrinker函数,会在内存较少时主动释放一些内存。shrink_slab()会遍历shrinker链表,回调所有注册了shrinker函数的处理内存的操作。当前,linux操作系统中主要的shrinker函数有:

  1. shrink_dcache_memory():负责dentry缓存。
  2. shrink_icache_memory():负责inode缓存。
  3. mb_cache_shrink_fn():负责用于文件系统元数据的缓存。

shrink_zone原理

shrink_zone()的主要工作可以分为三步:

  1. 通过shrink_active_list()将页面从active移到inactive list;
  2. 通过shrink_inactive_list()将inactive list的页放入临时链表;
  3. 最终调用shrink_page_list()回收页。回收算法如图2所示。

           图1 内存回收系统调用

            图2 回收页算法

oom killer

Oom killer是kernel在内存耗尽时的最后手段,使用oom_badness(),为每个进程计算得分,移除得分高的进程。

系统调用如下:

out_of_memory()->select_bad_process->oom_badness()

OOM killer计算进程得分的策略是:损失最少的工作,释放最大的内存同时不伤及无辜,并且杀掉的进程数尽量少。

oom killer设计原则

OOM killer简单粗暴地作风并不符合linux的风格,并且也不符合linux机制与策略分离的原则。然而当系统内存不足时,已经是将死之态,也无需讲究,暴力往往是最有效的解决方案。虽然粗暴,但是手段上也需要尽可能地“精致”,oom killer从提出到现在,也已经进行了几次优化,目前的设计遵循下面几方面:

  • 以进程所占物理内存作为判断依据

  进程实际所需的是物理内存,而早期以进程的虚拟地址空间大小为基准,显然不准确。

  • 可配置的用户建议策略

有些进程占用物理内存很大,但是也在做很重要的事情,如KDE桌面主进程。因此必须将一部分控制权交出给用户层,用户可以根据具体情况,对进程重要度加权。

  • 简单并且合理的默认策略

  将特权进程,免于oom killer机制;如果按现有选择策略,无法选择出需要被结束的进程,那么就直接panic,因为已经无能为力;另外,杀子不杀父,因为如果kill父进程,还需要为子进程托孤,需要做很多事情,而这会加重内存不足的情况,况且父进程往往是关键服务,而子进程往往是工作都线程,kill父进程对用户的影响更大。

OOM killer具体实现

  • 提供给用户层的接口:  /proc/<pid>/oom_adj & /proc/<pid>/oom_score_adj

    oom_adj是以前的接口,为了兼容低版本,现在还保留此接口,但是最好不要再使用了。

    oom_score_adj的值会影响各个进程的最终得分,范围是-1000(OOM_SCORE_ADJ_MIN)~1000(OOM_SCORE_ADJ_MAX)。用户空间可以调整进程的oom_score_adj值,来影响oom killer的行为,值越大,进程越容易被kill,-1000可以关闭oom killer对此进程的作用。

  • 算法

oom_badness是oom killer选择“bad”进程的核心算法,流程图如图3所示。

         图3 oom_badness流程图

android的lmk(Low Memory Killer)

 Linux 已经有oom killer了,那android为何又要引入LMK呢?

Android系统特点

Android是嵌入式系统,通常运行在内存很有限的设备上(如手机,平板)。这类设备,有一个特点就是“屏幕独占性”,即要调出一个任务,意味着必须退出或隐藏当前的任务。另外,此类设备同时运行的任务不会太多,并且影响用户使用体验的任务比较好识别。除此之外,“高交互”的系统特征,决定必须提高系统响应速度,因为这直接影响用户的使用体验。而在嵌入式系统中,资源相当有限,如何在低配置设备中,提高响应速度,是android需要重点考虑的。

Android使用的特点,决定设计的方案:

  1. Android进程并不主动退出,而是作为“空进程”保留在内存中,以便用户再次进入该应用时,可以提高响应速度;
  2. 因为进程不主动退出,必须有一套机制能实时按一定的策略回收内存;
  3. Android进程的重要度,随着用户操作的改变而改变,即实时改变;

oom killer在android中的不足

  • 启动时机不合适

OOM killer是在内存被耗尽时,启动的极端内存回收机制。Android中用户对系统反应快慢非常敏感。别说在内存被耗尽时,就是在内存不足时,系统出现,反应延迟,也会严重影响用户体验。再加上android进程的“不主动退出”机制,所以需要周期检查,进行内存管理。

  • 选择进程策略不合适

OOM killer根据进程所占物理内存为主要判断依据,加上oom_score_adj和其它一些因素调整。在android中,物理内存占用多少,不能成为其主要判断依据,而应该根据进程的重要程度,在相同重要度的情况下,才考虑内存因素。进程的重要度是相对于用户而言的。

  • 选择进程范围不合适

OOM killer候选的进程是除init进程,内核线程以及一些特权进程外的所有进程。而android只需要管理zygote启动之后的进程,之前的native进程都不需要考虑。

  • 用户层控制权的不合适

OOM killer提供了oom_score_adj的接口,程序可以自己设置值,降低OOM时的得分。一般而言,这个值不会频繁去变更。这对于android系统特点3)并不适用。在android系统中,用户层需要大部分的控制权限,并且能够根据用户操作实时变更进程的重要度。

​​​​​​​LMK概述

由于android有自己的应用场景,而OOM killer并不能满足其要求,因此引入了LMK机制。主要解决android中“进程不主动退出”机制所引起的内存回收问题。

LMK复用了linux低内存管理中的shrink_slab机制,将lmk注册到shrinker键表中,那么linux中无论是直接回收还是定期回收内存,都可以调到lmk的处理。

LMK还复用了用户层控制接口(/proc/<pid>/oom_score_adj),并将其作为选择进程的主要依据。FW层的AMS作为类似于“任务管理器”,根据用户行为管理进程。

LMK的整体结构如图4所示。

              图4 LMK整体结构

LMK提供的接口

接口说明

LMK将剩余内存分为几个等级,最多支持6个等级,分级的策略则交与用户层制定,其值写入以下文件,作为LMK驱动的参数。minfree值以页为单位(一页为4K)。

LMK按重要度(adj)为进程分组,最多支持6组,组数一般都设成与minfree对应。与minfree一样,分组的策略也由用户层制定,并将值写入以下文件,作为LMK驱动的参数。

这里需要注意,前面OOM killer中已经介绍过,adj是低版本的接口,现在已经换成oom_score_adj。但是由于android上层一直使用旧接口,修改上层太麻烦,因此android用户层仍然继续使用adj接口,只是在kernel层将adj自动转换成oom_score_adj。

上面二个接口,就是用户层制定的,即在什么的内存情况下,LMK开始工作,并且以什么标准工作。表1配置,当内存剩余6656*4K=26M时,LMK将kill所有 adj为9及以上的进程。

Minfree

3072

4096

4608

6656

8704

10752

adj

0

1

3

9

11

15

          表1 mk配置​​​​​​​

​​​​​​​minfree与adj初始化

Android原生对minfree和adj的初始化,放在ProcessList:updateOomLevels()中。系统默认adj配置如下所示:

  系统给minfree给了二个标准配置,mOomMinFreeLow与mOomMinFreeHigh​​​​​​​

然后根据内存屏幕大小,决定用lowminfree还是highminfree。内存在300~700M之间用lowminfree;内存在700M及以上,用highminfree;屏幕在480*800~1280*800之间用lowminfree;屏幕1280*800及以下,用highminfree。内存与屏幕,只要有一个需要用highminfree ,最终就用highminfree。

除此之外,如果是64bit设备,minfree*1.5。还可以根据设备配置config:

对minfree微调,adj与minfree的值,最终调用lmkd写入module/lowmemorykiller/parameters/,

作为参数传入lowmemorykiller驱动。

LMK实现

    LMK整套机制,可以分为二部分,一个是LMK驱动,一个是AM对进程的管理。

​​​​​​​LMK驱动实现

前面已经提到,LMK是通过shrinker机制,将自己整合进kernel。在内存不足或者定期检查内存时,都会通过shrink_slab,回调lmk操作。Lowmem_shrink()就是lmk驱动的核心。具体操作流程参考图5。

          图5 lowmem_shrink流程图

​​​​​​​android进程管理

Lmk驱动只是机制,实现非常简单。Android运行时的进程管理才是LMK的复杂点,因为需要在运行时实时为每个进程打分(设置adj)。

​​​​​​​Android组件

Android中,进程是由组件组成,每个组件都扮演不同的角色。组件的不同状态决定进程的adj。四大组件分别是activity,services,Broadcast,Content Providers。

1.Activity

   Activity是屏幕上单独的虚拟UI。通常,activity是应用与用户交互的主要组件。在activity的生命周期中,有create,running,pause,stop,destroy之些状态,当在running状态时,表示用户正在操作这个activity。

          图6 Activity生命周期

2.Services

Services通常是在后台运行的组件,但是也可以在前台启动。应用一旦在后台启动service,即使用户切换到其它应用,service还会在后台运行。服务没有用户交互界面。

         图7 service生命周期

3.Content Providers

Content Providers为不同的应用提供内容(数据),支持在文件或数据库中存储结构化数据。其它应用可以通过content resolver访问数据。

4.Broadcast Receiver

可以接收系统范围的广播。应用可以发起广播信息给另外一个应用,如文件下载已经完成等。它没有任何用户界面,但是会在status bar上形成注意信息。

​​​​​​​Android进程生命周期

Android系统试图尽可能地保持应用进程,但是最终需要为新的或者更重要的进程回收内存,而移除老的进程。为了决定kill哪个进程,系统根据进程中运行的组件和这些组件的状态,给每个进程一个重要度。系统总是先kill最不重要的进程,来回收系统资源。

重要度分为5个level:(the first is most important and is killed last)

1.Foreground process(前台进程)

用户正在操作的进程。下面任何一个条件成立,都可以认为是前台进程。

  1. 包含用户正在交互的Activity(Activity的onResume()已经被执行);
  2. 包含Service,并且这个Service绑定于用户正在交互的activity;
  3. 包含Service,并且这个Service运行在前台-Service已经执行startForeground();
  4. 包含Service,并且这个Service正在执行其生命周期的callbacks(onCreate(),onStart(),or onDestroy());
  5. 包启BroadcastReceiver,并且其正在执行onReceive()方法。

在给定的任一时间,通常只有小部分Foreground process(前台进程)。

2.Visible process(可见进程)

没有任何前台组件,但是用户在屏幕上仍然可以看到。下面任何一个条件成立,都可以认为是可见进程。

  1. 包含一个不在前台Activity,但是用户仍可以看到(其onPause()方法已经被执行);这可能发生,例如,前台activity起了一个dialog,此时后台的activity不在前台,但是用户仍能看到。
  2. 包含一个Service,并且此Service绑定到visible/foreground activity。

3.Service process(服务进程)

运行着Service但不属于前台和可见的进程,且此service已经执行过startService()。尽管服务进程与用户可见的无关,但是却正在做用户关心的事(如正在后台播放音乐或者从网上下载数据),因此系统会尽可能地保持他们运行。

4.Background process(后台进程)

包含一个activity,当前对用户不可见(activity的onStop()已经被执行)。这些进程不会直接影响用户体验,系统会为前台/可见/服务进程回收内存,随时会kill他们。通常会有很多后台进程在运行,他们被保存在LRU(least recently used)列表中,确保用户最后使用的最后被kill。如果一个activity正确地实现了其生命周期方法,并且保存了其正确地状态,那么它被kill,就不会影响用户体验,因为当用户回到这个activity时,由于其已经保存了其状态,用户看到地还是原来的。

5.Empty process(空进程)

不包含任何存活应用组件的进程。保存这些进程,纯粹就是为了缓存,方便下次组件运行在此进程时,加速启动时间。系统为了在进程缓存和kernel缓存间平衡系统资源,总是会kill这些进程。

进程adj调整

adj值

  adj的有效范围从-17~15,各值所代表的意思,如表2所示:

adj

意思

UNKNOWN_ADJ

16

实现需要,一般不会给进程设置,只做为临时值过渡

CACHED_APP_MAX_ADJ

15

进程不可见,并且只包启activity组件

CACHED_APP_MIN_ADJ

9

进程不可见,并且只包含activity组件

SERVICE_B_ADJ

8

包含service组件的进程分成二组,比起A,B组中的service相对比较陈旧,对用户影响小。

PREVIOUS_APP_ADJ

7

用户使用的前一个应用的进程。提高此进程优先级,是因为按back键返回到前一个应用的操作非常普通。

HOME_APP_ADJ

6

Home进程

SERVICE_ADJ

5

含serivce组件的进程

HEAVY_WEIGHT_APP_ADJ

4

重量级应用的进程

BACKUP_APP_ADJ

3

正在备份操作的进程

PERCEPTIBLE_APP_ADJ

2

进程含有用户可感知的组件,如后台音乐播放器

VISIBLE_APP_ADJ

1

进程含有用户可见的activity

FOREGROUND_APP_ADJ

0

前台进程

PERSISTENT_SERVICE_ADJ

-11

系统进程或长驻进程绑定的进程

PERSISTENT_PROC_ADJ

-12

系统长驻进程,如电话。

SYSTEM_ADJ

-16

系统进程,默认设为-16

NATIVE_ADJ

-17

系统不管的native进程,统一设为-17

​​​​​​​adj调整原则

Android基于进程中存活着组件的重要度,尽可能地提高进程的重要水平。例如,一个进程既有一个service,又有一个visible activity,那么进程会被标为可见进程,而非服务进程。

另外,如果其它进程依赖一个进程,那么这个进程的重要度也可能提高-提供服务的进程,其重要度不能低于其服务的进程。例如:进程A为进程B提供content provider,或者进程A的Service绑定于进程B的组件,那么进程A的重要度至少等同于进程B。

因为运行service的进程,其重要度比运行后台activity的进程高,所以如果activity需要长时间的操作(尤其这个操作比activity存活时间长),那么启动service比简单的创建工作线程,效果好。例如,网页上上传图片的activity,应该启动service来执行上传操作,这样即使用户leave这个activity,上传操作仍旧会在后台继续。使用service,其操作的优先级至少是“服务进程”,而不需要关心activity。同理,broadcast receivers也推荐使用service,而非将耗时操作放在线程里。

​​​​​​​adj调整时机

  只要组件状态发生变化,就会进行adj调整,具体看下面列出的各个函数。

  • Serivce状态变化

        bindServiceLocked

        unbindServiceLocked

        realStartServiceLocked

        sendServiceArgsLocked

        bringDownServiceLocked

        removeConnectionLocked

        serviceDoneExecutingLocked

  • Contentprovider处理

        getContentProviderImpl

        removeContentProvider

        removeContentProviderExternalUnchecked

        publishContentProviders

  • Broadcast处理

        processCurBroadcastLocked

        deliverToRegisteredReceiverLocked

        processNextBroadcast

  • Activity状态变更

        realStartActivityLocked

        resumeTopActivityInnerLocked

        finishSubActivityLocked

        finishVoiceTask

        finishCurrentActivityLocked

        destroyActivityLocked

  • Application调整

        addAppLocked

        attachApplicationLocked

        appDiedLocked

        setSystemProcess

        setProcessForeground

        updateProcessForegroundLocked

        killAllBackgroundProcesses

        killPackageProcessesLocked

        foregroundTokenDied

        trimApplications

        bindBackupAgent

        unbindBackupAgent

​​​​​​​adj调整算法

Adj值调整,主要在AMS:computeOomAdjLocked()方法中实现。算法实现如图8所示。

                     图8 调adj算法

如果进程中有service和contentprovider组件,那么进程的adj还需要随client进程adj而调整。

以bindService启动service时,会有bind flag,某些flag值会影响client进程与service宿主进程。如表2所示:

Bind flag

影响

BIND_WAIVE_PRIORITY

此次binding服务,不影响进程(包含service的进程)调度与内存管理优先级。

BIND_ALLOW_OOM_MANAGEMENT

允许进程(包含service的进程)通过其正常的内存管理。

BIND_NOT_FOREGROUND

不允许此次连接提高service进程的优先级到“foreground调度优先级”,但是service进程的“内存优先级”还是会被提高到与client进程一样(即client没被kill之前,service绝不会被kill),但是为了cpu调度的目的,service进程会被留在后台。这个flag只会影响这种情况:client是前台进程,但是service是后台进程。

BIND_ADJUST_WITH_ACTIVITY

如果从activity发起binding服务,进程的优先级会随着activity的优先级调整而调整。

BIND_ABOVE_CLIENT

认为连接的service比client进程重要,如果oom,会先kill client进程。

BIND_IMPORTANT

对于client来说,这个服务非常重要,如果client是前台进程,那么服务所在进程也应该是前台进程。通常情况下,即使client是前台的,进程也只能提高到visibility的优先级。

BIND_NOT_VISIBLE

即使client是visible,也不考虑进程为visible

           表2 serivce绑定flag

如何降低被kill概率

    进程要做到完全不被kill,基本也不可能。除非进程是系统进程,由init启动,那么就可以继承init的adj(-17),这样即使system_server进程被kill了,也不会被kill,不过可以做到尽可能不被lmk选中。

  • 提供进程优先级

后台操作尽可能用service来实现,而不用线程实现,因为包含service的进程优先级比普通进程高。

重载系统back按键事件,使activity在后台运行,而不是被destory。

依赖于其他优先级高的进程。

  • 修改进程属性

    如phone进程,设置persistent属性。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索 Python 的 vars() 函数

大家好&#xff0c;在软件开发的过程中&#xff0c;调试是一个不可或缺的环节。无论你是在解决 bug&#xff0c;优化代码&#xff0c;还是探索代码的执行流程&#xff0c;都需要一些有效的工具来帮助你更好地理解和调试代码。在 Python 编程中&#xff0c;vars() 函数是一个非常…

国产可视化爬虫助力AI大模型训练:精准爬取汉语词典

大语言模型&#xff0c;可以生成流畅对话的会话聊天机器人、通畅起草文章的内容生成器。在炫酷技术的背后&#xff0c;数据、算力、算法&#xff0c;被视作生成式AI的三个核心要素。由此可见&#xff0c;高质量的训练数据对于AI算法的准确性至关重要。 如何获得高质量的训练数…

【嵌入式硬件】DRV8874电机驱动

目录 1 芯片介绍 1.1 特性简介 1.2 引脚配置 1.3 最佳运行条件 2 详细说明 2.1 PMODE配置控制模式 2.1.1 PH/EN 控制模式 2.1.2 PWM 控制模式 2.1.3 独立半桥控制模式 2.2 电流感测和调节 2.2.1 IPROPI电流感测 2.2.2 IMODE电流调节 3.应用 3.1设计要求 3.2 设计…

数据结构严蔚敏版精简版-绪论

1.基本概念和术语 下列概念和术语将在以后各章节中多次出现&#xff0c;本节先对这些概念和术语赋予确定的含义。 数据(Data)&#xff1a;数据是客观事物的符号表示&#xff0c;是所有能输入到计算机中并被计算机程序处理的符号 的总称。 数据元素(DataElement)&#xff1a;…

JVM运行时数据区 - 程序计数器

运行时数据区 Java虚拟机在执行Java程序的过程中&#xff0c;会把它管理的内存划分成若干个不同的区域&#xff0c;这些区域有各自的用途、创建及销毁时间&#xff0c;有些区域随着虚拟机的启动一直存在&#xff0c;有些区域则随着用户线程的启动和结束而建立和销毁&#xff0…

JAVAEE1

Web前端&#xff1a; 1.建立web开发的息维模式写代码不仅仅是为了实现某个功能&#xff0c;更是学习解决问题的思维方式 2.先使用&#xff0c;再理解&#xff0c;会导致刚开始比较懵&#xff0c;不知其所以然.切忌不可深陷其中&#xff0c; 3.涉及简单的软件工程的设计思想&…

Java Agent利器

一、JavaAgent技术 1.1 什么是JavaAgent JavaAgent是一种特殊的Java程序&#xff0c;是Instrumentation的客户端。它与普通Java程序通过main方法启动不同&#xff0c;JavaAgent并不是一个可以单独启动的程序&#xff0c;它必须依附在一个Java应用程序&#xff08;JVM&#xf…

Spring创建对象的多种方式

一、对象分类 简单对象&#xff1a;使用new Obj()方式创建的对象 复杂对象&#xff1a;无法使用new Obj()方式创建的对象。例如&#xff1a; 1. AOP创建代理对象。ProxyFactoryBean; 2. Mybatis中的SqlSessionFactoryBean; 3. Hibernate中的SessionFactoryBean。二、创建对象方…

Docker学习笔记 - 创建自己的image

目录 基本概念常用命令使用docker compose启动脚本创建自己的image 使用Docker是现在最为流行的软件发布方式&#xff0c; 本系列将阐述Docker的基本概念&#xff0c;常用命令&#xff0c;启动脚本和如何生产自己的docker image。 在我们发布软件时&#xff0c;往往需要把我…

Visual Studio Installer 点击闪退

Visual Studio Installer 点击闪退问题 1. 问题描述2. 错误类型3. 解决方法4. 结果5. 说明6. 参考 1. 问题描述 重装了系统后&#xff08;系统版本&#xff1a;如下图所示&#xff09;&#xff0c;我从官方网站&#xff08;https://visualstudio.microsoft.com/ ) 下载了安装程…

OpenAI 推出ChatGPT Edu,为高校定制版本

近日&#xff0c;OpenAI 宣布推出 ChatGPT Edu&#xff0c;这是一款专为高校打造的 ChatGPT 版本&#xff0c;旨在帮助学生、教师、研究人员和校园运营部门以负责任的方式部署和使用 AI。 ChatGPT Edu 由 GPT-4o 提供支持&#xff0c;具备强大的文本和图像推理能力&#xff0c;…

FS212E 系列PD协议

PD快充协议芯片FS212EL、FS212EH可以智能的识别插入的手机类型&#xff0c;选择最为合适的协议应对手机快充需要。兼容多类USB Type-C协议&#xff0c;包括TypeC协议、TypeC PD2.0、TypeC PD3.0、TypeC PD3.2等协议。集成OPTO输出&#xff0c;通过电阻直驱反馈光耦。FS212E 的调…

hexo init命令报错:Error: EPERM: operation not permitted, mkdir ‘D:\‘

我用的是git bash通过hexo init安装hexo的&#xff0c;但是报错如下&#xff1a; $ hexo init INFO Cloning hexo-starter https://github.com/hexojs/hexo-starter.git fatal: unable to access https://github.com/hexojs/hexo-starter.git/: HTTP/2 stream 1 was not clos…

Firebase Local Emulator Suite详解

文章目录 Firebase Local Emulator Suite 组件安装和使用步骤1. 安装 Firebase CLI2. 初始化 Firebase 项目3. 配置模拟器4. 启动模拟器5. 配置应用程序使用本地模拟器 常见用途 Firebase Local Emulator Suite 是一组本地服务&#xff0c;可以模拟 Firebase 平台的在线服务&am…

以sqlilabs靶场为例,讲解SQL注入攻击原理【25-31关】

【Less-25】 首先分析源码 发现把 SQL语句中的 or、and 替换成了空格&#xff0c;这就导致无法使用之前的sql注入方式。 解决方案&#xff1a;用 && 代替 and &#xff0c; 用 || 代替 or &#xff0c; 而且&在url中有特殊含义&#xff0c;如果直接使用会有问题&a…

如何在镜像中安装固定版本的node和npm

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、使用 Dockerfile 创建自定义镜像二、如何安装固定版本的node及npm总结 前言 最近在做前端工程化相关的内容&#xff0c;需要在一个镜像内安装固定版本的 N…

redis 高可用及哨兵模式 @by_TWJ

目录 1. 高可用2. redis 哨兵模式3. 图文的方式让我们读懂这几个算法3.1. Raft算法 - 图文3.2. Paxos算法 - 图文3.3. 区别&#xff1a; 1. 高可用 在 Redis 中&#xff0c;实现 高可用 的技术主要包括 持久化、复制、哨兵 和 集群&#xff0c;下面简单说明它们的作用&#xf…

Linux共享内存创建和删除

最近项目中使用到了共享内存记录下 创建共享内存: 删除共享内存: 代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <u…

计算机视觉与模式识别实验1-3 图像滤波

文章目录 &#x1f9e1;&#x1f9e1;实验流程&#x1f9e1;&#x1f9e1;1. 对图像加入椒盐噪声&#xff0c;并用均值滤波进行过滤2.对图像加入高斯噪声&#xff0c;并用高斯滤波进行过滤3.对图像加入任意噪声&#xff0c;并用中值滤波进行过滤4.读入一张灰度图像&#xff0c;…

【前端开发--css学习笔记】CSS超详细的学习笔记。前端开发css学习笔记(非常详细,适合小白入门)

二&#xff0c;CSS学习笔记 1&#xff0c;CSS语法 1-1 CSS 实例 CSS声明总是以分号 ; 结束&#xff0c;声明总以大括号 {} 括起来: <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>菜鸟教程(runoob.com)</title…