lowmemorykiller----PSI 内核 源码分析

803 阅读15分钟

目录: PSI内核分析.png

本篇文章讲述内容:

lowmemorykiller整体架构分三层:

  • system_server 进程的 AMS 和 lmkd 交互部分----本篇文章不讲
  • native 层的 lmkd 进程----简单讲
  • Linux内核层的 PSI----重点讲

lmkd 和 内核PSI 交互过程:

  • lowmemorykiller 到 epoll 到 PSI 的过程
  • lowmemorykiller 和 PSI 之间的交互过程
  • 顺便以 PSI 为例,解析了 epoll 核心原理
    • epoll 是 Android进程间通讯的一个核心底层之一,另一个就是binder
  • VFS太过庞大,忽略。

lmkd和内核PSI交互过程概述

三个 wait 任务

  • write 流程 启动内核线程 psimon
    • psimon 线程启动后就直接 wait ,把线程挂在了 psi_group 结构体的成员变量 poll_wait 等待队列上
    • 等待队列【1】 psi_trigger.group->poll_wait
      • 如果从 file 对象开始访问的话:
        • psi_trigger *t = file->private_data->private
        • wait_queue_head_t wq = t->group->poll_wait
      • 或者直接使用 psi_system->poll_wait 访问
  • epoll_ctl系统调用添加回调函数 ep_ptable_queue_proc
    • ep_ptable_queue_proc把回调函数 ep_poll_callback 添加到 psi触发器的等待队列 psi_trigger.event_wait
    • 等待队列【2】 psi_trigger.event_wait 。挂到这里的 ep_poll_callback 是个回调类型的 wait_queue_entry,仅仅是个回调,唤醒后不做线程调度。
  • epoll_wait系统调用,使 lmkd 主线程 wait 到 eventpoll.wq 队列上
    • 等待队列【3】 eventpoll.wq

对应的三个唤醒

  • PSI 插桩事件启动定时任务
    • 定时任务 poll_timer_fn 唤醒 psi_group->poll_wait 队列上的任务
    • 唤醒 psimon线程
  • psimon线程 唤醒后,检测到内存压力事件,唤醒 psi_trigger.event_wait上的等待队列
    • 回调队列中的 ep_poll_callback 函数
  • ep_poll_callback 唤醒等待队列 eventpoll.wq 上等待的 lmkd 主线程
    • lmkd 主线程,在内核执行 epoll_wait 处的代码被唤醒,开始处理事件
      • 事件处理完后,把事件(没返回啥数据,就仅仅是事件)发回用户空间的 lmkd
      • lmkd 开始调用 PSI 事件的回调函数
        • 之后开始检查当前设备的内存情况,内存不足时,开始查杀进程

一、用户层 lowmemorykiller

lowmemorykiller 同 /proc/pressure/memory 文件交互过程

// system/memory/lmkd/lmkd.cpp
main
    |-->update_props();//获取系统属性中读取lmkd的各种配置信息
    |-->init();
			|-->init_monitors();
					|-->init_psi_monitors();
							|-->init_mp_psi(VMPRESS_LEVEL_LOW, use_new_strategy)
 							|-->init_mp_psi(VMPRESS_LEVEL_MEDIUM, use_new_strategy)
							|-->init_mp_psi(VMPRESS_LEVEL_CRITICAL, use_new_strategy)
                              |-->fd = init_psi_monitor(enum psi_stall_type stall_type, int threshold_us, int window_us);
									//PSI_PATH_MEMORY=/proc/pressure/memory
									// open 过程见:“open 系统调用 到 PSI 内核过程” 章节
									|-->fd = open(PSI_PATH_MEMORY, O_WRONLY | O_CLOEXEC);
									// write 过程见:“open 系统调用 到 PSI 内核过程” 章节
									|-->write(fd, buf, strlen(buf) + 1)
                              |	  vmpressure_hinfo[level].handler = use_new_strategy ? mp_event_psi : mp_event_common;
                              |	  vmpressure_hinfo[level].data = level;
                              |	  register_psi_monitor(epollfd, fd, &vmpressure_hinfo[level])
                              |-->int register_psi_monitor(int epollfd, int fd, void* data)
                                  |   int res;
								  |	  struct epoll_event epev;
								  //  这个 EPOLLPRI 在PSI内核中如果产生了 内存stall 事件,会把事件类型赋值为 EPOLLPRI,用于epoll_wait过滤事件
								  |   epev.events = EPOLLPRI;
								  // epev.data.ptr 指针指向 vmpressure_hinfo 的数组成员
								  // vmpressure_hinfo 是 event_handler_info 结构体数组
								  // event_handler_info.handler 存储了事件回调函数
								  |   epev.data.ptr = data;
                                  |-->epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev)
    |-->mainloop();
		|	struct event_handler_info* handler_info;
		|	struct epoll_event *evt;
		|-->while (1)//开启while无线循环
            |	int nevents;
			//	阻塞线程,等待监听的fd事件上报
            |-->nevents = epoll_wait(epollfd, events, maxevents, -1);
			|	evt = &events[n]
			|	handler_info = (struct event_handler_info*)evt->data.ptr;
			|	call_handler(handler_info, &poll_params, evt->events);
			|-->void call_handler(struct event_handler_info* handler_info, struct polling_params *poll_params, uint32_t events)
				//	回调注册的事件处理函数
				|-->handler_info->handler(handler_info->data, events, poll_params);
					// 之后的内容就是读取当前设备内存状态,然后查杀进程

