【IVI】EVS 应用

news2024/12/23 14:25:27

EVS 应用

  • 1、EVS启动
  • 2、EvsStateControl.cpp 控制管理
    • 2.1 EvsStateControl初始化
    • 2.2 EvsVehicleListener.h唤起处理`EvsStateControl::updateLoop()`
  • 3、EVS 应用逻辑流程

android12-release
增强型视觉系统 (EVS)


1、EVS启动

Android 包含与 EVS 管理器车载 HAL 通信的 EVS 应用的原生 C++ 参考实现,以提供基本的后视摄像头功能。该应用应在系统启动过程的早期启动,根据可用的相机和汽车状态(车轮和转向灯状态)显示合适的视频。原始设备制造商 (OEM) 可以使用自己的汽车专用逻辑和呈现来修改或替换 EVS 应用。

packages/services/Car/cpp/evs/apps/default/Android.bp
packages/services/Car/cpp/evs/apps/default/evs_app.rc
packages/services/Car/cpp/evs/apps/default/evs_app.cpp

evs_app.rc解析主要入口evs_app.cpp#main()

  1. ConfigManager config加载我们的配置信息:
    const char* CONFIG_DEFAULT_PATH = "/system/etc/automotive/evs/config.json";
    const char* CONFIG_OVERRIDE_PATH = "/system/etc/automotive/evs/config_override.json";
  2. pEvs = IEvsEnumerator::getService(evsServiceName);获取EVS 管理器服务,evsServiceName为default、EvsEnumeratorHw 或 EvsEnumeratorHw-Mock
  3. pDisplay = pEvs->openDisplay_1_1(displayId); 获取用于专门与系统的 EVS 显示进行交互的接口对象
  4. pVnet = IVehicle::getService() 连接Vehicle HAL;注册属性监听class EvsVehicleListener : public IVehicleCallback
    subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION) 车辆档位属性监听
    subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE) 车辆转向信号灯属性监听
  5. pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config) 转态控制器创建并启动pStateController->startUpdateLoop()
  6. pEvsListener->run(pStateController); 等到车辆属性事件,waitForEvents(5000)每隔一段时间唤醒并验证当前的状态“以防万一”
