(6)线程

news/2024/4/24 5:50:13/

文章目录

前言

1 定时器回调

2 HAL专用线程

3 驱动程序专用线程

4 ArduPilot驱动与平台驱动的对比

5 平台专用线程和任务

6 AP_Scheduler系统

7 信号量

8 无锁数据结构

9 PX4 ORB


前言

一旦你学会了 ArduPilot 库的基础知识,现在是时候让你了解 ArduPilot 是如何处理线程的。继承自 arduinosetup()/loop() 结构可能会让人觉得 ArduPilot 是一个单线程系统,但事实上它并不是。

ArduPilot 中的线程方法取决于它所构建的主板。有些主板(如 APM1APM2)不支持线程,所以用一个简单的定时器和回调来解决。有些主板(PX4Linux)支持具有实时优先级的丰富 Posix 线程模型,这些模型被 ArduPilot 广泛地使用。


ArduPilot 中,有一些与线程有关的关键概念,你需要了解:

  • 定时器回调;
  • HAL专用线程;
  • 驱动程序专用线程;
  • ardupilot驱动程序与平台驱动程序的对比;
  • 平台专用线程和任务;
  • AP_Scheduler系统;
  • 信号量;
  • 无锁数据结构。

1 定时器回调

每个平台都在 AP_HAL 中提供一个 1kHz 的定时器。ArduPilot 中的任何代码都可以注册一个定时器函数,然后以 1kHz 的速度调用。所有注册的定时器函数都是按顺序调用的。这个非常原始的机制被使用,因为它具有极高的可移植性,而且非常有用。你通过调用 hal.scheduler->register_timer_process() 来注册一个定时器回调,像这样:

hal.scheduler->register_timer_process(AP_HAL_MEMBERPROC(&AP_Baro_MS5611::_update));

这个特殊的例子来自 MS5611 气压计驱动程序。AP_HAL_MEMBERPROC() 宏提供了一种将 C++ 成员函数封装为回调参数的方法(将对象上下文与函数指针捆绑起来)。

当一段代码希望某件事情以低于 1kHz 的速度发生时,那么它应该维护自己的 "last_called"变量,如果时间不够就立即返回。你可以使用 hal.scheduler->millis()hal.scheduler->micros() 函数来获取启动后的时间,以毫秒和微秒为单位来支持。

你现在应该去修改一个现有的示例概述(或创建一个新的),并添加一个定时器回调。让定时器增加一个计数器,然后在 loop() 函数中每秒钟打印一次计数器的值。修改你的函数,使它每25毫秒增加一次计数器。

2 HAL专用线程

在支持真实线程的平台上,该平台的 AP_HAL 将创建一些线程来支持基本操作。例如,在 Pixhawk 上,会创建以下 HAL 专用的线程:

  • UART 线程,用于读取和写入 UARTs(和 USB);
  • 定时器线程,支持上述的 1kHz 定时器功能;
  • IO 线程,支持对 microSD 卡、EEPROMFRAM 的写入。

查看每个 AP_HAL 实现中的 Scheduler.cpp,看看创建了哪些线程以及每个线程的实时优先级是多少。

如果你有一个 Pixhawk,那么你现在也应该设置一个调试控制台电缆,并将其连接到 nsh 控制台(serial5 端口)。连接波特率为 57600。当你连接好后,尝试使用 "ps"命令,你会得到类似这样的结果:

PID PRI SCHD TYPE NP STATE NAME0 0 FIFO TASK READY Idle Task()1 192 FIFO KTHREAD WAITSIG hpwork()2 50 FIFO KTHREAD WAITSIG lpwork()3 100 FIFO TASK RUNNING init()37 180 FIFO TASK WAITSEM AHRS_Test()38 181 FIFO PTHREAD WAITSEM <pthread>(20005400)39 60 FIFO PTHREAD READY <pthread>(20005400)40 59 FIFO PTHREAD WAITSEM <pthread>(20005400)10 240 FIFO TASK WAITSEM px4io()13 100 FIFO TASK WAITSEM fmuservo()30 240 FIFO TASK WAITSEM uavcan()