二、open 系统调用 到 PSI 内核过程

  • 核心 就是关联了一个 seq_file 文件,赋值了这个文件的操作函数

    • 1、把打开的 file 的私有数据关联一个 seq_file 对象: file.private_data = seq_file

    • 2、给这个seq_file对象赋值操作函数op:seq_file->op = seq_operations

    • 3、最关键的是这个文件操作指针 op->show 赋值为 psi_memory_show

  • 在 proc_open 时,关联了 seq_file->show 的函数为 psi_memory_show 。因此,调用 seq_read 时,数据内容的输出是通过 psi_memory_show 输出的。

open(PSI_PATH_MEMORY, O_WRONLY | O_CLOEXEC)
    |-->vfs.open//linux内核 vfs open系统调用过程简略
    	|-->proc_open // common/kernel/sched/psi.c
    			|-->psi_memory_open
    				|-->psi_open(struct file *file, int (*psi_show)(struct seq_file *, void *))//传入seq_file->op->show 的函数指针
        				|-->single_open // common/fs/seq_file.c   安装 seq_file 文件的操作函数,用于读/proc/pressure/节点数据

psi_memory_show 函数又调用了 psi_show,

我们在命令行里执行 cat /proc/pressure/memory 返回的内容,就是通过 psi_show 函数生成的:

penrose:/ # cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.00 total=79297682
full avg10=0.00 avg60=0.00 avg300=0.00 total=19956021

三、write 流程

psi_write 过程主要是把 用户层lowmemorykiller传入的psi的配置写入到内核psi内存中

psi_trigger_create 函数会提取 lowmemorykiller 传过来的字符串 buf 中的数据,并把相关数据存储到触发器(psi_trigger) 结构体中.

// buf 中的字符串格式:类型+空格+压力时间(单位us)+空格+窗口时间(单位us) 
//如果是some,则类似这种:some 70000 1000000
//如果是full,则类似这种:full 70000 1000000
write(fd, buf, strlen(buf) + 1)
|-->vfs.write//linux内核 vfs open系统调用过程简略
 |-->proc_write
  |-->psi_memory_write
	|-->psi_write
    	 | struct seq_file *seq;
    	 | psi_trigger *new;
		 | seq = file->private_data;
    	 | new = psi_trigger_create(&psi_system, buf, res);//buf为 lowmemorykiller 传过来的字符串
		 |-->struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf, enum psi_res res)	
			 | struct psi_trigger *t;
			 | enum psi_states state;
			 // 1、提取 lowmemorykiller 传过来的字符串 buf 中的数据:state,threshold_us,window_us
			 // state 的值是 psi_states 枚举类型:
			 // PSI_IO_SOME,PSI_IO_FULL,PSI_MEM_SOME,PSI_MEM_FULL,PSI_CPU_SOME,PSI_CPU_FULL,PSI_IRQ_FULL,PSI_NONIDLE
			 // res 的值为 PSI_MEM,buf中的类型为some或者full,解析得到 state 为 2 或者 3
			 // 2、对新创建的触发器结构体 t 进行赋值:
			 | t->state = state;// lowmemorykiller传过来的要监听的状态赋值给 psi_trigger.state
			 | t->group = group; // group = psi_system; psi_system 是个全局变量。
			 | t->threshold = threshold_us * NSEC_PER_USEC; //threshold_us 转换为ns赋值给 t->threshold
			 | t->win.size = window_us * NSEC_PER_USEC;//window_us 转换为ns赋值给 t->win.size
			 | init_waitqueue_head(&t->event_wait);////初始化等待队列
    		 | task = kthread_create(psi_poll_worker, group, "psimon");//线程执行函数体为 psi_poll_worker
			 // &group->poll_wakeup==1 是 task wait后的唤醒条件。这里初始化poll_wakeup值为 0
             | atomic_set(&group->poll_wakeup, 0);
			 | wake_up_process(task);// 启动创建的线程
			 // UPDATES_PER_WINDOW 的值为10,表示每个窗口期间更新10次,除以这个值得到周期
			 // poll_min_period 初值为 U32_MAX,所以这里等于 lowmemorykiller 设置的 window/10
			 // group 上不止一个触发器psi_trigger, group->poll_min_period 使用监听的所有事件里周期中最小的那个
			 | 	group->poll_min_period = min(group->poll_min_period, div_u64(t->win.size, UPDATES_PER_WINDOW));
			 // 把 state 转换位 bit 位,存储到 poll_states 变量中
			 // poll_states 记录PSI需要监听[PSI_IO_SOME ~ PSI_NONIDLE]这些事件中的哪些事件
			 | group->poll_states |= (1 << t->state);
         // 把 触发器赋存储到 seq_file->private 属性中。
         | smp_store_release(&seq->private, new);