// Main entry point
int main(int argc, char** argv)
{
    LOG(INFO) << "EVS app starting";

    // Register a signal handler
    registerSigHandler();

    // Set up default behavior, then check for command line options
    bool useVehicleHal = true;
    bool printHelp = false;
    const char* evsServiceName = "default";
    int displayId = -1;
    bool useExternalMemory = false;
    android_pixel_format_t extMemoryFormat = HAL_PIXEL_FORMAT_RGBA_8888;
    int32_t mockGearSignal = static_cast<int32_t>(VehicleGear::GEAR_REVERSE);
    for (int i=1; i< argc; i++) {
        if (strcmp(argv[i], "--test") == 0) {
            useVehicleHal = false;
        } else if (strcmp(argv[i], "--hw") == 0) {
            evsServiceName = "EvsEnumeratorHw";
        } else if (strcmp(argv[i], "--mock") == 0) {
            evsServiceName = "EvsEnumeratorHw-Mock";
        } else if (strcmp(argv[i], "--help") == 0) {
            printHelp = true;
        } else if (strcmp(argv[i], "--display") == 0) {
            displayId = std::stoi(argv[++i]);
        } else if (strcmp(argv[i], "--extmem") == 0) {
            useExternalMemory = true;
            if (i + 1 >= argc) {
                // use RGBA8888 by default
                LOG(INFO) << "External buffer format is not set.  "
                          << "RGBA8888 will be used.";
            } else {
                if (!convertStringToFormat(argv[i + 1], &extMemoryFormat)) {
                    LOG(WARNING) << "Color format string " << argv[i + 1]
                                 << " is unknown or not supported.  RGBA8888 will be used.";
                } else {
                    // move the index
                    ++i;
                }
            }
        } else if (strcmp(argv[i], "--gear") == 0) {
            // Gear signal to simulate
            i += 1; // increase an index to next argument
            if (strcasecmp(argv[i], "Park") == 0) {
                mockGearSignal = static_cast<int32_t>(VehicleGear::GEAR_PARK);
            } else if (strcasecmp(argv[i], "Reverse") != 0) {
                LOG(WARNING) << "Unknown gear signal, " << argv[i] << ", is ignored "
                             << "and the reverse signal will be used instead";
            }
        } else {
            printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
            printHelp = true;
        }
    }
    if (printHelp) {
        printf("Options include:\n");
        printf("  --test\n\tDo not talk to Vehicle Hal, "
               "but simulate a given mock gear signal instead\n");
        printf("  --gear\n\tMock gear signal for the test mode.");
        printf("  Available options are Reverse and Park (case insensitive)\n");
        printf("  --hw\n\tBypass EvsManager by connecting directly to EvsEnumeratorHw\n");
        printf("  --mock\n\tConnect directly to EvsEnumeratorHw-Mock\n");
        printf("  --display\n\tSpecify the display to use.  If this is not set, the first"
                              "display in config.json's list will be used.\n");
        printf("  --extmem  <format>\n\t"
               "Application allocates buffers to capture camera frames.  "
               "Available format strings are (case insensitive):\n");
        printf("\t\tRGBA8888: 4x8-bit RGBA format.  This is the default format to be used "
               "when no format is specified.\n");
        printf("\t\tYV12: YUV420 planar format with a full resolution Y plane "
               "followed by a V values, with U values last.\n");
        printf("\t\tNV21: A biplanar format with a full resolution Y plane "
               "followed by a single chrome plane with weaved V and U values.\n");
        printf("\t\tYUYV: Packed format with a half horizontal chrome resolution.  "
               "Known as YUV4:2:2.\n");

        return EXIT_FAILURE;
    }

    // Load our configuration information
    ConfigManager config;
    if (!config.initialize(CONFIG_OVERRIDE_PATH)) {
        if (!config.initialize(CONFIG_DEFAULT_PATH)) {
            LOG(ERROR) << "Missing or improper configuration for the EVS application.  Exiting.";
            return EXIT_FAILURE;
        }
    }

    // Set thread pool size to one to avoid concurrent events from the HAL.
    // This pool will handle the EvsCameraStream callbacks.
    // Note:  This _will_ run in parallel with the EvsListener run() loop below which
    // runs the application logic that reacts to the async events.
    configureRpcThreadpool(1, false /* callerWillJoin */);

    // Construct our async helper object
    sp<EvsVehicleListener> pEvsListener = new EvsVehicleListener();

    // Get the EVS manager service
    LOG(INFO) << "Acquiring EVS Enumerator";
    pEvs = IEvsEnumerator::getService(evsServiceName);
    if (pEvs.get() == nullptr) {
        LOG(ERROR) << "getService(" << evsServiceName
                   << ") returned NULL.  Exiting.";
        return EXIT_FAILURE;
    }

    // Request exclusive access to the EVS display
    LOG(INFO) << "Acquiring EVS Display";

    // We'll use an available display device.
    displayId = config.setActiveDisplayId(displayId);
    if (displayId < 0) {
        PLOG(ERROR) << "EVS Display is unknown.  Exiting.";
        return EXIT_FAILURE;
    }

    pDisplay = pEvs->openDisplay_1_1(displayId);
    if (pDisplay.get() == nullptr) {
        LOG(ERROR) << "EVS Display unavailable.  Exiting.";
        return EXIT_FAILURE;
    }

    config.useExternalMemory(useExternalMemory);
    config.setExternalMemoryFormat(extMemoryFormat);

    // Set a mock gear signal for the test mode
    config.setMockGearSignal(mockGearSignal);

    // Connect to the Vehicle HAL so we can monitor state
    sp<IVehicle> pVnet;
    if (useVehicleHal) {
        LOG(INFO) << "Connecting to Vehicle HAL";
        pVnet = IVehicle::getService();
        if (pVnet.get() == nullptr) {
            LOG(ERROR) << "Vehicle HAL getService returned NULL.  Exiting.";
            return EXIT_FAILURE;
        } else {
            // Register for vehicle state change callbacks we care about
            // Changes in these values are what will trigger a reconfiguration of the EVS pipeline
            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::GEAR_SELECTION)) {
                LOG(ERROR) << "Without gear notification, we can't support EVS.  Exiting.";
                return EXIT_FAILURE;
            }
            if (!subscribeToVHal(pVnet, pEvsListener, VehicleProperty::TURN_SIGNAL_STATE)) {
                LOG(WARNING) << "Didn't get turn signal notifications, so we'll ignore those.";
            }
        }
    } else {
        LOG(WARNING) << "Test mode selected, so not talking to Vehicle HAL";
    }

    // Configure ourselves for the current vehicle state at startup
    LOG(INFO) << "Constructing state controller";
    pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config);
    if (!pStateController->startUpdateLoop()) {
        LOG(ERROR) << "Initial configuration failed.  Exiting.";
        return EXIT_FAILURE;
    }

    // Run forever, reacting to events as necessary
    LOG(INFO) << "Entering running state";
    pEvsListener->run(pStateController);

    // In normal operation, we expect to run forever, but in some error conditions we'll quit.
    // One known example is if another process preempts our registration for our service name.
    LOG(ERROR) << "EVS Listener stopped.  Exiting.";

    return EXIT_SUCCESS;
}

