-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathprint.html
More file actions
4735 lines (4554 loc) · 320 KB
/
Copy pathprint.html
File metadata and controls
4735 lines (4554 loc) · 320 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title></title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="1.illustrate.html"><strong aria-hidden="true">1.</strong> 项目简介</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="1.1.Design.html"><strong aria-hidden="true">1.1.</strong> 方案设计</a></li><li class="chapter-item expanded "><a href="1.2.Plan.html"><strong aria-hidden="true">1.2.</strong> 开发计划与进展</a></li><li class="chapter-item expanded "><a href="1.3.explain.html"><strong aria-hidden="true">1.3.</strong> 项目分工与目录说明</a></li></ol></li><li class="chapter-item expanded "><a href="2.Bitcomet_Design.html"><strong aria-hidden="true">2.</strong> Bitcomet设计文档</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="2.1.Bitcomet方案概述.html"><strong aria-hidden="true">2.1.</strong> Bitcomet方案分析</a></li><li class="chapter-item expanded "><a href="2.2.Bitcomet方案设计与主要工作.html"><strong aria-hidden="true">2.2.</strong> Bitcomet方案设计与主要工作</a></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.</strong> Bitcomet实现</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="3.1安卓图形架构中的HAL模块.html"><strong aria-hidden="true">3.1.</strong> Bitcomet图形架构HAL模块实现</a></li><li class="chapter-item expanded "><a href="3.2关于Anbox中图形渲染分析与总结.html"><strong aria-hidden="true">3.2.</strong> Bitcomet图形渲染实现</a></li><li class="chapter-item expanded "><a href="3.3Anbox实现分析-容器管理服务.html"><strong aria-hidden="true">3.3.</strong> Anbox分析资料:Anbox容器管理服务</a></li><li class="chapter-item expanded "><a href="3.4.Anbox实现分析-IO模型.html"><strong aria-hidden="true">3.4.</strong> Anbox分析资料:IO 模型</a></li><li class="chapter-item expanded "><a href="3.5.Anbox实现分析-会话管理器与容器管理器的通信.html"><strong aria-hidden="true">3.5.</strong> Anbox分析资料:会话管理器与容器管理器的通信</a></li></ol></li><li class="chapter-item expanded "><a href="4.Bitcomet实验测试.html"><strong aria-hidden="true">4.</strong> Bitcomet实验测试</a></li><li class="chapter-item expanded "><a href="5.run.html"><strong aria-hidden="true">5.</strong> 使用教程</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">6.</strong> 项目记录</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="6.1.record.html"><strong aria-hidden="true">6.1.</strong> 问题记录与总结</a></li><li class="chapter-item expanded "><a href="6.2.experience.html"><strong aria-hidden="true">6.2.</strong> 比赛收获</a></li></ol></li><li class="chapter-item expanded "><a href="7.Bitcomet未来展望与参考文献.html"><strong aria-hidden="true">7.</strong> Bitcomet未来展望与参考文献</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="proj156-一种基于linux系统运行android-11的解决方案"><a class="header" href="#proj156-一种基于linux系统运行android-11的解决方案">proj156 一种基于Linux系统运行Android 11的解决方案</a></h1>
<p>高校队伍:广东东软学院 Bitcomet队</p>
<p>项目成员:邹明燊、田梓汎、张阳彬</p>
<p>指导教师:刘翠莲、罗泉</p>
<h2 id="1-项目简介"><a class="header" href="#1-项目简介">1. 项目简介</a></h2>
<h3 id="11-目标描述"><a class="header" href="#11-目标描述">1.1 目标描述</a></h3>
<p> 本项目目标在现有Anbox实现Android 7在Linux平台正常运行的基础上将其进一步完善,实现Android11在Linux(Ubuntu20.04)平台上的运行,还可根据用户需求下载并正常运行第三方软件。Anbox(Android in a box)是一个基于容器的方法,可以在普通的 GNU/Linux 系统上启动完整的 Android 系统。它将Android应用放进密封的容器中,无需直接访问硬件或数据,所有硬件或数据的访问都是通过与主机上的Anbox守护进程进行的。由于Anbox 直接跑在硬件上,没有软件模拟层,无需虚拟化硬件即可运行 Android,因此可以无缝桥接硬件加速功能。</p>
<p> 本项目主要基于Anbox来参考实现,旨在完成以下四个目标:</p>
<ul>
<li>
<p><strong>目标1:<strong>完成针对Android11的</strong>SDcard的文件系统挂载机制</strong>、<strong>SELinux安全机制、非特权模式下运行处理</strong>对应在Docker容器环境内的处理。</p>
</li>
<li>
<p><strong>目标2:<strong>配置</strong>安卓项目工程</strong>,实现并配置好各个需要的系统模块和AIDL、HIDL、HAL模块,搭建初步调试与测试的Demo1版本,使用adb配合Scrcpy对内部Android远程访问以方便有个初步预期,并且方便本项目有个初步测试的环境。</p>
</li>
<li>
<p><strong>目标3:<strong>完成</strong>Anbox通信部分</strong>实现向Android 11的移植,使Android能与Anbox外部实现跨系统环境通信。</p>
</li>
<li>
<p><strong>目标4:<strong>完成</strong>Anbox其余部分的各个模块移植</strong>,使Android的三个基础部分OpenGL、HWC图形输出、键鼠输入能正常工作。</p>
<p> 目前,我们的赛题目标完成度如下:</p>
<center>表1.1 赛题完成度</center>
<div class="table-wrapper"><table><thead><tr><th style="text-align: center">目标编号</th><th style="text-align: center">基本完成情况</th><th>额外说明</th></tr></thead><tbody>
<tr><td style="text-align: center">1</td><td style="text-align: center">基本完成(≈85%)</td><td>1. 与SDcard以及SELinux组件相关的Android 11的基本功能、SDcard文件管理、安装第三方软件、闹铃与联系人等基础应用测试均通过。<br>2. 非特权模式仅做了部分处理,未进行测试。</td></tr>
<tr><td style="text-align: center">2</td><td style="text-align: center">基本完成(≈90%)</td><td>1. 搭建好相关容器,进行相关基础测试并通过。<br/>2. 目前网络部分由于在Docker特权模式下运行,可能会有Bug。</td></tr>
<tr><td style="text-align: center">3</td><td style="text-align: center">初步完成(≈80%)</td><td>1. 完成跨系统环境通信,安卓中Anbox的相关模块可以使用QEMU_PIPE(实际容器中是没有QEMU设备,Anbox实际用了Unix Domain Socket代替,但其通信的相关通道仍然叫做QEMU PIPE)进行通信或者使用Anbox实现的RPC调用。<br/>2. Anbox的OpenGL ES的emulation库实现初步测试能通过该通信创建QEMU_PIPE连接,实现RPC调用获取到主机侧支持的OpenGL信息。<br/>3. 由于各个部分的模块暂未完全移植好,还需要全部移植才能测试所有功能。</td></tr>
<tr><td style="text-align: center">4</td><td style="text-align: center">初步测试(≈60%)</td><td>1. 已经把相应的Anbox的各个模块的移植到Android中,其中GPS与Audio模块使用了谷歌给Goldfish的实现。<br/>2.OpenGL ES实现和HWC实现在Android 11下无法测试,由于Android 11的相关变动,导致输出画面调用了Gralloc的PostFB去输出,而在Anbox中是未做这部分RPC调用的,输出画面失败。<br/>3.把相关方案在Android 10上进行测试,测试可以输出画面,但卡在安卓Launcher第一屏画面。</td></tr>
<tr><td style="text-align: center">总计</td><td style="text-align: center">≈80%</td><td>1. 本项目自立项以来经历了三个月的时间进行研究与开发,实现方案的过程中遇到困难重重,主要遇到的阻力在系统庞大、相关实现的资料缺失方面。<br />2. 目前还有部分的工作需要测试和完成。由于Android 11在图形和音频方面变动较大,即使目前进行了相关模块的移植,但是还需要在图形模块、音频模块、输入模块方面进行各类测试和调试工作。<br />3. 项目将放在Github进行公开,把截至目前学习的成果公开,希望后续有更多的参与者参与进来进行开发完善。</td></tr>
</tbody></table>
</div></li>
</ul>
<h3 id="12-比赛题目分析和相关资料调研"><a class="header" href="#12-比赛题目分析和相关资料调研">1.2 比赛题目分析和相关资料调研</a></h3>
<h4 id="121-题目分析"><a class="header" href="#121-题目分析">1.2.1 题目分析</a></h4>
<p> (1). 题目旨在实现在Linux运行Android11的解决方案,在Linux上基于容器技术隔离一个环境运行Android,其运行机制是直接基于宿主机内核下运行Android,其运行效率非常高效,对系统资源占用和运算资源的损耗都极低。<br />
(2). 在所有实现方案中,选定了基于Anbox的实现方式。由于现今用户使用的软件硬件环境不统一,Mesa 3D等实现方式对图形处理硬件的支持有限,用户的图形环境不一定是Wayland而是X11偏多。同时考虑到可能未来需要在各类国产主机运行架构不统一(Arm、x86、MIPS、LoongArch等架构),<strong>Anbox对于不同系统不同硬件的兼容性优势使得其是现有方案中最合适的</strong>。<br />
(3). 在实现过程中,考虑到Anbox的LXC也是比较老的容器架构,同时其集成LXC的操作不利于后续容器核心升级、配置调整、在线镜像更新和镜像快速部署等,所以使用Docker来支撑其Android的运行环境,对比最初的LXC方案来说拥有更高的灵活性和可靠性。<br />
(4). 项目计划是初期构建调整好Android高版本核心跑起一个基于Docker容器实现的Demo,后续逐步移植剩余Anbox组件到Android11上。</p>
<h4 id="122-资料调研"><a class="header" href="#122-资料调研">1.2.2 资料调研</a></h4>
<h5 id="现有方案"><a class="header" href="#现有方案">现有方案</a></h5>
<p> (1). Anbox:基于LXC容器运行Android 7,利用QEMU pipe实现Android与Anbox上层Linux软件用户接口进行通信。其通信中输入的数据包括:接收传入的传感器数据,接收用户鼠标点击和触摸事件,接收APP启动和窗口调整数据、Anbox上层软件屏幕缓冲区的可选回传等;通信中输出的数据则包括:传输Android上2D图形画面和OpenGL的渲染指令到Linux环境中渲染、传输音频输出数据。Anbox运行Android的机制的实现方式就是通过将Android内必要的输入输出交给外部Linux环境处理,<strong>其实现接口均通过软件实现,这样对于兼容性容易出现问题的OpenGL加速部分尤为友好</strong>。但是也正因为如此,虽然保证了其兼容性,但是<strong>其支持的OpenGL库选择就非常有限</strong>,目前仅支持OpenGL ESv2,性能和稳定性会较为一般。总结其优点就是对于各类操作系统兼容性好,外部有D-Bus和OpenGL环境基本就能正常运行,缺点是通过QEMU pipe传输到外部渲染的方式,其模拟的库只支持OpenGL ESv2,<strong>稳定性一般,性能也比较逊色</strong>。</p>
<center><img src=images/Anbox.png></center>
<center>图1.1 Anbox实现架构图</center>
<p> (2). Waydroid: 基于Anbox繁衍而来的实现方案,Anbox除了2018年后面稍微更新了下支持Snap和Anbox Cloud外,实现的方案已经多年未进行更新,核心无较大变化,所支持的最新Android系统仍然是2016年11月24日发布的Android 7.1.1。因此Waydroid应运而生,其前身是基于Anbox的中期版本,基于重建脚本、更新的LXC3、Mesa 3D、最新的Android8-11版本、去掉Anbox代码,后续演化后改名Waydroid。Waydroid更加新颖和完善,其优势在于最新的Android、直接通过Mesa 3D驱动显卡、支持Wayland环境;缺点是<strong>只支持Wayland,Mesa 3D作为开源驱动,只支持部分显卡</strong>。</p>
<center><img src=images/Docker_run_Android_with_Mesa3d.png></center>
<center>图1.2 Docker运行Android+Mesa3D架构图</center>
<p> (3). 其他方案:xDroid、Kydroid、KMRE等第三方闭源方案:xDroid和Kydroid也是类似Anbox的方式,其核心原理仍然是通过容器技术让Android直接运行在Linux上,以Linux原生程序运行,由于其闭源,能够了解的公开信息不多。KMRE从其论文<sup class="footnote-reference"><a href="#1">1</a></sup>来看是类似Waydroid的Mesa3D加速的方案,其性能损耗低,但其一大特色优点在于其对接了现在国产系统常见的Xorg环境下的桌面环境,同时做好支持触摸、优化各类输入输出、传感器等的实现,对比以上开源项目,KMRE不但只实现了基本需求,而且在人性化交互等方面,易用性更高。</p>
<center>表1.2 方案对比</center>
<div class="table-wrapper"><table><thead><tr><th>现有方案</th><th>优点</th><th>缺点</th></tr></thead><tbody>
<tr><td>Anbox</td><td>对各类环境兼容性好、原生运行速度快</td><td>稳定性一般、其所支持的Android版本比较老旧、且Android内只支持OpenGL ESv2,图形化API老旧</td></tr>
<tr><td>waydroid</td><td>最新Android、支持Wayland、直接驱动显卡</td><td>只支持Wayland图形环境、Mesa 3D开源驱动只支持部分显卡,兼容性一般</td></tr>
<tr><td>其他闭源方案</td><td>闭源或商用的项目在用户交互等方面做到了更优,易用性更高</td><td>闭源,能查到公开的信息不多</td></tr>
</tbody></table>
</div>
<h5 id="项目需求和状况"><a class="header" href="#项目需求和状况">项目需求和状况</a></h5>
<p> (1). 项目现状:</p>
<p> 由于西方加速技术封锁的态势,我们必须迫切的找到相应的替代方案来给国内用户有选择的权利。虽然国产Linux系统目前在飞速发展,但其应用生态紧缺,甚至一个输入法、Linux版的QQ这种基础性应用都Bug百出,使用体验极其糟糕,导致用户普及度始终不能有明显的上涨,用户也不情愿使用国产Linux作为替代系统。</p>
<p> 因此我们需要一些<strong>中期可替代性方案</strong>,Linux上部署安卓这一个想法应运而生,我们可以利用安卓作为一个暂时的方案来承载现在的业务需求例如办公,视频通话等。安卓在2020年的中国移动操作系统市场份额占比达75.98%,相当于每4个使用智能手机的人中就有3名安卓用户。因其用户整体基数大,所以针对安卓应用开发者非常多,面对安卓延申出来的各种业务层出不穷,整个生态对于国产Linux有很大的优势。</p>
<p> 我们可以直接<strong>借助这个优势</strong>,在Linux上运行安卓系统,完成国产Linux跟安卓生态的整合,进而发展国产Linux,并对其产生助力。而同期也比较少同类型的厂商成功实现在GNU/Linux系统中运行着整个Android系统,市场上有空缺的需求等待填补,我们的项目就是要针对这个市场的空缺,成功实现在Linux系统中运行Android系统而不是单纯的模拟,为我们之后国产Linux和国内安卓的生态整合打下坚实的基础。</p>
<p> (2). 项目需求:</p>
<p> 单纯的Linux<strong>无法</strong>成功运行安卓系统中的各种应用程序,因为安卓在Linux层面上主要增加了ART虚拟机和其Application Framework框架,以及其他细枝末节的修改和优化。我们的项目需求就是需要通过一个安卓<strong>中间层</strong>在Linux上提供安卓运行环境,使安卓当中的应用程序也可以运行在我们所配置的容器当中。但是却不是以模拟器的方式,而是使用当前Linux内核基于<strong>容器技术隔离</strong>来运行安卓系统,不需要大量的软件模拟,又保证了安全性。</p>
<p> 我们选择了这种方案,对各类硬件资源可以被无缝桥接,让性能的效率损失降到比较低的水准,完全可以满足我们日常需求。该方案补充了当前国产Linux应用生态,可以实现国产Linux操作系统应用生态不足的前提下需要快速扩充用户数量的愿景,让国产Linux操作系统走入平民百姓家。</p>
<h2 id="2-参考资料"><a class="header" href="#2-参考资料">2. 参考资料</a></h2>
<ol>
<li>KMRE: An Efficient and Compatible Runtime to Execute Android Application on Linux System, [Date of Conference: 10-13 December 2021]. 10.1109/ICCC54389.2021.9674681</li>
<li>Anbox(所有项目仓库): https://github.com/anbox</li>
<li>安卓AOSP源码获取来自清华大学开源软件镜像站点</li>
</ol>
<div style="break-before: page; page-break-before: always;"></div><h2 id="11-方案设计"><a class="header" href="#11-方案设计">1.1 方案设计</a></h2>
<center><img src=images/anbox11.png></center>
<center>图1.3 Linux系统运行Android 11的解决方案架构图</center>
<p> 此项目基于原有Anbox方案进行<strong>更新及改进</strong>。我们<strong>改进</strong>使用基于Docker容器技术提供隔离的、与Linux并行运行的安卓环境,此种改进是对原有LXC容器的<strong>更优替代</strong>。但是该改进需要重新适配与调试,需要大量移植与测试的工作量才能保证维持原有稳定性,并更好地利用更多的新特性开发新的功能。除此之外<strong>更新</strong>新的Android 11替代原有Android 7,此过程需要重新移植原有Anbox的各个组件适配新安卓上的HIDL接口。同时需要修改Android底层,经过大量的移植和测试的工作,让其能正常运行在Docker容器中。此项目利用Anbox部分现成的模块,在Linux下上层APP实现与安卓底层对接输入输出和OpenGL ES的渲染等实现。这些设计目标在于使用Docker<strong>替代</strong>原有Anbox使用的较旧的LXC容器技术,并<strong>更新</strong>其Android 7到Android 11,其中技术革新点如下:<br />
(1). 使用更新的Android 11替代原有老旧的Android 7。<br />
(2). Android 11中的HAL层使用了HIDL接口这一新的Treble的架构,更加规范及更高的可移植性。<br />
(3). 使用生态丰富、快速部署、可移植、更加可控的Docker作为底层容器运行环境替代原有集成LXC这一高耦合、低可移植性的实现方式。<br />
(4). 活用成熟的Anbox部分对多平台高兼容性的实现方式,以应对目前国产系统多种硬件平台和系统环境的问题。</p>
<p>在以上的设计方案下,我们针对其各个部分进行详细设计,详细方案见“<strong>Bitcomet设计文档</strong>”。</p>
<p> (1). 底层部分:Docker下的安卓11容器设计:</p>
<center><img src=images/design/Anbox11_in_Docker.png></center>
<center>图1.4 Bitcomet实现底层部分设计框架图</center>
<p> • <strong>高可移植设计</strong>:使用了Docker便集成了Docker相关的优点,Docker相比原有LXC有可移植性、版本控制、回滚、快速部署等优点。同时原有LXC容器与Anbox进行了高度集成,在目前多平台和可移植性考量下,原有Anbox的系统镜像与各个配置选项都不能方便地进行更改,这将会导致我们适配到国产系统中遇到重重困难。<br />
• <strong>快速迭代更新</strong>:使用Docker提供底层容器运行环境,利用其快速部署的优点,可以使Android 11更加灵活地运行在目标平台。<br />
• <strong>高效的数据管理</strong>:利用Docker的版本控制和回滚特性,设计恢复出厂设置并清空用户数据的功能,并为以后Android镜像更新时能够快速部署,及时把新版本推送做准备。<br />
• <strong>较高系统安全性</strong>:把安卓放在密封的容器当中,其外部访问数据都通过QEMU pipe交给上层Anbox前端应用接管,其应用也只有基本的输入输出功能,安全性较高。<br />
• <strong>极低的运行损耗</strong>:通过容器直接基于现有系统Linux内核运行,并基于Linux内核特性进行容器隔离,这一方案提高了稳定性并降低了运行损耗。<br />
(2). 中间层:安卓中运行qemud提供QEMU pipe这一高速管道与上层通信:</p>
<center><img src=images/design/Anbox11_IO_Model.png></center>
<center>图1.5 Bitcomet中间层部分设计原理图</center>
<p> • <strong>成熟的通信方案</strong>:这一通信方案基于成熟的Anbox的通信这一部分的功能实现,其上层Session Manager提供接口,由安卓和Anbox两端利用相应API把接口打开进行通信。其实现方案最早可以追溯到2013年的谷歌Goldfish的Android模拟器实现并且沿用至今,实现了Android与Linux的之间通信的兼容性与健壮性并存的实现方式。<br />
• <strong>便捷的交互方式</strong>:本方案在安卓与Linux之间的通信方案设计基于QEMU pipe这一高速通道,直接在Android与Linux之间打通一个灵活的通信渠道。本方案不用考虑需要的具体通信实现,直接在安卓HAL层服务和上层Anbox前端应用中打开相应通道进行安卓与Linux对接的数据通信。</p>
<p> (3). 上层部分:Anbox通过QEMU pipe与底层通信,接收安卓的渲染信息渲染以及音频在上层APP输出,把上层前端APP的输入和状态传递给安卓。</p>
<center><img src=images/design/Anbox11_in_Linux.png></center>
<center>图1.6 Bitcomet上层部分设计原理图</center>
<p> • <strong>兼容性高</strong>:主要体现在Linux下对Anbox的兼容性,这一方案的设计把安卓需要与Linux交互和获取的数据都通过QEMU pipe传输给Linux处理,上层Anbox前端不依赖硬件。尤其是渲染也是调用系统现有OpenGL库实现,兼容性相比虚拟机来说更高。<br />
• <strong>易用性高</strong>:通过上层Anbox应用接管底层安卓的输入输出等部分,相当于把安卓应用直接映射给了Linux应用。这一操作安卓应用非常接近使用Linux应用的方式,极大的提高了其易用性,操作起来也更加简单。</p>
<div style="break-before: page; page-break-before: always;"></div><h2 id="12-开发计划与进展"><a class="header" href="#12-开发计划与进展">1.2 开发计划与进展</a></h2>
<h3 id="121-开发计划"><a class="header" href="#121-开发计划">1.2.1 开发计划</a></h3>
<h4 id="1-开发计划表"><a class="header" href="#1-开发计划表">(1) 开发计划表</a></h4>
<center>表1.3 开发计划表</center>
<div class="table-wrapper"><table><thead><tr><th>时间节点</th><th>内容</th></tr></thead><tbody>
<tr><td>5月1日-5月31日</td><td>完成安卓11运行在Docker上的核心Demo</td></tr>
<tr><td>6月1日-6月15日</td><td>移植Anbox各个组件到安卓11,初步先移植Anbox的OpenGL和输入部分</td></tr>
<tr><td>6月16日-6月30日</td><td>完成Anbox的通信组件移植</td></tr>
<tr><td>7月1日-7月31日</td><td>完成剩余Anbox组件移植并测试</td></tr>
<tr><td>8月1日-8月15日</td><td>完成最终项目提交、整理项目、准备答辩内容</td></tr>
</tbody></table>
</div>
<h3 id="122-比赛过程中的重要进展"><a class="header" href="#122-比赛过程中的重要进展">1.2.2 比赛过程中的重要进展</a></h3>
<h4 id="1-比赛进展表"><a class="header" href="#1-比赛进展表">(1) 比赛进展表</a></h4>
<center>表1.4 比赛进展表</center>
<div class="table-wrapper"><table><thead><tr><th>时间节点</th><th>负责人</th><th>完成工作</th><th>里程碑</th></tr></thead><tbody>
<tr><td>3月7日-3月22日</td><td>田梓汎</td><td>Android 11内核在docker上运行的实现</td><td></td></tr>
<tr><td>3月23日-4月5日</td><td>张阳彬</td><td>Android 11组件的改造和规范</td><td></td></tr>
<tr><td>4月6日-5月4日</td><td>邹明燊</td><td>完成Android工程文件编写和ASOP编译</td><td></td></tr>
<tr><td>5月5日-5月14日</td><td>田梓汎、张阳彬</td><td>完成Android基础核心功能测试</td><td></td></tr>
<tr><td>5月15日-5月20日</td><td>邹明燊</td><td>完成Android基础核心功能的运行演示</td><td>核心对Docker适配成功完成于5月20日</td></tr>
<tr><td>5月21日-6月5日</td><td>全体队员</td><td>完成项目文档编写和整理</td><td></td></tr>
<tr><td>6月20日-7月10日</td><td>邹明燊、张阳彬</td><td>完成Anbox中间层的通信组件移植</td><td>通信组件成功完成于7月10日并通过基础测试</td></tr>
<tr><td>7月1日-7月15日</td><td>张阳彬、田梓汎</td><td>完成对Anbox的图形及渲染部分的分析文档</td><td></td></tr>
<tr><td>7月10日-7月25日</td><td>邹明燊</td><td>完成Anbox中上层各组件移植</td><td></td></tr>
<tr><td>7月25日-8月2日</td><td>邹明燊</td><td>完成OpenGL部分渲染的初步RPC调用测试</td><td>渲染组件成功完成于8月2日</td></tr>
<tr><td>8月2月-8月15日</td><td>全体成员</td><td>完成项目文档编写和整理</td><td></td></tr>
</tbody></table>
</div>
<h3 id="123-项目测试"><a class="header" href="#123-项目测试">1.2.3 项目测试</a></h3>
<p> 测试思路主要分为三步:第一步检测Bitcomet能否成功启动容器内的Android系统,主要检测其Android系统开启是否正常,是否成功进入系统界面。如果第一步成功,则基础系统运行测试正常,可以保证Bitcomet内Android系统的基本运作,遂进入第二步测试。第二步测试主要针对Bitcomet容器内的Android系统主要功能是否正常,这一步的检测将包括三个部分分别针对Android系统的三大主要功能进行测试,其中包括有系统信息测试,基础功能测试以及基础应用测试。通过第二部测试的目的是检测Bitcomet内启动成功后Android系统的整体功能完善性。最后是第三步的测试。上两部分的测试主要是检测系统的基本运行以及功能,这一部分测试主要检测在Bitcomet中运行Android的效率,会通过统一变量同时对比Bitcomet与市面上成熟的Android模拟器的性能差距,从而展现Bitcomet方案不可忽视的优势。</p>
<h5 id="测试环境"><a class="header" href="#测试环境">测试环境</a></h5>
<p> 本次测试环境硬件参数将统一为下表状态</p>
<center>表1.5 测试环境</center>
<div class="table-wrapper"><table><thead><tr><th>部件</th><th>参数</th></tr></thead><tbody>
<tr><td>系统</td><td>Ubuntu20.04</td></tr>
<tr><td>CPU</td><td>英特尔 i5-8300H@2.3Ghz</td></tr>
<tr><td>内存</td><td>DDR4 16GB (2400MHz)</td></tr>
<tr><td>硬盘</td><td>主硬盘 128G SSD 从硬盘 2TB HDD</td></tr>
<tr><td>显卡</td><td>英伟达 GTX1050&英特尔UHD Graphics 630</td></tr>
</tbody></table>
</div>
<h4 id="1-基础系统运行测试简介以及结果"><a class="header" href="#1-基础系统运行测试简介以及结果">(1) 基础系统运行测试简介以及结果</a></h4>
<p> 这一步主要通过安装环境→加载内核和挂载文件系统→启动Bitcomet并连接容器内的Android来查看其运行情况。<br />
在根据测试步骤,下载测试镜像,安装环境并加载启动Bitcomet所需模块后,Bitcomet可成功启动,通过相关命令可以进入Android shell模式,QtScrcpy投屏软件可以连接Bitcomet并显示Android界面。系统成功启动,运行正常。</p>
<h4 id="2-系统功能测试简介以及结果"><a class="header" href="#2-系统功能测试简介以及结果">(2) 系统功能测试简介以及结果</a></h4>
<p>这一步测试分为三个部分进行,以下为各个部分的测试简介:<br />
① 系统信息测试,主要测试系统设备名。<br />
② 基础功能测试,主要测试各基础主要功能例如默认语言,WIFI等。<br />
③ 基础应用测试,主要测试系统默认应用运行情况以及第三方应用运行情况。</p>
<p> 测试结果主要如下:</p>
<center>表1.6 系统功能测试结果</center>
<div class="table-wrapper"><table><thead><tr><th style="text-align: left">成功</th><th>失败</th></tr></thead><tbody>
<tr><td style="text-align: left">系统信息和基础功能</td><td>输入(远程)</td></tr>
<tr><td style="text-align: left">通讯录、闹铃、浏览器等基础应用以及第三方应用</td><td>音频</td></tr>
<tr><td style="text-align: left">图形渲染(GPU软件渲染)</td><td>蓝牙WIFI</td></tr>
</tbody></table>
</div>
<h4 id="3-性能对比测试简介以及结果"><a class="header" href="#3-性能对比测试简介以及结果">(3) 性能对比测试简介以及结果</a></h4>
<p> 性能对比测试环节:采用Genymotion模拟器来作为对比对象,同时会进行CPU性能对比测试以及内存开销对比测试。CPU性能测试主要使用Android平台主流的基础测试应用Geekbench以及安兔兔AI。Geekbench主要针对于CPU的浮点运算和整数运算部分给出性能量化指标,而安兔兔AI则主要针对CPU中的AI运算部分给出性能量化指标。内存开销测试我们选择各方案仅运行一个Android系统,查看整体内存占用,测出内存开销。</p>
<p> CPU测试部分:在Geekbench测试中,Bitcomet成绩为单核4437分,多核14398分,Genymotion模拟器对照组单核4251分,多核12626分。相比于Genymotion模拟器,Bitcomet单核领先约4.3%,多核领先约14%。性能提升相当于当今移动端旗舰级芯片高通骁龙865和高通骁龙888的性能差距(性能对比数据来自www.socpk.com) ,也就是芯片厂商用一年时间更迭优化出的性能。而在安兔兔AI测试中,Genymotion模拟器得分为48231,Bitcomet得分为58840,Bitcomet更是取得了约22%的显著优势。</p>
<p> 内存测试部分:分别测试仅打开Genymotion启动Android系统和仅打开Bitcomet启动Android系统,不运行任何其他应用程序,记录其内存的开销情况,Genymotion模拟器占用整个系统约3.8GiB的内存空间,而Bitcomet占用仅为2.8GiB。占用内存大小仅为Genymotion模拟器的3/4。</p>
<h4 id="4-总结"><a class="header" href="#4-总结">(4) 总结</a></h4>
<p> 在系统运行测试,系统功能测试部分,Bitcomet已成功在Ubuntu20.04下运行。除部分功能未完善外,通讯录,闹铃,浏览器等基础应用及系统基础信息均可正常使用与显示,也可根据需求安装第三方软件。</p>
<p> 通过Bitcomet与Genymotion在CPU性能测试与内存开销测试的情况对比可知,Bitcomet无论是在CPU利用效率还是内存开销上都优于Genymotion。在虚拟化技术成熟的今天,14%性能提升意味着每一百台计算机平台可以少买12台计算机。每百万可以省下12万,又或是在能耗方面做出改进,我们可以限制机器的运行功耗,降至模拟器同样的性能,但是省下更多的电。与此同时,Bitcomet还只是一个“半成品”,其潜力之大可想而知。</p>
<p> 演示视频链接: https://pan.baidu.com/s/1FLokWbiU3WNq_i5bhbj7sA?pwd=ew23 提取码: ew23</p>
<div style="break-before: page; page-break-before: always;"></div><h2 id="13-项目总结与目录说明"><a class="header" href="#13-项目总结与目录说明">1.3 项目总结与目录说明</a></h2>
<h3 id="131-分工和协作"><a class="header" href="#131-分工和协作">1.3.1 分工和协作</a></h3>
<center>表1.7 分工与协作</center>
<div class="table-wrapper"><table><thead><tr><th>项目</th><th>负责</th></tr></thead><tbody>
<tr><td>Docker运行Android 11基础核心的实现</td><td>田梓汎和张阳彬</td></tr>
<tr><td>Androidlunch工程和项目管理</td><td>邹明燊</td></tr>
<tr><td>Android基础核心功能测试</td><td>田梓汎</td></tr>
<tr><td>Andox基础组件移植</td><td>邹明燊</td></tr>
<tr><td>图形渲染输入输出测试</td><td>张阳彬</td></tr>
</tbody></table>
</div>
<h3 id="132-提交仓库目录和文件描述"><a class="header" href="#132-提交仓库目录和文件描述">1.3.2 提交仓库目录和文件描述</a></h3>
<center>表1.8 仓库目录与文件描述</center>
<div class="table-wrapper"><table><thead><tr><th>仓库</th><th>说明</th></tr></thead><tbody>
<tr><td>Proj156-bitcomet</td><td>安卓manifests存放位置,项目说明</td></tr>
<tr><td>Proj156_device_bitcomet</td><td>安卓lunch工程,源码device/bitcomet目录</td></tr>
<tr><td>Proj156_frameworks_native</td><td>修改的android/frameworks/native目录</td></tr>
<tr><td>Proj156_frameworks_base</td><td>修改的android/frameworks/base目录</td></tr>
<tr><td>Proj156_system_bpf</td><td>修改的android/system/bpf目录</td></tr>
<tr><td>Proj156_external_selinux</td><td>修改的android/external/selinux目录</td></tr>
<tr><td>Proj156_system_netd</td><td>修改的android/system/netd目录</td></tr>
<tr><td>Proj156_system_libhwbinder</td><td>修改的android/system/libhwbinder目录</td></tr>
<tr><td>Proj156_system_core</td><td>修改的android/system/core目录</td></tr>
<tr><td>Proj156_vendor_bitcomet</td><td>增加的android/vendor/bitcomet目录放一些工程额外加入的包</td></tr>
<tr><td>Proj156_hardware_interfaces</td><td>修改的android/hardware/interfaces目录</td></tr>
<tr><td>Proj156_hardware_libhardware</td><td>修改的android/hardware/libhardware目录</td></tr>
<tr><td>Proj156_system_linkerconfig</td><td>修改的android/system/linkerconfig目录</td></tr>
<tr><td>Proj156_vendor_anbox</td><td>修改的android/vendor/anbox目录</td></tr>
</tbody></table>
</div>
<p> 注:由于安卓项目太多,所有项目需要通过repo工具组织管理起来,不需要单独拉取覆盖。</p>
<div style="break-before: page; page-break-before: always;"></div><h1 id="2-bitcomet设计开发文档"><a class="header" href="#2-bitcomet设计开发文档">2. Bitcomet设计开发文档</a></h1>
<h2 id="简介"><a class="header" href="#简介">简介</a></h2>
<p> Bitcomet是一个基于原有Anbox向Android 11移植实现一种基于Linux系统运行Android 11的解决方案,以下均<strong>称呼</strong>为<strong>Bitcomet</strong>。其主要与原有Anbox的<strong>改进点</strong>在于Android所运行的环境由LXC实现变为更加主流的Docker实现,原有在Anbox不再更新的Android 7更新为Android 11。此外,为了让Android 11能在Docker中运行,本项目针对其<strong>SDcard的文件系统挂载机制</strong>、<strong>SELinux安全机制</strong>等进行了相应的移植与优化。为了让Android能在Linux上满足正常使用以及调用GPU进行渲染的需求,本项目<strong>参考并移植</strong>原有Anbox以及Google安卓模拟器中的相关实现,在移植并搭建起Anbox的跨系统环境通信基础上,使相应的<strong>Android各类接口打通到外部Linux</strong>,其中最基础的包括<u>图形渲染、图形输出、键鼠输入</u>这三个部分的接口。</p>
<p> 本项目主要还是基于Anbox来参考实现,旨在完成以下四个目标:</p>
<ul>
<li><strong>目标1</strong>:完成Android11的<strong>SDcard的文件系统挂载机制</strong>、<strong>SELinux安全机制、非特权模式下运行处理</strong>对应在Docker容器环境内的处理。</li>
<li><strong>目标2</strong>:配置安卓项目工程,实现并配置好各个需要的系统模块和AIDL、HIDL、HAL模块,搭建初步调试与测试的Demo1版本,使用adb配合Scrcpy对内部Android远程访问以方便有个初步预期,并且方便本项目有个初步测试的环境。</li>
<li><strong>目标3</strong>:完成Anbox通信部分实现向Android 11的移植,使Android能与Anbox外部实现跨系统环境通信。</li>
<li><strong>目标4</strong>:完成Anbox安卓其余部分的各个模块移植,使Android的三个基础部分OpenGL、HWC图形输出、键鼠输入能正常工作。</li>
</ul>
<p> 目前,我们的赛题完成度如下:</p>
<center>表2.1 赛题完成度</center>
<div class="table-wrapper"><table><thead><tr><th style="text-align: center">目标编号</th><th style="text-align: center">基本完成情况</th><th>额外说明</th></tr></thead><tbody>
<tr><td style="text-align: center">1</td><td style="text-align: center">基本完成(≈85%)</td><td>1. 与SDcard以及SELinux组件相关的Android 11的基本功能、SDcard文件管理、安装第三方软件、闹铃与联系人等基础应用测试均通过。<br/>2. 非特权模式仅做了部分处理,未进行测试。</td></tr>
<tr><td style="text-align: center">2</td><td style="text-align: center">基本完成(≈90%)</td><td>1. 搭建好相关容器,进行相关基础测试并通过。<br/>2. 目前网络部分由于在Docker特权模式下运行,可能会有Bug。</td></tr>
<tr><td style="text-align: center">3</td><td style="text-align: center">初步完成(≈80%)</td><td>1. 完成跨系统环境通信,安卓中Anbox的相关模块可以使用QEMU_PIPE(实际容器中是没有QEMU设备,而是使用了Unix Domain Socket代替,但其通信的相关通道仍然叫做QEMU PIPE)进行通信或者使用Anbox实现的RPC调用。<br/>2. Anbox的OpenGL ES的emulation库实现初步测试能通过该通信创建QEMU_PIPE连接,实现RPC调用获取到主机侧支持的OpenGL信息。<br/>3. 由于各个部分的模块暂未完全移植好,还需要全部移植才能测试所有功能。</td></tr>
<tr><td style="text-align: center">4</td><td style="text-align: center">初步测试(≈60%)</td><td>1. 已经把相应的Anbox的各个模块的移植到Android中,其中GPS与Audio模块使用了谷歌给Goldfish的实现。<br/>2.OpenGL ES实现和HWC实现在Android 11下无法测试,由于Android 11的相关变动,导致输出画面调用了Gralloc的PostFB去输出,而在Anbox中是未做这部分RPC调用的,输出画面失败。<br/>3.把相关方案在Android 10上进行测试,测试可以输出画面,但卡在安卓Launcher第一屏画面。</td></tr>
<tr><td style="text-align: center">总计</td><td style="text-align: center">≈80%</td><td>1. 本项目自立项以来经历了三个月的时间进行研究与开发,实现方案的过程中遇到困难重重,主要遇到的阻力在系统庞大、相关实现的资料缺失方面。<br />2. 目前还有部分的工作需要测试和完成。由于Android 11在图形和音频方面变动较大,即使目前进行了相关模块的移植,但是还需要在图形模块、音频模块、输入模块方面进行各类测试和调试工作。<br />3. 项目将放在Github进行公开,把截至目前学习的成果公开,希望后续有更多的参与者参与进来进行开发完善。</td></tr>
</tbody></table>
</div><div style="break-before: page; page-break-before: always;"></div><h2 id="21-bitcomet方案设计与分析"><a class="header" href="#21-bitcomet方案设计与分析">2.1 Bitcomet方案设计与分析</a></h2>
<h3 id="211-为什么选择使用容器与anbox"><a class="header" href="#211-为什么选择使用容器与anbox">2.1.1 为什么选择使用容器与Anbox</a></h3>
<p> 本Bitcomet方案旨在于实现在Linux中运行Android 11的解决方案。在这个方案中会遇到一些问题,如<strong>下图2.1</strong>是Android系统框架图、<strong>图2.2 GNU/Linux系统架构图</strong>,这两张图清晰明了地展现了Android与Linux环境的差异:</p>
<p> Android与Linux除了内核相似外是<strong>完全不一样的系统环境</strong>,尤其体现在系统libc库、应用的运行方式、输入输出设备的管理方式、图像合成方式、OpenGL接口、网络管理组件等方面。这些组件绝大部分将会与Linux<strong>冲突</strong>,导致Linux运行异常或者Android无法直接运行,同时Android的APP也无法在Linux内直接运行。</p>
<center><img src=images/design/ape_fwk_all.png></center>
<center>图2.1 Android 系统架构图(图源自https://source.android.google.cn/devices/architecture?hl=zh-cn)</center>
<center><img src=images/design/Linux架构.png></center>
<center>图2.2 GNU/Linux系统架构图(图源自https://zhuanlan.zhihu.com/p/43029021)</center>
<p> <strong>旧方案:<strong>在过去面向用户的</strong>常见方案</strong>中,常用虚拟机来实现这个方案。利用<strong>虚拟化</strong>功能运行一个Android虚拟机,虚拟机内虚拟或者模拟了电脑绝大部分的硬件,这样既能解决两者接口不同,又能简单实现Android在其他系统上的运行。但是随之而来也有许多<strong>缺点</strong>:虚拟化需要硬件支持、虚拟化对CPU性能有一小部分损耗、虚拟机本身运行一个全新的系统也需要更多硬件资源、在虚拟机与主机系统间切换也无法很好地利用上常见PC系统的多任务多窗口特性。</p>
<p> 相比起虚拟机的实现方法,容器基于Linux内核的命名空间隔离,也能提供一个<strong>隔离</strong>了Android系统与Linux之间的磁盘、内存等的运行环境。同时容器<strong>不需要虚拟化,不需要跑起整个Linux内核</strong>,相对应的对硬件资源要求较少,这三个方面看起来有优势。但是容器又出现不一样的问题:相比一般Android模拟器使用的虚拟机,容器又缺少输入输出接口、3D加速与显示输出。</p>
<center><img src=images/design/virtualbox_run_android.png></center>
<center>图2.3 虚拟机运行Android x86架构图</center>
<p> <strong>Bitcomet方案:<strong>使用容器提供Android运行的环境来实现,其</strong>原因</strong>如下:该方案可能会在国产化平台上运行,面向不同的硬件环境下不一定支持虚拟化;对硬件资源需求较少;针对容器缺少的输入输出接口以及3D加速与显示输出,谷歌为解决这些问题在安卓模拟器AVD上设计了一套新的方案。另外Anbox在一部分输入输出及3D加速方面正好是<strong>复用</strong>了谷歌的安卓模拟器的方案,利用了其提供的QEMU_PIPE、emugl、传感器模拟、音频输出等方面提供的实现。</p>
<p> 以上是复用了谷歌的实现,但是前端不一样,安卓模拟器的前端是方便谷歌模拟器实现的,而Anbox是需要利用上PC系统的<strong>多任务多窗口</strong>、方便对接输入输出和控制等的特性。因此Anbox这部分针对谷歌的方案进行了<strong>参考重写</strong>,把Android中各个不同APP的窗口映射到不同的模拟Surface上。但相应的代价是,由于谷歌3D渲染部分是通过把其指令传输出来交给<strong>外部</strong>OpenGL渲染,这样性能利用是真机渲染的70%-90%(数据来自参考文献[1]),有一定的损耗。</p>
<center><img src=images/design/Docker_run_android.png></center>
<center>图2.4 Docker运行Android架构图</center>
<h3 id="212-总结"><a class="header" href="#212-总结">2.1.2 总结</a></h3>
<p> 本项目是这类移植方式中其中一种兼容性较好的方案,其OpenGL ES渲染是通过<strong>传输</strong>给Anbox并由emugl转换后交给Linux<strong>用户空间</strong>的OpenGL库实现,OpenGL的对应版本的API是<strong>统一</strong>的,而Linux平台一般都有GPU驱动+相应库,即使没有也会使用软件OpenGL库,所以这种方案理论上是能<strong>适应</strong>更多GPU平台的。</p>
<p> 总的来说这类移植方式主要都有一类<strong>共通点</strong>,为了把输入输出、显示渲染等<strong>接口打通到对应平台</strong>,而不断衍生的技术都有新的优化与改进,尽量减少通信期间数据的拷贝,降低损耗,提高硬件利用率。</p>
<div style="break-before: page; page-break-before: always;"></div><h2 id="22-bitcomet方案设计"><a class="header" href="#22-bitcomet方案设计">2.2 Bitcomet方案设计</a></h2>
<p> 本方案是在Ubuntu 20.04(下图GNU/Linux部分),部署好Docker环境。</p>
<p> <strong>Bitcomet底层</strong>:其中Android 11的Docker镜像以及Anbox相关组件,均为本方案的实现,经过编译打包并导入Docker的密封环境中。</p>
<p> <strong>Bitcomet中间层</strong>:在Android 11中,Anbox的后端及相关模块通过桌面下的Anbox前端产生的QEMU pipe进行通信。</p>
<p> <strong>Bitcomet上层</strong>:Android在容器内缺少图形输出、渲染、音频和键盘设备操作的输入输出功能,其相关功能依赖Anbox的Gralloc、HWC、Audio等相关模块实现。这些模块在QEMU pipe通信的基础上,搭建起对外Linux沟通桥梁,为外部Linux对接内部Android的输入输出、图形输出渲染等功能。外部Anbox针对相关功能实现调用Linux系统库,实现了输入输出渲染等功能。</p>
<p> 如图2.5是针对Bitcomet各个部分的详细设计:</p>
<center><img src=images/design/Anbox11_Flow_Design.png></center>
<center>图2.5 Bitcomet方案详细架构图</center>
<p> 其中各个部分有相关的实现文档,具体见当前项目下<strong>Docs目录</strong>内其他文档。</p>
<h3 id="221-docker下的安卓11容器设计"><a class="header" href="#221-docker下的安卓11容器设计">2.2.1 Docker下的安卓11容器设计</a></h3>
<center><img src=images/design/Anbox11_in_Docker.png></center>
<center>图2.6 Bitcomet实现底层部分设计框架图</center>
<h4 id="1-主要工作"><a class="header" href="#1-主要工作">(1) 主要工作</a></h4>
<p> 在Bitcomet方案底层部分中,使用Docker容器提供Android运行的环境。当然仅仅提供一个环境还不行,Android 11需要依赖Binder、SELinux、SDcardFS、Ashmem等环境的支持。具体的工作如下:</p>
<p>① <strong>内核定制</strong>:Binder与Ashmem需要针对内核定制,目前较新的Linux 5.x已经自带BinderFS和Ashmem模块的支持,旧的内核需要使用Anbox提供的Binder或Ashmem模块。</p>
<p>② <strong>SELinux安全机制处理</strong>:本方案选择了对其相关方法进行屏蔽处理,即使实际测试及Docker文档中都反应了Docker下是可以实现这个支持的,但我们仍然选择了屏蔽,其原因如下:</p>
<p> 第一,Docker外是针对指定配置文件的SELinux安全配置,而Android内的还需要单独配置并需要一部分特权。</p>
<p> 第二,不同平台的Linux内核不一定提供并使用了SELinux安全模块,遇上没有此模块的平台均无法运行。</p>
<p> 第三,使用SELinux后,Docker官方提到可能会对性能有一定的损失。</p>
<p>③ <strong>文件系统挂载处理</strong>:SDcardFS部分是安卓8以后提供的模块,可以让Android接管对SDcardFS下的目录文件的访问权限控制。我们选择了使用FUSE来实现挂载SDcard的虚拟文件系统,FUSE是较旧版本的Android采用的一种方式,其实现主要在用户空间,能提供几乎与SDcardFS相同的功能。但FUSE挂载SDcard的实现也有缺点,其速度比SDcardFS的实现慢,我们仍然选择FUSE方案的原因如下:</p>
<p> 第一, SDcardFS的实现依赖Linux内核实现,不同Linux内核对其支持不一样。</p>
<p> 第二, 由于SDcardFS在内核实现,挂载它也需要一定的特权。</p>
<h4 id="2-bitcomet底层部分特点"><a class="header" href="#2-bitcomet底层部分特点">(2) Bitcomet底层部分特点</a></h4>
<p>Docker下的安卓11容器设计:<br />
• <strong>高可移植设计</strong>:使用了Docker便集成了Docker相关的优点,Docker相比原有LXC有可移植性、版本控制、回滚、快速部署等优点。同时原有LXC容器与Anbox进行了高度集成,在目前多平台和可移植性考量下,原有Anbox的系统镜像与各个配置选项都不能方便地进行更改,这将会导致我们适配到国产系统中遇到重重困难。<br />
• <strong>快速迭代更新</strong>:使用Docker提供底层容器运行环境,利用其快速部署的优点,可以使Android 11更加灵活地运行在目标平台。<br />
• <strong>高效的数据管理</strong>:利用Docker的版本控制和回滚特性,设计恢复出厂设置并清空用户数据的功能,并为以后Android镜像更新时能够快速部署,及时把新版本推送。<br />
• <strong>较高系统安全性</strong>:把安卓放在密封的容器当中,其外部访问数据都通过QEMU pipe交给上层Anbox前端应用接管,其应用也只有基本的输入输出功能,安全性较高。<br />
• <strong>极低的运行损耗</strong>:通过容器直接基于现有系统Linux内核运行,并基于Linux内核特性进行容器隔离,这一方案提高了稳定性并降低了运行损耗。</p>
<h3 id="222-安卓与上层anbox通信设计"><a class="header" href="#222-安卓与上层anbox通信设计">2.2.2 安卓与上层Anbox通信设计</a></h3>
<center><img src=images/design/Anbox11_IO_Model.png></center>
<center>图2.7 Bitcomet中间层部分设计原理图</center>
<h4 id="1-主要工作-1"><a class="header" href="#1-主要工作-1">(1) 主要工作</a></h4>
<p> 在Bitcomet中间层中,我们复用了Anbox这一实现,把其相关实现移植到Android11,并实现其上面的各种功能。我们的主要工作如下:</p>
<p>① <strong>通信实现</strong>:把Anbox基于QEMU pipe和qemud的实现移植到Android11,这两个实现的本质均为通过QEMU pipe实现,但由于容器内没有实质上的QEMU pipe设备,因此Anbox魔改了一下使用Unix Domain Socket实现,这种实现从原理上说与QEMU pipe的区别在于:第一,QEMU pipe设备是通过QEMU的相关模拟实现的,其模拟的设备支持DMA,理论上比Unix Domain Socket这一软件实现更有效率;第二,Anbox这里相关实现的名字还是叫做QEMU pipe,而QEMU pipe本身也有tcp socket相关的实现。</p>
<p>② <strong>设备映射</strong>:在Docker中映射外部Anbox的Session Manager创建的Socket监听到容器内的/dev对应设备,以便容器内的QEMUd或者QEMU pipe相关实现进行调用。</p>
<p>③ <strong>Anbox通信模块移植</strong>:容器内Anbox为这些实现封装成了叫做HOST_CONNECTION的方法,容器外Anbox基于谷歌的ProtoBuf实现一种通信模型,其中包括OpenGL ES、HWC、模拟的Surface、Anbox Proxy等组件的RPC调用,以便内部Anbox通过这些封装的方法直接调用,我们也针对这些方法进行了移植和部分测试。</p>
<h4 id="2-bitcomet中间层部分特点"><a class="header" href="#2-bitcomet中间层部分特点">(2) Bitcomet中间层部分特点</a></h4>
<p>安卓中运行qemud提供QEMU pipe这一高速管道与上层通信:<br />
• <strong>成熟的通信方案</strong>:这一通信方案基于成熟的Anbox的通信这一部分的功能实现,其上层Session Manager提供接口,由安卓和Anbox两端利用相应API把接口打开进行通信。其实现方案最早可以追溯到2013年的谷歌Goldfish的Android模拟器实现并且沿用至今,实现了Android与Linux的之间通信的兼容性与健壮性并存的实现方式。<br />
• <strong>便捷的交互方式</strong>:本方案在安卓与Linux之间的通信方案设计基于QEMU pipe这一高速通道,直接在Android与Linux之间打通一个灵活的通信渠道。本方案不用考虑需要的具体通信实现,直接在安卓HAL层服务和上层Anbox前端应用中打开相应通道进行安卓与Linux对接的数据通信。</p>
<h3 id="223-上层anbox的opengl-es渲染以及输入输出设计"><a class="header" href="#223-上层anbox的opengl-es渲染以及输入输出设计">2.2.3 上层Anbox的OpenGL ES渲染以及输入输出设计</a></h3>
<center><img src=images/design/Anbox11_in_Linux.png></center>
<center>图2.8 Bitcomet上层部分设计原理图</center>
<h4 id="1-主要工作-2"><a class="header" href="#1-主要工作-2">(1) 主要工作</a></h4>
<p> 在Bitcomet的上层中,我们也复用Anbox的实现,把其相关部分移植到Android11和Ubuntu 20.04上。Android中对接外部Linux主要在图形输出、渲染、输入输出部分缺失,这些部分需要靠Anbox在中间通信层的部分实现之上,建立各个部分的连接,打通到外部Linux上。我们的主要工作如下:</p>
<p>① <strong>HAL层模块移植</strong>:这一部分主要工作在Android中HAL层的移植以及测试,我们经过在Android工程中对AIDL与HIDL对应HWC与Gralloc部分的模块进行配置,Gralloc的HAL层复用最新Android 11中对安卓模拟器的AVD实现。我们复用Android 11的这一部分主要原因在于其安卓模拟器的实现与Anbox的实现是一样的,都是软件实现的调用ashmem申请图形缓冲区。</p>
<p>② <strong>图形输出部分移植</strong>:从Android的HAL层中Gralloc与HWC模块开始,图形输出部分的核心模块是HWC模块,它负责被SurfaceFlinger调用进行图层合成输出到显示设备,而这里我们选择在Anbox的实现上进行移植修复。该实现主要测试点在于图形HWC,HWC中的发送图层部分开始,通过Anbox的RPC调用,发送图层到外部Anbox的Surface上,需要测试相关图层测试数据是否能到达外部Anbox上合成输出。</p>
<p>③ <strong>输入输出部分移植</strong>:在输入部分,Anbox的Session Manager注册了多个Event设备以及audio传输设备,我们需要把这些设备在Docher中映射到/dev下,以便在Android中调用。其中音频部分也复用Android11针对模拟器部分的audio HAL实现,原因在于Anbox这些部分的实现也是复用Android模拟器的实现,其原理是把音频PCM数据通过Anbox的RPC调用方法发送到外部Anbox进行解码播放。</p>
<p>④ <strong>OpenGL ES部分移植</strong>:Anbox的OpenGL库主要在Android的 /system/lib64/egl 目录下安装了三个动态库,分别为libEGL_emulation.so、libGLESv1_CM_emulation.so和libGLESv2_emulation.so。Anbox在其中提供虚拟的硬件厂商配置,为Android提供OpenGL ES渲染库。但Anbox实际渲染工作并不是上述三个库文件,这三个库文件作用为采集Android中APP渲染的OpenGL ES指令,并通过高速传输通道qemu-pipe传输将指令传至宿主机Anbox进程中,实际渲染工作由宿主机执行。这部分主要也是移植好Anbox这几个库,检验该三个库与上层Anbox的通信,这部分库也是复用谷歌Android的安卓模拟器AVD实现,主要测试工作在于做相关指令的通信以及实际渲染测试。</p>
<h4 id="2-bitcomet上层部分特点"><a class="header" href="#2-bitcomet上层部分特点">(2) Bitcomet上层部分特点</a></h4>
<p>Anbox通过QEMU pipe与底层通信,接收安卓的渲染信息渲染以及音频在上层APP输出,把上层前端APP的输入和状态传递给安卓:<br />
• <strong>兼容性高</strong>:主要体现在Linux下对Anbox的兼容性,这一方案的设计把安卓需要与Linux交互和获取的数据都通过QEMU pipe传输给Linux处理,上层Anbox前端不依赖硬件。尤其是渲染也是调用系统现有OpenGL库实现,兼容性相比虚拟机来说更高。<br />
• <strong>易用性高</strong>:通过上层Anbox应用接管底层安卓的输入输出等部分,相当于把安卓应用直接映射给了Linux应用。这一操作安卓应用非常接近使用Linux应用的方式,极大的提高了其易用性,操作起来也更加简单。</p>
<div style="break-before: page; page-break-before: always;"></div><h2 id="31-安卓图形架构中的anbox的hal模块"><a class="header" href="#31-安卓图形架构中的anbox的hal模块">3.1 安卓图形架构中的Anbox的HAL模块</a></h2>
<p> 在安卓官方文档当中,安卓的系统架构被分为五层架构,其五层架构分别为</p>
<center>表3.1 安卓系统结构</center>
<div class="table-wrapper"><table><thead><tr><th>架构</th><th>主要负责的内容</th></tr></thead><tbody>
<tr><td>应用框架 APP FRAME</td><td>应用框架主要为开发者使用,开发者API提供了很多映射至底层的HAL接口,提供实现驱动程序的相关信息</td></tr>
<tr><td>Binder IPC</td><td>主要负责进程间的通讯,不仅仅是顶层进程之间的交互,更重要的是支持顶层进程和安卓系统服务间甚至是HAL的交互</td></tr>
<tr><td>系统服务</td><td>安卓把各种各样的系统服务包装为各种模块化组件,可以方便后续升级。其中包括有DNS解析模块,媒体模块等。</td></tr>
<tr><td>硬件抽象层HAL</td><td>HAL可以定义一个接口给硬件供应商实现,主要用于实现系统服务和底层驱动程序的通讯和运作。其被封装成一个模块被安卓系统随时调用</td></tr>
<tr><td>Linux内核</td><td>Android使用的linux内核与一般的内核不同,如:必须支持Binder、包含一些特殊功能(内存守护)</td></tr>
</tbody></table>
</div>
<h3 id="311-安卓图形部分架构以及anbox图形模块"><a class="header" href="#311-安卓图形部分架构以及anbox图形模块">3.1.1 安卓图形部分架构以及Anbox图形模块</a></h3>
<p> 其中在Anbox的图形模块中,最主要关心的就是HAL层以及LINUX KERNEL。因为本项目中安卓是运行在容器内的,安卓不能像原生一样顺利地让HAL层直接调用其LINUX KERNEL内的驱动和所需资源,而是要跟容器外部正在运行的LINUX环境对接。在本项目中的这一目标就是努力<strong>让容器内的安卓与容器外本不兼容的LINUX环境对接</strong>,成功把图形化渲染的工作交给LINUX环境当中,实现渲染效率比软件模拟成倍提升的效果。</p>
<p> 在安卓主要的图形模块当中,所有的渲染信息流大致如图3.1所示。</p>
<center><img src=images/hal-display/display流程图.png></center>
<center>图3.1 安卓显示相关实现流程图</center>
<h4 id="1-hal层"><a class="header" href="#1-hal层">(1) HAL层</a></h4>
<p> 在本文档中,主要聚焦于HAL层anbox的实现。在安卓系统中HAL层包括两个组件,HWC(Hardware Composer)与Gralloc。</p>
<h5 id="hwchardware-composer模块"><a class="header" href="#hwchardware-composer模块">HWC(Hardware Composer)模块</a></h5>
<h6 id="①-初步介绍"><a class="header" href="#①-初步介绍">① 初步介绍</a></h6>
<p> 无论开发者使用什么渲染API,一切的内容都将会渲染到<strong>surface</strong>上,surface可以被理解为是一个生产缓冲区,整一个被渲染的surface队列都会被<strong>surfaceflinger</strong>所消耗而准备合成到屏幕上。</p>
<center><img src=images/hal-display/layer合成.png></center>
<center>图3.2 安卓显示流水线</center>
<p> <strong>合成是一种将缓冲区队列内所有元素有规律有层级的覆盖为一块画面的操作。</strong> 在安卓系统中,surface队列内的元素通常是大小不一且杂乱的,因为来自多个不同源的缓冲区,例如有些是出于顶部的状态栏,有些是出于底部的导航栏,而中间的应用内容也是一层缓冲区,直接送出来的画面完全不是屏幕在显示的画面。怎么解决这个问题?在安卓中选择了效率最高的方式来处理合成问题——将三个缓冲区全部传送到显示硬件,并指示它从不同的缓冲区读取屏幕不同部分的数据。这个操作的承担者就是硬件混合渲染器HAL层。</p>
<p> 合成到屏幕这一步就需要调用我们的HAL层中的HWC了。HWC主要跟屏幕显示组件通讯,获得屏幕相应的参数,计算出最常用的四个不同的部分——状态栏、系统栏、应用以及壁纸/背景所覆盖的画面,再接受<code>surfaceflinger</code>需要合成的队列所拥有的内容,把这些内容分别合成到状态栏、系统栏、应用以及壁纸/背景所覆盖的画面后再与屏幕沟通,最终显示在真正现实的手机屏幕上。</p>
<p> 当然,HWC也会负责虚拟屏幕合成,同时会向硬件设备注册三个回调,以便应对这些事件的发生:屏幕热插拔,刷新和VSync信号。</p>
<p> 在Anbox当中,HWC还有一个比较重要的功能,它可以把单个安卓里面的应用程序映射到桌面环境的单个窗口中,因为在桌面环境下我们不需要再像现实中让HWC把所有内容合成到一块屏幕上。Anbox会通过其<code>hwcomposer</code>实现告诉<code>SurfaceFlinger</code>为每个应用程序获取一个层,并将其与从<code>Android WindowManager</code>收到的额外信息结合起来,将单个层映射到应用程序。</p>
<center><img src=images/hal-display/layer合成.png></center>
<center>图3.3 HWC显示合成概念图1</center>
<center><img src=images/hal-display/layer合成.jpg></center>
<center>图3.4 HWC显示合成概念图2</center>
<h6 id="②-hwc模块重要实现"><a class="header" href="#②-hwc模块重要实现">② HWC模块重要实现</a></h6>
<p><strong>安卓中HWC部分的实现</strong></p>
<p> 首先<code>surfaceflinger</code>创建<code>HWComposer</code>。</p>
<pre><code class="language-c">//节选自main_surfaceflinger.cpp
void SurfaceFlinger::init() {
// Initialize the H/W composer object. There may or may not be an
// actual hardware composer underneath.
mHwc = new HWComposer(this,
*static_cast<HWComposer::EventHandler *>(this));//调用构造函数
}
</code></pre>
<p> <code>HWComposer</code>构造函数。</p>
<pre><code class="language-c">//节选自DisplayHardware/HWComposer.cpp
HWComposer::HWComposer(const sp<SurfaceFlinger>& flinger)
: mFlinger(flinger),
mAdapter(),
mHwcDevice(),
mDisplayData(2),
mFreeDisplaySlots(),
mHwcDisplaySlots(),
mCBContext(),
mEventHandler(nullptr),
mVSyncCounts(),
mRemainingHwcVirtualDisplays(0)
{
for (size_t i=0 ; i<HWC_NUM_PHYSICAL_DISPLAY_TYPES ; i++) {
mLastHwVSync[i] = 0;
mVSyncCounts[i] = 0;
}
loadHwcModule(); //调用HWC模块
}
</code></pre>
<p> <code>HWComposer</code>构造函数一般使用<code>loadHwcModule</code>方法加载来<code>HWComposer</code>模块。</p>
<pre><code class="language-c">//节选自DisplayHardware/HWComposer.cpp
void HWComposer::loadHwcModule()
{
ALOGV("loadHwcModule");
// 其定义在hardware.h中,表示一个硬件模块
hw_module_t const* module;
// 加载硬件厂商提供的hwcomposer模块,HWC_HARDWARE_MODULE_ID定义在hwcomposer_defs.h中,表示"hwcomposer"
if (hw_get_module(HWC_HARDWARE_MODULE_ID, &module) != 0) {
ALOGE("%s module not found, aborting", HWC_HARDWARE_MODULE_ID);
abort();
}
hw_device_t* device = nullptr;
// 通过硬件厂商提供的open函数打开一个"composer"硬件设备,HWC_HARDWARE_COMPOSER也定义在hwcomposer_defs.h中,表示"composer"
int error = module->methods->open(module, HWC_HARDWARE_COMPOSER, &device);
if (error != 0) {
ALOGE("Failed to open HWC device (%s), aborting", strerror(-error));
abort();
}
uint32_t majorVersion = (device->version >> 24) & 0xF;
// mHwcDevice是HWC2.h中定义的HWC2::Device,所有与HWC的交互都通过mHwcDevice
if (majorVersion == 2) { // HWC2,hwc2_device_t是hwcomposer2.h中的结构体
mHwcDevice = std::make_unique<HWC2::Device>(
reinterpret_cast<hwc2_device_t*>(device));
} else { // 设备是基于HWC1,这里用HWC2去适配,Android7.0及以前默认都是HWC1,hwc_composer_device_1_t是hwcomposer.h中的结构体
mAdapter = std::make_unique<HWC2On1Adapter>(
reinterpret_cast<hwc_composer_device_1_t*>(device));
uint8_t minorVersion = mAdapter->getHwc1MinorVersion();
if (minorVersion < 1) {
ALOGE("Cannot adapt to HWC version %d.%d",
static_cast<int32_t>((minorVersion >> 8) & 0xF),
static_cast<int32_t>(minorVersion & 0xF));
abort();
}
mHwcDevice = std::make_unique<HWC2::Device>(
static_cast<hwc2_device_t*>(mAdapter.get()));
}
// 获取硬件支持的最大虚拟屏幕数量,VirtualDisplay主要是用来用于录屏
mRemainingHwcVirtualDisplays = mHwcDevice->getMaxVirtualDisplayCount();
}
</code></pre>
<p> 先加载<code>hwcomposer</code>模块得到<code>hw_module_t</code>,再打开<code>composer</code>设备得到<code>hw_device_t</code>。所以一般情况下是先有HAL模块,再有实现此模块的硬件设备。</p>
<pre><code class="language-c">//节选自hardware/libhardware/include/hardware/hardware.h
typedef struct hw_module_t {
//每一个HAL库都会提供的一个方法methods,
struct hw_module_methods_t* methods;
}hw_module_t;
typedef struct hw_module_methods_t {
//这个 methods的数据结构中只有一个函数指针变量open,用来打开指定的硬件设备。
int (*open)(const struct hw_module_t* module, const char* id,
struct hw_device_t** device);
} hw_module_methods_t;
</code></pre>
<p> 在每个HAL层模块实现都要定义一个<code>HAL_MODULE_INFO_SYM</code>数据结构,并且该结构的第一个字段必须是<code>hw_module_t</code>,下面是anbox当中<code>hwcomposer</code>模块的定义。</p>
<pre><code class="language-c">//节选自vendor/anbox/android/hwcomposer/hwcomposer.cpp
hwc_module_t HAL_MODULE_INFO_SYM = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = HWC_HARDWARE_MODULE_ID,
.name = "Hardware Composer Module",
.author = "Anbox Developers",
.methods = &hwc_module_methods,
}
};
</code></pre>
<p> 这里Anbox中对应method如下,实现了打开模块的接口。</p>
<pre><code class="language-c">//节选自vendor/anbox/android/hwcomposer/hwcomposer.cpp
//methods数据结构中只有一个函数指针变量open,用来打开指定的硬件设备(Anbox中是软件实现)
static hw_module_methods_t hwc_module_methods = {
.open = hwc_device_open
};
//Anbox中这里注册了自己实现的HWC设备的各种接口对接函数
static int hwc_device_open(const hw_module_t* module, const char* name, hw_device_t** device) {
ALOGD("%s", __PRETTY_FUNCTION__);
auto dev = new HwcContext;
dev->device.common.tag = HARDWARE_DEVICE_TAG; // 标记这是一个硬件设备的模块
dev->device.common.version = HWC_DEVICE_API_VERSION_1_1; // HWC版本,Anbox还是用的HWC1
dev->device.common.module = const_cast<hw_module_t*>(module); // 对应上面模块信息结构体定义
dev->device.common.close = hwc_device_close; // HWC设备关闭调用的操作
dev->device.prepare = hwc_prepare; // 图层的配置,surfaceflinger把要显示的layers放在displays参数里,针对其参数对应Display硬件配置图层的参数类型等。
dev->device.set = hwc_set; // hwc_set方法,在SurfaceFlinger要求HWC发送图层数据,该方法进行发送相关图层数据给Anbox在Linux的前端实现。
dev->device.eventControl = hwc_event_control; // 使能/禁止vsync,Anbox未实现
dev->device.blank = hwc_blank; // 老的hwc(1.3以前)用blank控制display on/off,最新的hwc里用setPowerMode。实现的功能差不多,但setPowerMode的参数更丰富,不像blank就0/1。
dev->device.query = hwc_query;
dev->device.getDisplayConfigs = hwc_get_display_configs; //获取显示硬件的配置,一般是多显示屏等配置的获取返回,Anbox只有一个主显示器,因此这里只是配置0让HWC进入获取属性的调用。
dev->device.getDisplayAttributes = hwc_get_display_attributes;//获取显示硬件的各个属性
dev->device.registerProcs = hwc_register_procs;
dev->device.dump = nullptr;
*device = &dev->device.common;
return 0;
}
</code></pre>
<p> 可以看出这一阶段已经完成了Anbox的HWC具体实现的模块打开并注册接口,下面将针对hwc_get_display_attributes和hwc_set进行讲解,这两个是Anbox在HWC这一部分显示输出的精髓。</p>
<p><strong>Anbox部分重要实现</strong></p>
<p> 在Anbox的HWC模块中,其中最主要的就是hwc_get_display_attributes和hwc_set的实现,这里把一些重要的部分进行讲解。</p>
<p> <strong>hwc_get_display_attributes函数部分实现</strong></p>
<pre><code class="language-c">//节选自vendor/anbox/android/hwcomposer/hwcomposer.cpp
static int hwc_get_display_attributes(hwc_composer_device_1* dev,
int disp, uint32_t config,
const uint32_t* attributes,
int32_t* values) {
if (disp != 0 || config != 0) {
return -EINVAL;
}
//建立与Anbox前端的QEMU_PIPE连接
DEFINE_AND_VALIDATE_HOST_CONNECTION();
// 下面各个属性都是通过与Anbox连接获取返回的值
while (*attributes != HWC_DISPLAY_NO_ATTRIBUTE) {
//针对各种属性返回对应的值,其中HWC_DISPLAY_NO_ATTRIBUTE是这个attributes数组的结束
switch (*attributes) {
//获取屏幕VSYNC垂直同步信号的周期值
case HWC_DISPLAY_VSYNC_PERIOD:
*values = rcEnc->rcGetDisplayVsyncPeriod(rcEnc, disp);
break;
//获取屏幕宽高和DPI参数
case HWC_DISPLAY_WIDTH:
*values = rcEnc->rcGetDisplayWidth(rcEnc, disp);
break;
case HWC_DISPLAY_HEIGHT:
*values = rcEnc->rcGetDisplayHeight(rcEnc, disp);
break;
case HWC_DISPLAY_DPI_X:
*values = 1000 * rcEnc->rcGetDisplayDpiX(rcEnc, disp);
break;
case HWC_DISPLAY_DPI_Y:
*values = 1000 * rcEnc->rcGetDisplayDpiY(rcEnc, disp);
break;
default:
ALOGE("Unknown attribute value 0x%02x", *attributes);
}
++attributes;
++values;
}
return 0;
}
</code></pre>
<p> 可以看得出hwc_get_display_attributes是通过Anbox获取屏幕的参数返回给安卓HWC模块,方便给SurfaceFlinger和HWC进行屏幕输出。</p>
<p> 接下来查看其中rcEnc调用的方法的来源:</p>
<pre><code class="language-c">//节选自vendor/anbox/android/hwcomposer/hwcomposer.cpp
//其来自于建立QEMU_PIPE的地方
#define DEFINE_AND_VALIDATE_HOST_CONNECTION() \
//获取Anbox的QEMU_PIPE连接,这部分不再赘述
HostConnection *hostCon = HostConnection::get(); \
if (!hostCon) { \
ALOGE("hwcomposer.anbox: Failed to get host connection\n"); \
return -EIO; \
} \
//获取Host端的rcEncoder方法返回的远程调用对象
renderControl_encoder_context_t *rcEnc = hostCon->rcEncoder(); \
if (!rcEnc) { \
ALOGE("hwcomposer.anbox: Failed to get renderControl encoder context\n"); \
return -EIO; \
}
</code></pre>
<p> 其来自于建立QEMU_PIPE的地方,通过与外部Anbox建立连接,利用Anbox 基于 Protobuf 设计的 RPC 进行通信,实现远程方法调用。<br />
这里以rcEnc->rcGetDisplayWidth(rcEnc, disp)为例,远程能调用到Anbox在Host端的GL库函数返回的值:</p>
<pre><code class="language-cpp">//节选自vendor/anbox/src/anbox/graphics/emugl/RenderControl.cpp
int rcGetDisplayWidth(uint32_t display_id) {
(void)display_id;
// 调用到实际的GL库的函数,获取实际的垂直分辨率大小
return static_cast<int>(anbox::graphics::emugl::DisplayInfo::get()->vertical_resolution());
}
</code></pre>
<p> 最终能正确通信获得rcGetDisplayWidth返回值。</p>
<p> 以上的获取DisplayWidth的实现,其流程从自下向上来看,其流程如下:<br />
首先,hwc_get_display_attributes中的rcEnc->rcGetDisplayWidth远程调用获得了Host端Anbox的窗口垂直分辨率的值。<br />
再者,dev->device.getDisplayAttributes = hwc_get_display_attributes是该HWC注册的方法,被populateConfigs函数调用该模块获取信息。<br />
最后被HWC2On1Adapter::populatePrimary()、HWC2On1Adapter::hwc1Hotplug、HWC2On1Adapter::createVirtualDisplay等方法调用,主要用于在显示设备创建、热插拔时获取显示设备信息返回给SurfaceFlinger及HWC。</p>
<p> <strong>hwc_set函数部分实现</strong></p>
<p> dev->device.set是SurfaceFliner要求HWC发送图层数据,在这里Anbox对应注册的函数是hwc_set,其函数实现如下:</p>
<pre><code class="language-c">//节选自vendor/anbox/android/hwcomposer/hwcomposer.cpp
static int hwc_set(hwc_composer_device_1_t* dev, size_t numDisplays,
hwc_display_contents_1_t** displays) {
// HWC上下文
auto context = reinterpret_cast<HwcContext*>(dev);
if (displays == NULL || displays[0] == NULL)
return -EFAULT;
//通过QEMU_PIPE与Anbox连接,原理同上一步hwc_get_display_attributes中的实现
DEFINE_AND_VALIDATE_HOST_CONNECTION();
// 循环处理当前display的所有层
for (size_t i = 0 ; i < displays[0]->numHwLayers ; i++) {
const auto layer = &displays[0]->hwLayers[i];
//如果是指针或者需要跳过而不用刷新的层,跳过
if (layer->flags & HWC_SKIP_LAYER ||
layer->flags & HWC_IS_CURSOR_LAYER)
continue;
// Anbox下,HWC注册的compositionType是HWC_FRAMEBUFFER_TARGET
if(layer->compositionType == HWC_FRAMEBUFFER_TARGET) {
// 通过getprop去读取“anbox.layer_name”这一系统配置,获取当前要传输的Layer名称
std::string layer_name_temp = android::base::GetProperty("anbox.layer_name", "");
std::string layer_name = layer_name_temp.substr(0, layer_name_temp.find('#'));
strncpy(layer->name, layer_name.c_str(), layer_name.size());
}
// 根据图层的三组必要参数,发送Layer
rcEnc->rcPostLayer(rcEnc,
layer->name,
cb->hostHandle,
layer->planeAlpha / 255,
layer->sourceCrop.left,
layer->sourceCrop.top,
layer->sourceCrop.right,
layer->sourceCrop.bottom,
layer->displayFrame.left,
layer->displayFrame.top,
layer->displayFrame.right,
layer->displayFrame.bottom);
hostCon->flush(); //刷新缓冲区
}
// 通知Anbox所有发送完毕
rcEnc->rcPostAllLayersDone(rcEnc);
check_sync_fds(numDisplays, displays);
return 0;
}
</code></pre>
<p> 该函数中,第一个系统配置“anbox.layer_name”是Anbox修改了SurfaceFlinger,在准备输出图层前把图层名称保存成这个配置中,具体实现如下:</p>
<pre><code class="language-cpp">//节选自:frameworks/native/services/surfaceflinger/BufferLayer.cpp
//SurfaceFlinger
void BufferLayer::setPerFrameData(const sp<const DisplayDevice>& displayDevice,
const ui::Transform& transform, const Rect& viewport,
int32_t supportedPerFrameMetadata,
const ui::Dataspace targetDataspace) {
RETURN_IF_NO_HWC_LAYER(displayDevice);
// Apply this display's projection's viewport to the visible region
// before giving it to the HWC HAL.
//获取可见区域
Region visible = transform.transform(visibleRegion.intersect(viewport));
// 寻找当前显示器的输出图层
const auto outputLayer = findOutputLayerForDisplay(displayDevice);
LOG_FATAL_IF(!outputLayer || !outputLayer->getState().hwc);
//获取HWC
auto& hwcLayer = (*outputLayer->getState().hwc).hwcLayer;
//保存当前图层名称到anbox.layer_name系统配置中
android::base::SetProperty("anbox.layer_name", mName.string());
//设置刚刚获取的可见区域到HWC
auto error = hwcLayer->setVisibleRegion(visible);
if (error != HWC2::Error::None) {
ALOGE("[%s] Failed to set visible region: %s (%d)", mName.string(),
to_string(error).c_str(), static_cast<int32_t>(error));c
visible.dump(LOG_TAG);
}
</code></pre>
<p> 而rcEnc->rcPostLayer中planeAlpha、sourceCrop、displayFrame就是下图中各个区域,如图3.5所示。</p>
<center><img src=images/hal-display/安卓显示区域间的关系.png></center>
<center>图3.5 安卓显示区域间的关系图</center>
<p> 第一,sourceCrop是对Layer进行剪切的,值截取部分Layer的内容进行显示;sourceCrop不超过Layer的大小,超过没有意义。</p>
<p> 第二,displayFrame表示Layer在屏幕上的显示区域,具体说来,是sourceCrop区域在显示屏上的显示区域。displayFrame一般来说,小于屏幕的区域。而displayFrame可能比sourceCrop大,可能小,这都是正常的,只是需要做缩放,这就是合成时需要处理的。</p>
<p> 而在外部Linux的Anbox中,上面rcEnc->rcPostLayer、rcEnc->rcPostAllLayersDone也是属于Anbox的RPC实现,这里只截取最终在Anbox中调用的地方,不多赘述。</p>
<pre><code class="language-cpp">//节选自vendor/anbox/src/anbox/graphics/emugl/RenderControl.cpp
void rcPostLayer(const char *name, uint32_t color_buffer, float alpha,
int32_t sourceCropLeft, int32_t sourceCropTop,
int32_t sourceCropRight, int32_t sourceCropBottom,
int32_t displayFrameLeft, int32_t displayFrameTop,
int32_t displayFrameRight, int32_t displayFrameBottom) {
//建立渲染结构体
Renderable r{
name,
color_buffer,
alpha,
{displayFrameLeft, displayFrameTop, displayFrameRight, displayFrameBottom},
{sourceCropLeft, sourceCropTop, sourceCropRight, sourceCropBottom}};
//发送给Anbox的Surface处理
frame_layers.push_back(r);
}
void rcPostAllLayersDone() {
if (composer) composer->submit_layers(frame_layers);
//提交Layer后刷新缓冲区
frame_layers.clear();
}
</code></pre>
<h5 id="gralloc模块"><a class="header" href="#gralloc模块">Gralloc模块</a></h5>
<h6 id="①-初步介绍-1"><a class="header" href="#①-初步介绍-1">① 初步介绍</a></h6>
<p> 在android图形架构中,Gralloc属于低级别组件,用于给图形缓冲队列进行缓冲区分配,是一个内存分配器。其通过<code>用法标志</code>执行缓冲区分配。用法标志包括以下属性:</p>
<ul>
<li>从软件 (CPU) 访问内存的频率</li>
<li>从硬件 (GPU) 访问内存的频率</li>
<li>是否将内存用作 OpenGL ES (GLES) 纹理</li>
<li>视频编码器是否会使用内存</li>
</ul>
<p> 例如,如果生产方的缓冲区格式指定 <code>RGBA_8888</code> 像素,并且生产方指明将从软件访问缓冲区,Gralloc将以R-G-B-A的顺序为每个像素创建一个4字节的缓冲区。如果情况相反,生产方指明仅从硬件访问其缓冲区且缓冲区作为 GLES 纹理,那么Gralloc可以做任何GLES驱动想要做的事情,比如BGRA排序、非线性swizzled布局和其他颜色格式。允许硬件使用其首选格式可以提高性能。</p>
<p> Gralloc 返回的句柄可以通过 Binder 在进程之间进行传递。</p>
<h6 id="②-gralloc模块的重要实现"><a class="header" href="#②-gralloc模块的重要实现">② Gralloc模块的重要实现</a></h6>
<p><strong>Gralloc在安卓中的实现</strong></p>
<p> Gralloc主要定义了以<code>HAL_MODULE_INFO_SYM</code>为符号的类型为<code>private_module_t</code>的结构体</p>
<pre><code class="language-c">//节选自gralloc.cpp
static struct hw_module_methods_t gralloc_module_methods = {
.open = gralloc_device_open
};
struct private_module_t HAL_MODULE_INFO_SYM = {
.base = {
.common = {
.tag = HARDWARE_MODULE_TAG,
.version_major = 1,
.version_minor = 0,
.id = GRALLOC_HARDWARE_MODULE_ID,
.name = "Graphics Memory Allocator Module",
.author = "The Android Open Source Project",
.methods = &gralloc_module_methods
},
.registerBuffer = gralloc_register_buffer,
.unregisterBuffer = gralloc_unregister_buffer,
.lock = gralloc_lock,
.unlock = gralloc_unlock,
},
.framebuffer = 0,
.flags = 0,
.numBuffers = 0,
.bufferMask = 0,
.lock = PTHREAD_MUTEX_INITIALIZER,
.currentBuffer = 0,
};
</code></pre>
<p> <code>private_module_t</code>用于描述Gralloc模块下的系统帧缓冲区信息,主要作用是将图形缓冲区渲染到帧缓冲区。</p>
<pre><code class="language-c">//节选于gralloc/gralloc_priv.h
struct private_module_t {
gralloc_module_t base;
private_handle_t* framebuffer; //指向系统帧缓冲区的句柄
uint32_t flags; //用来标志系统帧缓冲区是否支持双缓冲
uint32_t numBuffers;//表示系统帧缓冲区包含有多少个图形缓冲区
uint32_t bufferMask; //记录系统帧缓冲区中的图形缓冲区的使用情况
pthread_mutex_t lock; //一个互斥锁,用来保护结构体private_module_t的并行访
buffer_handle_t currentBuffer; //用来描述当前正在被渲染的图形缓冲区
int pmem_master;
void* pmem_master_base;
struct fb_var_screeninfo info; //保存设备显示屏的动态属性信息
struct fb_fix_screeninfo finfo; //保存设备显示屏的固定属性信息
float xdpi; //描述设备显示屏在宽度
float ydpi; //描述设备显示屏在高度
float fps; //用来描述显示屏的刷新频率
};
</code></pre>
<p> <code>gralloc_module_t</code>用于描述gralloc模块信息,主要用于分配或者释放图形缓冲区。</p>
<pre><code class="language-c">//节选于hardware/gralloc.h
typedef struct gralloc_module_t {
struct hw_module_t common;
int (*registerBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);//映射一块图形缓冲区到一个进程的地址空间去
int (*unregisterBuffer)(struct gralloc_module_t const* module,
buffer_handle_t handle);//取消映射一块图形缓冲区到一个进程的地址空间去
int (*lock)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr);//锁定一个指定的图形缓冲区
int (*unlock)(struct gralloc_module_t const* module,
buffer_handle_t handle);//解锁一个指定的图形缓冲区
int (*perform)(struct gralloc_module_t const* module,
int operation, ... );
int (*lockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
void** vaddr, int fenceFd);
int (*unlockAsync)(struct gralloc_module_t const* module,
buffer_handle_t handle, int* fenceFd);
int (*lockAsync_ycbcr)(struct gralloc_module_t const* module
buffer_handle_t handle, int usage,
int l, int t, int w, int h,
struct android_ycbcr *ycbcr, int fenceFd);
void* reserved_proc[3];
} gralloc_module_t;
</code></pre>
<p> <code>alloc_device_t</code>用于描述gralloc设备的信息。</p>
<pre><code class="language-c">//节选于hardware/gralloc.h
typedef struct alloc_device_t {
struct hw_device_t common;
int (*alloc)(struct alloc_device_t* dev,
int w, int h, int format, int usage,
buffer_handle_t* handle, int* stride);//用于分配一块图形缓冲区
int (*free)(struct alloc_device_t* dev,
buffer_handle_t handle);//用于释放指定的图形缓冲区
void (*dump)(struct alloc_device_t *dev, char *buff, int buff_len);
void* reserved_proc[7];
} alloc_device_t;
</code></pre>
<p> 还有<code>hw_module_t</code>主要用于关联模块和设备,其在hardware.h被定义。<br />
在<code>gralloc.h</code>中,还定义了<code>GRALLOC_HARDWARE_GPU0</code>设备,其主要是用于分配图形缓冲区,hw_module_t用于描述硬件抽象层Gralloc模块,而hw_device_t则用于描述硬件抽象层Gralloc设备,通过硬件抽象层设备可以找到对应的硬件抽象层模块。</p>
<p> 图形缓冲区的结构则用<code>private_handle_t</code>来描述</p>
<pre><code class="language-c">//节选于gralloc/gralloc_priv.h
struct private_handle_t : public native_handle {
#else
struct private_handle_t {
struct native_handle nativeHandle;
#endif
enum {
PRIV_FLAGS_FRAMEBUFFER = 0x00000001
};
// file-descriptors
int fd; //指向一个文件描述符,这个文件描述符要么指向帧缓冲区设备,要么指向一块匿名共享内存
// ints
int magic;
int flags;//用来描述一个缓冲区的标志,当一个缓冲区的标志值等于PRIV_FLAGS_FRAMEBUFFER的时候,就表示它是在帧缓冲区中分配的。
int size;//用来描述一个缓冲区的大小
int offset;//用来描述一个缓冲区的偏移地址
// FIXME: the attributes below should be out-of-line
uint64_t base __attribute__((aligned(8)));//用来描述一个缓冲区的实际地址
int pid;//用来描述一个缓冲区的创建者的PID
</code></pre>
<p> <code>GRALLOC_HARDWARE_GPU0</code>设备使用结构体<code>alloc_device_t</code>来描述。结构体<code>alloc_device_t</code>有两个成员函数alloc和free,在上文当中[alloc_device_t用于描述gralloc设备的信息]已经提及。</p>
<pre><code class="language-c">//节选于hardware/gralloc.h
static inline int gralloc_open(const struct hw_module_t* module,
struct alloc_device_t** device) {
return module->methods->open(module,
GRALLOC_HARDWARE_GPU0, (struct hw_device_t**)device);
}
</code></pre>
<p> module指向的是一个用来描述Gralloc模块的<code>hw_module_t</code>结构体,它的成员变量methods所指向的一个<code>hw_module_methods_t</code>结构体的成员函数open指向了Gralloc模块中的函数<code>gralloc_device_open</code>。这里传入的设备名为<code>GRALLOC_HARDWARE_GPU0</code>,表示当前打开的是gpu设备。</p>
<p> 下面这个函数主要是用来创建一个<code>gralloc_context_t</code>结构体,并且对它的成员变量device进行初始化。结构体<code>gralloc_context_t</code>的成员变量device的类型为<code>gralloc_device_t</code>,它用来描述一个gralloc设备。前面提到,gralloc设备是用来分配和释放图形缓冲区的,这是通过调用它的成员函数alloc和free来实现的。从这里可以看出,函数<code>gralloc_device_open</code>所打开的gralloc设备的成员函数alloc和free分别被设置为Gralloc模块中的函数<code>gralloc_alloc</code>和<code>gralloc_free</code>。</p>
<p> Gralloc主要就是通过上面两大函数来进行缓冲区的分配和释放。</p>
<pre><code class="language-c">//节选自gralloc.cpp
int gralloc_device_open(const hw_module_t* module, const char* name,
hw_device_t** device)
{
int status = -EINVAL;
if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
gralloc_context_t *dev;
dev = (gralloc_context_t*)malloc(sizeof(*dev));
/* initialize our state here */
memset(dev, 0, sizeof(*dev));
/* initialize the procs */
dev->device.common.tag = HARDWARE_DEVICE_TAG; // 这是一个硬件模块标记
dev->device.common.version = 0;
dev->device.common.module = const_cast<hw_module_t*>(module);
dev->device.common.close = gralloc_close; // gralloc关闭
dev->device.alloc = gralloc_alloc; //分配图形缓冲区
dev->device.free = gralloc_free; // 释放图形缓冲区
*device = &dev->device.common;
status = 0;
} else {
status = fb_device_open(module, name, device);
}
return status;
}
</code></pre>
<p><strong>Anbox部分重要实现</strong></p>
<p> 上面已经针对Gralloc模块基础进行来介绍,为了了解这一部分的实现原理,这里只关注整个Gralloc的HAL模块中Anbox注册的主要函数gralloc_alloc和gralloc_free进行介绍,而其中用于帧缓冲刷新的fb_post,Anbox并未实现。</p>
<p> <strong>gralloc_alloc及gralloc_free函数的实现</strong></p>
<p> 首先这里介绍gralloc_alloc以及gralloc_free函数,这里本质就是实现图形缓冲区的申请与释放,由于代码量较多,这里截取主要部分。</p>
<pre><code class="language-cpp">//节选自:vendor/anbox/android/opengl/system/gralloc/gralloc.cpp
//申请图形缓冲区
static int gralloc_alloc(alloc_device_t* dev,
int w, int h, int format, int usage,
buffer_handle_t* pHandle, int* pStride)
{
//当前gralloc设备
gralloc_device_t *grdev = (gralloc_device_t *)dev;
//一些标记,不同标记表面当前Gralloc申请的缓冲区的用途,比如用于camera的缓冲区
//
// Note: in screen capture mode, both sw_write and hw_write will be on
// and this is a valid usage
//
bool sw_write = (0 != (usage & GRALLOC_USAGE_SW_WRITE_MASK));
bool hw_write = (usage & GRALLOC_USAGE_HW_RENDER);
bool sw_read = (0 != (usage & GRALLOC_USAGE_SW_READ_MASK));
bool hw_cam_write = usage & GRALLOC_USAGE_HW_CAMERA_WRITE;
bool hw_cam_read = usage & GRALLOC_USAGE_HW_CAMERA_READ;
bool hw_vid_enc_read = usage & GRALLOC_USAGE_HW_VIDEO_ENCODER;
// 缓冲区大小,Anbox的这一模块是采用了ashmem这一共享内存来申请缓冲区
int ashmem_size = 0;
int stride = w;
GLenum glFormat = 0;
GLenum glType = 0;
int bpp = 0;
int align = 1;
// 针对各种图像格式选择不同的像素位数(bpp)大小、格式(glFormat)等配置信息
switch (format) {
case HAL_PIXEL_FORMAT_RGBA_8888:
case HAL_PIXEL_FORMAT_RGBX_8888:
case HAL_PIXEL_FORMAT_BGRA_8888:
bpp = 4;
glFormat = GL_RGBA;
glType = GL_UNSIGNED_BYTE;
break;
case HAL_PIXEL_FORMAT_RGB_888:
bpp = 3;
glFormat = GL_RGB;
glType = GL_UNSIGNED_BYTE;
break;
default:
ALOGE("gralloc_alloc: Unknown format %d", format);
return -EINVAL;
}
//针对不同用途给ashmem_size增加大小
if (sw_read || sw_write || hw_cam_write || hw_vid_enc_read) {
// keep space for image on guest memory if SW access is needed
// or if the camera is doing writing
if (yuv_format) {
size_t yStride = (w*bpp + (align - 1)) & ~(align-1);
size_t uvStride = (yStride / 2 + (align - 1)) & ~(align-1);
size_t uvHeight = h / 2;