2022-09-20 update

This commit is contained in:
shzhxh 2022-09-20 20:41:38 +08:00
parent fd99a1abb2
commit 1854626938
37 changed files with 1210 additions and 37 deletions

View File

@ -0,0 +1,23 @@
### 简介
Rust核心的分配与集合库。这个库提供智能指针和集合用于管理堆分配的值。
#### Boxed值
`Box`类型是智能指针类型。
#### 引用计数指针
`Rc`类型是非线程安全的引用计数指针类型,用于在线程内共享内存。
#### 自动引用计数指针
`Arc`类型是线程安全的引用计数指针类型。
#### 集合
定义了最常用的数据结构的实现。
#### 堆接口
`alloc`模块定义了默认的全局分配器的底层接口。

View File

@ -0,0 +1,15 @@
线程安全的引用计数指针。
#### 结构体
##### Arc
Arc的意思是自动引用计数(Atomically Reference Counted)。这是一个线程安全的引用计数指针。
```rust
pub fn new(data: T) -> Arc<T> {} // 构造一个新的Arc<T>
```
##### Weak

View File

@ -0,0 +1,75 @@
#### 概述
从应用程序的角度看内核是辅助应用程序执行的一个程序。从硬件的角度看内核是管理硬件的程序。这两者都是外部视角本章就以maturin为例从外部视角来看内核。
通过观察ramfs里的内容看到并没有暴露内核里的任何数据也没有把设备抽象为设备文件所以maturin对上的接口仅是系统调用。
通过观察qemu启动时的参数看到maturin对下的接口是qemu virt物理机。
#### 系统调用
##### 执行流程
1. 用户程序执行`ecall`指令进入用户态。
2. 内核态下`stvec`寄存器保存了自陷处理的入口,这是在内核启动时执行`trap::init()`来设置的。
3. 从`trap::init()`可见,`__alltraps`是所有自陷处理的入口。
4. `__alltraps`先保存了上下文信息,然后调用了`trap_handler()`。
5. `trap_handler()`分发自陷给相应的处理函数。这是通过`sstatus`寄存器的SPP位得到进入S模式之前的特权级进而实现函数的分发。如果进入S模式之前是U模式则执行`user_trap_handler()`。
6. `user_trap_handler()`处理来自用户态的各种自陷(系统调用,异常,时钟中断),以及当前线程的信号。关于自陷的种类,是通过读`scause`寄存器获得的。如果是系统调用,则调用`syscall()`。
7. `syscall()`是系统调用的处理者,它通过系统调用号来把系统调用分发给不同的函数处理。
8. `syscall()`处理完成返回到`user_trap_handler()`,再返回到`trap_handler()`,再返回到`__alltraps`。
9. 在`__alltraps`里恢复上下文,执行`sret`返回用户程序继续执行。
##### 系统调用的列表
- 文件系统
文件描述符操作DUP DUP3 FCNTL64
文件操作UNLINKATLINKATOPENCLOSEREADLSEEKWRITEWRITEVREADVPREADSENDFILE64
获取文件状态STATFSFSTATATFSTATUTIMENSAT
目录操作GETCWDMKDIRCHDIRUMOUNTMOUNTGETDENTS64
其它PIPE, IOCTL
- 进程
状态管理EXITEXIT_GROUPYIELDKILLFUTEXCLONE, EXECVE, WAIT4, PPOLL
ID管理SET_TID_ADDRESSGETPIDGETPPIDGETUIDGETEUIDGETGIDGETEGIDGETTID
其它TIMES, PRLIMIT64
- 时间
NANOSLEEPCLOCK_GET_TIMEGET_TIME_OF_DAY
- 信号
SIGACTIONSIGPROCMASKSIGRETURN , SIGTIMEDWAIT
- 网络
SOCKET, SENDTO, RECVFROM
- 内存
BRK, MUNMAP, MMAP , MEMBARRIER
- 其它
UNAME
#### virt物理机
通过`qemu-system-riscv64 -M virt -M dumpdtb=riscv64-virt.dtb -bios default`导出设备树可以观察virt物理机的内容。也可以在qemu控制台通过`info ptree`命令观察virt物理机的内容
- plic, clint
- pci总线
- uart串口
- rtc时钟
- cpu
- 内存

View File