android12-releaseC++ EVS 示例应用是位于 packages/services/Car/cpp/evs/apps

在这里插入图片描述

2、EvsStateControl.cpp 控制管理

2.1 EvsStateControl初始化

pStateController = new EvsStateControl(pVnet, pEvs, pDisplay, config) :

  1. mVehicle(pVnet) :VehicleHal 车辆属性服务;参考 【IVI】VehicleService启动、【IVI】车载设备硬件抽象层VHAL
  2. mEvs(pEvs)EVS 管理器服务
  3. mDisplay(pDisplay) :与系统的 EVS 显示进行交互
  4. mConfig(config) :EVS json配置
  5. mCurrentState(OFF) :当前状态
  6. mEvsStats(EvsStats::build()) :evs状态
  7. mEvs->getCameraList_1_1() :系统中所有相机的说明的矢量
  8. mCameraList、mCameraDescList :保存相关相机说明信息CameraDesc_1_1
    hardware/interfaces/automotive/evs/1.1/types.hal
    hardware/interfaces/automotive/evs/1.0/types.hal
    在这里插入图片描述
    在这里插入图片描述
EvsStateControl::EvsStateControl(android::sp<IVehicle> pVnet, android::sp<IEvsEnumerator> pEvs,
                                 android::sp<IEvsDisplay> pDisplay, const ConfigManager& config) :
      mVehicle(pVnet),
      mEvs(pEvs),
      mDisplay(pDisplay),
      mConfig(config),
      mCurrentState(OFF),
      mEvsStats(EvsStats::build()) {
    // Initialize the property value containers we'll be updating (they'll be zeroed by default)
    static_assert(getPropType(VehicleProperty::GEAR_SELECTION) == VehiclePropertyType::INT32,
                  "Unexpected type for GEAR_SELECTION property");
    static_assert(getPropType(VehicleProperty::TURN_SIGNAL_STATE) == VehiclePropertyType::INT32,
                  "Unexpected type for TURN_SIGNAL_STATE property");

    mGearValue.prop       = static_cast<int32_t>(VehicleProperty::GEAR_SELECTION);
    mTurnSignalValue.prop = static_cast<int32_t>(VehicleProperty::TURN_SIGNAL_STATE);

    // This way we only ever deal with cameras which exist in the system
    // Build our set of cameras for the states we support
    LOG(DEBUG) << "Requesting camera list";
    mEvs->getCameraList_1_1(
        [this, &config](hidl_vec<CameraDesc> cameraList) {
            LOG(INFO) << "Camera list callback received " << cameraList.size() << "cameras.";
            for (auto&& cam: cameraList) {
                LOG(DEBUG) << "Found camera " << cam.v1.cameraId;
                bool cameraConfigFound = false;

                // Check our configuration for information about this camera
                // Note that a camera can have a compound function string
                // such that a camera can be "right/reverse" and be used for both.
                // If more than one camera is listed for a given function, we'll
                // list all of them and let the UX/rendering logic use one, some
                // or all of them as appropriate.
                for (auto&& info: config.getCameras()) {
                    if (cam.v1.cameraId == info.cameraId) {
                        // We found a match!
                        if (info.function.find("reverse") != std::string::npos) {
                            mCameraList[State::REVERSE].emplace_back(info);
                            mCameraDescList[State::REVERSE].emplace_back(cam);
                        }
                        if (info.function.find("right") != std::string::npos) {
                            mCameraList[State::RIGHT].emplace_back(info);
                            mCameraDescList[State::RIGHT].emplace_back(cam);
                        }
                        if (info.function.find("left") != std::string::npos) {
                            mCameraList[State::LEFT].emplace_back(info);
                            mCameraDescList[State::LEFT].emplace_back(cam);
                        }
                        if (info.function.find("park") != std::string::npos) {
                            mCameraList[State::PARKING].emplace_back(info);
                            mCameraDescList[State::PARKING].emplace_back(cam);
                        }
                        cameraConfigFound = true;
                        break;
                    }
                }
                if (!cameraConfigFound) {
                    LOG(WARNING) << "No config information for hardware camera "
                                 << cam.v1.cameraId;
                }
            }
        }
    );

    LOG(DEBUG) << "State controller ready";
}

