Android7.0 PackageManagerService (5) installd

在之前的博客中已经提到过,当SystemServer创建PKMS时,在其构造函数中传入了一个Installer对象。
Installer是一个系统服务,可以和installd通信,完成一些重要的工作,例如利用dexopt函数对APK文件进行dex优化;存储空间不足时,利用freeCache函数清理存储空间。

接下来,我们就分析一下installd及一些重要工作的流程。

一、installd的初始化
1、installd启动
在init.rc中,有以下代码片段:

....... service installd /system/bin/installd class main socket installd stream 600 system system .......

容易看出installd将作为service被init进程启动,同时会创建一个名为installd的socket。

在frameworks/native/cmds/installd/installd.cpp中:

int main(const int argc, char *argv[]) {
    return android::installd::installd_main(argc, argv);
}

static int installd_main(const int argc ATTRIBUTE_UNUSED, char *argv[]) {
    .........
    //以下初始化全局变量,包括创建data下的一些目录等
    if (!initialize_globals()) {
        ALOGE("Could not initialize globals; exiting.\n");
        exit(1);
    }

    if (initialize_directories() < 0) {
        ALOGE("Could not create directories; exiting.\n");
        exit(1);
    }
    ...........
    //得到"installd" socket
    lsocket = android_get_control_socket(SOCKET_PATH);
    ...........
    //"installd"变成服务端
    if (listen(lsocket, 5)) {
        ALOGE("Listen on socket failed: %s\n", strerror(errno));
        exit(1);
    }
    fcntl(lsocket, F_SETFD, FD_CLOEXEC);

    for (;;) {
        alen = sizeof(addr);
        //接受Java层的installer服务连接,形成与之连接的socket "s"
        s = accept(lsocket, &addr, &alen);
        ..........
        fcntl(s, F_SETFD, FD_CLOEXEC);
        ..........
        for (;;) {
            unsigned short count;
            //读取收到的消息的长度
            if (readx(s, &count, sizeof(count))) {
                ALOGE("failed to read size\n");
                break;
            }

            //判断有效性
            if ((count < 1) || (count >= BUFFER_MAX)) {
                ALOGE("invalid size %d\n", count);
                break;
            }

            //读取cmd
            if (readx(s, buf, count)) {
                ALOGE("failed to read command\n");
                break;
            }
            ..........
            buf[count] = 0;
            //执行cmd
            if (execute(s, buf)) break;
        }
        .........
        close(s);
    }

    return 0;
}

上面的代码中,我们省去了selinux相关的代码,只保留主干。
从主干代码容易看出,installd整体的结构非常简单,其实就是启动后,获取作为服务端的socket “installd”;
然后,监听”installd”,等待Java层installer服务的连接及命令的到来。

2、命令处理方式
一旦installd收到命令后,将调用execute函数进行处理,其代码如下:

static int execute(int s, char cmd[BUFFER_MAX]) {
    char reply[REPLY_MAX];
    char *arg[TOKEN_MAX+1];
    .......
    arg[0] = cmd;
    //解析参数的个数
    while (*cmd) {
        //当发现空格时
        if (isspace(*cmd)) {
            *cmd++ = 0;
            //参数个数+1
            n++;
            //保存参数
            arg[n] = cmd;
            if (n == TOKEN_MAX) {
                ALOGE("too many arguments\n");
                goto done;
            }
        }
        if (*cmd) {
          cmd++;
        }
    }

    //cmds为一个数组,保存了不同命令及对应的处理函数
    for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
        if (!strcmp(cmds[i].name,arg[0])) {
            if (n != cmds[i].numargs) {
                //log
                .......
            } else {
                //参数正确时,调用对应的执行函数进行处理
                ret = cmds[i].func(arg + 1, reply);
            }
        }
    }
    ...........
}

execute函数的思路很清晰,类似于收到命令后查表找到对应的处理函数,然后传入参数并执行函数。

在这一部分的最后,我们看看cmds中的内容:

struct cmdinfo {
    const char *name;
    unsigned numargs;
    int (*func)(char **arg, char reply[REPLY_MAX]);
};

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },

    { "create_app_data",      7, do_create_app_data },
    { "restorecon_app_data",  6, do_restorecon_app_data },
    { "migrate_app_data",     4, do_migrate_app_data },
    { "clear_app_data",       5, do_clear_app_data },
    { "destroy_app_data",     5, do_destroy_app_data },
    { "move_complete_app",    7, do_move_complete_app },
    { "get_app_size",         6, do_get_app_size },
    { "get_app_data_inode",   4, do_get_app_data_inode },

    { "create_user_data",     4, do_create_user_data },
    { "destroy_user_data",    3, do_destroy_user_data },

    { "dexopt",              10, do_dexopt },
    { "markbootcomplete",     1, do_mark_boot_complete },
    { "rmdex",                2, do_rm_dex },
    { "freecache",            2, do_free_cache },
    { "linklib",              4, do_linklib },
    { "idmap",                3, do_idmap },
    { "createoatdir",         2, do_create_oat_dir },
    { "rmpackagedir",         1, do_rm_package_dir },
    { "clear_app_profiles",   1, do_clear_app_profiles },
    { "destroy_app_profiles", 1, do_destroy_app_profiles },
    { "linkfile",             3, do_link_file },
    { "move_ab",              3, do_move_ab },
    { "merge_profiles",       2, do_merge_profiles },
    { "dump_profiles",        3, do_dump_profiles },
};