在这个例子中,你可以看到"AHRS_Test"线程,它正在运行来自 library/AP_AHRS/examples/AHRS_Test 的示例概述。你还可以看到定时器线程(优先级 181)、UART 线程(优先级 60)和 IO 线程(优先级 59)。

此外,你可以看到 px4iofmuservouavcanlpworkhpworkidle 任务。稍后会有更多关于这些的内容。

其他 AP_HAL 端口有更多或更少的线程,这取决于需要什么。

线程的一个常见用途是为驱动程序提供一种方法来调度缓慢的任务,而不中断主要的自动驾驶飞行代码。例如,AP_Terrain 库需要能够对 microSD 卡进行文件 IO(以存储和检索地形数据)。它的方法是这样调用函数 hal.scheduler->register_io_process()

hal.scheduler->register_io_process(AP_HAL_MEMBERPROC(&AP_Terrain::io_timer));

设置 AP_Terrain::io_timer 函数,使其定期被调用。这是在主板的 IO 线程内调用的,意味着它的实时优先级很低,适合于存储 IO 任务。重要的是,像这样的慢速 IO 任务不能在定时器线程中调用,因为它们会导致更重要的高速传感器数据的处理出现延迟。

3 驱动程序专用线程

也可以创建驱动程序的专用线程,以特定于一个驱动程序的方式支持异步处理。目前,你只能以与平台相关的方式创建驱动程序的专用线程,因此,仅当你的驱动程序只打算在一种类型的自动驾驶板上运行时,才适用。如果你想让它在多个 AP_HAL 目标上运行,那么你有两种选择:

  • 你可以使用 register_io_process()register_timer_process() 调度器调用来使用现有的定时器或 IO 线程;
  • 你可以添加一个新的 HAL 接口,为在多个 AP_HAL 目标上创建线程提供一种通用方法(请回馈补丁)。

Linux端口的ToneAlarm线程就是一个驱动程序专用线程的例子。参见AP_HAL_Linux/ToneAlarmDriver.cpp

4 ArduPilot驱动与平台驱动的对比

你可能会注意到 ArduPilot 中的一些重复的驱动程序。例如,我们在 libraries/AP_InertalSensor/AP_InertialSensor_MPU6000.cpp 中有一个 MPU6000 驱动程序,而在 PX4Firmware/src/drivers/mpu6000 中还有一个 MPU6000 的驱动程序。

这种重复的原因是,PX4 项目已经为 Pixhawk 主板上的硬件提供了一套经过良好测试的驱动程序,而且我们与 PX4 团队在开发和增强这些驱动程序方面保持着良好的合作关系。因此,当我们为 PX4 构建 ArduPilot 时,我们通过编写小型的 "垫片"驱动程序来利用 PX4 的驱动程序,这些驱动程序以标准的ArduPilot库接口呈现给PX4。如果你看一下libraries/AP_InertialSensor/AP_InertialSensor_PX4.cpp,你会看到一个小的 shim 驱动程序,它询问 PX4 这个板子上有哪些 IMU 驱动,并自动将所有这些驱动程序作为 ArduPilot AP_InertialSensor 库的一部分。

因此,如果我们在主板上有一个MPU6000,我们在非Pixhawk/NuttX平台上使用 AP_InertialSensor_MPU6000.cpp驱动程序,而在基于NuttX的平台上使用 AP_InertialSensor_PX4.cpp 驱动程序。

其他 AP_HAL 端口也可能发生相同类型的拆分。例如,我们可以将 Linux 内核驱动程序用于 Linux 主板上的某些传感器。对于其他传感器,我们使用通用的 AP_HAL I2CSPI 接口来使用ArduPilotin-tree” 驱动程序,该驱动程序可在各种主板上工作。

5 平台专用线程和任务

在一些平台上,启动过程将创建许多基础任务和线程。这些都是非常具体的平台,因此在本教程中,我将集中介绍基于 PX4 主板上使用的任务。

在上面的 "ps"输出中,我们看到一些任务和线程没有被 AP_HAL_PX4 调度器代码启动。具体来说,它们是:

  • 空闲任务 - 当没有其他任务需要运行时被调用;
  • init - 用于启动系统;
  • px4io -处理与 PX4IO 协处理器的通信;
  • hpwork - 处理基于PX4驱动程序的线程(主要是 I2C 驱动程序);
  • lpwork - 处理基于低优先级工作的线程(例如 IO);
  • fmuservo - 处理 FMU 上的辅助 PWM 输出的通讯;
  • uavcan - 处理 uavcanCANBUS 协议。

所有这些任务的启动都由 PX4 专用的 rc.APM 脚本控制( rc.APM script)。该脚本在 PX4 启动时运行,并负责检测我们使用的是哪种 PX4 主板,然后为该板加载正确的任务和驱动程序。它是一个 "nsh"脚本,类似于 bourne shell 脚本(尽管 nsh 要原始得多)。

作为一个练习,尝试编辑 rc.APM 脚本并添加一些睡眠和打印命令。然后上传一个新的固件,在主板启动的时候连接到调试控制台。你的打印命令应该在控制台显示出来。

探索 PX4 启动的另一个非常有用的方法是在插槽中没有 microSD 卡的情况下启动。在 rc.APM 之前运行的 rcS 脚本(rcS script)会检测是否插入了 microSD,如果没有,会在 USB 端口上给你一个最基本的 nsh 控制台。然后你可以自己在 USB 控制台手动运行 rc.APM 的所有步骤来学习它是如何工作的。

在没有 microSD 卡的情况下启动 Pixhawk 并连接到 USB 控制台后,尝试以下练习:

tone_alarm stop
uorb start
mpu6000 start
mpu6000 info
mpu6000 test
mount -t binfs /dev/null /bin
ls /bin
perf

试着练习其他的驱动程序。在 /bin 中看看有什么可用的。大多数这些命令的源代码在 PX4Firmware/src/drivers 中。浏览一下 mpu6000 驱动,以了解所涉及的内容。

鉴于我们正在讨论线程和任务的话题,对 PX4Firmware git 树中的线程进行简要描述是值得一提的。如果你在 mpu6000 驱动中查看,你看到如下一行:

hrt_call_every(&_call, 1000, _call_interval, (hrt_callout)&MPU6000::measure_trampoline, this);

这相当于 AP_HAL 中的 hal.scheduler->register_timer_process() 函数,但它是针对 PX4 的,也更灵活。它表示希望PX4HRT(高分辨率定时器)子系统每1000微秒调用 MPU6000::measure_trampoline 函数。

在操作非常快的驱动程序中,使用 hrt_call_every() 是驱动程序中常规事件的常用方法,比如 SPI 设备驱动。这些操作通常是在禁用中断的情况下运行的,最多只需要几十微秒的时间。

如果你将其与 hmc5883 驱动相比较,你会看到如下所示的行:

work_queue(HPWORK, &_work, (worker_t)&HMC5883::cycle_trampoline, this, 1);

使用一种替代机制来处理常规事件,这适用于较慢的设备,如 I2C 设备。这所做的是将 cycle_trampoline 函数添加到你在上面看到的 hpwork 线程中的一个工作队列中。在 HPWORK 角色中进行的调用应该在启用中断的情况下运行,可能需要几百微秒的时间。对于耗时超过这个时间的任务,应该使用 LPWORK 工作队列,它在低优先级的 lpwork 线程中运行。

6 AP_Scheduler系统

ArduPilot 线程和任务的下一个要了解的方面是 AP_Scheduler 系统。AP_Scheduler 库用于在主飞行器线程中划分时间,同时提供一些简单的机制来控制每个操作(在 AP_Scheduler 中称为 "任务")使用多少时间。

它的工作方式是,每个飞行器实现的 loop() 函数包含一些代码来做这个工作:

  • 等待一个新的 IMU 样本的到来;
  • 在每个 IMU 样本之间调用一组任务。

它是一个表驱动的调度程序,每种飞行器类型都有一个 AP_Scheduler::Task 表。要了解它是如何工作的,请查看  AP_Scheduler/examples/Scheduler_test.cpp 概述。

如果查看该文件,你将看到一个小表格,其中包含一组3个调度任务。与每个任务相关的是两个数字。该表如下所示:

static const AP_Scheduler::Task scheduler_tasks[] PROGMEM = {{ ins_update, 1, 1000 },{ one_hz_print, 50, 1000 },{ five_second_call, 250, 1800 },
};

