本篇主要内容1. rust cargo error1.1 cargo shows error1.2 cargo stuck in waiting for cargo metadata or cargo check1.3 A Chinese problem, we may need mirror accelerate2. defmt3. Why Embassy-Executor’s test can run with src in no_std?4. rust conditional compilation4.1 how to use [env] section of .cargo/config.toml 4.2 the usage of cfg & cfg_attr5.covariant6. fnonce vs fnmut vs fn rust7. how can we transfer from a function pointer to a closure8. const fn9. feature edition2021 is required10. liveshare give full access11. can’t use lazy_static with no_std12. the layout of rust13. how to design the OSTCBTbl14. the Function of UnsafeCell15. the import of Future16. the Executor17. String & str18. Global Static Var19. the Function of RefCell20. the Order of Atomic21. steps to adapt to embassy22. Design of the testing part22.1 I choose the defmt-test:22.2 And learn from the template to know how to use it:23. what does flip-link do ?24. We Need TaskPoolRef25. About Arena26. Sync and Send trait27. MaybeUninit28. Deref and DerefMut29. 关于refmutRefCellRefMut30. TokenStream31. quote!32. 过程宏33. OUT_DIR环境变量
本篇主要内容
本篇的主要内容为,在开发过程中遇到的一些代码上的问题,包括Rust的语言特性,以及对Embassy和uCOSII更深层次的一点理解和一些变动
1. rust cargo error
1.1 cargo shows error
rust analyzer runs at the same time you run a
cargo
command after updating the toolchain file. If it's not that please open a new rustup issueerror: the 'cargo' binary, normally provided by the 'cargo' component, is not applicable to the '1.78.0-x86_64-unknown-linux-gnu' toolchain
Updated Jun 13, 2024
The workaround is to uninstall the toolchain and reinstall it, usually stopping rust analyzer or similar while you're doing that
1.2 cargo stuck in waiting for cargo metadata or cargo check
Stuck at 'rust_analyzer: -32801: waiting for cargo metadata or cargo check'
Updated Feb 21, 2024
1.3 A Chinese problem, we may need mirror accelerate
and we set a proxy for git
2. defmt
as the code show below, the defmt and log feature can not be active at the same time.
3. Why Embassy-Executor’s test can run with src in no_std?
4. rust conditional compilation
4.1 how to use [env]
section of .cargo/config.toml
4.2 the usage of cfg & cfg_attr
5.covariant
6. fnonce vs fnmut vs fn rust
7. how can we transfer from a function pointer to a closure
8. const fn
The const fn can be called at compiling time so there are some restrictions of it(Just like cpp?).
9. feature edition2021
is required
Caused by:
feature
edition2021
is requiredconsider adding
cargo-features = ["edition2021"]
to the manifesthowever, my problem is that my toolchain set the rust so old
10. liveshare give full access
11. can’t use lazy_static with no_std
#![no_std] support
Updated May 30, 2016
12. the layout
of rust
13. how to design the OSTCBTbl
To make space allocation certain, we can not design the
OSTCBTbl
as uC/OS. In uC/OS the TCBs’ size is confirmed and same. But in our Rust-uC/OS, because we use future, the size of TaskStorage
is not the same, and we can not confirmed it before we know how the async func is defined.We can find that the source of the problem is the size of futures of every
TaskStorage
is different, but we want to make it the same to avoid some tough issues related to memory.So I thought about to set the Size of
TaskStorage
as the maximum of all the objects of TaskStorage
. But because we can not calculate the maximum when we init the TCB Table, so we abandoned the plan.There is another way to solve this problem: using pointer. But unavoidably there will be some raw pointers and unsafe code block. But for now, we will take this approach. In the future, we can wrap the raw pointer by some types to make our code safe.
14. the Function of UnsafeCell
Quote from copilot:
内部可变性:允许在外部看似不可变的情况下,修改UnsafeCell<T>
内部的值。这对于实现像RefCell<T>
和Mutex<T>
这样的类型非常重要,这些类型提供了在运行时而不是编译时检查借用规则的能力。 绕过借用规则:正常情况下,Rust不允许在存在不可变引用的同时创建可变引用,因为这可能导致数据竞争和不一致的状态。然而,通过使用UnsafeCell<T>
,开发者可以手动管理这种访问,允许在不违反所有权模型的前提下,进行更灵活的内存访问。 安全性责任:使用UnsafeCell<T>
意味着绕过了Rust编译器的安全检查,将安全性的责任转移到了开发者身上。开发者需要确保使用UnsafeCell<T>
时不会引入数据竞争或其他安全问题。 底层构建块:UnsafeCell<T>
通常被用作构建更高级别抽象的底层构建块,如同步原语(Mutex
、RwLock
等)或其他需要内部可变性的类型。直接使用UnsafeCell<T>
是不常见的,除非你正在实现这些高级抽象。
So, only when the inner var is immutable, but we need it to be mutable temporary will we wrap the var in
UnsafeCell
. It is common to wrap static var or var with static life-cycles in UnsafeCell
15. the import of Future
In the definition of TCB, we need to import the future of the task. But for Future is a trait, so we only import it as a trait bound:
But we want to be uncoupled so that in other crate, only OS_TCB_REF can be visited. So we wrap the TCB as OS_TCB_REF:
But after we import the Future, we need to add a trait bound to OS_TCB_REF too:
Besides, everything about the OS_TCB_REF needs to be added a trait bound. As a result, there will be too much var having a static life time, which is not what we want to see.
In uC,TCB can have a static life time in order to ensure the certainty of space allocation. But we don’t want to make the REF also having the static life time, because only the running task is meaningful to us.
In Embassy, the TCB is separated from the future:
TaskRef
still point to TaskHeader
, which has nothing to do with Future.:In this way, there is no need to add a trait bound to
TaskRef
. So it can be recycled freely. When we want to get the TaskStorage
, we can use type casting, for TaskHeader
is TaskStorage
’s first member.So we will refer to the realization of Embassy. So in OS_init, we should alloc an array of
OS_TASK_STORAGE
, instead of OS_TCB
.16. the Executor
In the Rust-uC we imagine, there is no concept of thread. So there just need an executor, which I will make it lazy_static.
Besides, there is also no need to add a member to TCB to store the executor, which is different to Embassy.
17. String & str
At the beginning, we can only use str, for it is a slice, which doesn’t require a heap allocator.
But there is still one problem: the size of str can be confirmed when compiling. So we have to impl a heap allocator. Otherwise, we can only use unsafe code.
After we impl a heap allocator, we can use the String type, which is Sized in Rust.
18. Global Static Var
In the first version of our uC/OS, we just use
pub
and static
to define global var. It is so annoyed because it makes our code unsafe. In the second version of Rust-uC, we try to refer to the realization of Embassy and rCore. In Embassy, the
RtcDriver
is static and we need to change its member in the static life time. It’s definition looks like:Here, Embassy use
Mutex
and AtomicU32
for the static structure’s member to ensure the thread safety. The Mutex
used here is defined in embassy::sync
and AtomicU32
is in the core::sync::atomic
. Actually, there is also a Mutex
in critical-section
. It expose a safe interface to us. Because there is only one core on our board, so we can ensure that if we acquire a critical section, the interrupt will be disable and task will not switch. So we will use
AtomicU32
to keep var of primary data type thread safe, and use critical-section::Mutex<RefCell<T>>
to keep var of other type safe.Besides, by using
Atomic
, we can change the global var without critical section:The period’s type is
AtomicU32
and it is one of the members of the RtcDriver. But in the example pick out from Embassy above, it can be get and set without a critical section.Before, our code looks like this:
Though we can ensure there will only one thread enter the critical section, there is still a huge unsafe block.
But now our code may looks like:
Good, there is no unsafe block.
we use the type:
Mutex<RefCell<i32>>
to define the static var because RefCell
is not thread safe.19. the Function of RefCell
In the last part, there is a type:
RefCell
. It provide a mechanism for borrowing checks at run time. We need this because if we do static check on our borrowing of the global var, there are many places that get mutable references to global variables, which will make our code can not pass compiling. But in an OS, this situation is unavoidable, so we need RefCell
to do borrowing checks at run time.20. the Order of Atomic
There is a para we should pass to func
load
and store
when we need to read or write the Atomic var. The para is order. Its type is Ordering
, which is an enum.As the comment on
Ordering
in Rust lib, Ordering
is used to:Memory orderings specify the way atomic operations synchronize memory.
There are five possible values of
Ordering
:- Relaxed
No ordering constraints, only atomic operations. https://en.cppreference.com/w/cpp/atomic/memory_order#Relaxed_ordering
- Release
When coupled with a store, all previous operations become ordered before any load of this value with [`Acquire`] (or stronger) ordering.In particular, all previous writes become visible to all threads that perform an [`Acquire`] (or stronger) load of this value. This ordering is only applicable for operations that can perform a store https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
- Acquire
When coupled with a load, if the loaded value was written by a store operation with [`Release`] (or stronger) ordering, then all subsequent operations become ordered after that store. In particular, all subsequent loads will see data written before the store. https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
- AcqRel
Has the effects of both [`Acquire`] and [`Release`] together: For loads it uses [`Acquire`] ordering. For stores it uses the [`Release`] ordering. https://en.cppreference.com/w/cpp/atomic/memory_order#Release-Acquire_ordering
- SeqCst
Like [`Acquire`]/[`Release`]/[`AcqRel`] (for load, store, and load-with-store operations, respectively) with the additional guarantee that all threads see all sequentially consistent operations in the same order. https://en.cppreference.com/w/cpp/atomic/memory_order#Sequentially-consistent_ordering
In the comment of the possible values, we can know that we can build a Memory Barrier by using
Release
coupled with store
and Acquire
coupled with load
(or just use AcqRel
). In this way, we can ensure the synchronization when we read or write an Atomic var, just like we set a mutex.By
Release
, the operations before store
will be finished before the store
operation, which can ensure that the store
op is effective and the stored value is visible to other threads. By Acquire
, the operations after load
will not be finished before the load
operation, which can ensure that the load
op can load the val we need.There is an example:
21. steps to adapt to embassy
embassy 改变设计想法22. Design of the testing part
22.1 I choose the defmt-test:
22.2 And learn from the template to know how to use it:
app-template
knurling-rs • Updated Nov 29, 2024
23. what does flip-link
do ?
flip-link
knurling-rs • Updated Nov 29, 2024
24. We Need TaskPoolRef
Just as the comments in Embassy:
type-erased `&'static mut TaskPool<F, N>`. Needed because statics can't have generics.
Because the TaskPool is static in both our uC and Embassy, so it is important to use
TaskPoolRef
to define a TaskPool
in a static life timethe
Arena
is also known as OSTCBTbl
in uC/OSplus: I add note of static usage:https://doc.rust-lang.org/reference/items/static-items.html
and the embassy use this to init TaskPool static var once in the task macro design:
from file embassy-executor-macros/src/macros:113
25. About Arena
In the last part, we know that we need
TaskPoolRef
because statics can't have generics. But a new problem arose: The TaskPoolRef
is static but TaskPool
is not. This will cause error because a ref’s life time is longer than the data it points. So we should do something to make TaskPool
static too.In Embassy,
TaskPool
become static with the help of Arena
. It defined as:For now, we just focus on
buf
. It is a UnsafeCell<MaybeUninit<[u8; N]>>
. About MaybeUninit
, we will discuss it below. Now we just need to know that MaybeUninit
’s memory layout is the same to [u8; N]
. So if we claim an Arena
with static life time, its member will be static too. We get what we want.“N” is defined as the number of bytes of the TaskPool.
Maybe you will ask that if we use
Arena
, why do we still need the TaskPool
and TaskPoolRef
? As shown above and in Embassy, the Arena
just used to alloc a piece of memory but TaskPool
or TaskPoolRef
is used to complete the relevant parts of the task and scheduling. By this, the coupling degree is reduced. Of course we can define Arena
as:In this way, the the coupling degree increases, and there is anther problem: if we use
TaskStorage
directly, we need genericity, which can’t be added to the static Arena
.26. Sync and Send trait
- A type is Send if it is safe to send it to another thread.
- A type is Sync if it is safe to share between threads (T is Sync if and only if
&T
is Send).
27. MaybeUninit
This type is used to define vars which are not init. Its memory layout is the same to the genericity var’s memory layout. MaybeUninit is defined as:
For more information about MaybeUninit, read: https://learnku.com/articles/65520
28. Deref and DerefMut
The usage of these trait is:
Maybe you will be confused with the
Target
in DerefMut
. Once you know how DerefMut
defined, you won't be confused:29. 关于refmut
RefCell
RefCell
is a type that provides interior mutability. It allows you to borrow its contents either mutably or immutably, but these borrows are checked at runtime. If you try to violate Rust's borrowing rules (e.g., having multiple mutable borrows or a mutable borrow while there are immutable borrows), the program will panic.RefMut
RefMut
is a smart pointer type that RefCell
returns when you borrow its contents mutably. It implements Deref
and DerefMut
, so you can use it like a regular mutable reference.30. TokenStream
31. quote!
32. 过程宏
学习过程宏的lab(感觉可以考虑开学出成题目):
proc-macro-workshop
dtolnay • Updated Nov 28, 2024
感觉还不错的中文博客:
我自己的lab笔记:
很有用的宏debug工具,把宏展开的样子写出来:
cargo-expand
dtolnay • Updated Nov 29, 2024