可以比较清晰地看出cmds数组中存储的就是cmdinfo结构体,结构体中定义了名称、参数个数及对应的处理函数。

二、installer服务的启动
1、启动
在SystemServer.java的startBootstrapServices函数中:

private void startBootstrapServices() {
    // Wait for installd to finish starting up so that it has a chance to
    // create critical directories such as /data/user with the appropriate
    // permissions. We need this to complete before we initialize other services.
    Installer installer = mSystemServiceManager.startService(Installer.class);
    ..............
}

public SystemService startService(String className) {
    final Class<SystemService> serviceClass;
    try {
        serviceClass = (Class<SystemService>)Class.forName(className);
    } catch (ClassNotFoundException ex) {
        //抛出异常
        ..........
    }
    return startService(serviceClass);
}

public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        final String name = serviceClass.getName();
        .............
        final T service;
        try {
            //反射调用构造函数
            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
        } catch(.....) {
            .............
        }.......

        //SystemServiceManager的mServices中存储所有由其启动的服务
        mServices.add(service);

        // Start it.
        try {
            service.onStart();
        } catch (RuntimeException ex) {
            //抛出异常
            ...........
        }
        return service;
    } finally {
        //log
        ...........
    }
}

从上面的代码容易看出,SystemServer利用SystemServerManager的startService启动服务时,其实就是通过反射来创建服务对象,然后调用服务对象的onStart函数。

因此,对于installer服务,我们此时只需要关注其构造函数和onStart函数:

public final class Installer extends SystemService {
    ........
    public Installer(Context context) {
        super(context);
        //与installd沟通的桥梁
        mInstaller = new InstallerConnection();
    }
    ......
    public void onStart() {
        Slog.i(TAG, "Waiting for installd to be ready.");
        //调用InstallerConnection的waitForConnection函数
        mInstaller.waitForConnection();
    }
    ........
}

从上面的代码可以看出,Installer继承自SystemService,其构造函数主要创建出InstallerConnection,然后在onStart函数中调用InstallerConnection的waitForConnection函数。

2、连接installd
接下来,我们就来看看InstallerConnection的waitForConnection函数:

public void waitForConnection() {
    for (;;) {
        try {
            //"ping"应该只是确认installd是否存活
            execute("ping");
            return;
        } catch (InstallerException ignored) {
        }
        Slog.w(TAG, "installd not ready");
        SystemClock.sleep(1000);
    }
}

跟进InstallerConnection的execute函数:

public String[] execute(String cmd, Object... args) throws InstallerException {
    final StringBuilder builder = new StringBuilder(cmd);
    //处理参数
    for (Object arg : args) {
        String escaped;
        if (arg == null) {
            escaped = "";
        } else {
            escaped = String.valueOf(arg);
        }

        if (escaped.indexOf('\0') != -1 || escaped.indexOf(' ') != -1 || "!".equals(escaped)) {
            //错误格式抛出异常
            ........
        }

        if (TextUtils.isEmpty(escaped)) {
            escaped = "!";
        }
        builder.append(' ').append(escaped);
    }

    //transact发送命令及对应参数,并返回结果
    final String[] resRaw = transact(builder.toString()).split(" ");
    int res = -1;
    try {
        //解析结果
        res = Integer.parseInt(resRaw[0]);
    } catch (ArrayIndexOutOfBoundsException | NumberFormatException ignored) {
    }

    if (res != 0) {
        throw new InstallerException(
                "Failed to execute " + cmd + " " + Arrays.toString(args) + ": " + res);
    }
    return resRaw;
}

容易看出transact函数负责进行实际的传输工作:

public synchronized String transact(String cmd) {
    ............
    //连接"installd"
    if (!connect()) {
        Slog.e(TAG, "connection failed");
        return "-1";
    }

    //发送命令
    if (!writeCommand(cmd)) {
        Slog.e(TAG, "write command failed? reconnect!");
        if (!connect() || !writeCommand(cmd)) {
            return "-1";
        }
    }

    //读取返回结果
    final int replyLength = readReply();
    if (replyLength > 0) {
        String s = new String(buf, 0, replyLength);
        ........
        return s;
    } else {
        ........
        return "-1";
    }
}

