当前位置 : 主页 > 手机开发 > android >

Android底层内存回收机制介绍

来源:互联网 收集:自由互联 发布时间:2021-06-11
Android底层基于Linux内核开发.随着Android版本不断更新,内存回收机制也在不断变化.本文简要介绍下不同版本下的内存回收原理. Linux OOM机制 OOM(out of memory)是linux中内存管理机制的一种,在系

Android底层基于Linux内核开发.随着Android版本不断更新,内存回收机制也在不断变化.本文简要介绍下不同版本下的内存回收原理.

Linux OOM机制

OOM(out of memory)是linux中内存管理机制的一种,在系统可用内存较少的情况下,内核为了保证系统还能够继续运行下去,会选择杀掉一些进程释放掉一些内存.通常oom_killer的触发流程是: 进程A想要分配物理内存->粗发缺页异常-> 内核去分配物理内存-> 物理内存不足-> OOM. 当OOM发生时,可以有两种选择:

  • kernelpanic(死机)
  • 启动oom_killer,遍历当前所有进程,根据进程的内存使用情况进行打分,然后从中选择一个分数最高进程杀掉,从而回收内存

主要处理流程

调用oom_killer前,系统会对oom_control做一个填充:

pagefault_out_of_memory(void)
{
	struct oom_control oc = {
		.zonelist = NULL,
		.nodemask = NULL,
		.memcg = NULL,
		.gfp_mask = 0,
		.order = 0,
	};
...
	out_of_memory(&oc);
}

oom_killer的处理主要集中在mm/oom_kill.c

核心函数为out_of_memory

bool out_of_memory(struct oom_control *oc)
{
	unsigned long freed = 0;
	enum oom_constraint constraint = CONSTRAINT_NONE;

	if (oom_killer_disabled)
		return false;

	if (!is_memcg_oom(oc)) {
		blocking_notifier_call_chain(&oom_notify_list, 0, &freed);
		if (freed > 0)
			/* Got some memory back in the last second. */
			return true;
	}

	/*
	 * If current has a pending SIGKILL or is exiting, then automatically
	 * select it.  The goal is to allow it to allocate so that it may
	 * quickly exit and free its memory.
	 */
	if (task_will_free_mem(current)) {
		mark_oom_victim(current);
		wake_oom_reaper(current);
		return true;
	}

	/*
	 * The OOM killer does not compensate for IO-less reclaim.
	 * pagefault_out_of_memory lost its gfp context so we have to
	 * make sure exclude 0 mask - all other users should have at least
	 * ___GFP_DIRECT_RECLAIM to get here.
	 */
	if (oc->gfp_mask && !(oc->gfp_mask & __GFP_FS))
		return true;

	/*
	 * Check if there were limitations on the allocation (only relevant for
	 * NUMA and memcg) that may require different handling.
	 */
	constraint = constrained_alloc(oc);
	if (constraint != CONSTRAINT_MEMORY_POLICY)
		oc->nodemask = NULL;
	check_panic_on_oom(oc, constraint);

	if (!is_memcg_oom(oc) && sysctl_oom_kill_allocating_task &&
	    current->mm && !oom_unkillable_task(current, NULL, oc->nodemask) &&
	    current->signal->oom_score_adj != OOM_SCORE_ADJ_MIN) {
		get_task_struct(current);
		oc->chosen = current;
		oom_kill_process(oc, "Out of memory (oom_kill_allocating_task)");
		return true;
	}

	select_bad_process(oc);
	/* Found nothing?!?! Either we hang forever, or we panic. */
	if (!oc->chosen && !is_sysrq_oom(oc) && !is_memcg_oom(oc)) {
		dump_header(oc, NULL);
		panic("Out of memory and no killable processes...\n");
	}
	if (oc->chosen && oc->chosen != (void *)-1UL) {
		oom_kill_process(oc, !is_memcg_oom(oc) ? "Out of memory" :
				 "Memory cgroup out of memory");
		/*
		 * Give the killed process a good chance to exit before trying
		 * to allocate memory again.
		 */
		schedule_timeout_killable(1);
	}
	return !!oc->chosen;
}

