第4章 启动流程分析:SurfaceFlinger进程启动、init.rc配置、main函数入口
好,咱们今天来聊聊SurfaceFlinger是怎么启动的。说实话,很多做Android开发的同学,对SurfaceFlinger的印象就是「一个系统服务」,但具体它怎么起来的、谁把它拉起来的,往往说不清楚。我当年刚接触这块时也懵,后来啃了源码才搞明白。
这一章,我们就从init.rc开始,一路跟到main函数入口。你跟着我走一遍,以后面试或者调试时心里就有底了。
4.1 init.rc 中的 SurfaceFlinger 配置
Android系统启动时,init进程会解析init.rc文件。SurfaceFlinger作为一个native服务,它的启动配置就写在这里。你想想看,系统里那么多服务,凭什么SurfaceFlinger能优先启动?答案就在它的启动优先级里。
我们来看一段典型的配置:
service surfaceflinger /system/bin/surfaceflinger
class core
user system
group graphics drmrpc
onrestart restart zygote
writepid /dev/cpuset/system-background/tasks
shutdown critical
这里有几个关键点,我一个个说:
- class core:表示SurfaceFlinger属于核心服务。核心服务在系统启动早期就会被拉起,比main class的服务要早。我遇到过一个问题,第三方ROM把surfaceflinger改成了main class,结果开机后黑屏了很久——因为其他依赖它的服务等不及了。
- user system / group graphics:以system用户运行,属于graphics组。这意味着它能访问GPU、帧缓冲区等图形硬件资源。嗯,这里要注意,如果group里少了drmrpc,某些设备上DRM相关的调用会失败。
- onrestart restart zygote:这条很关键。SurfaceFlinger挂了,Zygote也得重启。为什么?因为Zygote fork出来的所有应用进程都依赖SurfaceFlinger做合成。SurfaceFlinger一挂,所有窗口都刷不出来,应用活着也没意义。
- shutdown critical:关机时SurfaceFlinger是最后一批停止的服务。我调试过一个关机花屏的问题,就是因为SurfaceFlinger被提前杀掉了,导致最后一帧画面没合成完。
核心要点:SurfaceFlinger是core class服务,优先级极高。它挂了会连带重启Zygote,所以千万别在生产环境里随便kill surfaceflinger进程。
4.2 main函数入口:从init到SurfaceFlinger
init进程通过fork+execve启动surfaceflinger二进制文件。入口在哪?在frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp里。
我们直接看代码:
int main(int argc, char** argv) {
// 1. 设置binder线程池
ProcessState::self()->setThreadPoolMaxThreadCount(4);
sp<ProcessState> ps(ProcessState::self());
ps->startThreadPool();
// 2. 创建SurfaceFlinger实例
sp<SurfaceFlinger> flinger = new SurfaceFlinger();
// 3. 注册服务到ServiceManager
flinger->init();
sp<IServiceManager> sm(defaultServiceManager());
sm->addService(String16(SurfaceFlinger::getServiceName()), flinger, false);
// 4. 进入主循环
IPCThreadState::self()->joinThreadPool();
return 0;
}
这段代码看着简单,但每一步都有讲究。我拆开来讲:
4.3 关键步骤详解
4.3.1 Binder线程池设置
setThreadPoolMaxThreadCount(4),为什么是4?不是8也不是16?
我个人习惯是,binder线程数不宜过多。SurfaceFlinger本身是事件驱动的,大部分时间在等待VSYNC。线程多了反而增加上下文切换开销。4个线程,一个处理VSYNC,一个处理事务,一个处理合成,一个备用,刚刚好。
我曾经在一个8核平板上把线程数改成8,结果合成延迟反而增加了。后来用systrace一抓,发现大量时间花在线程切换上。嗯,有时候「少即是多」。
4.3.2 SurfaceFlinger实例化
new SurfaceFlinger() 这一步会做什么?
- 初始化成员变量,比如mCurrentState、mDrawingState
- 创建HWComposer实例(硬件合成器)
- 创建EventThread(VSYNC线程)
- 初始化DisplayDevice(显示设备)
注意,构造函数里不会做太重的操作。真正的初始化在init()方法里。
4.3.3 init()方法做了什么
我们看看init()的核心逻辑:
void SurfaceFlinger::init() {
// 1. 读取属性配置
property_get("debug.sf.enable_hwc_vds", ...);
// 2. 初始化EGL/GLES
mEGLDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(mEGLDisplay, &major, &minor);
// 3. 初始化HWComposer
mHwc->init(this);
// 4. 启动VSYNC事件线程
mEventThread->run("EventThread", PRIORITY_URGENT_DISPLAY);
// 5. 启动合成线程
mRenderEngine->init();
// 6. 注册监听
sp<IBinder> displayToken = ...;
onHotplug(displayToken, true);
}
这里有个坑,我提醒一下:init()里如果HWComposer初始化失败,SurfaceFlinger不会崩溃,而是会降级到纯GPU合成。我曾经在一款没有HWC的平板项目上,就靠这个降级机制跑起来的。当然,性能嘛...你懂的,帧率直接砍半。
调试技巧:如果开机后SurfaceFlinger启动异常,可以抓取logcat中带有SurfaceFlinger标签的日志。重点关注init()阶段的输出,比如EGL ready、HWC init等关键节点。
4.4 启动时序图
为了让你更直观地理解,我画了个简化的时序:
| 步骤 | 执行者 | 动作 | 说明 |
|---|---|---|---|
| 1 | init进程 | 解析init.rc | 找到surfaceflinger服务定义 |
| 2 | init进程 | fork+execve | 启动surfaceflinger二进制 |
| 3 | main函数 | 设置binder线程池 | 4个binder线程 |
| 4 | main函数 | new SurfaceFlinger() | 创建实例,初始化成员 |
| 5 | SurfaceFlinger | init() | EGL、HWC、VSYNC初始化 |
| 6 | main函数 | addService | 注册到ServiceManager |
| 7 | main函数 | joinThreadPool | 进入binder监听循环 |
4.5 常见启动问题排查
说几个我实际踩过的坑:
问题1:SurfaceFlinger启动后立即crash
原因通常是HWC HAL库不兼容。检查/vendor/lib64/hw/hwcomposer.*.so是否存在,以及权限是否正确。我曾经遇到过selinux策略导致HWC无法访问GPU节点,开机直接SIGSEGV。
问题2:SurfaceFlinger启动成功但黑屏
检查init.rc中group graphics drmrpc是否配置正确。少了drmrpc组,DRM接口调用会返回权限错误。另外,检查/dev/dri/card0的权限,确保system用户可读写。
问题3:开机动画不显示
这通常是SurfaceFlinger还没注册到ServiceManager,BootAnim就急着去获取服务了。检查init.rc中surfaceflinger和bootanim的启动顺序,确保surfaceflinger在core class中先启动。
4.6 小结
这一章我们走完了SurfaceFlinger从init.rc到main函数的完整启动链路。说白了,就是三步:
- init进程根据rc配置拉起surfaceflinger进程
- main函数创建SurfaceFlinger实例并初始化
- 注册到ServiceManager,进入binder循环等待客户端请求
下一章我们会深入SurfaceFlinger的合成循环,看看VSYNC来了之后,它到底是怎么一帧一帧把画面刷出来的。到时候我会结合systrace,带你手把手分析合成延迟问题。
好,今天就到这儿。有问题欢迎在评论区交流,或者去我的公众号「蓝海资料掘金营」找我,微信deep3321。咱们下章见。