ROS2底层机制源码分析

  • init
    ->init_and_remove_ros_arguments
        ->init
            ->Context::init 保存初始化传入的信号
            ->install_signal_handlers→SignalHandler::install 开线程响应信号
        ->_remove_ros_arguments 移除ros参数
    ->SingleNodeManager::instance().init 
    ->mogo_recorder::MogoRecorder::instance().Init 中间件录包初始化
    ->创建全局静态的NodeHandle
     

  • NodeHandle

    • 构造nodehandle,并校验命名空间,内部调用

      void NodeHandle::construct()

      {

          SingleNodeManager::instance().increase_ref();

      }

    • param->从redis获取配置信息

  • create_generic_subscription 订阅接口

    -->topics_interface->add_subscription(subscription, options.callback_group);

       -->void CallbackGroup::add_subscription(const rclcpp::SubscriptionBase::SharedPtr subscription_ptr) {

              std::lock_guard<std::mutex> lock(mutex_);

              subscription_ptrs_.push_back(subscription_ptr); // timer service client waitable一样的逻辑

              subscription_ptrs_.erase(

              std::remove_if(

              subscription_ptrs_.begin(),

              subscription_ptrs_.end(),

              [](rclcpp::SubscriptionBase::WeakPtr x) {return x.expired();}),

              subscription_ptrs_.end());

          }

  • mogo::AsyncSpinner

    /**
    * AsyncSpinner 用来异步spin 某一个 callback group
    * 如果thread_num =1,将创建一个独立的线程,进行指定callback group的spin
    * 如果thread_num >1,将使用multithead spinner 执行callback group的spin
    *
    * 提示:若只是想要进行整个node的多线程的spin,请使用 mogo::multithread_spin
    */

    • 构造

    • start

      创建执行器,将构造中传入的callback_group对象传入执行器,传入node;单独开个线程spin;多线程spin就是开指定多个线程并行spin动作

      void AsyncSpinner::start()

      {

        std::lock_guard<std::mutex> lock(mutex_);

        if (is_started_) {

          return;

        }

        mogo::spin();

        if (thread_num_ == 1) {

          exec_ = rclcpp::executors::SingleThreadedExecutor::make_shared();

        else {

          exec_ = rclcpp::executors::MultiThreadedExecutor::make_shared(rclcpp::ExecutorOptions(), thread_num_);

        }

        exec_->add_callback_group(callback_group_,

                                  SingleNodeManager::instance().get_node()->get_node_base_interface()); // 将回调组跟节点对象存入map

        th_ = std::thread([this] {

                        long tid = syscall(SYS_gettid);

                        if (tid > 0) {

                          this->th_id_.store((int)tid);

                        }

                        exec_->spin();

                      });

        is_started_ = true;

      }

      void

      MultiThreadedExecutor::spin()

      {

        if (spinning.exchange(true)) {

          throw std::runtime_error("spin() called while already spinning");

        }

        RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );

        std::vector<std::thread> threads;

        size_t thread_id = 0;

        {

          std::lock_guard wait_lock{wait_mutex_};

          for (; thread_id < number_of_threads_ - 1; ++thread_id) {

            auto func = std::bind(&MultiThreadedExecutor::run, this, thread_id);

            threads.emplace_back(func);

          }

        }

        run(thread_id);

        for (auto & thread : threads) {

          thread.join();

        }

      }

      void

      SingleThreadedExecutor::spin()

      {

        if (spinning.exchange(true)) {

          throw std::runtime_error("spin() called while already spinning");

        }

        RCPPUTILS_SCOPE_EXIT(this->spinning.store(false); );

        while (rclcpp::ok(this->context_) && spinning.load()) {

          rclcpp::AnyExecutable any_executable;

          if (get_next_executable(any_executable)) { // 内部从map中取

            execute_any_executable(any_executable);

          }

        }

      }

  • mogo::spin

    void spin()

    {

      SingleNodeManager::instance().spin();

    }

    void SingleNodeManager::spin()

    {

      if (MOGO_UNLIKELY(!is_start_)) {

        throw std::runtime_error("SingleNodeManager is not running, please create NodeHandle before that!");

      }

      if (!is_join_exec_) {

        std::lock_guard<std::mutex> lock(exec_mutex_);

        if (!is_join_exec_) {

          exec_->add_node(node_ptr_);

          is_join_single_exec_ = true;

          is_join_exec_ = true;

        else {

          if (!is_join_single_exec_) {

            throw std::runtime_error("Node has been joined in another exec");

          }

        }

      }

      exec_->spin();

      {

        std::lock_guard<std::mutex> lock(exec_mutex_);

        exec_->remove_node(node_ptr_);

        is_join_single_exec_ = false;

        is_join_exec_ = false;

      }

    }

    // 以subscription为例,以下详细函数调用栈

    1. 获取可执行对象

    bool

    Executor::get_next_ready_executable_from_map(

      AnyExecutable & any_executable,

      const rclcpp::memory_strategy::MemoryStrategy::WeakCallbackGroupsToNodesMap &

      weak_groups_to_nodes)

    {

      TRACEPOINT(rclcpp_executor_get_next_ready);

      bool success = false;

      std::lock_guard<std::mutex> guard{mutex_};

      // Check the timers to see if there are any that are ready

      memory_strategy_->get_next_timer(any_executable, weak_groups_to_nodes);

      if (any_executable.timer) {

        success = true;

      }

      if (!success) {

        // Check the subscriptions to see if there are any that are ready

        memory_strategy_->get_next_subscription(any_executable, weak_groups_to_nodes);

        if (any_executable.subscription) {

          success = true;

        }

      }

    ...

    void

    get_next_subscription(

        rclcpp::AnyExecutable & any_exec,

        const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes) override

      {

        auto it = subscription_handles_.begin();

        while (it != subscription_handles_.end()) {

          auto subscription = get_subscription_by_handle(*it, weak_groups_to_nodes);

          if (subscription) {

            // Find the group for this handle and see if it can be serviced

            auto group = get_group_by_subscription(subscription, weak_groups_to_nodes);

            if (!group) {

              // Group was not found, meaning the subscription is not valid...

              // Remove it from the ready list and continue looking

              it = subscription_handles_.erase(it);

              continue;

            }

            if (!group->can_be_taken_from().load()) {

              // Group is mutually exclusive and is being used, so skip it for now

              // Leave it to be checked next time, but continue searching

              ++it;

              continue;

            }

            // Otherwise it is safe to set and return the any_exec

            any_exec.subscription = subscription;

            any_exec.callback_group = group;

            any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);

            subscription_handles_.erase(it);

            return;

          }

          // Else, the subscription is no longer valid, remove it and continue

          it = subscription_handles_.erase(it);

        }

      }

    ...

    rclcpp::SubscriptionBase::SharedPtr

    MemoryStrategy::get_subscription_by_handle(

      const std::shared_ptr<const rcl_subscription_t> & subscriber_handle,

      const WeakCallbackGroupsToNodesMap & weak_groups_to_nodes)

    {

      for (const auto & pair : weak_groups_to_nodes) {

        auto group = pair.first.lock();

        if (!group) {

          continue;

        }

         

        // check传入的subscriber_handle跟之前创建的是否匹配

        auto match_subscription = group->find_subscription_ptrs_if(

          [&subscriber_handle](const rclcpp::SubscriptionBase::SharedPtr & subscription) -> bool {

            return subscription->get_subscription_handle() == subscriber_handle;

          });

        if (match_subscription) {

          return match_subscription;

        }

      }

      return nullptr;

    }

    ...

    template<typename Function>

    rclcpp::SubscriptionBase::SharedPtr

    find_subscription_ptrs_if(Function func) const

    {

      return _find_ptrs_if_impl<rclcpp::SubscriptionBase, Function>(func, subscription_ptrs_);

    }

    template<typename TypeT, typename Function>

    typename TypeT::SharedPtr _find_ptrs_if_impl(Function func, const std::vector<typename TypeT::WeakPtr> & vect_ptrs) const

    {

      std::lock_guard<std::mutex> lock(mutex_);

      for (auto & weak_ptr : vect_ptrs) {

        auto ref_ptr = weak_ptr.lock();

        if (ref_ptr && func(ref_ptr)) {

          return ref_ptr;

        }

      }

      return typename TypeT::SharedPtr();

    }

    至此就能匹配到对应的timer service client waitable subscription

    2. 构造执行器

     auto it = subscription_handles_.begin();

        while (it != subscription_handles_.end()) {

          auto subscription = get_subscription_by_handle(*it, weak_groups_to_nodes);

          if (subscription) {

            // Find the group for this handle and see if it can be serviced

            auto group = get_group_by_subscription(subscription, weak_groups_to_nodes);

            if (!group) {

              // Group was not found, meaning the subscription is not valid...

              // Remove it from the ready list and continue looking

              it = subscription_handles_.erase(it);

              continue;

            }

            if (!group->can_be_taken_from().load()) {

              // Group is mutually exclusive and is being used, so skip it for now

              // Leave it to be checked next time, but continue searching

              ++it;

              continue;

            }

            // Otherwise it is safe to set and return the any_exec

            any_exec.subscription = subscription;

            any_exec.callback_group = group;

            any_exec.node_base = get_node_by_group(group, weak_groups_to_nodes);

            subscription_handles_.erase(it);

            return;

          }

          // Else, the subscription is no longer valid, remove it and continue

          it = subscription_handles_.erase(it);

        }

    3. 执行

    void

    Executor::execute_any_executable(AnyExecutable & any_exec)

    {

      if (!spinning.load()) {

        return;

      }

      if (any_exec.timer) {

        TRACEPOINT(

          rclcpp_executor_execute,

          static_cast<const void *>(any_exec.timer->get_timer_handle().get()));

        execute_timer(any_exec.timer);

      }

      if (any_exec.subscription) {

        TRACEPOINT(

          rclcpp_executor_execute,

          static_cast<const void *>(any_exec.subscription->get_subscription_handle().get()));

        execute_subscription(any_exec.subscription);

      }

      if (any_exec.service) {

        execute_service(any_exec.service);

      }

      if (any_exec.client) {

        execute_client(any_exec.client);

      }

      if (any_exec.waitable) {

        any_exec.waitable->execute(any_exec.data);

      }

      // Reset the callback_group, regardless of type

      any_exec.callback_group->can_be_taken_from().store(true);

      // Wake the wait, because it may need to be recalculated or work that

      // was previously blocked is now available.

      try {

        interrupt_guard_condition_.trigger();

      catch (const rclcpp::exceptions::RCLError & ex) {

        throw std::runtime_error(

                std::string(

                  "Failed to trigger guard condition from execute_any_executable: ") + ex.what());

      }

    }

    callback如何传入?

    业务代码订阅

    subscription = node_handle_.get_node()->create_generic_subscription(

                    topic_meta.name,

                    topic_meta.type,

                    rosbag2_transport::Rosbag2QoS(queue_size),

                    [this, topic_meta](std::shared_ptr<mogo::SerializedMessage> message) { // TODO 超过Xs没有回调加事件上报

                        if (!mogo::ok())

                            return;

      

                        count_++;

                        static double now_timestamp = mogo::TimeHelper::to_sec(mogo::Time::now());

                        // calc hz every second

                        if (mogo::TimeHelper::to_sec(mogo::Time::now()) - now_timestamp >= mogo::TimeHelper::to_sec(mogo::Time::create(1))) {

                            MOGO_INFO_STREAM_THROTTLE(10"current callback frequency: " << count_);

                            count_ = 0;

                            now_timestamp = mogo::TimeHelper::to_sec(mogo::Time::now());

                        }

                        pushQueue(OutgoingMessage(message, topic_meta.name, topic_meta.type, mogo::Time::now()));

                    },

                    subscription_options

                );

    内层调用注册callback

    template<typename AllocatorT = std::allocator<void>>

      GenericSubscription(

        rclcpp::node_interfaces::NodeBaseInterface * node_base,

        const std::shared_ptr<rcpputils::SharedLibrary> ts_lib,

        const std::string & topic_name,

        const std::string & topic_type,

        const rclcpp::QoS & qos,

        // TODO(nnmm): Add variant for callback with message info. See issue #1604.

        std::function<void(std::shared_ptr<rclcpp::SerializedMessage>)> callback,

        const rclcpp::SubscriptionOptionsWithAllocator<AllocatorT> & options)

      : SubscriptionBase(

          node_base,

          *rclcpp::get_typesupport_handle(topic_type, "rosidl_typesupport_cpp", *ts_lib),

          topic_name,

          options.template to_rcl_subscription_options<rclcpp::SerializedMessage>(qos),

          true),

        callback_(callback),

        ts_lib_(ts_lib)

      {

        // This is unfortunately duplicated with the code in subscription.hpp.

        // TODO(nnmm): Deduplicate by moving this into SubscriptionBase.

        if (options.event_callbacks.deadline_callback) {

          this->add_event_handler(

            options.event_callbacks.deadline_callback,

            RCL_SUBSCRIPTION_REQUESTED_DEADLINE_MISSED);

        }

        if (options.event_callbacks.liveliness_callback) {

          this->add_event_handler(

            options.event_callbacks.liveliness_callback,

            RCL_SUBSCRIPTION_LIVELINESS_CHANGED);

        }

        if (options.event_callbacks.incompatible_qos_callback) {

          this->add_event_handler(

            options.event_callbacks.incompatible_qos_callback,

            RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS);

        else if (options.use_default_callbacks) {

          // Register default callback when not specified

          try {

            this->add_event_handler(

              [this](QOSRequestedIncompatibleQoSInfo & info) {

                this->default_incompatible_qos_callback(info);

              },

              RCL_SUBSCRIPTION_REQUESTED_INCOMPATIBLE_QOS);

          catch (UnsupportedEventTypeException & /*exc*/) {

            // pass

          }

        }

        if (options.event_callbacks.message_lost_callback) {

          this->add_event_handler(

            options.event_callbacks.message_lost_callback,

            RCL_SUBSCRIPTION_MESSAGE_LOST);

        }

      }

    处理消息

    void

    GenericSubscription::handle_serialized_message(

      const std::shared_ptr<rclcpp::SerializedMessage> & message,

      const rclcpp::MessageInfo &)

    {

      callback_(message);

    }

    消息从哪里来?---

    bool

    SubscriptionBase::take_serialized(

      rclcpp::SerializedMessage & message_out,

      rclcpp::MessageInfo & message_info_out)

    {

      rcl_ret_t ret = rcl_take_serialized_message(

        this->get_subscription_handle().get(),

        &message_out.get_rcl_serialized_message(),

        &message_info_out.get_rmw_message_info(),

        nullptr);

      if (RCL_RET_SUBSCRIPTION_TAKE_FAILED == ret) {

        return false;

      else if (RCL_RET_OK != ret) {

        rclcpp::exceptions::throw_from_rcl_error(ret);

      }

      return true;

    }

    rcl_ret_t

    rcl_take_serialized_message(

      const rcl_subscription_t * subscription,

      rcl_serialized_message_t * serialized_message,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation

    )

    {

      RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "Subscription taking serialized message");

      if (!rcl_subscription_is_valid(subscription)) {

        return RCL_RET_SUBSCRIPTION_INVALID;  // error already set

      }

      RCL_CHECK_ARGUMENT_FOR_NULL(serialized_message, RCL_RET_INVALID_ARGUMENT);

      // If message_info is NULL, use a place holder which can be discarded.

      rmw_message_info_t dummy_message_info;

      rmw_message_info_t * message_info_local = message_info ? message_info : &dummy_message_info;

      *message_info_local = rmw_get_zero_initialized_message_info();

      // Call rmw_take_with_info.

      bool taken = false;

      rmw_ret_t ret = rmw_take_serialized_message_with_info(

        subscription->impl->rmw_handle, serialized_message, &taken, message_info_local, allocation);

      if (ret != RMW_RET_OK) {

        RCL_SET_ERROR_MSG(rmw_get_error_string().str);

        return rcl_convert_rmw_ret_to_rcl_ret(ret);

      }

      RCUTILS_LOG_DEBUG_NAMED(

        ROS_PACKAGE_NAME, "Subscription serialized take succeeded: %s", taken ? "true" "false");

      if (!taken) {

        return RCL_RET_SUBSCRIPTION_TAKE_FAILED;

      }

      return RCL_RET_OK;

    }

    注意:这里已经到rmw层了(DDS的封装层)

    rmw_ret_t

    rmw_take_serialized_message_with_info(

      const rmw_subscription_t * subscription,

      rmw_serialized_message_t * serialized_message,

      bool * taken,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation)

    {

      return rmw_fastrtps_shared_cpp::__rmw_take_serialized_message_with_info(

        eprosima_fastrtps_identifier, subscription, serialized_message, taken, message_info,

        allocation);

    }

    核心代码---循环通过data_reader_->take(data_values, info_seq, 1)获取数据,最终内存拷贝到serialized_message中带出

    rmw_ret_t

    _take_serialized_message(

      const char * identifier,

      const rmw_subscription_t * subscription,

      rmw_serialized_message_t * serialized_message,

      bool * taken,

      rmw_message_info_t * message_info,

      rmw_subscription_allocation_t * allocation)

    {

      (void) allocation;

      *taken = false;

      RMW_CHECK_TYPE_IDENTIFIERS_MATCH(

        subscription handle,

        subscription->implementation_identifier, identifier,

        return RMW_RET_INCORRECT_RMW_IMPLEMENTATION)

      auto info = static_cast<CustomSubscriberInfo *>(subscription->data);

      RCUTILS_CHECK_FOR_NULL_WITH_MSG(info, "custom subscriber info is null"return RMW_RET_ERROR);

      eprosima::fastcdr::FastBuffer buffer;

      eprosima::fastdds::dds::SampleInfo sinfo;

      rmw_fastrtps_shared_cpp::SerializedData data;

      data.is_cdr_buffer = true;

      data.data = &buffer;

      data.impl = nullptr;    // not used when is_cdr_buffer is true

      eprosima::fastdds::dds::StackAllocatedSequence<void *, 1> data_values;

      const_cast<void **>(data_values.buffer())[0] = &data;

      eprosima::fastdds::dds::SampleInfoSeq info_seq{1};

      while (ReturnCode_t::RETCODE_OK == info->data_reader_->take(data_values, info_seq, 1)) {

        auto reset = rcpputils::make_scope_exit(

          [&]()

          {

            data_values.length(0);

            info_seq.length(0);

          });

        if (info_seq[0].valid_data) {

          auto buffer_size = static_cast<size_t>(buffer.getBufferSize());

          if (serialized_message->buffer_capacity < buffer_size) {

            auto ret = rmw_serialized_message_resize(serialized_message, buffer_size);

            if (ret != RMW_RET_OK) {

              return ret;           // Error message already set

            }

          }

          serialized_message->buffer_length = buffer_size;

          memcpy(serialized_message->buffer, buffer.getBuffer(), serialized_message->buffer_length);

          if (message_info) {

            _assign_message_info(identifier, message_info, &info_seq[0]);

          }

          *taken = true;

          break;

        }

      }

      return RMW_RET_OK;

    }

    fastrtps-fastdds---查数据

    ReturnCode_t DataReaderImpl::read_or_take(

            LoanableCollection& data_values,

            SampleInfoSeq& sample_infos,

            int32_t max_samples,

            const InstanceHandle_t& handle,

            SampleStateMask sample_states,

            ViewStateMask view_states,

            InstanceStateMask instance_states,

            bool exact_instance,

            bool single_instance,

            bool should_take)

    {

        if (reader_ == nullptr)

        {

            return ReturnCode_t::RETCODE_NOT_ENABLED;

        }

        ReturnCode_t code = check_collection_preconditions_and_calc_max_samples(data_values, sample_infos, max_samples);

        if (!code)

        {

            return code;

        }

    #if HAVE_STRICT_REALTIME

        auto max_blocking_time = std::chrono::steady_clock::now() +

                std::chrono::microseconds(::TimeConv::Time_t2MicroSecondsInt64(qos_.reliability().max_blocking_time));

        std::unique_lock<RecursiveTimedMutex> lock(reader_->getMutex(), std::defer_lock);

        if (!lock.try_lock_until(max_blocking_time))

        {

            return ReturnCode_t::RETCODE_TIMEOUT;

        }

    #else

        std::lock_guard<RecursiveTimedMutex> _(reader_->getMutex());

    #endif // if HAVE_STRICT_REALTIME

        set_read_communication_status(false);

        auto it = history_.lookup_available_instance(handle, exact_instance);

        if (!it.first)

        {

            if (exact_instance && !history_.is_instance_present(handle))

            {

                return ReturnCode_t::RETCODE_BAD_PARAMETER;

            }

            else

            {

                return ReturnCode_t::RETCODE_NO_DATA;

            }

        }

        code = prepare_loan(data_values, sample_infos, max_samples);

        if (!code)

        {

            return code;

        }

        detail::StateFilter states{ sample_states, view_states, instance_states };

        detail::ReadTakeCommand cmd(*this, data_values, sample_infos, max_samples, states, it.second, single_instance);

        while (!cmd.is_finished())

        {

            cmd.add_instance(should_take);

        }

        return cmd.return_value();

    }

  • create_publisher

  • publish

  • mogo::shutdown

    bool shutdown()

    {

      return rclcpp::shutdown();

    }

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

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

相关文章

条件判断if语句与case语句

一、条件测试 test命令进行条件测试&#xff0c;然后根据返回值来判断条件是否成立。 常用操作符&#xff1a; -e &#xff1a;既可以测试文件又可以测试目录是否存在 -d &#xff1a;测试目录是否存在 -f &#xff1a;测试文件是否存在 -r &#xff1a;测试当前用户是否…

前端加载 动画特效

效果图: 完整代码: <!DOCTYPE html> <html> <head><meta charset="UTF-8" /><title>加载动画</title><style type="text/css">/* 设置页面背景颜色 */body {background: #ECF0F1;}/* 定义加载动画容器的样式…

抖店没人做了?不是项目不行了,而是商家们都换思路去玩了

我是王路飞。 有没有发现现在很多抖店新手都在吐槽&#xff0c;抖店不好做了&#xff0c;做不起来&#xff0c;没人做了&#xff0c;太内卷了...... 对这种做不起来还在怪项目本身的&#xff0c;一定要离他远一点&#xff0c;省得被他的负能量给影响到自己的状态。 任何项目…

怎么学习汇川Codesys PLC教程?

前言 各位好&#xff0c;我在B站和抖音上都有发布视频的&#xff0c;搜索我的名称“阿凡工控分享”即可。在CSDN上发表文章也是想把我的一点见解和经验分享出来&#xff0c;进一步的方便大家进行学习。 我是正文 本文主要也是为了方便大家学习汇川的Codesys PLC而制作的&…

Java并发编程之线程池源码解析与实现详解

Java并发编程是现代开发中非常重要的一个领域。使用线程池来管理和控制线程的创建和生命周期&#xff0c;可以更加高效地利用系统资源&#xff0c;提高系统的并发性能。线程池是一种常见的并发编程模型&#xff0c;Java提供了ThreadPoolExecutor类来实现线程池。本文将详细解析…

⌈ 传知代码 ⌋ MonoCon解读与复现

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

写给大数据开发:好的指标定义、特性与业务价值

在大数据时代&#xff0c;数据的质量对于业务决策和数据分析至关重要。好的数据不仅丰富&#xff0c;而且准确、及时&#xff0c;为业务提供有力支撑。 文章目录 数据定义与特性数据对业务的价值指标展示与规范化定义一些指标的定义好的数据是业务成功的关键 数据定义与特性 好…

在 Wed 中应用 MyBatis(同时使用MVC架构模式,以及ThreadLocal 事务控制)

1. 在 Wed 中应用 MyBatis&#xff08;同时使用MVC架构模式&#xff0c;以及ThreadLocal 事务控制&#xff09; 文章目录 1. 在 Wed 中应用 MyBatis&#xff08;同时使用MVC架构模式&#xff0c;以及ThreadLocal 事务控制&#xff09;2. 实现步骤&#xff1a;1. 第一步&#xf…

python爬虫爬取微博评论--完整版(超详细,大学生不骗大学生)

目录 一、找到页面 二、学会使用检查元素 2.1 打开检查元素界面 2.2 找到所有评论所在的位置 2.2.1 搜索评论 2.2.2 找到data表 三、基础部分代码实现 四、格式化输出 4.1 了解存储格式 4.2 单独取出内容 4.3 取出所有评论内容 4.4 格式化读取信息 五、导出成表格…

【ARM Cache 与 MMU 系列文章 7.4 -- ARMv8 MMU 配置 寄存器使用介绍】

请阅读【ARM Cache 及 MMU/MPU 系列文章专栏导读】 及【嵌入式开发学习必备专栏】 文章目录 MMU 转换控制寄存器 TCR_ELxTCR_ELx 概览TCR_ELx 寄存器字段详解TCR 使用示例Normal MemoryCacheableShareability MMU 内存属性寄存器 MAIR_ELxMAIR_ELx 寄存器结构内存属性字段Devic…

两种典型的嵌入式系统架构模式

大多数嵌入式系统都具备实时特征&#xff0c;那么&#xff0c;这种嵌入式系统的典型架构可概括为两种模式&#xff0c;即层次化模式架构和递归模式架构。 1.层次化模式架构 为了达到概念一致性&#xff0c;许多系统通过层次化的方法进行搭建。这样做的结果是&#xff1a;位于高…

【漏洞复现】海洋CMS /js/player/dmplayer/dmku/ SQL注入漏洞复现(CVE-2024-29275)

0x01 产品简介 海洋CMS是一套专为不同需求的站长而设计的内容管理系统&#xff0c;灵活、方便、人性化设计、内容的专业网站。海洋CMS基于PHPMySql技术开发&#xff0c;完全开源免费、无任何加密代码。简单易用是最大的特色&#xff0c;可快速建立一个海量 0x02 漏洞概述 海…

翻译: Gen AI生成式人工智能学习资源路线图一

Introduction 介绍 本文档旨在作为学习现代人工智能系统背后的关键概念的手册。考虑到人工智能最近的发展速度&#xff0c;确实没有一个好的教科书式的资源来快速了解 LLMs 或其他生成模型的最新和最伟大的创新&#xff0c;但互联网上有大量关于这些主题的优秀解释资源&#x…

.NET周刊【6月第2期 2024-06-09】

国内文章 C#开源实用的工具类库&#xff0c;集成超过1000多种扩展方法 https://www.cnblogs.com/Can-daydayup/p/18230586 文章介绍了一个免费的C#工具类库Z.ExtensionMethods&#xff0c;可以通过NuGet包管理器轻松集成。该库支持.NET Standard 2.0和.NET Framework 4.0&am…

make和Cmake都有什么区别?(内附使用详解)

Make和Cmake make和Cmake的区别&#xff1a; 角色和功能: make: 是一个构建工具&#xff0c;它的任务是读取 Makefile 文件&#xff0c;并基于这些文件中的指令执行具体的构建操作。Makefile 文件包含了如何构建项目的规则&#xff0c;make 负责解析这些规则并执行必要的命令来…

webshell获取总结(cms获取方法、非cms获取方法、中间件拿Webshell方法)

目录 前期准备&#xff1a; 1、cookices靶场网站搭建&#xff1a; 2、dedecms靶场环境搭建&#xff1a; 获取Webshell方法总结&#xff1a; 一、CMS获取Webshell方法 二、非CMS获取Webshell方法 1、数据库备份获取Webshell 例如&#xff1a; 2、抓包上传获取Webshell 3、…

什么是APS计划排程? 企业产能与效率提升的智能引擎

快节奏和愈发激烈的现代工业环境中&#xff0c;如何提升生产效率、降低生产成本、最大化的满足订单交期、实现企业的降本增效&#xff0c;已成为众多企业关注的焦点。而APS系统——作为高级生产计划和调度系统的代表&#xff0c;正以其卓越的生产过程实时监控、产品加工过程优化…

[CAN] 创建解析CAN报文DBC文件教程

&#x1f449;本教程需要先安装CANdb软件&#xff0c;[CAN] DBC数据库编辑器的下载与安装 &#x1f64b;前言 DBC(全称为Database CAN)&#xff0c;是用于描述单个CAN网络中各逻辑节点的信息。 DBC是汽车ECU&#xff08;Electronic Control Unit&#xff0c;电子控制单元&…

Java面试八股之构造方法有哪些特性

构造方法有哪些特性 方法名与类名相同&#xff1a;构造方法的名称必须与它所在的类名称完全相同&#xff0c;包括大小写。 无返回类型&#xff1a;构造方法没有返回类型声明&#xff0c;连void也不需要。虽然没有明确的返回类型&#xff0c;但它隐式地返回了新创建的实例的引…

基于Java的诊所医院管理系统,springboot+html,MySQL数据库,用户+医生+管理员三种身份,完美运行,有一万一千字论文

演示视频 基本介绍 基于Java的诊所医院管理系统&#xff0c;springboothtml&#xff0c;MySQL数据库&#xff0c;用户医生管理员三种身份&#xff0c;完美运行&#xff0c;有一万一千字论文。 用户&#xff1a;个人信息管理、预约医生、查看病例、查看公告、充值、支付费用...…