psimon 线程

psi_poll_worker
// write 流程中调用 wake_up_process 启动线程psimon,在新内核线程中执行函数 psi_poll_worker
psi_poll_worker(void *data)// data 为psi_group类型全局变量:psi_system
 | struct psi_group *group = (struct psi_group *)data;
 |-->while (true) //开启死循环
     // 线程挂到 group->poll_wait 等待队列上
     // &group->poll_wakeup == 1 时,唤醒线程,或者线程被设置为需要stop的标记
     // 线程首次启动时,由于 poll_wakeup值被初始化为 0,所以线程在这里会 wait
     |-->wait_event_interruptible(group->poll_wait, atomic_cmpxchg(&group->poll_wakeup, 1, 0) || kthread_should_stop());
	 // 线程唤醒后执行 psi_poll_work 函数
	 |-->psi_poll_work(group);

四、epoll_ctl流程

  • epoll 层:把 lowmemorykiller 传来的 事件和 文件对象 保存在 epitem 结构体中
  • epoll 层:安装文件系统poll函数的回调函数 ep_ptable_queue_proc
  • PSI层:poll_wait 函数中回调 ep_ptable_queue_proc ,并把触发器 psi_trigger 中的等待队列 psi_trigger.event_wait 作为参数传入
  • 再回到epoll层:

epoll_ctl 系统调用 到 psi_fop_poll

epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &epev);

//参数 event:
struct epoll_event epev;
epev.events = EPOLLPRI;
event_handler_info[level].handler = mp_event_common;
event_handler_info[level].data = level;
epev.data.ptr = event_handler_info[x];
//参数 fd 为 /proc/pressure/memory 的文件描述符
//参数 op 为 EPOLL_CTL_ADD,用于添加新的需要监听的文件
//参数 epfd 为 epoll_create()的返回值,epoll的fd

epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
 // 通过系统调用进入内核
 |-->SYSCALL_DEFINE(epoll_ctl, int, epfd, int, op, int, fd, struct epoll_event __user *, event)
     //	把用户空间的 event 数据复制到内核空间 epds 变量中 
     |	copy_from_user(&epds, event, sizeof(struct epoll_event)))
     |	do_epoll_ctl(epfd, op, fd, &epds, false);
	 |-->do_epoll_ctl(int epfd, int op, int fd, struct epoll_event *epds, bool nonblock)
         |	struct fd f, tf;
         |	struct eventpoll *ep;
		 |	tf = fdget(fd);  /*获取要监听的文件fd 对应的文件对象, tf: target file 的意思*/
		 |	ep = f.file->private_data;
         |	ep_insert(ep, epds, tf.file, fd, full_check);
		 |-->int ep_insert(struct eventpoll *ep, struct epoll_event *event, struct file *tfile, int fd, int full_check)
             |	struct epitem *epi;
             |	struct ep_pqueue epq;
			 |	epi = kmem_cache_alloc(epi_cache, GFP_KERNEL);//分配 epi 内存
			 // &epi->ffd->file = tfile; &epi->ffd->fd = fd;
             |	ep_set_ffd(&epi->ffd, tfile, fd);// 把需要监听的文件的file对象和fd保存在 epitem 结构体的 ffd 成员变量中
			 |	epi->event = *event; //把
             // 指定回调函数为 ep_ptable_queue_proc
             // &epq.pt->_qproc = ep_ptable_queue_proc;  &epq.pt->_key   = ~0UL;
             |	init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
			 |-->init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
				|	pt->_qproc = qproc;
				|	pt->_key   = ~0UL;/* all events enabled */
             |	revents = ep_item_poll(epi, &epq.pt);
			 |-->int ep_item_poll(struct epitem *epi, poll_table *pt)
                 |	epi->ffd.file->f_op->poll(epi->ffd.file, pt)
                 // 调用底层文件的 poll 函数
                 //	进入内核的PSI代码  common/kernel/sched/psi.c
                 |-->psi_fop_poll(struct file *file, poll_table *wait)
                 	|-->psi_trigger_poll(void **trigger_ptr, struct file *file, poll_table *wait)
                 		|	poll_wait(file, &t->event_wait, wait);
						|-->poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
                            // 回调 common/fs/eventpoll.c 中的函数 ep_ptable_queue_proc
                            |	p->_qproc(filp, wait_address, p);
					|		if (cmpxchg(&t->event, 1, 0) == 1)
					|			ret |= EPOLLPRI;//上层lowmemorykiller传入的监听事件就是 EPOLLPRI
					|		return ret
              

ep_ptable_queue_proc

p->_qproc(filp, wait_address, p);
/*参数:
file:  要监听的文件对象
whead: 触发器的等待队列 psi_trigger.event_wait
pt:	   ep_pqueue.pt 的成员, ep_pqueue.epi 是个 epitem,存储了 lowmemorykiller 传来的 事件 和 文件对象.
*/
void ep_ptable_queue_proc(struct file *file, wait_queue_head_t *whead, poll_table *pt)
    |	struct ep_pqueue *epq = container_of(pt, struct ep_pqueue, pt);// 通过pt指针拿到 epq 指针
	|	struct epitem *epi = epq->epi;//拿到 epq 后进而就能拿到 epitem
	|	struct eppoll_entry *pwq;
	|	pwq = kmem_cache_alloc(pwq_cache, GFP_KERNEL); // 分配 eppoll_entry 内存
	//  这里初始化的是  回调函数类型的 wait_queue_entry ,被唤醒后执行的是回调函数 ep_poll_callback ,不是执行 线程唤醒
	|	init_waitqueue_func_entry(&pwq->wait, ep_poll_callback);
	//	把 &pwq->wait 挂到 PSI 传过来的等待队列
	|	add_wait_queue(whead, &pwq->wait);

五、epoll_wait 流程

epoll_wait(epollfd, events, maxevents, -1);

SYSCALL_DEFINE4(epoll_wait, int, epfd, struct epoll_event __user *, events, int, maxevents, int, timeout)
 |	struct fd f; // fd 结构体内包含 file 对象
 |	struct eventpoll *ep;
 |	f = fdget(epfd); //获取内核 fd 结构体
 // 从文件对象的私有数据中拿到 eventpoll
 |	ep = f.file->private_data; //这个 eventpoll 是在系统调用 epoll_create 中创建,并绑定到 private_data 中的
 |	ep_poll(ep, events, maxevents, timeout);
 |-->ep_poll(struct eventpoll *ep, struct epoll_event __user *events, int maxevents, long timeout)
     |	wait_queue_entry_t wait;
	 |	init_waitqueue_entry(&wait, current);
	 |	__add_wait_queue_exclusive(&ep->wq, &wait);//加入等待队列 &ep->wq
	 |	for (;;) {
         // 进入for 无线循环
         |	set_current_state(TASK_INTERRUPTIBLE); //设置线程状态为可中断的睡眠状态
         // 退出循环条件: 有epoll事件,或者超时
         |	if (ep_events_available(ep) || timed_out)
				break;
         // 线程睡眠,设置睡眠超时时间
         |	freezable_schedule_hrtimeout_range(to, slack, HRTIMER_MODE_ABS);
     |  }
	 //	线程从 睡眠状态唤醒:
	 |	__remove_wait_queue(&ep->wq, &wait);
	 |	__set_current_state(TASK_RUNNING); //设置线程状态为可运行状态

