源码基于:Android T
相关博文:
之前有粉丝在问笔者,如上面详解的两篇博文都是基于 Android 11,现在都使用 Android 13了,是否有很大的区别呢?笔者特地去看了下 Android T,本文简单地总结下 R 与 T 的区别。
在 Android T 中引入了 watchdog 机制,这个机制的引入是为了防止 lmkd 会在syscall 的时候长时间的卡住。当lmkd 在处理 events 的时候会通过watchdog 线程进行延迟的监听,在 events 处理完成后重置 watchdog。默认的延迟时长为 2s,如果在处理某个event 的时候超过了 timeout,就会唤醒 watchdog 并且会 kill 最近的重要的进程来防止内存压力。
下面结合代码对 watchdog 进行简单的分析,详细的代码逻辑请自行查看 Android T 源码。
在 lmkd 的main 函数中首先会对 watchdog 进行初始化:
if (!watchdog.init()) {
ALOGE("Failed to initialize the watchdog");
}
来看下 watchdog 的初始化:
bool Watchdog::init() {
pthread_t thread;
if (pthread_create(&thread, NULL, watchdog_main, this)) {
ALOGE("pthread_create failed: %s", strerror(errno));
return false;
}
if (pthread_setname_np(thread, "lmkd_watchdog")) {
ALOGW("pthread_setname_np failed: %s", strerror(errno));
}
return true;
}
正如上面所述,会在 lmkd 中新起一个线程,命名为 lmkd_watchdog。
在 step1 中,初始化 watchdog 的时候会通过 watchdog_main() 创建一个定时器:
static void* watchdog_main(void* param) {
Watchdog *watchdog = static_cast<Watchdog*>(param);
sigset_t sigset;
int signum;
// Ensure the thread does not use little cores
if (!SetTaskProfiles(gettid(), {"CPUSET_SP_FOREGROUND"}, true)) {
ALOGE("Failed to assign cpuset to the watchdog thread");
}
if (!watchdog->create_timer(sigset)) {
ALOGE("Watchdog timer creation failed!");
return NULL;
}
while (true) {
if (sigwait(&sigset, &signum) == -1) {
ALOGE("sigwait failed: %s", strerror(errno));
}
watchdog->bite();
}
return NULL;
}
代码比较简单,通过 create_timer() 创建定时器,同时初始化信号集;通过 sigwait() 等待捕捉信号,并最终通过 bite() 进行超时处理。
create_timer() 这里就不过多剖析了,主要是通过 timer_create() 创建一个 POSIX 的定时器,详细的定时器信息可以查看:《Linux中的几种定时器》一文。
static void call_handler(struct event_handler_info* handler_info,
struct polling_params *poll_params, uint32_t events) {
struct timespec curr_tm;
watchdog.start();
...
watchdog.stop();
}
在 mainloop() 和这里的 call_handler() 处理 events 的时候,首先会通过 watchdog.start() 启动定时器,在处理完成之后通过 watchdog.stop() 停止定时器。
下面来看下 start():
bool Watchdog::start() {
// Start the timer and keep it active until it's disarmed
struct itimerspec new_timer;
if (!timer_created_) {
return false;
}
new_timer.it_value.tv_sec = timeout_;
new_timer.it_value.tv_nsec = 0;
new_timer.it_interval.tv_sec = timeout_;
new_timer.it_interval.tv_nsec = 0;
if (timer_settime(timer_, 0, &new_timer, NULL)) {
ALOGE("timer_settime failed: %s", strerror(errno));
return false;
}
return true;
}
代码中在 timer_created 之后通过 timer_settime() 设置一个timeout 的定时器,详细的 timer_settime() 接口可以查看:《Linux中的几种定时器》一文。
我们在上面 step2 中说到,当定时器超时时,会通过 watchdog 的bite() 接口开咬,而这个 bite() 是在watchdog 实例化的时候传入:
static Watchdog watchdog(WATCHDOG_TIMEOUT_SEC, watchdog_callback);
下面来看下这个 callback:
static void watchdog_callback() {
int prev_pid = 0;
ALOGW("lmkd watchdog timed out!");
for (int oom_score = OOM_SCORE_ADJ_MAX; oom_score >= 0;) {
struct proc target;
if (!find_victim(oom_score, prev_pid, target)) {
oom_score--;
prev_pid = 0;
continue;
}
if (reaper.kill({ target.pidfd, target.pid, target.uid }, true) == 0) {
ALOGW("lmkd watchdog killed process %d, oom_score_adj %d", target.pid, oom_score);
killinfo_log(&target, 0, 0, 0, NULL, NULL, NULL, NULL, NULL);
break;
}
prev_pid = target.pid;
}
}
这个代码是 watchdog 的核心处理函数,首先是通过函数 find_victim() 轮询查找最终被牺牲的进程,如果找到了会调用 reaper.kill() 进行kill 和 reap 操作。这里的reaper 是 Android T 的另一个特点,下一节会进行简单的总结,需要注意的是这里最后一个参数为 true,即立即进行处理,而不会放入队列等待 repaer 线程处理。
至此,Android T 中第一个差异点 watchdog 分析完成。主要是 lmkd 的主线程太重要了,不希望出现严重超时的锁住状态,在内存压力紧张的情况采用 watchdog 措施是很有必要的。
下面来看下 Android T 中第二个差异点 reaper。
原则上,在 kill 某个进程之后,其内存应该立即得到回收并交给其他用户使用。然而在现实世界中情况并不那么简单。被 kill 的进程本身要负责清理和释放它的资源,这个工作是在内核上下文中进行的。然而,如果被 kill 的进程发现自己被一个 uninterruptible sleep 给阻塞住的话,那么这个清理工作就会被推迟,没人知道会被推后多久。还有其他一些因素也会导致内存释放的速度变慢,比如相关的 CPU 是否非常繁忙、该 CPU 是否在慢速、低功率的状态下运行。
在内核中有OOM killer 的机制,采用 reaper 的内核线程来进行内存的清理工作。这使得 OOM kill 的效果很明显,哪怕被选中的进程不能立即退出,也仍然能快速释放内存。但 OOM killer 的另外一个问题是,它选择的 badness 进程并不一定是用户希望,它只是为了保证系统的继续运行。
对于Android 系统来说,希望将这种 OOM killer 放在用户空间,例如这里的 lmkd。不过用户空间的 OOM killer 在希望释放内存的时候,必须要依靠 kill() 或者是 pidfd_send_signal(),这种方式杀死一个进程并不能使 OOM reaper 接受来发挥作用,所以用户空间的守护进程又回到了不能等待目标进程自己来释放资源的情况。
Suren Baghdasaryan 针对这个问题提出了一个新的系统调用:
int process_mrelease(int pidfd, unsigned int flags);
该系统调用能够在一个进程被kill 之后,用来加快内存释放。这样允许内存被释放,无需目标进程被调度,因此不依赖于目标进程的优先级或它所运行的CPU。
然而,process_mrelease() 系统调用需要相当长时间。当 lmkd 正忙于收割前一个目标进程的内存时,这段时间阻塞 lmkd 主线程,可能会引起 PSI events 的丢失。因为这个原因,收割工作需要在一个独立的线程中完成。这样,lmkd 主线程可以在内存被释放的同时保持监听 PSI。
lmkd 中引入 Reaper 类,该类包含一个线程池用来处理 kill 工作和 reap 工作。主线程给 Reaper 提交请求对目标进程进行 kill 和 reap ,只需要将目标进程放入队列中,唤醒线程进行处理即可,无需阻塞收割。当然,当线程池中所有的线程处于忙碌时,下一个 kill 将直接在主线程中处理。
在 lmkd 的main 函数中首先会对 reaper 进行初始化:
if (init_reaper()) {
ALOGI("Process reaper initialized with %d threads in the pool",
reaper.thread_cnt());
}
来看下 reaper 的初始化:
static bool init_reaper() {
if (!reaper.is_reaping_supported()) {
ALOGI("Process reaping is not supported");
return false;
}
if (!setup_reaper_comm()) {
ALOGE("Failed to create thread communication channel");
return false;
}
// Setup epoll handler
struct epoll_event epev;
static struct event_handler_info kill_failed_hinfo = { 0, kill_fail_handler };
epev.events = EPOLLIN;
epev.data.ptr = (void *)&kill_failed_hinfo;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, reaper_comm_fd[0], &epev)) {
ALOGE("epoll_ctl failed: %s", strerror(errno));
drop_reaper_comm();
return false;
}
if (!reaper.init(reaper_comm_fd[1])) {
ALOGE("Failed to initialize reaper object");
if (epoll_ctl(epollfd, EPOLL_CTL_DEL, reaper_comm_fd[0], &epev)) {
ALOGE("epoll_ctl failed: %s", strerror(errno));
}
drop_reaper_comm();
return false;
}
maxevents++;
return true;
}
首先,通过 is_reaping_supported() 系统调用来确认内核是否支持 process_mrelease(),这个需要 Linux 5.15 之后版本支持;
接着,通过 setup_reaper_comm() 创建一个 pipe,reaper_comm_fd[0] 为读pipe 端,reaper_comm_fd[1] 为写 pipe 端,reaper_comm_fd[0] 为非阻塞方式;
接着,通过 epoll_ctl 将 reaper_comm_fd[0] 加入监听,当 kill 失败时会通过 reaper_comm_fd[1] 写入信息进行管道通信,处理函数为 kill_fail_handler();
最后通过 init() 函数,创建 Reaper 的线程池,线程处理函数为 reaper_main(),详细看 step2;
system/memory/lmkd/reaper.cpp
bool Reaper::init(int comm_fd) {
char name[16];
if (thread_cnt_ > 0) {
// init should not be called multiple times
return false;
}
thread_pool_ = new pthread_t[THREAD_POOL_SIZE];
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
if (pthread_create(&thread_pool_[thread_cnt_], NULL, reaper_main, this)) {
ALOGE("pthread_create failed: %s", strerror(errno));
continue;
}
snprintf(name, sizeof(name), "lmkd_reaper%d", thread_cnt_);
if (pthread_setname_np(thread_pool_[thread_cnt_], name)) {
ALOGW("pthread_setname_np failed: %s", strerror(errno));
}
thread_cnt_++;
}
if (!thread_cnt_) {
delete[] thread_pool_;
return false;
}
queue_.reserve(thread_cnt_);
comm_fd_ = comm_fd;
return true;
}
system/memory/lmkd/reaper.cpp
static void* reaper_main(void* param) {
Reaper *reaper = static_cast<Reaper*>(param);
struct timespec start_tm, end_tm;
struct Reaper::target_proc target;
pid_t tid = gettid();
// Ensure the thread does not use little cores
if (!SetTaskProfiles(tid, {"CPUSET_SP_FOREGROUND"}, true)) {
ALOGE("Failed to assign cpuset to the reaper thread");
}
for (;;) {
target = reaper->dequeue_request();
if (reaper->debug_enabled()) {
clock_gettime(CLOCK_MONOTONIC_COARSE, &start_tm);
}
if (pidfd_send_signal(target.pidfd, SIGKILL, NULL, 0)) {
// Inform the main thread about failure to kill
reaper->notify_kill_failure(target.pid);
goto done;
}
if (process_mrelease(target.pidfd, 0)) {
ALOGE("process_mrelease %d failed: %s", target.pidfd, strerror(errno));
goto done;
}
if (reaper->debug_enabled()) {
clock_gettime(CLOCK_MONOTONIC_COARSE, &end_tm);
ALOGI("Process %d was reaped in %ldms", target.pid,
get_time_diff_ms(&start_tm, &end_tm));
}
done:
close(target.pidfd);
reaper->request_complete();
}
return NULL;
}
如 kill_one_process() 中所调用,在kill 目标进程时,会通过 reaper.kill() 进行kill 流程:
system/memory/lmkd/reaper.cpp
int Reaper::kill(const struct target_proc& target, bool synchronous) {
/* CAP_KILL required */
if (target.pidfd < 0) {
return ::kill(target.pid, SIGKILL);
}
if (!synchronous && async_kill(target)) {
// we assume the kill will be successful and if it fails we will be notified
return 0;
}
int result = pidfd_send_signal(target.pidfd, SIGKILL, NULL, 0);
if (result) {
return result;
}
return is_reaping_supported() ? process_mrelease(target.pidfd, 0) : 0;
}
如果参数 synchronous 为false,即进行异步处理,会通过 async_kill() 将目标进程加入到队列中,唤醒reaper 线程池中的一个线程进行处理。当然,当线程池中的线程已经都被唤醒进行操作了,那么此次的 kill 将不会在放入队列异步执行,而是直接立即执行;
如果参数 synchronous 为true,即立即处理,如上面 watchdog_callback(),此时会通过 pidfd_send_signal() 发送 SIGKILL 信号,并通过 process_mrelease() 进行加速内存回收。
至此,Android T 中第二个差异点 reaper 分析完成。reaper 的引入主要考虑在主线程中进行kill 会引起阻塞,这个阻塞的时间段可能会引起 PSI events 的丢失。因为这个原因,收割工作需要在一个独立的线程中完成。这样,lmkd 主线程可以在内存被释放的同时保持监听 PSI。另外,reaper 中还引入 process_mrelease(),当内核支持 process_mrelease() 系统调用时,那么会加速内存回收。
每一个版本的改变都是又一次的性能提升,除了上面这两点,Android T 中还有其他的逻辑细节处理,但大致的方向都是没有变化的。如果还有其他细节是笔者没有注意到的,也欢迎留言提醒!
相关博文:
我正在使用Ruby-Tk为OSX开发一个桌面应用程序,我想为该应用程序提供一个AppleEvents接口(interface)。这意味着应用程序将定义它将响应的AppleScript命令的字典(对应于发送到应用程序的Apple事件),并且用户/其他应用程序可以使用AppleScript命令编写Ruby-Tk应用程序的脚本。其他脚本语言支持此类功能——Python通过位于http://appscript.svn.sourceforge.net/viewvc/appscript/py-aemreceive/的py-aemreceive库和Tcl通过位于http://tclae.source
Method#unbind返回对该方法的UnboundMethod引用,稍后可以使用UnboundMethod#bind将其绑定(bind)到另一个对象.classFooattr_reader:bazdefinitialize(baz)@baz=bazendendclassBardefinitialize(baz)@baz=bazendendf=Foo.new(:test1)g=Foo.new(:test2)h=Bar.new(:test3)f.method(:baz).unbind.bind(g).call#=>:test2f.method(:baz).unbind.bind(h).
显示等待需要用到两个类:WebDriverWait和expected_conditions两个类WebDriverWait:指定轮询间隔、超时时间等expected_conditions:指定了很多条件函数(也可以自定义条件函数)具体可以参考官网:selenium.webdriver.support.expected_conditions—Selenium4.5documentationfromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.support.uiimpor
我收到错误AWS::S3::Errors::InvalidRequest不支持您提供的授权机制。请使用AWS4-HMAC-SHA256.当我尝试将文件上传到新法兰克福地区的S3存储桶时。所有适用于USStandard区域。脚本:backup_file='/media/db-backup_for_dev/2014-10-23_02-00-07/slave_dump.sql.gz's3=AWS::S3.new(access_key_id:AMAZONS3['access_key_id'],secret_access_key:AMAZONS3['secret_access_key'])s3_
Qt中的信息输出机制介绍QDebug在Qt中使用qDebug输出不同类型的信息浮点数:使用%!f(MISSING)格式化符号输出浮点数布尔值:使用%!(MISSING)和%!(MISSING)格式化符号输出布尔值对象:使用qPrintable()函数输出对象的信息qInfoqWarningqCritical自定义信息输出格式不同输出方式的区别和底层逻辑总结介绍在Qt中,信息输出机制用于在程序运行时输出各种信息,包括调试信息、提示信息、警告信息和错误信息等。Qt提供了多种信息输出机制,主要包括以下几种:qDebug:最常用的信息输出机制,用于输出各种调试信息,例如变量的值、函数的返回值和对象的状
我正在使用log4javascript来记录和跟踪我的JavaScript代码中的问题。我以前见过类似的日志记录辅助工具,但我很难理解应该如何使用这些日志级别中的每一个才能更有用和更有成效。大多数时候,我最终会记录调试、信息或跟踪,但并没有真正意识到它们各自的效率如何。随着代码变得越来越大,它变得越来越困难,我觉得日志麻烦多于帮助。有人可以给我一些指南/帮助,以便我可以很好地使用日志记录机制。以下是log4javascript支持的不同日志级别:log4javascript.Level.ALLlog4javascript.Level.TRACElog4javascript.Level.
文章目录Cookie的实现机制Cookie的安全隐患Cookie防篡改机制Session的实现机制Cookie和Session是为了在无状态的HTTP协议之上维护会话状态,使得服务器可以知道当前是和哪个客户在打交道。本文来详细讨论Cookie和Session的实现机制,以及其中涉及的安全问题。因为HTTP协议是无状态的,即每次用户请求到达服务器时,HTTP服务器并不知道这个用户是谁、是否登录过等。现在的服务器之所以知道我们是否已经登录,是因为服务器在登录时设置了浏览器的Cookie!Session则是借由Cookie而实现的更高层的服务器与浏览器之间的会话。Cookie是由网景公司的前雇员Lo
我正在学习node.js,我能找到的大多数示例都是处理简单示例的。我更感兴趣的是构建真实世界的复杂系统,并评估node.js基于事件的模型如何处理真实应用程序的所有用例。我想应用的一个常见模式是让阻塞执行超时,如果它没有在特定超时时间内发生。例如,如果执行一个数据库查询需要超过30秒,那么对于某些应用程序来说可能太多了。或者如果读取一个文件需要超过10秒。对我来说,带超时的理想程序流与带异常的程序流类似。如果某个事件没有在某个预定义的超时限制内发生,那么事件监听器将从事件循环中清除,并且会生成一个超时事件。此超时事件将有一个备用监听器。如果事件被正常处理,那么超时监听器和事件监听器都会
我一直在尝试理解一些用于打开websocket的代码:varws=newWebSocket('ws://my.domain.com');ws.onopen=function(event){...}我的问题是握手是如何开始的?如果它是在WebSocket构造函数中启动的,那么如果到那时还没有设置,如何调用onopen呢?如果WebSocket构造函数创建一个执行握手的线程,那么在握手结束之前是否必须足够快地定义onopen?如果是这样,那听起来有点危险,因为如果JS虚拟机变慢,握手可能会在定义onopen之前完成,这意味着事件没有得到处理。还是设置onopen函数触发握手?有人可以向我解
关于我正在使用的应用程序的一些背景知识:SpringMVC、JavaEEWeb应用程序、Maven。基本上,我正在寻找的是一种在每次部署我们的应用程序时刷新所有JS和CSS文件的机制。就目前而言,应用程序正在引用静态文件(例如“js/app.js”)。每次这个文件有变化,在本地重新部署后,当浏览器去下载它时,我们会得到一个304(文件没有被修改)。我的问题是:1)在重新部署应用程序时再次提供这些文件并使用应用程序版本作为缓存清除机制(例如“js/v1.0.0/app.js”)会更好吗?这可以通过servlet过滤器来完成。2)由于该元素是一个Maven动态Web元素,是否有某种插件可以