处理流程:

  1. 通知系统中注册了oom_nofiy_list的模块释放了内存,如果这些内存从模块中释放了一些内存,那么直接结束omm killer进程,回收失败,则进入下一步omm_killer;
  2. 触发OOM_killer通常是由当前进程进行内存分配引起,而如果当前进程已经挂起了一个SIG_KILL信号,直接选中当前进程,否则进入下一步
  3. check_panic_on_oom检查系统管理员设置,看oom时是直接panic还是进行OOM_killer.
  4. 如果管理原规定,谁引起oom,kill谁,那么就直接杀掉正在尝试分配内存的进程sysctl_oom_kill_allocating_task
  5. 调用select_bad_process选择合适的进程,然后调用OOM_kill_process杀死选中进程.如果没有找到,则触发panic

sysctl_panic_on_oom

该参数在check_panic_on_oom函数中引用,当参数等于0时,启动OOM killer.当参数等于2时,如果不是sysrq进程的话,强制进入kernel panic.等于其他值时,要分具体情况,对于某些情况可以panic,有些情况启动oom killer.

kernel代码中,enum oom_constraint就是一个进一步描述oom状态的参数.定义如下:

enum oom_constraint {
	CONSTRAINT_NONE,
	CONSTRAINT_CPUSET,
	CONSTRAINT_MEMORY_POLICY,
	CONSTRAINT_MEMCG,
};

对于UMA而言,oom_constrain永远都是CONSTRAINT_NONE,表示系统并没有什么约束就出现了oom.

在NUMA下,有可能附加了其他的约束导致了系统遇到OOM状态,实际上,系统中还有充足的内存.这些约束包括:

  • OCNSTRAINT_CPUSET

    cpusets是kernel中的一种机制,通过该机制可以把一组cpu和memory node资源分配给特定的一组进程.这时候如果出现OOM,仅仅说明该进程能分配memory的那个node出现状况了,整个系统有很多的memory node,其他的node可能有充足的memory资源.

  • CONSTRAINT_MEMORY_POLICY

    memory policy是NUMA系统中如何控制分配各个memory node资源的策略模块。用户空间程序(NUMA-aware的程序)可以通过memory policy的API,针对整个系统、针对一个特定的进程,针对一个特定进程的特定的VMA来制定策略。产生了OOM也有可能是因为附加了memory policy的约束导致的,在这种情况下,如果导致整个系统panic似乎有点不太合适。

  • CONSTRAINT_MEMCG

    MEMCG就是memory control group,Cgroup中的memory子系统就是控制系统memory资源分配的控制器,通俗的将就是把一组进程的内存使用限定在一个范围内。当这一组的内存使用超过上限就会OOM,在这种情况下的OOM就是CONSTRAINT_MEMCG类型的OOM。

select_bad_process

该函数从系统中选择一个合适被杀死的进程,对系统关键进程不能杀死,其他则通过 oom_badness 进行打分,分数最高者被选中:

graph TD; select_bad_process-->oom_evaluate_task oom_evaluate_task-->oom_badness

oom_badness

