当前位置: 首页 > article >正文

Framework层JNI侧Binder

目录

一,Binder JNI在整个系统的位置

1.1 小结

二,代码分析

2.1 BBinder创建

2.2 Bpinder是在查找服务时候创建的

2.3 JNI实现

 2.4 JNI层android_os_BinderProxy_transact

2.5 BPProxy实现

2)调用IPCThreadState发送数据到Binder驱动

2.6 waitForResponse处理    

2.7 talkWithDriver处理

三,Binder服务端分析

3.1 Binder服务端的驱动入口 getAndExecuteCommand

3.2 executeCommand执行回掉

3.3 BBinder处理回掉

3.4 再次回到android_util_Binder.cpp里

3.5 回调Java层Binder

四,结束语


一,Binder JNI在整个系统的位置

1.1 小结

1)上面红色圈出来的这部分就是Binder JNI,以及整个Android 系统JNI所在的位置,链接这Java层和Native层的纽带,起到承上启下的作用,就和系统的syscal函数一样

2)在研究Binder的时候,需要关注几个概念

 
Java层native层备注
Binder.avaBinder.cpp--JavaBBinderBinderProxy.java是Binder.ava的内部类
BinderProxy.javaBpBinder.cpp-->BpBinder
ProcessState.cpp。getStrongProxyForHandle创建BpBinder
mObjectgBinderOffsets.mObjectmObject其实就是BpBinder
mOwner
mDescriptor
initstatic void android_os_Binder_init(JNIEnv* env, jobject obj)
{ "init", "()V", (void*)android_os_Binder_init },
execTransact
mExecTransact

二,代码分析

java层Binder分析

 */
public class Binder implements IBinder {private long mObject;public Binder() {init();}public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {if (mDescriptor.equals(descriptor)) {return mOwner;}return null;}protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,int flags) throws RemoteException {if (code == INTERFACE_TRANSACTION) {reply.writeString(getInterfaceDescriptor());return true;} else if (code == DUMP_TRANSACTION) {ParcelFileDescriptor fd = data.readFileDescriptor();String[] args = data.readStringArray();if (fd != null) {try {dump(fd.getFileDescriptor(), args);} finally {IoUtils.closeQuietly(fd);}}// Write the StrictMode header.return true;}return false;}public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,int flags) throws RemoteException {boolean r = onTransact(code, data, reply, flags);if (reply != null) {reply.setDataPosition(0);}return r;}public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {}/*** Local implementation is a no-op.*/public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {return true;}private native final void init();private native final void destroyBinder();// Entry point from android_util_Binder.cpp's onTransactprivate boolean execTransact(int code, long dataObj, long replyObj,int flags) {res = onTransact(code, data, reply, flags);}  return res;}
}final class BinderProxy implements IBinder {public native boolean pingBinder();public native boolean isBinderAlive();public IInterface queryLocalInterface(String descriptor) {return null;}public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {try {return transactNative(code, data, reply, flags);} finally {}}public native String getInterfaceDescriptor() throws RemoteException;public native boolean transactNative(int code, Parcel data, Parcel reply,int flags) throws RemoteException;public native void linkToDeath(DeathRecipient recipient, int flags)throws RemoteException;public native boolean unlinkToDeath(DeathRecipient recipient, int flags);BinderProxy() {mSelf = new WeakReference(this);}private native final void destroy();final private WeakReference mSelf;private long mObject;private long mOrgue;
}

2.1 BBinder创建

整个java层Binder代码其实很简单,主要的对象就两个,客户端拥有的是BinderProxy,服务端拿到的是Binder对象,要特别注意的是Native层的BBinder是在Java层的Binder构造函数中同时初始化的

2.2 Bpinder是在查找服务时候创建的

AndroidRuntime::startReg(JNIEnv* env)注册的Binder JNI
REG_JNI(register_android_view_KeyCharacterMap),REG_JNI(register_android_os_Process),REG_JNI(register_android_os_SystemProperties),REG_JNI(register_android_os_Binder),

2.3 JNI实现

/Volumes/aosp/android-8.1.0_r52/frameworks/base/core/jni/android_util_Binder.cpp

int register_android_os_Binder(JNIEnv* env)
{if (int_register_android_os_Binder(env) < 0)return -1;if (int_register_android_os_BinderInternal(env) < 0)return -1;if (int_register_android_os_BinderProxy(env) < 0)return -1;jclass clazz = FindClassOrDie(env, "android/util/Log");gLogOffsets.mClass = MakeGlobalRefOrDie(env, clazz);gLogOffsets.mLogE = GetStaticMethodIDOrDie(env, clazz, "e","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I");clazz = FindClassOrDie(env, "android/os/ParcelFileDescriptor");gParcelFileDescriptorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);gParcelFileDescriptorOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>","(Ljava/io/FileDescriptor;)V");clazz = FindClassOrDie(env, "android/os/StrictMode");gStrictModeCallbackOffsets.mClass = MakeGlobalRefOrDie(env, clazz);gStrictModeCallbackOffsets.mCallback = GetStaticMethodIDOrDie(env, clazz,"onBinderStrictModePolicyChange", "(I)V");clazz = FindClassOrDie(env, "java/lang/Thread");gThreadDispatchOffsets.mClass = MakeGlobalRefOrDie(env, clazz);gThreadDispatchOffsets.mDispatchUncaughtException = GetMethodIDOrDie(env, clazz,"dispatchUncaughtException", "(Ljava/lang/Throwable;)V");gThreadDispatchOffsets.mCurrentThread = GetStaticMethodIDOrDie(env, clazz, "currentThread","()Ljava/lang/Thread;");return 0;
}

 2.4 JNI层android_os_BinderProxy_transact

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{if (dataObj == NULL) {jniThrowNullPointerException(env, NULL);return JNI_FALSE;}Parcel* data = parcelForJavaObject(env, dataObj);if (data == NULL) {return JNI_FALSE;}Parcel* reply = parcelForJavaObject(env, replyObj);if (reply == NULL && replyObj != NULL) {return JNI_FALSE;}IBinder* target = (IBinder*)env->GetLongField(obj, gBinderProxyOffsets.mObject);if (target == NULL) {jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");return JNI_FALSE;}ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",target, obj, code);bool time_binder_calls;int64_t start_millis;if (kEnableBinderSample) {// Only log the binder call duration for things on the Java-level main thread.// But if we don'ttime_binder_calls = should_time_binder_calls();if (time_binder_calls) {start_millis = uptimeMillis();}}//printf("Transact from Java code to %p sending: ", target); data->print();status_t err = target->transact(code, *data, reply, flags);//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();

 这块代码最核心的做了两件事情

1)找到target

IBinder* target = (IBinder*)env->GetLongField(obj, gBinderProxyOffsets.mObject);

其实就是BpBinder

3)调用target-->transact

2.5 BPProxy实现

/Volumes/aosp/android-8.1.0_r52/frameworks/native/libs/binder/BpBinder.cpp        

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{// Once a binder has died, it will never come back to life.if (mAlive) {status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);if (status == DEAD_OBJECT) mAlive = 0;return status;}return DEAD_OBJECT;
}

这块最核心的也是做了2年事情

1):创建新线程执行任务

/Volumes/aosp/android-8.1.0_r52/frameworks/native/libs/binder/IPCThreadState.cpp

从下面这块的代码可以看到,每次业务层调用一次binder发送数据到服务端都会创建一个新的binder线程,这个会导致binder驱动调用到Binder服务端创建新的线程,要特别注意这里的

mHandle,是根据这个mHandle,binder驱动找到对应的ref-->node-->proc
IPCThreadState* IPCThreadState::self()
{if (gHaveTLS) {
restart:const pthread_key_t k = gTLS;IPCThreadState* st = (IPCThreadState*)pthread_getspecific(k);if (st) return st;return new IPCThreadState;}if (gShutdown) {ALOGW("Calling IPCThreadState::self() during shutdown is dangerous, expect a crash.\n");return NULL;}pthread_mutex_lock(&gTLSMutex);if (!gHaveTLS) {int key_create_value = pthread_key_create(&gTLS, threadDestructor);if (key_create_value != 0) {pthread_mutex_unlock(&gTLSMutex);ALOGW("IPCThreadState::self() unable to create TLS key, expect a crash: %s\n",strerror(key_create_value));return NULL;}gHaveTLS = true;}pthread_mutex_unlock(&gTLSMutex);goto restart;
}

2)调用IPCThreadState发送数据到Binder驱动

/Volumes/aosp/android-8.1.0_r52/frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::transact(int32_t handle,uint32_t code, const Parcel& data,Parcel* reply, uint32_t flags)
{-----------------------if (err == NO_ERROR) {LOG_ONEWAY(">>>> SEND from pid %d uid %d %s", getpid(), getuid(),(flags & TF_ONE_WAY) == 0 ? "READ REPLY" : "ONE WAY");err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);}if (err != NO_ERROR) {if (reply) reply->setError(err);return (mLastError = err);}if ((flags & TF_ONE_WAY) == 0) {#if 0if (code == 4) { // relayoutALOGI(">>>>>> CALLING transaction 4");} else {ALOGI(">>>>>> CALLING transaction %d", code);}#endifif (reply) {err = waitForResponse(reply);} else {Parcel fakeReply;err = waitForResponse(&fakeReply);}-----------------------}return err;
}

1)通过writeTransactionData组装好数据,也就是我们说的打包数据,把数据打包到

binder_transaction_data对象
status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{binder_transaction_data tr;tr.target.ptr = 0; /* Don't pass uninitialized stack data to a remote process */tr.target.handle = handle;tr.code = code;tr.flags = binderFlags;tr.cookie = 0;tr.sender_pid = 0;tr.sender_euid = 0;
-------------------------mOut.writeInt32(cmd);mOut.write(&tr, sizeof(tr));return NO_ERROR;
}

2) 同步/异步处理,如果是同步的话,那么就在这里等结果,如果是异步的话,那么分两种情况,对于异步需要回复的,那么还是在这里等结果,否则就返回一个假结果

2.6 waitForResponse处理    

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{uint32_t cmd;int32_t err;while (1) {if ((err=talkWithDriver()) < NO_ERROR) break;err = mIn.errorCheck();if (err < NO_ERROR) break;if (mIn.dataAvail() == 0) continue;cmd = (uint32_t)mIn.readInt32();IF_LOG_COMMANDS() {alog << "Processing waitForResponse Command: "<< getReturnString(cmd) << endl;}switch (cmd) {case BR_TRANSACTION_COMPLETE:if (!reply && !acquireResult) goto finish;break;case BR_DEAD_REPLY:err = DEAD_OBJECT;goto finish;case BR_FAILED_REPLY:err = FAILED_TRANSACTION;goto finish;

此时循环等待binder驱动的结果   

2.7 talkWithDriver处理

status_t IPCThreadState::talkWithDriver(bool doReceive)
{if (mProcess->mDriverFD <= 0) {return -EBADF;}binder_write_read bwr;// Is the read buffer empty?const bool needRead = mIn.dataPosition() >= mIn.dataSize();// We don't want to write anything if we are still reading// from data left in the input buffer and the caller// has requested to read the next data.const size_t outAvail = (!doReceive || needRead) ? mOut.dataSize() : 0;bwr.write_size = outAvail;bwr.write_buffer = (uintptr_t)mOut.data();// This is what we'll read.if (doReceive && needRead) {bwr.read_size = mIn.dataCapacity();bwr.read_buffer = (uintptr_t)mIn.data();} else {bwr.read_size = 0;bwr.read_buffer = 0;}IF_LOG_COMMANDS() {TextOutput::Bundle _b(alog);if (outAvail != 0) {alog << "Sending commands to driver: " << indent;const void* cmds = (const void*)bwr.write_buffer;const void* end = ((const uint8_t*)cmds)+bwr.write_size;alog << HexDump(cmds, bwr.write_size) << endl;while (cmds < end) cmds = printCommand(alog, cmds);alog << dedent;}alog << "Size of receive buffer: " << bwr.read_size<< ", needRead: " << needRead << ", doReceive: " << doReceive << endl;}// Return immediately if there is nothing to do.if ((bwr.write_size == 0) && (bwr.read_size == 0)) return NO_ERROR;bwr.write_consumed = 0;bwr.read_consumed = 0;status_t err;do {IF_LOG_COMMANDS() {alog << "About to read/write, write size = " << mOut.dataSize() << endl;}
#if defined(__ANDROID__)if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)err = NO_ERROR;elseerr = -errno;
#elseerr = INVALID_OPERATION;
#endifif (mProcess->mDriverFD <= 0) {err = -EBADF;}IF_LOG_COMMANDS() {alog << "Finished read/write, write size = " << mOut.dataSize() << endl;}} while (err == -EINTR);

 通过ioctl把客户端数据发给binder驱动,等待binder驱动的cmd和数据

三,Binder服务端分析

上面的第2篇章分析了Binder proxy侧的原理,接下来分析binder 服务端原理

Binder服务端的驱动入口是在Binder驱动里面返回的cmd和数据

3.1 Binder服务端的驱动入口 getAndExecuteCommand

/Volumes/aosp/android-8.1.0_r52/frameworks/native/libs/binder/IPCThreadState.cpp

status_t IPCThreadState::getAndExecuteCommand()
{status_t result;int32_t cmd;result = talkWithDriver();if (result >= NO_ERROR) {size_t IN = mIn.dataAvail();if (IN < sizeof(int32_t)) return result;cmd = mIn.readInt32();IF_LOG_COMMANDS() {alog << "Processing top-level Command: "<< getReturnString(cmd) << endl;}pthread_mutex_lock(&mProcess->mThreadCountLock);mProcess->mExecutingThreadsCount++;if (mProcess->mExecutingThreadsCount >= mProcess->mMaxThreads &&mProcess->mStarvationStartTimeMs == 0) {mProcess->mStarvationStartTimeMs = uptimeMillis();}pthread_mutex_unlock(&mProcess->mThreadCountLock);result = executeCommand(cmd);pthread_mutex_lock(&mProcess->mThreadCountLock);mProcess->mExecutingThreadsCount--;if (mProcess->mExecutingThreadsCount < mProcess->mMaxThreads &&mProcess->mStarvationStartTimeMs != 0) {int64_t starvationTimeMs = uptimeMillis() - mProcess->mStarvationStartTimeMs;if (starvationTimeMs > 100) {ALOGE("binder thread pool (%zu threads) starved for %" PRId64 " ms",mProcess->mMaxThreads, starvationTimeMs);}mProcess->mStarvationStartTimeMs = 0;}pthread_cond_broadcast(&mProcess->mThreadCountDecrement);pthread_mutex_unlock(&mProcess->mThreadCountLock);}return result;
}

具体Binder驱动如何回掉过来的可以参考android系统_Binder驱动原理-CSDN博客这篇文章

3.2 executeCommand执行回掉

status_t IPCThreadState::executeCommand(int32_t cmd)
{BBinder* obj;RefBase::weakref_type* refs;status_t result = NO_ERROR;switch ((uint32_t)cmd) {case BR_TRANSACTION:{binder_transaction_data tr;result = mIn.read(&tr, sizeof(tr));ALOG_ASSERT(result == NO_ERROR,"Not enough command data for brTRANSACTION");if (result != NO_ERROR) break;Parcel buffer;buffer.ipcSetDataReference(reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer),tr.data_size,reinterpret_cast<const binder_size_t*>(tr.data.ptr.offsets),tr.offsets_size/sizeof(binder_size_t), freeBuffer, this);const pid_t origPid = mCallingPid;const uid_t origUid = mCallingUid;const int32_t origStrictModePolicy = mStrictModePolicy;const int32_t origTransactionBinderFlags = mLastTransactionBinderFlags;mCallingPid = tr.sender_pid;mCallingUid = tr.sender_euid;mLastTransactionBinderFlags = tr.flags;//ALOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);Parcel reply;status_t error;IF_LOG_TRANSACTIONS() {TextOutput::Bundle _b(alog);alog << "BR_TRANSACTION thr " << (void*)pthread_self()<< " / obj " << tr.target.ptr << " / code "<< TypeCode(tr.code) << ": " << indent << buffer<< dedent << endl<< "Data addr = "<< reinterpret_cast<const uint8_t*>(tr.data.ptr.buffer)<< ", offsets addr="<< reinterpret_cast<const size_t*>(tr.data.ptr.offsets) << endl;}if (tr.target.ptr) {// We only have a weak reference on the target object, so we must first try to// safely acquire a strong reference before doing anything else with it.if (reinterpret_cast<RefBase::weakref_type*>(tr.target.ptr)->attemptIncStrong(this)) {error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,&reply, tr.flags);reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);} else {error = UNKNOWN_TRANSACTION;}} else {error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);}//ALOGI("<<<< TRANSACT from pid %d restore pid %d uid %d\n",//     mCallingPid, origPid, origUid);if ((tr.flags & TF_ONE_WAY) == 0) {LOG_ONEWAY("Sending reply to %d!", mCallingPid);if (error < NO_ERROR) reply.setError(error);sendReply(reply, 0);} else {LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);}mCallingPid = origPid;mCallingUid = origUid;mStrictModePolicy = origStrictModePolicy;mLastTransactionBinderFlags = origTransactionBinderFlags;IF_LOG_TRANSACTIONS() {TextOutput::Bundle _b(alog);alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "<< tr.target.ptr << ": " << indent << reply << dedent << endl;}}break;

这里最核心的就是做了2年事情

1)BR_TRANSACTION,下面这几句代码才是真正的干货

 if (tr.target.ptr) {/ if (reinterpret_cast<RefBase::weakref_type*>(tr.target.ptr)->attemptIncStrong(this)) {error = reinterpret_cast<BBinder*>(tr.cookie)->transact(tr.code, buffer,&reply, tr.flags);reinterpret_cast<BBinder*>(tr.cookie)->decStrong(this);} else {error = UNKNOWN_TRANSACTION;}} else {error = the_context_object->transact(tr.code, buffer, &reply, tr.flags);}if ((tr.flags & TF_ONE_WAY) == 0) {LOG_ONEWAY("Sending reply to %d!", mCallingPid);if (error < NO_ERROR) reply.setError(error);sendReply(reply, 0);} else {LOG_ONEWAY("NOT sending reply to %d!", mCallingPid);}

我们看到这里是通过弱引用拿到BBinder,如果弱引用拿不到,就通过强引用那,最后调用阿斗BBinder的transact

2)同步处理

tr.flags & TF_ONE_WAY) == 0表示同步,

status_t IPCThreadState::sendReply(const Parcel& reply, uint32_t flags)
{status_t err;status_t statusBuffer;err = writeTransactionData(BC_REPLY, flags, -1, 0, reply, &statusBuffer);if (err < NO_ERROR) return err;return waitForResponse(NULL, NULL);
}

3.3 BBinder处理回掉

status_t BBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{data.setDataPosition(0);status_t err = NO_ERROR;switch (code) {case PING_TRANSACTION:reply->writeInt32(pingBinder());break;default:err = onTransact(code, data, reply, flags);break;}if (reply != NULL) {reply->setDataPosition(0);}return err;
}

 这里其实很关键,又再一次回到了android_util_Binder.cpp里面,class JavaBBinder : public BBinder,因为JavaBBinder继承了BBinder,所以会调用到JavaBBinder里面的onTransact方法

3.4 再次回到android_util_Binder.cpp里

 virtual status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0){JNIEnv* env = javavm_to_jnienv(mVM);--------------------------IPCThreadState* thread_state = IPCThreadState::self();const int32_t strict_policy_before = thread_state->getStrictModePolicy();//printf("Transact from %p to Java code sending: ", this);//data.print();//printf("\n");jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);
----------------------------------------------------return res != JNI_FALSE ? NO_ERROR : UNKNOWN_TRANSACTION;}
通过jni反射调用java层的Binder对象execTransact方法
jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

3.5 回调Java层Binder

/Volumes/aosp/android-8.1.0_r52/frameworks/base/core/java/android/os/Binder.java

 private boolean execTransact(int code, long dataObj, long replyObj,int flags) {--------------------------------res = onTransact(code, data, reply, flags);} c--------------------------------} }

到这里整个过程重于高明白了接下来的onTransact方法就会回到业务自己写的Service中

四,如何寻找Java层对应的JNI

这里有一个经验之谈,凡是在/Volumes/aosp/android-8.1.0_r52/frameworks/base/core/jni/这个目录下面的全是Java层对应的jni层,凡是以android_开头_.cpp结尾的都是Javac层对应的Jni,这个命名规范是:android_模块_对应的java类名.cpp,比如aandroid_os_Parcel.cpp,这个属于os模块,对应的java类是Parcel.java

五,结束语

鉴于作者水平有限,文章之中难免有错误或者遗漏地方,欢迎大家批评指正,也欢迎大家讨论,积极评论哈,谢谢

相关文章:

Framework层JNI侧Binder

目录 一&#xff0c;Binder JNI在整个系统的位置 1.1 小结 二&#xff0c;代码分析 2.1 BBinder创建 2.2 Bpinder是在查找服务时候创建的 2.3 JNI实现 2.4 JNI层android_os_BinderProxy_transact 2.5 BPProxy实现 2&#xff09;调用IPCThreadState发送数据到Binder驱动…...

Windows 图形显示驱动开发-WDDM 3.2-自动显示切换(九)

面板驱动程序 显示器驱动程序是根据从 EDID 生成的即插即用 (PnP) 硬件 ID 加载的。 由于 EDID 保持不变&#xff0c;当任何一个 GPU 控制内部面板时&#xff0c;都会加载面板驱动程序。 这两个驱动程序将显示相同的亮度功能。 因此&#xff0c;加载应该不会造成任何问题&…...

