├── Build-And-Run.md ├── Home.md ├── MMU初始化和使能主要代码.pdf ├── README.md ├── SynestiaOS基础设施:互斥量.md ├── SynestiaOS基础设施:原子操作.md ├── SynestiaOS基础设施:基本数据类型和宏定义.md ├── SynestiaOS基础设施:堆管理.md ├── SynestiaOS基础设施:自旋锁.md ├── SynestiaOS基础设施:链表List.md ├── SynestiaOS基础设施:队列KQueue.md ├── SynestiaOS文件系统:VFS顶层部分.md ├── SynestiaOS:内存分配和初始化.md ├── SynestiaOS:内核栈使用.md ├── SynestiaOs基础设施:信号量.md ├── Synestia进程管理:进程基础设施.md ├── _Footer.md ├── imgs ├── Heap │ ├── heap1.png │ ├── heap2.png │ ├── heap3.png │ ├── heapalloc1.png │ ├── heapalloc2.png │ ├── heapfree1.png │ └── heapfree2.png ├── KQueue │ ├── dequeue.png │ └── enqueue.png ├── List │ ├── example.png │ └── example2.png ├── env │ ├── clion_cmake.png │ ├── clion_debug.png │ ├── debug.png │ ├── external_tool.png │ ├── reset_cmake.png │ └── run_qemu.png ├── mem │ ├── PTE.png │ └── pagetable.png └── stack │ └── stackalloc.png ├── kernel_vm_map.png └── 开发环境搭建.md /Build-And-Run.md: -------------------------------------------------------------------------------- 1 | # Prepare Environment 2 | ## Clion 3 | [Clion](https://www.jetbrains.com/clion) is recommend to used as our development environment, and use docker to build the kernel. 4 | under this **Docker** dir, run following command to setup dev environment 5 | ``` bash 6 | docker-compose up -d dev-prebuilt 7 | ``` 8 | within the docker container, we setup the ssh environment, and we can connect to the container through ssh. 9 | ``` bash 10 | docker-compose exec dev-prebuilt /bin/bash 11 | #> cd Build 12 | #> cmake .. && make 13 | ``` 14 | within Clion, Settings->Build,Execution,Deployment->Toolchain, click add a remote host, 15 | and input the credentials, user: root, password: password. and it's done. 16 | 17 | ## for linux: 18 | ``` bash 19 | sudo bash Scripts/SetupLinux.sh 20 | ``` 21 | ## for windows: 22 | 23 | ## for docker: 24 | under this project dir, run following command to setup dev environment 25 | ``` bash 26 | docker-compose up -d dev-prebuilt 27 | ``` 28 | 29 | # code formatting 30 | under this project dir, run following command to format the code 31 | ``` bash 32 | docker-compose run dev-prebuilt bash run-clang-foramt.sh 33 | ``` 34 | # Build, Run and Debug 35 | within the docker container, under the Build dir, run following command to build kernel 36 | ``` bash 37 | cmake .. && make VERBOSE=1 38 | ``` 39 | if you are using mac, specify the cross compile toolchain in CMake/ToolchainMacArm.cmake, 40 | and specify the toolchain file location, then install cmake with command `brew install cmake` 41 | 42 | ``` bash 43 | cmake -DCMAKE_TOOLCHAIN_FILE=${PATH_TO_THIS_PROJECT}/SynestiaOS/CMake/ToolchainMacArm.cmake -DPLATFORM=pi3 .. && make VERBOSE=1 44 | ``` 45 | 46 | To run kernel, you can make a dir called Build and go into it, and install qemu system with command `brew install qemu`, then run: 47 | 48 | ``` bash 49 | qemu-system-arm -M raspi2 -kernel bin/Kernel.elf -nographic -serial mon:stdio #for raspi2 and arm32 50 | qemu-system-aarch64 -M raspi3 -kernel bin/Kernel.elf -nographic -serial mon:stdio #for raspi3 and arm64 51 | ``` 52 | 53 | To run kernel unit tests, you can find the kernel unit tests image under Build/ 54 | 55 | ``` bash 56 | qemu-system-arm -M raspi2 -kernel bin/KernelUnitTests.elf -nographic -serial mon:stdio #for raspi2 and arm32 57 | qemu-system-aarch64 -M raspi3 -kernel bin/KernelUnitTests.elf -nographic -serial mon:stdio #for raspi3 and arm64 58 | ``` 59 | 60 | To clean workspace: 61 | ``` bash 62 | make clean 63 | ``` 64 | 65 | To debug kernel, arm32 for instance: 66 | openup terminal 1: 67 | 68 | ``` bash 69 | qemu-system-arm -M raspi2 -kernel Kernel.img -s -S -nographic 70 | ``` 71 | openup terminal 2: 72 | 73 | ``` bash 74 | gdb-multiarch Kernel.img 75 | (gdb) target remote :1234 76 | (gdb) display/i $pc 77 | (gdb) break _start 78 | (gdb) c 79 | (gdb) si 80 | ``` 81 | 82 | # Project Management 83 | [JIRA](https://synestiaos.atlassian.net/) 84 | -------------------------------------------------------------------------------- /Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the SynestiaOS wiki! 2 | -------------------------------------------------------------------------------- /MMU初始化和使能主要代码.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/MMU初始化和使能主要代码.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This folder is for docs. -------------------------------------------------------------------------------- /SynestiaOS基础设施:互斥量.md: -------------------------------------------------------------------------------- 1 | 互斥信号量mutex是一种特殊的信号量,它的实现是在信号量的基础上做出了一些限制,mutex在某一时刻只允许有一个持有者,count的值只能是0或者1,1代表资源不可用,而0代表可用。互斥信号量使用的情景比计数信号量的情景要多。 2 | 3 | 信号量的定义位于:`SynestiaOS/SourceCode/Kernel/include/mutex.h` 和 `SynestiaOS/SourceCode/Kernel/src/mutex.c` 中。 4 | 5 | 6 | 7 | ### mutex的定义 8 | 9 | ```c 10 | typedef struct Mutex { 11 | Atomic *val; 12 | KQueue *waitQueue; 13 | } Mutex; 14 | ``` 15 | 16 | - mutex定义和信号量的定义相同,只不过 `count` 改成了 `Val`。只是在操作上加以限制,类似于链表和栈的关系。 17 | 18 | 19 | 20 | ### 创建mutex 21 | 22 | ```c 23 | void mutex_create(Mutex *mutex, Atomic *atomic) { 24 | mutex->val = atomic; 25 | mutex->waitQueue = nullptr; 26 | atomic_create(atomic); 27 | } 28 | ``` 29 | 30 | - 这里不需要强制原子变量 Val 的大小是1,因为在mutex的操作中会解决大小问题。 31 | 32 | 33 | 34 | ### 获取mutex 35 | 36 | ```c 37 | bool mutex_acquire(Mutex *mutex) { 38 | if (atomic_get(mutex->val) == 0) { 39 | atomic_set(mutex->val, 1); 40 | return true; 41 | } else { 42 | // can not get the lock, just add to lock wait list 43 | KernelStatus enQueueStatus = kqueue_enqueue(mutex->waitQueue, ¤tThread->threadReadyQueue); 44 | if (enQueueStatus != OK) { 45 | LogError("[Mutex]: thread add to wait list failed. \n"); 46 | return false; 47 | } 48 | 49 | // reomve from schd list 50 | KernelStatus removeStatus = schd_remove_from_schduler(currentThread); 51 | if (removeStatus != OK) { 52 | LogError("[Mutex]: thread remove from schd list failed. \n"); 53 | return false; 54 | } 55 | 56 | // 2. switch to the next thread in scheduler 57 | KernelStatus thradSwitchNextStatus = schd_switch_next(); 58 | if (thradSwitchNextStatus != OK) { 59 | LogError("[Mutex]: thread switch to next failed. \n"); 60 | } 61 | return false; 62 | } 63 | } 64 | ``` 65 | 66 | - 如果检测到mutex的Val属性不等于0,就代表有人拿到mutex,此时当前线程就需要被调度下CPU,和信号量操作类似,会进行添加到等待队列,移除出运行队列,切换到下一个线程等操作。 67 | - 如果检测到mutex的Val属性等于0,说明锁没有被占有,此时设置Val字段为1,代表mutex已经被占有。 68 | 69 | 70 | 71 | ### 释放mutex 72 | 73 | ```c 74 | void mutex_release(Mutex *mutex) { 75 | atomic_set(mutex->val, 0); 76 | KQueue *queueNode = kqueue_dequeue(mutex->waitQueue); 77 | Thread *releasedThread = getNode(queueNode, Thread, threadReadyQueue); 78 | KernelStatus addToSchduler = schd_add_to_schduler(releasedThread); 79 | if (addToSchduler != OK) { 80 | LogError("[Mutex]: thread add to schduler failed. \n"); 81 | } 82 | } 83 | ``` 84 | 85 | - 释放mutex,首先将Val的值设置为0,代表mutex现在没有被使用,也就是释放了mutex。 86 | - 之后和信号量类似,会进行移除出等待队列,得到线程描述符起始地址,添加进运行队列等操作。 -------------------------------------------------------------------------------- /SynestiaOS基础设施:原子操作.md: -------------------------------------------------------------------------------- 1 | 操作系统中,总会存在竞态条件,竞态条件一旦发生会对系统造成严重的影响,甚至是致命的。所以需要做一些工作来保护代码避免竞态条件。SynestiaOS实现了原子操作、自旋锁、互斥量等。 2 | 3 | 本篇文档介绍SynestiaOS同步方法中的原子操作,它是其它同步方法的基础,目前SynestiaOS实现了整数的原子操作,还没有实现单独位的原子操作,原子操作的定义位于:`SynestiaOS/SourceCode/Kernel/include/atomic.h` 和 `SynestiaOS/SourceCode/Kernel/src/atomic.c` 中。 4 | 5 | 在介绍原子操作之前,先来简单介绍下相关的一些术语: 6 | 7 | ### 临界区、竞态条件和同步 8 | 9 | **临界区**就是访问和操作共享数据的代码段,试想,多个执行的线程同时并发的访问同一个资源,例如说共享内存,那么通常情况下是很不安全的,为了避免在临界区中并发访问,就必须保证访问的操作是原子性的。 10 | 11 | 如果恰巧两个执行的线程处于同一个临界区同时执行,就叫做它们处于**竞态条件 `race conditions`**,也就是出现了线程竞争,虽然出现的概率比较小,但是一旦出现调试会非常困难。 12 | 13 | 而**同步**的意思就是避免并发和防止竞态条件。 14 | 15 | 16 | 17 | ### 可能的造成并发执行的原因 18 | 19 | 用户空间需要同步,因为用户程序会被调度器无情地抢占和重新调度,那么就有可能出现用户进程正处于临界区,并没有执行完,这个时候调度器就选择了另外一个进程调度上CPU了,也就是出现了非自愿的抢占了,而恰巧新调度上CPU的进程也处于同一个临界区,可以想象的一个场景就是两个进程在操作同一个段共享内存,那么这个时候这两个进程就发生了竞争。 20 | 21 | 内核中有很多可能造成并发执行的原因: 22 | 23 | - 中断:中断几乎可以在任何时刻异步地发生,可以随时打断当前正在执行的代码,而这段代码恰巧是临界区。 24 | - 软中断和tasklet:内核会在任何时刻唤醒或者调度软中断和tasklet,也会随时打断当前正在执行的代码 25 | - 内核抢占:处于内核态的任务会被另一个任务抢占,当然目前SynestiaOS是不允许内核抢占的。 26 | - ······ 27 | 28 | 29 | 30 | ### 原子变量的定义 31 | 32 | 关于原子操作,最经典的例子就是加1操作了,在汇编层面,加1操作会分为三个步骤:将计数器的值从内存复制到处理器寄存器中,之后将它的值加1,最后把寄存器中的数据写回到内存。所以如果有另外一个CPU同时也在操作这个内存单元进行加1操作,那么就会导致错误发生。 33 | 34 | 需要注意的是,原子操作是解决多个CPU在操作同一个内存单元时的情景,是面向多核cpu的,如果是单核心cpu系统,那么原子变量和普通变量是一样的,因为没有其它处理器的干扰了。另外原子操作最常见的例子就是实现计数器。 35 | 36 | ```c 37 | typedef struct Atomic { 38 | volatile uint32_t counter; 39 | } Atomic; 40 | ``` 41 | 42 | - 可以看到原子变量的定义就是一个地址,因为原子变量也是变量,一个变量就是一个内存单元中的内容,一个内存单元就使用一个地址来访问了。 43 | - 没有直接采用c语言中的数据类型例如 `u32` 的原因是让原子函数只接收 `Atomic`类型的操作数,可以强制原子操作只和这种数据类型一起使用,同时也防止原子类型的变量被视作普通变量进行加减操作。 44 | 45 | 创建一个原子变量的方法如下: 46 | 47 | ```c 48 | Atomic *lock; 49 | atomic_create(lock); 50 | 51 | 52 | void atomic_create(Atomic *atomic) { atomic_set(atomic, 0); } 53 | ``` 54 | 55 | - 它使用 `atomic_set` 方法,如下 56 | 57 | 58 | 59 | ### 给原子变量置值 60 | 61 | 那么如何给原子变量设置为某个值,这个操作其实可以通过 `*((atoimc)->counter) = (i)` 来进行,因为这条语句的操作本身就是原子性的,但是原子操作的加减中需要涉及到底层的体系结构,在SynestiaOS中,涉及的就是ARM底层的汇编指令了。所以为了统一,SynestiaOS也实现了汇编层面的给原子变量置值的操作: 62 | 63 | ```c 64 | /** 65 | * Because reading and writing directly to the memory address does not go through the register, 66 | * it is also atomic to directly manipulate the memory address. 67 | * So you can also use (*((atoimc)->counter) = (i)) 68 | */ 69 | void atomic_set(Atomic *atomic, uint32_t val) { 70 | volatile uint32_t tmp; 71 | __asm__ __volatile__("@ atomic_set\n\t" 72 | "1: ldrex %0, [%1]\n\t" //ldrex : arm的 73 | " strex %0, %2, [%1]\n\t" //strex 74 | " teq %0, #0\n\t" 75 | " bne 1b" 76 | : "=&r"(tmp) 77 | : "r"(&atomic->counter), "r"(val) 78 | : "cc"); 79 | } 80 | ``` 81 | 82 | - 第一个传入的参数就是要设置的原子变量,第二个传入的参数就是要设置的值是多少。 83 | - 在该函数中主要进行内联汇编操作 84 | 85 | 这里要借助 `ARM` 体系的 `LL` 和 `SC` 操作,也就是 `Load-Link` 和 `store-conditional` 操作,LL操作用来返回一个内存单元上的值,而SC操作就是向一个内存单元写入一个值,但是只有在LL开始到SC的时候都没有发生改变的时候,写入才会成功,所以ARM硬件提供的LL和SC操作,是SynestiaOS实现原子操作的基础。 86 | 87 | 88 | 89 | ### LDREX和STREX 90 | 91 | ARM中的 `LL` 和 `SC` 语句是使用 `ldrex` 和 `strex` 实现的: 92 | 93 | ``` 94 | LDREX Rx, [Ry] 95 | ``` 96 | 97 | - 用来读取 Ry 寄存器指向的内存单元,并保存到 `Rx` 寄存器当中,同时把Ry指向的内存区域标记为独占访问 98 | 99 | ``` 100 | STREX Rx, Ry, [Rz] 101 | ``` 102 | 103 | - strex在更新内存的时候,会检查这段内存是不是被标记了独占访问,如果被标记了独占访问,就把Ry中指向的值更新到Rz指向的内存,并把Rx设置成0,如果strex设置成功会d则会清除独占访问的标志。 104 | 105 | 所以说这两条指令是原子操作的基础。ARM本身还会有复杂的逻辑运行,可以移步[这里](https://blog.csdn.net/Roland_Sun/article/details/47670099)。 106 | 107 | 108 | 109 | ### 读取原子变量 110 | 111 | ```c 112 | /** 113 | * Because reading and writing directly to the memory address does not go through the register, 114 | * it is also atomic to directly manipulate the memory address. 115 | * So you can also use (*(volatile int *)&(atomic)->counter) 116 | */ 117 | uint32_t atomic_get(Atomic *atomic) { 118 | volatile uint32_t result; 119 | __asm__ __volatile__("@ atomic_get\n\t" 120 | " ldrex %0, [%1]" 121 | : "=&r"(result) 122 | : "r"(&atomic->counter)); 123 | return result; 124 | } 125 | ``` 126 | 127 | - 获取原子变量的值也可以通过 `(*(volatile int *)&(atomic)->counter)` 实现,因为内存读取也是原子操作,不经过寄存器,所以也是为了统一,实现了汇编层面的 `atomic_get`。 128 | 129 | - 读取原子变量使用的是上面介绍的 `ldrex` 指令。 130 | 131 | 132 | 133 | ### 原子变量加法减法操作 134 | 135 | ```c 136 | uint32_t atomic_add(Atomic *atomic, uint32_t val) { 137 | volatile uint32_t tmp; 138 | uint32_t result; 139 | __asm__ __volatile__("@ atomic_add\n\t" 140 | "1: ldrex %0, [%2]\n\t" 141 | " add %1, %0, %3\n\t" 142 | " strex %0, %1, [%2]\n\t" 143 | " teq %0, #0\n\t" 144 | " bne 1b" 145 | : "=&r"(tmp), "=&r"(result) 146 | : "r"(&atomic->counter), "r"(val) 147 | : "cc"); 148 | return result; 149 | } 150 | ``` 151 | 152 | - 原子变量的加法减法操作区别于原子变量的置值,加法操作必须经过寄存器,所以必须在汇编层面操作,类似于给原子变量置值,加法操作会在 `ldrex` 和 `strex` 之间进行add操作。 153 | - 原子变量的减法操作就对应使用sub指令了: 154 | 155 | ```c 156 | uint32_t atomic_sub(Atomic *atomic, uint32_t val) { 157 | volatile uint32_t tmp; 158 | uint32_t result; 159 | __asm__ __volatile__("@ atomic_sub\n\t" 160 | "1: ldrex %0, [%2]\n\t" 161 | " sub %1, %0, %3\n\t" 162 | " strex %0, %1, [%2]\n\t" 163 | " teq %0, #0\n\t" 164 | " bne 1b" 165 | : "=&r"(tmp), "=&r"(result) 166 | : "r"(&atomic->counter), "r"(val) 167 | : "cc"); 168 | return result; 169 | } 170 | ``` 171 | 172 | 173 | 174 | ### 原子变量的加1减1操作 175 | 176 | 回到文档开始的经典例子,原子变量一个经常遇到的应用场景就是加1减1操作了,所以SynestiaOS中,实现了原子变量的加1减1操作: 177 | 178 | ```c 179 | uint32_t atomic_inc(Atomic *atomic) { return atomic_add(atomic, 1); } 180 | 181 | uint32_t atomic_dec(Atomic *atomic) { return atomic_sub(atomic, 1); } 182 | ``` 183 | 184 | - 代码其实就是调用了 `atomic_add` 和 `atomic_sub` 函数。 -------------------------------------------------------------------------------- /SynestiaOS基础设施:基本数据类型和宏定义.md: -------------------------------------------------------------------------------- 1 | 中文版: 2 | 3 | 本篇文档介绍 SynestiaOS 中定义的基本数据类型和一些宏定义。这些数据类型和宏定义位于`SynestiaOS/SourceCode/Kernel/include/type.h` 、 `SynestiaOS/SourceCode/Libraries/LibC/include/stdint.h` 和 `SynestiaOS/SourceCode/Libraries/LibC/include/stdlib.h` 中。 4 | 5 | 6 | 7 | ### 基本数据类型 8 | 9 | 10 | ```c 11 | typedef unsigned char uint8_t; 12 | typedef unsigned short uint16_t; 13 | typedef unsigned long uint32_t; 14 | typedef unsigned long long uint64_t; 15 | 16 | typedef signed char int8_t; 17 | typedef signed short int16_t; 18 | typedef signed long int32_t; 19 | typedef signed long long int64_t; 20 | 21 | typedef _Bool bool; 22 | ``` 23 | 24 | 25 | 26 | ### 一些宏定义 27 | 28 | 29 | ```c 30 | #define DEFAULT_STRING_LEN 256 31 | 32 | #define B 1 33 | #define KB 1024 * B 34 | #define MB 1024 * KB 35 | #define GB 1024 * MB 36 | 37 | #define BITS_IN_UINT32 32 38 | #define MAX_UINT_32 0xFFFFFFFF 39 | 40 | #define nullptr (void *)0 41 | ``` 42 | 43 | 44 | 在 `SynestiaOS` 中,经常会遇到函数的返回值类型是 `KernelStatus`,该类型定义如下: 45 | 46 | 47 | ```c 48 | typedef enum KernelStatus { 49 | OK = 0, 50 | ERROR, 51 | } KernelStatus; 52 | ``` 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /SynestiaOS基础设施:堆管理.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | 本篇文档介绍 SynestiaOS 中内核态内存堆的管理,涉及到堆的初始化、堆分配和堆的回收。SynestiaOS 中的堆是通过List链表管理的,关于List的介绍可以跳转[这里]()。堆的定义位于`SynestiaOS/SourceCode/Kernel/include/kheap.h` 和 `SynestiaOS/SourceCode/Kernel/include/kheap.c` 。 4 | 5 | 6 | 7 | ### 基础数据结构 8 | 9 | 每一个堆使用如下结构体抽象: 10 | 11 | ```c 12 | typedef struct HeapArea { 13 | uint32_t size; 14 | ListNode list; 15 | } __attribute__((packed)) HeapArea; 16 | ``` 17 | 18 | - size代表该段堆的大小是多少 19 | - list字段将所有的堆连接成为一个链表,用于管理 20 | 21 | 22 | 23 | 堆管理中,定义了两个全局指针: 24 | 25 | ```c 26 | HeapArea *usingListHead; 27 | HeapArea *freeListHead; 28 | ``` 29 | 30 | - `usingListHead` 将所有分配了的堆空间连接成一个链表。 31 | - `freeListHead` 将所有的空闲的堆空间连接成一个链表。 32 | 33 | 34 | 35 | ### 堆的初始化 36 | 37 | 堆的初始化代码如下: 38 | 39 | ```c 40 | KernelStatus kheap_init() { 41 | kheap_set_alloc_callback(default_heap_alloc_func); 42 | kheap_set_free_callback(default_heap_free_func); 43 | 44 | uint32_t heapAddress = (uint32_t)&__HEAP_BEGIN; 45 | LogInfo("[KHeap] end bss at: %d. \n", heapAddress); 46 | 47 | uint32_t heapPhysicalPage = 48 | (uint32_t)page_alloc_huge_at(USAGE_KERNEL_HEAP, (heapAddress | 4 * KB) >> VA_OFFSET, 128 * MB - heapAddress); 49 | LogInfo("[KHeap] alloc heap page: %d. \n", (uint32_t)heapPhysicalPage); 50 | 51 | heapAddress = KERNEL_PHYSICAL_START + heapPhysicalPage * PAGE_SIZE; 52 | LogInfo("[KHeap] kheap at: %d. \n", heapAddress); 53 | 54 | freeListHead = (HeapArea *)heapAddress; 55 | freeListHead->size = 0; 56 | freeListHead->list.prev = nullptr; 57 | 58 | HeapArea *freeArea = (HeapArea *)(heapAddress + sizeof(HeapArea)); 59 | freeArea->size = (ALL_PHYSICAL_MEM_SIZE - (uint32_t)(char *)heapAddress - 2 * sizeof(HeapArea)); // all memory 60 | freeListHead->list.next = &freeArea->list; 61 | freeArea->list.next = nullptr; 62 | freeArea->list.prev = &freeListHead->list; 63 | 64 | usingListHead = nullptr; 65 | 66 | return OK; 67 | } 68 | ``` 69 | 70 | - 首先获取 `heap` 堆的起始地址,在内存分配的文档中可以知道页表之后的剩余的物理内存就全部是堆的空间了,起始地址是 `__HEAP_BEGIN`。 71 | 72 | - 之后标记物理页面,因为堆的空间是页表之后到内核空间完毕的所有空间,所以需要标记的物理页面的大小是 `128 * MB - heapAddress`,标记使用的类型是 `USAGE_KERNEL_HEAP` ,标记大小为 `128 * MB - heapAddress`。 73 | 74 | - 后面的计算`heapAddress = KERNEL_PHYSICAL_START + heapPhysicalPage * PAGE_SIZE;`的结果也就是 `__HEAP_BEGIN`了。 75 | 76 | - 之后全局的 `freeListHead` 指向 `__HEAP_BEGIN`,`freeListHead`的类型是 `HeapArea *`,接着就初始化 `size` 字段和 `list` 字段。此时内存的布局如下: 77 | 78 | ![](./imgs/Heap/heap1.png) 79 | 80 | - 从上图中可以看出来最下面的 `FREE` 区域就全部是空闲的堆空间了,于是接下来就定义一个 `freeArea` 来管理这块内存,它的起始地址从上图中可以看出是 `heap_address` 再往下偏移一个 `HeapArea` 的大小;由于又使用了一个 `freeArea` 来管理这块内存区域,所以size大小就是所有剩余的物理内存大小减去两个 `HeapArea`。此时的内存布局如下: 81 | 82 | ![](./imgs/Heap/heap2.png) 83 | 84 | 两个 `HeapArea` 的结构关系如下: 85 | 86 | ![](./imgs/Heap/heap3.png) 87 | 88 | - 管理好这一大块内存之后,就将这块内存连接到 `freeListHead` 链表上。因为这里是一开始的堆的初始化,所以没有使用的堆,就将 `usingListHead` 设置为 `nullptr`。 89 | 90 | 91 | 92 | ### 堆的分配 93 | 94 | 类似于Linux内核的`kmalloc`函数功能,堆分配的函数如下: 95 | 96 | ```c 97 | void *kheap_alloc(uint32_t size) { 98 | uint32_t allocSize = size + sizeof(HeapArea); 99 | 100 | if (freeListHead == nullptr) { 101 | printf("[KHeap]: failed to get freeListHead.\n"); 102 | return nullptr; 103 | } 104 | 105 | HeapArea *currentFreeArea = freeListHead; 106 | while (currentFreeArea != nullptr) { 107 | // if the size of the free block can contain the request size and a rest HeapArea, then just use it, and split a new 108 | // block 109 | if (currentFreeArea->size >= allocSize) { 110 | // 1. split a rest free HeapArea 111 | uint32_t newFreeHeapAreaAddress = (uint32_t)(void *)currentFreeArea + sizeof(HeapArea) + size; 112 | uint32_t restSize = currentFreeArea->size - allocSize; 113 | 114 | HeapArea *newFreeArea = (HeapArea *)newFreeHeapAreaAddress; 115 | newFreeArea->size = restSize; 116 | 117 | // 2.link new free heap area to free list 118 | newFreeArea->list.prev = currentFreeArea->list.prev; 119 | newFreeArea->list.next = currentFreeArea->list.next; 120 | currentFreeArea->list.prev->next = &newFreeArea->list; 121 | if (currentFreeArea->list.next != nullptr) { 122 | currentFreeArea->list.next->prev = &newFreeArea->list; 123 | } 124 | 125 | // 3. link this to using list 126 | currentFreeArea->list.prev = nullptr; 127 | currentFreeArea->list.next = nullptr; 128 | currentFreeArea->size = size; 129 | HeapArea *usingArea = usingListHead; 130 | if (usingArea == nullptr) { 131 | usingListHead = currentFreeArea; 132 | } else { 133 | while (usingArea->list.next != nullptr) { 134 | usingArea = getNode(usingArea->list.next, HeapArea, list); 135 | } 136 | usingArea->list.next = ¤tFreeArea->list; 137 | currentFreeArea->list.prev = &usingArea->list; 138 | } 139 | 140 | // 4. return the ptr of the using block 141 | void *ptr = (void *)currentFreeArea + sizeof(HeapArea); 142 | if (heapFreeFunc == nullptr) { 143 | default_heap_alloc_func(ptr, size); 144 | return ptr; 145 | } 146 | heapAllocFunc(ptr, size); 147 | return ptr; 148 | } 149 | currentFreeArea = getNode(currentFreeArea->list.next, HeapArea, list); 150 | // no free block found ,it's means we must do some memory defragmentation 151 | // todo: defragmentation 152 | } 153 | return nullptr; 154 | } 155 | ``` 156 | 157 | - 接收的参数是要分配的堆空间的内存大小size。 158 | - 实际上要分配给堆的内存空间是 `size` 加上一个 `HeapArea` 结构的大小,因为每一块heap内存都需要一个 `HeapArea` 结构来管理。 159 | - 接下来在 `freeListHead` 链表中寻找空闲的堆空间,如果剩余的堆空间大小比要分配的堆空间 `size` 加上一个`HeapArea` 结构要大,就直接分配一块空间。 160 | - 接下来把剩余的空闲堆空间放入到空闲堆链表中,并把分配走的堆空间放入到 `usingListHead` 链表中。 161 | - 目前还没有实现内存碎片整理。 162 | 163 | 详细的过程如下: 164 | 165 | ![](./imgs/Heap/heapalloc1.png) 166 | 167 | ![](./imgs/Heap/heapalloc2.png) 168 | 169 | 170 | 171 | ### 堆的回收 172 | 173 | 堆空间的 `free` 函数: 174 | 175 | ```c 176 | KernelStatus kheap_free(void *ptr) { 177 | // 1. get HeapArea address 178 | uint32_t address = (uint32_t)(ptr - sizeof(HeapArea)); 179 | HeapArea *currentArea = (HeapArea *)address; 180 | 181 | // 2. unlink from using list 182 | if (currentArea->list.prev != nullptr) { 183 | currentArea->list.prev->next = currentArea->list.next; 184 | } 185 | 186 | if (currentArea->list.next != nullptr && currentArea->list.prev != nullptr) { 187 | currentArea->list.next->prev = currentArea->list.prev; 188 | } 189 | 190 | // 3. link this to free list 191 | HeapArea *freeArea = freeListHead; 192 | while (freeArea->list.next != nullptr) { 193 | freeArea = getNode(freeArea->list.next, HeapArea, list); 194 | } 195 | currentArea->list.prev = &freeArea->list; 196 | currentArea->list.next = freeArea->list.next; 197 | freeArea->list.next = ¤tArea->list; 198 | 199 | // do some merge stuff, between two adjacent free heap area 200 | HeapArea *firstFreeArea = freeListHead; 201 | while (firstFreeArea->list.next != nullptr) { 202 | firstFreeArea = getNode(firstFreeArea->list.next, HeapArea, list); 203 | HeapArea *secondFreeArea = firstFreeArea; 204 | while (secondFreeArea->list.next != nullptr) { 205 | secondFreeArea = getNode(secondFreeArea->list.next, HeapArea, list); 206 | 207 | // check is adjacent free heap area 208 | if (firstFreeArea + sizeof(HeapArea) + firstFreeArea->size == secondFreeArea) { 209 | 210 | // resize the first heap area 211 | firstFreeArea->size = firstFreeArea->size + sizeof(HeapArea) + secondFreeArea->size; 212 | firstFreeArea->list.next = secondFreeArea->list.next; 213 | secondFreeArea->list.next->prev->next = &firstFreeArea->list; 214 | 215 | // delink the second heap area 216 | secondFreeArea->list.prev = nullptr; 217 | secondFreeArea->list.next = nullptr; 218 | secondFreeArea->size = 0; 219 | } 220 | } 221 | } 222 | 223 | if (heapFreeFunc == nullptr) { 224 | default_heap_free_func(ptr); 225 | ptr = nullptr; 226 | return OK; 227 | } 228 | 229 | heapFreeFunc(ptr); 230 | ptr = nullptr; 231 | return OK; 232 | } 233 | 234 | ``` 235 | 236 | - 传送的参数是要分配的堆空间的起始地址。 237 | - 先将这段堆空间从 `usingListHead` 上删除掉。 238 | - 将这段堆空间连接到 `freeListHead` 上。 239 | - 之后进行连续内存空间合并。 240 | 241 | 详细过程如下: 242 | 243 | ![](./imgs/Heap/heapfree1.png) 244 | 245 | ![](./imgs/Heap/heapfree2.png) 246 | 247 | 248 | 249 | ### 堆的重分配 250 | 251 | 方法为 `kheap_realloc` : 252 | 253 | ```c 254 | void *kheap_realloc(void *ptr, uint32_t size) { 255 | // 1. alloc new heap area 256 | void *newHeapArea = kheap_alloc(size); 257 | 258 | // 2. copy the data from old heap area to new heap area 259 | HeapArea *oldHeapArea = ptr - sizeof(HeapArea); 260 | uint32_t dataSize = oldHeapArea->size; 261 | memcpy(newHeapArea, ptr, dataSize); 262 | 263 | // 3. free old heap area 264 | kheap_free(ptr); 265 | return newHeapArea + sizeof(HeapArea); 266 | } 267 | ``` 268 | 269 | 应用场景是堆不够用的时候,需要重新分配堆,把之前堆内存中的内容先 `memcpy` 到新的`HeapArea`中,然后释放掉旧的`HeapArea`,最后返回新的堆的起始地址。 270 | 271 | 272 | 273 | ### 堆的Count分配 274 | 275 | 方法为 `kheap_calloc` : 276 | 277 | ```c 278 | void *kheap_calloc(uint32_t num, uint32_t size) { return kheap_alloc(num * size); } 279 | ``` 280 | 281 | 这个方法是要分配 `num * size` 大小的堆空间。即调用 `kheap_alloc` 函数即可。 282 | 283 | 284 | 285 | ### 堆按alignment对齐分配: 286 | 287 | 例如在分配栈的时候就需要8字节对齐,方法为`kheap_alloc_aligned`: 288 | 289 | ```c 290 | void *kheap_alloc_aligned(uint32_t size, uint32_t alignment) { 291 | uint32_t offset = alignment - 1 + sizeof(void *); 292 | void *p1 = kheap_alloc(size + offset); 293 | if (p1 == nullptr) { 294 | return nullptr; 295 | } 296 | void **p2 = (void **)(((uint32_t)p1 + offset) & ~(alignment - 1)); 297 | p2[-1] = p1; 298 | return p2; 299 | } 300 | ``` 301 | 302 | 303 | 304 | -------------------------------------------------------------------------------- /SynestiaOS基础设施:自旋锁.md: -------------------------------------------------------------------------------- 1 | 临界区理想的情况是每一个临界区的代码都能是变量加减操作,但是现实总是残酷的,在代码逻辑中经常会遇到复杂的情景,例如队列的入队和出队,在入队和出队的同时,可能还会伴随着一些逻辑操作,这个时候原子操作就无能为力了,就需要使用较为复杂的锁机制。 2 | 3 | 本篇文档介绍SynestiaOS的处理竞态的另一种方式,也就是另一种同步方法自旋锁`spin lock`,自旋锁则面向多个线程设置,用于保护短的代码段,所以会很快执行完毕,自旋锁存在于SynestiaOS中各个数据结构中,可以说大多数的数据结构都需要使用自旋锁,在处理某些关键的成员的时候,必须获得相应的自旋锁。 4 | 5 | 锁的使用是自愿的,不是强制性的,使用锁的原因完全是因为避免竞态条件,因为锁是采用原子操作实现的,而原子操作是不存在竞争的。需要注意的是,在编写代码的时候,能使用原子操作的时候,就尽量使用原子操作,不使用复杂的锁机制,因为原子操作带来的开销会小,对`cache-line` 的影响也会小。 6 | 7 | 自旋锁的定义位于:`SynestiaOS/SourceCode/Kernel/include/spinlock.h` 和 `SynestiaOS/SourceCode/Kernel/src/spinlock.c` 中。 8 | 9 | 10 | 11 | ### 自旋锁的定义 12 | 13 | ```c 14 | typedef struct SpinLock { 15 | Atomic *lock; 16 | } SpinLock; 17 | ``` 18 | 19 | - 自旋锁最多只能被一个可执行的线程持有,如果一个线程尝试去获取一个已经被持有的自旋锁,那么这个线程就会一直进行进行忙循环,直到等待到锁重新可用。一个例子就是两个人想要同时去同一个卫生间的同一个坑位,但是一个坑位只能允许一个人进入,另一个人就需要等待,直到里面的人出来。 20 | - 另外需要注意一个事情,那就是自旋锁不应该被长期占有,因为等待的线程此时什么也不干,只是在等待,可以想象一下在坑位门口等待着的人那种焦虑的心情。当然等待自旋锁的线程可以进入睡眠然后等到锁重新可用的时候在唤醒它,这样就不必等待,CPU也可以处理其他事情,但是这明显会带来两次进程的切换,也就是两 次 `context_switch()`,这又是一个比较大的开销,所以我们尽量保证临界区的代码执行时间小于两次 `context_switch()` 的时间,但是这个时间我们又无法估计,所以使用自旋锁时就遵循自旋锁的初心,只保护尽量短的代码段。 21 | - 目前SynestiaOS的自旋锁是通过原子变量实现的 22 | 23 | 24 | 25 | ### 自旋锁的使用 26 | 27 | 自旋锁的基本使用方法如下: 28 | 29 | ```c 30 | SpinLock *lock; 31 | spinlock_create(lock); 32 | 33 | void spinlock_create(SpinLock *spinLock, Atomic *atomic) { atomic_create(atomic); } 34 | ``` 35 | 36 | - 实际上就是要初始化 `SpinLock` 中的原子变量。 37 | - 注意该处代码尚不完善,后续会进行改进。 38 | 39 | 创建好之后就可以使用自旋锁来保护临界区了: 40 | 41 | ```c 42 | spinlock_acquire(lock); 43 | /*临界区*/ 44 | spinlock_release(lock); 45 | ``` 46 | 47 | - 如果内核中其它地方还没有获得锁,那么就由当前的CPU获取,其它的处理器不会再进入自旋锁保护的区域 48 | - 如果自旋锁已经被其他的CPU获取了,那么spinlock_acquire会进入一个等待,重复检查自旋锁是否已经被释放。等待可以获取锁的时候,再进入临界区。 49 | - 如果内核不支持抢占,而且是单核CPU的系统,那么就不会出现几个CPU同时进入临界区的情况,但是如果单核心CPU支持内核抢占,这种情况和两个处理器同时在临界区执行的情况是等效的。SynestiaOS目前不支持内核抢占,所以我们面对的是多CPU的情景。 50 | 51 | 52 | 53 | ### 获得Spin lock 54 | 55 | ```c 56 | void spinlock_acquire(SpinLock *spinLock) { 57 | if (atomic_get(spinLock->lock) == 0) { 58 | atomic_set(spinLock->lock, 1); 59 | } else { 60 | asm volatile("WFE"); 61 | } 62 | } 63 | ``` 64 | 65 | - 由于自旋锁是通过原子变量实现的,所以实际的操作就是操作原子变量了。 66 | 67 | - 获得锁的过程是检测当前spinlock的原子变量是否为0,0代表没有线程获得锁,而1代表已经被别的线程获得了锁, 68 | 69 | 如果没有人获得锁,那么当前CPU就会把这个自旋锁的原子变量加1,表示我拿到了锁;而如果检测到别的线程已经获得了锁,那么就会执行 `WFE` 指令。 70 | 71 | - WFE是ARM架构的指令,执行 `WFE` 指令后,根据 `Event Register` 的状态,有两种情况:如果Event Register为1,该指令会把它清零,然后执行完成(不会standby);如果Event Register为0,和WFI类似,进入low-power standby state,直到有 `WFE Wakeup events` 发生。所以前文所说的让等待的线程一直循环等待检测锁是否释放的操作就可以使用WFE指令优化,让该CPU进入standby模式,还可以节省功耗。 72 | 73 | 74 | 75 | ### 释放Spin lock 76 | 77 | ```c 78 | void spinlock_release(SpinLock *spinLock) { 79 | atomic_set(spinLock->lock, 0); 80 | asm volatile("SEV"); 81 | } 82 | ``` 83 | 84 | - 释放锁的操作相对简单,就是把自旋锁的原子变量设置成0即可。 85 | - 最重要的是要执行下面的SEV指令,所谓的SEV指令,就是一个用来改变 `Event Register` 的指令,`WFE` 可以被任何 `PE(process element)` 上执行的 `SEV`指令唤醒。这就是spinlock实现的关键,如果别的CPU核在等待自旋锁,说明它已经执行了 `WFE` 指令,进入了standby模式,那么这个时候如果其它的PE执行了 `SEV` 指令,就会唤醒它,从而继续运行。 86 | - 需要注意使用了自旋锁以后,一定要记得释放锁,如果获得了锁以后不释放,其它的线程迟早会进入临界区,就会进入无限循环的状态,或者CPU进入无限 `standby` 模式,就会产生死锁。 -------------------------------------------------------------------------------- /SynestiaOS基础设施:链表List.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | 链表是 SynestiaOS 使用最多的数据结构,和传统的链表不同,它作为一个成员存在于某一个类中。链表的定义位于`SynestiaOS/SourceCode/Kernel/include/list.h` 和 `SynestiaOS/SourceCode/Kernel/include/list.c` 。 4 | 5 | 6 | 7 | ### 链表定义 8 | 9 | 链表定义如下: 10 | 11 | ```c 12 | typedef struct ListNode { 13 | struct ListNode *prev; 14 | struct ListNode *next; 15 | } __attribute__((packed)) ListNode; 16 | ``` 17 | 18 | 链表必须配合如下两个宏才能发挥出它的威力,类似于Linux内核中的 container_of 宏 : 19 | 20 | ```c 21 | #define offsetOf(type, member) (char *)(&(((type *)0)->member)) 22 | #define getNode(ptr, type, member) ((type *)((char *)(ptr) - (char *)(&(((type *)0)->member)))) 23 | ``` 24 | 25 | - `offsetof`宏用于获取 type 结构体中 member成员的偏移地址: 26 | 27 | - 以 `struct Example` 为例介绍该宏的使用,下图是`struct Example`结构体,右边是该结构体在实际内存中的存储形式。 28 | 29 | ![](./imgs/List/example.png) 30 | 31 | - `offsetOf(Example, flags)` 用于获取 `flags` 在 `Example` 结构体中的偏移地址,目前SynestiaOS运行于ARM32上,所以指针占用的大小为4个字节,所以得到的 `flags` 在 `Example` 中的偏移是12个字节。 32 | 33 | - `getNode`宏中,`ptr`是结构体成员变量的地址,`type`是该结构体的类型,member是该结构体成员变量名,该宏的作用是利用该结构体成员的地址得到该结构体成员所在结构体的起始地址。 34 | 35 | - `getNode`宏的后半部分减去的正是 `offsetOf(type, member)`,还是以 `struct Example` 为例介绍: 36 | 37 | ![](./imgs/List/example2.png) 38 | 39 | - 假设我们知道 `flags` 的起始地址,`&flags` 是 `0x0000 FF12`,那么使用 `getNode(&flags, Example, flags)` ,也就是利用 `flags` 的起始地址减去它在 `struct Example` 中的偏移就可以算出来 `flags` 所在的 `struct Example` 实例的起始地址,是 `0x0000 FF00`。 40 | 41 | ### 链表方法 42 | 43 | 1. 将 `node` 节点插入到 `list` 链表中: 44 | 45 | ```c 46 | KernelStatus klist_insert(ListNode *list, ListNode *node) { 47 | if (list->next != nullptr) { 48 | list->next->prev = node; 49 | node->next = list->next; 50 | } 51 | node->prev = list; 52 | list->next = node; 53 | } 54 | ``` 55 | 56 | 57 | 58 | 2. 将 `node` 节点插入到 `list` 尾部,尾插法: 59 | 60 | ```c 61 | KernelStatus klist_append(ListNode *list, ListNode *node) { 62 | ListNode *tmp = list; 63 | while (tmp->next != nullptr) { 64 | tmp = tmp->next; 65 | } 66 | node->prev = tmp; 67 | tmp->next = node; 68 | } 69 | ``` 70 | 71 | 72 | 73 | 3. 得到 `node` 节点所在 `list` 的头结点: 74 | 75 | ```c 76 | ListNode *klist_get_head(ListNode *node) { 77 | ListNode *first = node; 78 | while (first->prev != nullptr) { 79 | first = first->prev; 80 | } 81 | return first; 82 | } 83 | ``` 84 | 85 | 86 | 87 | 4. 把 `node` 节点从 `node` 所在 `list` 中删除: 88 | 89 | ```c 90 | ListNode *klist_remove_node(ListNode *node) { 91 | 92 | if (node->next == nullptr && node->prev == nullptr) { 93 | return node; 94 | } 95 | 96 | if (node->next == nullptr && node->prev != nullptr) { 97 | node->prev->next = nullptr; 98 | node->prev = nullptr; 99 | return node; 100 | } 101 | 102 | if (node->prev == nullptr && node->next != nullptr) { 103 | node->next->prev = nullptr; 104 | node->next = nullptr; 105 | return node; 106 | } 107 | 108 | ListNode *prev = node->prev; 109 | ListNode *next = node->next; 110 | 111 | prev->next = next; 112 | next->prev = prev; 113 | 114 | node->next = nullptr; 115 | node->prev = nullptr; 116 | 117 | return node; 118 | } 119 | ``` 120 | 121 | 122 | 123 | 5. 计算 `node` 所在 `list` 有多少个节点: 124 | 125 | ```c 126 | uint32_t klist_size(ListNode *node) { 127 | ListNode *first = node; 128 | while (first->prev != nullptr) { 129 | first = first->prev; 130 | } 131 | 132 | uint32_t size = 1; 133 | while (first->next != nullptr) { 134 | first = first->next; 135 | size++; 136 | } 137 | 138 | return size; 139 | } 140 | ``` 141 | 142 | -------------------------------------------------------------------------------- /SynestiaOS基础设施:队列KQueue.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | SynestiaOS 中的队列结构是在 `List` 链表结构基础上开发的,关于List的介绍可以跳转[这里]()。队列的定义位于`SynestiaOS/SourceCode/Kernel/include/kqueue.h` 和 `SynestiaOS/SourceCode/Kernel/include/kqueue.c` 。 4 | 5 | 6 | 7 | ### 队列定义 8 | 9 | 队列定义如下: 10 | 11 | ```c 12 | typedef ListNode KQueue; 13 | ``` 14 | 15 | - SynestiaOS在List的基础上,对方法做了限制从而实现队列。 16 | 17 | 18 | 19 | ### 队列方法 20 | 21 | 1. 入队,队列只能从队尾入队,所以将 `node` 节点插入到 `queue` 的尾部,如果成功返回OK: 22 | 23 | ```c 24 | KernelStatus kqueue_enqueue(KQueue *queue, KQueue *node) { 25 | KQueue *last = queue; 26 | if (last == nullptr) { 27 | queue = node; 28 | node->prev = nullptr; 29 | node->next = nullptr; 30 | } 31 | while (last->next != nullptr) { 32 | last = last->next; 33 | } 34 | last->next = node; 35 | node->prev = last; 36 | node->next = nullptr; 37 | 38 | return OK; 39 | } 40 | ``` 41 | 42 | 入队的过程如下图: 43 | 44 | ![](./imgs/KQueue/enqueue.png) 45 | 46 | 47 | 48 | 49 | 2. 出队,队列只能从队头出队,出队成功后,会把出队的节点地址返回 50 | 51 | ```c 52 | KQueue *kqueue_dequeue(KQueue *queue) { 53 | KQueue *first = queue; 54 | while (first->prev != nullptr) { 55 | first = first->prev; 56 | } 57 | first->next->prev = nullptr; 58 | first->next = nullptr; 59 | first->prev = nullptr; 60 | return first; 61 | } 62 | ``` 63 | 64 | 出队的过程如下图: 65 | 66 | ![](./imgs/KQueue/dequeue.png) 67 | 68 | 69 | 70 | 3. 求队列长度 71 | 72 | ```c 73 | uint32_t kqueue_size(KQueue *queue) { return klist_size(queue); } 74 | ``` 75 | 76 | 由于队列使用 `List` 实现,所以返回队列长度也就是返回链表的长度了。 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /SynestiaOS文件系统:VFS顶层部分.md: -------------------------------------------------------------------------------- 1 | 文件系统是SynestiaOS内核的另一大组成部分,SynestiaOS实现了`VFS(Virtual File System)`,VFS可以理解为一个真实文件系统和用户进程之间的隔离层,也就是类和对象的关系,所以使用VFS,可以统一文件/目录的上层操作方法,而具体到某一处实现细节的时候,就可以由各个文件系统来完成。 2 | 3 | VFS和系统调用直接关联,使得用户程序可以使用open()、read()、write()这样的系统调用,而不用去考虑文件系统的具体实现细节。SynestiaOS的系统调用遵循 `POSIX` 协议,用户程序可以利用标准系统调用对不同的文件系统、不同介质上的文件系统进行读写操作。 4 | 5 | 同时每种文件系统可以作为一个模块存在于SynestiaOS中,关于SynestiaOS的模块实现,请点击[这里]()。 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SynestiaOS:内存分配和初始化.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | 本篇文档介绍 SynestiaOS 的页表分配、内存页表初始化等工作。 4 | 5 | 内存部分的代码位于`SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h`、`SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/vmm.h`、`SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/src/page.c`、`SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/src/vmm.c`中 6 | 7 | 其中会在`SynestiaOS/SourceCode/Kernel/src/init.c`的主函数中进行开始内存的初始化工作。 8 | 9 | ## 页表的划分 10 | 11 | SynestiaOS 启用了ARM处理器的[LAPE](https://elinux.org/images/6/6a/Elce11_marinas.pdf)模式,根据硬件手册,`SynestiaOS` 的页目录项的划分如下: 12 | 13 | ![](./imgs/mem/PTE.png) 14 | 15 | `SynestiaOS` 目前工作在32位模式,**页表**的大小是4KB`(#define PAGE_SIZE 4 * KB)`,**页表项**是64位,一共有三级页表,**[31:30]**表示一级页表,**[29:21]**表示二级页表,**[20:12]**表示三级页表;表示内存的方式为:`4(2^2) × 512(2^9) × 512(2^9) × 4KB = 4GB`。 16 | 17 | 32位可以寻址的内存范围是4GB(2^32),所以4GB的内存一共需要4GB / 4KB个物理页面: 18 | 19 | ```c 20 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/src/page.c 21 | 22 | #define PHYSICAL_PAGE_NUMBERS (1 << 20) 23 | ``` 24 | 25 | `SynestiaOS` 目前支持的内存大小为512MB,内核空间和用户空间按照1:3划分,所以**内核空间**占用128MB,**用户空间**占用384MB。所以在代码中会有如下宏,一级页表映射4个,二级页表只映射64个,所以表示内存的方式为:`4 × 64 × 512 × 4KB = 512MB`。 26 | 27 | ```c 28 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h 29 | 30 | #define KERNEL_L1PT_NUMBER 4 31 | #define KERNEL_L2PT_NUMBER 64 32 | #define KERNEL_PTE_NUMBER 512 33 | ``` 34 | 35 | 根据硬件手册,页目录项的定义如下: 36 | 37 | ```c 38 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h 39 | 40 | typedef struct PageTableEntry { 41 | /* These are used in all kinds of entry. */ 42 | uint64_t valid : 1; /* Valid mapping */ 43 | uint64_t table : 1; /* == 1 in 4k map entries too */ 44 | 45 | /* These ten bits are only used in Block entries and are ignored in Table entries. */ 46 | uint64_t ai : 3; /* Attribute Index */ 47 | uint64_t ns : 1; /* Not-Secure */ 48 | uint64_t user : 1; /* User-visible */ 49 | uint64_t ro : 1; /* Read-Only */ 50 | uint64_t sh : 2; /* Shareability */ 51 | uint64_t af : 1; /* Access Flag */ 52 | uint64_t ng : 1; /* Not-Global */ 53 | 54 | /* The base address must be appropriately aligned for Block entries */ 55 | uint64_t base : 28; /* Base address of block or next table */ 56 | uint64_t sbz : 12; /* Must be zero */ 57 | 58 | /* These seven bits are only used in Block entries and are ignored in Table entries. */ 59 | uint64_t hint : 1; /* In a block of 16 contiguous entries */ 60 | uint64_t pxn : 1; /* Privileged-XN */ 61 | uint64_t xn : 1; /* eXecute-Never */ 62 | uint64_t avail : 4; /* Ignored by hardware */ 63 | 64 | /* These 5 bits are only used in Table entries and are ignored in Block entries */ 65 | uint64_t pxnt : 1; /* Privileged-XN */ 66 | uint64_t xnt : 1; /* eXecute-Never */ 67 | uint64_t apt : 2; /* Access Permissions */ 68 | uint64_t nst : 1; /* Not-Secure */ 69 | } __attribute__((packed)) PTE; 70 | ``` 71 | 72 | 一级页表的定义如下,一共有4项: 73 | 74 | ```c 75 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h 76 | 77 | typedef struct Level1PageTable { 78 | PTE pte[KERNEL_L1PT_NUMBER]; // KERNEL_L1PT_NUMBER 为 4 79 | } L1PT; 80 | ``` 81 | 82 | 二级页表的定义如下,一共有512项,只映射64项: 83 | 84 | ```c 85 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h 86 | 87 | typedef struct Level2PageTable { 88 | PTE pte[KERNEL_PTE_NUMBER]; //KERNEL_PTE_NUMBER 为 512 89 | } L2PT; 90 | ``` 91 | 92 | 三级页表的定义如下,一共有 `512 × 512` 项: 93 | 94 | ```c 95 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/page.h 96 | 97 | typedef struct PageTable { 98 | PTE pte[KERNEL_PTE_NUMBER * KERNEL_PTE_NUMBER]; //KERNEL_PTE_NUMBER 99 | } PT; 100 | ``` 101 | 102 | 物理页面的定义如下: 103 | 104 | ```c 105 | typedef struct PhysicalPage { 106 | uint64_t ref_count : 8; 107 | PhysicalPageType type : 8; 108 | PhysicalPageUsage usage : 8; 109 | uint64_t reserved : 8; 110 | } __attribute__((packed)) PhysicalPage; 111 | ``` 112 | 113 | 一级页表的大小为:`4 × 8bytes = 32bytes`,**4KB对其**以后实际上占用4KB空间,32bytes到4KB这部分空间是空的;二级页表大小为:`4 × 512 × 8bytes = 16KB`,三级页表的大小为 `4 × 512 × 512 × 8bytes / 1024bytes = 8192KB = 8MB`。所以总的大小是 `8MB + 20KB`。页表的内存布局如下: 114 | 115 | ![](./imgs/mem/pagetable.png) 116 | 117 | 其中的关于物理页面的 `type` 和 `usage` 定义如下: 118 | 119 | ```c 120 | typedef enum PhysicalPageType { 121 | PAGE_UNKNOWD = 0, 122 | PAGE_4K, 123 | PAGE_2M, 124 | } PhysicalPageType; 125 | 126 | typedef enum PhysicalPageUsage { 127 | USAGE_UNKNOWD = 0, 128 | USAGE_KERNEL, 129 | USAGE_KERNEL_HEAP, 130 | USAGE_USER, 131 | USAGE_PERIPHERAL, 132 | USAGE_FRAMEBUFFER, 133 | USAGE_PAGETABLE, 134 | } PhysicalPageUsage; 135 | ``` 136 | 137 | - 物理页面的类型主要分为两种,即正常的4KB页面和2MB的大页。 138 | - 第二个 `PhysicalPageUsage` 代表页面是如何被使用的,目前页面可能被使用的场景是内核态、用户态、外设、framebuffer 、堆和页表。 139 | 140 | ## 内存初始化 141 | 142 | 内存初始化由主函数 `kernel_main()` 调用 `vmm_init()` 开始: 143 | 144 | ```c 145 | void vmm_init() { 146 | mmu_disable(); 147 | map_kernel_mm(); 148 | 149 | /** 150 | * if lpaeSupport = 5, means LPAE is supported 151 | */ 152 | uint32_t lpaeSupport = (read_mmfr0() & 0xF); 153 | printf("[LPAE]: mmfr0: %d\n", lpaeSupport); 154 | 155 | vmm_enable(); 156 | } 157 | ``` 158 | 159 | 首先执行mmu_disable()函数,之后初始化页表,再从实模式到保护模式,从平坦模式到分页模式: 160 | 161 | ```c 162 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/include/cache.h 163 | 164 | static inline void mmu_disable() { 165 | asm volatile("mrc p15, 0, r12, c1, c0, 0"); 166 | asm volatile("bic r12, r12, #0x1"); 167 | asm volatile("mcr p15, 0, r12, c1, c0, 0"); 168 | asm volatile("dsb"); 169 | } 170 | ``` 171 | 172 | 首先为了保险起见,关闭mmu,开始初始化页表 `map_kernel_mm()`: 173 | 174 | ```c 175 | void map_kernel_mm() { 176 | uint64_t pageTablePhysicalAddress = (uint64_t)&__PAGE_TABLE; 177 | 178 | uint64_t l1ptPhysicalAddress = pageTablePhysicalAddress; 179 | uint64_t l2ptPhysicalAddress = pageTablePhysicalAddress + 4 * KB; 180 | uint64_t ptPhysicalAddress = (l2ptPhysicalAddress + KERNEL_PTE_NUMBER * 4 * KB); 181 | 182 | map_kernel_l1pt(l1ptPhysicalAddress, l2ptPhysicalAddress); 183 | printf("[vmm]: level 1 page table done\n"); 184 | map_kernel_l2pt(l2ptPhysicalAddress, ptPhysicalAddress); 185 | printf("[vmm]: level 2 page table done\n"); 186 | map_kernel_pt(ptPhysicalAddress); 187 | printf("[vmm]: page table done\n"); 188 | } 189 | ``` 190 | 191 | - 首先得到页表的起始物理地址; 192 | - 之后根据页表大小,得到每一级页表的起始物理地址。 193 | - 之后开始填充/映射每一级页表项 194 | 195 | 需要注意代码中定义了三个全局变量,用来存放每一级物理地址的起始地址,在后面映射页表的过程中会进行初始化: 196 | 197 | ```c 198 | L1PT *kernelVMML1PT; 199 | L2PT *kernelVMML2PT; 200 | PT *kernelVMMPT; 201 | ``` 202 | 203 | 映射一级页表: 204 | 205 | ```c 206 | void map_kernel_l1pt(uint64_t l1ptPhysicalAddress, uint64_t l2ptPhysicalAddress) { 207 | kernelVMML1PT = (L1PT *)l1ptPhysicalAddress; 208 | kernelVMML1PT->pte[0].valid = 1; 209 | kernelVMML1PT->pte[0].table = 1; 210 | kernelVMML1PT->pte[0].af = 1; 211 | kernelVMML1PT->pte[0].base = (uint32_t)l2ptPhysicalAddress >> VA_OFFSET; 212 | 213 | kernelVMML1PT->pte[1].valid = 1; 214 | kernelVMML1PT->pte[1].table = 1; 215 | kernelVMML1PT->pte[1].af = 1; 216 | kernelVMML1PT->pte[1].base = (uint32_t)((l2ptPhysicalAddress + 4 * KB) >> VA_OFFSET); 217 | } 218 | ``` 219 | 220 | - 首先初始化全局变量kernelVMML1PT,之后填充第0项一级页表项,其中base存放二级页表的起始地址`(uint32_t)l2ptPhysicalAddress >> VA_OFFSET`。 221 | - 这里需要初始化第1项一级页表项,因为我们需要使用的`Peripheral`和`VideoBuffer`超过了第0项页表项的1GB寻址空间,所以需要初始化第1项一级页表项。 222 | 223 | 映射二级页表: 224 | 225 | ```c 226 | void map_kernel_l2pt(uint64_t l2ptPhysicalAddress, uint64_t ptPhysicalAddress) { 227 | kernelVMML2PT = (L2PT *)l2ptPhysicalAddress; 228 | for (uint32_t i = 0; i < KERNEL_PTE_NUMBER; i++) { 229 | kernelVMML2PT->pte[i].valid = 1; 230 | kernelVMML2PT->pte[i].table = 1; 231 | kernelVMML2PT->pte[i].af = 1; 232 | kernelVMML2PT->pte[i].base = (uint64_t)(ptPhysicalAddress + i * KERNEL_PTE_NUMBER * sizeof(PTE)) >> VA_OFFSET; 233 | } 234 | // Peripheral 16MB 0x3F000000 235 | for (uint32_t i = 0; i < 8; i++) { 236 | kernelVMML2PT->pte[504 + i].valid = 1; 237 | kernelVMML2PT->pte[504 + i].table = 0; 238 | kernelVMML2PT->pte[504 + i].af = 1; 239 | kernelVMML2PT->pte[504 + i].base = (0x3F000000 | (i * 2 * MB)) >> VA_OFFSET; 240 | } 241 | 242 | // VideoBuffer 8M 0x3C100000 243 | for (uint32_t i = 0; i < 4; i++) { 244 | kernelVMML2PT->pte[480 + i].valid = 1; 245 | kernelVMML2PT->pte[480 + i].table = 0; 246 | kernelVMML2PT->pte[480 + i].af = 1; 247 | kernelVMML2PT->pte[480 + i].base = (0x3C000000 | (i * 2 * MB)) >> VA_OFFSET; 248 | } 249 | 250 | // 2 level page table for second first page table 251 | L2PT *secondL2PT = (L2PT *)(l2ptPhysicalAddress + 4 * KB); 252 | secondL2PT->pte[0].valid = 1; 253 | secondL2PT->pte[0].table = 0; 254 | secondL2PT->pte[0].af = 1; 255 | secondL2PT->pte[0].base = 0x40000000 >> VA_OFFSET; 256 | } 257 | ``` 258 | 259 | - 第一个for循环初始化前512个二级页表项,base属性是三级页表的起始地址。 260 | - 第二个for循环初始化 `Peripheral` 相关内存区域,共16MB空间,后期给外设使用,table属性设置为0,代表分配的是2MB的大页面。 261 | - 第三个for循环初始化 `VideoBuffer` 相关内存区域。 262 | 263 | 映射三级页表: 264 | 265 | ```c 266 | void map_kernel_pt(uint64_t ptPhysicalAddress) { 267 | kernelVMMPT = (PT *)ptPhysicalAddress; 268 | uint32_t index = 0; 269 | for (uint32_t i = 0; i < KERNEL_L2PT_NUMBER; i++) { 270 | for (uint32_t j = 0; j < KERNEL_PTE_NUMBER; j++) { 271 | uint64_t physicalPageNumber = vmm_alloc_page(USAGE_KERNEL); 272 | kernelVMMPT->pte[index].valid = 1; 273 | kernelVMMPT->pte[index].table = 1; 274 | kernelVMMPT->pte[index].af = 1; 275 | kernelVMMPT->pte[index].base = 276 | ((KERNEL_PHYSICAL_START + physicalPageNumber * PAGE_SIZE) & 0x000FFFFF000) >> VA_OFFSET; 277 | index++; 278 | } 279 | } 280 | } 281 | ``` 282 | 283 | - 传入的参数是三级页表的起始地址,因为只映射64个二级页表项,所以共循环 `64 × 512` 次。 284 | - 在这里是建立内核空间,所以申请页面的时候传入的使用类型就是 `USAGE_KERNEL`。 285 | - 使用 `vmm_alloc_page` 函数来申请一个物理页面,就会得到分配到的是哪一个物理页面,后面利用这个位置信息来计算出起始地址,之后再取出第13-32位来设置base属性。 286 | - `vmm_alloc_page`见后文页面的分配和释放。 287 | 288 | 页面映射完毕以后,`map_kernel_mm()` 函数就执行完毕了,接下来执行 `vmm_enable()` 开启保护模式,进入分页模式: 289 | 290 | ```c 291 | void vmm_enable() { 292 | write_ttbcr(CONFIG_ARM_LPAE << 31); 293 | printf("[vmm]: ttbcr writed\n"); 294 | 295 | write_ttbr0((uint32_t)kernelVMML1PT); 296 | printf("[vmm]: ttbr0 writed\n"); 297 | 298 | write_dacr(0x55555555); 299 | printf("[vmm]: dacr writed\n"); 300 | 301 | mmu_enable(); 302 | printf("[vmm]: vmm enabled\n"); 303 | } 304 | ``` 305 | 306 | 至此,内存初始化完毕。 307 | 308 | 309 | 310 | ## 页面的分配和释放 311 | 312 | 首先在页面的管理上,使用了如下两个全局变量: 313 | 314 | ```c 315 | PhysicalPage physicalPages[PHYSICAL_PAGE_NUMBERS] = {'\0'}; 316 | uint32_t physicalPagesUsedBitMap[PHYSICAL_PAGE_NUMBERS / BITS_IN_UINT32] = {'\0'}; 317 | ``` 318 | 319 | - 第一个定义了 `physicalPages` 全局数组,用来存放所有的物理页面,并进行初始化。 320 | - 第二个存放使用了的物理页面,为了节省空间,使用32位位图来存放。`BITS_IN_UINT32` 的大小是32。 321 | 322 | 323 | 324 | ### 分配页面 325 | 326 | 分配页面的函数如下: 327 | 328 | ```c 329 | uint64_t page_alloc(PhysicalPageUsage usage) { 330 | for (uint32_t i = 0; i < PHYSICAL_PAGE_NUMBERS / BITS_IN_UINT32; i++) { 331 | if (physicalPagesUsedBitMap[i] != MAX_UINT_32) { 332 | for (uint8_t j = 0; j < BITS_IN_UINT32; j++) { 333 | if ((physicalPagesUsedBitMap[i] & ((uint32_t)0x1 << j)) == 0) { 334 | physicalPages[i * BITS_IN_UINT32 + j].ref_count += 1; 335 | physicalPages[i * BITS_IN_UINT32 + j].type = PAGE_4K; 336 | physicalPages[i * BITS_IN_UINT32 + j].usage = usage; 337 | physicalPagesUsedBitMap[i] |= (uint32_t)0x1 << j; 338 | return i * BITS_IN_UINT32 + j; 339 | } 340 | } 341 | } 342 | } 343 | } 344 | ``` 345 | 346 | - 物理页面一共有 `PHYSICAL_PAGE_NUMBERS` 也就是 `1 << 20 = 1048576` 个页面。 347 | 348 | - `MAX_UINT_32` 的值是 `0xFFFFFFFF`。 349 | 350 | - 如果分配成功,就会返回当前这个物理页面的位置信息。 351 | 352 | - 注意,关于位图索引的计算封装到两个函数中了: 353 | 354 | ```c 355 | void page_mark_as_free(uint64_t pageIndex) { 356 | uint32_t index = pageIndex / BITS_IN_UINT32; 357 | uint8_t bitIndex = pageIndex % BITS_IN_UINT32; 358 | 359 | physicalPagesUsedBitMap[index] ^= (uint32_t)0x1 << bitIndex; 360 | } 361 | 362 | void page_mark_as_used(uint64_t pageIndex) { 363 | uint32_t index = pageIndex / BITS_IN_UINT32; 364 | uint8_t bitIndex = pageIndex % BITS_IN_UINT32; 365 | 366 | physicalPagesUsedBitMap[index] |= (uint32_t)0x1 << bitIndex; 367 | } 368 | ``` 369 | 370 | 371 | 372 | ### 释放页面 373 | 374 | 释放页面的函数如下: 375 | 376 | ```c 377 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/src/page.c 378 | 379 | uint64_t page_free(uint64_t pageIndex) { 380 | if (physicalPages[pageIndex].ref_count > 0) { 381 | physicalPages[pageIndex].ref_count -= 1; 382 | 383 | page_mark_as_free(pageIndex); 384 | 385 | return pageIndex; 386 | } 387 | } 388 | ``` 389 | 390 | 需要注意的是释放页面的时候要将引用计数减1。 391 | 392 | 此外,SynestiaOS 也提供了大页的分配和释放方法: 393 | 394 | ```c 395 | SynestiaOS/SourceCode/Kernel/Arch/arm/vmm/src/page.c 396 | 397 | // alloc big page 398 | uint64_t page_alloc_huge_at(PhysicalPageUsage usage, uint64_t page, uint64_t size) { 399 | for (uint32_t pageOffset = 0; pageOffset < size / (4 * KB); pageOffset++) { 400 | physicalPages[page + pageOffset].ref_count += 1; 401 | physicalPages[page + pageOffset].type = PAGE_2M; 402 | physicalPages[page + pageOffset].usage = usage; 403 | 404 | page_mark_as_used(page + pageOffset); 405 | } 406 | return page; 407 | } 408 | 409 | // free big page 410 | uint64_t page_free_huge(uint64_t page, uint64_t size) { 411 | for (uint32_t pageOffset = 0; pageOffset < size / (4 * KB); pageOffset++) { 412 | page_free(page + pageOffset); 413 | } 414 | return page; 415 | } 416 | ``` 417 | 418 | 419 | 420 | 421 | 422 | -------------------------------------------------------------------------------- /SynestiaOS:内核栈使用.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | 内核栈是内核线程创建时首先需要做的事情,在内存初始化页表后,剩余的所有物理空间都作为 `SynestiaOS` 的heap段,所以内核栈也都是在heap堆上分配出来的。本篇文档就介绍 `SynestiaOS ` 栈的使用。 4 | 5 | 内核栈的代码位于 `SynestiaOS/SourceCode/Kernel/include/kstack.h` 和 `SynestiaOS/SourceCode/Kernel/src/kstack.c` 中。 6 | 7 | 8 | 9 | ### 栈的基础数据结构: 10 | 11 | ```c 12 | #define DEFAULT_KERNEL_STACK_SIZE 32 * KB 13 | 14 | typedef uint32_t VirtualAddress; 15 | 16 | typedef struct KernelStack { 17 | uint32_t size; 18 | VirtualAddress base; 19 | VirtualAddress top; 20 | VirtualAddress *virtualMemoryAddress; 21 | } __attribute__((packed)) KernelStack; 22 | ``` 23 | 24 | - 内核栈的默认大小是32KB。 25 | - 内核栈使用 `KernelStack` 来描述,size表示使用了的栈空间的大小;base是栈空间的起始地址(KernelStack的起始地址),top保存栈顶的指针; 26 | 27 | 28 | 29 | ### 内核栈的建立: 30 | 31 | ```c 32 | KernelStack *kstack_allocate() { 33 | // 1. allocate stack memory block from virtual memory (heap), and align. 34 | KernelStack *stack = (KernelStack *)kheap_alloc_aligned(DEFAULT_KERNEL_STACK_SIZE + sizeof(KernelStack), 16); 35 | if (stack == nullptr) { 36 | printf("[KStack] kStack allocate failed.\n"); 37 | return nullptr; 38 | } 39 | stack->virtualMemoryAddress = (uint32_t *)(stack + sizeof(KernelStack) + DEFAULT_KERNEL_STACK_SIZE); 40 | stack->size = 0; 41 | stack->base = stack->virtualMemoryAddress; 42 | stack->top = stack->base; 43 | return stack; 44 | } 45 | ``` 46 | 47 | - 在内存初始化页表后,剩余的所有物理空间都作为 `SynestiaOS` 的heap段,所以内核栈也都是在heap堆上分配出来的,分配的实际大小是 `DEFAULT_KERNEL_STACK_SIZE + 一个KernelStack的大小`,注意需要对齐 48 | - 下来初始化栈的属性,栈的 virtualMemoryAddress 就是实际使用的栈内存的起始地址 49 | - size设置为0 , 50 | - top指向栈顶,初始化的时候的栈顶是base。 51 | - 建立完成之后内存布局如下: 52 | 53 | ![](./imgs/stack/stackalloc.png) 54 | 55 | 56 | 57 | ### 压栈: 58 | 59 | ```c 60 | KernelStatus kstack_push(KernelStack *stack, uint32_t data) { 61 | if (kstack_is_full(stack)) { 62 | return ERROR; 63 | } 64 | stack->top = stack->top - sizeof(uint32_t); 65 | *(uint32_t *)(stack->top) = data; 66 | stack->size += 1; 67 | return OK; 68 | } 69 | ``` 70 | 71 | - 如果栈已满,则返回错误 72 | - top字段是栈顶的指针,压栈就将top的地址向上移动4个字节。 73 | - 之后压入数据data,将size属性加1。 74 | 75 | 76 | 77 | ### 出栈: 78 | 79 | ```c 80 | uint32_t kstack_pop(KernelStack *stack) { 81 | if (kstack_is_empty(stack)) { 82 | return ERROR; 83 | } 84 | uint32_t val = *(uint32_t *)(stack->top); 85 | stack->top = stack->top + sizeof(uint32_t); 86 | stack->size -= 1; 87 | return val; 88 | } 89 | ``` 90 | 91 | - 如果栈已经空了,就返回错误 92 | - 使用中间变量val保存出栈的数据,之后将top指针加4个字节。 93 | - 将size属性减1 94 | 95 | 96 | 97 | ### 清空栈: 98 | 99 | 注意这里是清空栈中的数据,而不是释放整个栈段。 100 | 101 | ```c 102 | KernelStatus kstack_clear(KernelStack *stack) { 103 | stack->size = 0; 104 | stack->top = stack->base; 105 | return OK; 106 | } 107 | ``` 108 | 109 | - 只需要把 `KernelStack` 的size和top重置就可以了,之前存放的数据就被认为是废弃的了。 110 | 111 | 112 | 113 | ### 释放栈: 114 | 115 | ```c 116 | KernelStatus kstack_free(KernelStack *stack) { 117 | stack->size = 0; 118 | stack->base = 0; 119 | stack->top = 0; 120 | return kheap_free(stack); 121 | } 122 | ``` 123 | 124 | - 这里是将整个栈进行释放,在分配栈的时候已经指出整个申请的堆的大小是 `DEFAULT_KERNEL_STACK_SIZE + sizeof(KernelStack)`,所以实际在内存上分配的大小是 `DEFAULT_KERNEL_STACK_SIZE + sizeof(KernelStack) + sizeof(HeapArea)`,释放的时候需要将这些内存全部释放掉。 125 | 126 | 127 | 128 | ### 其余的栈方法: 129 | 130 | 得到栈顶指针: 131 | 132 | ```c 133 | uint32_t kstack_peek(KernelStack *stack) { return *(uint32_t *)(stack->top); } 134 | ``` 135 | 136 | 判断栈是否为空: 137 | 138 | ```c 139 | bool kstack_is_empty(KernelStack *stack) { return stack->top == stack->base; } 140 | ``` 141 | 142 | 判断栈满: 143 | 144 | ```c 145 | bool kstack_is_full(KernelStack *stack) { return stack->top == stack->base - DEFAULT_KERNEL_STACK_SIZE; } 146 | ``` -------------------------------------------------------------------------------- /SynestiaOs基础设施:信号量.md: -------------------------------------------------------------------------------- 1 | 本篇文档介绍SynestiaOS实现的信号量Semaphore, 这里的信号量指的是SynestiaOS内核态使用的信号量,它和用户空间的信号有所不同。自旋锁适用于临界区比较短的情景,所以不会使得线程进入睡眠状态,那么信号量就是来解决临界区比较长的情景,所以信号量会使线程进入睡眠。那么由于会使进程进入睡眠状态,所以只能在进程上下文中获得信号量,因为中断上下文是不能进行调度的。另外一个就是占用信号量的时候补鞥呢再占用自旋锁了,因为线程会进入睡眠。 2 | 3 | 信号量分为计数信号量和二值信号量。回顾自旋锁的文档,自旋锁在某一个时刻只能允许一个线程持有,而信号量则没有要求这一点,它允许信号量同时持有的数量在定义信号量的时候可以指定,所以,信号量就需要一个属性来记录这个数量`count`,这个时候的信号量就叫做计数信号量,也就是允许多个线程同时访问临界区,听起来就会发生竞争,所以在内核中使用到它的情况并不多,但是对特定的代码是很有用的。 4 | 5 | 而我们一般想到的同一个时刻仅允许有一个信号量的持有者的情况下,就叫做二值信号量,也就是 `mutex` 了,这个放到`mutex` 文档中介绍。 6 | 7 | 信号量的定义位于:`SynestiaOS/SourceCode/Kernel/include/semphore.h` 和 `SynestiaOS/SourceCode/Kernel/src/semphore.c` 中。 8 | 9 | 10 | 11 | ### 信号量的定义 12 | 13 | ```c 14 | typedef struct Semphore { 15 | Atomic *count; 16 | KQueue *waitQueue; 17 | } Semphore; 18 | ``` 19 | 20 | - count代表可用的资源个数,由于有可能多个进程同时操作count属性,而且是进行加减操作,所以需要使用原子变量。 21 | - waitQueue是当前信号量的等待队列,如果资源为-1就代表有一个线程在等待信号量。 22 | 23 | 24 | 25 | ### 信号量的创建 26 | 27 | ```c 28 | void semphore_create(Semphore *semphore, Atomic *atomic, uint32_t count) { 29 | semphore->count = atomic; 30 | semphore->waitQueue = nullptr; 31 | atomic_set(atomic, count); 32 | } 33 | ``` 34 | 35 | - 传入的参数是一个信号量,一个原子变量,还有资源的个数; 36 | - 信号量的创建如上,进行字段的赋值。 37 | 38 | 39 | 40 | ### 信号量的DOWN操作 41 | 42 | ```c 43 | KernelStatus semphore_wait(Semphore *semphore) { 44 | if (atomic_get(semphore->count) > 0) { 45 | atomic_dec(semphore->count); 46 | return OK; 47 | } else { 48 | // can not get the lock, just add to lock wait list 49 | KernelStatus enQueueStatus = kqueue_enqueue(semphore->waitQueue, ¤tThread->threadReadyQueue); 50 | if (enQueueStatus != OK) { 51 | LogError("[Semphore]: thread add to wait list failed. \n"); 52 | return ERROR; 53 | } 54 | // reomve from schd list 55 | KernelStatus removeStatus = schd_remove_from_schduler(currentThread); 56 | if (removeStatus != OK) { 57 | LogError("[Semphore]: thread remove from schd list failed. \n"); 58 | return ERROR; 59 | } 60 | 61 | // 2. switch to the next thread in scheduler 62 | KernelStatus thradSwitchNextStatus = schd_switch_next(); 63 | if (thradSwitchNextStatus != OK) { 64 | LogError("[Semphore]: thread switch to next failed. \n"); 65 | return ERROR; 66 | } 67 | return OK; 68 | } 69 | } 70 | ``` 71 | 72 | - 该函数为SymestiaOS提供的内核API,用于信号量的sown操作,用于申请信号量。 73 | - 首先会判断当前想要获得的信号量中的资源个数,如果资源个数大于0,代表有资源可用,就把count变量减1,之后返回 74 | - 如果count的大小小于或者等于0了,代表没有资源可用了,这个时候就要把当前线程放入等待队列中,并把该线程从调度队列中移除,这里就会带来一次上下文切换的开销。 75 | - 当前进程被切换下CPU之后,就需要选择另一个线程来调度上CPU,这里就调用 `schd_switch_next()`,之后返回 76 | 77 | 78 | 79 | ### 信号量的UP操作 80 | 81 | ```c 82 | uint32_t semphore_post(Semphore *semphore) { 83 | atomic_inc(semphore->count); 84 | KQueue *queueNode = kqueue_dequeue(semphore->waitQueue); 85 | Thread *releasedThread = getNode(queueNode, Thread, threadReadyQueue); 86 | KernelStatus addToSchduler = schd_add_to_schduler(releasedThread); 87 | if (addToSchduler != OK) { 88 | LogError("[Semphore]: thread add to schduler failed. \n"); 89 | } 90 | return atomic_get(semphore->count); 91 | } 92 | ``` 93 | 94 | - 该函数为SymestiaOS提供的内核API,用于信号量的up操作,用于释放信号量。 95 | - 执行up操作的时候首先对原子变量执行加1操作,代表释放了资源 96 | - 之后从等待队列里取出第一个线程,注意线程进入等待队列中是通过每一个线程的 `KQueue threadReadyQueue;` 字段管理的,所以拿出来的是 `KQueue threadReadyQueue;`,需要通过 `getNode` 拿到 `Thread` 的首地址,之后再将线程添加到调度队列中去。 97 | - 返回值会返回资源个数 -------------------------------------------------------------------------------- /Synestia进程管理:进程基础设施.md: -------------------------------------------------------------------------------- 1 | 中文版 2 | 3 | 目前 SynestiaOs 实现了内核进程,本篇文档介绍 SynestiaOs 中进程的基础设施,包括进程的定义、进程的状态、进程的优先级、进程的 4 | 5 | 6 | 7 | 进程的定义如下: 8 | 9 | ```c 10 | typedef struct Thread { 11 | uint32_t magic; 12 | CpuContextSave cpuContextSave; 13 | 14 | uint64_t pid; 15 | char name[THREAD_NAME_LENGTH]; 16 | KernelStack *stack; 17 | ThreadStartRoutine entry; 18 | VMMAssociatedSpace vmmSpace; 19 | 20 | uint32_t flags; 21 | uint32_t signals; 22 | 23 | ThreadStatus threadStatus; 24 | ListNode threadList; 25 | KQueue threadReadyQueue; 26 | 27 | uint32_t priority; 28 | 29 | RBNode rbTree; 30 | uint64_t startTime; 31 | uint32_t runtimeNs; 32 | uint32_t runtimVirtualNs; 33 | 34 | bool interruptable; 35 | 36 | CpuNum lastCpu; 37 | CpuNum currCpu; 38 | CpuMask cpuAffinity; 39 | void *arg; 40 | 41 | uint32_t returnCode; 42 | } __attribute__((packed)) Thread; 43 | ``` 44 | 45 | - magic为每一个进程的魔数,用于调试,关于魔数字,有一个宏定义了 `#define THREAD_MAGIC (0x74687264)`,是ASCII码,转换过来就是`THRE`。 46 | - cpuContextSave 是 CpuContextSave 类型的结构体,目前没有使用。 47 | - pid:进程的ID 48 | - name:进程的名字 49 | - stack:进程栈,内核进程的栈位于内核空间中,用户态的栈位于用户空间中,只是分配的基址不一样,结构是一样的。 50 | - 51 | 52 | -------------------------------------------------------------------------------- /_Footer.md: -------------------------------------------------------------------------------- 1 | [SynestiaOS](https://synestiaos.org/) -------------------------------------------------------------------------------- /imgs/Heap/heap1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heap1.png -------------------------------------------------------------------------------- /imgs/Heap/heap2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heap2.png -------------------------------------------------------------------------------- /imgs/Heap/heap3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heap3.png -------------------------------------------------------------------------------- /imgs/Heap/heapalloc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heapalloc1.png -------------------------------------------------------------------------------- /imgs/Heap/heapalloc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heapalloc2.png -------------------------------------------------------------------------------- /imgs/Heap/heapfree1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heapfree1.png -------------------------------------------------------------------------------- /imgs/Heap/heapfree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/Heap/heapfree2.png -------------------------------------------------------------------------------- /imgs/KQueue/dequeue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/KQueue/dequeue.png -------------------------------------------------------------------------------- /imgs/KQueue/enqueue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/KQueue/enqueue.png -------------------------------------------------------------------------------- /imgs/List/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/List/example.png -------------------------------------------------------------------------------- /imgs/List/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/List/example2.png -------------------------------------------------------------------------------- /imgs/env/clion_cmake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/clion_cmake.png -------------------------------------------------------------------------------- /imgs/env/clion_debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/clion_debug.png -------------------------------------------------------------------------------- /imgs/env/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/debug.png -------------------------------------------------------------------------------- /imgs/env/external_tool.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/external_tool.png -------------------------------------------------------------------------------- /imgs/env/reset_cmake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/reset_cmake.png -------------------------------------------------------------------------------- /imgs/env/run_qemu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/env/run_qemu.png -------------------------------------------------------------------------------- /imgs/mem/PTE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/mem/PTE.png -------------------------------------------------------------------------------- /imgs/mem/pagetable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/mem/pagetable.png -------------------------------------------------------------------------------- /imgs/stack/stackalloc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/imgs/stack/stackalloc.png -------------------------------------------------------------------------------- /kernel_vm_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SynestiaOS/Documentation/ce9a0c6457f39f444db174dc9b5db02c98c5b434/kernel_vm_map.png -------------------------------------------------------------------------------- /开发环境搭建.md: -------------------------------------------------------------------------------- 1 | # 开发环境搭建 2 | 3 | # IDE 4 | 1. 推荐使用 Clion 来作为开发工具,因该项目使用 cmake 来进行构建,因此和 Clion 完美搭配,可以使用到 Clion 的全部功能,如代码提示,clang-tidy 代码分析,警告,快速的构建等 5 | 2. 使用 Clion 打开该工程,如下配置 Clion 中的 cmake,配置参数:-DCMAKE_TOOLCHAIN_FILE=/Users/shifu.wu/code/kernel/SynestiaOS/CMake/ToolchainMacArm.cmake -DARCH=arm -DPLATFORM=pi2,修改路径为本地机器的实际路径 6 | ![](./imgs/env/clion_cmake.png) 7 | 3. reset cmake,如下 8 | ![](./imgs/env/reset_cmake.png) 9 | 4. 由于我本机为 x86 架构,无法直接运行 arm 架构的文件,因此使用 Clion 中的 external tool 来运行 qemu 来运行该内核文件,配置 external tool 如下 10 | ![](./imgs/env/external_tool.png) 11 | 5. 点击运行按钮,即可直接在 Clion 中运行该内核了 12 | ![](./imgs/env/run_qemu.png) 13 | 6. 在 Clion 中调试,同样可以使用 external tool 来先运行 qemu,并打开 qemu 的调试功能,使用命令:qemu-system-arm -M raspi2 -kernel Kernel.elf -gdb tcp::9001 -S -nographic,可以修改端口号为其他端口,启动 qemu 调试后,在 Clion 中配置如下 14 | ![](./imgs/env/clion_debug.png) 15 | 7. 点击 debug 按钮,然后即可开始在 Clion 中调试 16 | ![](./imgs/env/debug.png) 17 | --------------------------------------------------------------------------------