目录:
本篇文章讲述内容:
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 访问
- 如果从 file 对象开始访问的话:
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
- 等待队列【3】
对应的三个唤醒
- 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 事件的回调函数
- 之后开始检查当前设备的内存情况,内存不足时,开始查杀进程
- lmkd 主线程,在内核执行 epoll_wait 处的代码被唤醒,开始处理事件
一、用户层 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已经提供了一个很好的框架。