Open cisen opened 4 years ago
tockloader install --board launchxl-cc26x2r1 --openocd blink
即可烧入apptockloader install --jlink --arch cortex-m4 --board nrf52dk --jtag-device nrf52 /home/cisen/develop/tock/libtock-rs/target/thumbv7em-none-eabi/tab/nrf52/hello_world.tab
tockloader uninstall --jlink --arch cortex-m4 --board nrf52dk --jtag-device nrf52
make flash-nrf52 EXAMPLE=hello_world
tockloader info --jlink --board nrf52dk
make nrf52 DEBUG=info
make flash-nrf52 EXAMPLE=button_leds DEBUG=debug/info/warn/error/fatal
JLinkExe -device nrf52 -if swd -speed 1000 -autoconnect 1
JLinkRTTClient
tockloader install --board nrf52dk --jlink blink/build/blink.tab
tockloader是如何将bin文件写进硬件的?
全局静态区,文字常量区,程序代码区是从内存地址分配的角度来描述的。
全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
文字常量区—常量字符串就是放在这里的。
程序代码区—存放函数体的二进制代码。
# mac
brew tap ARMmbed/homebrew-formulae && brew update && brew install arm-none-eabi-gcc
$ cd tock/boards/nrf52dk/jtag
# 重要侦听2331端口
$ ./jdbserver_pca10040.sh
$ JLinkGDBServer -device nrf52 -speed 1200 -if swd -AutoConnect 1 -port 2331
arm-none-eabi-gdb -x gdbinit_pca10040.jlink
# debian
sudo apt install gdb-arm-none-eabi gdb-multiarch
{
"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"
]
}
]
}
> in VirtualMuxAlarm::set_alarm
alarms: | alarm 1 | | alarm 2
before: prev | cur_alarm | now | when(设置1)
after: | | prev | cur_alarm(设置2)
alarms: | alarm 2 | | alarm 1 | before: prev/now | when | | cur_alarm | after: prev/now(fake)| cur_alarm | | | now(acturelly)
alarm=16779325, now=2109, prev=16776056, nowWrapPrew=4278193349, alarmWrapPrev=3269
```rust
// 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);
}
}
# now - prev = 3 < alarm1 - prev = 4导致alarm1要在下一个tick循环才能被捕获到
--alarm1----prev-alarm2--alarm1中断(now)----------
- - - 如果alarm1跟alarm2间距很大,出现设置alarm2的时候alarm1到期了还没执行的概率就很小
- - - 如果时钟中断随机设置,也很有可能出现alarm1跟alarm2很接近的情况,就会出现alarm1要等很久才能被捕获
## 问题翻译
这个计时器的问题跟这些最新的issue的是一样的。
**前提**
我开始注意到这个问题是整合RTT的时候,通过syscall的console打印了一堆信息。app有些时候会被冻结,似乎在永远等待console驱动程序返回“写完成”回调。因为debug的输出也会被冻结,我不是百分百肯定。但是通过LED的闪来调试确实发现这个问题。
在virtual_alarm capsule中,有很多针对某个参考的相互比较时间戳的检查(比如“now”)
第一个检查,如果新的时钟设置的到期时间戳when在当前已设置时钟的到期时间戳之前(这种情况时钟应该更新到期时间戳到when)
tock/capsules/src/virtual_alarm.rs
Lines 85 to 91 in d52a619
```rust
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是下一个设定值) tock/capsules/src/virtual_alarm.rs Lines 131 to 133 in d52a619
fn has_expired(alarm: u32, now: u32, prev: u32) -> bool {
now.wrapping_sub(prev) >= alarm.wrapping_sub(prev)
}
我不确定wrapping_sub算法的逻辑含义是什么,但是这似乎在保证 cur_alarm> = now && when> = now
(在第一种情况下),以及now> = prev && alarm> = prev
(在第二种情况)。
否则,由于时间戳是无符号整数,因此如果其中一个参数稍微小于now
(在第一种情况下)或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);
}
Kernel panic at [...]/tock/capsules/src/virtual_alarm.rs:91:
"cur_alarm=10351, when=10356, now=10353"
第二种情况发生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);
}
Kernel panic at [...]/tock/capsules/src/virtual_alarm.rs:141:
"now=7392136, alarm=7392134, prev=7392135"
在我观察到的两种panic中,参考点都在其他两者之间,从而使比较“翻转”。
例如,在第二种情况下,alarm = 7392134 <prev = 7392135 <now = 7392136
,但是代码却得到尚未到期的结论(这似乎是错误的)。
请注意,alarm capsule中有相同的功能。 tock/capsules/src/alarm.rs Lines 161 to 163 in d52a619
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上进行wrapping_sub
,如下所示:
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 在各种情况下的行为。
关于此问题的一些更新:通过运行libtock-rs
中的blink_random
示例,可以在virtual_alarm.rs
中的has_expired
中产生以下情况。
Kernel panic at [...]/tock/capsules/src/virtual_alarm.rs:141:
"now=1291, alarm=16778507, prev=16775234"
当转换为十六进制时,now= 0x50b,alarm= 0x100050b,prev= 0xfff842
。 因此,似乎systick只有24位(cc#1413),并且alarm实际在逻辑上应等于now
。
在那种情况下,我们得到now.wrapping_sub(prev)= 0xff000cc9
和alarm.wrapping_sub(prev)= 0xcc9
,因此has_expired
函数能正确地返回alarm已过期,尽管它会错误地得出now值: now= 1290
。
tock/capsules/src/virtual_uart.rs Line 353 in ad676ee
self.mux.do_next_op();
有传输请求时调用do_op。 如果没有待处理的请求,则可以立即处理。 如果失败,则发出带有错误的callback_done回调: tock/capsules/src/virtual_uart.rs Line 190 in ba11fd9
client.transmitted_buffer(rbuf.unwrap(), 0, rcode);
我们已经同意,出于竞争条件/数据完整性的原因,不应在调用期间发生回调。 如果立即处理了对虚拟UART的发送调用且失败了,则将同步触发回调。 相反,这应该使用延迟调用来异步发出回调。
此拉取请求增加了对schedule timer的支持,其中提供的systick counter实际上位于过去。 这对于将timer设置为很短的时间很有用。 如果在一个应用程序中同时调度计时器,就会发生这种情况,想象两个LED同时以500和250ms的间隔闪烁。 一个延迟将在第二个停止后开始,并且会任意缩短。 这很容易导致(不可避免的)情况,即设置为alarm的systick count其实已经结束。
此PR通过firing alarm(如果已结束)来解决此问题。
使用VirtualMuxAlarm设置alarm时,参考点(self.mux.prev)与alarm一起设置。 tock/capsules/src/virtual_alarm.rs Lines 84 to 95 in f30961f
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是否已过期。 tock/capsules/src/virtual_alarm.rs Line 149 in f30961f
.filter(|cur| cur.armed.get() && has_expired(cur.when.get(), now, prev))
问题
问题在于,在VirtualMuxAlarm :: set_alarm
内部无法保证cur_alarm> = now
。 我们可能会处在:已经设置了alarm1,正在设置alarm2,而alarm1已经在过去(但尚未触发)的情况。
> in VirtualMuxAlarm::set_alarm
alarms: | alarm 1 | | alarm 2
before: prev | cur_alarm | now | when(设置1)
after: | | prev | cur_alarm(设置2)
在这种情况下,由于新的prev就在alarm1之后,因此下次调用MuxAlarm :: fired
时,alarm1将不会被视为过期-而是需要花费ticks轮回的时间才能判断时钟已经到期。
这在Nordic上的Segger RTT调试中尤其明显,该调试器将计时器设置为将来非常接近(100us)。 到其他代码运行时,这可能已经过去了。 tock/capsules/src/segger_rtt.rs Line 243 in f30961f
let interval = (100 as u32) * <A::Frequency>::frequency() / 1000000;
但是使用virtual alarms的所有capsule都会受到影响。
可观察到的结果是alarm客户端(在本例中为Segger RTT)永远等待(对于Segger RTT,内核调试和控制台似乎冻结)。
解决方案
我认为在设置新alarm时移动prev是不正确的。 仅应在MuxAlarm :: fired
函数中对其进行更新,在该函数中实际上会触发所有过期的alarm。
这再次表明32位时间戳的“三向比较”容易出错,因为实现起来很复杂,难以理解,很难知道何时可以安全地更新参考点,等等。我认为这是 关于在与时间相关的胶囊和HIL中具有真正单调(即64位)时间戳的另一个论点(如先前在https://groups.google.com/d/msg/tock-dev/CWecIZq7jqg/RrWySQLbBgAJ中讨论的)。
rtc时钟是如何设置时间的?
alarm的CAPTURE/COMPARE 模式是什么?
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare
VirtualMuxAlarm是什么?
self.now和alarm.now有何区别?
cur_alarm,now,when,prev分别是什么?
Timer和Count两种模式有何区别?
既然MuxAlarm.virtual_alarms[0]=VirtualMuxAlarm
,就是MuxAlarm是VirtualMuxAlarm的父,那VirtualMuxAlarm是如何被push进去virtual_alarms的?
系统的所有时钟都是在MuxAlarm下进行管理的?
boards\components\src\alarm.rs
有个AlarmMuxComponent组件给各个主板使用rtc初始化的时候创建一个公共的MuxAlarmAlarmDriver跟VirtualMuxAlarm有什么关系?有什么区别?
AlarmDriver.alarm=VirtualMuxAlarm
VirtualMuxAlarm.client=AlarmDriver
subscribe
, command
VirtualMuxAlarm
是对接系统全局的MuxAlarm,是用于被系统调度的。所以VirtualMuxAlarm是时钟内核,既对接app的AlarmDriver,又对接操作系统的MuxAlarm每次订阅一次就创建一个virtualAlarm?
app_alarm是什么?
一个app只能setTimeout一次?
可以通过创建多个timeDriver实现多个setTimeout?
src\timer.rs
的函数activate_current_timer
# 几个重要的缩写的意义:
CC: Capture compare
CCXE: Capture/Compare x output enable
CCXNE:Capture/Compare 1 complementary output enable
CCXNP:Capture/Compare x complementary output polarity
OPM:The one-pulse mode (OPM)
TI: Timer
TIx: x Timer number,TIx Timer X,the timer channel inputs, TIx inputs.
IT: internal Trigger
ITx:internal Trigger x
TI1FP1:Timer Input 1 Filtered Priority channel 1
ED: Edge Detector:
FDTS: Frequency of Division Timer Clock:The FDTS clock signal is derived from the timer clock signal, and the CKD[1:0] control bit-field sets the ratio between these two clock signals.
fCK_INT: Frequency clock of internal
fDTS = FDTS
TIxF_ED:TIx Edge Detector
ETRF:External Trigger input
Internal Trigger 0 (ITR0).
Internal Trigger 1 (ITR1).
Internal Trigger 2 (ITR2).
Internal Trigger 3 (ITR3).
TI1 Edge Detector (TI1F_ED)
Filtered Timer Input 1 (TI1FP1)
Filtered Timer Input 2 (TI2FP2)
External Trigger input (ETRF)
trigger signal (TRGI)
- 16777216 = 2^24B÷bai1024÷1024=16MB
- 在`chips\nrf52\src\adc.rs`写着:`/// Capture and compare value. Sample rate is 16 MHz/CC`
- 如果您使用nRF52840的RTC,则对于32KHz的频率,溢出频率为2 ^ 24,因此为512秒,即仅超过8分钟。
alarm=16779325, now=2109, prev=16776056, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779355, now=2139, prev=16776086, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779392, now=2176, prev=16776123, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779425, now=2209, prev=16776156, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779463, now=2247, prev=16776194, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779356, now=2140, prev=16776087, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779415, now=2199, prev=16776146, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779430, now=2214, prev=16776161, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779469, now=2253, prev=16776200, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779505, now=2289, prev=16776236, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779409, now=2193, prev=16776140, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779444, now=2228, prev=16776175, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779453, now=2237, prev=16776184, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779475, now=2259, prev=16776206, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779542, now=2326, prev=16776273, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779460, now=2244, prev=16776191, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779485, now=2269, prev=16776216, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779515, now=2299, prev=16776246, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779605, now=2389, prev=16776336, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779646, now=2430, prev=16776377, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779576, now=2360, prev=16776307, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779618, now=2402, prev=16776349, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779682, now=2466, prev=16776413, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779745, now=2529, prev=16776476, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779810, now=2594, prev=16776541, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779759, now=2543, prev=16776490, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779778, now=2562, prev=16776509, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779831, now=2615, prev=16776562, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779868, now=2652, prev=16776599, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779885, now=2669, prev=16776616, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779810, now=2594, prev=16776541, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779826, now=2610, prev=16776557, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779914, now=2698, prev=16776645, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779985, now=2769, prev=16776716, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779999, now=2783, prev=16776730, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779932, now=2716, prev=16776663, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16779997, now=2781, prev=16776728, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780031, now=2815, prev=16776762, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780095, now=2879, prev=16776826, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780141, now=2925, prev=16776872, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780020, now=2804, prev=16776751, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780031, now=2815, prev=16776762, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780073, now=2857, prev=16776804, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780127, now=2911, prev=16776858, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780142, now=2926, prev=16776873, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780091, now=2875, prev=16776822, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780162, now=2946, prev=16776893, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780279, now=3063, prev=16777010, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780325, now=3109, prev=16777056, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780358, now=3142, prev=16777089, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780365, now=3149, prev=16777096, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780254, now=3038, prev=16776985, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780288, now=3072, prev=16777019, nowWrapPrew=4278193349, alarmWrapPrev=3269 alarm=16780307, now=3091, prev=16777038, nowWrapPrew=4278193349, alarmWrapPrev=3269
tbf中,创建进程的核心参数是什么?
各个芯片不同的针脚定义在哪里?
boards\nordic\nrf52dk\src\main.rs
chips\nrf52\src\uart.rs
寄存器有什么用?
如何发送一大波数据?如何分批发送
src\console.rs
的:
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(())
}
}
发送流程是怎样的?
见libtock的src/console,先将buffer分块,然后再将buff入口指针设置到寄存器里面,最后调用发送
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是如何初始化的?
boards\nordic\nrf52dk_base\src\lib.rs
搜索ConsoleComponent
kernel\src\debug.rs
kernel\src\ipc.rs
kernel\src\process.rs
kernel\src\sched.rs
[*] 编译swerv到fpga
问题列表
[ ] 运行汇编点亮led
[ ] 安装系统到swerv
需要准备什么?
需要sw暴露什么接口?只要内存分布就够了?
如何编译riscv版本?
cd boards/hail/
make
总结
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]。
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
,应该是没有固定大小的,就剩顶部空的,放顶部应该是最合理的选择let ctr_ptr = process.grant_ptr(self.grant_num) as mut mut T; // If the pointer at that location is NULL then the grant // memory needs to be allocated. let new_grant = if (ctr_ptr).is_null() { process .alloc(size_of::(), align_of::())
.map(|root_arr| {
let root_ptr = root_arr.as_mut_ptr() as mut T;
// Initialize the grant contents using ptr::write, to
// ensure that we don't try to drop the contents of
// uninitialized memory when T implements Drop.
write(root_ptr, Default::default());
// Record the location in the grant pointer.
write_volatile(ctr_ptr, root_ptr);
root_ptr
})
} else {
Some(*ctr_ptr)
};