1 前言
思君如满月,夜夜减清辉。
欢迎关注微信公众号“ZZH的Android”,一起探索Android系统源码,还能加入微信交流群与众多大佬相互切磋。
1.1 环境
1.2 内容概要
之前我们讲Binder驱动初始化的时候看到会初始化三个binder驱动设备dev/binder,dev/hwbinder,dev/vnbinder,
/dev/binder是system分区的进程间通过binder通信的binder设备;
/dev/hwbinder是system分区进程和vendor分区进程间通过binder通信的binder设备;
/dev/vnbinder是vendor分区的进程间通过binder通信的binder设备;
其中每个binder设备都有一个servicemanager进程与之对应,如下:
今天我们主要开始讲与/dev/binder驱动对应的servicemanager进程。本章主要包含如下图中绿色部分内容:
2 servicemanager进程启动
servicemanager进程由init进程启动,在init.rc文件里进行配置启动参数。如下:
// android-14.0.0_r17/frameworks/native/cmds/servicemanager/servicemanager.rc
service servicemanager /system/bin/servicemanager
class core animation
user system
group system readproc
critical
file /dev/kmsg w
onrestart setprop servicemanager.ready false
// 当servicemanager重启时,如下服务都重启
onrestart restart --only-if-running apexd
onrestart restart audioserver
onrestart restart gatekeeperd
onrestart class_restart --only-enabled main
onrestart class_restart --only-enabled hal
onrestart class_restart --only-enabled early_hal
task_profiles ServiceCapacityLow
// 关机时确保其最后退出
shutdown critical
可以看到其可执行文件为/system/bin/servicemanager,Android.bp配置如下:
// android-14.0.0_r17/frameworks/native/cmds/servicemanager/Android.bp
cc_binary {
name: "servicemanager",
defaults: ["servicemanager_defaults"],
init_rc: ["servicemanager.rc"],
srcs: ["main.cpp"],
bootstrap: true,
}
可以看到其入口函数为main.cpp
// android-14.0.0_r17/frameworks/native/cmds/servicemanager/main.cpp
int main(int argc, char** argv) {
android::base::InitLogging(argv, android::base::KernelLogger);
if (argc > 2) {
LOG(FATAL) << "usage: " << argv[0] << " [binder driver]";
}
// 可以看到会检查输入参数,如果参数为空,则driver为"/dev/binder",
// 这个地方vndservicemanagwer会用到,vndservicemanager
// 启动时会传入参数 “/dev/vndbinder”
const char* driver = argc == 2 ? argv[1] : "/dev/binder";
LOG(INFO) << "Starting sm instance on " << driver;
// 下面详细介绍ProcessState,每个进程只会有一个ProcessState对象。
sp<ProcessState> ps = ProcessState::initWithDriver(driver);
ps->setThreadPoolMaxThreadCount(0);
ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
IPCThreadState::self()->disableBackgroundScheduling(true);
sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
LOG(ERROR) << "Could not self register servicemanager";
}
IPCThreadState::self()->setTheContextObject(manager);
ps->becomeContextManager();
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
BinderCallback::setupTo(looper);
ClientCallbackCallback::setupTo(looper, manager);
#ifndef VENDORSERVICEMANAGER
if (!SetProperty("servicemanager.ready", "true")) {
LOG(ERROR) << "Failed to set servicemanager ready property";
}
#endif
while(true) {
looper->pollAll(-1);
}
// should not be reached
return EXIT_FAILURE;
}
接下来我们详细解析main.cpp的实现。
2.1 ProcessState
aosp源码里有两个ProcessState类,一个给使用/dev/binder和/dev/vndbinder的进程使用,另一个给使用/dev/hwbinder的进程使用。这里先知道这个,后面将/dev/hwbinder时再详叙述。
ProcessState::initWithDriver
// android-14.0.0_r17/frameworks/native/libs/binder/ProcessState.cpp
sp<ProcessState> ProcessState::initWithDriver(const char* driver)
{
return init(driver, true /*requireDefault*/);
}
// 主要看init函数,这里参数 driver=/dev/binder, requireDefault=true
sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault)
{
...
// 对于servicemanager来说,driver肯定不为空
// 如果driver为空的话其实就是获取当前实例。如果当前实例没有初始化过则返回空。
if (driver == nullptr) {
std::lock_guard<std::mutex> l(gProcessMutex);
if (gProcess) {
verifyNotForked(gProcess->mForked);
}
return gProcess;
}
// 这里使用C++ 11新特性,std::once_flag和std::call_once使得
// 在多线程环境下如下函数只会执行一次。这样就确保了一个进程当中只有
// 一个ProcessState对象,所有线程共享一个ProcessState对象。
[[clang::no_destroy]] static std::once_flag gProcessOnce;
std::call_once(gProcessOnce, [&](){
// 如下匿名函数只会执行一次
// 检查driver文件是否可读,如果不可读则重新赋值为/dev/binder
if (access(driver, R_OK) == -1) {
ALOGE("Binder driver %s is unavailable. Using /dev/binder instead.", driver);
driver = "/dev/binder";
}
// 如果是/dev/vndbinder文件且isVndservicemanagerEnabled返回false的情况
// isVndservicemanagerEnabled的实现为
// return access("/vendor/bin/vndservicemanager", R_OK) == 0;
if (0 == strcmp(driver, "/dev/vndbinder") && !isVndservicemanagerEnabled()) {
ALOGE("vndservicemanager is not started on this device, you can save resources/threads "
"by not initializing ProcessState with /dev/vndbinder.");
}
// 这里定义了fork进程的三个回调函数,目的是确保gProcess对象在父进程和子进程中状态
// 的一致性。我们下面会详细解释。
// we must install these before instantiating the gProcess object,
// otherwise this would race with creating it, and there could be the
// possibility of an invalid gProcess object forked by another thread
// before these are installed
int ret = pthread_atfork(ProcessState::onFork, ProcessState::parentPostFork,
ProcessState::childPostFork);
LOG_ALWAYS_FATAL_IF(ret != 0, "pthread_atfork error %s", strerror(ret));
// 创建gProcess对象,这里执行的是ProcessState的构造函数。
std::lock_guard<std::mutex> l(gProcessMutex);
gProcess = sp<ProcessState>::make(driver);
});
if (requireDefault) {
// Detect if we are trying to initialize with a different driver, and
// consider that an error. ProcessState will only be initialized once above.
// 检查gProcess的driverName跟传入的是否一致。
LOG_ALWAYS_FATAL_IF(gProcess->getDriverName() != driver,
"ProcessState was already initialized with %s,"
" can't initialize with %s.",
gProcess->getDriverName().c_str(), driver);
}
/* 凡是有这句代码的表示在子进程中执行到这里时程序会终止。
* 在子进程fork后,子进程的gProcess->mForked会标记为true。
*下面会详细介绍。
*/
verifyNotForked(gProcess->mForked);
return gProcess;
}
在ProcessState对象gProcess对象创建之前,先定义了fork相关的回调函数。
int ret = pthread_atfork(ProcessState::onFork, ProcessState::parentPostFork,
ProcessState::childPostFork);
LOG_ALWAYS_FATAL_IF(ret != 0, "pthread_atfork error %s", strerror(ret));
这三个回调函数定义如下:
onFork
在进程执行fork操作前调用。其作用是确保在fork操作进行时,没有其他线程在访问或修改全局的进程状态对象gProcess。只有执行fork操作的线程可以访问或修改gProcess,这样可以确保fork后子程序的状态跟当前执行fork操作的线程状态一致。
void ProcessState::onFork() {
// make sure another thread isn't currently retrieving ProcessState
gProcessMutex.lock();
}
parentPostFork
在fork操作后由父进程调用。其作用是解锁全局互斥量gProcessMutex,使其他线程可以继续访问或修改全局进程状态对象。
void ProcessState::parentPostFork() {
gProcessMutex.unlock();
}
childPostFork
函数在fork操作后由子进程调用。其作用是处理子进程特有的状态,包括关闭子进程中继承的文件描述符和标记进程状态。要注意这里的gProcess是子进程的,是拷贝的父进程的,同时与父进程的gProcess是分离的,所以这里只是修改了子进程的状态,父进程不受影响。这里以标志gProcess->mForked来表明是否是fork出的子进程。
void ProcessState::childPostFork() {
// another thread might call fork before gProcess is instantiated, but after
// the thread handler is installed
if (gProcess) {
gProcess->mForked = true;
// "O_CLOFORK"
close(gProcess->mDriverFD);
gProcess->mDriverFD = -1;
}
gProcessMutex.unlock();
}
还记得这个函数吗?forked为true,表明在子进程中,所以凡是有如下检查的地方,在fork出的子进程中遇到都会终止程序。LOG_ALWAYS_FATAL_IF在条件为true时会调用abort函数。
static void verifyNotForked(bool forked) {
LOG_ALWAYS_FATAL_IF(forked, "libbinder ProcessState can not be used after fork");
}
我们目前遇到的执行verifyNotForked的地方有两处,都在ProcessState::init函数中。说明fork的子进程中不能再调用该函数。
ProcessState构造函数
ProcessState::ProcessState(const char* driver)
: mDriverName(String8(driver)),
mDriverFD(-1),
mVMStart(MAP_FAILED),
mThreadCountLock(PTHREAD_MUTEX_INITIALIZER),
mThreadCountDecrement(PTHREAD_COND_INITIALIZER),
mExecutingThreadsCount(0),
mWaitingForThreads(0),
mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
mCurrentThreads(0),
mKernelStartedThreads(0),
mStarvationStartTimeMs(0),
mForked(false),
mThreadPoolStarted(false),
mThreadPoolSeq(1),
mCallRestriction(CallRestriction::NONE) {
// 打开/dev/binder文件,得到文件描述符,并且执行一些设置操作。
base::Result<int> opened = open_driver(driver);
if (opened.ok()) {
// mmap 的作用是将 /dev/binder 设备文件的一部分内存映射到调用进程的虚拟地址空间。
// 具体来说,它将从 /dev/binder 开始的 BINDER_VM_SIZE 大小的内存区域映射到进程
// 地址空间中的 mVMStart 地址。这种映射使得进程能够直接访问 Binder 驱动的数据区域,
// 从而高效地进行进程间通信(IPC)。
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
opened.value(), 0);
if (mVMStart == MAP_FAILED) {
close(opened.value());
// *sigh*
opened = base::Error()
<< "Using " << driver << " failed: unable to mmap transaction memory.";
mDriverName.clear();
}
}
#ifdef __ANDROID__
LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating: %s",
driver, opened.error().message().c_str());
#endif
if (opened.ok()) {
mDriverFD = opened.value();
}
}
下面看具体实现
open_driver
static base::Result<int> open_driver(const char* driver) {
// 打开binder驱动设备,得到文件描述符fd,内核里对应函数为binder_open
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd < 0) {
return base::ErrnoError() << "Opening '" << driver << "' failed";
}
// 下面的ioctl函数,在内核里对应的是binder_ioctl
// 获取 Binder 驱动的版本号
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
if (result == -1) {
close(fd);
return base::ErrnoError() << "Binder ioctl to obtain version failed";
}
// 如果 ioctl 调用返回值不为 0 或获取的版本号 vers 不等于
// 用户空间协议版本 BINDER_CURRENT_PROTOCOL_VERSION,
// 表示版本不匹配,关闭文件描述符并返回错误。
if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
close(fd);
return base::Error() << "Binder driver protocol(" << vers
<< ") does not match user space protocol("
<< BINDER_CURRENT_PROTOCOL_VERSION
<< ")! ioctl() return value: " << result;
}
// 设置 Binder 驱动的最大线程数,可以控制系统中用于处理 Binder 请求的并发线程数量。
// 这有助于防止资源过度消耗,提高系统稳定性和性能。(这里是15)
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
if (result == -1) {
ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
}
// 启用 Binder 驱动的单向(oneway)垃圾检测功能,以防止 Binder 通信中可能的滥用行为。
/*
单向消息(Oneway Message)
定义:单向消息是一种特殊类型的 IPC(进程间通信)调用。与普通的同步 IPC 调用不同,
单向消息是异步的,即调用者发出请求后立即返回,而不等待被调用者处理完成。
使用场景:单向消息通常用于不需要立即返回结果的操作。例如,发送通知或更新状态等。
单向垃圾(Oneway Spam)
单向垃圾:单向垃圾指的是大量的、不受控制的单向消息。这些消息可能会被滥用,
导致系统资源耗尽或性能下降。
滥用行为:滥用单向消息可能会导致:
资源耗尽:频繁发送单向消息可能会占用大量的系统资源(如内存、CPU),影响系统的稳定性。
性能下降:过多的单向消息处理可能会导致系统响应变慢,影响用户体验。
单向垃圾检测(Oneway Spam Detection)
单向垃圾检测功能用于监控和限制单向消息的发送,以防止滥用行为。
通过启用单向垃圾检测,可以确保系统不会因为过多的单向消息而受到影响
*/
uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
if (result == -1) {
ALOGE_IF(ProcessState::isDriverFeatureEnabled(
ProcessState::DriverFeature::ONEWAY_SPAM_DETECTION),
"Binder ioctl to enable oneway spam detection failed: %s", strerror(errno));
}
return fd;
}
我们知道,应用层的open和ioctl操作,在Kernel里是有对应的实现的,所以接下来我们需要看下上面涉及到的几个调用的kernel实现。
binder_open
应用层调用open函数时,kernel会执行binder_open函数。
其主要作用是初始化与该进程相关的 Binder 资源和数据结构,并将其加入到全局管理列表中。
// struct inode 是 Linux 内核中用于表示文件系统中节点(node)的数据结构。
// 每个文件或目录在文件系统中都有一个对应的 inode,它存储了文件的元数据,
// 例如文件大小、所有者、权限和时间戳等信息。struct inode *nodp 是一个
// 指向这种 inode 结构体的指针,表示文件系统中的某个节点。
// struct file 通常指的是一个指向 struct file 结构体的指针。
// 在 Linux 内核中,struct file 结构体用于表示一个打开的文件。
// 每当一个进程打开一个文件时,内核会为这个打开的文件创建一个 struct file 实例,
// 并返回一个文件描述符给用户空间。
static int binder_open(struct inode *nodp, struct file *filp)
{
// binder_proc_wrap包含一个binder_proc成员和一个自旋锁成员
// 其意义在于通过添加额外的锁来保护 binder_proc 结构体中的数据,
// 确保在多线程或多进程环境中对 binder_proc 的访问是线程安全的。
struct binder_proc_wrap *proc_wrap;
// binder_proc是 Android Binder 驱动程序中用于记录进程信息的重要数据结构。
// 它包含了与进程相关的各种信息,包括线程、节点、引用、事务等。
struct binder_proc *proc, *itr;
// 该结构体包含了与 Binder 设备节点相关的各种信息
struct binder_device *binder_dev;
// 用于描述与 Binder 文件系统(binderfs)挂载点相关的信息
struct binderfs_info *info;
// 用于表示 binderfs 文件系统中用于进程特定日志的目录项(dentry)。
// dentry 是 Linux 内核中的一个基本数据结构,用于表示文件系统中的目录项。
struct dentry *binder_binderfs_dir_entry_proc = NULL;
bool existing_pid = false;
// 输出调试信息,包输出当前进程组组长的PID和当前进程的PID。
// current是一个函数,返回task_struct结构体
binder_debug(BINDER_DEBUG_OPEN_CLOSE, "%s: %d:%d\n", __func__,
current->group_leader->pid, current->pid);
// 分配 binder_proc_wrap 结构体,并检查分配是否成功。
// 将 proc 指向分配的 binder_proc 结构体。
proc_wrap = kzalloc(sizeof(*proc_wrap), GFP_KERNEL);
if (proc_wrap == NULL)
return -ENOMEM;
proc = &proc_wrap->proc;
// 初始化proc的两个自旋锁。
spin_lock_init(&proc->inner_lock);
spin_lock_init(&proc->outer_lock);
// 增加current->group_leader的引用计数,防止其在使用期间被释放
get_task_struct(current->group_leader);
proc->tsk = current->group_leader;
// cred表示与这个打开的文件相关联的凭据(credentials)。
// 凭据包含了执行文件操作的进程的身份信息,如用户ID(UID)和组ID(GID)。
// get_cred 会增加 cred 结构体的引用计数,并返回指向该 cred 结构体的指针。
// 通过增加引用计数,可以确保在使用期间这个 cred 结构体不会被释放。
proc->cred = get_cred(filp->f_cred);
// INIT_LIST_HEAD 是一个宏,用于初始化链表头,使其成为一个有效的链表。
// proc->todo 将用于存储该进程的待处理事务。
INIT_LIST_HEAD(&proc->todo);
// init_waitqueue_head 函数用于初始化等待队列头,使其成为一个有效的等待队列。
// proc->freeze_wait 将用于处理冻结进程的等待事件, 是一个列表结构。
init_waitqueue_head(&proc->freeze_wait);
// 对 binder_proc 结构体中的 default_priority 成员进行初始化。
// 它检查当前进程的调度策略是否受支持,并相应地设置 default_priority 的调度策略和优先级。
if (binder_supported_policy(current->policy)) {
proc->default_priority.sched_policy = current->policy;
proc->default_priority.prio = current->normal_prio;
} else {
proc->default_priority.sched_policy = SCHED_NORMAL;
proc->default_priority.prio = NICE_TO_PRIO(0);
}
/* binderfs stashes devices in i_private */
// 从文件系统节点或文件指针中获取 Binder 设备信息,并根据设备类型执行不同的初始化操作
if (is_binderfs_device(nodp)) {
// 如果使用了binderfs文件系统
binder_dev = nodp->i_private;
info = nodp->i_sb->s_fs_info;
// 这个在创建binder进程调试信息文件时会用到。
binder_binderfs_dir_entry_proc = info->proc_log_dir;
} else {
binder_dev = container_of(filp->private_data,
struct binder_device, miscdev);
}
// 增加引用计数
refcount_inc(&binder_dev->ref);
proc->context = &binder_dev->context;
binder_alloc_init(&proc->alloc);
// 设置进程 PID 并初始化相关链表
binder_stats_created(BINDER_STAT_PROC);
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
INIT_LIST_HEAD(&proc->waiting_threads);
// 记住这个private_data是binder_proc
filp->private_data = proc;
// 检查是否已有相同 PID 的进程,这里理解应该为是否之前存在过pid相同的进程。
mutex_lock(&binder_procs_lock);
hlist_for_each_entry(itr, &binder_procs, proc_node) {
if (itr->pid == proc->pid) {
existing_pid = true;
break;
}
}
// 将新的proc->proc_node添加到binder_procs列表头
hlist_add_head(&proc->proc_node, &binder_procs);
mutex_unlock(&binder_procs_lock);
// 看到trace,应该是记录 binder_procs 和 binder_procs_lock 的状态
// 用于调试和性能分析
trace_android_vh_binder_preset(&binder_procs, &binder_procs_lock);
// 还记得这个binder_binderfs_dir_entry_proc吗,上一篇讲过。
// 表示/sys/kernel/debug/binder/proc目录创建成功。
// 并且之前没有存在过相同Pid的进程,就会创建一个文件。
if (binder_debugfs_dir_entry_proc && !existing_pid) {
char strbuf[11];
// strbuf表示文件名,为proc->pid,也就是以进程号命名。
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/*
* proc debug entries are shared between contexts.
* Only create for the first PID to avoid debugfs log spamming
* The printing code will anyway print all contexts for a given
* PID so this is not a problem.
*/
//创建文件pid
// 权限为0444,只读
// 父目录为/sys/kernel/debug/binder/proc/
// 文件的数据指针为进程的 PID(通过类型转换)
// 文件操作结构体为 proc_fops
proc->debugfs_entry = debugfs_create_file(strbuf, 0444,
binder_debugfs_dir_entry_proc,
(void *)(unsigned long)proc->pid,
&proc_fops);
}
// 如果binder使用了binderfs文件系统
if (binder_binderfs_dir_entry_proc && !existing_pid) {
char strbuf[11];
struct dentry *binderfs_entry;
snprintf(strbuf, sizeof(strbuf), "%u", proc->pid);
/*
* Similar to debugfs, the process specific log file is shared
* between contexts. Only create for the first PID.
* This is ok since same as debugfs, the log file will contain
* information on all contexts of a given PID.
*/
// 与前面类似,只是父目录为binder_binderfs_dir_entry_proc
binderfs_entry = binderfs_create_file(binder_binderfs_dir_entry_proc,
strbuf, &proc_fops, (void *)(unsigned long)proc->pid);
if (!IS_ERR(binderfs_entry)) {
// 记录到proc中
proc->binderfs_entry = binderfs_entry;
} else {
int error;
error = PTR_ERR(binderfs_entry);
pr_warn("Unable to create file %s in binderfs (error %d)\n",
strbuf, error);
}
}
return 0;
}
binder_ioctl
用户空间的ioctl调用对应kernel里的binder_ioctl函数。
// *struct file filp:打开/dev/binder的文件指针
// unsigned int cmd:ioctl操作命令。
// unsigned long arg:命令参数
static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret;
// 这个private_data上面提到过,binder_open时赋值的
struct binder_proc *proc = filp->private_data;
struct binder_thread *thread;
unsigned int size = _IOC_SIZE(cmd);
// 参数保存到ubuf里
void __user *ubuf = (void __user *)arg;
/*pr_info("binder_ioctl: %d:%d %x %lx\n",
proc->pid, current->pid, cmd, arg);*/
binder_selftest_alloc(&proc->alloc);
// 记录trace信息
trace_binder_ioctl(cmd, arg);
ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
if (ret)
goto err_unlocked;
thread = binder_get_thread(proc);
if (thread == NULL) {
ret = -ENOMEM;
goto err;
}
...
}
BINDER_VERSION
case BINDER_VERSION: {
struct binder_version __user *ver = ubuf;
if (size != sizeof(struct binder_version)) {
ret = -EINVAL;
goto err;
}
// 将binder version放到传进来的参数中,
// 这样用户空间就能获取到了。
// put_user 是一个内核函数,用于将数据从内核空间复制到用户空间。
if (put_user(BINDER_CURRENT_PROTOCOL_VERSION,
&ver->protocol_version)) {
ret = -EINVAL;
goto err;
}
break;
}
BINDER_SET_MAX_THREADS
case BINDER_SET_MAX_THREADS: {
int max_threads;
// 将用户空间的参数复制到max_threads
// copy_from_user 是一个内核函数,用于将数据从用户空间复制到内核空间。
if (copy_from_user(&max_threads, ubuf,
sizeof(max_threads))) {
ret = -EINVAL;
goto err;
}
binder_inner_proc_lock(proc);
// 赋值给proc->max_threads
proc->max_threads = max_threads;
binder_inner_proc_unlock(proc);
break;
}
BINDER_ENABLE_ONEWAY_SPAM_DETECTION
case BINDER_ENABLE_ONEWAY_SPAM_DETECTION: {
uint32_t enable;
if (copy_from_user(&enable, ubuf, sizeof(enable))) {
ret = -EFAULT;
goto err;
}
binder_inner_proc_lock(proc);
// 赋值proc的oneway_spam_detection_enabled
proc->oneway_spam_detection_enabled = (bool)enable;
binder_inner_proc_unlock(proc);
break;
}
下节内容
下一章将详细讲解binder_mmap函数的实现。