Android binder死亡通知机制

在Andorid 的binder系统中,当Bn端由于种种原因死亡时,需要通知Bp端,Bp端感知Bn端死亡后,做相应的处理。

使用
Bp需要先注册一个死亡通知,当Bn端死亡时,回调到Bp端。

1,java代码注册死亡通知

try {binder.asBinder().linkToDeath(new IBinder.DeathRecipient() {@Overridepublic void binderDied() {//处理}},0);
} catch (RemoteException e) {e.printStackTrace();
}

2,c++代码注册死亡通知

	// Create the death listener.class DeathObserver : public IBinder::DeathRecipient {void binderDied(const wp<IBinder>& who) {//处理ALOGD("service is died\n");}};sp<IBinder::DeathRecipient> mDeathObserver = new DeathObserver();sp<IBinder> binder = sm->getService(String16("xxx"));//获取一个BpBinder对象if(binder->linkToDeath(mDeathObserver) != NO_ERROR){ALOGE("link to death failed");}else{ALOGE("link to death sucess");}

当Bn端死亡时,回调binderDied方法

注册死亡通知分析
从上面可以看出来,Bp端通过linkToDeath方法注册死亡通知。我们从java端的linkToDeath开始分析。
binder.asBinder返回的是一个BinderProxy对象

//frameworks\base\core\java\android\os\Binder.java
public native void linkToDeath(DeathRecipient recipient, int flags)throws RemoteException;

这是一个native方法,对应android_os_BinderProxy_linkToDeath方法

//frameworks\base\core\jni\android_util_Binder.cpp
static const JNINativeMethod gBinderProxyMethods[] = {/* name, signature, funcPtr *///省略{"linkToDeath",         "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath},//省略
};

继续来看一下android_os_BinderProxy_linkToDeath方法

//frameworks\base\core\jni\android_util_Binder.cpp
static void android_os_BinderProxy_linkToDeath(JNIEnv* env, jobject obj,jobject recipient, jint flags) // throws RemoteException
{//省略IBinder* target = (IBinder*)env->GetLongField(obj, gBinderProxyOffsets.mObject);//取出native层的 BpBinder对象//省略if (!target->localBinder()) {//只有Bp端才可以注册死亡通知DeathRecipientList* list = (DeathRecipientList*)env->GetLongField(obj, gBinderProxyOffsets.mOrgue);//1sp<JavaDeathRecipient> jdr = new JavaDeathRecipient(env, recipient, list);//2status_t err = target->linkToDeath(jdr, NULL, flags);//3//省略}
}

注释1处取出DeathRecipientList对象,DeathRecipientList对象中有一个集合

//frameworks\base\core\jni\android_util_Binder.cpp
class DeathRecipientList : public RefBase {List< sp<JavaDeathRecipient> > mList;Mutex mLock;

注释2处新建一个JavaDeathRecipient对象,并将其加入到上面的集合中

//frameworks\base\core\jni\android_util_Binder.cpp
class JavaDeathRecipient : public IBinder::DeathRecipient
{
public:JavaDeathRecipient(JNIEnv* env, jobject object, const sp<DeathRecipientList>& list): mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)),mObjectWeak(NULL), mList(list){// These objects manage their own lifetimes so are responsible for final bookkeeping.// The list holds a strong reference to this object.LOGDEATH("Adding JDR %p to DRL %p", this, list.get());list->add(this);//加到集合中android_atomic_inc(&gNumDeathRefs);incRefsCreated(env);}

注释3处target为BpBinder对象,继续来看BpBinder的linkToDeath方法

//frameworks\native\libs\binder\BpBinder.cpp
status_t BpBinder::linkToDeath(const sp<DeathRecipient>& recipient, void* cookie, uint32_t flags)
{/*构造Obituary 对象*/Obituary ob;ob.recipient = recipient;ob.cookie = cookie;ob.flags = flags;LOG_ALWAYS_FATAL_IF(recipient == NULL,"linkToDeath(): recipient must be non-NULL");{AutoMutex _l(mLock);if (!mObitsSent) {//mObitsSent默认为0,可以看出只会发送一次死亡通知if (!mObituaries) {mObituaries = new Vector<Obituary>;if (!mObituaries) {return NO_MEMORY;}ALOGV("Requesting death notification: %p handle %d\n", this, mHandle);getWeakRefs()->incWeak(this);IPCThreadState* self = IPCThreadState::self();self->requestDeathNotification(mHandle, this);//1self->flushCommands();//2}ssize_t res = mObituaries->add(ob);//3return res >= (ssize_t)NO_ERROR ? (status_t)NO_ERROR : res;}}return DEAD_OBJECT;
}

注释1处封装数据

//frameworks\native\libs\binder\IPCThreadState.cpp
status_t IPCThreadState::requestDeathNotification(int32_t handle, BpBinder* proxy)
{mOut.writeInt32(BC_REQUEST_DEATH_NOTIFICATION);//注意:cmd为BC_REQUEST_DEATH_NOTIFICATIONmOut.writeInt32((int32_t)handle);mOut.writePointer((uintptr_t)proxy);//这个proxy是前面的BpBinder对象return NO_ERROR;
}

注释2处将数据写给binder驱动

//frameworks\native\libs\binder\IPCThreadState.cpp
void IPCThreadState::flushCommands()
{if (mProcess->mDriverFD <= 0)return;talkWithDriver(false);//通过ioctl写给驱动
}

注释3处将前面构造好的Obituary 添加到集合中,该Obituary对象的recipient 成员指向我们前面传入的JavaDeathRecipient对象。
binder驱动开始处理,注意cmd为BC_REQUEST_DEATH_NOTIFICATION

//kernel\drivers\android\binder.c
case BC_REQUEST_DEATH_NOTIFICATION:
case BC_CLEAR_DEATH_NOTIFICATION: {uint32_t target;binder_uintptr_t cookie;struct binder_ref *ref;struct binder_ref_death *death = NULL;if (get_user(target, (uint32_t __user *)ptr))//从用户空间取出handlereturn -EFAULT;ptr += sizeof(uint32_t);if (get_user(cookie, (binder_uintptr_t __user *)ptr))//从用户空间取出BpBinder对象地址return -EFAULT;ptr += sizeof(binder_uintptr_t);if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {death = kzalloc(sizeof(*death), GFP_KERNEL);//申请binder_ref_death空间//省略			}binder_proc_lock(proc);ref = binder_get_ref_olocked(proc, target, false);//根据handle,找到binder_ref//省略if (cmd == BC_REQUEST_DEATH_NOTIFICATION) {binder_stats_created(BINDER_STAT_DEATH);INIT_LIST_HEAD(&death->work.entry);death->cookie = cookie;//将BpBinder对象地址保存在death的cookie 中ref->death = death;//将death保存在binder_ref的death 成员中if (ref->node->proc == NULL) {//注册的时候,Bn端刚好死亡,一般不太可能ref->death->work.type = BINDER_WORK_DEAD_BINDER;binder_inner_proc_lock(proc);binder_enqueue_work_ilocked(&ref->death->work, &proc->todo);binder_wakeup_proc_ilocked(proc);binder_inner_proc_unlock(proc);}}//省略

对于注册死亡通知时驱动的处理上面的注释已经说的很清楚。主要是将BpBinder对象地址保存在binder_ref的binder_ref_death 结构体中,这里只是做了保存,我们还没有看到死亡通知到底是如何触发的呢,即binderDied是如何被调用到的?接下来我们就来看一下死亡通知的触发
死亡通知触发分析
当Bn端死亡时,就要开始释放资源,调用binder_release,从这个方法开始分析

//kernel\drivers\android\binder.c
static int binder_release(struct inode *nodp, struct file *filp)
{struct binder_proc *proc = filp->private_data;debugfs_remove(proc->debugfs_entry);binder_defer_work(proc, BINDER_DEFERRED_RELEASE);return 0;
}

调用binder_defer_work,注意这个proc还是当前进程即Bn端所处的进程,第二个参数为BINDER_DEFERRED_RELEASE

//kernel\drivers\android\binder.c
static DECLARE_WORK(binder_deferred_work, binder_deferred_func);
static void
binder_defer_work(struct binder_proc *proc, enum binder_deferred_state defer)
{mutex_lock(&binder_deferred_lock);proc->deferred_work |= defer;if (hlist_unhashed(&proc->deferred_work_node)) {hlist_add_head(&proc->deferred_work_node,&binder_deferred_list);queue_work(binder_deferred_workqueue, &binder_deferred_work);//1}mutex_unlock(&binder_deferred_lock);
}

注释1处开始执行工作队列,对于binder_deferred_work,则是执行binder_deferred_func函数

//kernel\drivers\android\binder.c
static void binder_deferred_func(struct work_struct *work)
{struct binder_proc *proc;struct files_struct *files;int defer;do {//省略if (defer & BINDER_DEFERRED_RELEASE)binder_deferred_release(proc); /* frees proc */if (files)put_files_struct(files);} while (proc);
}

对于BINDER_DEFERRED_RELEASE,调用binder_deferred_release继续处理

//kernel\drivers\android\binder.c
static void binder_deferred_release(struct binder_proc *proc)
{struct binder_context *context = proc->context;struct rb_node *n;int threads, nodes, incoming_refs, outgoing_refs, active_transactions;BUG_ON(proc->files);mutex_lock(&binder_procs_lock);hlist_del(&proc->proc_node);//删除proc_node节点mutex_unlock(&binder_procs_lock);mutex_lock(&context->context_mgr_node_lock);/*如果是servicemanager死亡,则删除context->binder_context_mgr_node*/if (context->binder_context_mgr_node &&context->binder_context_mgr_node->proc == proc) {//省略}mutex_unlock(&context->context_mgr_node_lock);binder_inner_proc_lock(proc);proc->tmp_ref++;proc->is_dead = true;threads = 0;active_transactions = 0;/*删除binder_thread*/while ((n = rb_first(&proc->threads))) {//省略}nodes = 0;incoming_refs = 0;/*删除binder_node*/while ((n = rb_first(&proc->nodes))) {struct binder_node *node;node = rb_entry(n, struct binder_node, rb_node);nodes++;/** take a temporary ref on the node before* calling binder_node_release() which will either* kfree() the node or call binder_put_node()*/binder_inc_node_tmpref_ilocked(node);rb_erase(&node->rb_node, &proc->nodes);binder_inner_proc_unlock(proc);incoming_refs = binder_node_release(node, incoming_refs);//1binder_inner_proc_lock(proc);}binder_inner_proc_unlock(proc);outgoing_refs = 0;binder_proc_lock(proc);/*删除binder_ref*/while ((n = rb_first(&proc->refs_by_desc))) {//省略}binder_proc_unlock(proc);binder_release_work(proc, &proc->todo);binder_release_work(proc, &proc->delivered_death);binder_debug(BINDER_DEBUG_OPEN_CLOSE,"%s: %d threads %d, nodes %d (ref %d), refs %d, active transactions %d\n",__func__, proc->pid, threads, nodes, incoming_refs,outgoing_refs, active_transactions);binder_proc_dec_tmpref(proc);

binder_deferred_release方法里面还是做了很多事情的,上面注释也已经说的很清楚。我们继续来看一下注释1处的binder_node_release方法

//kernel\drivers\android\binder.c
static int binder_node_release(struct binder_node *node, int refs)
{//省略hlist_for_each_entry(ref, &node->refs, node_entry) {refs++;binder_inner_proc_lock(ref->proc);if (!ref->death) {binder_inner_proc_unlock(ref->proc);continue;}death++;BUG_ON(!list_empty(&ref->death->work.entry));ref->death->work.type = BINDER_WORK_DEAD_BINDER;binder_enqueue_work_ilocked(&ref->death->work,&ref->proc->todo);binder_wakeup_proc_ilocked(ref->proc);binder_inner_proc_unlock(ref->proc);}//省略}

取出node中的binder_ref,如果binder_ref中有注册过死亡通知,则添加到Bp端的进程的todo两边,唤醒Bp端进程。注意work的type为BINDER_WORK_DEAD_BINDER。Bp端进程被唤醒,Bp端进程开始处理BINDER_WORK_DEAD_BINDER这个type。注意现在是运行在Bp端所在的进程

//kernel\drivers\android\binder.c
static int binder_thread_read(struct binder_proc *proc,struct binder_thread *thread,binder_uintptr_t binder_buffer, size_t size,binder_size_t *consumed, int non_block)
{//省略case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {struct binder_ref_death *death;uint32_t cmd;binder_uintptr_t cookie;death = container_of(w, struct binder_ref_death, work);//取出binder_ref中的binder_ref_deathif (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION)cmd = BR_CLEAR_DEATH_NOTIFICATION_DONE;elsecmd = BR_DEAD_BINDER;//传给用户空间的cmd为BR_DEAD_BINDERcookie = death->cookie;//取出cookie,这个cookie就是之前注册时的BpBinder对象if (w->type == BINDER_WORK_CLEAR_DEATH_NOTIFICATION) {//省略} else {binder_enqueue_work_ilocked(w, &proc->delivered_death);binder_inner_proc_unlock(proc);}if (put_user(cmd, (uint32_t __user *)ptr))return -EFAULT;ptr += sizeof(uint32_t);if (put_user(cookie,(binder_uintptr_t __user *)ptr))return -EFAULT;ptr += sizeof(binder_uintptr_t);binder_stat_br(proc, thread, cmd);if (cmd == BR_DEAD_BINDER)goto done; /* DEAD_BINDER notifications can cause transactions */} break;
}

经过上面的处理,Bp端进程的用户空间就会得到cmd为BR_DEAD_BINDER的命令。Bp端进程是在executeCommand方法中处理命令的

//frameworks\native\libs\binder\IPCThreadState.cpp
status_t IPCThreadState::executeCommand(int32_t cmd)
{//省略case BR_DEAD_BINDER:{BpBinder *proxy = (BpBinder*)mIn.readPointer();//1proxy->sendObituary();//2mOut.writeInt32(BC_DEAD_BINDER_DONE);mOut.writePointer((uintptr_t)proxy);} break;//省略    }

注释1处取出驱动传过来的BpBinder对象,注释2处调用BpBinder的sendObituary方法

//frameworks\native\libs\binder\BpBinder.cpp
void BpBinder::sendObituary()
{mAlive = 0;if (mObitsSent) return;mLock.lock();Vector<Obituary>* obits = mObituaries;/*首先先向驱动发送清楚这个死亡通知的事件*/if(obits != NULL) {ALOGV("Clearing sent death notification: %p handle %d\n", this, mHandle);IPCThreadState* self = IPCThreadState::self();self->clearDeathNotification(mHandle, this);self->flushCommands();mObituaries = NULL;}mObitsSent = 1;mLock.unlock();if (obits != NULL) {const size_t N = obits->size();for (size_t i=0; i<N; i++) {reportOneDeath(obits->itemAt(i));//1}delete obits;}
}

注释1处,从mObituaries取出一个个的Obituary对象,然后执行reportOneDeath方法。还记得之前在注册死亡通知时,将我们的recipient封装在了Obituary对象中了。继续来看reportOneDeath方法

void BpBinder::reportOneDeath(const Obituary& obit)
{sp<DeathRecipient> recipient = obit.recipient.promote();ALOGV("Reporting death to recipient: %p\n", recipient.get());if (recipient == NULL) return;recipient->binderDied(this);
}

这里取出我们的recipient对象,调用其binderDied方法。如果是C++注册的死亡通知,那C++层的binderDied就得到执行了。我们接下来看看是如何调用到java端的binderDied方法。对于java端,我们之前传入的是JavaDeathRecipient对象,所以接在看JavaDeathRecipient的binderDied方法

//frameworks\base\core\jni\android_util_Binder.cpp
void binderDied(const wp<IBinder>& who){LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);if (mObject != NULL) {JNIEnv* env = javavm_to_jnienv(mVM);env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,gBinderProxyOffsets.mSendDeathNotice, mObject);//调用BinderProxy的sendDeathNotice方法//省略}}

调用BinderProxy的sendDeathNotice方法,传入的mObject为之前我们注册时传入的DeathRecipient对象

//frameworks\base\core\java\android\os\Binder.java
private static final void sendDeathNotice(DeathRecipient recipient) {if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);try {recipient.binderDied();}catch (RuntimeException exc) {Log.w("BinderNative", "Uncaught exception from death notification",exc);}}

可以看出,调用binderDied,之前注册的死亡通知得以执行。Bp端就感知到了Bn端的死亡

总结
死亡通知机制包含Bp端注册死亡通知以及Bn端死亡时触发死亡通知,用一张图来总结下其流程

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/317614.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

hadoop学习---基于hive的航空公司客户价值的LRFCM模型案例

案例需求&#xff1a; RFM模型的复习 在客户分类中&#xff0c;RFM模型是一个经典的分类模型&#xff0c;模型利用通用交易环节中最核心的三个维度——最近消费(Recency)、消费频率(Frequency)、消费金额(Monetary)细分客户群体&#xff0c;从而分析不同群体的客户价值。在某些…

C++list的模拟实现

文章目录 一、观察源码二、模拟实现1. 节点结构体2. list类3. 迭代器的定义与实现&#xff08;1&#xff09; 前置--后置--模拟实现&#xff08;2&#xff09; *和->重载模拟实现&#xff08;3&#xff09; 和!重载实现 4. list成员函数模拟实现。&#xff08;1&#xff09;…

Unity Audio Filter 入门

概述&#xff1a; 如果你在你项目中需要一些特殊的声音效果&#xff0c;那这部分声音过滤器的部分一定不要错过喔&#xff0c;让我们来学习这部分的内容吧&#xff01; 这部分理论性比较强&#xff0c;认真看我的注解哈&#xff0c;我尽量解释的易懂一点。 Audio Chorus Filter…

翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习六

合集 ChatGPT 通过图形化的方式来理解 Transformer 架构 翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习一翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深度学习二翻译: 什么是ChatGPT 通过图形化的方式来理解 Transformer 架构 深…

基于Springboot+Vue的Java项目-火车票订票系统开发实战(附演示视频+源码+LW)

大家好&#xff01;我是程序员一帆&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &am…

html--剑雨

<!doctype html> <html> <head> <meta charset"utf-8"> <title>css3剑雨-jq22.com</title> <script src"http://www.jq22.com/jquery/jquery-1.10.2.js"></script> <style> .sword:before, .sword:…

Docker基础学习(5.Docker镜像命令)

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ ⭐微信公众号&#xff1a;码上言 文章目录 Docker run流程镜像是什么&a…

云计算技术概述_1.云计算相关概念

1.关于IBM“蓝云&#xff08;Blue Cloud&#xff09;”计划 IBM 推出的“蓝云&#xff08;Blue Cloud&#xff09;”计划为客户带来即可使用的云计算(Cloud Computing)。它包括一系列的云计算产品&#xff0c;使计算不仅仅局限在本地机器或远程Server Farms&#…

树莓派点亮LED灯

简介 使用GPIO Zero library 的 Python库实现点亮LED灯。接线 树莓派引脚参考图如下&#xff1a; LED正极 接GPIO17 LED负极 接GND 权限 将你的用户加到gpio组中&#xff0c; 否则无法控制GPIO sudo usermod -a -G gpio 代码 from gpiozero import LED from time impor…

MouseBoost PRO for Mac激活版:强大的 鼠标增强软件

在追求高效工作的今天&#xff0c;MouseBoost PRO for Mac成为了许多Mac用户的得力助手。这款功能强大的鼠标增强软件&#xff0c;以其独特的智能化功能和丰富的实用工具&#xff0c;让您的电脑操作更加便捷、高效。 MouseBoost PRO for Macv3.4.0中文激活版下载 MouseBoost PR…

【Mac】Photoshop 2024 for mac最新安装教程

软件介绍 Photoshop 2024是Adobe公司推出的一款图像处理软件&#xff0c;它支持Windows和Mac OS系统。Adobe Photoshop是业界领先的图像编辑和处理软件之一&#xff0c;广泛用于设计、摄影、数字绘画等领域。 Photoshop 2024的功能包括&#xff1a; 1.图像编辑&#xff1a;提…

图片壁纸社区app前后端开源小程序源码

图片壁纸社区APP前后端开源小程序源码&#xff0c;修改了开源版的前端样式&#xff0c;变成图片社区&#xff0c;也可以用来作为壁纸。 源码下载&#xff1a;https://download.csdn.net/download/m0_66047725/89122506 更多资源下载&#xff1a;关注我。

【Unity】修改模型透明度

在 Unity 中修改模型透明度主要有两种方法&#xff1a;通过材质和通过着色器。以下是两种方法的步骤和解释&#xff1a; 方法 1&#xff1a;通过材质 在 Unity 编辑器中&#xff0c;选择你想要修改透明度的模型。在 Inspector 窗口中&#xff0c;找到模型的 Renderer 组件&am…

简约大气的全屏背景壁纸导航网源码(免费)

简约大气的全屏背景壁纸导航网模板 效果图部分代码领取源码下期更新预报 效果图 部分代码 <!DOCTYPE html> <html lang"zh-CN"> <!--版权归孤独 --> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible…

【b站前端-小鑫】Vue Router(路由)快速掌握(入门到精通5节课)

课程地址&#xff1a;【Vue Router(路由)快速掌握&#xff08;入门到精通5节课&#xff09;】 https://www.bilibili.com/video/BV1aP4y1W7Uz/?share_sourcecopy_web&vd_sourceb1cb921b73fe3808550eaf2224d1c155 目录 1 Vue Router 1.1 Vue Router的安装 1.2 创建路由…

提高 RAG 效果示例配置

提高 RAG 效果示例配置 最近在调整一个学习赛&#xff0c;针对所有问题&#xff0c;为了尽可能的获得答案&#xff0c;尝试了各种配置。 20240501时点&#xff0c;下面配置暂时能够获得测试的所有十几个问题的答案。后续测试再更新更优化的配置。 未完待续&#xff01;

在UI界面中播放视频_unity基础开发教程

在UI界面中播放视频_unity基础开发教程 前言操作步骤结语 前言 之前我写过一篇在场景中播放视频的文章&#xff0c;但是在开发中有时候也会在UI的界面中播放视频&#xff0c;这期我们做一下在UI的界面中播放视频。 操作步骤 首先在场景中创建一个Raw Image&#xff0c;UI->…

手撕spring框架(3)

手撕spring框架&#xff08;3&#xff09; 相关系列 手撕spring框架&#xff08;1&#xff09; 手撕spring框架&#xff08;2&#xff09; InitializingBean 接口详解 什么是 InitializingBean 接口&#xff1f; InitializingBean 接口是 Spring 框架中的一个接口&#xff0c…

与Apollo共创生态:探索自动驾驶的未来蓝图

目录 引言Apollo开放平台Apollo开放平台企业生态计划Apollo X 企业自动驾驶解决方案&#xff1a;加速企业场景应用落地Apollo开放平台携手伙伴共创生态生态共创会员权益 个人心得与展望技术的多元化应用数据驱动的智能化安全与可靠性的重视 结语 引言 就在2024年4月19日&#x…

Golang | Leetcode Golang题解之第60题排列序列

题目&#xff1a; 题解&#xff1a; func getPermutation(n int, k int) string {factorial : make([]int, n)factorial[0] 1for i : 1; i < n; i {factorial[i] factorial[i - 1] * i}k--ans : ""valid : make([]int, n 1)for i : 0; i < len(valid); i {…