/**
 * oom_badness - heuristic function to determine which candidate task to kill
 * @p: task struct of which task we should calculate
 * @totalpages: total present RAM allowed for page allocation
 *
 * The heuristic for determining which task to kill is made to be as simple and
 * predictable as possible.  The goal is to return the highest value for the
 * task consuming the most memory to avoid subsequent oom failures.
 */
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,
			  const nodemask_t *nodemask, unsigned long totalpages)
{
	long points;
	long adj;
...
    adj = (long)p->signal->oom_score_adj;
	if (adj == OOM_SCORE_ADJ_MIN ||
			test_bit(MMF_OOM_SKIP, &p->mm->flags) ||
			in_vfork(p)) {
		task_unlock(p);
		return 0;
	}
    /*
	 * The baseline for the badness score is the proportion of RAM that each
	 * task‘s rss, pagetable and swap space use.
	 */
	points = get_mm_rss(p->mm) + get_mm_counter(p->mm, MM_SWAPENTS) +
		atomic_long_read(&p->mm->nr_ptes) + mm_nr_pmds(p->mm);
	task_unlock(p);

	/*
	 * Root processes get 3% bonus, just like the __vm_enough_memory()
	 * implementation used by LSMs.
	 */
	if (has_capability_noaudit(p, CAP_SYS_ADMIN))
		points -= (points * 3) / 100;

	/* Normalize to oom_score_adj units */
	adj *= totalpages / 1000;
	points += adj;

	/*
	 * Never return 0 for an eligible task regardless of the root bonus and
	 * oom_score_adj (oom_score_adj can‘t be OOM_SCORE_ADJ_MIN here).
	 */
	return points > 0 ? points : 1;
}

详细介绍下oom_badness主要工作:

  • 代码17行

    对某一个task进行打分(oom_score)主要由两部分组成:

    • 系统打分,主要是根据该task的内存使用情况

    • 用户打分,即oom_score_adj

    该task的实际得分需要综合两方面的打分.如果用户将task的oom_socre_adj设置成OOM_SCORE_ADJ_MIN(-1000)的话,实际上就是禁止了oom killer杀死该进程.

    返回0,即通知oom killer,该进程是"good process". 后面可以看到实际计算分数时最低分是1分.

  • 代码27行

    系统打分就是看物理内存消耗量,主要是三部分:RSS,swap fille或者swap device上占用的内存情况,页表占用的内存情况.

  • 代码35行

    root进程有3%的内存室友特权,因此这里要减去那些内存使用量

  • 代码39行

    用户可以调整oom_score,具体操作方法如下:

    Android system

    oom_score_adj的取值范围是-1000~1000,0表示用户不调整oom_score,负值表示要在实际打分值上减去一个折扣,正值表示要惩罚该task,也就是增加该进程的oom_score。在实际操作中,需要根据本次内存分配时候可分配内存来计算(如果没有内存分配约束,那么就是系统中的所有可用内存,如果系统支持cpuset,那么这里的可分配内存就是该cpuset的实际额度值)。oom_badness函数有一个传入参数totalpages,该参数就是当时的可分配的内存上限值。实际的分数值(points)要根据oom_score_adj进行调整,例如如果oom_score_adj设定-500,那么表示实际分数要打五折(基数是totalpages),也就是说该任务实际使用的内存要减去可分配的内存上限值的一半。

Android kernel LMK机制

在Android中,及时用户退出当前应用程序后,应用程序还是会存在于系统当中,这是为了方便程序的再次启动。但是这样的话,随着打开的程序的数量的增加,系统的内存就会不足,从而需要杀掉一些进程来释放内存空间。至于是否需要杀进程以及杀什么进程,这个就是由Android的内部机制LowMemoryKiller机制来进行的。

Andorid的Low Memory Killer是在标准的linux lernel的OOM基础上修改而来的一种内存管理机制。当系统内存不足时,杀死不必要的进程释放其内存。不必要的进程的选择根据有2个:oom_adj和占用的内存的大小。oom_adj代表进程的优先级,数值越高,优先级月低,越容易被杀死;对应每个oom_adj都可以有一个空闲进程的阀值。Android Kernel每隔一段时间会检测当前空闲内存是否低于某个阀值。假如是,则杀死oom_adj最大的不必要的进程,直到内存恢复低于阀值的状态。

对比下oom

名称 触发条件 OOM 某进程申请内存时发生页面错误,没有足够剩余的内存可供分配 Lowmemorykiller 定期扫描系统内存压力情况,低于某个阈值后,就正式启动回收.

LMK初始化

初始化主要为kobject注册和各通知链的注册