六、定时器任务

  • linux启动内核阶段,会加载PSI模块,执行 psi_proc_init ,在proc目录下创建文件pressure/io、pressure/memory、pressure/cpu、pressure/irq
  • 之后在执行调度器初始化时,执行 psi_init ,此流程调用 group_init(&psi_system); 函数内始化定时器
  • 定时器传入的函数执行体为 poll_timer_fn

1、定时器初始化

start_kernel // common/init/main.c
|-->sched_init // common/kernel/sched/core.c
    |-->psi_init //// common/kernel/sched/psi.c
        |-->group_init
			|--> timer_setup(&group->poll_timer, poll_timer_fn, 0);

static void poll_timer_fn(struct timer_list *t)
{
    //group 为全局变量 psi_system
	struct psi_group *group = from_timer(group, t, poll_timer);
	// 设置 &group->poll_wakeup 的值为 1
    // &group->poll_wakeup == 1,是唤醒条件 psi_poll_worker 函数所在线程唤醒的条件
	atomic_set(&group->poll_wakeup, 1);
    // 唤醒 &group->poll_wait 等待队列上的线程
	wake_up_interruptible(&group->poll_wait);// psi_poll_worker 唤醒后执行 psi_poll_work
}

2、触发定时任务启动

psi_memstall_enter

以 psi_memstall_enter 为例

// 在内存低于 min 时,不再使用swap异步收回内存,而是在当前线程启动直接回收内存,再分配内存
__alloc_pages_direct_reclaim(...)  //common/mm/page_alloc.c
|	psi_memstall_enter(&pflags);  // __alloc_pages_direct_reclaim 函数中 PSI 插桩代码
	//  传入事件类型 TSK_MEMSTALL | TSK_MEMSTALL_RUNNING ,表示出现了内存失速事件
	|	psi_task_change(current, 0, TSK_MEMSTALL | TSK_MEMSTALL_RUNNING);
	|-->void psi_task_change(struct task_struct *task, int clear, int set);
		|	psi_group_change(group, cpu, clear, set, now, true);
		|-->void psi_group_change(struct psi_group *group,int cpu,unsigned int clear,unsigned int set,u64 now,bool wake_clock);
            |	psi_schedule_poll_work(group, 1, false);
			|-->void psi_schedule_poll_work(struct psi_group *group, unsigned long delay, bool force);
				//	delay=1, jiffies + 1 表示在下一次节拍后启动定时任务,即,下一次时钟中断后启动
				|	mod_timer(&group->poll_timer, jiffies + delay); // 启动定时任务
psi_poll_worker 被唤醒

定时任务 poll_timer_fn 唤醒 psi_group->poll_wait 队列上的任务 psi_poll_worker

psi_poll_worker 继续往下执行,调用 psi_poll_work

void psi_poll_work(struct psi_group *group);
|	u32 changed_states;
|	u64 now;
|	mutex_lock(&group->trigger_lock);
|	now = sched_clock();//获取当前时间
|	// 更新 group->total[aggregator][s] 
|	collect_percpu_times(group, PSI_POLL, &changed_states);
|	if (changed_states & group->poll_states)
    |	if (now > group->polling_until)
        |	init_triggers(group, now);// 赋值 polling_next_update = now + group->poll_min_period;
		/*
			 #define UPDATES_PER_WINDOW 10	// 10 updates per window 
			 group->poll_min_period * UPDATES_PER_WINDOW // 通常就等于 lowmemorykiller 传过来的窗口时间
			 group->polling_until = 当前时间 + 窗口时间, 表示下次更新时间
			 也就是说会有 UPDATES_PER_WINDOW=10 次轮询
		*/
	|	group->polling_until = now + group->poll_min_period * UPDATES_PER_WINDOW;
	// 当前时间大于  polling_until 时,不再启动定时器, 定时器不启动, psi_poll_worker 无线循环代码就会再次睡眠.
	// 如果当前时间小于 polling_until ,则会持续触发定时器,不断循环执行 psi_poll_work ,直到当前时间大于 polling_until
|	if (now > group->polling_until)
    |	group->polling_next_update = ULLONG_MAX;
	|	goto out;
	// 当前时间 >= polling_next_update 时,更新数据。除了首次开始轮询,后边每次都会进入此分支