2.2 EvsVehicleListener.h唤起处理EvsStateControl::updateLoop()

VehicleProperty::GEAR_SELECTION、VehicleProperty::TURN_SIGNAL_STATE事件接收onPropertyEventmEventCond.notify_one()处理EvsVehicleListener#run死循环调用pStateController->postCommand(cmd)
mWakeSignal.notify_all()唤起最终调用EvsStateControl::updateLoop()

  1. CHECK_VEHICLE_STATE 这里提示 Just running selectStateForCurrentConditions below will take care of this
    selectStateForCurrentConditions() 就是根据 车辆档位和转向信号灯属性 判断 configureEvsPipeline 处理状态和renderer
  2. mCurrentRenderer->drawFrame(convertBufferDesc(tgtBuffer)) 生成我们的输出图像

packages/services/Car/cpp/evs/apps/default/EvsVehicleListener.h

void run(EvsStateControl *pStateController) {
    while (true) {
        // Wait until we have an event to which to react
        // (wake up and validate our current state "just in case" every so often)
        waitForEvents(5000);

        // If we were delivered an event (or it's been a while) update as necessary
        EvsStateControl::Command cmd = {
            .operation = EvsStateControl::Op::CHECK_VEHICLE_STATE,
            .arg1      = 0,
            .arg2      = 0,
        };
        pStateController->postCommand(cmd);
    }
}

packages/services/Car/cpp/evs/apps/default/EvsStateControl.cpp

void EvsStateControl::updateLoop() {
    LOG(DEBUG) << "Starting EvsStateControl update loop";

    bool run = true;
    while (run) {
        // Process incoming commands
        {
            std::lock_guard <std::mutex> lock(mLock);
            while (!mCommandQueue.empty()) {
                const Command& cmd = mCommandQueue.front();
                switch (cmd.operation) {
                case Op::EXIT:
                    run = false;
                    break;
                case Op::CHECK_VEHICLE_STATE:
                    // Just running selectStateForCurrentConditions below will take care of this
                    break;
                case Op::TOUCH_EVENT:
                    // Implement this given the x/y location of the touch event
                    break;
                }
                mCommandQueue.pop();
            }
        }

        // Review vehicle state and choose an appropriate renderer
        if (!selectStateForCurrentConditions()) {
            LOG(ERROR) << "selectStateForCurrentConditions failed so we're going to die";
            break;
        }

        // If we have an active renderer, give it a chance to draw
        if (mCurrentRenderer) {
            // Get the output buffer we'll use to display the imagery
            BufferDesc_1_0 tgtBuffer = {};
            mDisplay->getTargetBuffer([&tgtBuffer](const BufferDesc_1_0& buff) {
                                          tgtBuffer = buff;
                                      }
            );

            if (tgtBuffer.memHandle == nullptr) {
                LOG(ERROR) << "Didn't get requested output buffer -- skipping this frame.";
                run = false;
            } else {
                // Generate our output image
                if (!mCurrentRenderer->drawFrame(convertBufferDesc(tgtBuffer))) {
                    // If drawing failed, we want to exit quickly so an app restart can happen
                    run = false;
                }

                // Send the finished image back for display
                mDisplay->returnTargetBufferForDisplay(tgtBuffer);

                if (!mFirstFrameIsDisplayed) {
                    mFirstFrameIsDisplayed = true;
                    // returnTargetBufferForDisplay() is finished, the frame should be displayed
                    mEvsStats.finishComputingFirstFrameLatency(android::uptimeMillis());
                }
            }
        } else if (run) {
            // No active renderer, so sleep until somebody wakes us with another command
            // or exit if we received EXIT command
            std::unique_lock<std::mutex> lock(mLock);
            mWakeSignal.wait(lock);
        }
    }

    LOG(WARNING) << "EvsStateControl update loop ending";

    if (mCurrentRenderer) {
        // Deactive the renderer
        mCurrentRenderer->deactivate();
    }

    // If `ICarTelemetry` service was not ready before, we need to try sending data again.
    mEvsStats.sendCollectedDataBlocking();

    printf("Shutting down app due to state control loop ending\n");
    LOG(ERROR) << "Shutting down app due to state control loop ending";
}