static int __init lowmem_init(void)
{
	rc = kobject_init_and_add(lowmem_notify_kobj, &lowmem_notify_kobj_type,
				  mm_kobj, "lowmemkiller");
	register_shrinker(&lowmem_shrinker);
#ifdef CONFIG_OOM_NOTIFIER
	register_oom_notifier(&android_oom_notifier);
#endif
#ifdef CONFIG_E_SHOW_MEM
	register_e_show_mem_notifier(&tasks_e_show_mem_notifier);
#endif
	vmpressure_notifier_register(&lmk_vmpr_nb);
	nl_sk = netlink_kernel_create(&init_net, LMK_NETLINK_PROTO, &cfg);
}

其中lowmen_shrinker定义如下:

static struct shrinker lowmem_shrinker = {
	.scan_objects = lowmem_scan,
	.count_objects = lowmem_count,
	.seeks = DEFAULT_SEEKS * 16,
	.flags = SHRINKER_LMK
};
static short lowmem_adj[6] = {
	0,
	1,
	6,
	12,
};
static int lowmem_minfree[6] = {
	3 * 512,	/* 6MB */
	2 * 1024,	/* 8MB */
	4 * 1024,	/* 16MB */
	16 * 1024,	/* 64MB */
};

lowmem_adj这个数据在系统运行中会有填充.具体数值可在如下节点获取:

/sys/module/lowmemorykiller/parameters/minfree:里面是以”,”分割的一组数,每个数字代表一个内存级别 /sys/module/lowmemorykiller/parameters/adj:对应上面的一组数,每个数组代表一个进程优先级级别

sp9832e_1h10:/sys/module/lowmemorykiller/parameters # cat minfree ; cat adj 18432,23040,27648,32256,55296,80640 0,100,200,300,900,906

代表的意思:两组数一一对应,当手机内存低于80640时,就去杀掉优先级906以及以上级别的进程,当内存低于55296时,就去杀掉优先级900以及以上的进程。

内存回收功能实现主要在lowmem_scan函数中:

static unsigned long lowmem_scan(struct shrinker *s, struct shrink_control *sc)
{
	/* work around for antutu */
	struct task_struct *selected_antutu = NULL;
	int selected_antutu_tasksize = 0;
	short selected_antutu_adj = -1000;
	bool has_antutu_3D = false;

#ifdef CONFIG_LOWMEM_NOTIFY_KOBJ
	lowmem_notif_sc.gfp_mask = sc->gfp_mask;
	if (get_free_ram(&other_free, &other_file_orig, &other_file, sc)) {
		if (mutex_is_locked(&kernfs_mutex))
			msleep(1);

		if (!mutex_is_locked(&kernfs_mutex))
			lowmem_notify_killzone_approach();
		else
			lowmem_print(1, "skip as kernfs_mutex is locked.");
	}
#else
	get_current_ram(&other_free, &other_file_orig, &other_file, sc);
#endif

	for (i = 0; i < array_size; i++) {
		minfree = lowmem_minfree[i];
		if (other_free < minfree && other_file < minfree) {
			min_score_adj = lowmem_adj[i];
			break;
		}
	}

	ret = adjust_minadj(&min_score_adj, &pressure);

	selected_oom_score_adj = min_score_adj;

	for_each_process(tsk) {
		struct task_struct *p;
		short oom_score_adj;

		if (tsk->flags & PF_KTHREAD)
			continue;

		if (time_before_eq(jiffies, lowmem_deathpending_timeout)) {
			if (test_task_flag(tsk, TIF_MEMDIE)) {
				rcu_read_unlock();
				mutex_unlock(&scan_mutex);
				return 0;
			}
		}

		/* workaround for antutu */
		if (strstr("com.antutu.benchmark.full", p->comm))
			has_antutu_3D = true;

		oom_score_adj = p->signal->oom_score_adj;
		if (oom_score_adj < min_score_adj) {
			task_unlock(p);
			continue;
		}
		tasksize = get_mm_rss(p->mm);
		task_unlock(p);
		if (tasksize <= 0)
			continue;
		if (selected) {
			if (oom_score_adj < selected_oom_score_adj)
				continue;
			if (oom_score_adj == selected_oom_score_adj &&
			    tasksize <= selected_tasksize)
				continue;
		}
		/* workaround for antutu */
		if (!selected_antutu &&
		    strstr("com.antutu.ABenchMark", p->comm)) {
			selected_antutu = p;
			selected_antutu_tasksize = tasksize;
			selected_antutu_adj = oom_score_adj;
			continue;
		}
		selected = p;
		selected_tasksize = tasksize;
		selected_oom_score_adj = oom_score_adj;
		lowmem_print(2, "select ‘%s‘ (%d), adj %hd, size %d, to kill\n",
			     p->comm, p->pid, oom_score_adj, tasksize);
	}
	/* workaround for antutu:
	 * if 3D task is not exist, check if the antutu task is more suited
	 * to be killed
	 */
	if (selected && selected_antutu && !has_antutu_3D) {
		if (selected_antutu_adj > selected_oom_score_adj ||
		    (selected_antutu_adj == selected_oom_score_adj &&
		    selected_antutu_tasksize > selected_tasksize)) {
			selected = selected_antutu;
			selected_tasksize = selected_antutu_tasksize;
			selected_oom_score_adj = selected_antutu_adj;
		}
	}
	if (selected) {
		long cache_size = other_file * (long)(PAGE_SIZE / 1024);
		long cache_size_orig = other_file_orig * (long)(PAGE_SIZE / 1024);
		long cache_limit = minfree * (long)(PAGE_SIZE / 1024);
		long free = other_free * (long)(PAGE_SIZE / 1024);

		if (test_task_flag(selected, TIF_MEMDIE) &&
		    (test_task_state(selected, TASK_UNINTERRUPTIBLE))) {
			lowmem_print(2, "‘%s‘ (%d) is already killed\n",
				     selected->comm,
				     selected->pid);
			rcu_read_unlock();
			mutex_unlock(&scan_mutex);
			return 0;
		}

		task_lock(selected);
		/* add for lmfs */
		selected_process_uid = from_kuid(&init_user_ns,
						 selected->cred->uid);
		selected_process_pid = selected->pid;
		selected_process_adj = selected_oom_score_adj;

		send_sig(SIGKILL, selected, 0);
		/*
		 * FIXME: lowmemorykiller shouldn‘t abuse global OOM killer
		 * infrastructure. There is no real reason why the selected
		 * task should have access to the memory reserves.
		 */
		if (selected->mm)
			mark_oom_victim(selected);
		task_unlock(selected);
		trace_lowmemory_kill(selected, cache_size, cache_limit, free);
		si_swapinfo(&si);

		lowmem_deathpending_timeout = jiffies + HZ;
		rem += selected_tasksize;
		trace_almk_shrink(selected_tasksize, ret,
			other_free, other_file, selected_oom_score_adj);
	} else {
	    trace_almk_shrink(1, ret, other_free, other_file, 0);
	}


	if (selected) {
		send_killing_app_info_to_user(selected_process_uid,
					      selected_process_pid,
					      selected_process_adj);
	return rem;
}

Android LMKD 机制

下面介绍Android 9 中新增的用户空间 lowmemorykiller 守护进程 (lmkd) 功能及其配置方法

代码位置 platform/system/core/lmkd/

过去,Android 使用内核中的 lowmemorykiller 驱动程序来缓解内存压力(通过终止非必需进程)。此机制非常严格,具体取决于硬编码值。此外,从内核版本 4.12 开始,lowmemorykiller 驱动程序会从上游内核中排除。

用户空间?lmkd?进程可实现相同的功能,但它是通过现有的内核机制来检测和估测内存压力。该进程使用内核生成的 vmpressure 事件来获取关于内存压力级别的通知。此外,它还可以使用内存 cgroup 功能来限制分配给相应进程的内存资源(根据每个进程的重要性)

ProcessList中定义有进程的优先级,越重要的进程的优先级越低,前台APP的优先级为0,系统APP的优先级一般都是负值,所以一般进程管理以及杀进程都是针对与上层的APP来说的,而这些进程的优先级调整都在AMS里面,AMS根据进程中的组件的状态去不断的计算每个进程的优先级,计算之后,会及时更新到对应进程的文件节点中,而这个对文件节点的更新并不是它完成的,而是lmkd,他们之间通过socket通信。

lmkd在手机中是一个常驻进程,用来处理上层ActivityManager在进行updateOomAdj之后,通过socket与lmkd进行通信,更新进程的优先级,如果必要则杀掉进程释放内存。lmkd是在init进程启动的时候启动的,在lmkd中有定义lmkd.rc:

service lmkd /system/bin/lmkd
    class core
    group root readproc
    critical
    socket lmkd seqpacket 0660 system system
    socket lmfs stream 0660 root system
    socket vmpressure stream 0666 root system
    writepid /dev/cpuset/system-background/tasks

配置内核以支持LMKD

从 Android 9 开始,用户空间?lmkd?会在未检测到内核 lowmemorykiller 驱动程序时激活。请注意,用户空间?lmkd?要求内核支持内存 cgroup。因此,要改用用户空间?lmkd,您应使用以下配置设置编译内核:

CONFIG_ANDROID_LOW_MEMORY_KILLER=n
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y

LMKD 终止策略

lmkd 支持基于以下各项的新终止策略:

  • vmpressure event
  • severity
  • 其他提示(如交换利用率swap utilization
  • 旧模式(在该模式下,lmkd 会像内核 lowmemorykiller 驱动程序一样做出终止决策)。

内存不足的设备和高性能设备的新终止策略有所不同。对于内存不足的设备,一般情况下,系统会选择承受较大的内存压力;对于高性能设备,如果存在内存压力,则属于异常情况,应及时修复,以免影响整体性能。ro.config.low_ram 属性允许选择其中一种模式。有关如何设置此属性的说明,请参阅低内存配置。

在旧模式下,lmkd 终止决策是基于可用内存和文件缓存阈值做出的。您可以将 ro.lmk.use_minfree_levels 属性设置为 true,从而启用此模式。

为特定设备配置LMKD

属性 使用情况 默认值 ro.config.low_ram 在内存不足的设备和高性能设备之间进行选择。 false ro.lmk.use_minfree_levels 使用可用内存和文件缓存阈值来决定何时终止。此模式与内核 lowmemorykiller 驱动程序之前的工作原理相同。 false ro.lmk.low 可在低 vmpressure 级别下被终止的进程的最低 oom_adj 得分。 1001 (已停用) ro.lmk.medium 可在中等 vmpressure 级别下被终止的进程的最低 oom_adj 得分。 800 (已缓存或非必需服务) ro.lmk.critical 可在临界 vmpressure 级别下被终止的进程的最低 oom_adj 得分。 0 (任何进程) ro.lmk.critical_upgrade 能够升级到临界级别。 false ro.lmk.upgrade_pressure 由于系统交换次数过多,将在该级别升级 vmpressure 事件的 mem_pressure 上限。 100 (已停用) ro.lmk.downgrade_pressure 由于仍有足够的可用内存,将在该级别忽略 vmpressure 事件的 mem_pressure* 下限。 100 (已停用) ro.lmk.kill_heaviest_task 终止符合条件的最重要任务(最佳决策)与任何符合条件的任务(快速决策)。 true ro.lmk.kill_timeout_ms 从某次终止后到其他终止完成之前的持续时间(以毫秒为单位)。 0 (已停用) ro.lmk.debug 启用 lmkd 调试日志。 false

*注意:*mem_pressure = 内存使用量/RAM_and_swap 使用量(以百分比的形式表示)

low level 正常回收;medium level就开始swaping;critical就是快没内存了

具体实现

AMS与LMKD通信command

主要分为五种,每种command代表一种数据控制方式,在ProcessList及lmkd中都有定义:

LMK_TARGET:更新/sys/module/lowmemorykiller/parameters/中的minfree及adj
LMK_PROCPRIO:更新指定进程的优先级,也就是oom_socre_adj
LMK_PROCREMOVE:移除进程
Purge:清理所有注册的进程
LMK_GETKILLCNT:获取kill的进程数

数据结构

用来描述handle events的数据结构

struct event_handler_info {
    int data;
    void (*handler)(int data, uint32_t events);
};

定义描述vmpressure event的结构体变量: vmpressure_hinfo

用来描述socket events的数据结构

struct sock_event_handler_info {
    int sock;
    struct event_handler_info handler_info;
};

用来定义了两个数据: ctrl_sock data_sock

table,类似hashtable,不过计算index的方式不是hash,而是oom_score_adj经过转换后直接作为index.数组的每个元素都是双向循环链表进程的优先级作为数组的index.即以进程的优先级为index,从-1000到+1000 + 1大小的数组,根据优先级,同优先级的进程index相同.每个元素是一个双向链表,这个链表上的所有proc的优先级都相同.这样根据优先级杀进程的时候就会非常方便,要杀指定优先级的进程可以根据优先级获取到一个进程链表,逐个去杀。

static struct adjslot_list procadjslot_list[ADJTOSLOT(OOM_SCORE_ADJ_MAX) + 1];

时序图

分享图片

lmkd工作流程

入口main
int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };
    /*  2019/04/22 11:15:44 by jinliang
     * 将此进程现在和未来所使用到的内存锁在物理内存中,防止被交换
     */
    if (mlockall(MCL_CURRENT | MCL_FUTURE | MCL_ONFAULT) && (errno != EINVAL)) {
    	ALOGW("mlockall failed %s", strerror(errno));
    }

        /* CAP_NICE required */
        /*  2019/04/22 11:18:12 by jinliang */
        //设置调度策略为FIFO
        if (sched_setscheduler(0, SCHED_FIFO, &param)) {
  	      	ALOGW("set SCHED_FIFO failed %s", strerror(errno));
        }
        if (lmfs_enabled)
            start_lmfs();
        /*  
         * 进入死循环等待fd事件
         */
        mainloop();
    }