|	if (now >= group->polling_next_update)
    |	group->polling_next_update = update_triggers(group, now);//更新数据=============!!!!!
	|-->update_triggers(group, now);
		|	if (cmpxchg(&t->event, 0, 1) == 0)
				wake_up_interruptible(&t->event_wait);
	// 再次启动定时器
	// 定时器任务启动时间间隔为: polling_next_update-now, 通常为 poll_min_period
|	psi_schedule_poll_work(group, nsecs_to_jiffies(group->polling_next_update - now) + 1, force_reschedule);
out:
	mutex_unlock(&group->trigger_lock);
update_triggers
static u64 update_triggers(struct psi_group *group, u64 now)
{
	struct psi_trigger *t;
	bool update_total = false;
	u64 *total = group->total[PSI_POLL];

	/*
	 * On subsequent updates, calculate growth deltas and let
	 * watchers know when their specified thresholds are exceeded.
	 在随后的更新中,计算增长增量,并让观察者知道何时超过了他们指定的阈值
	 */
	list_for_each_entry(t, &group->triggers, node) {
		u64 growth;
		bool new_stall;

        //group->polling_total 和 group->total中的数据对比。不一致说明发生了新的失速事件
		new_stall = group->polling_total[t->state] != total[t->state];

		/* Check for stall activity or a previous threshold breach */
		if (!new_stall && !t->pending_event)
			continue;
		/*
		 * Check for new stall activity, as well as deferred
		 * events that occurred in the last window after the
		 * trigger had already fired (we want to ratelimit
		 * events without dropping any).
		 检查新的暂停活动,以及触发器触发后在最后一个窗口中发生的延迟事件(我们希望在不丢弃任何事件的情况下对事件进行评级)
		 */
		if (new_stall) {
			/*
			 * Multiple triggers might be looking at the same state,
			 * remember to update group->polling_total[] once we've
			 * been through all of them. Also remember to extend the
			 * polling time if we see new stall activity.
			 */
			update_total = true;

			/* Calculate growth since last update */
            // 计算增长
			growth = window_update(&t->win, now, total[t->state]);
			if (!t->pending_event) {
				if (growth < t->threshold)//增量未超过用户层设置的门槛时,不会唤醒 &t->event_wait 上的线程
					continue;

				t->pending_event = true;
			}
		}
		/* Limit event signaling to once per window */
		if (now < t->last_event_time + t->win.size)//增量未超过用户层设置的门槛时,不会唤醒 &t->event_wait 上的线程
			continue;

		/* Generate an event */
//cmpxchg(void *ptr, unsigned long old, unsigned long new);
//将old和ptr指向的内容比较,如果相等,则将new写入到ptr中,返回old,如果不相等,则返回ptr指向的内容。
//如果&t->event和0的值一样,则把1写到&t->event内存,返回0;否则返回&t->event指向的值
		if (cmpxchg(&t->event, 0, 1) == 0)
			wake_up_interruptible(&t->event_wait); // 【唤醒 t->event_wait 队列上的任务】
		t->last_event_time = now;
		/* Reset threshold breach flag once event got generated */
		t->pending_event = false;
	}

	if (update_total)// 把 total内存数据 复制到 group->polling_total内存
		memcpy(group->polling_total, total, sizeof(group->polling_total));

	return now + group->poll_min_period;
}

window_update
/*
 * PSI growth tracking window update and growth calculation routine.
 *
 * This approximates a sliding tracking window by interpolating
 * partially elapsed windows using historical growth data from the
 * previous intervals. This minimizes memory requirements (by not storing
 * all the intermediate values in the previous window) and simplifies
 * the calculations. It works well because PSI signal changes only in
 * positive direction and over relatively small window sizes the growth
 * is close to linear.
 */
static u64 window_update(struct psi_window *win, u64 now, u64 value)
{
	u64 elapsed;
	u64 growth;

	elapsed = now - win->start_time;   //经过的时间
	growth = value - win->start_value; // 减去起始的值,得到增长值
	/*
	 * After each tracking window passes win->start_value and
	 * win->start_time get reset and win->prev_growth stores
	 * the average per-window growth of the previous window.
	 * win->prev_growth is then used to interpolate additional
	 * growth from the previous window assuming it was linear.
	 */
	if (elapsed > win->size)  
        // 如果经过的时间超过 窗口时间,则重置 psi_window 的start_time,start_value,prev_growth 为最新的值
        // 此时需要开始新的一轮计算
		window_reset(win, now, value, growth);
	else {
		u32 remaining;

		remaining = win->size - elapsed;
        // 估算增长值. 假定是线性增长的, 则, 增长值 = 上次的增长值 * (剩余时间/窗口) + 当前增长值
		growth += div64_u64(win->prev_growth * remaining, win->size); 
	}

	return growth;
}

