type
status
date
slug
summary
tags
category
icon
password

本篇主要内容

本篇的主要内容为,在开发过程中遇到的一些代码上的问题,包括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 issue
error: 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

notion image

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 required
consider adding cargo-features = ["edition2021"] to the manifest
however, 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>通常被用作构建更高级别抽象的底层构建块,如同步原语(MutexRwLock等)或其他需要内部可变性的类型。直接使用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 :
  • 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
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 改变设计想法 (1)

22. Design of the testing part

notion image

22.1 I choose the defmt-test:

22.2 And learn from the template to know how to use it:

app-template
knurling-rsUpdated Oct 7, 2024

23. what does flip-link do ?

flip-link
knurling-rsUpdated Oct 8, 2024
notion image
notion image

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 TaskPoolin a static life time
💡
the Arena is also known as OSTCBTbl in uC/OS
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
dtolnayUpdated Oct 17, 2024
感觉还不错的中文博客:
我自己的lab笔记:
很有用的宏debug工具,把宏展开的样子写出来:
cargo-expand
dtolnayUpdated Oct 17, 2024

33. OUT_DIR环境变量

 
Embassy_LearnNVMe
Loading...
Noah
Noah
永远年轻,永远热泪盈眶
公告
❗❗复习笔记问题❗❗
由于兼容性问题
导入md文件可能导致了一些格式错误
🌹如发现格式错误,请联系我~🌹
🌹如博客内容有误也欢迎指出~🌹