init
/*1. 初始化socket监听接口ctrl_sock
 *2. 创建epollfd套接字epollfd
 *3. 填充epoll_event epev
 *  1) epoll监听的事件为EPOLLIN
 *  2) 监听的fd为ctrl_sock.sock
 *  2) 回调函数为 ctrl_sock.handler_info
 *4. 监听事件注册*/
static int init(void) {
    struct epoll_event epev;

/*  2019/04/15 16:39:00 by jinliang */
/*---get _SC_PAGESIZE value---
 * PAGE_SIZE is 4096,so total about 16M?
 */
    page_k = sysconf(_SC_PAGESIZE);
    if (page_k == -1)
        page_k = PAGE_SIZE;
    page_k /= 1024;
/*创建一个epoll句柄*/
    epollfd = epoll_create(MAX_EPOLL_EVENTS);
/*get the lmkd control socket fd to listen the AMS command*/
    ctrl_sock.sock = android_get_control_socket("lmkd");
    /*list the socket command pass by AMS*/
    /* listen():监听来自客户端的tcp socket的连接请求
     * #include<sys/socket.h>
     * int listen(int sockfd, int backlog)
     * 参数sockfd是被listen函数作用的套接字
     * 参数backlog是侦听队列的长度。*/
    ret = listen(ctrl_sock.sock, MAX_DATA_CONN);

    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++;
    if (use_inkernel_interface) {
        ALOGI("Using in-kernel low memory killer interface");
    } else {
    /*设置监听/dev/memcg/memory.pressure_level 和 /dev/memcg/cgroup.event_control*/
        if (!init_mp_common(VMPRESS_LEVEL_LOW) ||
            !init_mp_common(VMPRESS_LEVEL_MEDIUM) ||
            !init_mp_common(VMPRESS_LEVEL_CRITICAL)) {
            ALOGE("Kernel does not support memory pressure events or in-kernel low memory killer");
            return -1;
        }
    }
    return 0;
}
处理socket传递过来的数据

