前言:
I/O Prefetcher是高通本身提供的一套优化方案,可以用在Android手机App冷启动的时候。
但是因为是和soc相关的,所以android 原生代码中是没有办法查看到的,也就是andoridxref中是看不到的,只能通过高通的代码看。
同时我们也可以看到在n和o中的实现已经不同了,n中还是通过iop-p-d来启动的,但是o中已经是通过hidl来启动了。hidl其实就是project treble提出来的,具体的内容补充我们现在讨论的内容,所以也就不去细讲了。那现在就是基于o版本的源码。
1.IO Prefetcher的初始化
首先模块的代码在 vendor/qcom/proprietary/android-perf/iop-hal/文件夹中,首先从入口main函数说起:io-p-d.c
107IIop* HIDL_FETCH_IIop(const char* /* name */) {
108 ALOGE("IOP-HAL: inside HIDL_FETCH_IIop");
109 Iop *iopObj = new Iop();
110 ALOGE("IOP-HAL: boot Address of iop object");
111 if (iopObj) {
//首先load对应的lib库,获取到对应的函数地址
112 iopObj->LoadIopLib();
113 ALOGE("IOP-HAL: loading library is done");
//再进行init操作
114 if (iopObj->mHandle.iop_server_init != NULL ) {
115 (*(iopObj->mHandle.iop_server_init))();
116 }
117 }
118 return iopObj;
119}
获取函数地址的来看一下:
62void Iop::LoadIopLib() {
...
75 *(void **) (&mHandle.iop_server_init) = dlsym(mHandle.dlhandle, "iop_server_init");
76 if ((rc = dlerror()) != NULL) {
77 ALOGE("IOP-HAL %s Failed to get iop_server_init\n", rc);
78 dlclose(mHandle.dlhandle);
79 mHandle.dlhandle = NULL;
80 return;
81 }
...
99 mHandle.is_opened = true;
100 }
102 return;
103}
所以可以看到这就到了iopinit服务了。我们看一下iop_server_init的具体内容:io-p.cpp
247//interface implementation
248int iop_server_init() {
//得到当前soc的id只有几个芯片支持iop
254 soc_id = get_soc_id();
255 switch(soc_id)
256 {
257 case SOCID_8994:
258 case SOCID_8092:
259 case SOCID_8992:
260 case SOCID_8996:
261 case SOCID_8996PRO:
262 case SOCID_8096PRO:
263 case SOCID_8096:
264 case SOCID_8998:
265 case SOCID_8939:
266 case SOCID_SDM845:
267 break;
268 default:
269 QLOGI("Fail to init IOP Server");
270 return 0;
271 }
272
273 IOPevqueue.GetDataPool().SetCBs(Alloccb, Dealloccb);
//进入无限循环来等待消息
274 rc = pthread_create(&iop_server_thread, NULL, iop_server, NULL);
275 if (rc != 0) {
276 stage = 3;
277 goto error;
278 }
279 return 1;
280
281error:
282 QLOGE("Unable to create control service (stage=%d, rc=%d)", stage, rc);
283 return 0;
284}
创建线程进入无限循环处理消息,除非产生了错误,才会退出这个服务。接着看一下iop_server
122static void *iop_server(void *data)
123{
129 /* Main loop */
130 for (;;) {
131 //wait for perflock commands
132 EventData *evData = IOPevqueue.Wait();
//获取到当前的数据库是否建立了,没有建立的话就先创建
142 if(is_db_init == false)
143 {
144 if(create_database() == 0)
145 {
146 //Success
147 is_db_init = true;
148 }
149 }
150
151 cmd = evData->mEvType;
152 msg = (iop_msg_t *)evData->mEvData;
//对产生的数据进行处理
154 switch (cmd) {
//处理的环节我们后面讨论
231}
所以可以看出先是创建了一个database,源码在dblayer.cpp
15/******************************************************************************
16 pkg_file_tbl pkg_tbl
17 |-----------------| |-----------------|
18 | pkg_name | | pkg_name |
19 | file_name | |-----------------|
20 |-----------------| | pkg_use_time |
21 | | | num_of_launches |
22 | file_use_ctr | |-----------------|
23 | file_time_stamp |
24 | file_size |
25 | mark_for_delete |
26 |-----------------|
27******************************************************************************/
所以就直接看table的结构吧。看字段名就应该知道是什么意义了,现在来看具体是如何使用这个database的
154 switch (cmd) {
155 case IOP_CMD_PERFLOCK_IOPREFETCH_START:
156 {
157 static bool is_in_recent_list = false;
158 char property[PROPERTY_VALUE_MAX];
159 int enable_prefetcher = 0;
160
161 property_get("enable_prefetch", property, "1");
162 enable_prefetcher = atoi(property);
163
164 if(!enable_prefetcher)
165 {
166 QLOGE("io prefetch is disabled");
167 break;
168 }
169 // if PID < 0 consider it as playback operation
170 if(msg->pid < 0)
171 {
172 int ittr = 0;
173 is_in_recent_list = false;
174 //Check app is in recent list
175 for(ittr = 0; ittr < IOP_NO_RECENT_APP; ittr++)
176 {
177 if(0 == strcmp(msg->pkg_name,recent_list[ittr]))
178 {
179 is_in_recent_list = true;
180 QLOGE("is_in_recent_list is TRUE");
181 break;
182 }
183 }
184 // IF Application is in recent list, return
185 if(true == is_in_recent_list)
186 {
187 QLOGE("io prefetch is deactivate");
188 break;
189 }
190
191 if(recent_list_cnt == IOP_NO_RECENT_APP)
192 recent_list_cnt = 0;
193
194 //Copy the package name to recent list
195 strlcpy(recent_list[recent_list_cnt],msg->pkg_name,PKG_LEN);
196 recent_list_cnt++;
197
198 stop_capture();
199 stop_playback();
//注意此处就是在playback
200 start_playback(msg->pkg_name);
201 }
202 // if PID > 0 then consider as capture operation
203 if(msg->pid > 0)
204 {
205 if(true == is_in_recent_list)
206 {
207 QLOGE("io prefetch Capture is deactivated ");
208 break;
209 }
210 stop_capture();
//关键函数,开始进行capture抓取
211 start_capture(msg->pid,msg->pkg_name,msg->code_path);
212 }
213
214 break;
215 }
216
217 case IOP_CMD_PERFLOCK_IOPREFETCH_STOP:
218 {
//关闭函数,停止capture
219 stop_capture();
220 break;
221 }
222
223 default:
224 QLOGE("Unknown command %d", cmd);
225 }
所以我们一起来看一下start_capture函数:
436int start_capture(pid_t pid, char *pkg_name, char *code_path) {
...
486 //TBD must remove comments
487 if(pthread_create(&capture_pthread_id, NULL, capture_thread, arg_bundle)) {
488 return -1;
489 }
490 QLOGI("start_capture EXIT");
491 return 0;
492}
恩,高通自己写了 to be done 一定要删除注释,然后还是留下了,哈哈!其实就是启动了一个capture_thread线程:
375void * capture_thread(void * arg) {
...
390 //Copy the package to same string to remove ':'
391 pkg_len = strlen(arg_bundle->pkg_name);
392 i = 0;
393 while(i < pkg_len && arg_bundle->pkg_name[i] != ':')
394 {
395 i++;
396 }
397 arg_bundle->pkg_name[i] = '\0';
398 strlcpy(list_pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN);
399
400 QLOGI("pkg_name = %s",arg_bundle->pkg_name);
401
402 if(polling_enable)
403 {
404 while (duration_counter < halt_counter) {
405 if (halt_thread) goto cleanup;
406 QLOGI("Getting snapshot %d\n", arg_bundle->pid);
//获取当前pid打开的文件描述符
407 get_snapshot(list_pkg_name,arg_bundle->pid);
408 duration_counter++;
409 usleep(read_fd_interval_ms * 1000);
410 }
411 }
//获取到代码位置
412 get_priv_code_files(arg_bundle->pkg_name);
//获取文件位置
413 get_priv_files(arg_bundle);
414 QLOGI("pkg_name = %s total_files = %d ",arg_bundle->pkg_name,total_files);
415
416 // Insert package into the table
417 strlcpy(pkg_info.pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN);
418 time(&pkg_info.last_time_launched);
//更新在database中
419 update_pkg_details(pkg_info);
420 update_file_details(arg_bundle->pkg_name, file_list, total_files);
421 delete_mark_files();
422cleanup:
423 for(i = 0; i < total_files; i++) {
424 QLOGE("%d. Final entry : %s %s %d %d\n",i,arg_bundle->pkg_name,file_list[i]->file_name
425 , file_list[i]->file_time_stamp, file_list[i]->filesize);
426 free(file_list[i]);
427 }
428 ATRACE_END();
429
430 free(arg_bundle->pkg_name);
431 free(arg_bundle);
432 QLOGI("Exit capture_thread");
433 return NULL;
434}
首先来看get_snapshot函数,就是获取当前pid打开的所有文件描述符
148int get_snapshot(char *pkg_name, pid_t pid)
149{
164
165 snprintf(dirname,PATH_MAX,"/proc/%d/fd", pid);
166 QLOGI("Dirname = %s MAX_PATH = %d\n", dirname, PATH_MAX);
167 dp = opendir(dirname);
168 QLOGI("get_snapshot Enter dp = %p",dp);
169 QLOGI("dp = %p",dp);
170 if (dp != NULL) {
171 int i = 0;
172 while ((ep = readdir(dp)))
173 {
...
207
208 if (S_ISREG(sb.st_mode)) {
//将linke的文件存放到database中
209 find_insert_into_list(pkg_name, linkname, sb.st_size, file_capture_time);
210 }
211 }
212 QLOGI("get_snapshot Exit dp = %p",dp);
213 closedir(dp);
214 }
215 ATRACE_END();
216 return 0;
217}
这里可以看到
polaris:/ # ps -A | grep mobileq
u0_a153 9824 643 1821656 172712 SyS_epoll_wait 0 S com.tencent.mobileqqi
u0_a153 9848 643 1796008 123640 SyS_epoll_wait 0 S com.tencent.mobileqqi:MSF
polaris:/ #
然后打开proc中的文件描述符
polaris:/proc/9824/fd # ls -al
total 0
dr-x------ 2 u0_a153 u0_a153 0 2018-08-21 14:24 .
dr-xr-xr-x 9 u0_a153 u0_a153 0 2018-08-21 14:24 ..
lrwx------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 0 -> /dev/null
lrwx------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 1 -> /dev/null
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 10 -> /system/framework/qtiNetworkLib.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 11 -> /system/framework/com.nxp.nfc.nq.jar
lrwx------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 12 -> anon_inode:[eventfd]
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 13 -> /system/framework/qcom.fmradio.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 14 -> /system/framework/com.qualcomm.qti.camera.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 15 -> /system/framework/QPerformance.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 16 -> /system/framework/UxPerformance.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 17 -> /system/framework/core-oj.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 18 -> /system/framework/core-libart.jar
lr-x------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 19 -> /system/framework/conscrypt.jar
lrwx------ 1 u0_a153 u0_a153 64 2018-08-21 14:25 2 -> /dev/null
然后就是插入到数据库中了:
76static int find_insert_into_list(char *pkg_name,char *filename, long long size, long file_capture_time) {
...
99
100 if (index == total_files) {
101 file_list[index] = (file_details *) malloc (sizeof(file_details));
102 if(NULL == file_list[index])
103 {
104 QLOGE("Fail to allocate memory");
105 return -1;
106 }
107 strlcpy(file_list[index]->file_name, filename,FILE_NAME_LEN);
108 file_list[index]->file_time_stamp = file_capture_time;
109 file_list[index]->filesize = size;
110 total_files++;
111 }
112 return 0;
113}
先是存在了内存中了。在获取到snapshot后,就是get_priv_code_files和get_priv_files了
这两者都是打开不同的文件然后缓存起来
310void get_priv_files(capture_thread_arg * arg_bundle)
311{
312 char f_name[FILE_NAME_LEN];
313 DIR *dp;
314 char trace_buf[1024];
315 snprintf(trace_buf, sizeof(trace_buf), "Get_prive_file");
316 ATRACE_BEGIN(trace_buf);
317
318 snprintf(f_name,FILE_NAME_LEN,"/data/user/0/%s",arg_bundle->pkg_name);
319 QLOGI("Dir = %s",f_name);
320 dp = opendir(f_name);
321 QLOGI("First dp = %p",dp);
322 if (dp != NULL) {
323 capture_pkg_file(arg_bundle->pkg_name, dp,f_name,2);
324 closedir(dp);
325 }
326 else
327 {
328 QLOGI("dp is NULL");
329 }
330
331
332 snprintf(f_name,FILE_NAME_LEN,"/data/data/%s",arg_bundle->pkg_name);
333 QLOGI("Dir = %s",f_name);
334 dp = opendir(f_name);
335 QLOGI("First dp = %p",dp);
336
337 if (dp != NULL) {
338 capture_pkg_file(arg_bundle->pkg_name, dp,f_name, 4);
339 closedir(dp);
340 }
341 else
342 {
343 QLOGI("dp is NULL");
344 }
345 ATRACE_END();
346}
347
348static void get_priv_code_files(char *pkg_name) {
349 char f_name[FILE_NAME_LEN];
350 DIR *dp;
351
352 //oat first
353 snprintf(f_name,FILE_NAME_LEN,"%s/oat",list_code_path);
354 QLOGI("Dir = %s",f_name);
355 dp = opendir(f_name);
356 QLOGI("First dp = %p",dp);
357 if (dp != NULL) {
358 capture_pkg_file(pkg_name, dp, f_name, -1);
359 closedir(dp);
360 } else {
361 QLOGI("dp is NULL");
362 }
363
364 //then scan apk file
365 dp = opendir(list_code_path);
366 QLOGI("First dp = %p",dp);
367 if (dp != NULL) {
368 capture_pkg_file(pkg_name, dp, list_code_path, 1);
369 closedir(dp);
370 } else {
371 QLOGI("dp is NULL");
372 }
373}
关键函数其实就是capture_pkg_file了,关键的方法也是find_insert_into_list方法,就是插入到内存中,这里就不赘述了。
后面就是update_pkg_detail和update_file_detail了,这些都是数据库操作
522int update_pkg_details(pkg_details pkg_info)
523{
530 if(NULL == db_conn)
531 open_db();
532//可以看到,就是通过sqlite数据库进行更新的
533 if(is_file_present)
534 {
535 //Update
536 snprintf(query_str,sizeof(query_str),IO_PREFETCHER_QUERY_UPDATE_PKG_DETAILS
537 ,pkg_info.last_time_launched
538 ,pkg_info.pkg_name);
539 QLOGI("\nQuery = %s \n",query_str);
540 iop_query_exec(query_str);
541 }
542 else
543 {
544 //Insert
545 snprintf(query_str,sizeof(query_str),IO_PREFETCHER_QUERY_INSERT_PKG
546 ,pkg_info.pkg_name
547 ,pkg_info.last_time_launched
548 );
549
550 QLOGI("\nQuery = %s \n",query_str);
551 iop_query_exec(query_str);
552 }
553 return 0;
554}
至此,可以看到,在打开了iop之后,在打开应用的时候,会查看该pid打开的文件描述符列表,将一些需要打开的文件先缓存到数据库中。那么缓存到数据库中,该怎么使用呢。