第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 readyHWC 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函数的完整启动链路。说白了,就是三步:

  1. init进程根据rc配置拉起surfaceflinger进程
  2. main函数创建SurfaceFlinger实例并初始化
  3. 注册到ServiceManager,进入binder循环等待客户端请求

下一章我们会深入SurfaceFlinger的合成循环,看看VSYNC来了之后,它到底是怎么一帧一帧把画面刷出来的。到时候我会结合systrace,带你手把手分析合成延迟问题。

好,今天就到这儿。有问题欢迎在评论区交流,或者去我的公众号「蓝海资料掘金营」找我,微信deep3321。咱们下章见。