Android - Namespace

本文详细介绍了Android系统中如何利用Linux Namespace技术实现不同用户的隔离存储空间。从4.2版本开始,通过创建独立的mnt namespace并结合bind mount技术,确保每个用户拥有自己的外部存储空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这也是n年前的笔记,当时应该还是Android 4.X的时代。


Linux Namespace

Linux在fork/clone Process时,涉及到namespace的flag有:

#define CLONE_NEWNS         0x00020000  /* New mnt namespace group? */
#define CLONE_NEWUTS        0x04000000  /* New utsname group? */
#define CLONE_NEWIPC        0x08000000  /* New ipcs */
#define CLONE_NEWUSER       0x10000000  /* New user namespace */
#define CLONE_NEWPID        0x20000000  /* New pid namespace */
#define CLONE_NEWNET        0x40000000  /* New network namespace */

Android当前只利用了CLONE_NEWNS。

如果进程想创建自己的space,可以利用系统调用unshare,其实现如下:

文件: fork.c
函数: SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)

if (unshare_flags & CLONE_NEWNS)
    unshare_flags |= CLONE_FS; 
...
/* If unsharing namespace, must also unshare filesystem information.*/
unshare_fs(...)
unshare_userns(...)
--> create_user_ns
    --> proc_alloc_inum: 
unshare_nsproxy_namespaces
--> create_new_namespaces
    --> copy_mnt_ns
        --> dup_mnt_ns
            --> alloc_mnt_ns
                --> proc_alloc_inum

可以看到unshare系统调用会为当前进程创建一个新的mnt space。


Android的Namespace实现


从4.2开始,Android开始支持不同的user可以有独立的私有存储空间,见如下的描述。

Multi-user external storage

Starting in Android 4.2, devices can support multiple users, and external storage must meet the following constraints:

- Each user must have their own isolated primary external storage, and must not have access to the primary external storage of other users.
- The /sdcard path must resolve to the correct user-specific primary external storage based on the user a process is running as.
- Storage for large OBB files in the Android/obb directory may be shared between multiple users as an optimization.
- Secondary external storage must not be writable by apps, except in package-specific directories as allowed by synthesized permissions.

The default platform implementation of this feature leverages Linux kernel namespaces to create isolated mount tables for each Zygote-forked process, and then uses bind mounts to offer the correct user-specific primary external storage into that private namespace.

At boot, the system mounts a single emulated external storage FUSE daemon atEMULATED_STORAGE_SOURCE, which is hidden from apps. After the Zygote forks, it bind mounts the appropriate user-specific subdirectory from under the FUSE daemon toEMULATED_STORAGE_TARGET so that external storage paths resolve correctly for the app. Because an app lacks accessible mount points for other users' storage, they can only access storage for the user it was started as.

This implementation also uses the shared subtree kernel feature to propagate mount events from the default root namespace into app namespaces, which ensures that features like ASEC containers and OBB mounting continue working correctly. It does this by mounting the rootfs as shared, and then remounting it as slave after each Zygote namespace is created.

独立的存储空间就是利用Namespace的mnt空间实现的。

Zygot初始化时:
文件: runtime.cc
函数: Runtime::InitZygote()

   unshare(CLONE_NEWNS)
  // See storage config details at http://source.android.com/tech/storage/
  // Create private mount namespace shared by all children

创建App Process时:
文件: frameworks/base/core/jni/com_android_internal_os_Zygot.cpp
函数: ForkAndSpecializeCommon

   MountEmulatedStorage
   unshare(CLONE_NEWNS) 
   /*Create a second private mount namespace for our process*/
   mount(...,MS_BIND,...)

由上可见,Zygot进程和Java进程都会调用unshare创建自己的mnt space。

另外我们还需注意到 mount(…, MS_BIND,…) 的调用。新建立了mnt space后,在此新space下绑定文件目录,从而此文件目录可成为进程的私有空间。关于bind的使用,请查看mount中文手册的“绑定挂载”部分。也可参考“man 2 mount”。

上述unshare和mount的调用,在Android 5.X中被封装成了函数MountEmulatedStorage(…)。


下面我们看看实际的效果:

这里写图片描述

从文件 /proc/$PID/ns 可见:

  • 所有kernel thread的ns都是一样的;
  • 所有native daemons的ns都是一样的, 和kernel thread的ns是一样的;
  • 进程Zygot的mnt_namespace已经变得和上述2类Process不一样了,
    这是由于在Runtime::InitZygote中zygote创建了自己的namespace。
  • App Process的mnt ns都不一样,这是由于在创建App Process时,Zygot为他们创建了自己私有的namespace。

上图中的mnt:[XXXX] 显示的就是调用proc_alloc_inum为ns分配的ns->proc_inum.


至于mnt space的直接体验,可参见 宋宝华- Linux namespace - Docker 背后的故事的“第三步,mount名称空间”部分。

至于mnt space为什么能实现上述的功能,这里就不细讲了,相关的文章应该不少。

### 如何在 Android 模块的 `build.gradle` 文件中设置 namespaceAndroid Gradle Plugin (AGP) 的 7.0 版本开始,引入了命名空间的概念。如果未在模块级别的 `build.gradle` 文件中显式声明命名空间,则会触发错误提示:“Namespace not specified. Please specify a namespace”。以下是具体的解决方案: #### 配置方式 在模块级的 `build.gradle` 文件中的 `android` 块内添加 `namespace` 属性,并为其赋值为完整的包名。 ```groovy android { ... namespace 'com.example.yourpackage' ... } ``` 此配置的作用是指定该模块的唯一标识符,用于区分不同的模块和避免冲突[^4]。 #### 自动迁移旧项目的命名空间 如果项目中已经存在 `AndroidManifest.xml` 文件并指定了 `package` 属性,可以利用 AGP 升级助手自动完成迁移工作。具体操作如下: 1. 打开 Android Studio 并运行 AGP Upgrade Assistant 工具。 2. 跟随工具指引逐步升级 AGP 到最新版本。 3. 工具将检测现有的 `package` 属性并将它们迁移到 `build.gradle` 文件中的 `namespace` 字段[^2]。 访问官方文档了解更多细节:[https://developer.android.com/studio/build/agp-upgrade-assistant](https://developer.android.com/studio/build/agp-upgrade-assistant) #### 使用 Kotlin DSL 替代传统 Groovy 构建脚本 为了更高效地管理构建逻辑以及更好地融入现代开发实践,建议采用 Kotlin DSL (`build.gradle.kts`) 来代替传统的 Groovy 构建文件。Kotlin DSL 提供更强类型的语法支持,从而减少潜在的错误风险[^3]。 示例代码片段展示如何通过 Kotlin DSL 设置命名空间: ```kotlin android { ... namespace = "com.example.yourpackage" ... } ``` 以上更改完成后需同步 Gradle 项目以应用新的配置项。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值