LWN:用 process_mrelease() 加快进程清理!

本文介绍了在高性能系统中,内存管理的重要性。当前,当需要回收内存时,通过kill进程的方式可能导致延迟,因为进程需要自行清理资源。SurenBaghdasaryan提出了一种新的系统调用process_mrelease(),旨在加快被杀进程的内存回收,提高系统效率。这个调用允许在进程退出时立即释放内存,减少不必要的延迟,特别适用于需要快速响应的系统,如手机应用和云计算环境。

关注了就能看到更多这么棒的文章哦~

Hastening process cleanup with process_mrelease()

By Jonathan Corbet
July 26, 2021
DeepL assisted translation
https://lwn.net/Articles/864184/

Computing 领域的一个永远有效的基本定律:无论系统中安装了多少内存都是不够的。这对于性能要求非常高的系统中尤其如此,这些系统中每一个内存 page 都被分配和使用掉了,在急需时很难获取到更多内存。而要想再得到一些内存,有一种方法就是 kill 一个或多个进程,从而为其他用户释放出资源。但这个过程往往不像用户以为的那样可以迅速、可靠地完成。为了改善这种情况,Suren Baghdasaryan 建议增加一个名为 process_mrelease()的系统调用。

那些运行了许多 task 的系统中,有一些任务通常总是比其他任务更重要的。系统在尽全力运行时,可能会有一些相对来说不那么重要的 task 最终可能会挤占了其他更重要的 task 所需要的内存,这时如果这些不重要的进程直接消失掉可能对大家来说会更好。这类系统上通常都会有一个进程管理器(process manager),在这种情况下会直接 kill 掉低优先级的进程。采用这种模式的最常见的例子可能要算是安卓系统了,只要可用的内存不足以满足前台运行程序的需求,它就会杀掉后台运行的应用程序。如果有更重要的工作需要内存的话,云计算(cloud-computing)系统也会杀死低优先级的、一些在尽力而为的工作进程。

原则上,kill 某个进程之后,其内存应该立即得到回收并交给其他用户使用。然而在现实世界中情况并不那么简单。被 kill 的进程本身要负责清理和释放它的资源,这个工作是在内核上下文中进行的。然而,如果被 kill 的进程发现自己被一个 uninterruptible sleep 给阻塞住的话,那么这个清理工作就会被推迟,没人知道会被推后多久。还有其他一些因素也会导致内存释放的速度变慢,比如相关的 CPU 是否非常繁忙、该 CPU 是否在慢速、低功率的状态下运行。

当这种情况发生的时候,系统已经付出了 kill 进程的代价(毕竟该进程可能正在做一些有用的事情),但却没有得到相应的好处。尤其不幸的是,这些好处往往是系统在迫切需要的,否则它也就不会去 kill 进程了。进程清理工作的延后处理明显会对优先级较高的 task 也产生直接影响,比如手机上的应用响应变得更迟钝,或者那些没有耐心的观众迟迟看不到他们等着看的猫猫视频。

这个问题几年前人们就碰到了,是在系统的 out-of-memory (OOM) killer 工作时暴露出来的,OOM killer 是内核在内存耗尽时最后的保底措施了。早在 2015 年开发的 OOM reaper 中就解决了这个问题,它把内存清理工作从这个即将死亡的进程中拿出来,交给一个独立运行的内核线程去做了。这使得 OOM kill 的效果更加明显了,哪怕被选中的进程不能立即退出,也仍然能快速释放内存。

但这项工作并没有解决 OOM killer 的另一个问题:它在判断系统中哪些进程是不重要的时候,往往与系统使用者的看法不一致。调用 OOM killer 可能会让整个系统得以继续运行,但如果用户的窗口界面被 kill 掉的话,他们肯定对这个动作感到非常恼火。

由于这个原因,所以系统开发者们倾向于将杀死进程来回收内存的决策掌握在自己手中。在用户空间运行的一些 out-of-memory handler 处理程序可以采取一些更积极的措施,来避免让系统一下子就进入 OOM 状态,而且它也可能更清楚哪些进程在意外死亡的时候大家最不会感到痛心。Facebook 发布的 oomd 守护进程就这样的一个例子,当然还有其他家的。

不过,用户空间的 OOM killer 与内核的 OOM killer 的地位还是不一样的。在希望释放内存的时候,它们必须依靠 kill() 系统调用(或者类似的 pidfd_send_signal())。用这种方式杀死一个进程的话并不能使 OOM reaper 接手来发挥作用,所以用户空间的守护进程又回到了此前那种不得不等待目标进程自己来释放资源的情况。

Baghdasaryan 针对这个问题提出了一个新的系统调用:

int process_mrelease(int pidfd, unsigned int flags);

pidfd 参数是用 pidfd 来指明目标进程。调用此函数时,该进程必须正在进行 exit 动作(比如之前先发送一个 kill()操作)。flags 参数目前必须是 0。这个调用的效果,就跟指定 OOM reaper 强制回收这个进程是一样的效果,会尽可能多地回收其内存。

之所以要为这个动作开发一个专门的系统调用,主要是因为需要给系统一个用来完成这项工作的上下文。遍历进程的地址空间并依次释放这些内存的任务将由发起 process_mrelease() 这个调用的进程来完成,这个进程可能是之前 kill 掉目标进程的那个进程,也可能不是。内核可以根据调用进程的优先级以及它可以运行在哪些 CPU 上这些信息,来完成这项工作,从而让清理工作可以在那些不会干扰(其他)系统 task 的地方来完成。