3、EVS 应用逻辑流程

在这里插入图片描述

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

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

相关文章

CAD2021安装教程适合新手小白【附安装包和手册】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、下载文件二、使用步骤1.安装软件前&#xff0c;断开电脑网络&#xff08;拔掉网线、关闭WIFI&#xff09;2、鼠标右击【AutoCAD2021(64bit)】压缩包选择【解…

解密:GPT-4框架与训练过程,数据集组成,并行性的策略,专家权衡,推理权衡等细节内容

大家好&#xff0c;我是微学AI&#xff0c;今天给大家解密一下GPT-4框架与训练过程&#xff0c;数据集组成&#xff0c;并行性的策略&#xff0c;专家权衡&#xff0c;推理权衡等细节内容。2023年3月14日&#xff0c;OpenAI发布GPT-4&#xff0c;然而GPT-4的框架没有公开&#…

Nacos服务注册和配置中心(Config,Eureka,Bus)2

Nacos数据模型 Nacos领域模型,Namespace命名空间、Group分组、集群这些都是为了进行归类管理&#xff0c;把服务和配置文件进行归类&#xff0c;归类之后就可以实现一定的效果&#xff0c;比如隔离。对于服务来说&#xff0c;不同命名空间中的服务不能够互相访问调用 N…

msvcr110.dll丢失的解决方法分享,教你如何快速解决

首先介绍msvcr110.dll是什么&#xff1f;下面再介绍解决方法。 msvcr110.dll文件它提供了一系列用于C编程的函数和资源。这个文件通常用于支持使用了C语言编写的程序&#xff0c;如一些游戏、图形应用程序、数据库管理工具等。 与msvcp110.dll文件类似&#xff0c;msvcr110.dl…

Linux系统编程(守护进程)

文章目录 前言一、守护进程概念二、空洞文件三、创建守护进程总结 前言 本篇文章我们来讲解守护进程&#xff0c;守护进程在进程中是一个比较重要的概念&#xff0c;在笔试面试中也经常考到&#xff0c;这篇文章就带大家来学习一下什么是守护进程。 一、守护进程概念 守护进…

golang IDE 使用 go-1.7 无法识别 goroot问题

问题 当前使用了 golang IDE 要设定 go-1.17 版本作为默认 GOROOT 系统环境变量已经定义好 打开了 ide 会出现下面问题&#xff0c;选择 1.17 后会出现下面报错 error message The selected directory is not a valid horne for GO SDK 解决方法 修改 $GOROOT 下文件增加一个变…

动态表单实现原理

目录 动态表单是什么 动态表单的关键 前后端职责 数据库与表结构 功能实现与改进建议 动态表单是什么 静态表单是很常见&#xff0c;也是常规做法&#xff0c;其表单的结构是固定的&#xff0c;通常情况下一个表单对应数据库的一张表&#xff0c;表单中一个数据项对应数据表的一…

物业小程序制作:提升管理效率与服务质量

随着物业管理的日益复杂&#xff0c;物业小程序成为了提高管理效率和提供优质服务的重要工具。物业小程序旨在提供高效的物业管理服务。通过物业小程序&#xff0c;物业公司能够方便地与业主进行信息交流、报修处理等操作。 物业小程序的好处 提高管理效率&#xff1a;物业小程…

暑假第七天打卡

离散&#xff1a; 主析取范式和主合取范式的应用&#xff1a; &#xff08;1&#xff09;求公式成真与成假赋值&#xff1a; 化为主析取范式后&#xff0c;下标化为二进制就是成真赋值&#xff0c;不在下标里的就是成假赋值 化为主合取范式后&#xff0c;下标化为二进制就是…

2.Postgresql--array

