-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
tock 源码相关 #840
Comments
kernel包括功能
systick
|
tockloader
问答
tockloader是如何将bin文件写进硬件的?
程序进程的数据结构:
全局静态区,文字常量区,程序代码区是从内存地址分配的角度来描述的。
|
调试
{
"version": "0.2.0",
"configurations": [
{
"name": "nRF52-DK",
"type": "gdb",
"request": "attach",
"executable": "${workspaceRoot}/boards/nordic/nrf52dk/target/thumbv7em-none-eabi/release/nrf52dk",
"target": "localhost:2331",
"remote": true,
"cwd": "${workspaceRoot}",
"gdbpath": "gdb-multiarch",
"autorun": [
"monitor reset",
"monitor speed auto",
"b reset_handler"
]
}
]
} |
改bug跟踪
总结
// has_expired函数
fn has_expired(alarm: u32, now: u32, prev: u32) -> bool {
now.wrapping_sub(prev) >= alarm.wrapping_sub(prev)
if (now as i32).wrapping_sub(prev as i32) < 0
|| (alarm as i32).wrapping_sub(prev as i32) < 0
{
panic!("now={}, alarm={}, prev={}", now, alarm, prev);
}
}
问题翻译这个计时器的问题跟这些最新的issue的是一样的。 在virtual_alarm capsule中,有很多针对某个参考的相互比较时间戳的检查(比如“now”) 第一个检查,如果新的时钟设置的到期时间戳when在当前已设置时钟的到期时间戳之前(这种情况时钟应该更新到期时间戳到when) let cur_alarm = self.mux.alarm.get_alarm();
let now = self.now();
if cur_alarm.wrapping_sub(now) > when.wrapping_sub(now) {
self.mux.prev.set(self.mux.alarm.now());
self.mux.alarm.set_alarm(when);
} 第二个检查,如果一个alarm跟now相比已经到期了(alarm是设定值,now是时钟动态的当前值,prev是前一个设定值,when是下一个设定值) fn has_expired(alarm: u32, now: u32, prev: u32) -> bool {
now.wrapping_sub(prev) >= alarm.wrapping_sub(prev)
} 我不确定wrapping_sub算法的逻辑含义是什么,但是这似乎在保证 然而,这些假设在现在的代码种是不会出现的,不管通过插入一些紧急语句却很容易生成示例。 第一种情况,下面的代码很容易让我的app出现panic if (cur_alarm as i32).wrapping_sub(now as i32) < 0
|| (when as i32).wrapping_sub(now as i32) < 0
{
panic!("cur_alarm={}, when={}, now={}", cur_alarm, when, now);
}
第二种情况发生panics的频率要少得多,但是我仍然可以在某个时候触发它(您可以看到systicks的数字要大得多)。 if (now as i32).wrapping_sub(prev as i32) < 0
|| (alarm as i32).wrapping_sub(prev as i32) < 0
{
panic!("now={}, alarm={}, prev={}", now, alarm, prev);
}
在我观察到的两种panic中,参考点都在其他两者之间,从而使比较“翻转”。 例如,在第二种情况下, 请注意,alarm capsule中有相同的功能。 fn has_expired(alarm: u32, now: u32, prev: u32) -> bool {
now.wrapping_sub(prev) >= alarm.wrapping_sub(prev)
} 无论如何,我不确定逻辑到底应该是什么。 看来在i32格式上进行比较会更准确。 virtual_alarm的多路复用器还可以更好地跟踪过去是否触发了哪些alarm或“只是发生了”(即发生在过去的2 ^ 31个ticks中?),这与#1496和#1499有关。 或确保上一个alarm确实早于任何其他alarm(与第二次panic不同)。 我还想知道“参考点”是否在此比较中带来了任何价值,而不是在i32上进行 fn has_expired(alarm: u32, now: u32) -> bool {
(now as i32).wrapping_sub(alarm as i32) >= 0
} 最终,ticks会回绕,但是在这些比较中,我认为应该将时间戳假定为“足够接近”(这样i32会更准确)。 当然,如果您按2 ^ 31个ticks的顺序等待,那么所有关于比较逻辑正确性的押注都会被取消。 我还认为,更好的输入数据结构会有所帮助,例如,使用“ Timestamp”和“ Duration”类型代替无处不在的u32类型。 无论如何,似乎在这里非常需要更多的单元测试(即#1512)来模拟alarm muxer 在各种情况下的行为。 关于此问题的一些更新:通过运行
当转换为十六进制时, 在那种情况下,我们得到 1496 修复virtual_uart 种的transmit_buffer 函数在异步回调种的bugtock/capsules/src/virtual_uart.rs self.mux.do_next_op(); 有传输请求时调用do_op。 如果没有待处理的请求,则可以立即处理。 如果失败,则发出带有错误的callback_done回调: client.transmitted_buffer(rbuf.unwrap(), 0, rcode); 我们已经同意,出于竞争条件/数据完整性的原因,不应在调用期间发生回调。 如果立即处理了对虚拟UART的发送调用且失败了,则将同步触发回调。 相反,这应该使用延迟调用来异步发出回调。 1499 添加alarm的测试此拉取请求增加了对schedule timer的支持,其中提供的systick counter实际上位于过去。 这对于将timer设置为很短的时间很有用。 如果在一个应用程序中同时调度计时器,就会发生这种情况,想象两个LED同时以500和250ms的间隔闪烁。 一个延迟将在第二个停止后开始,并且会任意缩短。 此PR通过firing alarm(如果已结束)来解决此问题。 1651 VirtualMuxAlarm 错误使用VirtualMuxAlarm设置alarm时,参考点(self.mux.prev)与alarm一起设置。 if enabled > 0 {
let cur_alarm = self.mux.alarm.get_alarm();
let now = self.now();
if cur_alarm.wrapping_sub(now) > when.wrapping_sub(now) {
self.mux.prev.set(self.mux.alarm.now());
self.mux.alarm.set_alarm(when);
}
} else {
self.mux.prev.set(self.mux.alarm.now());
self.mux.alarm.set_alarm(when);
} 然后,该上一个参考点用于确定多路复用的alarms中的alarm是否已过期。 .filter(|cur| cur.armed.get() && has_expired(cur.when.get(), now, prev)) 问题
在这种情况下,由于新的prev就在alarm1之后,因此下次调用 这在Nordic上的Segger RTT调试中尤其明显,该调试器将计时器设置为将来非常接近(100us)。 到其他代码运行时,这可能已经过去了。 let interval = (100 as u32) * <A::Frequency>::frequency() / 1000000; 但是使用virtual alarms的所有capsule都会受到影响。 可观察到的结果是alarm客户端(在本例中为Segger RTT)永远等待(对于Segger RTT,内核调试和控制台似乎冻结)。 解决方案 delay_ms (1) blocks the process (and maybe the whole alarm system) on stm boards问答rtc时钟是如何设置时间的? alarm的CAPTURE/COMPARE 模式是什么?
VirtualMuxAlarm是什么?
self.now和alarm.now有何区别? cur_alarm,now,when,prev分别是什么?
Timer和Count两种模式有何区别?
既然
系统的所有时钟都是在MuxAlarm下进行管理的?
AlarmDriver跟VirtualMuxAlarm有什么关系?有什么区别?
每次订阅一次就创建一个virtualAlarm?
app_alarm是什么?
一个app只能setTimeout一次?
可以通过创建多个timeDriver实现多个setTimeout?
寄存器
|
进程总结进程变量问答tbf中,创建进程的核心参数是什么?
|
URAT 相关问答各个芯片不同的针脚定义在哪里?
寄存器有什么用? 如何发送一大波数据?如何分批发送
impl Console {
pub fn write<S: AsRef<[u8]>>(&mut self, text: S) -> TockResult<()> {
let mut not_written_yet = text.as_ref();
while !not_written_yet.is_empty() {
let num_bytes_to_print = self.allow_buffer.len().min(not_written_yet.len());
self.allow_buffer[..num_bytes_to_print]
.copy_from_slice(¬_written_yet[..num_bytes_to_print]);
self.flush(num_bytes_to_print)?;
not_written_yet = ¬_written_yet[num_bytes_to_print..];
}
Ok(())
}
} 发送流程是怎样的?
fn flush(&mut self, num_bytes_to_print: usize) -> TockResult<()> {
let shared_memory = syscalls::allow(
DRIVER_NUMBER,
allow_nr::SHARE_BUFFER,
&mut self.allow_buffer[..num_bytes_to_print],
)?;
let is_written = Cell::new(false);
let mut is_written_alarm = || is_written.set(true);
// 设置发送完的回调函数
let subscription = syscalls::subscribe::<Identity0Consumer, _>(
DRIVER_NUMBER,
subscribe_nr::SET_ALARM,
&mut is_written_alarm,
)?;
// 触发发送函数
syscalls::command(DRIVER_NUMBER, command_nr::WRITE, num_bytes_to_print, 0)?;
unsafe { executor::block_on(futures::wait_until(|| is_written.get())) };
mem::drop(subscription);
mem::drop(shared_memory);
Ok(())
} write_buffer和tx_buffer有什么关系?
fn send_continue(&self, app_id: AppId, app: &mut App) -> Result<bool, ReturnCode> {
if app.write_remaining > 0 {
app.write_buffer
.take()
.map_or(Err(ReturnCode::ERESERVE), |slice| {
// 这里slice就会转化为tx_buffer
self.send(app_id, app, slice);
Ok(true)
})
} else {
Ok(false)
}
} console是如何初始化的?
release |
kernel 机会查找关键文件
|
安装到swerv
问答需要准备什么? 需要sw暴露什么接口?只要内存分布就够了? 如何编译riscv版本?
|
总结
crc
校验算法rng
随机数生成spi
总线uart
串口i2c
总线usb
radio
IEEE 802.15.4通信包GPIO
EIC
外部中断控制器,另外一种中断形式流程
.cargo/config
配置链接脚本后,生成elf文件,然后再用elf2tab转化为tab格式编译安装
raspberry
sudo apt install rust-lldb
问答
文件系统在哪里?
capsules\src\sdcard.rs
提供对sd卡的读写没有线程?
thread
关键字进程是单线程的?
如何跟底层硬件链接?
arch
文件夹里有各个cpu的汇编实现基础函数asm!
宏来调用汇编什么叫MPU?
为什么回调比闭包好?又什么区别?
进程的回调是什么?为什么是这种结构?
kernel\src\process.rs
FunctionCall进程是如何到指令的?
kernel是什么?代码在哪里?
kernel\src\sched.rs
kernel是先创建process在创建kernel?
chips\sam4l\src\lib.rs
nrf52832::init()
kernel_loop
,内核不断循环capsules是什么?
进程在哪里创建和初始化的?
kernel\src\process.rs
的load_processes函数创建线程kernel\src\process.rs
的crate unsafe fn create(
函数里面chip_layout
声明prog (rx) : ORIGIN = 0x00030000, LENGTH = 0x00010000
应用程序的安装地址apps_start_address
程序的安装位置,然后就可以使用openocd将app烧录进对应从内存地址load_processes
前面会从链接脚本中捞出app的安装地址_sapps
,读取固定长度的tbf头,找到app的体积,然后遍历安装所有的app_sapps
是这样跟prog
关联的:多线程是如何调用多核cpu的?
线程包含哪些必要的内存参数?
线程是如何切换的?
创建一个进程需要什么?
boards\components
有各种硬件基础组件的抽象,硬件线程的初始化都是从这里开始的每个硬件比如led都占用一个线程?
现在每块板会创建多少个进程?
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
只有这里创建了跟内核通信的ipc进程的数量是固定的,app是不固定的?
load_processes
函数如何确定一段内存里面有几个app?
Process::create
函数里面syscall做什么的?有什么用?
systick有什么用?怎么用?
tock系统是如何注入systick_handler的?
BASE_VECTORS
的arch下面的SysTick有什么用?如何跟systick_handler关联
如何将systick和process联合在一起?
Cortex-M3 事实上有两个栈指针寄存器,分别为:
它们可以分别存储不同的值,且可以通过寄存器名 MSP 和 PSP 直接读写。寄存器名 SP 指代其中哪一个取决于当前 CONTROL 寄存器的 bit[1]。
通过切换 CONTROL 寄存器的值我们可以轻松地在内核与应用间切换调用栈。通常我们不会直接读写 CONTROL 寄存器,而是使用不同的中断返回地址来切换调用栈。(这是因为如果开启了特权保护,用户进程无法使用 MRS 以及 MSR 指令,进而无法读写 CONTROL,只能通过中断提权)
tock是如何注入app?
.bin
文件转化为tab格式tbfheader
load_processes
函数时,根据链接脚本获得app flash开始的位置app的开发流程是什么?
libtock-rs
.tab
文件,实际上还是使用cargo build --release --target=thumbv7em-none-eabi
构建出应用(就是.bin
文件,只是没带后缀),然后使用elf2tab
转化为tab文件系统的启动/关闭/重启是如何在哪里实现的?
reset_handler
,来初始化kernel,加载驱动capsule,加载进程process,开始kernel循环tock能否通过不重启来加载和启动app?就是添加一个flash进程把app写入flash
目前实现了哪些协议?
MUC内部实现了数据链路层?
IEEE802154的实现原理是什么?
chips\nrf52\src\ieee802154_radio.rs
TXADDRESS, RXADDRESSES RXMATCH
IP协议是如何调用数据链路层的?
网络调用的原理是什么?
tock是如何调用cortex的MAC层进行通信的?
IEEE 802.15.4相关
IPC通信的原理是什么?
用户进程是如何跟内核进程通信的?
普通进程是如何订阅硬件的?比如button
进程死循环怎么处理?
如何知道硬件button按下了?
arch\cortex-m\src\nvic.rs
,还有比如:chips\nrf52\src\interrupt_service.rs
,chips\cc26x2\src\gpio.rs
hard_fault是什么?
NVIC是在哪里跟GPIO绑在一起的?
chips\sam4l\src\chip.rs
文件里的service_pending_interrupts
函数cortexm4::nvic::Nvic::new(interrupt)
arch\cortex-m\src\nvic.rs
的n.enable()
往寄存器中填入处理函数地址,就完成了中断侦听chips\stm32f4xx\src\chip.rs
的service_pending_interrupts
函数第一次中断时如何插入的?
GPIO的中断是在哪里开始执行的?
nrf52的nvic在哪里?
chips\nrf52\src\nvmc.rs
什么是VolatileCell?
chips/sam4l/src
pub unsafe extern "C" fn generic_isr() {
函数的最后就有这个单词fired的实现在哪里?
capsules\src\gpio.rs
fired调用的schedule函数在哪里?
kernel\src\callback.rs
fired的执行流程是什么?
capsules\src\gpio.rs
)通过kernel找到自己的进程,然后将callback回调插入进程的回调队列中GPIO中断的执行流程是什么?
pub static IRQS: [unsafe extern "C" fn(); 80] = [generic_isr; 80];
往芯片插入generic_isr
中断函数subscribe
函数将回调插入Driver中保存pub unsafe extern "C" fn generic_isr() {
函数会保存中断信息,停止中断,继续kernel循环kernel_loop
不断通过service_pending_interrupts
查找是否有中断handle_interrupt
函数上面的中断如何跟drive的subscribe关联的?
handle_interrupt
时,会将自己身上callback重新插入process中,然后kernel循环到进程时会执行callback如何知道触发的中断类型的?
中断的初始化流程
generic_isr
给BASE_VECTORS
写入boards\kernel_layout.ld
的vectors
ROM中reset_handle
,然后pub unsafe fn init
开始读取ROM的数据到RAMzero_bss是做什么的?
内存布局时如何确定的?
layout.ld
确定自己的内存大小,由统一的kernel_layout
布局系统内存如何读取和识别elf/tab文件的?
为什么要编译为机器代码而不是执行?
ids是如何被rust读取和解析的?
.cargo\config
tockloader是如何安装程序的?
tock的程序不用在该系统里面编译?
tock的内存分页是如何做的?
kernel\src\hil\flash.rs
capsules\src\nonvolatile_to_pages.rs
?tock写入页的流程是什么?
chips\sam4l\src\flashcalw.rs
,入口时write_page
函数write_page
函数复制缓存一份将要被写的数据到self.buffer
,修改状态为WriteUnlocking
(开始写操作),然后执行lock_page_region
触发中断handle_interrupt
,匹配到状态WriteUnlocking
,触发WriteErasing
进行清空那一页的数据,并继续触发中断WriteErasing
,执行write_to_page_buffer
将page的数据写入缓存中,写的实现是找到page的地址,然后进行指针偏移复制。设置状态为WriteWriting
,并触发写入保护中断WriteWriting
,说明已经写完,清空缓存,设置状态为Ready
,并执行写完成的kernel flash 回调。完成写操作注意的是:
nvic::HFLASHC => flashcalw::FLASH_CONTROLLER.handle_interrupt(),
capsules\src\nonvolatile_to_pages.rs和capsules\src\nonvolatile_storage_driver.rs的功能分别是什么?
nonvolatile_to_pages
是将内存分页读写的控制器,也是暴露给用户去使用的,跟nonvolatile_storage_driver关系不大nonvolatile_storage_driver
是暴露给app和内核操作RAM的接口,采用发布订阅的方式内核的页目录在哪里?
kernel\src\process.rs
的memory: &'static mut [u8],
?内核是如何分配内存给各个app的?
kernel\src\memop.rs
统一进程的内存操作码,内存的控制仍然在kernel\src\sched.rs
内核的processes
指针kernel\src\process.rs
的create函数里面,通过kernel的process全局app内存指针和tbf文件的数据,分配出app的内存。关键分配还是在创建进程这里安装app和安装内核的openocd命令有何不同?
boards/launchxl/flash-kernel.openocd
:tock系统的安装流程是怎样的?
最后生成的.bin文件是怎样生成的?入口在哪里?
LLVM-objcopy
转化而来的,最后make flash
的时候也是将这个.bin文件烧进主板的,实际上.bin和.elf文件是同一个文件LLVM-objcopy
类型GNU的OBJCOPY命令将ELF转化为bin文件系统文件elf是怎样生成的?
main.rs
文件,打包为elf。tock的一般会配上build.rs
引入内存分布说明进程是在哪里开始执行app里面的代码的?
let init_fn = app_flash_address.offset(tbf_header.get_init_function_offset() as isize) as usize;
获得app的入口函数init_fn
连同上下文塞入process.tasks
里面,多个tasks可能会指向同一个init_fn
do_process
时,发现task带回调(回调就是init_fn)Task::FunctionCall(ccb)
,就会调用process的set_process_function
,而process的set_process_function又会调用arch\cortex-m\src\syscall.rs
的set_process_function
将app的init_fn
地址(也就是callback.pc)写入PC寄存器,并且将参数写入其他寄存器。process的callback是什么?
进程的执行在哪里?
kernel\src\sched.rs
的do_process
函数Ring buffer跟task的组合有什么作用?
app是被插入PC寄存器队列的,kernel又是死循环的,芯片是单线程的,那app是如何在kernel循环中被执行的?
执行完用户app的代码,是如何切回kernel进程的?
systick_handler
是切换的关键内核态和用户态是如何切换的?
self.state.set(State::Running);
State::Running
则启动systick,通过switch_to ->switch_to_process
(实际就是SP寄存器),产生systick中断,触发systick_handler
中断处理函数开始执行用户app的代码systick_handler的作用是什么?
systick.enable()
的时候回不断产生systick中断,执行systick_handler
systick_handler
会检查用户app代码的执行状态,如果超时或出错就会通过sp指针切换回内核进程arch\cortex-m\src\syscall.rs
的switch_to_process
做了什么?一个process有多个taskQueue,一个taskQueue又有多个task?
用户进程app内申请的heap和stack是如何分配和隔离的?
libtock的timer是什么?怎么实现的?
capsules/src/virtual_alarm.rs
是什么?有什么用?libtock-rs的DRIVER_NUMBER是每个驱动固定的值?
libtock里面使用
SVC 1/2/3这种来调用内核硬件,这个1/2/3如何解决并发问题?
收到SVC后,在哪里开始调用硬件?
kernel\src\syscall.rs
arch\cortex-m\src\syscall.rs
的switch_to_process
函数有一句let syscall = kernel::syscall::arguments_to_syscall(svc_num, r0, r1, r2, r3);
返回系统调用do_process
检查到是Syscall::SUBSCRIBE``Syscall::COMMAND
service_pending_interrupts
检查nvic的地址,看有没有中断,如果有则调用对应的handle_interrupt
时钟如何倒计时的?
chips\sam4l\src\ast.rs
tock将时间处理分为时钟(alarm)和时间(time),各有什么区别?
NVIC硬件调用逻辑
svc_handler
将R0寄存器SYSCALL_FIRED
设置为1process.switch_to();
发现SYSCALL_FIRED
是1,需要切换到syscallswitch_to_process
函数读取寄存器的参数和arguments_to_syscall
函数获得syscall的类型Syscall::SUBSCRIBE
和传入的driver_num,进程id创建订阅回调,完成订阅对应硬件的capsuleNVIC订阅如何返回硬件结果?调用是在哪里?
时钟是在哪里初始化的?如何设置的?
boards\components\src\alarm.rs
cpu如何知道那个中断被订阅了?比如时钟中断,怎样使能时钟中断?
boards\nordic\nrf52dk_base\src\lib.rs
初始化rtc.start();
?capsules\src\alarm.rs
的command函数重置alarmreset_active_alarm
->self.alarm.set_alarm(next_alarm);
使能和设置时钟寄存器,等触发中断将调用generic_isr中断监听函数components/capsules/chips的驱动有何区别?
COMMAND,SUBSCRIBE,ALLOW有什么区别?
kernel\src\syscall.rs
,区别在kernel\src\sched.rs
如何输入调试信息
debug!("Initialization complete. Entering main loop");
测试项目tests文件在哪里?
libtock-c/examples/tests/printf_long/main.c
像print这种在本机的terminal输出信息的是如何实现的?
栈的生长方向是如何确定的?
栈的大小是如何确定的?
STACK_SIZE
,一般是2048个指针大小(2048*32位)data的大小是如何确定的?
heap和stack之间隔着data,app_heap_start是如何确定的?默认是怎样的?
core\src\entry_point\start_item_arm.rs
的汇编里,app启动的之前会先执行_start(在layout_generic.ld里面定义)这段汇编确定app_heap_start(memop(11, r4))app的启动链接脚本是如何在哪里启动的?
layout_nrf52.id
将layout_generic.id引入build.rs
里面重命名layout_nrf52.id
为layout.ld
.cargo\config
配置里面的layout.id,链接脚本作用,每个app有自己的虚拟内存分布grant的是做什么用的?为什么是1024?为什么放到内存顶部?
doc\Design.md#Grants
,grant是用于跟kernel通信的,包括与驱动capsule的通信。tock不允许驱动在系统里面存数据,只允许放进程内kernel\src\grant.rs
,应该是没有固定大小的,就剩顶部空的,放顶部应该是最合理的选择app rust代码里面申请的堆和栈是如何映射到系统的内存分配的?
虚拟内存跟进程有何关系?sp指针指向的虚拟内存还是真实内存?
sp指针代表栈指针?
mpu是如何判断野指针访问内存的?
访问权限、cache和写缓存分别是什么意思?
到2000年,DRAM部件每片的容量到达256Mbit,随机访问速率在30MHz左右。微处理器每秒需要访问存储器几百兆次。如果处理器速率远高于存储器,那么只能借助Cache才能满足其全部性能。
Cache存储器是一个容量小但存取速度非常快的存储器,它保存最近用到的存储器数据拷贝。对于程序员来说,Cache是透明的。它自动决定保存哪些数据、覆盖哪些数据。现在Cache通常与处理器在同一芯片上实现。Cache能够发挥作用是因为程序具有局部性特性。所谓局部性就是指,在任何特定的时间,微处理器趋于对相同区域的数据(如堆栈)多次执行相同的指令(如循环)。
Cache经常与写缓存器(write buffer)一起使用。写缓存器是一个非常小的先进先出(FIFO)存储器,位于处理器核与主存之间。使用写缓存的目的是,将处理器核和Cache从较慢的主存写操作中解脱出来。当CPU向主存储器做写入操作时,它先将数据写入到写缓存区中,由于写缓存器的速度很高,这种写入操作的速度也将很高。写缓存区在CPU空闲时,以较低的速度将数据写入到主存储器中相应的位置。
通过引入Cache和写缓存区,存储系统的性能得到了很大的提高,但同时也带来了一些问题。比如,由于数据将存在于系统中的不同的物理位置,可能造成数据的不一致性;由于写缓存区的优化作用,可能有些写操作的执行顺序不是用户期望的顺序,从而造成操作错误。
tock没有MMU,有实现4G虚拟内存和内存映射到物理内存吗?
layout_generic.ld
声明内存布局不支持虚拟内存,rust创建变量的时候是如何知道目标栈地址?sp指针?
.cargo\config
的rustflags,使用c的link-arg传递参数给连接器,用-Tlayout.ld指定连接脚本声明内存布局包括寄存器的所有信息nRF52832_PS_v1.4.pdf
The text was updated successfully, but these errors were encountered: