Android车载——VehicleHal运行流程(Android 11)

1 概述

本篇主要讲解VehicleHal的主要运行流程,包括设置属性、获取属性、订阅属性、取消订阅、持续上报属性订阅等。

2 获取属性流程

2.1 获取属性流程源码分析

作为服务注册到hwServiceManager中的类是VehicleHalManager,所以,CarService对服务端的调用的hidl接口都是调用到了VehicleHalManager中。

get(VehiclePropValue requestedPropValue)generates (StatusCode status, VehiclePropValue propValue);

IVehicle这个hidl接口中的定义如上

Return<void> VehicleHalManager::get(const VehiclePropValue& requestedPropValue, get_cb _hidl_cb) {const auto* config = getPropConfigOrNull(requestedPropValue.prop);if (config == nullptr) {ALOGE("Failed to get value: config not found, property: 0x%x",requestedPropValue.prop);_hidl_cb(StatusCode::INVALID_ARG, kEmptyValue);return Void();}if (!checkReadPermission(*config)) {_hidl_cb(StatusCode::ACCESS_DENIED, kEmptyValue);return Void();}StatusCode status;auto value = mHal->get(requestedPropValue, &status);_hidl_cb(status, value.get() ? *value : kEmptyValue);return Void();
}

调用之后会调用到VehicleHalManager中的get函数

const VehiclePropConfig* VehicleHalManager::getPropConfigOrNull(int32_t prop) const {return mConfigIndex->hasConfig(prop)? &mConfigIndex->getConfig(prop) : nullptr;
}

首先,会从属性配置列表中是否存在这个属性,这个是初始化的时候缓存的,缓存的是所有的属性配置。
如果获取的属性不在属性配置列表中,则不能够获取,如果存在,会判断访问权限,访问权限校验通过之后,会调用mHal的get函数。

VehicleHal::VehiclePropValuePtr EmulatedVehicleHal::get(const VehiclePropValue& requestedPropValue, StatusCode* outStatus) {auto propId = requestedPropValue.prop;ALOGV("get(%d)", propId);auto& pool = *getValuePool();VehiclePropValuePtr v = nullptr;switch (propId) {case OBD2_FREEZE_FRAME:v = pool.obtainComplex();*outStatus = fillObd2FreezeFrame(requestedPropValue, v.get());break;case OBD2_FREEZE_FRAME_INFO:v = pool.obtainComplex();*outStatus = fillObd2DtcInfo(v.get());break;default:if (mEmulatedUserHal != nullptr && mEmulatedUserHal->isSupported(propId)) {ALOGI("get(): getting value for prop %d from User HAL", propId);const auto& ret = mEmulatedUserHal->onGetProperty(requestedPropValue);if (!ret.ok()) {ALOGE("get(): User HAL returned error: %s", ret.error().message().c_str());*outStatus = StatusCode(ret.error().code());} else {auto value = ret.value().get();if (value != nullptr) {ALOGI("get(): User HAL returned value: %s", toString(*value).c_str());v = getValuePool()->obtain(*value);*outStatus = StatusCode::OK;} else {ALOGE("get(): User HAL returned null value");*outStatus = StatusCode::INTERNAL_ERROR;}}break;}auto internalPropValue = mPropStore->readValueOrNull(requestedPropValue);if (internalPropValue != nullptr) {v = getValuePool()->obtain(*internalPropValue);}*outStatus = v != nullptr ? StatusCode::OK : StatusCode::INVALID_ARG;break;}if (v.get()) {v->timestamp = elapsedRealtimeNano();}return v;
}

首先,会对userhal的一些判断操作,这个EmulatedUserHal是跟用户身份相关的hal定义,用于处理用户切换相关事件。如果是用户hal相关的prop获取,则获取完成之后就直接跳出。如果不是,则走普通的property获取路径,从VehiclePropertyStore中读取。

using PropertyMap = std::map<RecordId, VehiclePropValue>;
PropertyMap mPropertyValues;std::unique_ptr<VehiclePropValue> VehiclePropertyStore::readValueOrNull(int32_t prop, int32_t area, int64_t token) const {RecordId recId = {prop, isGlobalProp(prop) ? 0 : area, token };MuxGuard g(mLock);const VehiclePropValue* internalValue = getValueOrNullLocked(recId);return internalValue ? std::make_unique<VehiclePropValue>(*internalValue) : nullptr;
}const VehiclePropValue* VehiclePropertyStore::getValueOrNullLocked(const VehiclePropertyStore::RecordId& recId) const  {auto it = mPropertyValues.find(recId);return it == mPropertyValues.end() ? nullptr : &it->second;
}

从mPropertyValues这个map中去获取对应propId的property。mPropertyValues里面的值是在哪填充的呢?

bool VehiclePropertyStore::writeValue(const VehiclePropValue& propValue,bool updateStatus) {MuxGuard g(mLock);if (!mConfigs.count(propValue.prop)) return false;RecordId recId = getRecordIdLocked(propValue);VehiclePropValue* valueToUpdate = const_cast<VehiclePropValue*>(getValueOrNullLocked(recId));if (valueToUpdate == nullptr) {mPropertyValues.insert({ recId, propValue });return true;}// propValue is outdated and drops it.if (valueToUpdate->timestamp > propValue.timestamp) {return false;}// update the propertyValue.// The timestamp in propertyStore should only be updated by the server side. It indicates// the time when the event is generated by the server.valueToUpdate->timestamp = propValue.timestamp;valueToUpdate->value = propValue.value;if (updateStatus) {valueToUpdate->status = propValue.status;}return true;
}

是在这个函数中,这个函数是在VHAL初始化的时候调用的,初始化的时候,会遍历一个定义了所有支持属性的列表,并调用writeValue函数将属性配置和属性值缓存到VehiclePropertyStore中。
以上就是CarService从VHAL获取属性的流程,总结来说就是:从VHAL的缓存map中获取属性。

2.2 获取属性流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant EmulatedVehicleHal
participant VehiclePropertyStore
endboxCarService -> VehicleHalManager: get(const VehiclePropValue& \n\trequestedPropValue, get_cb _hidl_cb)
VehicleHalManager -> EmulatedVehicleHal: get(const VehiclePropValue& \n\trequestedPropValue, get_cb _hidl_cb)
EmulatedVehicleHal -> VehiclePropertyStore: readValueOrNull(int32_t prop, \n\tint32_t area, int64_t token)
VehiclePropertyStore -> EmulatedVehicleHal: propValue
EmulatedVehicleHal -> VehicleHalManager: propValue
VehicleHalManager -> CarService: _hidl_cb(propValue)@enduml

在这里插入图片描述

3 设置属性流程

3.1 设置属性流程源码分析

hidl调用后还是从VehicleHalManager开始的

Return<StatusCode> VehicleHalManager::set(const VehiclePropValue &value) {auto prop = value.prop;const auto* config = getPropConfigOrNull(prop);if (config == nullptr) {ALOGE("Failed to set value: config not found, property: 0x%x", prop);return StatusCode::INVALID_ARG;}if (!checkWritePermission(*config)) {return StatusCode::ACCESS_DENIED;}handlePropertySetEvent(value);auto status = mHal->set(value);return Return<StatusCode>(status);
}

首先判断缓存中是否有该属性的属性配置,有才支持后续的set操作
handlePropertySetEvent是对带有EVENTS_FROM_ANDROID这个订阅标签属性的处理,这种属性的设置需要直接上报给上层。
然后是调用EmulatedVehicleHal的set函数

StatusCode EmulatedVehicleHal::set(const VehiclePropValue& propValue) {constexpr bool updateStatus = false;//这里是模拟车辆属性if (propValue.prop == kGenerateFakeDataControllingProperty) {// Send the generator controlling request to the server.// 'updateStatus' flag is only for the value sent by setProperty (propValue in this case)// instead of the generated values triggered by it. 'propValue' works as a control signal// here, since we never send the control signal back, the value of 'updateStatus' flag// does not matter here.auto status = mVehicleClient->setProperty(propValue, updateStatus);return status;//处理空调相关的属性} else if (mHvacPowerProps.count(propValue.prop)) {auto hvacPowerOn = mPropStore->readValueOrNull(toInt(VehicleProperty::HVAC_POWER_ON),(VehicleAreaSeat::ROW_1_LEFT | VehicleAreaSeat::ROW_1_RIGHT |VehicleAreaSeat::ROW_2_LEFT | VehicleAreaSeat::ROW_2_CENTER |VehicleAreaSeat::ROW_2_RIGHT));if (hvacPowerOn && hvacPowerOn->value.int32Values.size() == 1&& hvacPowerOn->value.int32Values[0] == 0) {return StatusCode::NOT_AVAILABLE;}} else {// Handle property specific codeswitch (propValue.prop) {case OBD2_FREEZE_FRAME_CLEAR:return clearObd2FreezeFrames(propValue);case VEHICLE_MAP_SERVICE:// Placeholder for future implementation of VMS property in the default hal. For// now, just returns OK; otherwise, hal clients crash with property not supported.return StatusCode::OK;}}if (propValue.status != VehiclePropertyStatus::AVAILABLE) {// Android side cannot set property status - this value is the// purview of the HAL implementation to reflect the state of// its underlying hardwarereturn StatusCode::INVALID_ARG;}//读取当前值auto currentPropValue = mPropStore->readValueOrNull(propValue);if (currentPropValue == nullptr) {return StatusCode::INVALID_ARG;}if (currentPropValue->status != VehiclePropertyStatus::AVAILABLE) {// do not allow Android side to set() a disabled/error propertyreturn StatusCode::NOT_AVAILABLE;}/*** After checking all conditions, such as the property is available, a real vhal will* sent the events to Car ECU to take actions.*/// Send the value to the vehicle server, the server will talk to the (real or emulated) car//设置属性到VehicleClient,通过这个设置到模拟车辆或者实际车辆auto setValueStatus = mVehicleClient->setProperty(propValue, updateStatus);if (setValueStatus != StatusCode::OK) {return setValueStatus;}return StatusCode::OK;
}

其中mVehicleClient是初始化时传入的EmulatedVehicleConnector对象,所以调用的是EmulatedVehicleConnector的setProperty函数。

StatusCode setProperty(const VehiclePropValue& value, bool updateStatus) override {return this->onSetProperty(value, updateStatus);
}

setProperty函数在EmulatedVehicleConnector的父类IPassThroughConnector中,然后调用onSetProperty函数,这个函数在EmulatedVehicleConnector类中。

StatusCode EmulatedVehicleConnector::onSetProperty(const VehiclePropValue& value,bool updateStatus) {if (mEmulatedUserHal.isSupported(value.prop)) {LOG(INFO) << "onSetProperty(): property " << value.prop << " will be handled by UserHal";const auto& ret = mEmulatedUserHal.onSetProperty(value);if (!ret.ok()) {LOG(ERROR) << "onSetProperty(): HAL returned error: " << ret.error().message();return StatusCode(ret.error().code());}auto updatedValue = ret.value().get();if (updatedValue != nullptr) {LOG(INFO) << "onSetProperty(): updating property returned by HAL: "<< toString(*updatedValue);onPropertyValueFromCar(*updatedValue, updateStatus);}return StatusCode::OK;}return this->VehicleHalServer::onSetProperty(value, updateStatus);
}

首先处理UserHal相关的属性操作。然后调用VehicleHalServer中的onSetProperty函数。

StatusCode VehicleHalServer::onSetProperty(const VehiclePropValue& value, bool updateStatus) {LOG(DEBUG) << "onSetProperty(" << value.prop << ")";// Some properties need to be treated non-triviallyswitch (value.prop) {case kGenerateFakeDataControllingProperty:return handleGenerateFakeDataRequest(value);// set the value from vehicle side, used in end to end test.case kSetIntPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::INT32, 1);updatedPropValue->prop = value.value.int32Values[0];updatedPropValue->value.int32Values[0] = value.value.int32Values[1];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case kSetFloatPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::FLOAT, 1);updatedPropValue->prop = value.value.int32Values[0];updatedPropValue->value.floatValues[0] = value.value.floatValues[0];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case kSetBooleanPropertyFromVehicleForTest: {auto updatedPropValue = createVehiclePropValue(VehiclePropertyType::BOOLEAN, 1);updatedPropValue->prop = value.value.int32Values[1];updatedPropValue->value.int32Values[0] = value.value.int32Values[0];updatedPropValue->timestamp = value.value.int64Values[0];updatedPropValue->areaId = value.areaId;onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;}case AP_POWER_STATE_REPORT:switch (value.value.int32Values[0]) {case toInt(VehicleApPowerStateReport::DEEP_SLEEP_EXIT):case toInt(VehicleApPowerStateReport::SHUTDOWN_CANCELLED):case toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL):// CPMS is in WAIT_FOR_VHAL state, simply move to ON// Send back to HAL// ALWAYS update status for generated property valueonPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::ON, 0),true /* updateStatus */);break;case toInt(VehicleApPowerStateReport::DEEP_SLEEP_ENTRY):case toInt(VehicleApPowerStateReport::SHUTDOWN_START):// CPMS is in WAIT_FOR_FINISH state, send the FINISHED command// Send back to HAL// ALWAYS update status for generated property valueonPropertyValueFromCar(*createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0),true /* updateStatus */);break;case toInt(VehicleApPowerStateReport::ON):case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE):case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE):// Do nothingbreak;default:// Unknown statebreak;}break;default:break;}// In the real vhal, the value will be sent to Car ECU.// We just pretend it is done here and send back to HALauto updatedPropValue = getValuePool()->obtain(value);updatedPropValue->timestamp = elapsedRealtimeNano();onPropertyValueFromCar(*updatedPropValue, updateStatus);return StatusCode::OK;
}