Excel大文件拆分

import pandas as pddef split_excel_file(input_file, output_prefix, num_parts10):# 读取Excel文件df pd.read_excel(input_file)# 计算每部分的行数total_rows len(df)rows_per_part total_rows // num_partsremaining_rows total_rows % num_partsstart_row 0for i i…...

OpenCV计算摄影学(7)HDR成像之多帧图像对齐的类cv::AlignMTB

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 该算法将图像转换为‌中值阈值位图‌&#xff08;Median Threshold Bitmap&#xff0c;MTB&#xff09;&#xff1a; 1.位图生成‌&#xff1a;…...

JWT+redis实现三大令牌管理方案深度解析

三种令牌管理方案对比与评估 1. 仅续期Redis&#xff08;不生成新令牌&#xff09; 实现原理 通过延长Redis中的令牌有效期维持会话&#xff0c;JWT本身不包含动态过期时间。 优点 ✅ 低开销&#xff1a;无需生成新令牌&#xff0c;减少JWT签名计算成本。 ✅ 简单实现&#x…...

北京大学DeepSeek提示词工程与落地场景(PDF无套路免费下载)

近年来&#xff0c;大模型技术飞速发展&#xff0c;但许多用户发现&#xff1a;即使使用同一款 AI 工具&#xff0c;效果也可能天差地别——有人能用 AI 快速生成精准方案&#xff0c;有人却只能得到笼统回答。这背后的关键差异&#xff0c;在于提示词工程的应用能力。 北京大…...

Axure PR 9 中继器 03 翻页控制

大家好&#xff0c;我是大明同学。 接着上期的内容&#xff0c;这期内容&#xff0c;我们来了解一下Axure中继器图表翻页控制。 预览地址&#xff1a;https://pvie5g.axshare.com 翻页控制 1.打开上期RP 文件&#xff0c;在元件库中拖入一个矩形&#xff0c;宽值根据业务实际…...

IO流(师从韩顺平)

文章目录 文件什么是文件文件流 常用的文件操作创建文件对象相关构造器和方法应用案例 获取文件的相关信息应用案例 目录的操作和文件删除应用案例 IO 流原理及流的分类Java IO 流原理IO流的分类 IO 流体系图-常用的类IO 流体系图&#xff08;重要&#xff01;&#xff01;&…...

基于Javase的停车场收费管理系统

基于Javase的停车场收费管理系统 停车场管理系统开发文档 项目概述 1.1 项目背景 随着现代化城市的不断发展&#xff0c;车辆数量不断增加&#xff0c;停车难问题也日益突出。为了更好地管理停车场资 源&#xff0c;提升停车效率&#xff0c;需要一个基于Java SE的停车场管理…...

Exoplayer(MediaX)实现音频变调和变速播放

在K歌或录音类应用中变调是个常见需求&#xff0c;比如需要播出萝莉音/大叔音等。变速播放在影视播放类应用中普遍存在&#xff0c;在传统播放器Mediaplayer中这两个功能都比较难以实现&#xff0c;特别在低版本SDK中&#xff0c;而Exoplayer作为google官方推出的Mediaplayer替…...

