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

@startuml

participant CarService
box
participant VehicleHalManager
participant EmulatedVehicleHal
participant VehiclePropertyStore
endbox

CarService -> 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 code
        switch (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 hardware
        return 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 property
        return 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-trivially
    switch (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 value
                    onPropertyValueFromCar(*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 value
                    onPropertyValueFromCar(
                            *createApPowerStateReq(VehicleApPowerStateReq::FINISHED, 0),
                            true /* updateStatus */);
                    break;
                case toInt(VehicleApPowerStateReport::ON):
                case toInt(VehicleApPowerStateReport::SHUTDOWN_POSTPONE):
                case toInt(VehicleApPowerStateReport::SHUTDOWN_PREPARE):
                    // Do nothing
                    break;
                default:
                    // Unknown state
                    break;
            }
            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 HAL
    auto updatedPropValue = getValuePool()->obtain(value);
    updatedPropValue->timestamp = elapsedRealtimeNano();

    onPropertyValueFromCar(*updatedPropValue, updateStatus);
    return StatusCode::OK;
}

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

3.2 设置属性流程图

plantuml

@startuml

participant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant IPassThroughConnector
participant EmulatedVehicleConnector
participant VehicleHalServer
endbox

CarService -> 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_ANDROID
    VehicleHalManager -> 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

@startuml

participant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant HalClientVector
participant EmulatedVehicleHal
participant RecurrentTimer
endbox

CarService -> 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 mClients
    SubscriptionManager -> HalClient: new HalClient(const sp<IVehicleCallback> &callback)
    HalClient -> SubscriptionManager: client
    SubscriptionManager -> SubscriptionManager: mClients.insert({clientId, client})
end
loop i in optionList.size
    SubscriptionManager -> HalClient: addOrUpdateSubscription(const SubscribeOptions &opts)
    alt not in mSubscriptions
        HalClient -> HalClient: emplace(opts.propId, opts)
    else in mSubscriptions
        HalClient -> HalClient: mergeSubscribeOptions(oldOpts, opts, &updatedOptions)
        HalClient -> HalClient: erase(it)
        HalClient -> HalClient: emplace(opts.propId, opts)
    end
    SubscriptionManager -> SubscriptionManager: addClientToPropMapLocked(opts.propId, client)
    alt not in mPropToClients
        SubscriptionManager -> HalClientVector: new HalClientVector()
        HalClientVector -> SubscriptionManager: propClients
        SubscriptionManager -> SubscriptionManager: mPropToClients.insert(std::make_pair(propId, propClients));
    end
    SubscriptionManager -> SubscriptionManager: propClients->addOrUpdate(client)
end
loop opt in updatedOptions
    SubscriptionManager -> EmulatedVehicleHal: subscribe(opt.propId, opt.sampleRate)
    alt isContinuousProperty
        EmulatedVehicleHal -> 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

@startuml

participant CarService
box
participant VehicleHalManager
participant SubscriptionManager
participant EmulatedVehicleHal
participant RecurrentTimer
endbox

CarService -> 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 mClients
    SubscriptionManager -> SubscriptionManager: propertyClients->remove(client)
    alt propertyClients->isEmpty()
        SubscriptionManager -> SubscriptionManager: mPropToClients.erase(propId)
    end
    loop propClient in mPropToClients
        alt client in propClient
            SubscriptionManager -> SubscriptionManager: isClientSubscribedToOtherProps = true
        end
    end
    alt isClientSubscribedToOtherProps == false
        SubscriptionManager -> SubscriptionManager: client->getCallback()->\n\tunlinkToDeath(mCallbackDeathRecipient)
    end
end
alt propertyClients == nullptr || propertyClients->isEmpty()
    SubscriptionManager -> VehicleHalManager: onAllClientsUnsubscribed(propertyId)
    VehicleHalManager -> EmulatedVehicleHal: unsubscribe(property)
    alt isContinuousProperty
        EmulatedVehicleHal -> 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是VehiclePropValue
    std::map<sp<HalClient>, std::list<VehiclePropValue*>> clientValuesMap;

    {
        MuxGuard g(mLock);
        //遍历所有的propValue
        for (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

@startuml

participant CarService
box
participant EmulatedVehicleHal
participant VehicleHalManager
participant SubscriptionManager
participant HalClient
participant IPassThroughConnector
participant VehicleHalClient
participant VehiclePropertyStore
participant VehicleEmulator
participant SocketComm
participant ConcurrentQueue
participant BatchingConsumer
endbox

EmulatedVehicleHal -> IPassThroughConnector: onPropertyValueFromCar(*updatedPropValue, updateStatus)
IPassThroughConnector -> VehicleHalClient: onPropertyValue(value, updateStatus)
VehicleHalClient -> EmulatedVehicleHal: onPropertyValue(value, updateStatus)
EmulatedVehicleHal -> VehiclePropertyStore: writeValue(*updatedPropValue, updateStatus)
alt writeValue success
    EmulatedVehicleHal -> 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: clientValues
    loop cv in clientValues
        VehicleHalManager -> HalClient: getCallback()
        HalClient -> VehicleHalManager: callback
        VehicleHalManager -> CarService: callback->onPropertyEvent(values)
    end
end

@enduml

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Qt】控件概述(2)—— 按钮类控件

控件概述&#xff08;2&#xff09; 1. PushButton2. RadioButton——单选按钮2.1 使用2.2 区分信号 clicked&#xff0c;clicked(bool)&#xff0c;pressed&#xff0c;released&#xff0c;toggled(bool)2.3 QButtonGroup分组 3. CheckBox——复选按钮 1. PushButton QPushB…

简单粗暴理解GNN、GCN、GAT

GNN 思想&#xff1a;近朱者赤近墨者黑 GNN的流程&#xff1a; 聚合&#xff08;把邻居的信息贴到自己身上来&#xff0c;作为它自己特征的补足&#xff09;更新循环&#xff08;为什么要多次&#xff1f;看以下例子&#xff09; GNN能干嘛&#xff1f; 1.结点分类&#xf…

动态规划lc

先找到规律&#xff0c;然后找边界情况&#xff1b;部分特殊情况分类讨论 *递归 70.爬楼梯 简单 提示 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 示例 1&#xff1a; 输入&#xff1a…

基于yolov8、yolov5的PCB板缺陷检测系统(含UI界面、数据集、训练好的模型、Python代码)

blog.csdnimg.cn/direct/6f53422ed9fd44dc8daad6dc5481c4c9.png) 项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8、yolov8 SE注意力机制 或 yolov5、yolov5 SE注意力机制 &#xff0c; 直接提供最少两个训练好的模型…

第十八届 图像像素类型转化于归一

知识点&#xff1a;像素归一化 opencv中提供四种归一的方法 -NORM_MINMAX -NORM_INF -NORM_L1 -NORM_L2 最常用的就是NORM_MINMAX归一的方法 相关的API normalize&#xff1a;void normalize(InputArray src, OutputArray dst, double alpha 1, double beta 0, int n…

关于C语⾔内存函数 memcpy memmove memset memcmp

memcpy使⽤和模拟实现 void * memcpy ( void * destination, const void * source, size_t num ); 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。 这个函数在遇到 \0 的时候并不会停下来。 如果source和destination有任何的重叠&am…

树莓派3b安装ubuntu18.04服务器系统server配置网线连接

下载ubuntu镜像网址 img镜像&#xff0c;即树莓派官方烧录器使用的镜像网址 ubuntu18.04-server&#xff1a;ARM/RaspberryPi - Ubuntu Wiki 其他版本&#xff1a;Index of /ubuntu/releases 下载后解压即可。 发现使用官方烧录器烧录配置时配置wifi无论如何都不能使用&am…

linux入门——“权限”

linux中有权限的概念&#xff0c;最常见的就是安装一些命令的时候需要输入sudo来提权&#xff0c;那么为什么要有这个东西呢&#xff1f; linux是一个多用户操作系统&#xff0c;很多东西看起来是有很多分&#xff0c;但是实际的存储只有一份&#xff08;比如命令&#xff0c;不…

小程序知识付费的优势 知识付费服务 知识付费平台 知识付费方法

在信息爆炸的时代&#xff0c;知识如同繁星点点&#xff0c;璀璨而散落。如何在这片知识的海洋中精准捕捞&#xff0c;成为现代人追求自我提升的迫切需求。小程序知识付费&#xff0c;正是这样一座桥梁&#xff0c;它以独特的优势&#xff0c;让智慧触手可及&#xff0c;轻触未…

【C++差分数组】P1672何时运输的饲料

本文涉及知识点 C差分数组 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 P1672何时运输的饲料 原文比较啰嗦&#xff0c;我简述一下&#xff1a; 第x天运来F1(1<F1<1e6)千克的饲料&#xff0c;第D&#xff08;1<2e3)天还剩F2&…

数论与同余 - 离散数学系列(七)

目录 1. 整数的性质 整除与因数 最大公约数与最小公倍数 2. 欧几里得算法 算法步骤 3. 模运算与同余 模运算 同余关系 同余的性质 4. 数论在密码学中的应用 RSA 加密算法 5. 实际应用场景 1. 数字签名 2. 哈希函数与数据完整性 3. 密钥交换 6. 例题与练习 例题…

单链表速通后续!

目录 1>>闲话 2>>头删 3>>查找 4>>在指定位置之前插入 5>>删除指定结点 6>>指定位置之后插入 7>>删除指定位置之后的结点 特别思考&#xff1a; 8>>销毁单链表 Slist.h Slist.c test.c 9>>总结 1>>闲话…

干货|SQL注入思路总结(非常详细)零基础入门到精通,收藏这一篇就够 了

1.SQL注入的业务场景及危害 1.1 什么是SQL注入 SQL注入是服务器端未严格校验客户端发送的数据&#xff0c;而导致服务端SQL语句被恶意修改并成功执行的行为称为SQL注入。 1.2 为什么会有SQL注入 代码对带入SQL语句的参数过滤不严格 未启用框架的安全配置&#xff0c;例如&a…

[面试] java开发面经-1

前言 目录 1.看到你的简历里说使用Redis缓存高频数据&#xff0c;说一下Redis的操作 2.说一下Redis的缓存击穿、缓存穿透、缓存雪崩 3.你的项目中使用了ThreadLocal&#xff0c;那么当有两个请求同时发出时&#xff0c;会怎么处理&#xff0c;可以同时处理两个请求吗 4.使用…

【多线程】多线程(12):多线程环境下使用哈希表

【多线程环境下使用哈希表&#xff08;重点掌握&#xff09;】 可以使用类&#xff1a;“ConcurrentHashMap” ★ConcurrentHashMap对比HashMap和Hashtable的优化点 1.优化了锁的粒度【最核心】 //Hashtable的加锁&#xff0c;就是直接给put&#xff0c;get等方法加上synch…

时间序列预测(一)——线性回归(linear regression)

目录 一、原理与目的 1、线性回归基于两个的假设&#xff1a; 2、线性回归的优缺点: 3、线性回归的主要目的是&#xff1a; 二、损失函数&#xff08;loss function&#xff09; 1、平方误差损失函数&#xff08;忽略了噪声误差&#xff09; 2、均方误差损失函数 三、随…

微服务实战——登录(普通登录、社交登录、SSO单点登录)

登录 1.1. 用户密码 PostMapping("/login")public String login(UserLoginVo vo, RedirectAttributes redirectAttributes, HttpSession session){R r memberFeignService.login(vo);if(r.getCode() 0){MemberRespVo data r.getData("data", new Type…

英伟达股价分析:英伟达股价能否上涨到150美元,接下来该如何操作?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经​ 猛兽财经核心观点&#xff1a; &#xff08;1&#xff09;华尔街投行Oppenheimer已将英伟达的目标价上调到了150美元。 &#xff08;2&#xff09;产品方面的最新进展和合作伙伴关系进一步提升了英伟达的市场地位。 &…

Nacos配置管理和Nacos集群配置

目录 Nacos作为配置中心实现配置管理 统一配置管理 如何在nocas添加配置文件 在微服务拉取nacos配置中心的配置 1&#xff09;引入nacos-config依赖 2&#xff09;添加bootstrap.yaml 3&#xff09;测试&#xff0c;读取nacos配置中心中配置文件的内容 ​编辑 总结&…

ORA-65096:公用用户名或角色名无效

CREATE USER DATA_SHARING IDENTIFIED BY "Ab2"; Oracle建立用户的的时候&#xff0c;可能会出现一直提示 ORA-65096:公用用户名或角色名无效&#xff1b; 我查了一下&#xff0c;好像是 oracle 12版本及以上版本的特性&#xff0c;用户名必须加c##或者C##前缀才能创…