Android 进阶——Binder IPC之Native 服务的启动及代理对象的获取详解(六)
文章大纲
- 引言
- 一、Binder线程池的启动
- 1、ProcessState#startThreadPool函数来启动线程池
- 2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求
- 二、Service 代理对象的获取
- 1、获取Service Manager 代理对象BpServiceManager
- 2、调用BpServiceManager#getService
- 2.1、Service Manager 处理CHECK_SERVICE_TRANSACTION
- 2.2、Binder 驱动为Client进程创建对应的Service组件的Binder引用对象
- 2.3、Binder库为Client进程创建Binder代理对象
- 3、asInterface "转换"
引言
我们都知道Binder IPC可以支持并发访问和响应,但是你知道是为什么么?
一、Binder线程池的启动
Binder线程池的启动主要就是依赖以下两个函数。一个进程通过调用其内部的ProcessState对象的成员startThreadPool函数来启动线程池,通过IPCThreadState对象的成员函数joinThreadPool,将启动的线程加入到Binder线程池并注册成为一个Binder线程。当线程启动之后,会不断与binder驱动进行通信,读取本线程和本进程中待处理的事务,如果接受到Binder驱动的请求,则处理之。而当进程繁忙时, Binder驱动会向目标进程发送一个BR_SPAWN_LOOPER命令,申请一个新的进程。
ProcessState::self()->startThreadPool();IPCThreadState::self()->joinThreadPool();
当Binder驱动接到Server发来的IPC请求时,就会回复一个BR_SPAWN_LOOPER
通知创建Binder线程用于处理这个请求,当threadLoop函数返回false时则会被回收相应的对象。
1、ProcessState#startThreadPool函数来启动线程池
ProcessState在同一进程内是唯一的,主要用于初始化Binder设备,而IPCThreadState 用于与Binder驱动通信。
通常Service组件注册完毕之后对应的进程就会自动开启一个Binder线程池来处理Client进程发送过来的IPC请求。通常进程是通过调用其内部的ProcessState对象的startThreadPool函数来启动的。
\frameworks\native\libs\binder\ProcessState.cpp
void ProcessState::startThreadPool()
{AutoMutex _l(mLock);if (!mThreadPoolStarted) {//防止重复启动线程池mThreadPoolStarted = true;spawnPooledThread(true);}
}
从上面我们可以得知一个进程中有且只有一个Binder线程池且只启动一次,接着是真正通过ProcessState#spawnPooledThread来启动线程池的。
isMain为true表示是线程是进程主动创建并加入到它Binder线程池的,对应的是BC_ENTER_LOOPER协议,而Binder驱动请求进程创建的线程则isMain为false,对应的是BC_REGISTER_LOOPER。
void ProcessState::spawnPooledThread(bool isMain)
{if (mThreadPoolStarted) {String8 name = makeBinderThreadName();//Binder线程的名称sp<Thread> t = new PoolThread(isMain);t->run(name.string());}
}String8 ProcessState::makeBinderThreadName() {int32_t s = android_atomic_add(1, &mThreadPoolSeq);pid_t pid = getpid();String8 name;name.appendFormat("Binder:%d_%X", pid, s);return name;
}
主要就是创建PoolThread对象并调用其run函数启动一个新线程,而PoolThread 继承Thread并重写了线程入口函数threadLoop
class PoolThread : public Thread
{
public:PoolThread(bool isMain): mIsMain(isMain){}protected:virtual bool threadLoop(){IPCThreadState::self()->joinThreadPool(mIsMain);return false;}const bool mIsMain;
};
接着IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求。
Binder驱动接到IPC 请求的时候就会发一个 BR_SPAWN_LOOPE ,然后Server 端就创建一个线程来处理,但是一个fd 最多绑定15线程。
2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求
IPCThreadState#joinThreadPool 函数将当前线程注册到Binder驱动中,成为一个Binder线程,以便Binder驱动可以分发IPC请求给它处理,在进入Binder驱动前talkWithDriver会自动检测IPCThreadState内部的协议输出缓冲区是否还有协议,有则发给BInder驱动处理,处理完成返回后Binder驱动回复返回协议保存至IPCThreadState内部的协议输入缓冲区中,函数getAndExecuteCommand将会去处理。
void IPCThreadState::joinThreadPool(bool isMain)
{//将协议写入到IPCThreadState的输出缓冲区mOut.writeInt32(isMain ? BC_ENTER_LOOPER : BC_REGISTER_LOOPER);set_sched_policy(mMyThreadId, SP_FOREGROUND);status_t result;do {processPendingDerefs();// now get the next command to be processed, waiting if necessaryresult = getAndExecuteCommand();if (result < NO_ERROR && result != TIMED_OUT && result != -ECONNREFUSED && result != -EBADF) {abort();}if(result == TIMED_OUT && !isMain) {break;}} while (result != -ECONNREFUSED && result != -EBADF);LOG_THREADPOOL("**** THREAD %p (PID %d) IS LEAVING THE THREAD POOL err=%p\n",(void*)pthread_self(), getpid(), (void*)result);mOut.writeInt32(BC_EXIT_LOOPER);talkWithDriver(false);
}
若talkWithDriver函数长期没有等到IPC请求或者getAndExecuteCommand函数执行超时,且isMain为false时就跳出循环,并向Binder驱动发送BC_EXIT_LOOPER协议告知Binder驱动,前面创建的这个线程它要退出Binder线程池了。
二、Service 代理对象的获取
Server进程和Client进程的通信要依靠Binder驱动来进行。
Service组件在启动时,会将自己注册到ServiceManager组件中,以便Client组件通过ServiceManager来找到这个Service组件。
在Binder IPC机制中Client要与Server通信,Client组件必须要先获取Service组件的代理对象,使用的时候很方便直接通过Service Manager代理对象的getService
接口就可以获取,前面分析了ServiceManager 自身代理对象的获取,普通Service 组件代理对象获取的流程也大同小异,虽然说要与Binder驱动沟通,但是对于我们开发者来说这一部分的工作被Service Manager 帮忙承担了。
sp<IServiceManager> sm = defaultServiceManager();
sp<IBinder> binder = sm->getService(String16(DETECT_SERVICE_NAME));
sp<IDetectService> bpDetectService = interface_cast <IDetectService>(binder);
总结起来在Client进程(本例中名称为DetectClient)获取一个普通Service 代理对象三部曲为:
1、获取Service Manager 代理对象BpServiceManager
主要就是通过IServiceManager#defaultServiceManager函数获取,预知详情参见前文。
2、调用BpServiceManager#getService
BpServiceManager继承自BpInterface,而BpInterface是一个继承BpRefsBase的模板类,在getService函数中最多会尝试100次来尝试获取名称对应的Service组件代理对象
class BpServiceManager : public BpInterface<IServiceManager>
{
public:BpServiceManager(const sp<IBinder>& impl): BpInterface<IServiceManager>(impl){}virtual sp<IBinder> getService(const String16& name) const{unsigned n;for (n = 0; n < 100; n++){if (n > 0) {ALOGI("Waiting for service %s...", String8(name).string());usleep(50000);}sp<IBinder> svc = checkService(name);if (svc != NULL) return svc;}return NULL;}...
};
在checkService函数来真正去尝试获取Service组件代理对象,对比前文addService的核心流程大同小异(通过Service Manager代理对象请求Service Manager进程执行ADD_SERVICE_TRANSACTION),把协议换为CHECK_SERVICE_TRANSACTION协议即可。
virtual sp<IBinder> checkService( const String16& name) const{Parcel data, reply;//执行writeInterfaceToken函数,拼装Binder协议头data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());data.writeString16(name);//remote()即0号引用remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);return reply.readStrongBinder();}
换言之,getService 的实现就一个标准的Binder IPC 五部曲:
步骤 | 说明 |
---|---|
1 | DetectClient将DetectService代理对象对应的名称封装为Parcel对象并传递到Binder驱动。 |
2 | DetectClient向Binder驱动发送BC_TRANSACTION_COMPLETE协议,Binder驱动根据协议内容找到目标Service Manager进程后回复一个BR_TRANSACTION_COMPLETE告知DetectClient其通信请求已被接受,Client接到BR_TRANSACTION_COMPLETE并处理后,会再次进入到Binder驱动程序中等待Server Manager进程的返回它想要的代理对象的句柄。 |
3 | Binder驱动在给DetectClient回复一个BR_TRANSACTION_COMPLETE的同时,向Service Manager进程发送一个BR_TRANSACTION返回协议,请求目标Server Manager 进程执行CHECK_SERVICE_TRANSACTION指令。 |
4 | Service Manager进程接收BR_TRANSACTION并处理CHECK_SERVICE_TRANSACTION后,给驱动回复一个BC_REPLY协议(包含了DetectClient申请Service组件的信息),驱动根据协议内容为DetectClient进程创建一个对应的Binder引用对象,给Service Manager进程发送一个BR_TRANSACTION_COMPLETE返回协议,告知Service Manager它返回的目标Service 组件信息已经收到了,已接到返回的相应结果,Service Manager接到并处理了BR_TRANSACTION_COMPLETE协议后,一次IPC流程就结束了,接着会重新进入到驱动程序中等待下一次IPC请求。 |
5 | Binder驱动在给Service Manager进程发送BR_TRANSACTION_COMPLETE的同时,也会向DetectClient进程发送一个BR_REPLY返回协议(内容包含了前面所创建的Binder引用对象的举止值),DetectClient进程就可以拿着这个句柄来创建一个目标Binder代理对象,同时也代表Service Manager进程已经处理完成IPC请求了并将结果返回给Client |
通过这样子就可以拿到了Service 代理对象,简而言之,Client进程通过Service Manager进程拿到目标Service代理对象的引用对象的句柄值
,再根据这个句柄值创建对应的Service 代理对象
。
2.1、Service Manager 处理CHECK_SERVICE_TRANSACTION
前面说过Service Manager是在svcmgr_handle
函数中统一处理Client进程的IPC请求。
\frameworks\native\cmds\servicemanager\service_manager.c
int svcmgr_handler(struct binder_state *bs,struct binder_transaction_data *txn,struct binder_io *msg,struct binder_io *reply)
{struct svcinfo *si;uint16_t *s;size_t len;uint32_t handle;uint32_t strict_policy;int allow_isolated;...switch(txn->code) {case SVC_MGR_GET_SERVICE:case SVC_MGR_CHECK_SERVICE://从binder_io结构体中的msg数据缓冲区得到代理对象的名称s = bio_get_string16(msg, &len);if (s == NULL) {return -1;}//获取目标Binder引用对象的句柄handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);if (!handle)break;//传入目标引用对象的句柄到Binder驱动,并封装为一个对应的binder_object结构体并下入reply中,Binder驱动拿到的返回值就是replybio_put_ref(reply, handle);return 0;case SVC_MGR_ADD_SERVICE:...break;case SVC_MGR_LIST_SERVICES: {...}bio_put_uint32(reply, 0);return 0;
}
从binder_io结构体中的msg数据缓冲区还原出代理对象的名称后,通过do_find_service函数从已注册的Service组件列表svclist中查找与之对应的一个svcinfo结构体,并返回Service 组件的句柄。
在Service Manager中每一个被注册了的Service组件对应一个svcinfo 结构并保存在一个全局队列svclist中,其中svcinfo的成员next指向下一个svcinfo结构体、prt是一个Service组件Binder引用对象的句柄值、name是Service组件名称,death指向一个用于描述死亡接收通知的结构体binder_death。
uint32_t do_find_service(const uint16_t *s, size_t len, uid_t uid, pid_t spid)
{// 查找与字符串s对应一个svcinfo结构体struct svcinfo *si = find_svc(s, len);...if (!svc_can_find(s, len, spid, uid)) {return 0;}return si->handle;
}
2.2、Binder 驱动为Client进程创建对应的Service组件的Binder引用对象
找到Service组件结构体后就得到了其他Binder引用对象的句柄,返回到svcmgr_handler函数中,Service Manager接着通过bio_put_ref函数把句柄传给Binder驱动,Binder驱动就根据它找到对应的Binder引用对象,进而找到该引用对象所指向(引用)的Binder实体对象,最后Binder驱动就在请求该Service组件的代理对象时创建另一个Binder引用对象了。
\frameworks\native\cmds\servicemanager\binder.c
void bio_put_ref(struct binder_io *bio, uint32_t handle)
{struct flat_binder_object *obj;if (handle)//非0位trueobj = bio_alloc_obj(bio);elseobj = bio_alloc(bio, sizeof(*obj));if (!obj)return;obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;obj->type = BINDER_TYPE_HANDLE;obj->handle = handle;obj->cookie = 0;
}
bio_alloc函数在binder_io结构体的数据缓冲区分配一个未初始化的binder_object结构体并赋值给obj,保存了位置后返回到bio_put_ref函数,继续对创建的结构体进行初始化后
static struct flat_binder_object *bio_alloc_obj(struct binder_io *bio)
{struct flat_binder_object *obj;obj = bio_alloc(bio, sizeof(*obj));//在binder_io结构体的数据缓冲区分配一个未初始化的binder_object结构体//在binder_io的bio偏移数组中分配一个元素来保存binder_object结构体在数据缓冲区的位置,方便Binder驱动获知Service Manager给它返回的IPC结果中包含了一个Binder对象if (obj && bio->offs_avail) {bio->offs_avail--;*bio->offs++ = ((char*) obj) - ((char*) bio->data0);return obj;}bio->flags |= BIO_F_OVERFLOW;return NULL;
}
再回到svcmgr_handler函数中将Binder驱动IPC结果保存到binder_io类型结构体reply中了,接着回到binder_parse中最后调用binder_send_reply函数将reply的内容返回给Binder驱动(即向Binder驱动发送BC_REPLY协议)。
Binder驱动是在binder_transaction函数中处理Service Manager 发来的协议
Service Manager进程返回给Binder驱动的IPC结果中包含了BINDER_TYPE_WEAK_HANDLE类型的binder_object结构体(即flat_binder_object 结构体),Binder驱动就对这个结构体进行解包并从其中找到Binder引用对象ref(即指向的是运行在Server进程中的DetectService组件),然后查找是否存在对应的Binder引用对象,存在则直接返回给调用者,不存在则为Client进程创建一个新的Binder引用对象再返回,再经过Binder驱动一系列的处理之后
static void binder_transaction(struct binder_proc *proc,struct binder_thread *thread,struct binder_transaction_data *tr, int reply,binder_size_t extra_buffers_size)
{int ret;struct binder_transaction *t;struct binder_work *tcomplete;struct binder_proc *target_proc = NULL;struct binder_thread *target_thread = NULL;struct binder_node *target_node = NULL;struct binder_transaction *in_reply_to = NULL;struct binder_buffer_object *last_fixup_obj = NULL;struct binder_context *context = proc->context;.../* TODO: reuse incoming transaction for reply */t = kzalloc(sizeof(*t), GFP_KERNEL);binder_stats_created(BINDER_STAT_TRANSACTION_COMPLETE);...for (; offp < off_end; offp++) {struct binder_object_header *hdr;switch (hdr->type) {case BINDER_TYPE_BINDER:case BINDER_TYPE_WEAK_BINDER: {...}break;case BINDER_TYPE_HANDLE:case BINDER_TYPE_WEAK_HANDLE: {struct flat_binder_object *fp;...} break;}
}
2.3、Binder库为Client进程创建Binder代理对象
最终目标线程target_thread返回到用户空间之后,再次进去到IPCThreadState#waitForResponse
函数处理从Binder驱动读取回来的BR_REPLY协议,将Binder 驱动IPC结果封装为Parcel的对象reply中,再次返回到Service Manager代理对象的成员函数checkService
中,最后通过Parcel的readStrongBinder
函数得到Binder的代理对象。
status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{return unflatten_binder(ProcessState::self(), *this, val);
}status_t unflatten_binder(const sp<ProcessState>& proc,const Parcel& in, sp<IBinder>* out)
{const flat_binder_object* flat = in.readObject(false);if (flat) {switch (flat->type) {case BINDER_TYPE_BINDER:*out = reinterpret_cast<IBinder*>(flat->cookie);return finish_unflatten_binder(NULL, *flat, in);case BINDER_TYPE_HANDLE:*out = proc->getStrongProxyForHandle(flat->handle);return finish_unflatten_binder(static_cast<BpBinder*>(out->get()), *flat, in);}}return BAD_TYPE;
}
本质上还是通过ProcessState#getStrongProxyForHandle函数从其内部的Binder代理对象列表中查找是否存在一个与flat_binder_object结构体句柄值handle对应的Binder代理对象,存在则返回,不存在则根据handle创建相应的BInder代理对象再返回,而且flat_binder_object的handle同时是指向了Service组件引用对象,因此ProcessState#getStrongProxyForHandle函数返回的Binder代理对象也指向了Service组件的引用对象。
3、asInterface “转换”
当BpServiceManager#checkService 函数执行完成之后,就将一个Binder代理对象的IBinder接口返回到DetectClient进程的入口函数main,接着最差最后一步——"转换"为具体的Binder代理对象。
interface_cast并不是指针转换,而是利用BpBinder指针,构建出一个新的BpServiceManager对象。宏函数实现的。
android::sp<IDetectService> IDetectService::asInterface( const android::sp<android::IBinder>& obj) { android::sp<IDetectService> intr; if (obj != NULL) { intr = static_cast<IDetectService*>( obj->queryLocalInterface(IDetectService::descriptor).get()); if (intr == NULL) { intr = new BpIDetectService(obj); } } return intr; }
相关文章:

Android 进阶——Binder IPC之Native 服务的启动及代理对象的获取详解(六)
文章大纲引言一、Binder线程池的启动1、ProcessState#startThreadPool函数来启动线程池2、IPCThreadState#joinThreadPool 将当前线程进入到线程池中去等待和处理IPC请求二、Service 代理对象的获取1、获取Service Manager 代理对象BpServiceManager2、调用BpServiceManager#ge…...

企业官网怎么做?
企业官网是企业展示形象和吸引潜在客户的重要渠道之一,因此如何打造一款优秀的企业官网显得尤为重要。本文将从策划、设计、开发和上线等方面,为您介绍企业官网的制作步骤。 一、策划 1.明确目标 企业官网的制作需要明确目标,即确定官网的主…...

FPGA和IC设计怎么选?哪个发展更好?
很多人纠结FPGA和IC设计怎么选,其实往小了说,要看你选择的具体是哪个方向岗位。往大了说,将来你要是走更远,要成为大佬,那基本各个方向的都要有涉及的。 不同方向就有不同的发展,目前在薪资上IC设计要比FP…...

宁盾目录成功对接Coremail邮箱,为其提供LDAP统一认证和双因子认证
近日,宁盾与 Coremail 完成兼容适配,在 LDAP 目录用户同步、统一身份认证及双因子认证等模块成功对接。借此机会,双方将加深在产品、解决方案等多个领域的合作,携手共建信创合作生态,打造信创 LDAP 身份目录服务新样本…...
Go: struct 结构体类型和指针【学习笔记记录】
struct 结构体类型和指针struct 结构体类型1. 定义结构体2. 访问结构体成员3. 结构体的使用及匿名字段指针1. 指针变量的声明及使用2. 指针数组的定义及使用3. 函数传参修改值struct 结构体类型 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项…...

量化派递交上市申请,数字经济风口上开启“狂飙”模式
今年全国两会,代表委员们纷纷围绕“中小企业数字化转型”建言献策。如全国政协委员、甘肃省工业和信息化厅副厅长黄宝荣建议,在工业领域加快数字经济立法,支撑中小企业数字化转型;全国政协委员、中国财政科学研究院院长刘尚希建议…...
Linux:IO接口
目录系统调用接口文件描述符一、open二、write三、read四、lseek五、close之前介绍了IO库函数,本文主要介绍系统提供的IO接口,与IO库函数搭配食用效果更佳。 系统调用接口 常使用的IO系统调用接口如下: 接口作用open打开指定的文件write向指…...

cron表达式?
简单理解corn表达式:在使用定时调度任务的时候,我们最常用的,就是cron表达式了。通过cron表达式来指定任务在某个时间点或者周期性的执行。cron表达式配置起来简洁方便,无论是Spring的Scheduled还是用Quartz框架,都支持…...

日常任务开发系统
简介 要求 1、人员信息管理:姓名、性别、出生年月、职称、学位、学习或承担的课 2、任务发布模块: 1)任务信息至少需包含:任务名称、任务类型、任务开始时间、任务截至时间、任务需要的人数、任务分值,是否需要提交任务成果等字段 2)可指定任务申领人…...

SQLMap安装教程
注意:在python3环境下安装sqlmap的时候会提示需要在python2的环境下才能安装,其实在python3.6以后也都支持sqlmap了。 sqlmap安装步骤: 一、下载python; 下载地址 https://www.python.org/downloads/ 下载教程参考(…...
【每日一题】蓝桥杯Day06
文章目录一、星期计算1、问题描述2、思路解析3、AC代码4、代码解析二、考勤刷卡1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析三、卡片1、问题描述2、解题思路3、AC代码4、代码解析5、算法分析一、星期计算 原题链接:星期计算 1、问题描述 本题为填空题&a…...

实体店创业项目 - 开个网咖需要投入多少钱?主要有哪些费用?
创业开个网咖需求投入的资金主要包括场所租金、装饰费用、设备费用、人员薪酬、水电费用等。详细投入多少钱,需求依据不同区域的市场情况和经营策略来确定。一般来说,开一家中等规划的网咖需求投入10万元以上的资金。 主要有哪些费用? 场所租…...

Linux基础命令-ss显示socket信息
Linux基础命令-netstat显示网络状态 ss 一. 命令介绍 先使用手册查看命令介绍信息 NAME ss - another utility to investigate sockets DESCRIPTION ss is used to dump socket statistics. It allows showing information similar to netstat. It can display more TCP and …...

用一个例子告诉你 怎样在spark中创建累加器
目录 1.说明 1.1 什么是累加器 1.2 累加器的功能 2. 使用累加器 3. 累加器和reduce、fold算子的区别 1.说明 1.1 什么是累加器 累加器是Spark提供的一个共享变量(Shared Variables) 默认情况下,如果Executor节点上使用到了Driver端定义的变量(通过算子传…...
ICG-Avidin,吲哚菁绿标记的亲和素,应用:生物成像、生物检测、免疫组织化学、微阵列检测制备纳米胶束或微球或其他纳米粒子装载ICG实现成像。
ICG-Avidin,吲哚菁绿标记的亲和素 中文名称:吲哚菁绿标记的亲和素 英文名称:ICG-Avidin 激发发射波长:785/821nm 性状:绿色粉末 溶剂:水,部分常规有机溶剂 稳定性:-20℃下干燥避光 应用&…...

Promise的理解和使用
Promise是什么 抽象表达 promise 是一门新的技术(ES6规范)Promise 是JS中进行异步编程的新解决方案 具体表达 从语法上来说:Promise是一个构造函数从功能上来说:promise对象用来封装一个异步操作并可以获取其成功/失败的结果 回调函数就…...

TCP
TCP 流量控制 一般来说,我们希望数据传输的快一些,但如果对方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失 流量控制就是让发送方的发送速率不要太快,让接收方来得及接收 利用滑动窗口机制可以在TCP连接上实现对发送方的流量控制 TCP接收方利用自己的接收…...

Python每日一练(20230310)
目录 1. 爬楼梯 ★ 2. 删除无效的括号 ★★★ 3. 给表达式添加运算符 ★★★ 🌟 每日一练刷题专栏 C/C 每日一练 专栏 Python 每日一练 专栏 1. 爬楼梯 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方…...
LeetCode-1590. 使数组和能被 P 整除【前缀和,哈希表】
LeetCode-1590. 使数组和能被 P 整除【前缀和,哈希表】题目描述:解题思路一:前缀和,具体看注释。解题思路二:在遍历过程中计算前缀和解题思路三:0题目描述: 给你一个正整数数组 nums࿰…...
Java核心类库
Java核心类库类Math(☆☆☆)System(☆☆☆)Object(☆☆☆☆)Objects (☆)BigDecimal(☆☆☆☆)基本类型的包装类(☆☆☆☆☆)算法(☆☆☆☆☆)二分查找冒泡排序递归Arrays(☆☆☆☆)Date (☆☆☆☆☆)SimpleDateFormat(☆☆☆☆☆)LocalDateTime (☆)Throwable 类(☆☆☆☆)Str…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...

PH热榜 | 2025-06-08
1. Thiings 标语:一套超过1900个免费AI生成的3D图标集合 介绍:Thiings是一个不断扩展的免费AI生成3D图标库,目前已有超过1900个图标。你可以按照主题浏览,生成自己的图标,或者下载整个图标集。所有图标都可以在个人或…...
Python第七周作业
Python第七周作业 文章目录 Python第七周作业 1.使用open以只读模式打开文件data.txt,并逐行打印内容 2.使用pathlib模块获取当前脚本的绝对路径,并创建logs目录(若不存在) 3.递归遍历目录data,输出所有.csv文件的路径…...

OPENCV图形计算面积、弧长API讲解(1)
一.OPENCV图形面积、弧长计算的API介绍 之前我们已经把图形轮廓的检测、画框等功能讲解了一遍。那今天我们主要结合轮廓检测的API去计算图形的面积,这些面积可以是矩形、圆形等等。图形面积计算和弧长计算常用于车辆识别、桥梁识别等重要功能,常用的API…...

二叉树-144.二叉树的前序遍历-力扣(LeetCode)
一、题目解析 对于递归方法的前序遍历十分简单,但对于一位合格的程序猿而言,需要掌握将递归转化为非递归的能力,毕竟递归调用的时候会调用大量的栈帧,存在栈溢出风险。 二、算法原理 递归调用本质是系统建立栈帧,而非…...