Spring Boot集成Jetty、Tomcat或Undertow及支持HTTP/2协议

目录 一、常用Web服务器 1、Tomcat 2、Jetty 3、Undertow 二、什么是HTTP/2协议 1、定义 2、特性 3、优点 4、与HTTP/1.1的区别 三、集成Web服务器并开启HTTP/2协议 1、生成证书 2、新建springboot项目 3、集成Web服务器 3.1 集成Tomcat 3.2 集成Jetty 3.3 集成…...

《Python实战进阶》专栏 No 5:GraphQL vs RESTful API 对比与实现

《Python实战进阶》专栏包括68集&#xff0c;每一集聚焦一个中高级技术知识点&#xff0c;涵盖Python在Web开发、数据处理、自动化、机器学习、并发编程等领域的应用&#xff0c;系统梳理Python开发者的知识集。本集的主题为&#xff1a; No4 : GraphQL vs RESTful API 对比与实…...

类和对象——static修饰类的成员

static修饰类的成员 static成员1 static成员的概念2 特性 static成员 有时会有这样的需求&#xff1a;计算程序中创建出了多少个类的对象&#xff0c;以及多少个正在使用的对象。 因为构造函数和析构函数都只会调用一次&#xff0c;所以可以通过设置生命周期和main函数一致的…...

RabbitMQ系列(七)基本概念之Channel

RabbitMQ 中的 Channel&#xff08;信道&#xff09; 是客户端与 RabbitMQ 服务器通信的虚拟会话通道&#xff0c;其核心作用在于优化资源利用并提升消息处理效率。以下是其核心机制与功能的详细解析&#xff1a; 一、Channel 的核心定义 虚拟通信链路 Channel 是建立在 TCP 连…...

你对 Spring Cloud 的理解

Spring Cloud 是一个基于 Spring Boot 的微服务架构开发工具集&#xff0c;为开发者提供了快速构建分布式系统的一系列解决方案&#xff0c;涵盖了服务发现、配置管理、熔断器、智能路由、微代理、控制总线等多个方面。 从核心组件来看&#xff1a; 服务发现&#xff1a;以 Eu…...

MYSQL 5.7数据库,关于1067报错 invalid default value for,解决方法!

???作者&#xff1a; 米罗学长 ???个人简介&#xff1a;混迹java圈十余年&#xff0c;精通Java、小程序、数据库等。 ???各类成品java毕设 。javaweb&#xff0c;ssm&#xff0c;springboot&#xff0c;mysql等项目&#xff0c;源码丰富&#xff0c;欢迎咨询。 ???…...

C# Enumerable类 之 数据筛选

总目录 前言 在 C# 中&#xff0c;System.Linq.Enumerable 类是 LINQ&#xff08;Language Integrated Query&#xff09;的核心组成部分&#xff0c;它提供了一系列静态方法&#xff0c;用于操作实现了 IEnumerable 接口的集合。通过这些方法&#xff0c;我们可以轻松地对集合…...

运维基础知识(一)

一:SSH端口 首先SSH是什么? SSH(Secure Shell)是Linux、Unix、Mac及其他网络设备最常用的远程CLI管理协议,SSH使用秘钥对数据进行加密,保证了远程管理数据的安全性。 Secure Shell (SSH) 是一种网络协议,允许用户通过加密的通道安全地访问另一台计算机。SSH广泛用于远程…...

权重生成图像

简介 前面提到的许多生成模型都有保存了生成器的权重,本章主要介绍如何使用训练好的权重文件通过生成器生成图像。 但是如何使用权重生成图像呢? 一、参数配置 ima_size 为图像尺寸,这个需要跟你模型训练的时候resize的时候一样。 latent_dim为噪声维度,一般的设置都是…...

【Linux基础】Linux下的C编程指南

目录 一、前言 二、Vim的使用 2.1 普通模式 2.2 插入模式 2.3 命令行模式 2.4 可视模式 三、GCC编译器 3.1 预处理阶段 3.2 编译阶段 3.3 汇编阶段 3.4 链接阶段 3.5 静态库和动态库 四、Gdb调试器 五、总结 一、前言 在Linux环境下使用C语言进行编程是一项基础且…...