每个函数名称后面的第一个数字是调用频率,单位由 ins.init() 调用控制。在这个例子中,ins.init() 使用 RATE_50HZ,所以每个调度步骤是 20ms。这意味着每20毫秒进行一次 ins_update() 调用,每50次(即每秒一次)调用一次 one_hz_print() 函数,每250次(即每5秒一次)调用一次 five_second_call()。(这里的调用频率计算我觉得有问题

  • 第二个数字是该函数预计花费的最大时间。这是用来避免调用的,除非在这个调度运行中还有足够的时间来运行这个函数。当 scheduler.run() 被调用时,它被传递给运行任务的可用时间总量(以微秒为单位),如果是这个任务最坏情况下的时间,意味着它在时间用完之前无法适应,那么它将不会被调用。

另一个需要仔细观察的点是 ins.wait_for_sample() 调用。这是驱动 ArduPilot 调度的 "节拍器"。它阻止了飞行器主线程的执行,直到有新的 IMU 样本可用。IMU 采样之间的时间是由 ins.init() 调用的参数控制的。

请注意,AP_Scheduler 表中的任务必须具有以下属性:

  • 它们不应该阻塞(除了 ins.update() 调用);
  • 它们不应该在飞行时调用睡眠函数(一个自动驾驶仪,就像一个真正的飞行员,不应该在飞行时睡眠);
  • 它们应该有可预测的最坏情况的时间。

你现在应该去修改 Scheduler_test 的例子,加入你自己的任务来运行。尝试添加做以下工作的任务:

  • 读取气压计;
  • 读取磁罗盘;
  • 读取 GPS
  • 更新 AHRS 并打印横滚/俯仰数据。

查看你在本教程前面使用过的每个库的示例概述,了解如何使用每个传感器库。

7 信号量

当你有多个线程(或定时器回调)时,你需要确保两个执行逻辑线程共享的数据结构以防止损坏的方式更新。在 ArduPilot 中,有3种主要的方式可以做到这一点--信号灯、无锁数据结构和 PX4 ORB

AP_HAL 信号量只是对特定平台上可用的信号量系统的包装,并提供一个简单的互斥机制。例如,I2C 驱动可以请求 I2C 总线信号,以确保在同一时间内只使用一个 I2C 设备。

查看 libraries/AP_Compass/AP_Compass_HMC5843.cpp 中的 hmc5843 驱动程序,并查找 get_semaphore() 调用。查看所有使用它的地方,看看是否可以弄清为什么需要它。

8 无锁数据结构

ArduPilot 的代码还包含了使用无锁数据结构的例子,以避免对一个信号的请求。这比信号量要高效得多。

ArduPilot 中无锁数据结构的两个例子是:

  • libraries/AP_InertialSensor/AP_InertialSensor_MPU9250.cpp 中的 _shared_data 结构;
  • 在许多地方使用的环形缓冲区。一个很好的例子是 libraries/DataFlash/DataFlash_File.cpp

查看这两个例子,并向你自己证明它们对于并发访问是安全的。对于 DataFlash_File 来说,查看 _writebuf_head_writebuf_tail 变量的使用。

如果能创建一个通用的环形缓冲器类,可以代替 ArduPilot 中多个地方的独立环形缓冲器实现,那就更好了。如果你想做出贡献,请提出拉取请求!!

9 PX4 ORB

这类机制的另一个例子是 PX4 ORBORB(对象请求代理)是一种使用在多线程环境中安全的发布/订阅模型,将数据从系统的一个部分提供给另一个部分的方式(例如,设备驱动程序->飞行器代码)。

ORB 提供了一个很好的机制来声明,将以这种方式共享的结构(全部定义在 PX4Firmware/src/modules/uORB/)。然后,代码可以将数据 "发布"到这些主题之一,而其他代码则可以选择这些主题。

一个例子是发布执行器的值,这样 uavcan 电调就可以在 Pixhawk 上使用。查看 AP_HAL_PX4/RCOutput.cpp 中的 _publish_actuators() 函数。你会看到,它发布了一个 "actuator_direct"主题,其中包含了每个 ESC 所需的速度。uavcan 代码会观察PX4Firmware/src/modules/uavcan/uavcan_main.cpp 中该主题的变化,并将新值输出给 uavcan 电调。

PX4 驱动程序进行通信的另外两种常见机制是:

  • ioctl calls (see the examples in AP_HAL_PX4/RCOutput.cpp)
  • /dev/xxx read/write calls (see _timer_tick in AP_HAL_PX4/RCOutput.cpp)

如果你不确定新代码应该使用哪种机制,请在ArduPilot开发者讨论区(ArduPilot Developers Discord)与 ardupilot 开发团队交流。


http://www.ppmy.cn/news/789157.html

相关文章

CPU核心数目 与 多线程

一直以来有这样的疑惑&#xff0c;在现如今多核多线程的电脑处理器之下&#xff0c;一个进程中的几个线程是 怎么运行的呢&#xff1f;&#xff08;是经系统和JVM分配少量的资源 最后轮流切换 时间调度&#xff1f;还是这几个线程分配到不同的核上同时运行&#xff1f;&#xf…

C++11(六)线程库

C11线程库 在C11之前&#xff0c;涉及到多线程问题&#xff0c;都是和平台相关的&#xff0c;比如windows和Linux下各有自己的接口&#xff0c;这使得代码的可移植性比较差。C11中最重要的特性就是对线程进行支持了&#xff0c;使得C在并行编程时不需要依赖第三方库&#xff0…

CPU个数,核心数,线程数

我们在买电脑的时候&#xff0c;经常会看cpu的参数&#xff0c;对cpu的描述有这几种&#xff1a;“双核”、“双核四线程”、“四核”、“四核四线程”、“四核八线程”……。 我们接触的电脑基本上都只有一个cup。cpu的个数很容易得到&#xff0c;螺丝刀拆开你的电脑数一下就…

CPU个数、内核数、线程数

我们在买电脑的时候&#xff0c;经常会看cpu的参数&#xff0c;对cpu的描述有这几种&#xff1a;“双核”、“双核四线程”、“四核”、“四核四线程”、“四核八线程” cpu个数是指物理上安装了几个cpu&#xff0c;一般的个人电脑是安装了1个cpu cpu内核数是指物理上&#xf…

TL-ER2260T安装dnsmasq并设置域名劫持

TL-ER2260T安装dnsmasq并设置域名劫持 第一步&#xff0c;安装entware 安装entwarecd /tmp wget http://bin.entware.net/armv7sf-k3.2/installer/generic.sh sh generic.sh将entware加入环境变量修改环境变量&#xff1a;vi /etc/profile 修改行4 export PATH/opt/bin:/opt/…

Tomcat 线程池

目录 概述 tomcat线程池工作原理 关键源码 Connector 配置 Executor 线程配置 tomcat核心组件&#xff08;题外&#xff09; 概述 Tomcat 是一个流行的 Java Web 服务器&#xff0c;它使用线程池来处理客户端请求。线程池是一组预先创建的线程&#xff0c;用于执行并发任…

sci-hub油猴插件失效后,网址修改

打开油猴找到sci-hub插件&#xff0c;点击右边–编辑按钮&#xff1b; 进入插件界面&#xff0c;CtrF&#xff1b;输入搜索内容《sci-hub》&#xff0c;在736行更改sci-hub后缀 点击&#xff0c;文件-保存&#xff0c;即本地修改插件成功。

Tampermonkey 油猴插件使用

下载后为Tampermonkey.crx 将后缀改为rar,并解压为文件夹, 然后在chrome浏览器右上角点开竖三点 —> 更多工具 —> 扩展程序 亦或是在chrome浏览器输入框输入chrome://extensions/进入扩展程序 在扩展程序页面&#xff0c;首先打开开发者模式, 接着点击加载已解压的扩…

油猴插件安装和使用

一.安装油猴 进入油猴的官网Greasy Fork&#xff0c;下载你对应浏览器的版本例如我谷歌浏览器下载的是这个Tampermonkey.crx&#xff1b; 下载好后&#xff0c;谷歌浏览器中&#xff0c;打开更多工具——扩展程序&#xff0c;进入拓展程序后&#xff0c;把下载的油猴插件拖到拓…

油猴编写-使用Bootstrap

// UserScript // name bootstrapUI // namespace http://tampermonkey.net/ // version 0.1 // description 实现bootstrap的弹出框 // author soledadchao // match https://wenku.baidu.com/* // require https://cdn.bootcdn.net/ajax/…

关于谷歌浏览器安装油猴插件失败的解决方法

今天拿到了一台二手电脑&#xff0c;刷完之后开始安装需要的程序&#xff0c;在给谷歌浏览器安装油猴插件的时候出现了很多错误&#xff0c;现在一一道来&#xff0c;希望对大家有所帮助。 一、不知道如何找油猴插件 上某度搜了一下&#xff0c;都是exe执行文件&#xff0c;运…

如何简单有效的同步油猴插件

如何同步油猴插件 我看了两个方法&#xff1a;但是我都用不了&#xff0c;不过我找到了一种更方便的方法 先说另外两种方法 两种方法都是利用的“设置”里的“同步脚本”&#xff08;需要在最上面把配置模式由新手改为初学者&#xff09;&#xff0c;一种是默认的“浏览器同步”…

火狐版Idm配合油猴使用教程

一、下载并安装绿色版idm 链接&#xff1a;https://pan.baidu.com/s/1Vw7L_aCORYYsp1GpS4ttmA 提取码&#xff1a;777a 双击文件夹中的绿化进行安装&#xff08;等待安装成功提示&#xff0c;期间不要乱动电脑&#xff09;&#xff0c;后期卸载的话就点卸载 安装成功后启动…

油猴的使用

油猴 tampermonkey 【不同的人叫法也不一样】 油猴 &#xff1f;&#xff1f; 好多人会问&#xff0c; 这个是什么东西&#xff1f;&#xff1f; 这个就由我来给大家介绍一下吧 我们看油猴官网介绍 https://www.tampermonkey.net/ Tampermonkey 是一款免费的浏览器扩展和最…

Edge浏览器安装油猴插件以及好用的插件推荐

目录 一、介绍 二、下载步骤 一、介绍 油猴插件是非常好用的&#xff0c;可以帮助我们解放双手眼睛这些。帮助学习&#xff0c;通过这些 二、下载步骤 1.打开edge浏览器&#xff0c;右上角点击三小点&#xff0c;选择扩展 2.点击这个 3.点击下载油猴插件 下载失败的看&a…

油猴插件安装

下载并解压插件 下载地址&#xff1a;https://wwt.lanzouy.com/iVGxT0dhswbc 下载浏览器&#xff08;推荐Edge或者谷歌&#xff09; 下载地址https://www.google.cn/chrome/index.html 安装插件

github1s 油猴插件

github1s 是一个非常有趣的项目&#xff0c;它可以让你在 1 秒内&#xff08;俗称 1s&#xff09;通过在线版本的 VS Code 来打开 GitHub 上的代码&#xff0c;只需要在对应项目的 URL 后面加上 1s 即可。 这是一个很有创意、很赞的项目&#xff0c;只需要在对应的 GitHub 项目…

使用油猴下载文库

简介 工作中经常需要下载资料&#xff0c;大多数情况下&#xff0c;我们搜索到的资料会在某度文库中&#xff0c;激动的准备存在本地方便以后观摩&#xff0c;又因为页面下方的VIP下载&#xff0c;露出尴尬的笑容。这里介绍两种方式&#xff0c;一种省钱省事&#xff0c;一种免…

安装油猴插件详解

方法1&#xff1a; 进入油猴官网&#xff1a; https://www.tampermonkey.net/ 然后根据自己浏览器类型安装相对应适合的稳定版油猴插件 其中谷歌浏览器油猴插件可在以下链接中获取&#xff0c;其他自行在官网下载&#xff1b; 链接: https://pan.baidu.com/s/1FcD9Er9CFRnFE…

油猴插件免费下载

安利一个黑科技&#xff0c;名叫"油猴子"。点击下载 Tampermonkey中文名俗称油猴&#xff0c;是一款免费的浏览器插件&#xff0c;目前最为流行的用户脚本管理器&#xff0c;用户可以通过油猴添加和使用脚本&#xff0c;而脚本是一种可以修改网页JavaScript的程序。…