/* 
psi_trigger_create--> window_reset(&t->win, sched_clock(), group->total[PSI_POLL][t->state], 0);//初始化
window_update--> window_reset(win, now, value, growth); // 重置为当前最新的值
psi_poll_work-->init_triggers--> window_reset(&t->win, now, group->total[PSI_POLL][t->state], 0) //初始化
*/
static void window_reset(struct psi_window *win, u64 now, u64 value, u64 prev_growth)
{
	win->start_time = now;
	win->start_value = value;
	win->prev_growth = prev_growth;
}
ep_poll_callback 函数回调
int ep_poll_callback(wait_queue_t *wait, unsigned mode, int sync, void *key);
|	wake_up_locked(&ep->wq);// lmkd 主线程,在内核执行 epoll_wait 处的代码被唤醒,开始处理事件
    
epoll_wait 被阻塞的代码唤醒(lmkd 主线程,在内核执行 epoll_wait 处的代码)
  • 把数据发回用户空间
ep_send_events(ep, events, maxevents);
int ep_send_events(struct eventpoll *ep, struct epoll_event __user *events, int maxevents);
|	ep_scan_ready_list(ep, ep_send_events_proc, &esed, 0, false);
|-->int ep_scan_ready_list(struct eventpoll *ep, int (*sproc)(struct eventpoll *, struct list_head *, void *),
			      void *priv, int depth, bool ep_locked);
    |	(*sproc)(ep, &txlist, priv);
    |-->int ep_send_events_proc(struct eventpoll *ep, struct list_head *head, void *priv);
		|	init_poll_funcptr(&pt, NULL); //回调函数传入的是 NULL,所以不会再次调用 ep_ptable_queue_proc
		|	//再次调用 epi->ffd.file->f_op->poll,因为 pt->_qproc = null,所以不会再次调用 ep_ptable_queue_proc
		|	revents = ep_item_poll(epi, &pt);
					  |	 return epi->ffd.file->f_op->poll(epi->ffd.file, pt) & epi->event.events;
		|	__put_user(revents, &uevent->events); // 把 psi_fop_poll 调用结果传回用户空间
		// 把 epi->event.data 写回用户空间,epi->event.data本就是用户空间传过来的,再次原模原样带回去
		|	__put_user(epi->event.data, &uevent->data) 

附-部分数据结构释义

struct psi_trigger

// common/include/linux/psi_types.h
struct psi_trigger {
	/* PSI state being monitored by the trigger */
    /*lowmemorykiller传过来的要监听的状态赋值给 psi_trigger.state
    psi_states 枚举类型:PSI_IO_SOME,PSI_IO_FULL,PSI_MEM_SOME,PSI_MEM_FULL,PSI_CPU_SOME,PSI_CPU_FULL,PSI_IRQ_FULL,PSI_NONIDLE
    */
	enum psi_states state;

	/* User-spacified threshold in ns */
    /* lowmemorykiller 传过来的,门限值,转换为ns单位,存储到 threshold */
	u64 threshold;

	/* List node inside triggers list */
    /* 链接到 psi_group.triggers ,用于通过 psi_group 检索所有的 psi_trigger */
	struct list_head node;

	/* Backpointer needed during trigger destruction */
    /* 所属的 psi_group */
	struct psi_group *group;

	/* Wait queue for polling */
    /* 此触发器上的wait队列 */
	wait_queue_head_t event_wait;

	/* Pending event flag */
    /* 初始创建时为 0 , 表示是否有stall事件 */
	int event;

	/* Tracking window */
    /* win.size 保存 lowmemorykiller 传过来的窗口时间,单位ns  */
	struct psi_window win;

	/*
	 * Time last event was generated. Used for rate-limiting
	 * events to one per window
	 */
	u64 last_event_time;

	/* Deferred event(s) from previous ratelimit window 
	前一个速率限制窗口的延迟事件*/
	bool pending_event;
};

struct psi_group