DeepSeek-OpenSourceWeek-第四天-Optimized Parallelism Strategies

DeepSeek 在 #OpenSourceWeek(开源周) 的第四天推出了两项新工具,旨在让深度学习更快、更高效:**DualPipe** 和 **EPLB**。 DualPipe 定义:DualPipe 是一种用于 V3/R1 训练中计算与通信重叠的双向pipline并行算法。 作用:它通过实现前向和后向计算-通信阶段的完全重叠,减…...

Python Cookbook-2.15 用类文件对象适配真实文件对象

任务 需要传递一个类似文件的对象(比如&#xff0c;调用urllib.urlopen 返回的结果)给一个函数或者方法&#xff0c;但这个函数或方法要求只接受真实的文件对象(比如&#xff0c;像marshalload 这样的函数)。 解决方案 为了过类型检查这一关&#xff0c;我们需要将类文件对象…...

浅谈HTTP及HTTPS协议

1.什么是HTTP&#xff1f; HTTP全称是超文本传输协议&#xff0c;是一种基于TCP协议的应用非常广泛的应用层协议。 1.1常见应用场景 一.浏览器与服务器之间的交互。 二.手机和服务器之间通信。 三。多个服务器之间的通信。 2.HTTP请求详解 2.1请求报文格式 我们首先看一下…...

Pytest自定义测试用例执行顺序

文章目录 1.前言2.pytest默认执行顺序3.pytest自定义执行顺序 1.前言 在pytest中&#xff0c;我们可能需要自定义测试用例的执行顺序&#xff0c;例如登陆前需要先注册&#xff0c;这个时候就需要先执行注册的测试用例再执行登录的测试用例。 本文主要讲解pytest的默认执行顺序…...

人大金仓KCA | 用户与角色

人大金仓KCA | 用户与角色 一、知识预备1. 用户和角色 二、具体实施1. 用户管理-命令行1.1 创建和修改用户1.2 修改用户密码1.3 修改用户的并发连接数1.4 修改用户的密码有效期 2.用户管理-EasyKStudio2.1 创建和修改用户2.2 修改用户密码2.3 修改用户的并发连接数2.4 修改用户…...

【Azure 架构师学习笔记】- Azure Databricks (12) -- Medallion Architecture简介

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Databricks】系列。 接上文 【Azure 架构师学习笔记】- Azure Databricks (11) – UC搭建 前言 使用ADB 或者数据湖&#xff0c;基本上绕不开一个架构“Medallion”&#xff0c; 它使得数据管理更为简单有效。ADB 通过…...

什么是Ollama?什么是GGUF?二者之间有什么关系?

一、Ollama:本地化大模型运行框架 Ollama 是一款开源工具,专注于在本地环境中快速部署和运行大型语言模型(LLM)。它通过极简的命令行操作简化了模型管理流程,支持离线运行、多模型并行、私有化部署等场景。 核心特性 本地化运行:无需依赖云端API,用户可在个人电脑或服务…...

智能证件照处理器(深度学习)

功能说明:支持常见证件照尺寸(一寸、二寸、护照等) 智能背景去除(使用深度学习模型)自定义背景颜色选择自动调整尺寸并保持比例实时预览处理效果注意:整合rembg进行抠图,使用Pillow处理图像缩放和背景替换,定义常见证件照尺寸,并提供用户交互选项。首次运行时会自动下…...

【软考】【2025年系统分析师拿证之路】【啃书】第十五章 系统运行与维护(十六)

目录 运维技术指标系统运行管理系统用户管理网络资源管理软件资源管理 系统故障管理软件系统维护系统评价遗留系统处理遗留系统的评价遗留系统的演化 新旧系统转换数据转换和迁移 现有系统演进 运维技术指标 平均故障修复时间&#xff08;MTTR&#xff09;平均应答时间&#x…...

C++-第十三章:红黑树

目录 第一节&#xff1a;红黑树的特征 第二节&#xff1a;实现思路 2-1.插入 2-1-1.unc为红 2-1-2.cur为par的左子树&#xff0c;且par为gra的左子树(cur在最左边) 2-1-2-1.unc不存在 2-1-2-2.unc为黑 2-1-3.cur为par的右子树&#xff0c;且par为gra的右子树(cur在最右侧) 2-…...