上面是模拟属性设置流程,这里就相当于模拟属性设置完成了。更新属性的值和时间戳之后,调用onPropertyValueFromCar模拟属性设置成功的上报操作

3.2 设置属性流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant IPassThroughConnector
participant EmulatedVehicleConnector
participant VehicleHalServer
endboxCarService -> VehicleHalManager: set(const VehiclePropValue& value)
VehicleHalManager -> VehicleHalManager: handlePropertySetEvent(const VehiclePropValue& value)
VehicleHalManager -> SubscriptionManager: getSubscribedClients(int32_t propId, SubscribeFlags flags)
SubscriptionManager -> VehicleHalManager: value
alt flags == SubscribeFlags::EVENTS_FROM_ANDROIDVehicleHalManager -> CarService: onPropertySet(value)
end
VehicleHalManager -> EmulatedVehicleHal: set(const VehiclePropValue& value)
EmulatedVehicleHal -> IPassThroughConnector: setProperty(const VehiclePropValue& \n\tvalue, bool updateStatus)
IPassThroughConnector -> EmulatedVehicleConnector: onSetProperty(const VehiclePropValue& \n\tvalue, bool updateStatus)
EmulatedVehicleConnector -> VehicleHalServer: onSetProperty(const VehiclePropValue& value, bool updateStatus)@enduml

流程图:
在这里插入图片描述
这里由于没有实际车辆,没有往下设置,OEM厂商需要设置到ECU中。至于设置之后,VHAL缓存的改变则是由设置成功的通知上报之后才会写入缓存的。

4 订阅属性流程

4.1 普通订阅属性流程源码分析

订阅入口也是在VehicleHalManager中

Return<StatusCode> VehicleHalManager::subscribe(const sp<IVehicleCallback> &callback,const hidl_vec<SubscribeOptions> &options) {hidl_vec<SubscribeOptions> verifiedOptions(options);for (size_t i = 0; i < verifiedOptions.size(); i++) {SubscribeOptions& ops = verifiedOptions[i];auto prop = ops.propId;const auto* config = getPropConfigOrNull(prop);if (config == nullptr) {ALOGE("Failed to subscribe: config not found, property: 0x%x",prop);return StatusCode::INVALID_ARG;}if (ops.flags == SubscribeFlags::UNDEFINED) {ALOGE("Failed to subscribe: undefined flag in options provided");return StatusCode::INVALID_ARG;}if (!isSubscribable(*config, ops.flags)) {ALOGE("Failed to subscribe: property 0x%x is not subscribable",prop);return StatusCode::INVALID_ARG;}ops.sampleRate = checkSampleRate(*config, ops.sampleRate);}std::list<SubscribeOptions> updatedOptions;auto res = mSubscriptionManager.addOrUpdateSubscription(getClientId(callback),callback, verifiedOptions,&updatedOptions);if (StatusCode::OK != res) {ALOGW("%s failed to subscribe, error code: %d", __func__, res);return res;}for (auto opt : updatedOptions) {mHal->subscribe(opt.propId, opt.sampleRate);}return StatusCode::OK;
}

首先判断属性配置是否存在,存在才支持订阅
然后判断是否可以订阅

bool VehicleHalManager::isSubscribable(const VehiclePropConfig& config,SubscribeFlags flags) {bool isReadable = config.access & VehiclePropertyAccess::READ;if (!isReadable && (SubscribeFlags::EVENTS_FROM_CAR & flags)) {ALOGW("Cannot subscribe, property 0x%x is not readable", config.prop);return false;}if (config.changeMode == VehiclePropertyChangeMode::STATIC) {ALOGW("Cannot subscribe, property 0x%x is static", config.prop);return false;}return true;
}

判断访问权限,判断flag,如果flag是从car来的,则返回false。然后是判断属性的changeMode,如果changeMode是STATIC,表示属性不变,则不支持订阅。
然后添加订阅addOrUpdateSubscription,添加之前首先创建clientId

using ClientId = uint64_t;
ClientId VehicleHalManager::getClientId(const sp<IVehicleCallback>& callback) {//TODO(b/32172906): rework this to get some kind of unique id for callback interface when this// feature is ready in HIDL.if (callback->isRemote()) {BpHwVehicleCallback* hwCallback = static_cast<BpHwVehicleCallback*>(callback.get());return static_cast<ClientId>(reinterpret_cast<intptr_t>(hwCallback->onAsBinder()));} else {return static_cast<ClientId>(reinterpret_cast<intptr_t>(callback.get()));}
}

根据传入的回调函数的额指针来强转成ClientId这种int类型,作为客户端的唯一标识。

StatusCode SubscriptionManager::addOrUpdateSubscription(ClientId clientId,const sp<IVehicleCallback> &callback,const hidl_vec<SubscribeOptions> &optionList,std::list<SubscribeOptions>* outUpdatedSubscriptions) {outUpdatedSubscriptions->clear();MuxGuard g(mLock);ALOGI("SubscriptionManager::addOrUpdateSubscription, callback: %p", callback.get());const sp<HalClient>& client = getOrCreateHalClientLocked(clientId, callback);if (client.get() == nullptr) {return StatusCode::INTERNAL_ERROR;}for (size_t i = 0; i < optionList.size(); i++) {const SubscribeOptions& opts = optionList[i];ALOGI("SubscriptionManager::addOrUpdateSubscription, prop: 0x%x", opts.propId);client->addOrUpdateSubscription(opts);addClientToPropMapLocked(opts.propId, client);if (SubscribeFlags::EVENTS_FROM_CAR & opts.flags) {SubscribeOptions updated;if (updateHalEventSubscriptionLocked(opts, &updated)) {outUpdatedSubscriptions->push_back(updated);}}}return StatusCode::OK;
}

首先,创建客户端对象HalClient