// common/include/linux/psi_types.h
struct psi_group {
	struct psi_group *parent;
	bool enabled;

	/* Protects data used by the aggregator */
	struct mutex avgs_lock;

	/* Per-cpu task state & time tracking */
	struct psi_group_cpu __percpu *pcpu;//每个cpu 线程状态 & 时间跟踪

	/* Running pressure averages */
	u64 avg_total[NR_PSI_STATES - 1];
	u64 avg_last_update;
	u64 avg_next_update;

	/* Aggregator work control */
	struct delayed_work avgs_work;

	/* Total stall times and sampled pressure averages */
    //二维数组,存储平均值 和 最新的总失速时间
	u64 total[NR_PSI_AGGREGATORS][NR_PSI_STATES - 1];
    
	unsigned long avg[NR_PSI_STATES - 1][3];

	/* Monitor work control */
    /* psimon 线程 */
	struct task_struct __rcu *poll_task;
    // 定时器
	struct timer_list poll_timer; 
    // wait队列
	wait_queue_head_t poll_wait;
    /*
    psimon 中使用此变量判断唤醒条件;
    psi_poll_worker代码中这样使用: 如果 poll_wakeup 为 1 则唤醒, 然后再把 poll_wakeup 设置为 0 。
    下次如果没有代码启动定时任务 poll_timer_fn ,psi_poll_worker中的代码再次睡眠。
    */
	atomic_t poll_wakeup;
    
    /* 初始化时为 0 ; 内核线程psimon销毁时设置为 0
      触发器被全部销毁时, poll_task 变为null, 此时需要设置 poll_scheduled = 0
     psi_poll_work 轮询结束时,或者刚开始时,会设置为0
    */
	atomic_t poll_scheduled;

	/* Protects data used by the monitor */
	struct mutex trigger_lock;

	/* Configured polling triggers */
    // trigger 链表,当前psi_group的所有 psi_trigger。链表元素是 psi_trigger.node
	struct list_head triggers;
    
	u32 nr_triggers[NR_PSI_STATES - 1];
    
    // poll_states 记录PSI需要监听[PSI_IO_SOME ~ PSI_NONIDLE]这些事件中的哪些事件
	u32 poll_states;
    
    /* 
    poll_min_period 表示数据更新的周期,用于计算 polling_next_update。
    poll_min_period 初值为 U32_MAX, 在 psi_trigger_create 中真正赋值; 
    使用 lowmemorykiller 传过来的窗口值计算得到; 
    poll_min_period = window/UPDATES_PER_WINDOW;
    UPDATES_PER_WINDOW = 10; 表示每个窗口时间内更新10次内存数据。
    更新数据并不会把压力事件返回给 lowmemorykiller,需要到达窗口时间,才会返回事件,唤醒 lowmemorykiller 主线程
    poll_min_period = window/10;
    lowmemorykiller默认窗口时间为1秒,所以通常情况下 poll_min_period = 100ms * 1000 * 1000  = 100000000 ns
    group 上不止一个触发器psi_trigger, poll_min_period 使用监听的所有事件里周期中最小的那个.
    */
	u64 poll_min_period;

	/* Total stall times at the start of monitor activation */
    // 存储上一次的失速总时间, 用于同 total 中的数据对比,
	u64 polling_total[NR_PSI_STATES - 1];
    
    /* polling_next_update 表示下次更新数据的时间
    通常是 当前时间 + poll_min_period 
    时间到了之后,调用 update_triggers 函数更新数据
    init_triggers 中赋值为 now + group->poll_min_period;
    */
	u64 polling_next_update;
    /*仅在 psi_poll_work 函数中使用;
    表示轮询的窗口时间
    初始化时为0;
    psi_poll_work 函数中赋值为 now + group->poll_min_period * UPDATES_PER_WINDOW;
    表示 当前时间 + 一个窗口时间
    如果当前时间小于 polling_until ,则会持续触发定时器,
    	定时器 polling_next_update-now 时间后执行 psi_poll_work ,直到当前时间大于 polling_until
    */
	u64 polling_until;
};

enum psi_aggregators {
	PSI_AVGS = 0,
	PSI_POLL,
	NR_PSI_AGGREGATORS,
}

后记

目前更新的这些还不完整,还有部分细节,因为需要验证,没写上来,等验证过后再更新吧。

其实,从PSI源码上看,这个内核源码还可以有更多的作为,因为PSI已经提供了一个很好的框架。