@ -0,0 +1,98 @@
#### 背景知识
##### 编译的过程
1. `make testcases-img`生成文件系统镜像`fat.img`,且在`fat.img`里包含测例。
1. `make run`核心操作就是`cargo build`。
1. maturin使用了构建脚本`build.rs`cargo会先编译并执行该脚本再构建整个项目。
1. `build.rs`通过`println!`和cargo通信告诉它如果`build.rs`或`../fat.img`发生变化则重新编译运行它自己。
1. `build.rs`调用`insert_fs_img()`,在`kernel`的源码里加入一个包含一小段汇编代码的文件`fs.S`,把`../fat.img`包含进去。
1. `build.rs`在输出目录里创建`linker.ld`文件,把字符串`LINKER`的内容写入这个文件。
1. `build.rs`调用`println!`告诉cargo把`-C link-arg=-Tlinker.ld`选项传给编译器,其实就是指定了链接脚本。链接脚本里比较重要的信息有:内核入口点是`_start`,内核装载位置是`0xffffffff80200000`。
##### 初始化的流程
1. 入口为`arch/riscv/mod.rs`的`entry()`函数,使用汇编代码设置栈和页表,然后跳转到`start_kernel()`开始Rust代码的执行。
2. `start_kernel()`调用相关模块实现内存、自陷、文件系统的初始化。
3. `start_kernel()`通过调用`arch::secondary_entry`初始化其它的核。
1. 使用汇编代码设置栈和页表,然后跳转到`start_kernel_secondary()`开始Rust代码的执行。
2. `start_kernel_secondary()`初始化当前核的页表和自陷,等待主核初始化结束后开始执行`task::run_tasks()`。
4. 主核执行`task::run_tasks()`,开始运行用户程序。
5. `task::run_tasks()`就是一个大`loop`循环,这个循环的内容是:
1. 首先调用`fetch_task_from_scheduler()`从调度器器里取一个任务。
1. 执行`task.vm.lock().activate()`切换到用户的页表。最终是写`satp`寄存器实现的且要注意刷新TLB.
2. 通过`__switch`切换到任务的上下文,用户程序开始执行。
3. 用户程序执行完毕后,调用`enable_kernel_page_table()`切换回内核页表。
4. 获取任务的状态,执行相应的操作。
5. 如果没有任务了,则调用`panic!()`退出系统。
##### 第一个用户进程
1. `fetch_task_from_scheduler()`调用`load_next_testcase()`创建一个用户程序的PCB先`push`进调度器`GLOBAL_TASK_SCHEDULER`,再`pop`出来返回该测例的TCB.
2. `load_next_testcase()`把用户程序加载到TCB里。这最终是通过`from_app_name()`实现的。
3. `from_app_name()`从用户程序的名称生成TCB。其中在构造内核栈的时候调用了`app_init_context()`初始化用户程序的上下文。
4. `app_init_context()`把`sstatus`寄存器的`SPP`位设置为`SPP::User`。
#### 概述
应用程序对用户是有意义的,用户不需要内核,是应用程序需要内核。
应用程序需要内存来运行自己,于是内核把内存抽象成页,再把页分配给应用程序。
应用程序需要把运行结果记录下来,于是内核把硬盘抽象成文件系统,这样应用程序就可以把结果保存为文件。
应用程序需要并行以提高效率于是内核把CPU抽象成进程。
内核为了完成上述的三个抽象,还需要一个调度进程来调度所有的应用程序,需要一个自陷管理进程来让应用程序和外设进行交互,需要提供进程间交互的机制(比如信号)。
#### 内存
RISC-V在S态提供了把内存抽象成页的功能和其它架构的实现方式一样这在本质上就是“多级查表”。第一级表是4k的页这个页的物理地址放在`satp`寄存器。
RV32的分页方式是Sv32RV64的分页方式是Sv39和Sv48。分页方式也记录在`satp`寄存器。
- Sv32是二级查表寻址4G的虚拟空间。
- Sv39是三级查表寻址512G的虚拟空间。
- Sv48是四级查表寻址256T的虚拟空间。
如果修改了页表,需要用`sfence.vma`指令刷新TLB。
addr子模块实现物理地址与虚拟地址、页与地址之间的转换。
allocator子模块实现内存分配主要是堆分配和页桢分配。
area子模块实现物理段的trait虚拟段的操作。
page_table子模块实现页表操作。
user子模块实现用户空间指针的检查。
vmm子模块实现虚拟段映射操作。
#### 进程
进程是拥有自己的上下文(寄存器值)的一串机器指令。它的存在形式由ELF格式控制它在内核里表现为TCB(任务控制块)。
控制进程的状态是通过控制TCB里的状态字段实现的。
#### 文件系统
maturin的文件系统在底层是RAM块上层是通过调用外部库[rust-fatfs](https://github.com/rafalh/rust-fatfs)实现的。
#### 调度进程
调度进程维护了一个就绪队列`ready_queue``push`方法把进程加到队列末尾,`pop`方法把进程从队列顶头弹出,这样就实现了轮转调度算法。
#### 自陷管理进程
自陷分为系统调用,中断和异常。
`__alltraps`是所有自陷的入口。
`kernel_trap_handler`处理内核态的自陷。
`user_trap_handler`处理用户态的自陷。
#### 信号
操作TCB里与信号相关的字段。

View File

View File

@ -0,0 +1,88 @@
maturin的文件系统在底层是RAM块上层是通过调用外部库[rust-fatfs](https://github.com/rafalh/rust-fatfs)实现的。
#### file模块
```rust
mod backend;
mod device;
mod epoll;
mod fd_manager;
mod fs_stat;
mod kstat;
mod pipe;
mod poll_events;
mod stdio;
mod vfs;
pub mod socket;
use crate::timer::TimeSpec;
use alloc::vec::Vec;
use core::any::Any;
pub use fatfs::SeekFrom;
/// 文件类抽象
pub trait File: Send + Sync + AsAny {
...
}
pub use device::{...};
pub use backend::{BackEndFile, SyncPolicy};
pub use device::{FileDisc, OpenFlags};
pub use epoll::{EpollFile, EpollEvent, EpollEventType, EpollCtl};
pub use fd_manager::FdManager;
pub use fs_stat::FsStat;
pub use kstat::normal_file_mode;
pub use kstat::{Kstat, StMode};
pub use pipe::{Pipe, RingBuffer};
pub use poll_events::PollEvents;
pub use socket::Socket;
pub use vfs::{...};
```
##### device
```rust
lazy_static::lazy_static! {
//static ref MEMORY_FS: Arc<Mutex<FileSystem<FsIO, FsTP, FsOCC>>> = Arc::new(Mutex::new(new_memory_mapped_fs()));
static ref MEMORY_FS: FATFileSystem = new_memory_mapped_fs();
}
/// 通过info级的日志打印出根目录下的所有文件
pub fn list_files_at_root() {...}
/// 初始化硬盘的内容
pub fn fs_init() {
// 1. 生成目录/dev, /lib, /tmp, /dev/shm
// 2. 把根目录下的库文件链接到/lib目录下
// 3. 生成目录/sbin, 并把根目录下的测试程序链接到/sbin目录下
// 4. 创建/proc目录及其文件
// 5. 创建/dev/disc目录及其文件
}
/// 若干函数
```
##### epoll
##### socket
##### vfs
##### backend
##### fd_manager
##### fs_stat
##### kstat
##### pipe
##### poll_events
##### stdio

View File

@ -165,3 +165,91 @@ ioctl(file_descriptor, request, argp);
当键盘输入最终发生时会产生两个中断一个是键被按下时产生一个是键被释放时产生。需要注意的是键盘生成的不是ASCII码而是**扫描码**(scan code)。同一个键扫描码的低7位是相同的最高位按下是0释放是1。
键盘中断是IRQ 1。此中断线无法在系统总线上访问也不能被其它I/O适配器共享。当`_hwint01`调用`intr_handle`要通知的TTY会被很快找到。低层的中断处理例程通过直接调用系统任务里的`generic_handler`发出通知消息。`tty_task`接收到`HARD_INT`消息后就分发给`kbd_interrupt``kbd_interrupt`再调用`scan_keyboard`。`scan_keyboard`进行了3个内核调用让系统调用读写若干 I/O端口最终得到扫描码并把扫描码放到循环缓冲区中。然后设置`tty_events`标志,表示此缓冲区包含字符不再为空。
每当`tty_task`的主循环开始另一轮循环时,它检索每个终端设备的`tty_events`标志,对于设置了此标志的设备则调用`handle_events`。`handle_events`调用设备相关的函数来进行输入输出,因为`tty_events`标志可以标示各种活动。对于键盘输入来说`handle_events`调用的是`kb_read``kb_read`把扫描码转换为ASCII码。`kb_read`再调用`in_process`来处理ASCII码一般来说就是把字符加到控制台的输入队列`tty_table`。`in_process`还需要把ASCII码回显到显示器上。
当接收到足够的字符终端驱动程序执行另一个内核调用让系统任务把数据复制到shell所要求的地址上。复制数据不是通过消息传递来实现的。为满足用户需求可能要进行多次这样的操作。当操作最终完成驱动程序会向文件系统发消息表示自己的工作完成了然后文件系统会向shell发消息来解除阻塞。
判断是否接收到足够字符的标准取决于终端的模式。规范模式下的标准是,接收到了换行码(linefeed),行尾码(end-of-line)或文件尾码(end-of-file),且一个行的大小不能超过输入队列的大小。非规范模式下的标准是,文件系统接收到操作完成的消息,在此之前读操作可以请求大量的字符,`in_process`可能需要传输多次。
注意系统任务是直接把字符从TTY的地址空间复制到shell的地址空间的。这并不经过文件系统。对于块I/O开说数据确实是经过文件系统的文件系统使用缓冲区缓存来保存常用的块。
然而对于键盘I/O缓存是没用的。文件系统请求磁盘只需要几百毫秒所以等的起。键盘I/O则可能要花几小时不可能让文件系统阻塞来等它完成的。
##### 其它输入
串行端口上传输的是字符而不是扫描码。老的UART没有缓冲所以每次按键都需要一个中断。新的UART有缓冲(16~128字节),可以配置为缓冲区有多个字符时产生一个中断。以太网传输速度比串行线更快,但以太网适配器可以缓存整个包,每个包只需要一个中断。
##### 终端输入总结
终端驱动程序被读请求首次激活时发生的事件:
- 消息抵达终端驱动程序以请求键盘字符,主例程`tty_task`调用`do_read`来处理此请求。`do_read`把调用参数保存到`tty_table`的键盘入口里,以防止没有缓存足够的字符来满足此请求。
- `do_read`先调用`in_transfer`以接收已经在等待的输入,再调用`handle_events`。`handle_events`先调用`kb_read`把键盘输入立即复制给用户,再调用`in_transfer`以从输入流中把字符提取出来。如果`in_transfer`或`handle_events`完成了读操作,则向文件系统发消息,这样文件系统就可以解除对调用者的阻塞。如果没有完成读操作(因为字符数不够),则`do_read`会告诉文件系统挂起调用者(阻塞的读操作)或取消读操作(非阻塞的读操作)。
终端驱动程序被键盘输入再次激活时发生的事件:
- 当键入一个字符,中断“处理例程“`kbd_interrupt`调用`scan_keyboard``scan_keyboard`通过调用系统任务来完成I/O。`kbd_interrupt`不是真正的处理例程,是系统任务里的`generic_handler`向`tty_task`发消息才把它激活的。)`kbd_interrupt`把扫描码放到键盘缓冲区`ibuf`,并设置一个标志以表明在控制台设备上有事件发生了。当`kbd_interrupt`把控制转移给`tty_task`,一个`continue`语句让主循环开始下一轮循环。`tty_task`会检查所有终端设备的事件标志(event flags),对于被标志的设备则调用`handle_events`。如果被标志的是键盘,`handle_events`则调用`kb_read`和`in_transfer`,就像读请求首次激活时那样处理。此事件可能会发生多次,直到接收到足够的字符满足`do_read`的要求。
- `kb_read`调用的函数包括:`map_key`把扫描码转换成ASCII码。`make_break`追踪修饰键modifier keys的状态比如SHIFT键。`in_process`处理复杂情况,比如退格键、其它特殊字符、不同输入模式下的可用选项。`in_process`也会调用`tty_echo`,所以输入的字符也会显示在显示器上。
##### 从应用程序到控制台输出的流程
进程一般是调用`printf`来打印一些东西。`printf`调用`write`向文件系统发消息这个消息里包含了一个指针指向要打印的字符。文件系统向终端驱动程序发消息终端驱动程序把那些字符复制到视频RAM里。
当终端驱动程序收到写屏幕的消息,则调用`do_write()`把参数保存进`tty_table`里控制台的结构体`tty`里。`do_write()`接下来再调用`handle_events()`,此函数为参数里指定的设备再调用输入和输出例程。对于控制台输出,这意味着等待着的键盘输入都先被处理过了。如果还有等待着的输入,则会把要回显的字符添加到等待输出的字符中。`handle_events()`接下来再调用`cons_write()``cons_write()`就是内存映射显示器的输出例程。`cons_write()`使用`phys_copy`从用户进程复制字符块到当前缓冲区。当前缓冲区满后(64字节),所有字节被转移到另一个缓冲区`ramqueue`。`ramqueue`是由16位字组成的数组。16位字里的另一半是屏幕属性字节它控制着前景色、后景色和其它属性。一般字符直接传进`ramqueue`里即可,但特殊字符需要特别处理。当字符位置超过屏幕宽度,或`ramqueue`满时也需要特殊处理。`out_char`就是这个进行特殊处理的函数。例如,在屏幕最后一行时收到换行符`out_char()`会调用`scroll_screen()`,如果是转义序列则调用`parse_escape()`来处理字符。`out_char()`调用`flush()`把`ramqueue`的内容复制到视频显示内存中,这是通过汇编例程`mem_vid_copy`实现的。最后一个字符传进`ramqueue`后也会调用`flush()`,确保所有的输出都会显示出来。`flush()`最后会让6845视频控制器芯片显示正确的光标位置。
##### 视频RAM的管理
| 字段 | 含义 |
| -------- | ------------------------------------------------ |
| c_start | 当前控制台显存的起始位位置 |
| c_limit | 当前控制台显存的大小 |
| c_column | 光标所在列的列号 |
| c_row | 光标所在行的行号 |
| c_cur | 光标在显存中的位置 |
| c_org | 6845基址寄存器指向的内存位置即屏幕显示的起始点 |
`console`结构体的`c_start`和`c_limit`字段代表了控制台的视频RAM。当前的光标位则在`c_column`和`c_row`字段。坐标(0,0)在屏幕的左上角,这个位置就是硬件填充屏幕的起点。视频扫描从`c_org`的地址开始延续80x25字节(即4000字节)。这就意味着6845芯片从`c_org`拉取字符,并从屏幕的左上角开始显示它们。先是第一行从(0,0)到(79,0),然后是第二行从(0,1)到(79,1),最后一直到第二十五行(0,24)到(79,24)。
当计算机启动屏幕被清空输出被写入到视频RAM的`c_start`位,此时`c_org`和`c_start`是相等的。当行满或检测到换行符,输出被写入到`cstart + 80`。当25行都满则会要求屏幕滚动。一些程序比如编辑程序还有向下滚动的要求如果此时光标在顶行且要求继续向上移动。
滚动屏幕有两种方式。一种是**软件滚动**,总是把要在(0,0)坐标上显示的字符放在显存的起始位置,`c_start`代表第0个字的位置。`c_org`保存和`c_start`相同的值这样视频控制器芯片就会首先从此位置开始显示了。当屏幕需要滚动时视频RAM上相对位置80的内容要复制到相对位置081则复制到相对位置1以此类推。扫描队列没有改变还是把屏幕上(0,0)位置的数据放在显存的第0个位置视屏图像整体向上移动一行。开销是CPU移动了192080*24个字。一种是**硬件滚动**不是数据在显存里移动而是视频控制芯片从不同的位置开始显示。比如从数据的第80个字符开始显示。把80加进`c_org`里然后把这个值写入视频控制器芯片对应的寄存器里。这要求要么控制器足够聪明能够利用有限的视频RAM进行正确的显示要么视频RAM足够大可以保存多屏的内容。
较老的显卡内存小只能让显示的内容回绕。新显卡内存大控制器不能回绕。比如保存204行需要32768字节的显存(204x80x2)则可滚动179次(204-25)。但触底后还是需要把最后24行复制到显存的顶部。
当开启了虚拟控制台,这些控制台就会均分视频适配器的内存,这是通过设置每个控制台的`c_start`和`c_limit`字段实现的。这会影响到滚屏。尽管硬件滚屏是有效的,但此时一般会使用软件滚屏。当控制台的数量达到可能的最大值时,每次滚屏都将是软件滚屏。
虽然光标位可以通过`c_column`和`c_row`计算出来,但用字段`c_cur`记录它的值会更高效。被打印的字符会放在`c_cur`的位置,然后`c_cur`和`c_column`都被更新。
通过调整`c_column`, `c_row`和`c_cur`就可以处理影响光标位置的字符(比如换行,回退)。`flush()`通过在最后调用`set_6845()`实现此功能。
##### 解析转义序列
Minix3实现了一个有限状态自动机来解析转义序列。控制台结构体里的字段`c_esc_state`一般为0。当`out_char()`检测到`ESC`字符,它就把`c_esc_state`置1后继字符则由`parse_escape()`来处理。`parse_escape()`会依据`c_esc_state`的值来选择相应的操作,如果下一个字符是控制序列引导符`[`则将`c_esc_state`置2否则就认为此序列已完成并调用`do_escape()`。如果是2只要下一个字符是数字则将`c_esc_parmp`转换为`c_esc_parmp*10 `加上该字符对应的数值(因为`c_esc_parmp`的初值为0这就可以把数字的字符序列转化为对应的数值。当下一个字符变成分号则把处理转到`c_esc_parmp`数组的下一个元素(此数组只包含两个元素)。当下一个字符即非数字也非分号,则认为序列已完成,会再次调用`do_escape()`。`do_escape()`依据当前字符来选择采取什么动作,怎么解释参数。
##### 可加载的键盘映射
扫描码是按键的编号按下时产生的扫描码高位是0松开时产生的扫描码高位是0。通过记录按下和松开的状态可产生大量的组合键。
操作系统使用**键盘映射**(keymap)来把键的状态转换为字符码。Minix3的键盘映射从逻辑上看就是一个128行6列的数组。128行代表了可能的扫描码之所以设计的这么大是为了兼容日文键盘。6列分别代表了无修饰键(no modifier)SHIFT键Control键左ALT键右ALT键ALT+SHIFT组合键。这种方案可产生720种字符码`(128-6)*6`。这就要求键盘映射里每个表项都是16位大小。
在`keyboard.c`里通过`#include keymaps/us-std.src`把标准键盘映射编译进了Minix3内核但通过`ioctl(0, KIOCSMAP, keymap)`也可以把不同的映射载入内核地址`keymap`。 一个完整的映射占据1536字节(`128*6*2`)。额外的键盘映射会以压缩形式存储。程序`genmap`用于生成新的压缩的键盘映射。`genmap()`会为指定的键盘映射包含`<keymap>.src`文件,并把压缩的键盘映射输出为文件。`loadkeys`命令读取压缩的键盘映射,把它展开,然后调用`ioctl()`把键盘映射转移到内核内存。Minix3在启动时会自动执行`loadkeys`,用户也可以调用`loadkeys`。
键盘映射的源码在`src/kernel/keymaps/us-std.src`里面定义了一个已经初始化的大数组。扫描码0不对应IBM-PC键盘上的任何一个键。扫描码1对应ESC键当按下SHIFT或CTRL键不影响返回值但ALT和ESC一同按下会返回不同的编码。在`include/minix/keymap.h`里的宏定义了这些返回值的计算方法:
```c
#define C(c) ((c) & 0x1F) /* 映射到控制码 */
#define A(c) ((c) | 0x80) /* 最高位置1 (ALT) */
#define CA(c) A(C(c)) /* Control-Alt */
#define L(c) ((c) | HASCAPS) /* 添加 "大写生效" 属性 */
```
前三个宏通过位运算生成应用程序所需的编码。最后一个宏则是将高字节的第16位置1来使大写生效。观察扫描码2, 13, 16可以分别了解到数字标点符号字母是怎么处理的。扫描码28代表了ENTER键一般来说ENTER键对应ASCII码的CR字符(0x0d),即`C('M')`的运算结果。然而在UNIX里新行是LF字符(0x0a)所以当前键盘映射提供了CTRL-ENTER组合键即`C('J')`来对应`0x0a`。
扫描码29是修饰键CTRL的编码不管按下什么键都不会对它产生影响。扫描码59对应功能键F1它在ASCII表里没有对应的值它所对应的值都定义在`include/minix/keymap.h`里。扫描码127是表里的最后一项表里的靠后的很多项都和它一样。因为很多键盘类型都无法生成那么多的编码所以那些项都被填充为0。
##### 可加载的字体
早期的PC要在显示器屏幕上生成字符只能使用存储在ROM上的生成模型。但现代的显示器在显卡上提供了RAM可用于加载自定义的字符自成模型。Minix3上`ioctl(0, TIOCSFON, font)`可支持此操作。
Minix3支持80列x25行的视频模式字体文件包含4096字节。每个字符使用8x16个像素故描述一个字符需要16个字节。但显卡使用32个字节来映射字符这样就可以支持更高的分辨率了但Minix3现在还不支持这种模式。`loadfont`命令把字体文件转化为`font`结构体,并调用`ioctl()`来加载字体。和键盘映射一样即可以在启动时加载字体也可以在随后任何时间加载字体。每个显卡都在它的ROM里保存了默认字体随时可用。没有必要把字体编译进Minix3在内核里只有`TIOCSFON ioctl`支持必要的字体操作。

View File

@ -0,0 +1,21 @@
为了支持更复杂的屏幕显示许多终端驱动程序都支持ANSI转义序列。
当驱动程序看到ESC字符(0x1B)时,它设置一个标志以接收全部的转义序列,并负责解释转义序列的含义。
有两种类型的转义序列:不包含可变参数的;可能包含参数的。不包含参数的比如`ESC M`。包含参数的以`ESC[`开头,这个`[`就是**控制序列引导码**CSI, **control sequence introducer**)。
| 转义序列 | 含义 |
| -------- | ------------------------------------------------------------ |
| ESC[nA | 向上移动n行 |
| ESC[nB | 向下移动n行 |
| ESC[nC | 向右移动n行 |
| ESC[nD | 向左移动n行 |
| ESC[m;nH | 移动到m行n列 |
| ESC[sJ | 清屏。s=0光标到结尾。s=1,开头到光标。s=2,开头到结尾。 |
| ESC[nL | 插入n行 |
| ESC[nM | 删除n行 |
| ESC[nP | 删除n个字符 |
| ESC[n@ | 插入n个字符 |
| ESC[nm | 开启字符特效。n为数字m为字符'm'。n=0正常n=1加粗n=4下划线n=5闪烁n=7反显。 |
| ESC M | 把光标向上移动一行,如果光标在顶行则向下滚动屏幕 |

143
OS/os_dev/fs-fat.md Normal file
View File

@ -0,0 +1,143 @@
#### fat32的磁盘结构
FAT文件系统=引导记录(512字节) + 文件分配表 + 根目录 + 数据区
#### 引导记录
引导记录=BIOS参数块(0x00-0x23) + 扩展引导记录(0x24-0x1ff)
FSInfo结构体占用512字节扇区号在扩展引导记录里指定。
```rust
pub(crate) struct BootSector {
bootjmp: [u8; 3], // offset:0x00
oem_name: [u8; 8], // offset:0x03
pub(crate) bpb: BiosParameterBlock,
boot_code: [u8; 448], // offset:0x5a
boot_sig: [u8; 2], // offset:0x1fe, 启动分区的签名"0xAA55"
}
pub(crate) struct BiosParameterBlock {
pub(crate) bytes_per_sector: u16, // offset:0x0b
pub(crate) sectors_per_cluster: u8, // offset:0x0d
pub(crate) reserved_sectors: u16, // offset:0x0e
pub(crate) fats: u8, // offset:0x10, 文件分配表的个数一般是2
pub(crate) root_entries: u16, // offset:0x11, 根目录包含多少个条目
pub(crate) total_sectors_16: u16, // offset:0x13
pub(crate) media: u8, // offset:0x15
pub(crate) sectors_per_fat_16: u16, // offset:0x16
pub(crate) sectors_per_track: u16, // offset:0x18
pub(crate) heads: u16, // offset:0x1a
pub(crate) hidden_sectors: u32, // offset:0x1c
pub(crate) total_sectors_32: u32, // offset:0x20
// Extended BIOS Parameter Block
pub(crate) sectors_per_fat_32: u32, // offset:0x24
pub(crate) extended_flags: u16, // offset:0x28
pub(crate) fs_version: u16, // offset:0x2a
pub(crate) root_dir_first_cluster: u32, // offset:0x2c, 根目录的簇号
pub(crate) fs_info_sector: u16, // offset:0x30, FSInfo的扇区号
pub(crate) backup_boot_sector: u16, // offset:0x32
pub(crate) reserved_0: [u8; 12], // offset:0x34
pub(crate) drive_num: u8, // offset:0x40
pub(crate) reserved_1: u8, // offset:0x41
pub(crate) ext_sig: u8, // offset:0x42, 只能是0x28或0x29
pub(crate) volume_id: u32, // offset:0x43
pub(crate) volume_label: [u8; 11], // offset:0x47
pub(crate) fs_type_label: [u8; 8], // offset:0x52, 总是"FAT32"
}
struct FsInfoSector {
free_cluster_count: Option<u32>, // offset:0x1e8
next_free_cluster: Option<u32>, // offset:0x1ec
dirty: bool,
}
impl FsInfoSector {
const LEAD_SIG: u32 = 0x4161_5252; // 先导签名的值offset:0x0
const STRUC_SIG: u32 = 0x6141_7272; // 中间签名的值offset:0x1e4
const TRAIL_SIG: u32 = 0xAA55_0000; // 结尾签名的值offset:0x1fc
...
}
```
#### 文件分配表
文件分配表里的元素为28字节大小代表了当前文件下一个簇的编号。
当元素值大于等于0x0ffffff8代表没有多余的簇了即整个文件已读取完成。
当元素值等于0x0ffffff7代表这个簇是坏的。
当元素值等于0代表这个簇是空闲的。所以0号元素是被保留的。
1号元素是被保留的留做将来使用。其值必须是0xffffffff。
#### 根目录
记录了整个磁盘的结构。是由目录条目组成的数组数组元素为32字节大小。
```rust
pub(crate) struct DirFileEntryData {
name: [u8; SFN_SIZE], // offset:0, SFN_SIZE值为11.
attrs: FileAttributes, // offset:11, u8类型。可以决定当前条目是目录还是文件。
reserved_0: u8, // offset:12
create_time_0: u8, // offset:13
create_time_1: u16, // offset:14
create_date: u16, // offset:16
access_date: u16, // offset:18
first_cluster_hi: u16, // offset:20, 当前文件第一个簇的簇号的高16位。
modify_time: u16, // offset:22
modify_date: u16, // offset:24
first_cluster_lo: u16, // offset:26, 当前文件第一个簇的簇号的低16位。
size: u32, // offset:28, 当前文件占用了多少字节。
}
```
#### rust-fs库
```rust
mod log_macros;
mod boot_sector;
mod dir;
mod dir_entry;
mod error;
mod file;
mod fs;
mod io;
mod table;
mod time;
pub use crate::dir::*;
pub use crate::dir_entry::*;
pub use crate::error::*;
pub use crate::file::*;
pub use crate::fs::*;
pub use crate::io::*;
pub use crate::time::*;
```
##### fs包
```rust
pub struct FileSystem<IO: ReadWriteSeek, TP, OCC> {
pub(crate) disk: RefCell<IO>,
pub(crate) options: FsOptions<TP, OCC>,
fat_type: FatType,
bpb: BiosParameterBlock,
first_data_sector: u32,
root_dir_sectors: u32,
total_clusters: u32,
fs_info: RefCell<FsInfoSector>,
current_status_flags: Cell<FsStatusFlags>,
}
impl<IO: Read + Write + Seek, TP, OCC> FileSystem<IO, TP, OCC> {
pub fn new() // 创建一个文件系统的实例
...
pub fn root_dir() // 返回根目录的对象
}
```

295
OS/os_dev/fs-sfs.md Normal file
View File

@ -0,0 +1,295 @@
# SFS文件系统
## 概览
| 层级 | 描述 | 代码 |
| -------------- | -------------------------------- | -------------------------------------------------------- |
| 索引结点层 | 文件控制块数据结构与处理 | `rcore-fs::vfs::FileSystem` |
| 磁盘块管理层 | 定义磁盘文件系统的数据结构及处理 | `rcore-fs-sfs::SimpleFileSystem` |
| 磁盘数据结构层 | 磁盘上的数据结构及处理 | `rcore-fs-sfs::struct::{SuperBlock,DiskInode,DiskEntry}` |
| 块缓冲层 | 在内存中缓存磁盘数据 | rcore-fs::dev::block_cache::{BlockCache,LRU} |
| 块设备接口层 | 定义读写磁盘的接口 | `rcore-fs::dev::BlockDevice` |
制作sfs镜像
```bash
rcore-fs-fuse sfs.img rootfs zip # 从目录rootfs生成镜像sfs.img
rcore-fs-fuse dir image git-version # 版本验证应为7f5eea
cargo install rcore-fs-fuse --git https://github.com/rcore-os/rcore-fs --rev 7f5eea --force # 安装版本号为7f5eea的rcore-fs-fuse
```
## 磁盘结构
| 名称 | 位置 | 大小 |
| -------------- | ----- | ------------------------- |
| 超级块 | 0号块 | 1个块 |
| 根目录索引结点 | 1号块 | 1个块 |
| 空闲位图区 | 2号块 | SuperBlock.freemap_blocks |
| 索引结点区 | | |
| 数据区 | | |
块大小4k字节
inode直接块12
# rcore-fs
zCore使用的文件系统的库是[rcore-fs](https://github.com/rcore-os/rcore-fs)。
其中rcore-fs是OS可以用到的接口和实用工具。
对于VFS会用到`FileSystem`和`INode`。
对于设备和缓冲层,会用到`BlockDevice`和`BlockCache`。
rcore-fs仅仅是对如下模块的包含
## dev模块
设备接口层和块缓冲层实现
```rust
/// FS读、写的接口
pub trait Device: Send + Sync {
fn read_at(&self, offset: usize, buf: &mut [u8]) -> Result<usize>;
fn write_at(&self, offset: usize, buf: &[u8]) -> Result<usize>;
fn sync(&self) -> Result<()>;
}
/// 它是与底层驱动程序之间的接口,属于设备接口层。定义了对块设备的操作(读和写)。
pub trait BlockDevice: Send + Sync {
const BLOCK_SIZE_LOG2: u8;
// 把块block_id写到缓冲区buf
fn read_at(&self, block_id: BlockId, buf: &mut [u8]) -> Result<()>;
// 把缓冲区buf写入到块block_id
fn write_at(&self, block_id: BlockId, buf: &[u8]) -> Result<()>;
fn sync(&self) -> Result<()>;
}
```
### block_cache模块
块缓冲层实现采用LRU算法
```rust
/// 定义块缓存
pub struct BlockCache<T: BlockDevice> {
device: T, // 通过它进行块的读写
bufs: Vec<Mutex<Buf>>, // 代表内存中的缓冲区
lru: Mutex<LRU>,
}
/// BlockCache的元数据
struct Buf {...}
enum BufStatus {...}
impl<T: BlockDevice> BlockCache<T> {
pub fn new() // 创建块缓存,把块上的数据读到缓冲区
fn get_buf() // 获取block_id对应的缓冲区地址
fn _get_buf()
fn get_unused()
fn write_back()
}
impl<T: BlockDevice> Drop for BlockCache<T> {
fn drop(&mut self) {
BlockDevice::sync(self).expect("failed to sync");
}
}
impl<T: BlockDevice> BlockDevice for BlockCache<T> {
fn read_at()
fn write_at()
fn sync()
}
/// 双向链表的LRU算法实现
struct LRU {
prev: Vec<usize>,
next: Vec<usize>,
}
impl LRU {
fn new()
fn visit()
fn victim()
fn _list_remove()
fn _list_insert_head()
}
```
### std_impl模块
## dirty模块
用于读写`dirty`标志。
## file模块
实现了`File`对象。
```rust
pub struct File {
inode: Arc<dyn INode>,
offset: usize,
readable: bool,
writable: bool,
}
impl File {
pub fn new()
pub fn read()
pub fn write()
pub fn info()
pub fn get_entry()
}
```
## util模块
## vfs模块
```rust
/// 抽象的文件系统对象,代表了文件或目录
pub trait INode {}
impl dyn INode {}
/// INode的元数据
pub struct Metadata {...}
pub struct Timespec {...}
pub enum FileType {...}
/// FileSystem的元数据
pub struct FsInfo {...}
pub enum FsError {...}
impl fmt::Display for FsError {...}
impl From<DevError> for FsError {...}
/// 抽象的文件系统
pub trait FileSystem: Sync + Send {
fn sync(&self) -> Result<()>; // 同步数据到存储
fn root_inode(&self) -> Arc<dyn INode>; // 获取根索引节点
fn info(&self) -> FsInfo; // 获取文件系统的信息
}
```
# rcore-fs-sfs库
```rust
trait DeviceExt: Device {...}
impl DeviceExt for dyn Device {}
/// SFS的INode
pub struct INodeImpl {...}
impl Debug for INodeImpl {...}
impl INodeImpl {...}
impl vfs::INode for INodeImpl {...}
impl Drop for INodeImpl {...}
/// SFS的文件系统
pub struct SimpleFileSystem {...}
impl SimpleFileSystem {
pub fn open() -> vfs::Result<Arc<Self>> {} // 从设备载入SFS
pub fn create
fn wrap
fn alloc_block
fn free_block
pub fn new_device_inode
fn _new_inode
fn get_inode
...
}
impl vfs::FileSystem for SimpleFileSystem {...}
impl Drop for SimpleFileSystem {...}
trait BitsetAlloc {
fn alloc(&mut self) -> Option<usize>;
}
impl BitsetAlloc for BitVec<Lsb0, u8> {...}
impl AsBuf for BitVec<Lsb0, u8> {...}
impl AsBuf for [u8; BLKSIZE] {}
impl From<FileType> for vfs::FileType {...}
```
## strust子模块
描述磁盘上的结构
```rust
/// 超级块
pub struct SuperBlock {
pub magic: u32, // offset:0x0必须是0x2f8dbe2b
pub blocks: u32, // offset:0x4
pub unused_blocks: u32, // offset:0x8
pub info: Str32, // offset:0xc, 占32个字节默认值"simple file system"
pub freemap_blocks: u32, // offset:0x2c
}
/// 磁盘上的索引结点
pub struct DiskINode {
pub size: u32, // 文件有多少个字节,对目录则无效
pub type_: FileType, // 1:文件2:目录3:符号链接4:字符设备5:块设备
pub nlinks: u16, // 硬链接数。包括"."和".."
pub blocks: u32, // 文件占多少个块
pub direct: [u32; NDIRECT], // 直接块的数组
pub indirect: u32, // 间接块的地址
pub db_indirect: u32, // 双间接块的地址
pub device_inode_id: usize, // 用于设备,记录(主设备号,次设备号)
pub atime: Timespec, // 最后访问时间
pub mtime: Timespec, // 最后修改时间
pub ctime: Timespec, // 最后改变时间
}
pub type DeviceINode = dyn vfs::INode;
pub struct IndirectBlock {
pub entries: [u32; BLK_NENTRY],
}
/// 目录项,即目录文件的内容。一个目录项就代表了一个文件或子目录。
pub struct DiskEntry {
pub id: u32, // 索引结点号
pub name: Str256, // 文件名占256个字符
}
impl SuperBlock {
pub fn check() // 检查魔数是否匹配
}
impl DiskINode {
pub const fn new_file()
pub const fn new_symlink()
pub const fn new_dir()
pub const fn new_chardevice()
}
```
# rcore-fs-hostfs库
```rust
/// 宿主机上的文件系统
pub struct HostFS {
path: PathBuf,
self_ref: Weak<HostFS>,
}
impl HostFS {
pub fn new() -> Arc<HostFS> {} // 创建HostFS
fn wrap() -> Arc<Self> {} // new方法用此方法把HostFS打包为Arc
}
```
# rcore-fs-sefs
简单加密文件系统
# rcore-fs-fuse程序
main执行过程
1. `env_log::init()`初始化日志系统。
2. 参数如果是zip则将`create`设为`true`表示创建镜像如果是unzip则将`create`设为`false`表示解压镜像如果是git-version则打印git commit号。

125
OS/zCore/文件系统.md Normal file
View File

@ -0,0 +1,125 @@
# zCore::fs子模块
`zCore/src/fs.rs`为`main()`函数提供了`fs::rootfs()`方法来生成文件系统的对象。在linux_libos模式下是获取HostFS在linux_bare模式下是从设备载入SFS。
- linux_libos模式下的rootfs()
先指定根目录`rootfs`的值再创建一个HostFS对象。
- linux_bare模式下的rootfs()
先指定设备`device`的值再从设备载入SFS对象。
- init_ram_disk()函数
1. 如果定义了`link-user-img`,则根据内存镜像的指针和大小来生成片断。
2. 否则返回初始化的RAM盘的片断。
# linux_object::fs子模块
`linux_object/src/fs`实现了fs模块是文件系统具体的实现者。
```rust
mod devfs;
mod file;
mod ioctl;
mod pipe;
mod pseudo;
mod stdio;
pub trait FileLike: KernelObject {
...
}
/// 创建根文件系统挂载DevFS和RamFS。
pub fn create_root_fs() {...}
/// 把path分隔成(base_path, filename)
pub fn split_path(path: &str) -> (&str, &str) {...}
```
## devfs模块
```rust
mod fbdev;
mod input;
mod random;
mod uartdev;
pub use fbdev::FbDev;
pub use input::{EventDev, MiceDev};
pub use random::RandomINode;
pub use uartdev::UartDev;
```
### input模块
为MiceDev和EventDev实现INode
### fbdev模块
为framebuffer实现INode
### random模块
为RandomINode实现INode
### uartdev模块
为UartDev实现INode
## device模块
看起来没什么用的模块,没有在其它地方被使用。应该是`rcore_fs_wrapper`模块取代了此模块的地位。
## file模块
对文件的各种操作。
## ioctl模块
定义了IOR和IOW用到的一些常数。
## pipe模块
为管道实现INode
## pseudo模块
为伪文件系统实现INode
## stdio模块
为Stdin和Stdout实现INode
## rcore_fs_wrapper模块
是对`rcore_fs::dev::Device`的实现,这样就可以从设备载入文件系统了。
```rust
/// 内存上的缓冲区这个缓冲区是for device的
pub struct MemBuf(RwLock<&'static mut [u8]>);
impl MemBuf {
pub fn new () -> Self {} // 创建一个Membuf结构体
}
impl Device for MemBuf {
fn read_at()
fn write_at()
fn sync()
}
/// 实现了BlockScheme的块设备
pub struct Block(Arc<dyn BlockScheme>);
impl Block {
pub fn new() -> Self {} // 创建一个Block结构体
}
impl BlockDevice for Block {
fn read_at()
fn write_at()
fn sync()
}
```

View File

@ -15,7 +15,7 @@ add <name> <url> # 添加远程仓库,url是远程仓库的地址,name是远程
rename <old> <new> # 将远程仓库的名称从old改为new
remove <name> # 删除对远程仓库name的追踪
set-head <name> # 为远程仓库设置或删除默认分支
set-branches <name> <branch> # 改变远程仓库的分支列表
set-branches [--add] <name> <branchs> # 改变远程仓库<name>所要追踪的分支列表。用于在初始化远程仓库之后追踪指定的分支。
show <name> # 显示远程仓库name与本地仓库之间分支的对应关系
get-url <name> # 检索远程分支的url
set-url <name> <newurl> # 改变远程分支的url
@ -38,5 +38,8 @@ git push
git remote add myrepo https://gitee.com/myname/newrepo.git
git fetch myrepo
git push myrepo local-branch
# 仅追踪远程仓库abc的master分支
git remote set-branches abc master
```

View File

@ -23,5 +23,7 @@ git reset <mode> [<commit>] # 将当前分支的head指向commit
```
git reset HEAD <file> # 撤销对暂存区的修改
git reset HEAD^ # 撤销最近一次提交
git reset HEAD^^^ # 撤销最近三次提交
git reset HEAD~3 # 撤销最近三次提交
```

View File

@ -1,3 +1,7 @@
#### 简介
子模块就是将一个仓库作为另一个仓库的子目录
```
git submodule <command> [options] # 初始化,更新或分析子模块
```
@ -63,14 +67,19 @@ update [options] [--] [<path>...]
可以查看.gitmodules文件查看文件夹与子模块的对应关系
```shell
# 子模块就是将一个仓库作为另一个仓库的子目录
git submodule add <repo> <path> # 添加子模块
# 添加子模块
git submodule add <repo> [path]
# 克隆包含子模块的项目,默认包含子模块的目录,但目录内没有任何文件,需要如下命令使子模块充实起来
git submodule init # 初始化子模块
git submodule update # 更新子模块
# --init --recursive = "git submodule init" + "git submodule update --recursive"
# 删除子模块
git submodule deinit [path]
git rm --cached [path]
vim .gitmodules # 删除[path]对应的条目
rm -r .git/modules/[path] # 删除脏文件
```
#### 错误解决
@ -91,4 +100,11 @@ git submodule update # 更新子模块
cd .. && git add <submodule> && git commit -m ""
```
3. fatal: No url found for submodule path 'xxx' in .gitmodules
```
# 可能是仓库的原主人需要子模块xxx,但我不需要它,我只要把它删除即可
git rm --cached xxx
```

View File

@ -26,7 +26,7 @@ git [optons] <command> [args]
#### 选项
```
-C <path> # 指定git命令的执行目录。默认为当前目录。
```
#### 高层命令(porcelain)
@ -195,4 +195,4 @@ blob对象是通过**tree对象**组织在一起的。一个tree对象由一条
一个**reference**是对一个commit取了个别名方便记忆。HEAD文件保存了当前分支的reference这样在分支切换的时候就可以指向最后一次提交的commit ID。
一个**tag对象**是对一个commit的引用。共有两种taglightweight和annotated。lightweight tag仅仅是对一个commit的引用。annotated tag则会创建一个tag对象此tag对象指向commit。
一个**tag对象**是对一个commit的引用。共有两种taglightweight和annotated。lightweight tag仅仅是对一个commit的引用。annotated tag则会创建一个tag对象此tag对象指向commit。

View File

@ -33,13 +33,19 @@ close <name> # 删除映射的name并消除内核空间里的key。
##### resize
```
resize [options] <name> # 修改<name>的大小,其中<name>是一个活动的映射。
```
##### refresh
##### reencrypt
#### PLAIN
#### PLAIN模式
#### LUKS
#### LUKS扩展
##### luksFormat
@ -56,13 +62,13 @@ luksOpen <dev> <name> # 旧语法
#### loop-AES
#### loop-AES扩展
#### TCRYPT
#### TCRYPT扩展
#### BITLK
#### BITLK扩展
#### 杂项
#### 其它动作
#### 选项

View File

@ -7,6 +7,11 @@ gzip [options] [names] # 压缩或解压
#### 选项
```
-[num], --fast, --best # 用[num]控制压缩速度。-1相当于--fast,代表最快但最弱的压缩。-9相当于--best,代表最慢但最强的压缩。[num]默认为6。
-c, --stdout, --to-stdout # 输出到标准输出,这样就可以保持原文件不变。
-d, --decompress, --uncompress # 解压
-f, --force # 强制压缩或解压缩,不管文件是否有多个链接,不管相关文件是不存在,不管压缩数据是读取自还是写入到终端。
-q, --quiet # 抑制所有的警告。
-v, --verbose # 详细模式。
```

View File

@ -13,7 +13,7 @@ mv [option]... -t DIRECTORY SOURCE... # 将SOURCES移动到DIRECTORY
#### 选项
```
--backup
--backup[=CONTROL] # 如果目标文件已存在,则生成备份文件
-b # 类似于--backup但不接收参数
-f # 在覆盖前不提示
-i # 在覆盖前提示
@ -29,3 +29,6 @@ mv [option]... -t DIRECTORY SOURCE... # 将SOURCES移动到DIRECTORY
--version
```
#### 注意
1. `mv`命令默认不移动隐藏文件或目录,除非显式地指定它。

View File

@ -35,6 +35,10 @@ sudo apt install ttf-mscorefonts-installer
sudo cp -r fonts_dir /usr/share/fonts # 直接把字体文件夹复制到对应目录下即可
```
#### 审阅
review -> track changes -> track changes开启审阅。
#### 问题解决
1. 无法输入中文

View File

@ -50,26 +50,30 @@ qemu-system-riscv64 [options] [disk_image]
# 使用"-device help"获取可用的设备名。使用"-device <drive>,help"获取某个设备的可用属性。
# 可用的prop有
## Controller/Bridge/Hub设备
# usb-host:bus usb-bus
usb-host:bus usb-bus
## USB设备
# qemu-xhci:bus PCI
qemu-xhci:bus PCI
## 存储设备
# virtio-9p-pci:bus PCI, "virtio-9p"的别名
# virtio-blk-pic:bus PCI, 是"virtio-blk"的别名
virtio-9p-pci:bus PCI, "virtio-9p"的别名
virtio-blk-pic:bus PCI, 是"virtio-blk"的别名
## 网络设备
# e1000 : bus PCI,"e1000-82540em"的别名,desc "Intel Gigabit Ethernet"
e1000 : bus PCI,"e1000-82540em"的别名,desc "Intel Gigabit Ethernet"
## 输入设备
# isa-serial:bus ISA
# usb-kbd:bus usb-bus
# usb-mouse:bus usb-bus
isa-serial:bus ISA
usb-kbd:bus usb-bus
usb-mouse:bus usb-bus
## 显示设备
## 声音设备
## Misc设备
# ich9-intel-hda:bus PCI, desc "Intel HD Audio Controller (ich9)
# intel-hda:bus PCI, desc "Intel HD Audio Controller (ich6)"
# intel-iommu : bus系统desc "Intel IOMMU (VT-d) DMA Remapping device"
# vhost-vsock-pci : bus PCI
ich9-intel-hda:bus PCI, desc "Intel HD Audio Controller (ich9)
intel-hda:bus PCI, desc "Intel HD Audio Controller (ich6)"
intel-iommu : bus系统desc "Intel IOMMU (VT-d) DMA Remapping device"
loader : desc"通用loader"
# file=<str> : 需指定<str>,代表要装载的文件
# addr=<uint64> : 默认值0, 代表要装载的地址
vhost-vsock-pci : bus PCI
## CPU设备
## 看门狗设备
## 未分类的设备
-global driver.property=value
-global driver=driver, property=property, value=value
@ -554,3 +558,15 @@ other:包括VMDK, VDI, VHD (vpc), VHDX, qcow1 and QED
解决方法:使用`strace`命令观察`qemu-system-*`执行过程的输出,发现打开了一个非系统的`libusb.so`库删除那个库所属的应用再次安装qemu问题解决
原因分析:使用了不正确的`libusb.so`库
##### 问题三
问题描述:`-kernel`、` -device loader`、` -bios`的区别。来源:[stackoverflow](https://stackoverflow.com/questions/58420670/qemu-bios-vs-kernel-vs-device-loader-file)
解答:
`-kernel`的意思是装载Linux内核。它会为当前架构使用最佳的装载和启动方式。对于x86架构它只是把文件提供给BIOS由BIOS做实际的装载工作。对于ARM怎么加载内核取决于内核启动规则。
`-device loader`是一个“通用loader”它在所有架构上都是相同的行为。它仅仅是把ELF镜像加载进内存。如果一个镜像支持裸机启动应该使用此选项。
`-bios`的意思是装载BIOS镜像依据机器模型和架构而不同。x86架构必须要加载BIOS在用户没有指定的情况下会加载默认的二进制。如果是arm virt开发板用户指定此选项才加载BIOS否则不加载。注意BIOS镜像不应该是ELF文件而应该是原始的二进制文件因为它们只是放进ROM或闪存中的原始数据是硬件最初启动时的那段代码。还可以采用更正规的方式来指定BIOS镜像比如`-drive if=pflash...`这样。

View File

@ -0,0 +1,8 @@
```bash
ulimit [options] [limit] # 提供对shell及其子进程可用资源的控制
# 选项
-S # “软件”资源的限制
-H # “硬件”资源的限制
-a # 报告所有的限制
```

View File

@ -11,3 +11,13 @@ pftp [options] [host [port]]
open <host> [port] # 连接到ftp服务器
```
#### 安装tftp服务进程
```
# 注tftp是Trivial File Transfer Protocol简单的文件传输协议
# for debian
sudo ufw allow tftp # 打开tftp端口
sudo apt install tftpd-hpa # 安装tftp服务进程
# 配置文件在/etc/default/tftpd-hpa
```

View File

@ -36,4 +36,9 @@ mkimage [options] [image-name] # 创建传统格式的镜像
##### 创建FIT镜像
##### 创建FIT镜像
```
-f [source-file | auto] # [source-file]描述了FIT镜像的结构和内容。对于一些简单的情况可以使用[auto]来自动生成,这相当于使用了-d, -A, -O, -T, -C, -a, -e, 且不需要指定 .its 文件。
```

View File

@ -2,6 +2,8 @@
从文件名中删除目录和后缀。会删除名称中的所有先导目录。如指定的话,也可删除后面的后缀。
注:如果要留下的是目录则使用`dirname`命令。
#### 语法
```

View File

@ -1,5 +1,6 @@
```
dirname [option] <names> # 给出name所在的目录。如name不包含'/',则输出'.'以表明在当前目录。
# 注如果要的是文件名则使用basename命令。
```
选项

View File

@ -0,0 +1,7 @@
#### 快捷键
- Ctrl + Alt + 方向 切换窗口
- Ctrl + Alt + Home/End :把应用程序移动到其它窗口
- Print : 打开截图软件
- Shift + Print : 选定区域截图
- Alt + Print : 对活跃窗口截图

View File

@ -185,13 +185,62 @@ update [options] # 把Cargo.lock里的依赖更新到最新的版本。Cargo.loc
#### 包命令
##### search
```
init [options] [path] # 创建Cargo包
install # 构建和安装一个rust库。
new # 创建一个cargo包
search [options] [query...] # 从crates.io查找包
```
##### new
```
new [options] <path> # 创建一个cargo包
--bin # 默认选项。包里会有src/main.rs(二进制目标)。
--lib # 包里会有src/lib.rs(库目标)。
search [options] [query...] # 从crates.io查找包
```
##### init
```
init [options] [path] # 基于已有目录创建Cargo包
# 初始化选项
--bin # 创建带有二进制目标(src/main.rs)的包。这是默认行为。
--lib # 创建带有库目标(src/lib.rs)的包。
--edition <edition> # 定义要使用的Rust版本。默认2021。
--name <name> # 设置包名。默认为目录名。
--vcs <vcs> #
--registry <reg> #
# 显示选项
-v
-q
--color <when>
# 通用选项
+toolchain
--config <KEY=VALUE>
-h
-Z <flag>
```
##### install
构建和安装一个rust二进制文件。只有包含**bin**和**example**对象的包才能被安装,所有的可执行文件都被安装在`bin`目录。
```
install [options] <crate>[@version] # 从crate.io安装
install [options] --path <path> # 从本地路径安装
install [options] --git <url> [crate] # 从git仓库安装
install [options] --list
# 安装选项
--git <url> # 从<url>来安装指定的库。
--rev <sha> # 从git仓库安装的时候指定commit号
--list # 列出所有已安装的包和它们的版本号
-f, --force # 如果库或二进制文件已存在,则覆盖它们。
# feature选项
# 编译选项
# 清单选项
# 其它选项
# 显示选项
# 普通选项
```

View File

@ -8,6 +8,7 @@ gcc一般会进行预处理编译汇编连接四步。
```
-c # 不运行连接器,这样就只生成汇编器生成的目标文件。
-pipe # 各编译阶段之间使用管道通信,而不是使用临时文件通信。
-S|-E #
-specs=<file> # 编译器读取标准specs文件后再处理<file>,用来覆盖传给cc1,cc1plus,as,ld等的默认开关。
-v # 打印各个执行阶段所运行的命令。
@ -16,8 +17,11 @@ gcc一般会进行预处理编译汇编连接四步。
##### C语言选项
```
-ansi # C模式下等价于-std=c90。C++模式下,等价于-std=c++98。
-fno-buitin # 不使用C语言的内建函数
-std=<standard> #
-std=<standard> # 定义语言的标准。
# c90 - ISO C90标准。
# c99 - ISO C99标准。
```
##### C++语言选项
@ -38,6 +42,16 @@ gcc一般会进行预处理编译汇编连接四步。
-w # 不生成警告信息
-Wall # 生成所有警告信息
-Werror # 让所有的警告都变成错误
-Werror=<switch> # 让<switch>代表的警告变成错误。
# implicit-function-declaration
# implicit-int
# pointer-sign # 使用不同的签名对指针参数进行传递或赋值
# pointer-arith # 关于函数类型的大小或"void"的计算
-Wno-missing-braces
-Wno-overflow # 当常量表达式在编译时溢出,不进行警告。
-Wno-unknown-pragmas
-Wno-unused # 对于所有unused-*选项,都不进行警告。
-Wno-unused-function
-Wunused-function #
-Wunused-label # 当一个标签只声明而不使用时进行警告
-Wunused-parameter # 当函数参数未使用时进行警告
@ -61,9 +75,11 @@ gcc一般会进行预处理编译汇编连接四步。
-ffuction-sections
-fdata-sections
-fomit-frame-pointer # 在不需要frame指针的函数中忽略frame指针。
-fno-builtin # 不以__builtin_为前缀的函数不被认为是内置函数(built-in function)
-fno-omit-frame-pointer # 看起来与-fomit-frame-pointer相反。
-foptimize-sibling-calls # 优化同级和尾部递归调用。
-fno-optimize-sibling-calls # 看起来与-foptimize-sibling-calls相反。
-frounding-math # 对于默认的浮点舍入行为,禁止转换和优化。
-fno-stack-protector # 禁用堆栈保护
-O0 # 不进行优化
-O1或-O # 缺省优化
@ -97,6 +113,7 @@ gcc一般会进行预处理编译汇编连接四步。
-pie # 产生一个动态链接的位置无关可执行文件。为获得一个可预测的结果,还需要指定编译时对应的选项(-fpic, -fPIE, 或模型子选项)。
-no-pie # 不要产生动态链接的位置无关可执行文件。
-l # 指定要链接的库。默认为系统的库的路径。可查看/etc/ld.so.conf文件获取系统库路径的详情。
-s # 从可执行文件里删除所有的符号表和重定位(relocation)信息。
-static # 覆盖-pie选项且不链接到共享库。
-T # 指定链接脚本。在没有操作系统的裸板上,可能需要-T选项来避免对未定义符号的引用。可以在搜索“lds链接脚本”关键字得到更详细信息。
-Wl,option # 把option作为选项传递给链接器。如果option里包含逗号传递给链接器的时候逗号会被替换为空格这个语法的好处是可以传递多个选项过去或者给选项指定一个参数。

View File

@ -70,3 +70,7 @@ elf64lriscv elf64lriscv_lp64f elf64lriscv_lp64 elf32lriscv elf32lriscv_ilp32f el
描述:/usr/bin/ld: cannot find -lxxxxx
原因分析1系统内是否有这个库`apt search libxxxxx-dev`2gcc能否搜索到这个库`gcc -lxxxxx --verbose`3gcc的搜索路径是否包含了该库文件查看`/etc/ld.so.conf`文件或`LD_LIBRARY_PATH`环境变量。若修改`LD_LIBRARY_PATH`环境变量无效,还需要修改`LIBRARY_PATH`环境变量。
##### 动态分配只读段
描述read-only segment has dynamic relocations

View File

@ -70,3 +70,11 @@ make SHELL="/bin/bash -x" # 要了解详细的编译过程,但"make -n"又失
1. 不管执行什么命令都提示No such file or directory.
原因分析因为我在Makefile里错误地设置了PATH环境变量导致make把那些命令都理解成了当前目录下的文件。
2. 描述recipe commences before first target.
原因分析第一个以冒号结尾的字符串前面有空白字符make命令把这个字符串识别为target了。
3. 描述:使用`ifeq`后提示`*** **missing separator**. Stop.`
原因分析:形如`ifeq(...)`是不行的,在`ifeq`和`(`之间需要有个空格。

View File

@ -129,8 +129,3 @@ rustup component add llvm-tools-preview
2 crates.io的索引无法更新
解决方法参考cargo的"换源"

View File

@ -0,0 +1 @@
debian里没有`cdrecord`命令,应使用`xorrecord`命令。

View File

@ -1 +0,0 @@
在Debian软件包里没有此应用应使用`xorrisofs`命令。

View File

@ -2,9 +2,19 @@
在Linux下创建MS-DOS文件系统
```
mkfs.fat [options] <dev> [block-count]
# <dev> : 设备文件或镜像文件。当使用-C选项时镜像文件可以不存在。
# [block-count] : 设备上块的数量。每个块固定为1024字节与扇区大小或簇(cluster)大小无关。当使用-C选项后必须指定此参数。
```
#### 选项
```bash
-C # 创建<dev>对应的镜像文件这样就不需要再用dd命令来创建文件了。使用此选项后必须指定[block-count]。
-F <FAT-SIZE> # 指定文件分配表的类型(12, 16或32位)。
-n <volume-name> # 指定卷的名称。
```

View File

@ -0,0 +1 @@
在Debian软件包里没有此应用`mkisofs`,应使用`xorrisofs`命令。

View File

@ -0,0 +1,30 @@
```
xorrecord [options] dev=<device> [track_source] # 向CD, DVD, BD写入格式化的数据。
```
选项
```
# 定位设备
dev=<device> # 设置要使用设备的libburn地址。比如dev=/dev/sr0。
# 查询设备
# 烧写相关的设置
blank=<mode> # 让CD-RW或DVD-RW变为空的这样就能重新使用它了。
-eject # 工作完成后弹出驱动器托盘。
fs=<size> # 设置fifo缓冲区的大小。默认为4m。
padsize=size #
speed=<value> # 设置写的速度。
# 其它设置
-v # 详细模式。
# 与cdrecord不兼容的设置
```
示例
```
# 浏览设备
xorrecord --devices
# 写入单个iso镜像
xorrecord -v dev=/dev/sr0 speed=12 fs=8m blank=as_needed -eject padsize=300k my_image.iso
```

View File

@ -24,7 +24,7 @@ xorrisofs [options] [-o filename] <pathspecs>
##### 标准的扩展
```
-J, --joliet # 除了Rock Ridge目录树再添加Joliet目录树。
-J, --joliet # 除了Rock Ridge目录树再添加Joliet目录树。为了与Windows兼容。
-r, --rational-rock # 类似于-R。不同之处是它不保留文件的属主信息而是把用户id和组id都置为0并且文件的访问权限是只读。
-R, --rock # 开启Rock Ridge扩展。对于xorrisofs开说是默认开启的。
```