CREATE TABLE city(country character varying(64),city character varying(64) );INSERT INTO city VALUES (中国,台北), (中国,香港), (中国,上海), (日本,东京), (日本,大阪);select country,string_agg(city,; order by city desc) from city group by countryselect coun…

React native 已有项目升级兼容web

基础 概念 | webpack 中文文档 | webpack 中文文档 | webpack 中文网 深入理解Webpack及Babel的使用 - 掘金 Introduction to React Native for Web // React Native for Web Webpack 是一个现代的 JavaScript 应用程序的静态模块打包工具&#xff0c;它将应用程序所依赖的各…

DynaSLAM2 2020论文翻译

DynaSLAM2:紧耦合的多目标追踪和SLAM 摘要 - 场景刚度的假设在视觉SLAM算法中很常见。但是&#xff0c;它限制了它们在人口稠密的现实环境中的适用性。此外&#xff0c;大多数智力包括自动驾驶&#xff0c;多机器人协作和增强/虚拟现实&#xff0c;都需要对周围环境进行明确的…

Low-Light Image Enhancement via Self-Reinforced Retinex Projection Model 论文阅读笔记

这是马龙博士2022年在TMM期刊发表的基于改进的retinex方法去做暗图增强&#xff08;非深度学习&#xff09;的一篇论文 文章用一张图展示了其动机&#xff0c;第一行是估计的亮度层&#xff0c;第二列是通常的retinex方法会对估计的亮度层进行RTV约束优化&#xff0c;从而产生…

Java正则校验:密码必须由字母和数字组成,且大于等于8个字符。

需求 对登录密码进行校验&#xff0c;需要密码必须由字母和数字组成&#xff08;同时包括数字和数字&#xff09;&#xff0c;且大于等于8个字符。 原理 使用正则表达式校验字符串。 正则表达式构建思路&#xff1a; 字符为数字或字母&#xff1b;不能全是数字&#xff1b…

关于cip.cc查IP出口地址的工具到底准确不准确?

关于 cip.cc 或其他在线IP查询工具的准确性&#xff0c; 首先来看2张图片&#xff0c;分别如下 实际情况就是作者在杭州&#xff0c;使用的网络出口地址应该是百度的&#xff0c;而不是cip.cc所显示的地址。 所以结论是cip.cc并不靠谱&#xff0c;我又查阅了相关资料如下 1.…

Centos7部署nacos集群

一、GitHub下载Nacoc安装包 https://github.com/alibaba/nacos/releases 二、环境准备 1.服务器准备 2、JVAV环境安装 每台服务器都安装 JDK&#xff1a;yum install java-1.8.0-openjdk* -y三、软件安装 1、上传下载好的压缩包导服务目录&#xff08;自定义/app&#xff…

Spring Scope

Spring中五种 Scope域 singleton&#xff0c;容器启动时创建&#xff08;未设置延迟&#xff09;&#xff0c;容器关闭时销毁【单例】prototype&#xff0c;每次使用时创建&#xff0c;不会自动销毁&#xff0c;需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁【…

前端Web实战:从零打造一个类Visio的流程图拓扑图绘图工具

前言 大家好&#xff0c;本系列从Web前端实战的角度&#xff0c;给大家分享介绍如何从零打造一个自己专属的绘图工具&#xff0c;实现流程图、拓扑图、脑图等类Visio的绘图工具。 你将收获 免费好用、专属自己的绘图工具前端项目实战学习如何从0搭建一个前端项目等基础框架项…

小白到运维工程师自学之路 第五十三集 (rsync+inotify备份)

一、概述 Rsync是一个用于在不同计算机之间同步文件和文件夹的工具。它可以在本地计算机和远程服务器之间复制、更新和备份文件。rsync通过比较源和目标文件的差异来最小化传输的数据量&#xff0c;从而提供高效的文件同步功能。 Inotify是Linux内核提供的一种机制&#xff0…

【C++】模板(函数模板与类模板)讲解

本篇文章会对C中的模板进行讲解&#xff0c;其中会对函数模板和类模板进行讲解。希望本篇文章会对你有所帮助。 文章目录 一、函数模板 1、1 模板的引入 1、2 函数模板举例讲解 1、2、1 函数模板的概念 1、2、2 函数模板格式 1、2、3 函数模板实例化 1、2、4 模板参数的匹配原则…