说起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);......// 初始化uidpkg.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 trueif (!create) {return null;}s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);// 创建新的uids.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 nullif (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 packagesPackageSetting dis = mDisabledSysPackages.get(name);if (dis != null) {......} else {// Assign new user idp.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

更多精彩文章,欢迎大家关注阳哥的公众号:阳哥说技术。

uid、userId和appId之间不得不说的事 (一)相关推荐

  1. uid、userId和appId之间不得不说的事 (二)

    大家好,我是阳哥!在uid.userId和appId之间不得不说的事(一)中,阳哥介绍了uid是如何生成的以及系统进程uid和用户名USER之间是如何关联在一起的.本篇文章,阳哥将为大家介绍userI ...

  2. Android Uid,UserId,AppId,Pid

    Android Uid,UserId,AppId,Pid 1.Uid 1.1.Uid来源(android 9.0) 1.2.查看Uid 2.UserId 3.AppId 4.Pid 1.Uid and ...

  3. 云图说|ROMA演进史:一个ROMA与应用之间不得不说的故事

    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说).深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云.更多精彩内容请单击此处. 摘要: 华为云ROMA源 ...

  4. VMware vSAN网络设计中不得不说的事

    VMware vSAN网络设计中不得不说的事 原创 李严省 虚实之路 2018-06-07 VMware vSAN分布式存储我想大家都已经很了解了,我再简单的啰嗦一下.VMware vSAN架构如下图 ...

  5. python与excel的关系-Python 与 Excel 不得不说的事

    原标题:Python 与 Excel 不得不说的事 数据处理是 Python 的一大应用场景,而 Excel 则是最流行的数据处理软件.因此用 Python 进行数据相关的工作时,难免要和 Excel ...

  6. 我与前端之间不得不说的三天两夜之javaScript

    前端基础之JavaScript JavaScript概述 JavaScript的历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后 ...

  7. int, float, double之间不得不说的故事

    抱歉我用了一个这么"二"的题目,不过二点就二点吧,希望内容还不算太二. 其实学习过编程的同学,都对这三个东西再熟悉不过了.int,又称作整型,在.net中特指的是Int32,为32 ...

  8. 我与前端之间不得不说的三天两夜之html基础

    HTML 初识 分类 cs模式 client-server bs模式 Browser-server web服务本质 from socket import *def main():service=soc ...

  9. 统计学必知!「标准差amp;方差」之间不得不说的关系

    来  源:博客园/作  者:Climber 简单来说,标准差是一组数值自平均值分散程度的一种测量观念.一个较大的标准差,代表大部分的数值和其平均值之间差异较大,一个较小的标准差,代表这些数值较接近平均 ...

最新文章

  1. 2017校赛 问题 D: 我知道了,你知道了吗?【递归】
  2. 沃舍尔算法_[数据结构拾遗]图的最短路径算法
  3. LNMP(nginx防盗链,访问控制,解析php相关配置,Nginx代理,常见502问题)
  4. Java SimpleTimeZone equals()方法与示例
  5. php laravel 优点,Laravel 特点有哪些?
  6. 配置Tomcat使用https协议(配置SSL协议)
  7. 【arc068F】Solitaire
  8. 继扫楼推广后,P图病历也可发起筹款,水滴筹回应...
  9. 3认证老外主任_首批18款App认证名单公布 未来将开展数据安全管理认证
  10. 【poj3468】A Simple Problem with Integers
  11. 【运维安全】-sqlmap使用
  12. java sql2000驱动下载_SQL Server 2000 JDBC驱动程序
  13. 测试吃鸡游戏帧数软件,帧数暴涨10%+ RX 560D开核“吃鸡”测试
  14. dnf时装补丁教程_【时装补丁制作】消灭伸手党!最详细的图文教程~
  15. dws中间表模型设计: 页面受访明细宽表
  16. 【python】一个整数,它加上100后是一个完全平方数,再加上168又是一个完全平方数,请问该数是多少?
  17. EasyFlash 里的 EF_WRITE_GRAN
  18. 获取硬盘序列号的真正方法!!
  19. 单片机毕设 stm32人体健康状态检测系统(项目开源)
  20. 【华为云技术分享】最终,我决定将代码迁出x86架构!

热门文章

  1. 设计模式(1)-创建模式、结构模式、行为模式的区别
  2. SQL学习 | 用SQL进行日期截取和日期加减
  3. 如何将两部手机内容互换_两个手机怎么互换机身内存 呼唤方法
  4. vscode官网下载失败或者下载速度太慢
  5. 洛杉矶房价预测-数据快查表
  6. 陕西师范大学计算机学院课表,2019年陕西师范大学数学与信息科学学院课程表.doc...
  7. ambari 2.7.6源码编译指南
  8. 使用O2OA二次开发搭建企业办公平台(十二)流程开发篇:报销审批流程需求和应用创建
  9. 鲲鹏HCIA系列笔记题库汇总(内含PDF)
  10. 便签添加到桌面后怎么删除?电脑便签支持找回删除便签的软件是哪个?