uid、userId和appId之间不得不说的事 (一)

说起Android OS中uid、userId、appId这三个概念,我想大部分人可能会对uid比较熟悉,而对userId、appId相对陌生,更不用说它们三者之间的关系。那么,到底它们代表着什么?它们之间又是如何联系在一块的呢?阳哥将通过两篇文章的篇幅为大家一一解答,本篇主要介绍uid。

uid是与Android进程有关的概念,Android Framework中有关uid的使用主要集中在与ApplicationInfo和Process这两个类相关的操作中,我们看一看它们是如何定义uid的。

The kernel user-ID that has been assigned to this application; currently this is not a unique ID (multiple applications can have the same uid).
—ApplicationInfo#uid

Returns the identifier of this process’s uid. This is the kernel uid that the process is running under, which is the identity of its app-specific sandbox. It is different from {@link #myUserHandle} in that a uid identifies a specific app sandbox in a specific user.
—Process#myUid()

众所周知,每一个Android应用进程都是从zygote fork出来的,站在Linux的角度,它们实际上也属于Linux进程。而Linux进程也有一个uid的概念,它的定义如下:

A user ID (UID) is a unique positive integer assigned by a Unix-like operating system to each user. Each user is identified to the system by its UID, and user names are generally used only as an interface for humans.
—LINFO

那么,这两个层面的uid是不是一样的?它们之间又有什么样的关系呢?事实上,不管是Android Framework层的uid,还是Linux层的uid,它们对应的都是同一个概念。参考以上三种定义,我们可以将uid的定义总结为:uid是一个基于特定用户的Android应用身份标识,同一Android应用中的所有进程共享同一个uid,它也可以在不同Android应用之间共享。

Android应用进程uid是在何时又由谁生成的呢?一个应用的ApplicationInfo的构建是在应用安装过程中完成的,那么我们自然会想到是由PackageManagerService(以下简称PMS)在解析、安装APK的时候创建了uid。

// 本文使用的源码版本为Android N
// PackageManagerService#scanPackageDirtyLI
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
    final int policyFlags, final int scanFlags, long currentTime, UserHandle user)
    throws PackageManagerException {

    ......
    SharedUserSetting suid = null;
    PackageSetting pkgSetting = null;
    ......
    
    synchronized (mPackages) {
        // Manifest使用了android:sharedUserId属性
        if (pkg.mSharedUserId != null) {
            suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, 0, true);
            if (suid == null) {
                throw new PackageManagerException(INSTALL_FAILED_INSUFFICIENT_STORAGE,
                        "Creating application package " + pkg.packageName
                        + " for shared user failed");
            }
            ......
        }
        ......

        // Just create the setting, don't add it yet. For already existing packages
        // the PkgSetting exists already and doesn't have to be created.
        pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
            destResourceFile, pkg.applicationInfo.nativeLibraryRootDir,
            pkg.applicationInfo.primaryCpuAbi,
            pkg.applicationInfo.secondaryCpuAbi,
            pkg.applicationInfo.flags, pkg.applicationInfo.privateFlags,
            user, false);

        ......
        // 初始化uid
        pkg.applicationInfo.uid = pkgSetting.appId;
        ......
    }
    ......
}

从上面的执行过程可以看到如果当前安装的应用声明了sharedUserId,那么会先创建SharedUserSetting,我们看看它是如何创建的:

// Settings#getSharedUserLPw
SharedUserSetting getSharedUserLPw(String name,
                                   int pkgFlags, int pkgPrivateFlags, boolean create) {
    SharedUserSetting s = mSharedUsers.get(name);
    if (s == null) {
        // create is true
        if (!create) {
            return null;
        }
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        // 创建新的uid
        s.userId = newUserIdLPw(s);
        Log.i(PackageManagerService.TAG, "New shared user " + name + ": id=" + s.userId);
        // < 0 means we couldn't assign a userid; fall out and return
        // s, which is currently null
        if (s.userId >= 0) {
            mSharedUsers.put(name, s);
        }
    }
    return s;
}

getSharedUserLPw方法首先会去检查mSharedUsers中是否有相同sharedUserId的SharedUserSetting记录,如果没有则依次创建一个新的SharedUserSetting和uid,并将它保存到mSharedUsers中。

如果当前安装的应用没有声明sharedUserId,即代表它不与其他应用共享uid,也就意味着它的uid是独一无二的。这个时候PMS会为该应用创建一个新的PackageSetting。

// Settings#getPackageLPw
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
        String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
        String legacyNativeLibraryPathString, String primaryCpuAbiString,
        String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
        UserHandle installUser, boolean add, boolean allowInstall, String parentPackage,
        List<String> childPackageNames) {
    PackageSetting p = mPackages.get(name);
    ......
    if (p == null) {
        if (origPackage != null) {
            ......
        } else {
            p = new PackageSetting(name, realName, codePath, resourcePath,
                    legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                    null /* cpuAbiOverrideString */, vc, pkgFlags, pkgPrivateFlags,
                    parentPackage, childPackageNames);
            p.setTimeStamp(codePath.lastModified());
            p.sharedUser = sharedUser;
            ......
            if (sharedUser != null) {
                p.appId = sharedUser.userId;
            } else {
                // Clone the setting here for disabled system packages
                PackageSetting dis = mDisabledSysPackages.get(name);
                if (dis != null) {
                ......
                } else {
                    // Assign new user id
                    p.appId = newUserIdLPw(p);
                }
            }
        }
        ......
    }
    ......
    return p;
}