从上面的代码来看,整个通信结构非常的清晰,典型的socket通信架构。
为了分析的完整性,我们再看看connect、writeCommand和readReply函数:

private boolean connect() {
    //第一次才需要进行实际的连接,之后就不需要了
    if (mSocket != null) {
        return true;
    }
    ......
    try {
        mSocket = new LocalSocket();

        //得到"installd"目的端地址
        LocalSocketAddress address = new LocalSocketAddress("installd",
                LocalSocketAddress.Namespace.RESERVED);

        //进行连接的过程
        mSocket.connect(address);

        //以下得到输入流和输出流
        mIn = mSocket.getInputStream();
        mOut = mSocket.getOutputStream();
    } catch (IOException ex) {
        disconnect();
        return false;
    }
    return true;
}

private boolean writeCommand(String cmdString) {
    final byte[] cmd = cmdString.getBytes();
    final int len = cmd.length;
    ........
    buf[0] = (byte) (len & 0xff);
    buf[1] = (byte) ((len >> 8) & 0xff);
    try {
        //写入长度
        mOut.write(buf, 0, 2);
        //写入命令和参数
        mOut.write(cmd, 0, len);
    } catch (IOException ex) {
        Slog.e(TAG, "write error");
        disconnect();
        return false;
    }
    return true;
}

private int readReply() {
    if (!readFully(buf, 2)) {
        return -1;
    }

    final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
    ........
    if (!readFully(buf, len)) {
        return -1;
    }

    return len;
}

private boolean readFully(byte[] buffer, int len) {
    try {
        //还是靠mIn来读取数据
        Streams.readFully(mIn, buffer, 0, len);
    } catch (IOException ioe) {
        Slog.e(TAG, "read exception");
        disconnect();
        return false;
    }
    ........
    return true;
}

至此Installer服务的启动主要的流程基本介绍完毕。
从上述代码可以看出,Installer服务完全可以看做是installd在Java层的代理。
该Java服务自身基本不承载任何主要的逻辑,就是通过Socket将请求发送给native层的installd进行处理。

接下来,我们以几个例子,看看installd的主要工作流程。

三、installd实例分析
1、ping命令
installd的cmds数组中定义:

....... { "ping", 0, do_ping }, .......

正如上文所述,在初始连接时,Java层的Installer服务将向installd发送ping命令。
此时,installd将调用do_ping函数进行处理。

static int do_ping(char **arg ATTRIBUTE_UNUSED, char reply[REPLY_MAX] ATTRIBUTE_UNUSED)
{
    return 0;
}

明显可见,do_ping进行任何实际的操作,只是返回执行结果。因此,初始时Java层发送ping请求,确实只是确认installd处于正常状态。

2、freeCache命令
在安装APK文件时,如果系统空间不足,最终将发送freeCache的命令给installd。
根据如下installd中cmds数组,我们知道将调用do_free_cache进行处理。

......... { "freecache", 2, do_free_cache }, .........

我们看看do_free_cache函数:

static int do_free_cache(char **arg, char reply[REPLY_MAX] ATTRIBUTE_UNUSED) {
    return free_cache(parse_null(arg[0]), (int64_t)atoll(arg[1]));
}

int free_cache(const char *uuid, int64_t free_size) {
    ...........
    //得到需要清理的路径,例如'/data'目录或Sd card中的路径
    auto data_path = create_data_path(uuid);

    avail = data_disk_free(data_path);
    ........
    //利用statfs函数得到路径对应的文件系统信息
    //利用非超级用户可获取的块数,乘以经过优化的每个传输块大小
    //得到当前系统的剩余空间大小
    avail = data_disk_free(data_path);
    .......
    //此时不需要free cache
    if (avail >= free_size) return 0;

    auto users = get_known_users(uuid);
    for (auto user : users) {
        //添加指定路径中的cache文件
        add_cache_files(cache, create_data_user_ce_path(uuid, user));
        add_cache_files(cache, create_data_user_de_path(uuid, user));
        add_cache_files(cache,
                StringPrintf("%s/Android/data", create_data_media_path(uuid, user).c_str()));
    }

    //逐级删除cache文件
    clear_cache_files(data_path, cache, free_size);
    finish_cache_collection(cache);

    //删除cache后,剩余可用内存大于要求,则返回0
    return data_disk_free(data_path) >= free_size ? 0 : -1;
}

四、总结
本篇博客重在分析installd与Installer服务之间的通信结构。
installd中有些命令的实现细节还是比较复杂的,自己也没有做太深入的了解,就不在这里赘述了。
大家感兴趣的话,可以自己再分析一下。

    原文作者:ZhangJianIsAStark
    原文地址: https://blog.csdn.net/gaugamela/article/details/52769139
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