std::map<ClientId, sp<HalClient>> mClients;sp<HalClient> SubscriptionManager::getOrCreateHalClientLocked(ClientId clientId, const sp<IVehicleCallback>& callback) {auto it = mClients.find(clientId);if (it == mClients.end()) {uint64_t cookie = reinterpret_cast<uint64_t>(clientId);ALOGI("Creating new client and linking to death recipient, cookie: 0x%" PRIx64, cookie);auto res = callback->linkToDeath(mCallbackDeathRecipient, cookie);if (!res.isOk()) {  // Client is already dead?ALOGW("%s failed to link to death, client %p, err: %s",__func__, callback.get(), res.description().c_str());return nullptr;}sp<HalClient> client = new HalClient(callback);mClients.insert({clientId, client});return client;} else {return it->second;}
}

mClients是保存订阅客户端的map,key是ClientId,value是HalClient对象。这里会先判断mClients这个map中是否存在对应的客户端,如果没有,则创建HalClient对象,并加入到这个map之中,如果有则直接返回。
然后for循环遍历所有的订阅项,由于订阅的时候,CarService传入的是SubscribeOptions的列表:

struct SubscribeOptions {/** Property to subscribe */int32_t propId;/*** Sample rate in Hz.** Must be provided for properties with* VehiclePropertyChangeMode::CONTINUOUS. The value must be within* VehiclePropConfig#minSamplingRate .. VehiclePropConfig#maxSamplingRate* for a given property.* This value indicates how many updates per second client wants to receive.*/float sampleRate;/** Flags that indicate to which event sources to listen. */SubscribeFlags flags;
};

所以可以一次性订阅多个属性。但是这多个属性是一次订阅,也是走一个回调函数上去的。这里会遍历所有的SubscribeOptions,然后调用addOrUpdateSubscription函数。

std::map<int32_t, SubscribeOptions> mSubscriptions;void HalClient::addOrUpdateSubscription(const SubscribeOptions &opts)  {ALOGI("%s opts.propId: 0x%x", __func__, opts.propId);auto it = mSubscriptions.find(opts.propId);if (it == mSubscriptions.end()) {mSubscriptions.emplace(opts.propId, opts);} else {const SubscribeOptions& oldOpts = it->second;SubscribeOptions updatedOptions;if (mergeSubscribeOptions(oldOpts, opts, &updatedOptions)) {mSubscriptions.erase(it);mSubscriptions.emplace(opts.propId, updatedOptions);}}
}
bool mergeSubscribeOptions(const SubscribeOptions &oldOpts,const SubscribeOptions &newOpts,SubscribeOptions *outResult) {float updatedRate = std::max(oldOpts.sampleRate, newOpts.sampleRate);SubscribeFlags updatedFlags = SubscribeFlags(oldOpts.flags | newOpts.flags);bool updated = (updatedRate > oldOpts.sampleRate) || (updatedFlags != oldOpts.flags);if (updated) {*outResult = oldOpts;outResult->sampleRate = updatedRate;outResult->flags = updatedFlags;}return updated;
}

首先判断这个SubscribeOptions中包含的propId是否已经存在于mSubscriptions这个map中,如果存在,则更新flag和采样率和相应的SubscribeOptions对象,如果不存在,则添加。
这个map的key是propId,value是SubscribeOptions。

std::map<int32_t, sp<HalClientVector>> mPropToClients;void SubscriptionManager::addClientToPropMapLocked(int32_t propId, const sp<HalClient> &client) {auto it = mPropToClients.find(propId);sp<HalClientVector> propClients;if (it == mPropToClients.end()) {propClients = new HalClientVector();mPropToClients.insert(std::make_pair(propId, propClients));} else {propClients = it->second;}propClients->addOrUpdate(client);
}

mPropToClients是SubscriptionManager类中的一个map,key是propId,value是HalClientVector对象。HalClientVectot中存储的是对于同一个propId订阅的不同HalClient对象。
这里,先判断mPropToClients中是否有对应的propId的HalClientVector,如果没有,则表示这个propId没有客户端订阅过。如果有则将当前的client对象添加到HalClientVector中。
总结一下属性订阅做的事就是:

  1. 根据传入的callback指针,转换成ClientId,作为客户端标识
  2. 创建HalClient对象,作为客户端实例,并保存客户端的callback回调
  3. 缓存所有订阅属性到HalClient对象的mSubscriptions中
  4. 缓存HalClient到mClients这个map中
  5. 缓存HalClient到mPropToClients这个map中

4.2 连续类型属性订阅流程源码分析

连续类型属性,订阅之后需要VHAL周期性上报给CarService
连续属性订阅,其他和上面的普通订阅相同,最后会走到EmulatedVehicleHal中进行处理

StatusCode EmulatedVehicleHal::subscribe(int32_t property, float sampleRate) {ALOGI("%s propId: 0x%x, sampleRate: %f", __func__, property, sampleRate);if (isContinuousProperty(property)) {mRecurrentTimer.registerRecurrentEvent(hertzToNanoseconds(sampleRate), property);}return StatusCode::OK;
}bool EmulatedVehicleHal::isContinuousProperty(int32_t propId) const {const VehiclePropConfig* config = mPropStore->getConfigOrNull(propId);if (config == nullptr) {ALOGW("Config not found for property: 0x%x", propId);return false;}return config->changeMode == VehiclePropertyChangeMode::CONTINUOUS;
}

主要就是通过属性配置的changeMode是否是CONTINUOUS来判断,判断如果还连续属性,调用registerRecurrentEvent

std::unordered_map<int32_t, RecurrentEvent> mCookieToEventsMap;void registerRecurrentEvent(std::chrono::nanoseconds interval, int32_t cookie) {TimePoint now = Clock::now();// Align event time point among all intervals. Thus if we have two intervals 1ms and 2ms,// during every second wake-up both intervals will be triggered.TimePoint absoluteTime = now - Nanos(now.time_since_epoch().count() % interval.count());{std::lock_guard<std::mutex> g(mLock);mCookieToEventsMap[cookie] = { interval, cookie, absoluteTime };}mCond.notify_one();
}

用propId作为key,将定时事件的定时周期,propId,当前转换后时间封装成RecurrentEvent对象,存储到mCookieToEventsMap这个map中,然后通过mCond唤醒线程,至此连续订阅完成,等待特定时间上报。

4.3 订阅流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant HalClientVector
participant EmulatedVehicleHal
participant RecurrentTimer
endboxCarService -> VehicleHalManager: subscribe(const sp<IVehicleCallback> &callback, \n\tconst hidl_vec<SubscribeOptions> &options)
VehicleHalManager -> SubscriptionManager: addOrUpdateSubscription(\n\tClientId clientId, \n\tconst sp<IVehicleCallback> &callback, \n\tconst hidl_vec<SubscribeOptions> &optionList, \n\tstd::list<SubscribeOptions>* outUpdatedSubscriptions)
SubscriptionManager -> SubscriptionManager: getClientId(const sp<IVehicleCallback>& callback)
SubscriptionManager -> SubscriptionManager: getOrCreateHalClientLocked(ClientId clientId, \n\tconst sp<IVehicleCallback>& callback)
alt not in mClientsSubscriptionManager -> HalClient: new HalClient(const sp<IVehicleCallback> &callback)HalClient -> SubscriptionManager: clientSubscriptionManager -> SubscriptionManager: mClients.insert({clientId, client})
end
loop i in optionList.sizeSubscriptionManager -> HalClient: addOrUpdateSubscription(const SubscribeOptions &opts)alt not in mSubscriptionsHalClient -> HalClient: emplace(opts.propId, opts)else in mSubscriptionsHalClient -> HalClient: mergeSubscribeOptions(oldOpts, opts, &updatedOptions)HalClient -> HalClient: erase(it)HalClient -> HalClient: emplace(opts.propId, opts)endSubscriptionManager -> SubscriptionManager: addClientToPropMapLocked(opts.propId, client)alt not in mPropToClientsSubscriptionManager -> HalClientVector: new HalClientVector()HalClientVector -> SubscriptionManager: propClientsSubscriptionManager -> SubscriptionManager: mPropToClients.insert(std::make_pair(propId, propClients));endSubscriptionManager -> SubscriptionManager: propClients->addOrUpdate(client)
end
loop opt in updatedOptionsSubscriptionManager -> EmulatedVehicleHal: subscribe(opt.propId, opt.sampleRate)alt isContinuousPropertyEmulatedVehicleHal -> RecurrentTimer: registerRecurrentEvent(hertzToNanoseconds(sampleRate), property)RecurrentTimer -> RecurrentTimer: mCookieToEventsMap[cookie] = \n\t{ interval, cookie, absoluteTime }end
end@enduml

流程图如下:
在这里插入图片描述

5 取消订阅流程

5.1 取消订阅流程源码分析

取消订阅入口也是在VehicleHalManager中

Return<StatusCode> VehicleHalManager::unsubscribe(const sp<IVehicleCallback>& callback,int32_t propId) {mSubscriptionManager.unsubscribe(getClientId(callback), propId);return StatusCode::OK;
}

根据callback获取ClientId,并转到SubscriptionManager中处理

void SubscriptionManager::unsubscribe(ClientId clientId,int32_t propId) {MuxGuard g(mLock);auto propertyClients = getClientsForPropertyLocked(propId);auto clientIter = mClients.find(clientId);if (clientIter == mClients.end()) {ALOGW("Unable to unsubscribe: no callback found, propId: 0x%x", propId);} else {auto client = clientIter->second;if (propertyClients != nullptr) {propertyClients->remove(client);if (propertyClients->isEmpty()) {mPropToClients.erase(propId);}}bool isClientSubscribedToOtherProps = false;for (const auto& propClient : mPropToClients) {if (propClient.second->indexOf(client) >= 0) {isClientSubscribedToOtherProps = true;break;}}if (!isClientSubscribedToOtherProps) {auto res = client->getCallback()->unlinkToDeath(mCallbackDeathRecipient);if (!res.isOk()) {ALOGW("%s failed to unlink to death, client: %p, err: %s",__func__, client->getCallback().get(), res.description().c_str());}mClients.erase(clientIter);}}if (propertyClients == nullptr || propertyClients->isEmpty()) {mHalEventSubscribeOptions.erase(propId);mOnPropertyUnsubscribed(propId);}
}
sp<HalClientVector> SubscriptionManager::getClientsForPropertyLocked(int32_t propId) const {auto it = mPropToClients.find(propId);return it == mPropToClients.end() ? nullptr : it->second;
}

首先,调用getClientsForPropertyLocked函数,根据propId获取HalClientVector对象。然后从mClients中根据ClientId找到对应的HalClient对象,如果该HalClient存在,则从HalClientVector中移除,如果HalClientVector中是最后一个HalClient,则从mPropToClients中移除这个propId的HalClientVector对象。
如果这个client还订阅了其他propId,则不把这个client从mClients中移除,如果没有订阅其他的propId,则从mClients中移除。
同时,如果这个HalClientVector为空了,则移除mHalEventSubscribeOptions中的对应propId的订阅属性,并调用mOnPropertyUnsubscribed这个回调函数。

void VehicleHalManager::onAllClientsUnsubscribed(int32_t propertyId) {mHal->unsubscribe(propertyId);
}StatusCode EmulatedVehicleHal::unsubscribe(int32_t property) {ALOGI("%s propId: 0x%x", __func__, property);if (isContinuousProperty(property)) {mRecurrentTimer.unregisterRecurrentEvent(property);}return StatusCode::OK;
}void unregisterRecurrentEvent(int32_t cookie) {{std::lock_guard<std::mutex> g(mLock);mCookieToEventsMap.erase(cookie);}mCond.notify_one();}

这个地方就是对连续属性的取消订阅处理,从mCookieToEventsMap中移除。

5.2 取消订阅流程图

plantuml

@startumlparticipant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant RecurrentTimer
endboxCarService -> VehicleHalManager: unsubscribe(const sp<IVehicleCallback>& callback,\n\t int32_t propId)
VehicleHalManager -> SubscriptionManager: unsubscribe(getClientId(callback), propId)
SubscriptionManager -> SubscriptionManager: propertyClients = getClientsForPropertyLocked(propId)
alt client in mClientsSubscriptionManager -> SubscriptionManager: propertyClients->remove(client)alt propertyClients->isEmpty()SubscriptionManager -> SubscriptionManager: mPropToClients.erase(propId)endloop propClient in mPropToClientsalt client in propClientSubscriptionManager -> SubscriptionManager: isClientSubscribedToOtherProps = trueendendalt isClientSubscribedToOtherProps == falseSubscriptionManager -> SubscriptionManager: client->getCallback()->\n\tunlinkToDeath(mCallbackDeathRecipient)end
end
alt propertyClients == nullptr || propertyClients->isEmpty()SubscriptionManager -> VehicleHalManager: onAllClientsUnsubscribed(propertyId)VehicleHalManager -> EmulatedVehicleHal: unsubscribe(property)alt isContinuousPropertyEmulatedVehicleHal -> RecurrentTimer: unregisterRecurrentEvent(property)RecurrentTimer -> RecurrentTimer: mCookieToEventsMap.erase(cookie)end
end@enduml

流程图:
在这里插入图片描述

6 属性上报流程

6.1 属性上报流程源码分析

void onPropertyValueFromCar(const VehiclePropValue& value, bool updateStatus) override {return this->onPropertyValue(value, updateStatus);
}

由于EmulatedVehicleConnector同时继承VehicleHalServer和VehicleHalClient,所以这个onPropertyValue继承于VehicleHalClient,调用的是VehicleHalClient中的onPropertyValue函数。

void VehicleHalClient::onPropertyValue(const VehiclePropValue& value, bool updateStatus) {if (!mPropCallback) {LOG(ERROR) << __func__ << ": PropertyCallBackType is not registered!";return;}return mPropCallback(value, updateStatus);
}

调用mPropCallback这个注册的回调函数,这个回调函数是:

void EmulatedVehicleHal::onPropertyValue(const VehiclePropValue& value, bool updateStatus) {VehiclePropValuePtr updatedPropValue = getValuePool()->obtain(value);if (mPropStore->writeValue(*updatedPropValue, updateStatus)) {getEmulatorOrDie()->doSetValueFromClient(*updatedPropValue);doHalEvent(std::move(updatedPropValue));}
}

首先,将上报的属性写入缓存中。然后调用doSetValueFromClient

void VehicleEmulator::doSetValueFromClient(const VehiclePropValue& propValue) {vhal_proto::EmulatorMessage msg;vhal_proto::VehiclePropValue* val = msg.add_value();populateProtoVehiclePropValue(val, &propValue);msg.set_status(vhal_proto::RESULT_OK);msg.set_msg_type(vhal_proto::SET_PROPERTY_ASYNC);mSocketComm->sendMessage(msg);if (mPipeComm) {mPipeComm->sendMessage(msg);}
}

这里会通过SocketComm和PipeComm通知其他客户端。
然后会调用doHalEvent上报给CarService。

void doHalEvent(VehiclePropValuePtr v) {mOnHalEvent(std::move(v));
}

mOnHalEvent是VehicleHalManager::onHalEvent函数

void VehicleHalManager::onHalEvent(VehiclePropValuePtr v) {mEventQueue.push(std::move(v));
}

将VehiclePropValuePtr传入mEventQueue,这个是初始化的时候与BatchingConsumer中的mQueue绑定的一个queue,用于处理上报事件。同时BatchingConsumer这个类初始化的时候也会创建一个线程来处理上报事件,运行的函数为:

void runInternal(const OnBatchReceivedFunc& onBatchReceived) {if (mState.exchange(State::RUNNING) == State::INIT) {while (State::RUNNING == mState) {mQueue->waitForItems();if (State::STOP_REQUESTED == mState) break;std::this_thread::sleep_for(mBatchInterval);if (State::STOP_REQUESTED == mState) break;std::vector<T> items = mQueue->flush();if (items.size() > 0) {onBatchReceived(items);}}}mState = State::STOPPED;
}void waitForItems() {std::unique_lock<std::mutex> g(mLock);while (mQueue.empty() && mIsActive) {mCond.wait(g);}
}std::vector<T> flush() {std::vector<T> items;MuxGuard g(mLock);if (mQueue.empty() || !mIsActive) {return items;}while (!mQueue.empty()) {items.push_back(std::move(mQueue.front()));mQueue.pop();}return items;
}void push(T&& item) {{MuxGuard g(mLock);if (!mIsActive) {return;}mQueue.push(std::move(item));}mCond.notify_one();
}

当没有上报事件时,会waitForItems阻塞线程。如果有事件,则会在循环中处理,等待一个时间间隔mBatchInterval=10,如果在这个事件内没有上报事件,会等10s,如果有,则会在push的时候立马唤醒线程执行。
然后将queue中的所有事件都取出,调用回调函数处理,回调函数是:

void VehicleHalManager::onBatchHalEvent(const std::vector<VehiclePropValuePtr>& values) {const auto& clientValues =mSubscriptionManager.distributeValuesToClients(values, SubscribeFlags::EVENTS_FROM_CAR);for (const HalClientValues& cv : clientValues) {auto vecSize = cv.values.size();hidl_vec<VehiclePropValue> vec;if (vecSize < kMaxHidlVecOfVehiclPropValuePoolSize) {vec.setToExternal(&mHidlVecOfVehiclePropValuePool[0], vecSize);} else {vec.resize(vecSize);}int i = 0;for (VehiclePropValue* pValue : cv.values) {shallowCopy(&(vec)[i++], *pValue);}auto status = cv.client->getCallback()->onPropertyEvent(vec);if (!status.isOk()) {ALOGE("Failed to notify client %s, err: %s",toString(cv.client->getCallback()).c_str(),status.description().c_str());}}
}

先看下封装

std::list<HalClientValues> SubscriptionManager::distributeValuesToClients(const std::vector<recyclable_ptr<VehiclePropValue>>& propValues,SubscribeFlags flags) const {//创建一个map,key是HalClient,value是VehiclePropValuestd::map<sp<HalClient>, std::list<VehiclePropValue*>> clientValuesMap;{MuxGuard g(mLock);//遍历所有的propValuefor (const auto& propValue: propValues) {VehiclePropValue* v = propValue.get();//获取所有订阅了该属性的客户端HalClient对象auto clients = getSubscribedClientsLocked(v->prop, flags);//遍历所有HalClient,并将其和propValue一起封装加入clientValuesMap中for (const auto& client : clients) {clientValuesMap[client].push_back(v);}}}//遍历map中所有的对象,并封装成HalClientValues对象,存入clientValues中std::list<HalClientValues> clientValues;for (const auto& entry : clientValuesMap) {clientValues.push_back(HalClientValues {.client = entry.first,.values = entry.second});}return clientValues;
}std::list<sp<HalClient>> SubscriptionManager::getSubscribedClientsLocked(int32_t propId, SubscribeFlags flags) const {std::list<sp<HalClient>> subscribedClients;//通过propId获取该id对应的HalClientVector对象sp<HalClientVector> propClients = getClientsForPropertyLocked(propId);if (propClients.get() != nullptr) {//遍历HalClientVector中HalClient,并返回for (size_t i = 0; i < propClients->size(); i++) {const auto& client = propClients->itemAt(i);if (client->isSubscribed(propId, flags)) {subscribedClients.push_back(client);}}}return subscribedClients;
}sp<HalClientVector> SubscriptionManager::getClientsForPropertyLocked(int32_t propId) const {auto it = mPropToClients.find(propId);return it == mPropToClients.end() ? nullptr : it->second;
}

对客户端和属性值进行封装,然后遍历封装后的数组,逐个调用其回调函数onPropertyEvent,这个就是客户端传入的回调函数。

6.2 连续属性上报流程源码分析

连续属性订阅之后,会根据传入的采样率转换后的频率进行上报,订阅时,将订阅事件保存在mCookieToEventsMap这个map中,并唤醒了处理线程。
这个线程是什么时候初始化,在执行什么呢?这个可以参考https://editor.csdn.net/md/?articleId=140752076

void loop(const Action& action) {static constexpr auto kInvalidTime = TimePoint(Nanos::max());std::vector<int32_t> cookies;while (!mStopRequested) {auto now = Clock::now();auto nextEventTime = kInvalidTime;cookies.clear();{std::unique_lock<std::mutex> g(mLock);for (auto&& it : mCookieToEventsMap) {RecurrentEvent& event = it.second;if (event.absoluteTime <= now) {event.updateNextEventTime(now);cookies.push_back(event.cookie);}if (nextEventTime > event.absoluteTime) {nextEventTime = event.absoluteTime;}}}if (cookies.size() != 0) {action(cookies);}std::unique_lock<std::mutex> g(mLock);mCond.wait_until(g, nextEventTime);  // nextEventTime can be nanoseconds::max()}
}

唤醒这个线程,并在这个里面从mCookieToEventsMap中取出RecurrentEvent,判断时间戳,如果已经到时了,则执行里面的action回调函数。

void EmulatedVehicleHal::onContinuousPropertyTimer(const std::vector<int32_t>& properties) {VehiclePropValuePtr v;auto& pool = *getValuePool();for (int32_t property : properties) {if (isContinuousProperty(property)) {auto internalPropValue = mPropStore->readValueOrNull(property);if (internalPropValue != nullptr) {v = pool.obtain(*internalPropValue);}} else {ALOGE("Unexpected onContinuousPropertyTimer for property: 0x%x", property);}if (v.get()) {v->timestamp = elapsedRealtimeNano();doHalEvent(std::move(v));}}
}

如果是连续属性,则从VehiclePropertyStore读取缓存,并调用doHalEvent往上报,这个和6.1节中的上报流程相同。

6.3 属性上报流程图

plantuml

@startumlparticipant CarService
box
participant EmulatedVehicleHal
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant IPassThroughConnector
participant VehicleHalClient
participant VehiclePropertyStore
participant VehicleEmulator
participant SocketComm
participant ConcurrentQueue
participant BatchingConsumer
endboxEmulatedVehicleHal -> IPassThroughConnector: onPropertyValueFromCar(*updatedPropValue, updateStatus)
IPassThroughConnector -> VehicleHalClient: onPropertyValue(value, updateStatus)
VehicleHalClient -> EmulatedVehicleHal: onPropertyValue(value, updateStatus)
EmulatedVehicleHal -> VehiclePropertyStore: writeValue(*updatedPropValue, updateStatus)
alt writeValue successEmulatedVehicleHal -> VehicleEmulator: doSetValueFromClient(*updatedPropValue)VehicleEmulator -> SocketComm: sendMessage(msg)EmulatedVehicleHal -> EmulatedVehicleHal: doHalEvent(std::move(updatedPropValue))EmulatedVehicleHal -> VehicleHalManager::onHalEvent(updatedPropValue)VehicleHalManager -> VehicleHalManager: mEventQueue.push(std::move(updatedPropValue))VehicleHalManager -> ConcurrentQueue: mQueue.push(updatedPropValue)ConcurrentQueue -> ConcurrentQueue: mCond.notify_one()ConcurrentQueue -> BatchingConsumer: runInternal(const OnBatchReceivedFunc& onBatchReceived)BatchingConsumer -> VehicleHalManager: onBatchHalEvent(const std::vector<VehiclePropValuePtr>& values)VehicleHalManager -> SubscriptionManager: distributeValuesToClients(values, \n\tSubscribeFlags::EVENTS_FROM_CAR)SubscriptionManager -> VehicleHalManager: clientValuesloop cv in clientValuesVehicleHalManager -> HalClient: getCallback()HalClient -> VehicleHalManager: callbackVehicleHalManager -> CarService: callback->onPropertyEvent(values)end
end@enduml

流程图
在这里插入图片描述
连续属性上报流程图省略,大致和这个上面差不多,只是触发在定时器中。

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

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

相关文章

WOFOST模型与PCSE模型

农作物生长模型概述 1、介绍农作物生长模型的用途和应用领域 2、比较WOFOST模型和PCSE模型的特点和优势 数据准备 1、气象数据&#xff1a; 数据类型&#xff1a;温度、降水、湿度、风速等气象要素数据。 数据格式&#xff1a;时间序列数据&#xff0c;通常以日为单位。 …

使用 Vertex AI Gemini 模型和 Elasticsearch Playground 快速创建 RAG 应用程序

作者&#xff1a;来自 Elastic Jeff Vestal 在这篇博客中&#xff0c;我们将使用 Elastic 的 Playground 和 Vertex AI API 将 Elasticsearch 连接到 Google 的 Gemini 1.5 聊天模型。将 Gemini 模型添加到 Playground 使 Google Cloud 开发人员能够快速建立 LLM、测试检索、调…

宠物空气净化器怎么选?希喂、霍尼韦尔、美的宠物哪款除毛好?

身为养宠五年的资深铲屎官&#xff0c;最近收到了很多新手养宠朋友关于宠物空气净化器的挑选疑问。宠物空气净化器作为宠物领域目前最火热的产品&#xff0c;谈论度一直很高&#xff0c;评价也褒贬不一。双十一购物节又即将到来&#xff0c;大家都想赶上这一波优惠活动。 铺天盖…

低代码工单管理app评测,功能与效率解析

预计到2030年&#xff0c;低代码平台市场将达1870亿美元。ZohoCreator助力企业构建定制化软件应用&#xff0c;以建筑行业工作订单管理app为例&#xff0c;简化流程&#xff0c;提升管理效率&#xff0c;降低成本。其用户友好界面、自动化管理、跨平台使用及全面报告功能受企业…

基于差分进化灰狼混合优化的SVM(DE-GWO-SVM)数据预测算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1 DE优化 4.2 GWO优化 5.完整程序 1.程序功能描述 基于差分进化灰狼混合优化的SVM(DE-GWO-SVM)数据预测算法matlab仿真&#xff0c;对比SVM和GWO-SVM。 2.测试软件版本以及运行结果展示…

实施威胁暴露管理、降低网络风险暴露的最佳实践

随着传统漏洞管理的发展&#xff0c;TEM 解决了因攻击面扩大和安全工具分散而产生的巨大风险。 主动式 TEM 方法优先考虑风险并与现有安全工具无缝集成&#xff0c;使组织能够在威胁被有效利用之前缓解威胁。 为什么威胁暴露管理 (TEM) 在现代网络安全策略中变得至关重要&…

获取时隔半个钟的三天

摘要&#xff1a; 今天遇到需求是配送时间&#xff0c;时隔半个钟的排线&#xff01;所以需要拼接时间&#xff01;例如2024-10-08 14&#xff1a;30&#xff0c;2024-10-08 15&#xff1a;00&#xff0c;2024-10-08 15&#xff1a;30 <el-form-item label"配送时间&a…

如何使用bpmn-js实现可视化流程管理

介绍 BPMN-JS是一个流行的开源库&#xff0c;用于在Web应用程序中可视化、创建、编辑和分析BPMN&#xff08;Business Process Model and Notation&#xff0c;业务流程建模与表示法&#xff09;2.0 图。BPMN是一种国际标准的图形化语言&#xff0c;用于描述企业中的业务流程&a…

BlackMarket_ 1靶机渗透

项目地址 plain https://download.vulnhub.com/blackmarket/BlackMarket.zip 实验过程 开启靶机虚拟机 ![](https://img-blog.csdnimg.cn/img_convert/169d964d61ea9660c1104e723f71449e.png) 使用nmap进行主机发现&#xff0c;获取靶机IP地址 plain nmap 192.168.47.1-254…

图论day57|建造最大岛屿(卡码网)【截至目前,图论的最高难度】

图论day57|建造最大岛屿&#xff08;卡码网&#xff09;【截至目前所做的题中&#xff0c;图论的最高难度】 思维导图分析 104.建造最大岛屿&#xff08;卡码网&#xff09;【截至目前所做的题中&#xff0c;图论的最高难度】 思维导图分析 104.建造最大岛屿&#xff08;卡码网…

带你解锁Open_FLUX.1模型的神奇世界!

大家好我是极客菌&#xff01;&#xff01;&#xff01; Open_FLUX.1模型&#xff0c;作为ComfyUI的最新力作&#xff0c;已经在AI绘画领域引起了广泛的关注。这款模型以其独特的艺术风格和强大的创作能力&#xff0c;为艺术家们提供了一个全新的创作平台。今天&#xff0c;就…

OJ在线评测系统 微服务高级 Gateway网关接口路由和聚合文档 引入knife4j库集中查看管理并且调试网关项目

Gateway微服务网关接口路由 各个服务之间已经能相互调用了 为什么需要网关 因为我们的不同服务是放在不同的端口上面的 如果前端调用服务 需要不同的端口 8101 8102 8103 8104 我们最好提供一个唯一的 给前端去调用的路径 我们学习技术的时候必须要去思考 1.为什么要用&am…

百度文心智能体平台开发萌猫科研加油喵

百度文心智能体平台开发萌猫科研加油喵 在科研的道路上&#xff0c;研究生们常常面临着巨大的压力和挑战。为了给这个充满挑战的群体带来一些鼓励和温暖&#xff0c;我借助百度文心智能体平台开发了一个独特的智能体 《萌猫科研加油喵》。 一、百度文心智能体平台介绍 百度文…

k8s 的网络通信

目录 1 k8s通信整体架构 2 flannel 网络插件 2.1 flannel 插件组成 2.2 flannel 插件的通信过程 2.3 flannel 支持的后端模式 3 calico 网络插件 3.1 calico 简介 3.2 calico 网络架构 3.3 部署 calico 1 k8s通信整体架构 k8s通过CNI接口接入其他插件来实现网络通讯。目前比较…

DOM对象

DOM概述 官方定义&#xff1a; DOM是W3C制定的一个规范(标准)&#xff0c;(Document Object Model&#xff0c;文档对象模型)&#xff0c;是提供了访问和操作网页中各元素的方法&#xff0c;让程序可以动态的修改或改变网页元素的内容、样式、结构。 DOM是W3C制定的一个规范…

回南天,即使没有除湿机,也能除湿

前几天收到网友私信&#xff0c;询问烘干机干衣吹热风&#xff0c;这个比较好理解&#xff0c;热气吹到衣服上加速水分蒸发。但空调除湿为什么不吹热风了&#xff0c;而吹冷风呢&#xff1f;以及除湿机的原理等的问题。 因为我在南京总是觉得干&#xff0c;加湿器24小时不停&a…

The 2024 ICPC Kunming Invitational Contest K. Permutation(交互 期望)

在知乎内查看 题目 思路来源 题解 首先特判n1的情况&#xff0c;其实也不用问 分治&#xff0c;假设当前解决到[l,r]&#xff0c;要递归的vector是x&#xff0c; 维护两个vector L、R&#xff0c;代表下一步要在[l,mid]和[mid1,r]分治的vector 每次将x random_shuffle后&a…

易基因:J Hazard Mater/IF12.2:RRBS揭示农药诱导胰腺全基因组DNA甲基化跨代改变与代谢表型相关

大家好&#xff0c;这里是专注表观组学十余年&#xff0c;领跑多组学科研服务的易基因。 人造化学品的不当使用会对生物多样性和人类健康构成重大威胁&#xff0c;某些化学物质&#xff08;如农药&#xff09;可能通过表观遗传机制&#xff08;如DNA甲基化&#xff09;对代谢健…

如何批量找到企业联系电话

在拓展业务的过程中&#xff0c;找到企业的联系电话是必不可少的一步。然而&#xff0c;手动查找不仅耗时费力&#xff0c;效率也低下。对于需要批量获取客户信息的销售人员或企业来说&#xff0c;寻找一种快速、精准的方式批量获取企业联系电话显得尤为重要。本文将为你介绍几…

MPLS LDP协议

文章目录 LDP标签分发协议工作原理LDP应用倒数第二跳弹出 LDP标签分发协议 基于FEC自动分配标签构建LSP用于建立动态LSPLDP报文头部结构信息类型 工作原理 LDP工作过程 发送Hello消息用于发现邻居&#xff1b;UDP发送LSR_1主动发起并建立TCP连接&#xff1b;TCP进行建立主动方发…