该步主要功能为维护minfree和adj以及process数据链表

在ctrl_connect_handler方法中处理了accept,并开始ctrl_data_handler中读取数据并进行处理:

static void ctrl_command_handler(int dsock_idx) {
    len = ctrl_data_read(dsock_idx, (char *)packet, CTRL_PACKET_MAX_SIZE);
    cmd = lmkd_pack_get_cmd(packet);
    switch(cmd) {
    case LMK_TARGET:
        targets = nargs / 2;
        if (nargs & 0x1 || targets > (int)ARRAY_SIZE(lowmem_adj))
            goto wronglen;
        cmd_target(targets, packet);
        break;
    case LMK_PROCPRIO:
        if (nargs != 3)
            goto wronglen;
        cmd_procprio(packet);
        break;
    case LMK_PROCREMOVE:
        if (nargs != 1)
            goto wronglen;
        cmd_procremove(packet);
        break;
    case LMK_PROCPURGE:
        if (nargs != 0)
            goto wronglen;
        cmd_procpurge();
        break;
    case LMK_GETKILLCNT:
        if (nargs != 2)
            goto wronglen;
        kill_cnt = cmd_getkillcnt(packet);
        len = lmkd_pack_set_getkillcnt_repl(packet, kill_cnt);
        if (ctrl_data_write(dsock_idx, (char *)packet, len) != len)
            return;
        break;
}

在use_inkernel_interface的情况下,做的事情都是很简单的,只是更新一下文件节点。如果不使用kernel interface,就需要lmkd自己维护两个table,在每次更新adj的时候去更新table。 且在初始化的时候也能看到,如果不使用kernel的lowmemorykiller,则需要lmkd自己获取手机内存状态,如果匹配到了minfree中的等级,则需要通过杀掉一些进程释放内存。

杀进程

杀进程主要是通过之前在init_mp_common 中设置的memory.pressure_level监听回调函数实现mp_event_common:

static void mp_event_common(int data, uint32_t events __unused) {
    /*
     * when only LMK enabled, we can pass vmpressure & swap-pressure to
     * PerformanceManagerService because-of enable_adaptive_lmk,
     * PerformanceManagerService then force-stop apps from the LRU list
     * according to current vmpressure & swap-pressure;
     *
     * when only MEMCG enabled, the vmpressure & swap-pressure should also
     * be passed to PerformanceManagerService
     */
    if (!use_inkernel_interface) {
        enum vmpressure_level level = (enum vmpressure_level)data;
        int vmpressure_value = 0;
        switch (level) {
            case VMPRESS_LEVEL_LOW:
                vmpressure_value = 70;
                break;
            case VMPRESS_LEVEL_MEDIUM:
                vmpressure_value = 80;
                break;
            case VMPRESS_LEVEL_CRITICAL:
                vmpressure_value = 90;
                break;
            default:
                break;
        }
        handle_vmpressure(vmpressure_value);
    }
}

经过层层调用 mp_event_common->handle_vmpressure->find_and_kill_process_adj->find_and_kill_process_adj_locked->kill_one_process


参考文档:

  1. https://source.android.com/devices/tech/perf/lmkd
  2. https://www.sohu.com/a/238012686_467784
  3. http://gityuan.com/2016/09/17/android-lowmemorykiller/
  4. https://blog.csdn.net/u011733869/article/details/78820240

??

上一篇:View 层
下一篇:android 日志方案研究
网友评论