之前讨论过的另一个解决这个问题的方法是,当一个进程被杀死时就无条件地回收它的内存,这样就不需要专门的系统调用来实现这个目的了。不过,在这种情况下,这个清理工作将在发送信号的进程的上下文中完成,这可能不是一个受欢迎的决定。如果采用这个策略的话,一个进程如果 kill 了很多其他进程——比如调用了 killall 命令——那就会大大减慢清理工作的速度。添加一个单独的系统调用的话,用户空间就可以更好地控制何时、以及如何完成这项清理。

在这组 patch 的上一版本发布出来时,人们主要讨论的是系统调用的名字,当时的名字还是 process_reap()。这是一个相当明确的迹象,表明那些更重要的问题已经得到了解决,这个功能很可能已经准备好进入下一阶段了。process_mrelease() 的调用者数量可能不多,但似乎在某些情况下它会是一个非常有用的工具。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; int battery_type_check = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug( "[battery_type_check] read channel err = %d,\n", ret); bm_debug( "[battery_type_check]: ret = %d,ret_value[%d]\n", ret, ret_value); value = ret_value; if(is_fuelgauge_apply() == true) { switch(battery_id) case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default:【兼容原来的方案】 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; }
08-21
这段代码的主要功能是检测电池类型,通过读取电池的ADC(模数转换)值来判断使用的是哪种类型的电池。以下是代码的详细解释: ### 代码功能说明: 1. **变量定义**: - `value`:存储读取到的ADC值。 - `ret`:存储读取ADC值的返回值(用于判断是否读取成功)。 - `ret_value`:从ADC通道读取的原始值。 - `battery_id`:表示电池的ID。 - `battery_type_check`:用于判断电池类型的变量。 2. **空指针检查**: 如果 `batt_id` 为 NULL,则将电池类型设置为默认值 `BAT_TYPE__ATL_4400mV`,并将 `battery_id` 设置为 0。 3. **读取ADC值**: 使用 `iio_read_channel_processed` 函数读取ADC值,并进行错误检查。 4. **电池类型判断**: 根据 `battery_id` 的值,使用 `switch-case` 结构判断电池类型。每个 `case` 对应一个电池类型,并检查ADC值是否在该类型的范围内。 5. **默认情况处理**: 如果 `battery_id` 不在预设范围内,则进入 `default` 分支,尝试匹配其他电池类型的ADC范围。 6. **日志输出**: 使用 `printk` 输出调试信息,包括ADC值、电池类型和电池ID。 ### 问题分析: 1. **语法错误**: - `switch-case` 结构的语法不正确,`case` 分支应该直接跟在 `switch` 后面,而不是嵌套在 `if` 语句中。 - `default:` 后面的冒号应该是英文冒号 `:`,而不是中文冒号 `:`。 2. **逻辑错误**: - `battery_id` 变量在 `switch-case` 结构中被用作判断条件,但它的值在代码中没有被正确更新或传递。 3. **代码结构问题**: - `switch-case` 结构应该放在 `if(is_fuelgauge_apply() == true)` 条件之外,或者需要重新设计逻辑以确保正确执行。 ### 改进后的代码: ```c int battery_type_check(int *battery_type) { int value = 0; int ret = 0; int ret_value = 0; int battery_id = 0; if (batt_id == NULL) { bm_debug("[battery_type_check]: batt_id iio channel is null []\n"); *battery_type = BAT_TYPE__ATL_4400mV; battery_id = 0; return battery_id; } ret = iio_read_channel_processed(batt_id, &ret_value); if (ret < 0) bm_debug("[battery_type_check] read channel err = %d,\n", ret); bm_debug("[battery_type_check]: ret = %d, ret_value[%d]\n", ret, ret_value); value = ret_value; if (is_fuelgauge_apply() == true) { switch (battery_id) { case 0: if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } break; case 1: if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } break; case 2: if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } break; case 3: *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; break; default: // 兼容原来的方案 if (value >= BAT_TYPE_ATL_4450mV_ADC_MIN && value <= BAT_TYPE_ATL_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__ATL_4450mV; battery_id = BAT_ATL_BATT_ID; } else if (value >= BAT_TYPE_LWN_4450mV_ADC_MIN && value <= BAT_TYPE_LWN_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__LWN_4450mV; battery_id = BAT_LWN_BATT_ID; } else if (value >= BAT_TYPE_COS_4450mV_ADC_MIN && value <= BAT_TYPE_COS_4450mV_ADC_MAX) { *battery_type = BAT_TYPE__COS_4450mV; battery_id = BAT_COS_BATT_ID; } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } break; } } else { *battery_type = BAT_TYPE__UNKNOWN; battery_id = BAT_ENC_BATT_ID; } printk(KERN_ERR "[battery_type_check]: adc_value[%d], battery_type[%d], g_fg_battery_id[%d]\n", value, *battery_type, battery_id); return battery_id; } ``` ### 代码改进说明: 1. **语法修正**: - 修复了 `switch-case` 结构的语法错误,确保每个 `case` 分支正确嵌套在 `switch` 语句中。 - 修正了 `default` 分支的冒号符号。 2. **逻辑优化**: - 确保 `battery_id` 在 `switch-case` 结构中被正确使用,并根据ADC值更新电池类型。 3. **代码结构优化**: - 保持代码结构清晰,确保 `if(is_fuelgauge_apply() == true)` 条件下的逻辑正确执行。 ### 相关问题: 1. 如何在Linux内核中实现ADC读取? 2. 如何设计电池类型的ADC阈值? 3. 如何调试和优化电池类型检测逻辑? 4. 如何处理电池类型检测中的异常情况?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值