getPackageLPw方法首先会使用应用包名name作为键值去mPackages中查找是否已经有对应的PackageSetting。因为这是一个新安装的应用,所以变量p和参数origPackage都为空,直接创建一个新的PackageSetting。接着,如果是共享用户就用sharedUser的userId作为uid,否则就使用newUserIdLPw方法创建一个新的uid。

通过之前的分析,可以发现无论是否使用sharedUserId,应用uid的生成都要借助newUserIdLPw函数来实现。毫无疑问,这个函数就是我们寻找的构建uid的地方。

// Settings#newUserIdLPw
// Returns -1 if we could not find an available UserId to assign
private int newUserIdLPw(Object obj) {
    // Let's be stupidly inefficient for now...
    final int N = mUserIds.size();
    for (int i = mFirstAvailableUid; i < N; i++) {
        if (mUserIds.get(i) == null) {
            mUserIds.set(i, obj);
            return Process.FIRST_APPLICATION_UID + i;
        }
    }
    // None left?
    if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
        return -1;
    }
    mUserIds.add(obj);
    return Process.FIRST_APPLICATION_UID + N;
}

mUserIds记录了uid和PackageSetting之间的映射关系,默认size为0。mFirstAvailableUid是一个初始值为0的静态变量,记录了首个可用的uid。所以对于第一个安装的应用来说,它的uid取值为Process.FIRST_APPLICATION_UID (10000)。后续安装的应用可以分配到的最大uid取值是Process.LAST_APPLICATION_UID (19999),如果不存在共享uid的应用,那么单个用户最多可以安装10000个应用,超过这个范围就会分配失败,抛异常。实际上,对于一个用户来说他能分配的uid取值范围为UserHandle.PER_USER_RANGE (100000)个。这其中既包括应用进程的uid取值范围,也包括常见的系统进程、隔离进程等的uid取值。

除了定义应用uid的取值范围,Process类也定义了获取当前进程uid、pid、ppid等信息的方法。

public class Process {
    ......
    /**
     * Defines the root UID.
     * @hide
     */
    public static final int ROOT_UID = 0;

    /**
     * Defines the UID/GID under which system code runs.
     */
    public static final int SYSTEM_UID = 1000;

    /**
     * Defines the UID/GID under which the telephony code runs.
     */
    public static final int PHONE_UID = 1001;

    /**
     * Defines the UID/GID for the user shell.
     * @hide
     */
    public static final int SHELL_UID = 2000;
    /**
     * Returns the identifier of this process's uid.  This is the kernel uid
     * that the process is running under, which is the identity of its
     * app-specific sandbox.  It is different from {@link #myUserHandle} in that
     * a uid identifies a specific app sandbox in a specific user.
     */
    public static final int myUid() {
        return Os.getuid();
    }
    ......
}

到这里,我们就有两种方法来获取应用进程的uid。一种是通过调用Process的myUid方法,另一种是查询ApplicationInfo的uid取值。实际上,我们也可以通过命令行这种更直观的方法来查看:
在这里插入图片描述
这里截取了一部分pid或ppid为2429的相关进程信息,第一列展示的是进程的用户名USER。以zygote进程为例,它的用户名是root,2429是它的pid,可以通过查看其对应的proc文件来获取zygote的uid信息:
在这里插入图片描述
可以看到zygote进程的uid和gid取值都是0(默认uid和gid取值相同),这和Process类中定义的ROOT_UID数值是一致的。

通过前文LINFO对uid的定义,我们知道Linux系统是通过uid来识别每一个用户的,那uid和用户又是如何关联起来的呢?Android将它们之间的映射关系定义在/system/core/include/private/android_filesystem_config.h中。

#define AID_ROOT             0  /* traditional unix root user */
#define AID_SYSTEM        1000  /* system server */
#define AID_RADIO         1001  /* telephony subsystem, RIL */
#define AID_BLUETOOTH     1002  /* bluetooth subsystem */
......
/* The range 2900-2999 is reserved for OEM, and must never be
* used here */
#define AID_OEM_RESERVED_START 2900
#define AID_OEM_RESERVED_END   2999
......
#define AID_APP          10000  /* first app user */
#define AID_USER        100000  /* offset for uid ranges for each user */
......

static const struct android_id_info android_ids[] = {
    { "root",          AID_ROOT, },
    { "system",        AID_SYSTEM, },
    { "radio",         AID_RADIO, },
    { "bluetooth",     AID_BLUETOOTH, },
    ......
}

android_filesystem_config.h定义了比Process更加丰富的Android ID (AID)信息,包含了所有的主用户uid/gid,还列出了OEM可定义的uid/gid范围,而且给出了用户名和uid的关系映射表android_ids。系统正是根据这张表来识别每一个进程的用户名。

android_ids只给出了系统进程uid和用户名USER之间的映射关系。那对于应用进程来说,它的用户名(比如:u0_a106)和uid之间是如何关联起来的呢?Android Framework层计算的uid是如何同步给Linux内核的呢?uid和userId、appId之间又有着什么样的关系呢?阳哥将在下篇文章为大家解答,敬请期待!

参考:

  1. http://www.linfo.org/uid.html
  2. https://source.android.com/devices/tech/config/filesystem
  3. https://pierrchen.blogspot.com/2016/09/an-walk-through-of-android-uidgid-based.html
更多精彩文章,欢迎大家关注阳哥的公众号:阳哥说技术。

在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值