欢迎来到DragonOS的文档!
DragonOS简介
DragonOS龙操作系统(以下简称“DragonOS”)是一个面向服务器领域的,从0开发内核及用户态环境,并提供Linux兼容性的64位操作系统。它使用Rust与C语言进行编写,并正在逐步淘汰原有的C代码,以在将来提供更好的安全性与可靠性。
DragonOS的目标是,构建一个完全独立自主的、开源的、高性能及高可靠性的服务器操作系统,为国家数字基础设施建设提供完全独立自主的底层核心动力。
作为一个社区驱动的开源操作系统,为了促进其发展,避免让其遭受一些不遵守开源协议的商业公司的侵权,我们决定使用GPLv2协议开放源代码,以严格的开源协议来保护DragonOS。
你可能对DragonOS中已经实现了哪些功能感兴趣,您可以转到这里:功能特性
DragonOS的功能
规范
启动引导:Multiboot2
接口:posix 2008
内核层
内存管理
页分配器
slab分配器
VMA
MMIO地址空间自动分配
多核
多核引导
ipi框架
进程管理
进程创建
进程回收
内核线程
fork
exec
进程睡眠(支持高精度睡眠)
kthread机制
同步原语
mutex互斥量
semaphore信号量
atomic原子变量
spinlock自旋锁
wait_queue等待队列
调度
CFS调度器
单核调度
IPC
匿名pipe管道
signal信号
文件系统
VFS
fat12/16/32
Devfs
RamFS
procfs
异常及中断处理
APIC
softirq 软中断
内核栈traceback
内核数据结构
普通二叉树
kfifo缓冲区
循环链表
IDR
内核实用库
LZ4压缩库(1.9.3)
字符串操作库
ELF可执行文件支持
printk
基础数学库
屏幕管理器
textui框架
CRC函数库
系统调用
测试框架
ktest
驱动程序
ACPI 高级电源配置模块
IDE硬盘
AHCI硬盘
PCI
XHCI(usb3.0)
ps/2 键盘
ps/2 鼠标
HPET高精度定时器
RTC时钟
local apic定时器
UART串口
VBE显示
VirtIO网卡
x87FPU
TTY终端
用户层
LibC
基础系统调用
基础标准库函数
部分数学函数
shell命令行程序
基于字符串匹配的解析
基本的几个命令
驱动程序
ps/2键盘用户态驱动
软件移植
GCC 11.3.0 (暂时只支持了x86_64的Cross Compiler)https://github.com/DragonOS-Community/gcc
binutils 2.38(暂时只支持了x86_64的Cross Compiler)https://github.com/DragonOS-Community/binutils
mpfr 4.1.1 https://github.com/DragonOS-Community/mpfr
构建DragonOS
1.写在前面
无论您采用后文中的何种方式来编译DragonOS,您必须先按照本小节中的步骤,初始化您的开发环境。
开始之前,您需要一台运行Linux或MacOS的计算机,并且处理器架构为X86-64.
对于Linux发行版,建议使用Ubuntu22、Debian、Arch Linux这样的,仓库软件版本较新的发行版,这能为您减少很多麻烦。
1.1 下载DragonOS的源代码
假设您的计算机上已经安装了git,您可以通过以下命令,获得DragonOS的最新的源代码:
git clone https://github.com/DragonOS-Community/DragonOS
cd DragonOS
警告
DragonOS的源代码托管在Github上,但是,由于Github在国内的访问速度较慢,因此,我们建议您在github上绑定您的电脑的ssh公钥, 然后通过以下命令来克隆代码,防止频繁出现git clone、pull、push失败的情况。
git clone git@github.com:DragonOS-Community/DragonOS.git
2.使用一键初始化脚本进行安装(推荐)
我们提供了一键初始化脚本,可以一键安装,只需要在控制台运行以下命令:
cd tools
bash bootstrap.sh # 这里请不要加上sudo, 因为需要安装的开发依赖包是安装在用户环境而非全局环境
备注
一键配置脚本目前只支持以下系统:
Ubuntu/Debian/Deepin/UOS 等基于Debian的衍生版本
欢迎您为其他的系统完善构建脚本!
如果一键初始化脚本能够正常运行,并输出最终的“祝贺”界面(如下所示),请关闭当前终端,然后重新打开。
|-----------Congratulations!---------------|
| |
| 你成功安装了DragonOS所需的依赖项! |
| |
| 请关闭当前终端, 并重新打开一个终端 |
| 然后通过以下命令运行: |
| |
| make run |
| |
|------------------------------------------|
接着,请直接跳到编译命令讲解进行阅读!
3.手动安装
3.1 依赖清单
如果自动安装脚本不能支持您的操作系统,那么您需要手动安装依赖程序。以下是依赖项的清单:
在以下依赖项中,除了docker-ce
和Rust及其工具链
以外,其他的都能通过系统自带的包管理器进行安装。关于docker以及rust的安装,请看后文。
docker-ce
llvm-dev
libclang-dev
clang
gcc-multilib
qemu qemu-system qemu-kvm
build-essential
fdisk
lsb-release
git
dosfstools
Rust以及其工具链
请留意,若您的Linux系统是在虚拟机中运行的,还请您在您的VMware/Virtual Box虚拟机的处理器设置选项卡中,开启Intel VT-x或AMD-V选项,否则,DragonOS将无法运行。
备注
在某些Linux发行版的软件仓库中构建的Qemu可能由于版本过低而不兼容DragonOS,如果遇到这种问题,请卸载Qemu,并采用编译安装的方式重新安装Qemu
在该地址下载Qemu源代码: https://download.qemu.org/
解压后进入源代码目录,然后执行下列命令:
# 安装编译依赖项
sudo apt install -y autoconf automake autotools-dev curl libmpc-dev libmpfr-dev libgmp-dev \
gawk build-essential bison flex texinfo gperf libtool patchutils bc \
zlib1g-dev libexpat-dev pkg-config libglib2.0-dev libpixman-1-dev libsdl2-dev \
git tmux python3 python3-pip ninja-build
./configure --enable-kvm
make -j 8
sudo make install
# 编译安装完成
请注意,编译安装的QEMU,将通过VNC模式进行链接,因此,您还需要在您的计算机上安装VNC viewer以连接至QEMU虚拟机。
3.2 安装Docker
您可以在docker官网下载安装docker-ce.
3.3 安装Rust
警告
【常见误区】:如果您打算采用docker进行编译,尽管docker镜像中已经安装了Rust编译环境,但是,为了能够在VSCode中使用Rust-Analyzer进行代码提示,以及make clean
命令能正常运行,您的客户机上仍然需要安装rust环境。
您可以在控制台输入以下命令,安装rust。
# 这两行用于换源,加速Rust的安装过程
export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static
export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup
# 安装Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly
# 把Rustup加到环境变量
echo "export PATH=\"\$HOME/.cargo/bin:\$PATH\"" >> ~/.bashrc
source ~/.cargo/env
source "$HOME/.cargo/env"
# 更换cargo的索引源
touch ~/.cargo/config
echo -e "[source.crates-io] \n \
registry = \"https://github.com/rust-lang/crates.io-index\" \n \
\n \
replace-with = 'dragonos-gitee' \n \
[source.dragonos-gitee] \n \
registry = \"https://gitee.com/DragonOS/crates.io-index.git\" \n \
" > ~/.cargo/config
# 安装DragonOS所需的工具链
cargo install cargo-binutils
rustup toolchain install nightly
rustup default nightly
rustup component add rust-src
rustup component add llvm-tools-preview
rustup target add x86_64-unknown-none
# Rust安装完成
至此,公共依赖项已经安装完成,您可以根据自己的需要,阅读后续章节
关于编译命令的用法,请见:编译命令讲解
4.从Docker构建(不推荐)
DragonOS发布了一个Docker编译环境,便于开发者运行DragonOS。但是,由于编码过程仍需要在客户机上进行,因此,您需要在客户机上安装Rust编译环境。
本节假设以下操作均在Linux下进行。
4.1 安装qemu虚拟机
在本节中,我们建议您采用命令行安装qemu:
sudo apt install -y qemu qemu-system qemu-kvm
4.2 创建磁盘镜像
首先,您需要使用tools文件夹下的create_hdd_image.sh,创建一块虚拟磁盘镜像。您需要在tools文件夹下运行此命令。
bash create_hdd_image.sh
4.3 运行DragonOS
如果不出意外的话,这将是运行DragonOS的最后一步。您只需要在DragonOS的根目录下方,执行以下命令,即可运行DragonOS。
make run-docker
稍等片刻,DragonOS将会被运行。
在qemu虚拟机被启动后,我们需要在控制台输入字母c
,然后回车。这样,虚拟机就会开始执行。
备注
首次编译时,由于需要下载Rust相关的索引(几百MB大小),因此需要一定的时间,请耐心等候!
输入命令可能需要加上sudo
关于编译命令的用法,请见:编译命令讲解
5.其他注意事项
5.1 创建磁盘镜像
首先,您需要使用普通用户权限运行tools/create_hdd_image.sh
,为DragonOS创建一块磁盘镜像文件。该脚本会自动完成创建磁盘镜像的工作,并将其移动到bin/
目录下。
请注意,由于权限问题,请务必使用普通用户权限运行此脚本。(运行后,需要提升权限时,系统可能会要求您输入密码)
5.2 编译、运行DragonOS
安装编译及运行环境
进入DragonOS文件夹
输入
make run
即可编译并写入磁盘镜像,并运行
在qemu虚拟机被启动后,我们需要在控制台输入字母c
,然后回车。这样,虚拟机就会开始执行。
备注
首次编译时,由于需要下载Rust相关的索引(几百MB大小),因此需要一定的时间,请耐心等候!
关于编译命令的用法,请见:编译命令讲解
6.编译命令讲解
本地编译,不运行:
make all -j 您的CPU核心数
本地编译,并写入磁盘镜像,不运行:
make build
本地编译,写入磁盘镜像,并在QEMU中运行:
make run
Docker编译,并写入磁盘镜像,:
make docker
Docker编译,写入磁盘镜像,并在QEMU中运行:
make run-docker
不编译,直接从已有的磁盘镜像启动:
make qemu
备注
如果您需要在vnc中运行DragonOS,请在上述命令后加上-vnc
后缀。如:make run-vnc
DragonOS镜像站
您可以从以下镜像站下载到DragonOS的源代码和其他文件:
引导加载
DragonOS采用GRUB2作为其引导加载程序,支持Multiboot2协议引导。目前仅支持GRUB2.06版本。
引导加载程序
原理
目前,DragonOS支持Legacy BIOS以及UEFI两种方式,进行启动引导。
在head.S
的头部包含了Multiboot2引导头,里面标志了一些Multiboot2相关的特定信息,以及一些配置命令。
在DragonOS的启动初期,会存储由GRUB2传来的magic number以及multiboot2_boot_info_addr。当系统进入Start_Kernel
函数之后,将会把这两个信息保存到multiboot2驱动程序之中。信息的具体含义请参照Multiboot2 Specification进行理解,该部分难度不大,相信读者经过思考能理解其中的原理。
参考资料
Multiboot2支持模块
Multiboot2支持模块提供对Multiboot2协议的支持。位于kernel/driver/multiboot2
文件夹中。
根据Multiboot2协议,操作系统能够从BootLoader处获得一些信息,比如基本的硬件信息以及ACPI表的起始地址等。
数据结构
kernel/driver/multiboot2/multiboot2.h
中按照Multiboot2协议的规定,定义了大部分的数据结构,具体细节可查看该文件: DragonOS/multiboot2.h at master · fslongjin/DragonOS · GitHub
迭代器
由于Multiboot2的信息存储在自multiboot2_boot_info_addr
开始的一段连续的内存空间之中,且不同类型的header的长度不同,因此设计了一迭代器multiboot2_iter
。
函数原型
void multiboot2_iter(bool (*_fun)(const struct iter_data_t *, void *, unsigned int *),
void *data, unsigned int *count)
_fun
指定的handler。当某个header的tag与该handler所处理的tag相同时,handler将处理该header,并返回true。
其第一个参数为tag类型,第二个参数为返回的数据的指针,第三个值为计数(某些没有用到该值的地方,该值可以为空)
data
传递给_fun
的第二个参数,_fun
函数将填充该指针所指向的内存区域,从而返回数据。
count
当返回的data为一个列表时,通过该值来指示列表中有多少项。
迭代工作函数
在模块中,按照我们需要获取不同类型的tag的需要,定义了一些迭代器工作函数。
核心API文档
这里是DragonOS的核心api文档。
DragonOS内核核心API
循环链表管理函数
循环链表是内核的重要的数据结构之一。包含在kernel/common/list.h
中。
void list_init(struct List *list)
描述
初始化一个List结构体,使其prev和next指针指向自身
参数
list
要被初始化的List结构体
void list_add(struct List *entry, struct List *node)
描述
将node插入到entry的后方
参数
entry
已存在于循环链表中的一个结点
node
待插入的结点
void list_append(struct List *entry, struct List *node)
描述
将node插入到entry的前方
参数
entry
已存在于循环链表中的一个结点
node
待插入的结点
void list_del(struct List *entry)
描述
从链表中删除结点entry
参数
entry
待删除的结点
list_del_init(struct List *entry)
描述
从链表中删除结点entry,并将这个entry使用list_init()进行重新初始化。
参数
entry
待删除的结点
bool list_empty(struct List *entry)
描述
判断链表是否为空
参数
entry
链表中的一个结点
struct List *list_prev(struct List *entry)
描述
获取entry的前一个结点
参数
entry
链表中的一个结点
struct List *list_next(struct List *entry)
描述
获取entry的后一个结点
参数
entry
链表中的一个结点
void list_replace(struct List *old, struct List *new)
描述
将链表中的old结点替换成new结点
参数
old
要被换下来的结点
new
要被换入链表的新的结点
list_entry(ptr, type, member)
描述
该宏能通过ptr指向的List获取到List所处的结构体的地址
参数
ptr
指向List结构体的指针
type
要被换入链表的新的结点
member
List结构体在上述的“包裹list结构体的结构体”中的变量名
list_first_entry(ptr, type, member)
描述
获取链表中的第一个元素。请注意,该宏要求链表非空,否则会出错。
参数
与list_entry()相同
list_first_entry_or_null(ptr, type, member)
描述
获取链表中的第一个元素。若链表为空,则返回NULL。
参数
与list_entry()相同
list_last_entry(ptr, type, member)
描述
获取链表中的最后一个元素。请注意,该宏要求链表非空,否则会出错。
参数
与list_entry()相同
list_last_entry_or_full(ptr, type, member)
描述
获取链表中的最后一个元素。若链表为空,则返回NULL。
参数
与list_entry()相同
list_next_entry(pos, member)
描述
获取链表中的下一个元素
参数
pos
指向当前的外层结构体的指针
member
链表结构体在外层结构体内的变量名
list_prev_entry(pos, member)
描述
获取链表中的上一个元素
参数
list_for_each(ptr, head)
描述
遍历整个链表(从前往后)
参数
ptr
指向List结构体的指针
head
指向链表头结点的指针(struct List*)
list_for_each_prev(ptr, head)
描述
遍历整个链表(从后往前)
参数
list_for_each_safe(ptr, n, head)
描述
从前往后遍历整个链表(支持删除当前链表结点)
该宏通过暂存中间变量,防止在迭代链表的过程中,由于删除了当前ptr所指向的链表结点从而造成错误.
参数
ptr
指向List结构体的指针
n
用于存储临时值的List类型的指针
head
指向链表头结点的指针(struct List*)
list_for_each_prev_safe(ptr, n, head)
描述
从后往前遍历整个链表.(支持删除当前链表结点)
该宏通过暂存中间变量,防止在迭代链表的过程中,由于删除了当前ptr所指向的链表结点从而造成错误.
参数
list_for_each_entry(pos, head, member)
描述
从头开始迭代给定类型的链表
参数
pos
指向特定类型的结构体的指针
head
指向链表头结点的指针(struct List*)
member
struct List在pos的结构体中的成员变量名
list_for_each_entry_reverse(pos, head, member)
描述
逆序迭代给定类型的链表
参数
list_for_each_entry_safe(pos, n, head, member)
描述
从头开始迭代给定类型的链表(支持删除当前链表结点)
参数
pos
指向特定类型的结构体的指针
n
用于存储临时值的,和pos相同类型的指针
head
指向链表头结点的指针(struct List*)
member
struct List在pos的结构体中的成员变量名
list_prepare_entry(pos, head, member)
描述
为list_for_each_entry_continue()准备一个’pos’结构体
参数
pos
指向特定类型的结构体的,用作迭代起点的指针
head
指向要开始迭代的struct List结构体的指针
member
struct List在pos的结构体中的成员变量名
list_for_each_entry_continue(pos, head, member)
描述
从指定的位置的【下一个元素开始】,继续迭代给定的链表
参数
pos
指向特定类型的结构体的指针。该指针用作迭代的指针。
head
指向要开始迭代的struct List结构体的指针
member
struct List在pos的结构体中的成员变量名
list_for_each_entry_continue_reverse(pos, head, member)
描述
从指定的位置的【上一个元素开始】,【逆序】迭代给定的链表
参数
list_for_each_entry_from(pos, head, member)
描述
从指定的位置开始,继续迭代给定的链表
参数
list_for_each_entry_safe_continue(pos, n, head, member)
描述
从指定的位置的【下一个元素开始】,继续迭代给定的链表.(支持删除当前链表结点)
参数
pos
指向特定类型的结构体的指针。该指针用作迭代的指针。
n
用于存储临时值的,和pos相同类型的指针
head
指向要开始迭代的struct List结构体的指针
member
struct List在pos的结构体中的成员变量名
list_for_each_entry_safe_continue_reverse(pos, n, head, member)
描述
从指定的位置的【上一个元素开始】,【逆序】迭代给定的链表。(支持删除当前链表结点)
参数
list_for_each_entry_safe_from(pos, n, head, member)
描述
从指定的位置开始,继续迭代给定的链表.(支持删除当前链表结点)
参数
基础C函数库
内核编程与应用层编程不同,你将无法使用LibC中的函数来进行编程。为此,内核实现了一些常用的C语言函数,并尽量使其与标准C库中的函数行为相近。值得注意的是,这些函数的行为可能与标准C库函数不同,请在使用时仔细阅读以下文档,这将会为你带来帮助。
字符串操作
int strlen(const char *s)
描述
测量并返回字符串长度。
参数
src
源字符串
long strnlen(const char *src, unsigned long maxlen)
描述
测量并返回字符串长度。当字符串长度大于maxlen时,返回maxlen
参数
src
源字符串
maxlen
最大长度
long strnlen_user(const char *src, unsigned long maxlen)
描述
测量并返回字符串长度。当字符串长度大于maxlen时,返回maxlen。
该函数会进行地址空间校验,要求src字符串必须来自用户空间。当源字符串来自内核空间时,将返回0.
参数
src
源字符串,地址位于用户空间
maxlen
最大长度
char *strncpy(char *dst, const char *src, long count)
描述
拷贝长度为count个字节的字符串,返回dst字符串
参数
src
源字符串
dst
目标字符串
count
要拷贝的源字符串的长度
char *strcpy(char *dst, const char *src)
描述
拷贝源字符串,返回dst字符串
参数
src
源字符串
dst
目标字符串
long strncpy_from_user(char *dst, const char *src, unsigned long size)
描述
从用户空间拷贝长度为count个字节的字符串到内核空间,返回拷贝的字符串的大小
该函数会对字符串的地址空间进行校验,防止出现地址空间越界的问题。
参数
src
源字符串
dst
目标字符串
size
要拷贝的源字符串的长度
int strcmp(char *FirstPart, char *SecondPart)
描述
比较两个字符串的大小。
返回值
情况 |
返回值 |
---|---|
FirstPart == SecondPart |
0 |
FirstPart > SecondPart |
1 |
FirstPart < SecondPart |
-1 |
参数
FirstPart
第一个字符串
SecondPart
第二个字符串
printk(const char* fmt, ...)
描述
该宏能够在控制台上以黑底白字格式化输出字符串.
参数
fmt
源格式字符串
…
可变参数
printk_color(unsigned int FRcolor, unsigned int BKcolor, const char* fmt, ...)
描述
在控制台上以指定前景色和背景色格式化输出字符串.
参数
FRcolor
前景色
BKcolor
背景色
fmt
源格式字符串
…
可变参数
int vsprintf(char *buf, const char *fmt, va_list args)
描述
按照fmt格式化字符串,并将结果输出到buf中,返回写入buf的字符数量。
参数
buf
输出缓冲区
fmt
源格式字符串
args
可变参数列表
int sprintk(char *buf, const char *fmt, ...)
描述
按照fmt格式化字符串,并将结果输出到buf中,返回写入buf的字符数量。
参数
buf
输出缓冲区
fmt
源格式字符串
…
可变参数
内存操作
void *memcpy(void *dst, const void *src, uint64_t size)
描述
将内存从src处拷贝到dst处。
参数
dst
指向目标地址的指针
src
指向源地址的指针
size
待拷贝的数据大小
void *memmove(void *dst, const void *src, uint64_t size)
描述
与memcpy()
类似,但是在源数据区域与目标数据区域之间存在重合时,该函数能防止数据被错误的覆盖。
参数
dst
指向目标地址的指针
src
指向源地址的指针
size
待拷贝的数据大小
CRC函数
函数列表
uint8_t crc7(uint8_t crc, const uint8_t *buffer, size_t len)
uint8_t crc8(uint8_t crc, const uint8_t *buffer, size_t len)
uint16_t crc16(uint16_t crc, uint8_t const *buffer, size_t len)
uint32_t crc32(uint32_t crc, uint8_t const *buffer, size_t len)
uint64_t crc64(uint64_t crc, uint8_t const *buffer, size_t len)
描述
用于计算循环冗余校验码
参数说明
crc
传入的CRC初始值
buffer
待处理的数据缓冲区
len
缓冲区大小(字节)
原子变量
简介
DragonOS实现了原子变量,类型为atomic_t. 原子变量是基于具体体系结构的原子操作指令实现的。具体实现在kernel/common/atomic.h
中。
API
请注意,以下API均为原子操作。
inline void atomic_add(atomic_t *ato, long val)
描述
原子变量增加指定值
参数
ato
原子变量对象
val
变量要增加的值
inline void atomic_sub(atomic_t *ato, long val)
描述
原子变量减去指定值
参数
ato
原子变量对象
val
变量要被减去的值
void atomic_inc(atomic_t *ato)
描述
原子变量自增1
参数
ato
原子变量对象
void atomic_dec(atomic_t *ato)
描述
原子变量自减1
参数
ato
原子变量对象
inline void atomic_set_mask(atomic_t *ato, long mask)
描述
将原子变量的值与mask变量进行or运算
参数
ato
原子变量对象
mask
与原子变量进行or运算的变量
inline void atomic_clear_mask(atomic_t *ato, long mask)
描述
将原子变量的值与mask变量进行and运算
参数
ato
原子变量对象
mask
与原子变量进行and运算的变量
内核数据结构
内核中实现了常用的几种数据结构,这里是他们的api文档。
kfifo先进先出缓冲区
kfifo先进先出缓冲区定义于common/kfifo.h
中。您可以使用它,创建指定大小的fifo缓冲区(最大大小为4GB)
kfifo_alloc
int kfifo_alloc(struct kfifo_t *fifo, uint32_t size, uint64_t reserved)
描述
通过动态方式初始化kfifo缓冲队列。fifo缓冲区的buffer将由该函数进行申请。
参数
fifo
kfifo队列结构体的指针
size
缓冲区大小(单位:bytes)
reserved
当前字段保留,请将其置为0
返回值
当返回值为0时,表示正常初始化成功,否则返回对应的errno
kfifo_init
void kfifo_init(struct kfifo_t *fifo, void *buffer, uint32_t size)
描述
使用指定的缓冲区来初始化kfifo缓冲队列
参数
fifo
kfifo队列结构体的指针
buffer
缓冲区基地址指针
size
缓冲区大小(单位:bytes)
kfifo_free_alloc
void kfifo_free_alloc(struct kfifo_t* fifo)
描述
释放通过kfifo_alloc创建的fifo缓冲区. 请勿通过该函数释放其他方式创建的kfifo缓冲区。
参数
fifo
kfifo队列结构体的指针
kfifo_in
uint32_t kfifo_in(struct kfifo_t *fifo, const void *from, uint32_t size)
描述
向kfifo缓冲区推入指定大小的数据。当队列中空间不足时,则不推入数据。
参数
fifo
kfifo队列结构体的指针
from
源数据基地址指针
size
数据大小(单位:bytes)
返回值
返回成功被推入的数据的大小。
kfifo_out
uint32_t kfifo_out(struct kfifo_t *fifo, void *to, uint32_t size)
描述
从kfifo缓冲区取出数据,并从队列中删除数据。当队列中数据量不足时,则不取出。
参数
fifo
kfifo队列结构体的指针
to
目标缓冲区基地址指针
size
数据大小(单位:bytes)
返回值
返回成功被取出的数据的大小。
kfifo_out_peek
uint32_t kfifo_out_peek(struct kfifo_t *fifo, void *to, uint32_t size)
描述
从kfifo缓冲区取出数据,但是不从队列中删除数据。当队列中数据量不足时,则不取出。
参数
fifo
kfifo队列结构体的指针
to
目标缓冲区基地址指针
size
数据大小(单位:bytes)
返回值
返回成功被取出的数据的大小。
kfifo_reset
kfifo_reset(fifo)
描述
忽略kfifo队列中的所有内容,并把输入和输出偏移量都归零
参数
fifo
kfifo队列结构体的指针
kfifo_reset_out
kfifo_reset_out(fifo)
描述
忽略kfifo队列中的所有内容,并将输入偏移量赋值给输出偏移量
参数
fifo
kfifo队列结构体的指针
kfifo_total_size
kfifo_total_size(fifo)
描述
获取kfifo缓冲区的最大大小
参数
fifo
kfifo队列结构体的指针
返回值
缓冲区最大大小
kfifo_size
kfifo_size(fifo)
描述
获取kfifo缓冲区当前已使用的大小
参数
fifo
kfifo队列结构体的指针
返回值
缓冲区当前已使用的大小
kfifo_empty
kfifo_empty(fifo)
描述
判断kfifo缓冲区当前是否为空
参数
fifo
kfifo队列结构体的指针
返回值
情况 |
返回值 |
---|---|
空 |
1 |
非空 |
0 |
kfifo_full
kfifo_full(fifo)
描述
判断kfifo缓冲区当前是否为满
参数
fifo
kfifo队列结构体的指针
返回值
情况 |
返回值 |
---|---|
满 |
1 |
不满 |
0 |
ID Allocation
ida的主要作用是分配+管理id. 它能分配一个最小的, 未被分配出去的id. 当您需要管理某个数据结构时, 可能需要使用id来区分不同的目标. 这个时候, ida将会是很好的选择. 因为ida的十分高效, 运行常数相对数组更小, 而且提供了基本管理id需要用到的功能, 值得您试一试.
IDA定义于idr.h
文件中. 您通过DECLARE_IDA(my_ida)
来创建一个ida对象, 或者struct ida my_ida; ida_init(&my_ida);
来初始化一个ida.
ida_init
void ida_init(struct ida *ida_p)
描述
通初始化IDA, 你需要保证调用函数之前, ida的free_list为空, 否则会导致内存泄漏.
参数
ida_p
指向ida的指针
返回值
无返回值
ida_preload
int ida_preload(struct ida *ida_p, gfp_t gfp_mask)
描述
为ida预分配空间.您可以不自行调用, 因为当ida需要空间的时候, 内部会自行使用kmalloc
函数获取空间. 当然, 设计这个函数的目的是为了让您有更多的选择. 当您提前调用这个函数, 可以避免之后在开辟空间上的时间开销.
参数
ida_p
指向ida的指针
gfp_mask
保留参数, 目前尚未使用.
返回值
如果分配成功,将返回0; 否则返回负数错误码, 有可能是内存空间不够.
ida_alloc
int ida_alloc(struct ida *ida_p, int *p_id)
描述
获取一个空闲ID. 您需要注意, 返回值是成功/错误码.
参数
ida_p
指向ida的指针
p_id
您需要传入一个int变量的指针, 如果成功分配ID, ID将会存储在该指针所指向的地址.
返回值
如果分配成功,将返回0; 否则返回负数错误码, 有可能是内存空间不够.
ida_count
bool ida_count(struct ida *ida_p, int id)
描述
查询一个ID是否被分配.
参数
ida_p
指向ida的指针
id
您查询该ID是否被分配.
返回值
如果分配,将返回true; 否则返回false.
ida_remove
void ida_remove(struct ida *ida_p, int id)
描述
删除一个已经分配的ID. 如果该ID不存在, 该函数不会产生异常错误, 因为在检测到该ID不存在的时候, 函数将会自动退出.
参数
ida_p
指向ida的指针
id
您要删除的id.
返回值
无返回值.
ida_destroy
void ida_destroy(struct ida *ida_p)
描述
释放一个IDA所有的空间, 同时删除ida的所有已经分配的id.(所以您不用担心删除id之后, ida还会占用大量空间.)
参数
ida_p
指向ida的指针
返回值
无返回值
ida_empty
void ida_empty(struct ida *ida_p)
描述
查询一个ida是否为空
参数
ida_p
指向ida的指针
返回值
ida为空则返回true,否则返回false。
IDR
idr是一个基于radix-tree的ID-pointer的数据结构. 该数据结构提供了建id与数据指针绑定的功能, 它的主要功能有以下4个:
获取一个ID, 并且将该ID与一个指针绑定
删除一个已分配的ID
根据ID查找对应的指针
根据ID使用新的ptr替换旧的ptr
您可以使用DECLARE_idr(my_idr)
来创建一个idr。或者您也可以使用struct idr my_idr; idr_init(my_idr);
这两句话创建一个idr。 至于什么是radix-tree,您可以把他简单理解为一个向上生长的多叉树,在实现中,我们选取了64叉树。
idr_init
void idr_init(struct idr *idp)
描述
通初始化IDR, 你需要保证调用函数之前, idr的free_list为空, 否则会导致内存泄漏.
参数
idp
指向idr的指针
返回值
无返回值
idr_preload
int idr_preload(struct idr *idp, gfp_t gfp_mask)
描述
为idr预分配空间.您可以不自行调用, 因为当idr需要空间的时候, 内部会自行使用kmalloc
函数获取空间. 当然, 设计这个函数的目的是为了让您有更多的选择. 当您提前调用这个函数, 可以避免之后在开辟空间上的时间开销.
参数
idp
指向idr的指针
gfp_mask
保留参数, 目前尚未使用.
返回值
如果分配成功,将返回0; 否则返回负数错误码, 有可能是内存空间不够.
idr_alloc
int idr_alloc(struct idr *idp, void *ptr, int *id)
描述
获取一个空闲ID. 您需要注意, 返回值是成功/错误码.
调用这个函数,需要您保证ptr是非空的,即: ptr != NULL
, 否则将会影响 idr_find/idr_find_next/idr_find_next_getid/...
等函数的使用。(具体请看这三个函数的说明,当然,只会影响到您的使用体验,并不会影响到idr内部函数的决策和逻辑)
参数
idp
指向ida的指针
ptr
指向数据的指针
id
您需要传入一个int变量的指针, 如果成功分配ID, ID将会存储在该指针所指向的地址.
返回值
如果分配成功,将返回0; 否则返回负数错误码, 有可能是内存空间不够.
idr_remove
void* idr_remove(struct idr *idp, int id)
描述
删除一个id, 但是不释放对应的ptr指向的空间, 同时返回这个被删除id所对应的ptr。 如果该ID不存在, 该函数不会产生异常错误, 因为在检测到该ID不存在的时候, 函数将会自动退出,并返回NULL。
参数
idp
指向idr的指针
id
您要删除的id.
返回值
如果删除成功,就返回被删除id所对应的ptr;否则返回NULL。注意:如果这个id本来就和NULL绑定,那么也会返回NULL
idr_remove_all
void idr_remove_all(struct idr *idp)
描述
删除idr的所有已经分配的id.(所以您不用担心删除id之后, idr还会占用大量空间。)
但是你需要注意的是,调用这个函数是不会释放数据指针指向的空间的。 所以您调用该函数之前, 确保IDR内部的数据指针被保存。否则当IDR删除所有ID之后, 将会造成内存泄漏。
参数
idp
指向idr的指针
返回值
无返回值
idr_destroy
void idr_destroy(struct idr *idp)
描述
释放一个IDR所有的空间, 同时删除idr的所有已经分配的id.(所以您不用担心删除id之后, ida还会占用大量空间.) - 和idr_remove_all
的区别是, 释放掉所有的空间(包括free_list的预分配空间)。
参数
idp
指向idr的指针
返回值
无返回值
idr_find
void *idr_find(struct idr *idp, int id)
描述
查询一个ID所绑定的数据指针
参数
idp
指向idr的指针
id
您查询该ID的数据指针
返回值
如果分配,将返回该ID对应的数据指针; 否则返回NULL.(注意, 返回NULL不一定代表这ID不存在,有可能该ID就是与空指针绑定。)
当然,我们也提供了idr_count
函数来判断id是否被分配,具体请查看idr_count介绍。
idr_find_next
void *idr_find_next(struct idr *idp, int start_id)
描述
传进一个start_id,返回满足 “id大于start_id的最小id” 所对应的数据指针。
参数
idp
指向idr的指针
start_id
您提供的ID限制
返回值
如果分配,将返回该ID对应的数据指针; 否则返回NULL.(注意, 返回NULL不一定代表这ID不存在,有可能该ID就是与空指针绑定。)
当然,我们也提供了idr_count
函数来判断id是否被分配,具体请查看idr_count介绍。
idr_find_next_getid
void *idr_find_next_getid(struct idr *idp, int start_id, int *nextid)
描述
传进一个start_id,返回满足 “id大于start_id的最小id” 所对应的数据指针。同时,你获取到这个满足条件的最小id, 即参数中的 *nextid。
参数
idp
指向idr的指针
start_id
您提供的ID限制
返回值
如果分配,将返回该ID对应的数据指针; 否则返回NULL.(注意, 返回NULL不一定代表这ID不存在,有可能该ID就是与空指针绑定。)
当然,我们也提供了idr_count
函数来判断id是否被分配,具体请查看idr_count介绍。
idr_replace
int idr_replace(struct idr *idp, void *ptr, int id)
描述
传进一个ptr,使用该ptr替换掉id所对应的Old_ptr。
参数
idp
指向idr的指针
ptr
您要替换原来的old_ptr的新指针
id
您要替换的指针所对应的id
返回值
0代表成功,否则就是错误码 - 代表错误。
idr_replace_get_old
int idr_replace_get_old(struct idr *idp, void *ptr, int id, void **oldptr)
描述
传进一个ptr,使用该ptr替换掉id所对应的Old_ptr,同时你可以获取到old_ptr。
参数
idp
指向idr的指针
ptr
您要替换原来的old_ptr的新指针
id
您要替换的指针所对应的id
old_ptr
您需要传进该(void**)指针,old_ptr将会存放在该指针所指向的地址。
返回值
0代表成功,否则就是错误码 - 代表错误。
idr_empty
void idr_empty(struct idr *idp)
描述
查询一个idr是否为空
参数
idp
指向idr的指针
返回值
idr为空则返回true,否则返回false。
idr_count
bool idr_count(struct idr *idp, int id)
描述
查询一个ID是否被分配.
参数
ida_p
指向idr的指针
id
您查询该ID是否被分配.
返回值
如果分配,将返回true; 否则返回false.
内存管理
这里快速讲解了如何在DragonOS中分配、使用内存。以便您能快速的了解这个模块。
详细的内存管理模块的文档请参见:内存管理文档
内存分配指南
DragonOS提供了一些用于内存分配的api。您可以使用kmalloc来分配小的内存块,也可以使用alloc_pages分配连续的2MB大小的内存页面。
选择合适的内存分配器
在内核中,最直接、最简单的分配内存的方式就是,使用kmalloc()
函数进行分配。并且,出于安全起见,除非内存在分配后一定会被覆盖,且您能确保内存中的脏数据一定不会对程序造成影响,在其余情况下,我们建议使用kzalloc()
进行内存分配,它将会在kmalloc()
的基础上,把申请到的内存进行清零。
您可以通过kmalloc()
函数分配得到32bytes到1MBytes之间的内存对象。并且,这些内存对象具有以下的性质:
内存起始地址及大小按照2次幂对齐。(比如,申请的是80bytes的内存空间,那么获得的内存对象大小为128bytes且内存地址按照128bytes对齐)
对于需要大量连续内存的分配,可以使用alloc_pages()
向页面分配器申请连续的内存页。
当内存空间不再被使用时,那么必须释放他们。若您使用的是kmalloc()
分配的内存,那么您需要使用kfree()
释放它。若是使用alloc_pages()
分配的内存,则需要使用free_pages()
来释放它们。
内存管理API
SLAB内存池
SLAB内存池提供小内存对象的分配功能。
void *kmalloc(unsigned long size, gfp_t gfp)
获取小块的内存。
描述
kmalloc用于获取那些小于2M内存页大小的内存对象。可分配的内存对象大小为32bytes~1MBytes. 且分配的内存块大小、起始地址按照2的n次幂进行对齐。(比如,申请的是80bytes的内存空间,那么获得的内存对象大小为128bytes且内存地址按照128bytes对齐)
参数
size
内存对象的大小
gfp
标志位
void *kzalloc(unsigned long size, gfp_t gfp)
描述
获取小块的内存,并将其清零。其余功能与kmalloc相同。
参数
size
内存对象的大小
gfp
标志位
unsigned long kfree(void *address)
释放从slab分配的内存。
描述
该函数用于释放通过kmalloc申请的内存。如果address
为NULL,则函数被调用后,无事发生。
请不要通过这个函数释放那些不是从kmalloc()
或kzalloc()
申请的内存,否则将会导致系统崩溃。
参数
address
指向内存对象的起始地址的指针
物理页管理
DragonOS支持对物理页的直接操作
struct Page *alloc_pages(unsigned int zone_select, int num, ul flags)
描述
从物理页管理单元中申请一段连续的物理页
参数
zone_select
要申请的物理页所位于的内存区域
可选值:
ZONE_DMA
DMA映射专用区域ZONE_NORMAL
正常的物理内存区域,已在页表高地址处映射ZONE_UNMAPPED_IN_PGT
尚未在页表中映射的区域
num
要申请的连续物理页的数目,该值应当小于64
flags
分配的页面要被设置成的属性
可选值:
PAGE_PGT_MAPPED
页面在页表中已被映射PAGE_KERNEL_INIT
内核初始化所占用的页PAGE_DEVICE
设备MMIO映射的内存PAGE_KERNEL
内核层页PAGE_SHARED
共享页
返回值
成功
成功申请则返回指向起始页面的Page结构体的指针
失败
当ZONE错误或内存不足时,返回NULL
void free_pages(struct Page *page, int number)
描述
从物理页管理单元中释放一段连续的物理页。
参数
page
要释放的第一个物理页的Page结构体
number
要释放的连续内存页的数量。该值应小于64
页表管理
int mm_map_phys_addr(ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool use4k)
描述
将一段物理地址映射到当前页表的指定虚拟地址处
参数
virt_addr_start
虚拟地址的起始地址
phys_addr_start
物理地址的起始地址
length
要映射的地址空间的长度
flags
页表项的属性
use4k
使用4级页表,将地址区域映射为若干4K页
int mm_map_proc_page_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool user, bool flush, bool use4k)
描述
将一段物理地址映射到指定页表的指定虚拟地址处
参数
proc_page_table_addr
指定的顶层页表的起始地址
is_phys
该顶层页表地址是否为物理地址
virt_addr_start
虚拟地址的起始地址
phys_addr_start
物理地址的起始地址
length
要映射的地址空间的长度
flags
页表项的属性
user
页面是否为用户态可访问
flush
完成映射后,是否刷新TLB
use4k
使用4级页表,将地址区域映射为若干4K页
返回值
映射成功:0
映射失败:-EFAULT
void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul length)
描述
取消给定页表中的指定地址空间的页表项映射。
参数
proc_page_table_addr
指定的顶层页表的基地址
is_phys
该顶层页表地址是否为物理地址
virt_addr_start
虚拟地址的起始地址
length
要取消映射的地址空间的长度
mm_unmap_addr(virt_addr, length)
描述
该宏定义用于取消当前进程的页表中的指定地址空间的页表项映射。
参数
virt_addr
虚拟地址的起始地址
length
要取消映射的地址空间的长度
内存信息获取
struct mm_stat_t mm_stat()
描述
获取计算机目前的内存空间使用情况
参数
无
返回值
返回值是一个mm_mstat_t
结构体,该结构体定义于mm/mm.h
中。其中包含了以下信息(单位均为字节):
参数名 |
解释 |
---|---|
total |
计算机的总内存数量大小 |
used |
已使用的内存大小 |
free |
空闲物理页所占的内存大小 |
shared |
共享的内存大小 |
cache_used |
位于slab缓冲区中的已使用的内存大小 |
cache_free |
位于slab缓冲区中的空闲的内存大小 |
available |
系统总空闲内存大小(包括kmalloc缓冲区) |
锁
这里是DragonOS的锁变量的说明文档。
锁的类型及其规则
简介
DragonOS内核实现了一些锁,大致可以分为两类:
休眠锁
自旋锁
锁的类型
休眠锁
休眠锁只能在可抢占的上下文之中被获取。
在DragonOS之中,实现了以下的休眠锁:
semaphore
mutex_t
自旋锁
spinlock_t
RawSpinLock(Rust版本的spinlock_t,但与spinlock_t不兼容)
SpinLock —— 在RawSpinLock的基础上,封装了一层守卫(Guard), 将锁及其要保护到的数据绑定在一个结构体内,并能在编译期避免未加锁就访问数据的问题。
进程在获取自旋锁后,将改变pcb中的锁变量持有计数,从而隐式地禁止了抢占。为了获得更多灵活的操作,spinlock还提供了以下的方法:
后缀 |
说明 |
---|---|
_irq() |
在加锁时关闭中断/在放锁时开启中断 |
_irqsave()/_irqrestore() |
在加锁时保存中断状态,并关中断/在放锁时恢复中断状态 |
当您同时需要使用自旋锁以及引用计数时,一个好的方法是:使用lockref
. 这是一种额外的加速技术,能额外提供“无锁修改引用计数”的功能。详情请见:lockref
详细介绍
自旋锁的详细介绍
关于自旋锁的详细介绍,请见文档:自旋锁
semaphore信号量
semaphore信号量是基于计数实现的。
当可用资源不足时,尝试对semaphore执行down操作的进程将会被休眠,直到资源可用。
mutex互斥量
请见Mutex文档
备注
作者:龙进 longjin@RinGoTek.cn
自旋锁
1.简介
自旋锁是用于多线程同步的一种锁,线程反复检查锁变量是否可用。由于线程在这一过程中保持运行的状态,因此是一种忙等待。一旦获取了自旋锁,线程会一直保持该锁,直至显式释放自旋锁。
DragonOS在kernel/src/lib/spinlock.rs
文件中,实现了自旋锁。根据功能特性的略微差异,分别提供了RawSpinLock
和SpinLock
两种自旋锁。
2. RawSpinLock - 原始自旋锁
RawSpinLock
是原始的自旋锁,其数据部分包含一个AtomicBool, 实现了自旋锁的基本功能。其加锁、放锁需要手动确定对应的时机,也就是说,和我们在其他语言中使用的自旋锁一样,
需要先调用lock()
方法,然后当离开临界区时,手动调用unlock()
方法。我们并没有向编译器显式地指定该自旋锁到底保护的是哪些数据。
RawSpinLock为程序员提供了非常自由的加锁、放锁控制。但是,正是由于它过于自由,因此在使用它的时候,我们很容易出错。很容易出现“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题。当使用RawSpinLock时,编译器并不能对这些情况进行检查,这些问题只能在运行时被发现。
警告
RawSpinLock
与C版本的spinlock_t
不具有二进制兼容性。如果由于暂时的兼容性的需求,要操作C版本的spinlock_t
,请使用spinlock.rs
中提供的C版本的spinlock_t的操作函数。
但是,对于新开发的功能,请不要使用C版本的spinlock_t
,因为随着代码重构的进行,我们将会移除它。
3. SpinLock - 具备守卫的自旋锁
SpinLock
在RawSpinLock
的基础上,进行了封装,能够在编译期检查出“未加锁就访问临界区的数据”、“忘记放锁”、“双重释放”等问题;并且,支持数据的内部可变性。
其结构体原型如下:
#[derive(Debug)]
pub struct SpinLock<T> {
lock: RawSpinlock,
/// 自旋锁保护的数据
data: UnsafeCell<T>,
}
3.1. 使用方法
您可以这样初始化一个SpinLock:
let x = SpinLock::new(Vec::new());
在初始化这个SpinLock时,必须把要保护的数据传入SpinLock,由SpinLock进行管理。
当需要读取、修改SpinLock保护的数据时,请先使用SpinLock的lock()
方法。该方法会返回一个SpinLockGuard
。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用)
完整示例如下方代码所示:
let x :SpinLock<Vec<i32>>= SpinLock::new(Vec::new());
{
let mut g :SpinLockGuard<Vec<i32>>= x.lock();
g.push(1);
g.push(2);
g.push(2);
assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]);
// 在此处,SpinLock是加锁的状态
kdebug!("x={:?}", x);
}
// 由于上方的变量`g`,也就是SpinLock守卫的生命周期结束,自动释放了SpinLock。因此,在此处,SpinLock是放锁的状态
kdebug!("x={:?}", x);
对于结构体内部的变量,我们可以使用SpinLock进行细粒度的加锁,也就是使用SpinLock包裹需要细致加锁的成员变量,比如这样:
pub struct a {
pub data: SpinLock<data_struct>,
}
当然,我们也可以对整个结构体进行加锁:
struct MyStruct {
pub data: data_struct,
}
/// 被全局加锁的结构体
pub struct LockedMyStruct(SpinLock<MyStruct>);
3.2. 原理
SpinLock
之所以能够实现编译期检查,是因为它引入了一个SpinLockGuard
作为守卫。我们在编写代码的时候,保证只有调用SpinLock
的lock()
方法加锁后,才能生成一个SpinLockGuard
。 并且,当我们想要访问受保护的数据的时候,都必须获得一个守卫。然后,我们为SpinLockGuard
实现了Drop
trait,当守卫的生命周期结束时,将会自动释放锁。除此以外,没有别的方法能够释放锁。因此我们能够得知,一个上下文中,只要SpinLockGuard
的生命周期没有结束,那么它就拥有临界区数据的访问权,数据访问就是安全的。
3.3. 存在的问题
3.3.1. 双重加锁
请注意,SpinLock
支持的编译期检查并不是万能的。它目前无法在编译期检查出“双重加锁”问题。试看这样一个场景:函数A中,获得了锁。然后函数B中继续尝试加锁,那么就造成了“双重加锁”问题。这样在编译期是无法检测出来的。
针对这个问题,我们建议采用这样的编程方法:
如果函数B需要访问临界区内的数据,那么,函数B应当接收一个类型为
&SpinLockGuard
的参数,这个守卫由函数A获得。这样一来,函数B就能访问临界区内的数据。
lockref
lockref是将自旋锁与引用计数变量融合在连续、对齐的8字节内的一种技术。
目前,DragonOS中,通过C、Rust各实现了一个版本的lockref。请注意,二者不兼容。对于新的功能模块,请使用Rust版本的lockref。随着代码重构工作的进行,我们将会删除C版本的lockref。
1. lockref结构
1.1. Rust版本
/// 仅在x86_64架构下使用cmpxchg
#[cfg(target_arch = "x86_64")]
/// 由于需要cmpxchg,所以整个lockref按照8字节对齐
#[repr(align(8))]
#[derive(Debug)]
pub struct LockRef {
pub lock: RawSpinlock,
pub count: i32,
}
/// 除了x86_64以外的架构,不使用cmpxchg进行优化
#[cfg(not(target_arch = "x86_64"))]
pub struct LockRef {
lock: RawSpinlock,
count: i32,
}
1.2. C版本
struct lockref
{
union
{
#ifdef __LOCKREF_ENABLE_CMPXCHG__
aligned_u64 lock_count; // 通过该变量的声明,使得整个lockref的地址按照8字节对齐
#endif
struct
{
spinlock_t lock;
int count;
};
};
};
2. 特性描述
由于在高负载的情况下,系统会频繁的执行“锁定-改变引用变量-解锁”的操作,这期间很可能出现spinlock和引用计数跨缓存行的情况,这将会大大降低性能。lockref通过强制对齐,尽可能的降低缓存行的占用数量,使得性能得到提升。
并且,在x64体系结构下,还通过cmpxchg()指令,实现了无锁快速路径。不需要对自旋锁加锁即可更改引用计数的值,进一步提升性能。当快速路径不存在(对于未支持的体系结构)或者尝试超时后,将会退化成“锁定-改变引用变量-解锁”的操作。此时由于lockref强制对齐,只涉及到1个缓存行,因此性能比原先的spinlock+ref_count的模式要高。
3. 关于cmpxchg_loop
在改变引用计数时,cmpxchg先确保没有别的线程持有锁,然后改变引用计数,同时通过lock cmpxchg
指令验证在更改发生时,没有其他线程持有锁,并且当前的目标lockref的值与old变量中存储的一致,从而将新值存储到目标lockref。这种无锁操作能极大的提升性能。如果不符合上述条件,在多次尝试后,将退化成传统的加锁方式来更改引用计数。
4. Rust版本的API
4.1. 引用计数自增
pub fn inc(&mut self)
pub fn inc_not_zero(&mut self) -> Result<i32, SystemError>
pub fn inc_not_dead(&mut self) -> Result<i32, SystemError>
4.1.1. inc
说明
原子的将引用计数加1。
返回值
无
4.1.2. inc_not_zero
说明
原子地将引用计数加1.如果原来的count≤0,则操作失败。
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.1.3. inc_not_dead
说明
引用计数自增1。(除非该lockref已经被标记为死亡)
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.2. 引用计数自减
pub fn dec(&mut self) -> Result<i32, SystemError>
pub fn dec_return(&mut self) -> Result<i32, SystemError>
pub fn dec_not_zero(&mut self) -> Result<i32, SystemError>
pub fn dec_or_lock_not_zero(&mut self) -> Result<i32, SystemError>
4.2.1. dec
说明
原子地将引用计数-1。如果已处于count≤0的状态,则返回Err(SystemError::EPERM)
本函数与lockref_dec_return()
的区别在于,当在cmpxchg()
中检测到count<=0
或已加锁,本函数会再次尝试通过加锁来执行操作,而lockref_dec_return()
会直接返回错误
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.2.2. dec_return
原子地将引用计数减1。如果处于已加锁或count≤0的状态,则返回SystemError::EPERM
本函数与lockref_dec()
的区别在于,当在cmpxchg()
中检测到count<=0
或已加锁,本函数会直接返回错误,而lockref_dec()
会再次尝试通过加锁来执行操作.
备注
若当前处理器架构不支持cmpxchg,则退化为self.dec()
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.2.3. dec_not_zero
说明
原子地将引用计数减1。若当前的引用计数≤1,则操作失败.
该函数与lockref_dec_or_lock_not_zero()
的区别在于,当cmpxchg()
时发现old.count≤1
时,该函数会直接返回Err(-1)
,而lockref_dec_or_lock_not_zero()
在这种情况下,会尝试加锁来进行操作。
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.2.4. dec_or_lock_not_zero
说明
原子地将引用计数减1。若当前的引用计数≤1,则操作失败.
该函数与lockref_dec_not_zero()
的区别在于,当cmpxchg()时发现old.count≤1
时,该函数会尝试加锁来进行操作,而lockref_dec_not_zero()
在这种情况下,会直接返回Err(SystemError::EPERM)
.
返回值
返回值 |
说明 |
---|---|
Ok(self.count) |
成功,返回新的引用计数 |
Err(SystemError::EPERM) |
失败,返回EPERM |
4.3. 其他
pub fn mark_dead(&mut self)
4.3.1. mark_dead
说明
将引用计数原子地标记为死亡状态.
参考资料
备注
作者:龙进 longjin@RinGoTek.cn
mutex互斥量
mutex是一种轻量级的同步原语,只有被加锁、空闲两种状态。
当mutex被占用时,尝试对mutex进行加锁操作的进程将会被休眠,直到资源可用。
1. 特性
同一时间只有1个任务可以持有mutex
不允许递归地加锁、解锁
只允许通过mutex的api来操作mutex
在硬中断、软中断中不能使用mutex
2. 定义
mutex定义在lib/mutex.rs
中,定义如下所示:
/// @brief Mutex互斥量结构体
/// 请注意!由于Mutex属于休眠锁,因此,如果您的代码可能在中断上下文内执行,请勿采用Mutex!
#[derive(Debug)]
pub struct Mutex<T> {
/// 该Mutex保护的数据
data: UnsafeCell<T>,
/// Mutex内部的信息
inner: SpinLock<MutexInner>,
}
#[derive(Debug)]
struct MutexInner {
/// 当前Mutex是否已经被上锁(上锁时,为true)
is_locked: bool,
/// 等待获得这个锁的进程的链表
wait_list: LinkedList<&'static mut process_control_block>,
}
3. 使用
与SpinLock类似,Rust版本的Mutex具有一个守卫。使用的时候,需要将要被保护的数据的所有权移交Mutex。并且,守卫只能在加锁成功后产生,因此,每个时刻,每个Mutex最多存在1个守卫。
当需要读取、修改Mutex保护的数据时,请先使用Mutex的lock()
方法。该方法会返回一个MutexGuard
。您可以使用被保护的数据的成员函数来进行一些操作。或者是直接读取、写入被保护的数据。(相当于您获得了被保护的数据的可变引用)
完整示例如下方代码所示:
let x :Mutex<Vec<i32>>= Mutex::new(Vec::new());
{
let mut g :MutexGuard<Vec<i32>>= x.lock();
g.push(1);
g.push(2);
g.push(2);
assert!(g.as_slice() == [1, 2, 2] || g.as_slice() == [2, 2, 1]);
// 在此处,Mutex是加锁的状态
kdebug!("x={:?}", x);
}
// 由于上方的变量`g`,也就是Mutex守卫的生命周期结束,自动释放了Mutex。因此,在此处,Mutex是放锁的状态
kdebug!("x={:?}", x);
对于结构体内部的变量,我们可以使用Mutex进行细粒度的加锁,也就是使用Mutex包裹需要细致加锁的成员变量,比如这样:
pub struct a {
pub data: Mutex<data_struct>,
}
当然,我们也可以对整个结构体进行加锁:
struct MyStruct {
pub data: data_struct,
}
/// 被全局加锁的结构体
pub struct LockedMyStruct(Mutex<MyStruct>);
4. API
4.1. new - 初始化Mutex
原型
pub const fn new(value: T) -> Self
说明
new()
方法用于初始化一个Mutex。该方法需要一个被保护的数据作为参数。并且,该方法会返回一个Mutex。
4.2. lock - 加锁
原型
pub fn lock(&self) -> MutexGuard<T>
说明
对Mutex加锁,返回Mutex的守卫,您可以使用这个守卫来操作被保护的数据。
如果Mutex已经被加锁,那么,该方法会阻塞当前进程,直到Mutex被释放。
4.3. try_lock - 尝试加锁
原型
pub fn try_lock(&self) -> Result<MutexGuard<T>, i32>
说明
尝试对Mutex加锁。如果加锁失败,不会将当前进程加入等待队列。如果加锁成功,返回Mutex的守卫;如果当前Mutex已经被加锁,返回Err(错误码)
。
5. C版本的Mutex(在将来会被废弃)
mutex定义在common/mutex.h
中。其数据类型如下所示:
typedef struct
{
atomic_t count; // 锁计数。1->已解锁。 0->已上锁,且有可能存在等待者
spinlock_t wait_lock; // mutex操作锁,用于对mutex的list的操作进行加锁
struct List wait_list; // Mutex的等待队列
} mutex_t;
5.1. API
mutex_init
void mutex_init(mutex_t *lock)
初始化一个mutex对象。
mutex_lock
void mutex_lock(mutex_t *lock)
对一个mutex对象加锁。若mutex当前被其他进程持有,则当前进程进入休眠状态。
mutex_unlock
void mutex_unlock(mutex_t *lock)
对一个mutex对象解锁。若mutex的等待队列中有其他的进程,则唤醒下一个进程。
mutex_trylock
void mutex_trylock(mutex_t *lock)
尝试对一个mutex对象加锁。若mutex当前被其他进程持有,则返回0.否则,加锁成功,返回1.
mutex_is_locked
void mutex_is_locked(mutex_t *lock)
判断mutex是否已被加锁。若给定的mutex已处于上锁状态,则返回1,否则返回0。
RwLock读写锁
1. 简介
读写锁是一种在并发环境下保护多进程间共享数据的机制. 相比于普通的spinlock,读写锁将对 共享数据的访问分为读和写两种类型: 只读取共享数据的访问使用读锁控制,修改共享数据的访问使用 写锁控制. 读写锁设计允许同时存在多个”读者”(只读取共享数据的访问)和一个”写者”(修改共享数据 的访问), 对于一些大部分情况都是读访问的共享数据来说,使用读写锁控制访问可以一定程度上提升性能.
2. DragonOS中读写锁的实现
2.1 读写锁的机理
读写锁的目的是维护多线程系统中的共享变量的一致性. 数据会被包裹在一个RwLock的数据结构中, 一切的访问必须通过RwLock的数据结构进行访问和修改. 每个要访问共享数据的会获得一个守卫(guard), 只读进程获得READER(读者守卫),需要修改共享变量的进程获得WRITER(写者守卫),作为RwLock的”影子”, 线程都根据guard来进行访问和修改操作.
在实践中, 读写锁除了READER, WRITER, 还增加了UPGRADER; 这是一种介于READER和WRITER之间的守卫, 这个守卫的作用就是防止WRITER的饿死(Staration).当进程获得UPGRADER时,进程把它当成READER来使用;但是UPGRADER可以进行升级处理,升级后的UPGRADER相当于是一个WRITER守卫,可以对共享数据执行写操作.
所有守卫都满足rust原生的RAII机理,当守卫所在的作用域结束时,守卫将自动释放.
2.2 读写锁守卫之间的关系
同一时间点, 可以存在多个READER, 即可以同时有多个进程对共享数据进行访问;同一时间只能存在一个WRITER,而且当有一个进程获得WRITER时,不能存在READER和UPGRADER;进程获得UPGRADER的前提条件是,不能有UPGRADER或WRITER存在,但是当有一个进程获得UPGRADER时,进程无法成功申请READER.
2.3 设计的细节
2.3.1 RwLock数据结构
pub struct RwLock<T> {
lock: AtomicU32,//原子变量
data: UnsafeCell<T>,
}
2.3.2 READER守卫的数据结构
pub struct RwLockReadGuard<'a, T: 'a> {
data: *const T,
lock: &'a AtomicU32,
}
2.3.3 UPGRADER守卫的数据结构
pub struct RwLockUpgradableGuard<'a, T: 'a> {
data: *const T,
inner: &'a RwLock<T>,
}
2.3.4 WRITER守卫的数据结构
pub struct RwLockWriteGuard<'a, T: 'a> {
data: *mut T,
inner: &'a RwLock<T>,
}
2.3.5 RwLock的lock的结构介绍
lock是一个32位原子变量AtomicU32, 它的比特位分配如下:
UPGRADER_BIT WRITER_BIT
^ ^
OVERFLOW_BIT +------+ +-------+
^ | |
| | |
+-+--+--------------------------------------------------------+-+--+-+--+
| | | | |
| | | | |
| | The number of the readers | | |
| | | | |
+----+--------------------------------------------------------+----+----+
31 30 2 1 0
(从右到左)第0位表征WRITER是否有效,若WRITER_BIT=1, 则存在一个进程获得了WRITER守卫; 若UPGRADER_BIT=1, 则存在一个进程获得了UPGRADER守卫,第2位到第30位用来二进制表示获得READER守卫的进程数; 第31位是溢出判断位, 若OVERFLOW_BIT=1, 则不再接受新的读者守卫的获得申请.
3. 读写锁的主要API
3.1 RwLock的主要API
///功能: 输入需要保护的数据类型data,返回一个新的RwLock类型.
pub const fn new(data: T) -> Self
///功能: 获得READER守卫
pub fn read(&self) -> RwLockReadGuard<T>
///功能: 尝试获得READER守卫
pub fn try_read(&self) -> Option<RwLockReadGuard<T>>
///功能: 获得WRITER守卫
pub fn write(&self) -> RwLockWriteGuard<T>
///功能: 尝试获得WRITER守卫
pub fn try_write(&self) -> Option<RwLockWriteGuard<T>>
///功能: 获得UPGRADER守卫
pub fn upgradeable_read(&self) -> RwLockUpgradableGuard<T>
///功能: 尝试获得UPGRADER守卫
pub fn try_upgradeable_read(&self) -> Option<RwLockUpgradableGuard<T>>
3.2 WRITER守卫RwLockWriteGuard的主要API
///功能: 将WRITER降级为READER
pub fn downgrade(self) -> RwLockReadGuard<'rwlock, T>
///功能: 将WRITER降级为UPGRADER
pub fn downgrade_to_upgradeable(self) -> RwLockUpgradableGuard<'rwlock, T>
3.3 UPGRADER守卫RwLockUpgradableGuard的主要API
///功能: 将UPGRADER升级为WRITER
pub fn upgrade(mut self) -> RwLockWriteGuard<'rwlock, T>
///功能: 将UPGRADER降级为READER
pub fn downgrade(self) -> RwLockReadGuard<'rwlock, T>
4. 用法实例
static LOCK: RwLock<u32> = RwLock::new(100 as u32);
fn t_read1() {
let guard = LOCK.read();
let value = *guard;
let readers_current = LOCK.reader_count();
let writers_current = LOCK.writer_count();
println!(
"Reader1: the value is {value}
There are totally {writers_current} writers, {readers_current} readers"
);
}
fn t_read2() {
let guard = LOCK.read();
let value = *guard;
let readers_current = LOCK.reader_count();
let writers_current = LOCK.writer_count();
println!(
"Reader2: the value is {value}
There are totally {writers_current} writers, {readers_current} readers"
);
}
fn t_write() {
let mut guard = LOCK.write();
*guard += 100;
let writers_current = LOCK.writer_count();
let readers_current = LOCK.reader_count();
println!(
"Writers: the value is {guard}
There are totally {writers_current} writers, {readers_current} readers",
guard = *guard
);
let read_guard=guard.downgrade();
let value=*read_guard;
println!("After downgraded to read_guard: {value}");
}
fn t_upgrade() {
let guard = LOCK.upgradeable_read();
let value = *guard;
let readers_current = LOCK.reader_count();
let writers_current = LOCK.writer_count();
println!(
"Upgrader1 before upgrade: the value is {value}
There are totally {writers_current} writers, {readers_current} readers"
);
let mut upgraded_guard = guard.upgrade();
*upgraded_guard += 100;
let writers_current = LOCK.writer_count();
let readers_current = LOCK.reader_count();
println!(
"Upgrader1 after upgrade: the value is {temp}
There are totally {writers_current} writers, {readers_current} readers",
temp = *upgraded_guard
);
let downgraded_guard=upgraded_guard.downgrade_to_upgradeable();
let value=*downgraded_guard;
println!("value after downgraded: {value}");
let read_guard=downgraded_guard.downgrade();
let value_=*read_guard;
println!("value after downgraded to read_guard: {value_}");
}
fn main() {
let r2=thread::spawn(t_read2);
let r1 = thread::spawn(t_read1);
let t1 = thread::spawn(t_write);
let g1 = thread::spawn(t_upgrade);
r1.join().expect("r1");
t1.join().expect("t1");
g1.join().expect("g1");
r2.join().expect("r2");
}
进程管理模块
kthread 内核线程
内核线程模块定义在common/kthread.h
中,提供对内核线程的及支持功能。内核线程作为内核的“分身”,能够提升系统的并行化程度以及故障容错能力。
原理
每个内核线程都运行在内核态,执行其特定的任务。
内核线程的创建是通过调用kthread_create()
或者kthread_run()
宏,向kthreadd
守护线程发送创建任务来实现的。也就是说,内核线程的创建,最终是由kthreadd
来完成。
当内核线程被创建后,虽然会加入调度队列,但是当其被第一次调度,执行引导程序kthread()
后,将进入休眠状态。直到其他模块使用process_wakeup()
,它才会真正开始运行。
当内核其他模块想要停止一个内核线程的时候,可以调用kthread_stop()
函数。该函数将会置位内核线程的worker_private
中的KTHREAD_SHOULD_STOP
标志位,并等待内核线程的退出,然后获得返回值并清理内核线程的pcb。
内核线程应当经常检查KTHREAD_SHOULD_STOP
标志位,以确定其是否要退出。当检测到该标志位被置位时,内核线程应当完成数据清理工作,并调用kthread_exit()
或直接返回一个返回码,以退出内核线程。
创建内核线程
kthread_create()
原型
kthread_create(thread_fn, data, name_fmt, arg...)
简介
在当前NUMA结点上创建一个内核线程(DragonOS目前暂不支持NUMA,因此node可忽略。)
请注意,该宏会创建一个内核线程,并将其设置为停止状态.
参数
thread_fn
该内核线程要执行的函数
data
传递给 thread_fn 的参数数据
name_fmt
printf-style format string for the thread name
arg
name_fmt的参数
返回值
创建好的内核线程的pcb
kthread_run()
原型
kthread_run(thread_fn, data, name_fmt, ...)
简介
创建内核线程并加入调度队列。
该宏定义是kthread_create()
的简单封装,提供创建了内核线程后,立即运行的功能。
kthread_run_rt()
原型
kthread_run_rt(thread_fn, data, name_fmt, ...)
简介
创建内核实时线程并加入调度队列。
类似kthread_run()
,该宏定义也是kthread_create()
的简单封装,提供创建了内核实时线程后,在设置实时进程的参数后,立即运行的功能。
停止内核线程
kthread_stop()
原型
int kthread_stop(struct process_control_block * pcb)
简介
当外部模块希望停止一个内核线程时,调用该函数,向kthread发送停止消息,请求其结束。并等待其退出,返回内核线程的退出返回值。
参数
pcb
内核线程的pcb
返回值
内核线程的退出返回码。
kthread_should_stop()
原型
bool kthread_should_stop(void)
简介
内核线程可以调用该函数得知是否有其他进程请求结束当前内核线程。
返回值
一个bool变量
值 |
解释 |
---|---|
true |
有其他进程请求结束该内核线程 |
false |
该内核线程没有收到停止消息 |
kthread_exit()
原型
void kthread_exit(long result)
简介
让当前内核线程退出,并返回result参数给kthread_stop()函数。
参数
result
内核线程的退出返回码
PCB 进程控制块
PCB的全称为process control block, 它是每个进程/线程的核心控制结构。定义于kernel/src/process/proc-types.h
中。
PCB详解
Todo:
与PCB的管理相关的API
根据pid寻找pcb
process_find_pcb_by_pid
该API提供了根据pid寻找pcb的功能,定义在kernel/src/process/process.h
中。
当找到目标的pcb时,返回对应的pcb,否则返回NULL。
参数
pid 进程id
返回值
struct process_control_block 目标pcb
DragonOS调度
这里是DragonOS中,与进程调度相关的说明文档。
与“等待”相关的api(C语言)
警告
随着内核的发展,我们将会逐步将C语言的等待机制替换为Rust语言的等待机制。在这个过程中,我们将会同时保留C语言和Rust语言的等待机制,以便于我们在开发过程中进行对比。 待时机成熟,我们将会逐步将C语言的等待机制移除。
如果几个进程需要等待某个事件发生,才能被运行,那么就需要一种“等待”的机制,以实现进程同步。
一. wait_queue等待队列
wait_queue是一种进程同步机制,中文名为“等待队列”。它可以将当前进程挂起,并在时机成熟时,由另一个进程唤醒他们。
当您需要等待一个事件完成时,使用wait_queue机制能减少进程同步的开销。相比于滥用自旋锁以及信号量,或者是循环使用usleep(1000)这样的函数来完成同步,wait_queue是一个高效的解决方案。
警告
wait_queue.h
中的等待队列的实现并没有把队列头独立出来,同时没有考虑为等待队列加锁。所以在后来的开发中加入了wait_queue_head_t
的队列头实现,实质上就是链表+自旋锁。它与wait_queue.h
中的队列wait_queue_node_t
是兼容的,当你使用struct wait_queue_head
作为队列头时,你同样可以使用等待队列添加节点的函数。
简单用法
等待队列的使用方法主要分为以下几部分:
创建并初始化一个等待队列
使用
wait_queue_sleep_on_
系列的函数,将当前进程挂起。晚挂起的进程将排在队列的尾部。通过
wait_queue_wakeup()
函数,依次唤醒在等待队列上的进程,将其加入调度队列
要使用wait_queue,您需要#include<common/wait_queue.h>
,并创建一个wait_queue_node_t
类型的变量,作为等待队列的头部。这个结构体只包含两个成员变量:
typedef struct
{
struct List wait_list;
struct process_control_block *pcb;
} wait_queue_node_t;
对于等待队列,这里有一个好的命名方法:
wait_queue_node_t wq_keyboard_interrupt_received;
这样的命名方式能增加代码的可读性,更容易让人明白这里到底在等待什么。
初始化等待队列
函数wait_queue_init(wait_queue_node_t *wait_queue, struct process_control_block *pcb)
提供了初始化wait_queue结点的功能。
当您初始化队列头部时,您仅需要将wait_queue首部的结点指针传入,第二个参数请设置为NULL
将结点插入等待队列
您可以使用以下函数,将当前进程挂起,并插入到指定的等待队列。这些函数大体功能相同,只是在一些细节上有所不同。
函数名 |
解释 |
---|---|
wait_queue_sleep_on() |
将当前进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE |
wait_queue_sleep_on_unlock() |
将当前进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的自旋锁 |
wait_queue_sleep_on_interriptible() |
将当前进程挂起,并设置挂起状态为PROC_INTERRUPTIBLE |
从等待队列唤醒一个进程
您可以使用void wait_queue_wakeup(wait_queue_node_t * wait_queue_head, int64_t state);
函数,从指定的等待队列中,唤醒第一个挂起时的状态与指定的state
相同的进程。
当没有符合条件的进程时,将不会唤醒任何进程,仿佛无事发生。
二. wait_queue_head等待队列头
数据结构定义如下:
typedef struct
{
struct List wait_list;
spinlock_t lock; // 队列需要有一个自旋锁,虽然目前内部并没有使用,但是以后可能会用.
} wait_queue_head_t;
等待队列头的使用逻辑与等待队列实际是一样的,因为他同样也是等待队列的节点(仅仅多了一把锁)。且wait_queue_head的函数基本上与wait_queue一致,只不过多了***_with_node_***的字符串。
同时,wait_queue.h文件中提供了很多的宏,可以方便您的工作。
提供的宏
宏 |
解释 |
---|---|
DECLARE_WAIT_ON_STACK(name, pcb) |
在栈上声明一个wait_queue节点,同时把pcb所代表的进程与该节点绑定 |
DECLARE_WAIT_ON_STACK_SELF(name) |
传在栈上声明一个wait_queue节点,同时当前进程(即自身进程)与该节点绑定 |
DECLARE_WAIT_ALLOC(name, pcb) |
使用 |
DECLARE_WAIT_ALLOC_SELF(name) |
使用 |
创建等待队列头
您可以直接调用宏
DECLARE_WAIT_QUEUE_HEAD(m_wait_queue_head); // 在栈上声明一个队列头变量
也可以手动声明
struct wait_queue_head_t m_wait_queue_head = {0};
wait_queue_head_init(&m_wait_queue_head);
将结点插入等待队列
函数名 |
解释 |
---|---|
wait_queue_sleep_with_node(wait_queue_head_t *head, wait_queue_node_t *wait_node) |
传入一个等待队列节点,并设置该节点的挂起状态为PROC_UNINTERRUPTIBLE |
wait_queue_sleep_with_node_unlock(wait_queue_head_t *q, wait_queue_node_t *wait, void *lock) |
传入一个等待队列节点,将该节点的pcb指向的进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的自旋锁 |
wait_queue_sleep_with_node_interriptible(wait_queue_head_t *q, wait_queue_node_t *wait) |
传入一个等待队列节点,将该节点的pcb指向的进程挂起,并设置挂起状态为PROC_INTERRUPTIBLE |
从等待队列唤醒一个进程
在wait_queue.h
中的wait_queue_wakeup
函数直接kfree掉了wait_node节点。对于在栈上的wait_node,您可以选择wait_queue_wakeup_on_stack(wait_queue_head_t *q, int64_t state)
来唤醒队列里面的队列头节点。
三. completion完成量
简单用法
完成量的使用方法主要分为以下几部分:
声明一个完成量(可以在栈中/使用kmalloc/使用数组)
使用wait_for_completion等待事件完成
使用complete唤醒等待的进程
等待操作
void wait_fun() {
DECLARE_COMPLETION_ON_STACK(comp); // 声明一个completion
// .... do somethind here
// 大部分情况是你使用kthread_run()创建了另一个线程
// 你需要把comp变量传给这个线程, 然后当前线程就会等待他的完成
if (!try_wait_for_completion(&comp)) // 进入等待
wait_for_completion(&comp);
}
完成操作
void kthread_fun(struct completion *comp) {
// ...... 做一些事 .......
// 这里你确定你完成了目标事件
complete(&comp);
// 或者你使用complete_all
complete_all(&comp);
}
更多用法
kernel/sched/completion.c文件夹中,你可以看到 __test 开头的几个函数,他们是completion模块的测试代码,基本覆盖了completion的大部分函数.你可以在这里查询函数使用方法.
初始化完成量
函数completion_init(struct completion *x)
提供了初始化completion的功能。当你使用DECLARE_COMPLETION_ON_STACK
来创建(在栈上创建)的时候,会自动初始化.
关于完成量的wait系列函数
函数名 |
解释 |
---|---|
wait_for_completion(struct completion *x) |
将当前进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE。 |
wait_for_completion_timeout(struct completion *x, long timeout) |
将当前进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE。当等待timeout时间(jiffies时间片)之后,自动唤醒进程。 |
wait_for_completion_interruptible(struct completion *x) |
将当前进程挂起,并设置挂起状态为PROC_INTERRUPTIBLE。 |
wait_for_completion_interruptible_timeout(struct completion *x, long timeout) |
将当前进程挂起,并设置挂起状态为PROC_INTERRUPTIBLE。当等待timeout时间(jiffies时间片)之后,自动唤醒进程。 |
wait_for_multicompletion(struct completion x[], int n) |
将当前进程挂起,并设置挂起状态为PROC_UNINTERRUPTIBLE。(等待数组里面的completion的完成) |
关于完成量的complete系列函数
函数名 |
解释 |
---|---|
complete(struct completion *x) |
表明一个事件被完成,从等待队列中唤醒一个进程 |
complete_all(struct completion *x) |
表明与该completion有关的事件被标记为永久完成,并唤醒等待队列中的所有进程 |
其他用于查询信息的函数
函数名 |
解释 |
---|---|
completion_done(struct completion *x) |
查询completion的done变量是不是大于0,如果大于0,返回true;否则返回false。在等待前加上这个函数有可能加速?(暂未经过实验测试,有待证明) |
try_wait_for_completion(struct completion *x) |
查询completion的done变量是不是大于0,如果大于0,返回true(同时令done-=1);否则返回false。在等待前加上这个函数有可能加速?(该函数和 |
与“等待”相关的api(rust语言)
如果几个进程需要等待某个事件发生,才能被运行,那么就需要一种“等待”的机制,以实现进程同步。
1. WaitQueue等待队列
WaitQueue是一种进程同步机制,中文名为“等待队列”。它可以将当前进程挂起,并在时机成熟时,由另一个进程唤醒他们。
当您需要等待一个事件完成时,使用 WaitQueue机制能减少进程同步的开销。相比于滥用自旋锁以及信号量,或者是循环使用usleep(1000)这样的函数来完成同步, WaitQueue是一个高效的解决方案。
1.1 WaitQueue的使用
WaitQueue的使用非常简单,只需要三步:
初始化一个WaitQueue对象。
调用这个WaitQueue的挂起相关的API,将当前进程挂起。
当事件发生时,由另一个进程,调用这个WaitQueue的唤醒相关的API,唤醒一个进程。
下面是一个简单的例子:
1.1.1 初始化一个WaitQueue对象
WaitQueue对象的初始化非常简单,只需要调用WaitQueue::INIT即可。
let mut wq = WaitQueue::INIT;
1.1.2 挂起进程
您可以这样挂起当前进程:
wq.sleep();
当前进程会被挂起,直到有另一个进程调用了wq.wakeup()
。
1.1.3 唤醒进程
您可以这样唤醒一个进程:
// 唤醒等待队列头部的进程(如果它的state & PROC_INTERRUPTIBLE 不为0)
wq.wakeup(PROC_INTERRUPTIBLE);
// 唤醒等待队列头部的进程(如果它的state & PROC_UNINTERRUPTIBLE 不为0)
wq.wakeup(PROC_UNINTERRUPTIBLE);
// 唤醒等待队列头部的进程(无论它的state是什么)
wq.wakeup((-1) as u64);
1.2 API
1.2.1 挂起进程
您可以使用以下函数,将当前进程挂起,并插入到指定的等待队列。这些函数大体功能相同,只是在一些细节上有所不同。
函数名 |
解释 |
---|---|
sleep() |
将当前进程挂起,并设置进程状态为PROC_INTERRUPTIBLE |
sleep_uninterruptible() |
将当前进程挂起,并设置进程状态为PROC_UNINTERRUPTIBLE |
sleep_unlock_spinlock() |
将当前进程挂起,并设置进程状态为PROC_INTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的自旋锁 |
sleep_unlock_mutex() |
将当前进程挂起,并设置进程状态为PROC_INTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的Mutex |
sleep_uninterruptible_unlock_spinlock() |
将当前进程挂起,并设置进程状态为PROC_UNINTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的自旋锁 |
sleep_uninterruptible_unlock_mutex() |
将当前进程挂起,并设置进程状态为PROC_UNINTERRUPTIBLE。待当前进程被插入等待队列后,解锁给定的Mutex |
1.2.2 唤醒进程
您可以使用wakeup(state)
函数,唤醒等待队列中的第一个进程。如果这个进程的state与给定的state进行and操作之后,结果不为0,则唤醒它。
返回值:如果有进程被唤醒,则返回true,否则返回false。
1.2.3 其它API
函数名 |
解释 |
---|---|
len() |
返回等待队列中的进程数量 |
进程调度器相关的api
定义了DragonOS的进程调度相关的api,是系统进行进程调度的接口。同时也抽象出了Scheduler的trait,以供具体的调度器实现
1. 调度器介绍
一般来说,一个系统会同时处理多个请求,但是其资源是优先的,调度就是用来协调每个请求对资源的使用的方法。
1.1 主要函数
cpu_executing(): 获取指定的cpu上正在执行的进程的pcb
sched_enqueue(): 将进程加入调度队列
sched_init(): 初始化进程调度器模块
sched_update_jiffies(): 当时钟中断到达时,更新时间片。请注意,该函数只能被时钟中断处理程序调用
sys_sched(): 让系统立即运行调度器的系统调用。请注意,该系统调用不能由ring3的程序发起
完全公平调度器相关的api
CFS(Completely Fair Scheduler),顾名思义,完全公平调度器。CFS作为主线调度器之一,也是最典型的O(1)调度器之一
1. CFSQueue 介绍
CFSQueue是用来存放普通进程的调度队列,每个CPU维护一个RTQueue,主要使用Vec作为主要存储结构来实现。
1.1 主要函数
enqueue(): 将pcb入队列
dequeue(): 将pcb从调度队列中弹出,若队列为空,则返回IDLE进程的pcb
sort(): 将进程按照虚拟运行时间的升序进行排列
2. SchedulerCFS 介绍
CFS调度器类,主要实现了CFS调度器类的初始化以及调度功能函数。
2.1 主要函数
sched(): 是对于Scheduler trait的sched()实现,是实时进程进行调度时的逻辑处理,该函数会返回接下来要执行的pcb,若没有符合要求的pcb,返回None
enqueue(): 同样是对于Scheduler trait的sched()实现,将一个pcb加入调度器的调度队列
update_cpu_exec_proc_jiffies(): 更新这个cpu上,这个进程的可执行时间。
timer_update_jiffies(): 时钟中断到来时,由sched的core模块中的函数,调用本函数,更新CFS进程的可执行时间
实时进程调度器相关的api
RT(realtime scheduler),实时调度器。实时调度是为了完成实时处理任务而分配CPU的调度方法。
DragonOS的进程分为“实时进程”和“普通进程”两类;实时进程的优先级高于普通进程,如果当前的系统的执行队列中有“实时进程”,RT调度器会优先选择实时进程;如果队列中会有多个实时进程,调度器会选择优先级最高的实时进程来执行;
1. RTQueue 介绍
RTQueue是用来存放state为running的实时进程的调度队列,每个CPU维护一个RTQueue,主要使用Vec作为主要存储结构来实现。
1.1 主要函数
enqueue(): 将pcb入队列
dequeue(): 将pcb出队列
2. SchedulerRT 介绍
RT调度器类,主要实现了RT调度器类的初始化以及调度功能函数。
2.1 主要函数
pick_next_task_rt(): 获取当前CPU中的第一个需要执行的RT pcb
sched(): 是对于Scheduler trait的sched()实现,是实时进程进行调度时的逻辑处理,该函数会返回接下来要执行的pcb,若没有符合要求的pcb,返回None
enqueue(): 同样是对于Scheduler trait的sched()实现,将一个pcb加入调度器的调度队列
2.2 内核调度策略
目前在DragonOS中,主要的调度策略有SCHED_NORMAL 策略 | SCHED_FIFO 策略 | SCHED_RT 策略,具体的调度策略为:
SCHED_NORMAL 策略: SCHED_NORMAL 是“绝对公平调度策略”,该策略的进程使用CFS进行调度。
SCHED_FIFO 策略: SCHED_FIFO是“实时进程调度策略”,这是一种先进先出的调度策略,该策略不涉及到CPU时间片机制,在没有更高优先级进程的前提下,只能等待其他进程主动释放CPU资源; 在SCHED_FIFO策略中,被调度器调度运行的进程,其运行时长不受限制,可以运行任意长的时间。
SCHED_RR 策略: SCHED_RR是“实时进程调度策略”,使用的是时间片轮转机制,对应进程的time_slice会在运行时减少,进程使用完CPU时间片后,会加入该CPU的与该进程优先级相同的执行队列中。 同时,释放CPU资源,CPU的使用权会被分配给下一个执行的进程
3. Q&A
几种常用的方法
如何创建实时进程
struct process_control_block *pcb_name = kthread_run_rt(&fn_name, NULL, "test create rt pcb");
其中kthread_run_rt,是创建内核实时线程的宏
pcb中涉及到实时进程的字段含义
policy:实时进程的策略,目前有:SCHED_FIFO与SCHED_RR
priority: 实时进程的优先级,范围为0-99,数字越大,表示优先级越高
rt_time_slice: 实时进程的时间片,默认为100,随着CPU运行而减少,在rt_time_slice为0时,将时间片赋初值并将该进程加入执行队列。
如何实时进程存储队列
目前是使用Vec来保存,因为具体实现的逻辑原因,目前的入队列和出队列都是对队尾的操作,因此会有如下现象:系统中有多个优先级相同的实时进程等待运行时,会出现饥饿现象,也即上一个因为时间片耗尽的进程会在下一个执行,造成同优先级等待的进程饥饿。
todo
将存储实时进程的队列使用双向链表存储(或者其他办法解决上述的饥饿问题)
目前的实时调度是针对单CPU的,需要实现多CPU的实时调度
实现RT进程和普通进程之间的分配带宽的比例
多个CPU之间实现负载均衡
进程间通信
这里是DragonOS进程间通信(IPC)的说明文档。
Signal信号
信号是一种进程间通信机制,当我们发送一个信号给特定的进程,就能触发它的特定行为(例如退出程序,或者运行一些信号处理程序)。信号是发送到进程或同一进程内的特定线程的异步通知,用于通知它有事件发生。信号的常见用途是中断、挂起、终止或终止进程。发送信号时,操作系统会中断目标进程的正常执行流程以传递信号。可以在任何非原子指令期间中断执行。如果该进程之前注册了一个信号处理程序,则执行该例程。否则,将执行默认信号处理程序。
信号类似于中断,区别在于中断由CPU调解并由内核处理,而信号在内核中产生(也可通过系统调用让内核产生)并由各个进程或内核的默认处理函数处理。
1.信号处理概要
1.1 信号发送
当进程A想发送信号给进程B的时候,使用kill(pid, signal)
接口进行发送。然后陷入内核的sys_kill()
函数中进行处理。然后内核将会把信号加入目标进程的pcb的sigpending
中。
示意图如下:
┌────────────┐
│ Process A: │
│ │
│ sys_kill │
└──────┬─────┘
│
│
┌──────▼──────┐ ┌────────────────────┐
│ Send Signal ├────►Add to sigpending of│
└─────────────┘ │ process B. │
└────────────────────┘
1.2 信号处理
进程会在退出内核态的时候,跳转到do_signal()
函数内,检查当前是否有需要被处理的信号,如果有的话,就会开启信号处理流程。
信号处理流程示意图:
┌───────────────────────┐
│ Process B: │
│ ◄─────────────────────────────────┐
│ Return from syscall...│ │
└─────────┬─────────────┘ │
│ │
│ │
│ ┌────────────────┐ │
┌─────▼─────┐ default │ │ │
│ do_signal ├────────► │ stop process B.│ │
└─────┬─────┘ action │ │ │
│ └────────────────┘ │
│ custom action │
┌──────▼───────┐ │
│ setup signal │ │
│ frame │ │
└──────┬───────┘ │
│jump to │
┌──────▼───────┐ ┌────────────┐ sys_sigreturn ┌────────┴────────┐
│ userland ├─►sa_restorer ├──────────────►│Restore the stack│
│ sig handler │ └────────────┘ │ frame. │
└──────────────┘ └─────────────────┘
如果内核检查发现,进程没有指定信号处理函数,且信号处理动作不是“忽略”,那就会杀掉进程。
如果内核发现该信号没有被忽略,那么将会:
备份当前内核栈
设置信号处理的用户态栈帧
回到用户态,执行信号处理函数
信号处理函数结束之后,将会进入由libc提供的__sa_restorer中,发起
sys_sigreturn()
系统调用,回到内核态内核恢复处理信号之前的内核栈。
信号处理流程结束,内核继续执行“返回用户态”的过程。
如果内核发现当前信号被忽略,那么就检查下一个信号。
发现没有任何需要处理的信号时,返回用户态。
2. 其他问题
暂无。
内存管理文档
这里讲解了内存管理模块的一些设计及实现原理。
如果你正在寻找使用内存管理模块的方法,请转到:内存管理API文档
MMIO
MMIO是“内存映射IO”的缩写,它被广泛应用于与硬件设备的交互之中。
地址空间管理
DragonOS中实现了MMIO地址空间的管理机制,本节将介绍它们。
为什么需要MMIO地址空间自动分配?
由于计算机上的很多设备都需要MMIO的地址空间,而每台计算机上所连接的各种设备的对MMIO地址空间的需求是不一样的。如果我们为每个类型的设备都手动指定一个MMIO地址,会使得虚拟地址空间被大大浪费,也会增加系统的复杂性。并且,我们在将来还需要为不同的虚拟内存区域做异常处理函数。因此,我们需要一套能够自动分配MMIO地址空间的机制。
这套机制提供了什么功能?
为驱动程序分配4K到1GB的MMIO虚拟地址空间
对于这些虚拟地址空间,添加到VMA中进行统一管理
可以批量释放这些地址空间
这套机制是如何实现的?
这套机制本质上是使用了伙伴系统来对MMIO虚拟地址空间进行维护。在mm/mm.h
中指定了MMIO的虚拟地址空间范围,这个范围是0xffffa10000000000
开始的1TB的空间。也就是说,这个伙伴系统为MMIO维护了这1TB的虚拟地址空间。
地址空间分配过程
初始化MMIO-mapping模块,在mmio的伙伴系统中创建512个1GB的
__mmio_buddy_addr_region
驱动程序使用
mmio_create
请求分配地址空间。mmio_create
对申请的地址空间大小按照2的n次幂进行对齐,然后从buddy中申请内存地址空间创建VMA,并将VMA标记为
VM_IO|VM_DONTCOPY
。MMIO的vma只绑定在initial_mm
下,且不会被拷贝。分配完成
一旦MMIO地址空间分配完成,它就像普通的vma一样,可以使用mmap系列函数进行操作。
MMIO的映射过程
在得到了虚拟地址空间之后,当我们尝试往这块地址空间内映射内存时,我们可以调用mm_map
函数,对这块区域进行映射。
该函数会对MMIO的VMA的映射做出特殊处理。即:创建Page
结构体以及对应的anon_vma
. 然后会将对应的物理地址,填写到页表之中。
MMIO虚拟地址空间的释放
当设备被卸载时,驱动程序可以调用mmio_release
函数对指定的mmio地址空间进行释放。
释放的过程中,mmio_release
将执行以下流程:
取消mmio区域在页表中的映射。
将释放MMIO区域的VMA
将地址空间归还给mmio的伙伴系统。
MMIO的伙伴算法
伙伴的定义
同时满足以下三个条件的两个内存块被称为伙伴内存块:
两个内存块的大小相同
两个内存块的内存地址连续
两个内存块由同一个大块内存分裂得到
伙伴算法
伙伴(buddy)算法的作用是维护以及组织大块连续内存块的分配和回收,以减少系统时运行产生的外部碎片。伙伴系统中的每个内存块的大小均为\(2^n\)。 在DragonOS中,伙伴系统内存池共维护了1TB的连续存储空间,最大的内存块大小为\(1G\),即\(2^{30}B\),最小的内存块大小为\(4K\),即 \(2^{12}B\)。
伙伴算法的核心思想是当应用申请内存时,每次都分配比申请的内存大小更大的最小内存块,同时分配出去的内存块大小为\(2^nB\)。(e.g. 假设某应用申请了\(3B\)内存,显然并没有整数值n,使\(2^n = 3\) ,且\(3 \in [2^1,2^2]\),所以系统会去取一块大小为\(2^2B\)的内存块,将它分配给申请的应用,本次申请内存操作顺利完成。)
那么当伙伴系统中没有如此“合适”的内存块时该怎么办呢?系统先会去寻找更大的内存块,如果找到了,则会将大内存块分裂成合适的内存块分配给应用。(e.g. 假设申请\(3B\)内存,此时系统中比\(3B\)大的最小内存块的大小为\(16B\),那么\(16B\)会被分裂成两块\(8B\)的内存块,一块放入内存池中,一块继续分裂成两块\(4B\)的内存块。两块\(4B\)的内存块,一块放入内存池中,一块分配给应用。至此,本次申请内存操作顺利完成。)
如果系统没有找到更大的内存块,系统将会尝试合并较小的内存块,直到符合申请空间的大小。(e.g. 假设申请\(3B\)内存,系统检查内存池发现只有两个\(2B\)的内存块,那么系统将会把这两个\(2B\)的内存块合并成一块\(4B\)的内存块,并分配给应用。至此,本次申请内存操作顺利完成。)
最后,当系统既没有找到大块内存,又无法成功合并小块内存时,就会通知应用内存不够,无法分配内存。
伙伴算法的数据结构
MmioBuddyMemPool
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ pool_start_addr │
│ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ pool_size │
│ │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ │
│ free_regions │
│ │
│ ┌────────────┐ │
│ │ │ ┌───────┐ ┌────────┐ │
│ │ ┌────────┬─┼────►│ ├────►│ │ │
│ │ │ list │ │ │ vaddr │ │ vaddr │ │
│ │ │ │◄├─────┤ │◄────┤ │ │
│ MmioFreeRegionList├────────┤ │ └───────┘ └────────┘ │
│ │ │num_free│ │ │
│ │ └────────┘ │ MmioBuddyAddrRegion │
│ MMIO_BUDDY_MIN_EXP - 12 │ 0 │ │
│ ├────────────┤ │
│ │ 1 │ │
│ ├────────────┤ │
│ │ 2 │ │
│ ├────────────┤ │
│ │ 3 │ │
│ ├────────────┤ │
│ │ ... │ │
│ ├────────────┤ │
│ │ ... │ │
│ ├────────────┤ │
│ MMIO_BUDDY_MAX_EXP - 12 │ 18 │ │
│ └────────────┘ │
│ │
│ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────┘
/// 最大的内存块为1G,其幂为30
const MMIO_BUDDY_MAX_EXP: u32 = PAGE_1G_SHIFT;
/// 最小的内存块为4K,其幂为12
const MMIO_BUDDY_MIN_EXP: u32 = PAGE_4K_SHIFT;
/// 内存池数组的大小为18
const MMIO_BUDDY_REGION_COUNT: u32 = MMIO_BUDDY_MAX_EXP - MMIO_BUDDY_MIN_EXP + 1;
/// buddy内存池
pub struct MmioBuddyMemPool {
/// 内存池的起始地址
pool_start_addr: u64,
/// 内存池大小:初始化为1TB
pool_size: u64,
/// 空闲内存块链表数组
/// MMIO_BUDDY_REGION_COUNT = MMIO_BUDDY_MAX_EXP - MMIO_BUDDY_MIN_EXP + 1
free_regions: [SpinLock<MmioFreeRegionList>; MMIO_BUDDY_REGION_COUNT as usize],
}
/// 空闲内存块链表结构体
pub struct MmioFreeRegionList {
/// 存储了空闲内存块信息的结构体的链表
list: LinkedList<Box<MmioBuddyAddrRegion>>,
/// 当前链表空闲块的数量
num_free: i64,
}
/// mmio伙伴系统内部的地址区域结构体
pub struct MmioBuddyAddrRegion {
/// 内存块的起始地址
vaddr: u64,
}
设计思路
DragonOS中,使用MmioBuddyMemPool
结构体作为buddy(为表述方便,以下将伙伴算法简称为buddy)内存池的数据结构,其记录了内存池的起始地址(pool_start_addr)以及内存池中内存块的总大小(pool_size),同时其维护了大小为MMIO_BUDDY_REGION_COUNT
的双向链表数组(free_regions)。free_regions
中的各个链表维护了若干空闲内存块(MmioBuddyAddrRegion)。
free_regions
的下标(index)与内存块的大小有关。由于每个内存块大小都为\(2^{n}\) bytes,那么可以令\(exp = n\)。index与exp的换算公式如下:\(index = exp - 12\)。e.g. 一个大小为\(2^{12}\) bytes的内存块,其\(exp = 12\),使用上述公式计算得\(index = 12 -12 = 0\),所以该内存块会被存入free_regions[0].list
中。通过上述换算公式,每次取出或释放\(2^n\)大小的内存块,只需要操作free_regions[n -12]
即可。DragonOS中,buddy内存池最大的内存块大小为\(1G = 2^{30}bytes\),最小的内存块大小为 \(4K = 2^{12} bytes\),所以\(index\in[0,18]\)。
作为内存分配机制,buddy服务于所有进程,为了解决在各个进程之间实现free_regions中的链表数据同步的问题,free_regions
中的链表类型采用加了 自旋锁(SpinLock)的空闲内存块链表(MmioFreeRegionList),MmioFreeRegionList
中封装有真正的存储了空闲内存块信息的结构体的链表(list)和对应链表长度(num_free)。有了自选锁后,同一时刻只允许一个进程修改某个链表,如取出链表元素(申请内存)或者向链表中插入元素(释放内存)。
MmioFreeRegionList
中的元素类型为MmioBuddyAddrRegion
结构体,MmioBuddyAddrRegion
记录了内存块的起始地址(vaddr)。
伙伴算法内部api
P.S 以下函数均为MmioBuddyMemPool的成员函数。系统中已经创建了一个MmioBuddyMemPool类型的全局引用MMIO_POOL
,如要使用以下函数,请以MMIO_POOL.xxx()
形式使用,以此形式使用则不需要传入self。
函数名 |
描述 |
---|---|
__create_region(&self, vaddr) |
将虚拟地址传入,创建新的内存块地址结构体 |
__give_back_block(&self, vaddr, exp) |
将地址为vaddr,幂为exp的内存块归还给buddy |
__buddy_split(&self,region,exp,list_guard) |
将给定大小为\(2^{exp}\)的内存块一分为二,并插入内存块大小为\(2^{exp-1}\)的链表中 |
__query_addr_region(&self,exp,list_guard) |
从buddy中申请一块大小为\(2^{exp}\)的内存块 |
mmio_buddy_query_addr_region(&self,exp) |
对query_addr_region进行封装,请使用这个函数,而不是__query_addr_region |
__buddy_add_region_obj(&self,region,list_guard) |
往指定的地址空间链表中添加一个内存块 |
__buddy_block_vaddr(&self, vaddr, exp) |
根据地址和内存块大小,计算伙伴块虚拟内存的地址 |
__pop_buddy_block( &self, vaddr,exp,list_guard) |
寻找并弹出指定内存块的伙伴块 |
__buddy_pop_region( &self, list_guard) |
从指定空闲链表中取出内存区域 |
__buddy_merge(&self,exp,list_guard,high_list_guard) |
合并所有\(2^{exp}\)大小的内存块 |
__buddy_merge_blocks(&self,region_1,region_2,exp,high_list_guard) |
合并两个已经从链表中取出的内存块 |
伙伴算法对外api
函数名 |
描述 |
---|---|
__mmio_buddy_init() |
初始化buddy系统,在mmio_init()中调用,请勿随意调用 |
__exp2index(exp) |
将\(2^{exp}\)的exp转换成内存池中的数组的下标(index) |
mmio_create(size,vm_flags,res_vaddr,res_length) |
创建一块根据size对齐后的大小的mmio区域,并将其vma绑定到initial_mm |
mmio_release(vaddr, length) |
取消地址为vaddr,大小为length的mmio的映射并将其归还到buddy中 |
文件系统
DragonOS的文件系统模块由VFS(虚拟文件系统)及具体的文件系统组成。
todo: 由于文件系统模块重构,文档暂时不可用,预计在2023年4月10日前补齐。
概述
在本文中,我们将介绍DragonOS文件系统的架构设计。
总览
如下图所示,DragonOS的文件系统相关的机制主要包括以下几个部分:
系统调用接口
虚拟文件系统
文件抽象(File)
挂载文件系统(MountFS)
具体的文件系统
┌─────────────────────────────────────────────────┐
│ │
Syscall: │ sys_open, sys_read, sys_write, sys_close, │
│ │
│ sys_lseek, etc.. │
│ │
└───────────────────────┬─────────────────────────┘
│
│
VFS: ┌──────▼─────┐
│ │
│ File │
│ │
└──────┬─────┘
│
┌────────▼────────┐
│ │
│ MountFS │
│ │
└────┬────────────┘
│
Filesystems: ┌─────────────┼─────────────┬────────────┐
│ │ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼────┐ ┌─────▼─────┐
│ │ │ │ │ │ │ │
│ FAT │ │ DevFS │ │ ProcFS │ │ RamFS │
│ │ │ │ │ │ │ │
└───────────┘ └───────────┘ └──────────┘ └───────────┘
系统调用接口
DragonOS的文件系统相关的系统调用接口主要包括以下几个:
sys_open
:打开文件sys_read
:读取文件sys_write
:写入文件sys_close
:关闭文件sys_lseek
:定位文件指针sys_mkdir
:创建目录sys_unlink_at
:删除文件或目录(通过参数flag
区分到底是删除文件还是目录)sys_ioctl
:控制设备 (未实现)sys_fstat
:获取文件状态(未实现)sys_fsync
:同步文件(未实现)sys_ftruncate
:截断文件(未实现)sys_fchmod
:修改文件权限(未实现)其他系统调用接口(未实现)
关于接口的具体含义,可以参考 DragonOS系统调用接口。
虚拟文件系统(VFS)
VFS是DragonOS文件系统的核心,它提供了一套统一的文件系统接口,使得DragonOS可以支持多种不同的文件系统。VFS的主要功能包括:
提供统一的文件系统接口
提供文件系统的挂载和卸载机制(MountFS)
提供文件抽象(File)
提供文件系统的抽象(FileSystem)
提供IndexNode抽象
提供文件系统的缓存、同步机制(尚未实现)
关于VFS的详细介绍,请见DragonOS虚拟文件系统。
具体的文件系统
DragonOS目前支持的文件系统包括:
FAT文件系统(FAT12、FAT16、FAT32)
DevFS
ProcFS
RamFS
VFS虚拟文件系统
在DragonOS中,VFS作为适配器,遮住了具体文件系统之间的差异,对外提供统一的文件操作接口抽象。
VFS是DragonOS文件系统的核心,它提供了一套统一的文件系统接口,使得DragonOS可以支持多种不同的文件系统。VFS的主要功能包括:
提供统一的文件系统接口
提供文件系统的挂载和卸载机制(MountFS)
提供文件抽象(File)
提供文件系统的抽象(FileSystem)
提供IndexNode抽象
提供文件系统的缓存、同步机制(尚未实现)
设计
VFS的架构设计如下图所示:
┌─────────┐
│ │
│ read │
File │ │
│ write │
│ │ │
│ │ ioctl │
│ │ │
│ │ lseek │
│ │ │
│ │ etc.. │
│ └─────────┘
│
▼ ┌──────────────────────────────────────────────────────────────────────────────┐
MountFS │ Maintain the mount tree and handle the mounting of file systems. │
│ │ In particular, it handles the "crossing file system boundaries" condition │
│ │ while doing "lookup" or "find" operations. │
│ └──────────────────────────────────────────────────────────────────────────────┘
│
│
│
Filesystems: │
│
▼ ┌────────────────────────────────────────────────────────────────────┐
xxxFSInode │ Implement corresponding operations based on different file systems │
└────────────────────────────────────────────────────────────────────┘
1. File
File结构体是VFS中最基本的抽象,它代表了一个打开的文件。每当进程打开了一个文件,就会创建一个File结构体,用于维护该文件的状态信息。
2. Traits
对于每个具体文件系统,都需要实现以下的trait:
FileSystem:表明某个struct是一个文件系统
IndexNode: 表明某个struct是一个索引节点
一般情况下,FileSystem和IndexNode是一对一的关系,也就是,一个文件系统对应一种IndexNode。但是,对于某些特殊的文件系统,比如DevFS,根据不同的设备类型,会有不同的IndexNode,因此,FileSystem和IndexNode是一对多的关系。
3. MountFS
挂载文件系统虽然实现了FileSystem和IndexNode这两个trait,但它并不是一个“文件系统”,而是一种机制,用于将不同的文件系统挂载到同一个文件系统树上. 所有的文件系统要挂载到文件系统树上,都需要通过MountFS来完成。也就是说,挂载树上的每个文件系统结构体的外面,都套了一层MountFS结构体。
对于大部分的操作,MountFS都是直接转发给具体的文件系统,而不做任何处理。同时,为了支持跨文件系统的操作,比如在目录树上查找,每次lookup操作或者是find操作,都会通过MountFSInode的对应方法,判断当前inode是否为挂载点,并对挂载点进行特殊处理。如果发现操作跨越了具体文件系统的边界,MountFS就会将操作转发给下一个文件系统,并执行Inode替换。这个功能的实现,也是通过在普通的Inode结构体外面,套一层MountFSInode结构体来实现的。
VFS API文档
内核调试模块
这里是DragonOS的内核调试模块文档。
内核栈traceback
简介
内核栈traceback的功能位于kernel/debug/traceback/
文件夹中。为内核态提供traceback的功能,打印调用栈到屏幕上。
API
void traceback(struct pt_regs * regs)
作用
该接口定义于kernel/debug/traceback/traceback.h
中,将会对给定内核栈进行traceback,并打印跟踪结果到屏幕上。
参数
regs
要开始追踪的第一层内核栈栈帧(也就是栈的底端)
实现原理
当内核第一次链接之后,将会通过Makefile中的命令,运行kernel/debug/kallsyms
程序,提取内核文件的符号表,然后生成kernel/debug/kallsyms.S
。该文件的rodata段中存储了text段的函数的符号表。接着,该文件将被编译为kallsyms.o
。最后,Makefile中再次调用ld
命令进行链接,将kallsyms.o链接至内核文件。
当调用traceback
函数时,其将遍历该符号表,找到对应的符号并输出。
未来发展方向
增加写入到日志文件的功能
内核测试
本章节将介绍如何测试内核,包括手动测试以及自动测试。
我们需要尽可能的对内核进行完善的测试,以便我们能够更好的保证内核的稳定性,且减少其他模块的debug难度。
设置完善的测试用例能帮助我们尽可能的检测到问题,防止我们在写新的模块的时候,被已有的模块的一些藏得很深的bug“背刺一刀”。
由于您难以借助GDB等工具进行调试,因此在内核中进行手动测试比应用程序测试要困难一些。
对于一些模块,我们可以使用编写代码进行单元测试,并输出异常信息。遗憾的是,并非所有模块都可以进行单元测试。比如我们常见的内存管理、进程管理等模块都不能进行单元测试。
内核测试框架
DragonOS提供了一个测试框架,旨在对内核的一些模块进行自动化测试。内核测试框架位于ktest/
下。
我们可以使用这个测试框架,按照规范编写测试代码,然后在合适的地方使用ktest_start()
创建一个全新的内核线程并发起测试。
使用方法
创建自动测试程序
假如您要对kfifo模块进行自动测试,您可以在ktest/
下,创建一个名为test-kfifo.c
的测试文件,并编写Makefile。
在test-kfifo.c
中,包含ktest_utils.h
和ktest.h
这两个头文件。
您需要像下面这样,在test-kfifo.c
中,创建一个测试用例函数表,并把测试用例函数填写到其中:
static ktest_case_table kt_kfifo_func_table[] = {
ktest_kfifo_case0_1,
};
然后创建一个函数,作为kfifo测试的主函数。请注意,您需要将它的声明添加到ktest.h
中。
uint64_t ktest_test_kfifo(uint64_t arg)
{
kTEST("Testing kfifo...");
for (int i = 0; i < sizeof(kt_kfifo_func_table) / sizeof(ktest_case_table); ++i)
{
kTEST("Testing case %d", i);
kt_kfifo_func_table[i](i, 0);
}
kTEST("kfifo Test done.");
return 0;
}
编写测试用例
您可以创建一个或多个测试用例,命名为:ktest_kfifo_case_xxxxx
. 在这个例子中,我创建了一个测试用例,命名为:ktest_kfifo_case0_1
.如下所示:
static long ktest_kfifo_case0_1(uint64_t arg0, uint64_t arg1)
这里最多允许我们传递两个参数到测试函数里面。
那么,我们该如何编写测试用例呢?
我们主要是需要设置一些情节,以便能测试到目标组件的每个情况。为了检验模块的行为是否符合预期,我们需要使用assert(condition)
宏函数,对目标condition
进行校验。若condition
为1,则表明测试通过。否则,将会输出一行assert failed信息到屏幕上。
发起测试
我们可以在pid≥1的内核线程中发起测试。由于DragonOS目前尚不完善,您可以在process/process.c
中的initial_kernel_thread()
函数内,发起内核自动测试。具体的代码如下:
ktest_start(ktest_test_kfifo, 0);
这样就发起了一个内核测试,它会创建一个新的内核线程进行自动测试,您不必担心第一个内核线程会被阻塞。
API文档
ktest_start
pid_t ktest_start(uint64_t (*func)(uint64_t arg), uint64_t arg)
描述
开启一个新的内核线程以进行测试
参数
func
测试函数. 新的测试线程将会执行该函数,以进行测试。
arg
传递给测试函数的参数
返回值
测试线程的pid
assert
#define assert(condition)
描述
判定condition是否为1,若不为1,则输出一行错误日志信息:
[ kTEST FAILED ] Ktest Assertion Failed, file:%s, Line:%d
kTEST
#define kTEST(...)
描述
格式化输出一行以[ kTEST ] file:%s, Line:%d
开头的日志信息。
ktest_case_table
typedef long (*ktest_case_table)(uint64_t arg0, uint64_t arg1)
描述
ktest用例函数的类型定义。
处理器架构
该部分文档提供了和处理器架构相关的一些编程实现细节的描述。
LibC文档
简介
LibC是连接用户程序和操作系统的纽带,LibC为应用程序提供了一系列标准库函数。应用程序可以通过DragonOS的LibC,快速地与操作系统进行交互。 DragonOS的LibC主要依照POSIX 2008规范实现,与Linux下的glibC具有相似之处。
API文档
ctype.h
函数列表(这里只列出已实现的函数):
``int isprint(int c)`` : 传入一个字符,判断是否可以被输出
``int islower(int c)`` : 传入一个字符,判断是否是小写字母
``int isupper(int c)`` : 传入一个字符,判断是否是大写字母
``int isalpha(int c)`` : 传入一个字符,判断是否是字母
``int isdigit(int c)`` : 传入一个字符,判断是否是数字
``int toupper(int c)`` : 传入一个小写字母字符,返回这个字母的大写形式
``int tolower(int c)`` : 传入一个大写字母字符,返回这个字母的小写形式
``int isspace(int c)`` : 传入一个字符,判断是否是空白字符
宏定义:
### 暂无用处
``#define _U 01``
``#define _L 02``
``#define _N 04``
``#define _S 010``
``#define _P 020``
``#define _C 040``
``#define _X 0100``
``#define _B 0200``
dirent.h
简介
与文件夹有关的头文件。
结构体列表:
``struct DIR`` :
变量列表:
``int fd`` : 文件夹id(不推荐修改)
``int buf_pos`` : 文件夹缓冲区指针的位置
``int buf_len`` : 文件夹缓冲区的大小(默认为256)
``struct dirent`` :
变量列表:
``ino_t(see libc/sys/types.h) ino`` : 文件序列号(不推荐修改)
``off_t d_off`` : dir偏移量(不推荐修改)
``unsigned short d_reclen`` : 文件夹中的记录数
``unsigned char d_type`` : 目标的类型(有可能是文件,文件夹,磁盘)
``char d_name[]`` : 目标的名字
函数列表(这里只列出已实现的函数):
``DIR opendir(const char *path)``
传入文件夹的路径,返回文件夹结构体
``int closedir(DIR *dirp)``
传入文件夹结构体,关闭文件夹,释放内存
若失败,返回-1
``dirent readdir(DIR *dir)``
传入文件夹结构体,读入文件夹里的内容,并打包为dirent结构体返回
宏定义:
文件夹类型:
``#define VFS_IF_FILE (1UL << 0)``
``#define VFS_IF_DIR (1UL << 1)``
``#define VFS_IF_DEVICE (1UL << 2)``
缓冲区长度的默认值
``#define DIR_BUF_SIZE 256``
errno.h
简介:
共享错误号码
属性:
``extern int errno`` : 通用错误代码
宏定义(复制自代码,了解即可):
#define E2BIG 1 /* 参数列表过长,或者在输出buffer中缺少空间 或者参数比系统内建的最大值要大 Argument list too long.*/
#define EACCES 2 /* 访问被拒绝 Permission denied*/
#define EADDRINUSE 3 /* 地址正在被使用 Address in use.*/
#define EADDRNOTAVAIL 4 /* 地址不可用 Address not available.*/
#define EAFNOSUPPORT 5 /* 地址family不支持 Address family not supported.*/
#define EAGAIN 6 /* 资源不可用,请重试。 Resource unavailable, try again (may be the same value as [EWOULDBLOCK]).*/
#define EALREADY 7 /* 连接已经在处理 Connection already in progress.*/
#define EBADF 8 /* 错误的文件描述符 Bad file descriptor.*/
#define EBADMSG 9 /* 错误的消息 Bad message.*/
#define EBUSY 10 /* 设备或资源忙 Device or resource busy.*/
#define ECANCELED 11 /* 操作被取消 Operation canceled.*/
#define ECHILD 12 /* 没有子进程 No child processes.*/
#define ECONNABORTED 13 /* 连接已断开 Connection aborted.*/
#define ECONNREFUSED 14 /* 连接被拒绝 Connection refused.*/
#define ECONNRESET 15 /* 连接被重置 Connection reset.*/
#define EDEADLK 16 /* 资源死锁将要发生 Resource deadlock would occur.*/
#define EDESTADDRREQ 17 /* 需要目标地址 Destination address required.*/
#define EDOM 18 /* 数学参数超出作用域 Mathematics argument out of domain of function.*/
#define EDQUOT 19 /* 保留使用 Reserved*/
#define EEXIST 20 /* 文件已存在 File exists.*/
#define EFAULT 21 /* 错误的地址 Bad address*/
#define EFBIG 22 /* 文件太大 File too large.*/
#define EHOSTUNREACH 23 /* 主机不可达 Host is unreachable.*/
#define EIDRM 24 /* 标志符被移除 Identifier removed.*/
#define EILSEQ 25 /* 不合法的字符序列 Illegal byte sequence.*/
#define EINPROGRESS 26 /* 操作正在处理 Operation in progress.*/
#define EINTR 27 /* 被中断的函数 Interrupted function.*/
#define EINVAL 28 /* 不可用的参数 Invalid argument.*/
#define EIO 29 /* I/O错误 I/O error.*/
#define EISCONN 30 /* 套接字已连接 Socket is connected.*/
#define EISDIR 31 /* 是一个目录 Is a directory*/
#define ELOOP 32 /* 符号链接级别过多 Too many levels of symbolic links.*/
#define EMFILE 33 /* 文件描述符的值过大 File descriptor value too large.*/
#define EMLINK 34 /* 链接数过多 Too many links.*/
#define EMSGSIZE 35 /* 消息过大 Message too large.*/
#define EMULTIHOP 36 /* 保留使用 Reserved.*/
#define ENAMETOOLONG 37 /* 文件名过长 Filename too long.*/
#define ENETDOWN 38 /* 网络已关闭 Network is down.*/
#define ENETRESET 39 /* 网络连接已断开 Connection aborted by network.*/
#define ENETUNREACH 40 /* 网络不可达 Network unreachable.*/
#define ENFILE 41 /* 系统中打开的文件过多 Too many files open in system.*/
#define ENOBUFS 42 /* 缓冲区空间不足 No buffer space available.*/
#define ENODATA 43 /* 队列头没有可读取的消息 No message is available on the STREAM head read queue.*/
#define ENODEV 44 /* 没有指定的设备 No such device.*/
#define ENOENT 45 /* 没有指定的文件或目录 No such file or directory.*/
#define ENOEXEC 46 /* 可执行文件格式错误 Executable file format error.*/
#define ENOLCK 47 /* 没有可用的锁 No locks available.*/
#define ENOLINK 48 /* 保留 Reserved.*/
#define ENOMEM 49 /* 没有足够的空间 Not enough space.*/
#define ENOMSG 50 /* 没有期待类型的消息 No message of the desired type.*/
#define ENOPROTOOPT 51 /* 协议不可用 Protocol not available.*/
#define ENOSPC 52 /* 设备上没有空间 No space left on device.*/
#define ENOSR 53 /* 没有STREAM资源 No STREAM resources.*/
#define ENOSTR 54 /* 不是STREAM Not a STREAM*/
#define ENOSYS 55 /* 功能不支持 Function not supported.*/
#define ENOTCONN 56 /* 套接字未连接 The socket is not connected.*/
#define ENOTDIR 57 /* 不是目录 Not a directory.*/
#define ENOTEMPTY 58 /* 目录非空 Directory not empty.*/
#define ENOTRECOVERABLE 59 /* 状态不可覆盖 State not recoverable.*/
#define ENOTSOCK 60 /* 不是一个套接字 Not a socket.*/
#define ENOTSUP 61 /* 不被支持 Not supported (may be the same value as [EOPNOTSUPP]).*/
#define ENOTTY 62 /* 不正确的I/O控制操作 Inappropriate I/O control operation.*/
#define ENXIO 63 /* 没有这样的设备或地址 No such device or address.*/
#define EOPNOTSUPP 64 /* 套接字不支持该操作 Operation not supported on socket (may be the same value as [ENOTSUP]).*/
#define EOVERFLOW 65 /* 数值过大,产生溢出 Value too large to be stored in data type.*/
#define EOWNERDEAD 66 /* 之前的拥有者挂了 Previous owner died.*/
#define EPERM 67 /* 操作不被允许 Operation not permitted.*/
#define EPIPE 68 /* 断开的管道 Broken pipe.*/
#define EPROTO 69 /* 协议错误 Protocol error.*/
#define EPROTONOSUPPORT 70 /* 协议不被支持 Protocol not supported.*/
#define EPROTOTYPE 71 /* 对于套接字而言,错误的协议 Protocol wrong type for socket.*/
#define ERANGE 72 /* 结果过大 Result too large.*/
#define EROFS 73 /* 只读的文件系统 Read-only file system.*/
#define ESPIPE 74 /* 错误的寻道 Invalid seek.*/
#define ESRCH 75 /* 没有这样的进程 No such process.*/
#define ESTALE 76 /* 保留 Reserved.*/
#define ETIME 77 /* 流式ioctl()超时 Stream ioctl() timeout*/
#define ETIMEDOUT 78 /* 连接超时 Connection timed out.*/
#define ETXTBSY 79 /* 文本文件忙 Text file busy.*/
#define EWOULDBLOCK 80 /* 操作将被禁止 Operation would block (may be the same value as [EAGAIN]).*/
#define EXDEV 81 /* 跨设备连接 Cross-device link.*/
fcntl.h
简介
文件操作
函数列表:
``int open(const char * path,int options, ...)``
传入文件路径,和文件类型(详细请看下面的宏定义),将文件打开并返回文件id。
宏定义(粘贴自代码,了解即可):
#define O_RDONLY 00000000 // Open Read-only
#define O_WRONLY 00000001 // Open Write-only
#define O_RDWR 00000002 // Open read/write
#define O_ACCMODE 00000003 // Mask for file access modes
#define O_CREAT 00000100 // Create file if it does not exist
#define O_EXCL 00000200 // Fail if file already exists
#define O_NOCTTY 00000400 // Do not assign controlling terminal
#define O_TRUNC 00001000 // 文件存在且是普通文件,并以O_RDWR或O_WRONLY打开,则它会被清空
#define O_APPEND 00002000 // 文件指针会被移动到文件末尾
#define O_NONBLOCK 00004000 // 非阻塞式IO模式
#define O_EXEC 00010000 // 以仅执行的方式打开(非目录文件)
#define O_SEARCH 00020000 // Open the directory for search only
#define O_DIRECTORY 00040000 // 打开的必须是一个目录
#define O_NOFOLLOW 00100000 // Do not follow symbolic links
math.h
简介:
数学库
函数列表:
``double fabs(double x)`` : 返回 x 的绝对值
``float fabsf(float x)`` : 返回 x 的绝对值
``long double fabsl(long double x)``: 返回 x 的绝对值
``double round(double x)`` 四舍五入 x
``float roundf(float x)`` 四舍五入 x
``long double roundl(long double x)`` 四舍五入 x
``int64_t pow(int64_t x,int y)`` 返回 x 的 y 次方
stdio.h
简介:
向标准输入输出里操作
函数列表:
``int64_t put_string(char *str, uint64_t front_color, uint64_t bg_color)``
输出字符串(带有前景色,背景色)
``int printf(const char *fmt, ...)``
就是正常的 ``printf``
``int sprintf(char *buf,const char *fmt,...)```
就是正常的 ``sprintf``
``int vsprintf(char *buf,const char *fmt,va_list args)``
格式化,不建议调用,请用 printf 或 sprintf 替代。
宏定义
### 字体颜色的宏定义
``#define COLOR_WHITE 0x00ffffff //白``
``#define COLOR_BLACK 0x00000000 //黑``
``#define COLOR_RED 0x00ff0000 //红``
``#define COLOR_ORANGE 0x00ff8000 //橙``
``#define COLOR_YELLOW 0x00ffff00 //黄``
``#define COLOR_GREEN 0x0000ff00 //绿``
``#define COLOR_BLUE 0x000000ff //蓝``
``#define COLOR_INDIGO 0x0000ffff //靛``
``#define COLOR_PURPLE 0x008000ff //紫``
### 无需使用
``#define SEEK_SET 0 /* Seek relative to start-of-file */``
``#define SEEK_CUR 1 /* Seek relative to current position */``
``#define SEEK_END 2 /* Seek relative to end-of-file */``
``#define SEEK_MAX 3``
printf.h
不建议引用,需要 printf
函数请引用 stdio.h
stddef.h
简介:
定义了关于指针的常用类型
定义:
``typedef __PTDIFF_TYPE__ ptrdiff_t`` : 两个指针相减的结果类型
``NULL ((void *) 0)`` : 空指针
stdlib.h
简介:
一些常用函数
函数列表:
``void *malloc(ssize_t size)`` : 普通的 ``malloc``
``void free(void *ptr)`` : 释放内存
``int abs(int x)`` : x 的绝对值
``long labs(long x)`` : x 的绝对值
``long long llabs(long long x)`` : x 的绝对值
``int atoi(const char *str)`` 字符串转数字
``void exit(int status)`` : 普通的 ``exit``
string.h
简介:
字符串操作
函数列表:
``size_t strlen(const char *s)`` : 返回字符串长度
``int strcmp(const char *a,const char *b)`` 比较字符串的字典序
``char* strncpy(char *dst,const char *src,size_t count)``
拷贝制定字节数的字符串
dst: 目标地址
src: 原字符串
count: 字节数
``char* strcpy(char *dst,const char *src)`` : 复制整个字符串
``char* strcat(char *dest,const char* src)`` : 拼接两个字符串
time.h
简介:
时间相关
时刻以纳秒为单位
结构体:
``struct timespec`` : 时间戳
### 变量列表:
``long int tv_sec`` : 秒
``long int tv_nsec`` : 纳秒
宏定义:
``#define CLOCKS_PER_SEC 1000000`` 每一秒有1000000个时刻(纳秒)
函数列表:
``int nanosleep(const struct timespec *rdtp,struct timespec *rmtp)``
休眠指定时间
rdtp : 指定休眠的时间
rmtp : 返回剩余时间
``clock_t clock()`` : 获得当前系统时间
unistd.h
简介:
也是一些常用函数
函数列表:
``int close(int fd)`` : 关闭文件
``ssize_t read(int fd,void *buf,size_t count)`` : 从文件读取
传入文件id,缓冲区,以及字节数
返回成功读取的字节数
``ssize_t write(int fd,void const *buf,size_t count)`` : 写入文件
传入文件id,缓冲区,字节数
返回成功写入的字节数
``off_t lseek(int fd,off_t offset,int whence)`` : 调整文件访问位置
传入文件id,偏移量,调整模式
返回结束后的文件访问位置
``pid_t fork(void)`` : fork 当前进程
``pid_t vfork(void)`` : fork 当前进程,与父进程共享 VM,flags,fd
``uint64_t brk(uint64_t end_brk)`` : 将堆内存调整为end_brk
若end_brk 为 -1,返回堆区域的起始地址
若end_brk 为 -2,返回堆区域的结束地址
否则调整堆区的结束地址域,并返回错误码
``void *sbrk(int64_t increment)`` :
将堆内存空间加上offset(注意,该系统调用只应在普通进程中调用,而不能是内核线程)
increment : 偏移量
``int64_t chdir(char *dest_path)``
切换工作目录(传入目录路径)
``int execv(const char* path,char * const argv[])`` : 执行文件
path : 路径
argv : 执行参数列表
``extern int usleep(useconds_t usec)`` : 睡眠usec微秒
这里是所有libc头文件的集合,在代码里可以这样引用:
#include<libc/src/xxx.h>
设计文档
[内容待完善]
参与开发
DragonOS社区欢迎您的加入!学习技术本身固然重要,但是以下这些文档将会帮助您了解DragonOS社区需要什么。
阅读这些文档将会帮助您参与到开发之中,并且能让您的代码能更快合并到主线。
开发流程介绍
作为一名想要参与开发的新人,您可能迫切想要了解如何参与开发,仔细阅读这篇文章将能帮助您了解整个开发流程,以及一些注意事项。
注:本文参考了Linux文档中的一些思想、内容,非常感谢Linux社区的工作者们的经验!
1.概要
对于新人而言,参与开发的过程主要包括以下几步:
运行DragonOS:按照文档:构建DragonOS中的教程,编译DragonOS,并成功运行。在运行过程中,如果有任何的疑问,欢迎您在交流群或者BBS上进行反馈!
联系Maintainer:您可以通过邮箱longjin@DragonOS.org或者QQ
184829088
与龙进取得联系,或者是对应的模块的开发者进行联系(目前您可以通过发行日志上的邮箱与他们建立联系,在将来我们将编写一份“Maintainers List”以便于您能快速找到要联系的人)。 为了节省您的时间,请简要说明:如何称呼您
您目前掌握的技术能力
您希望为DragonOS贡献什么功能,或者进行某个方面的开发,亦或者是想要按照社区的需要来参与新功能开发及bug的修复。
如果您是来自高校/科研单位/企业/组织的代表,希望与社区开展合作研究、开发。那么,除使用QQ交流之外,还请麻烦您通过您的教师邮箱/学生邮箱/企业邮箱向contact@DragonOS.org发送一封相关内容的邮件!这么做的目的是为了确认您是来自您的单位,而不是网络上其他人员冒充您的身份。
加入工作群:在进一步了解,确认您愿意参与开发后,我们将邀请您加入工作群。
开始开发:正式开始代码开发工作。
备注
一些小功能的改进以及Bug修复并不一定需要提前与社区建立正式的联系,对于这些patch,您可以直接开发,然后在Github上进行Pull Request. 这也是可以的。
但是,如果您愿意的话,与Maintainer联系会是一个更好的选择。
2.开发过程是如何运作的?
如今的DragonOS由于正处于开发的早期阶段,开发者数量不超过50人,因此,现在DragonOS的开发过程是通过比较松散的方式组织起来的。
2.1.版本发布周期
自从2022年11月6日,DragonOS发布第一个版本以来,版本发布就被定义为15~21天发布一个更新版本。由于开发人员数量仍然较少,因此,目前这个时间是21天。我们将版本号定义为x.y.z
的格式,每21天发布一个z
版本. 在积累了2~3个月后,当DragonOS引入了足够的新功能,则发布一个y
版本。请注意,我们仍未定义x
版本的发行周期。当前,x
版本号仍然是0
。
创建没有BUG的、具有尽可能少BUG的代码,是每个开发者的目标之一。我们希望在每个y
版本发布时,能够修复已知的问题。然而,由于在操作系统中,影响问题的变量太多了,以至于我们只能尽全力去减少BUG,我们无法保证y
版本不存在bug.
2.2.每个补丁的生命周期
当您向DragonOS的仓库发起一次PR,那么这次PR就是一个补丁。我们会对您的补丁进行Review,以确保每个补丁都实现了一个希望在主线中进行的更改。并且,Maintainer或者感兴趣的小伙伴会对您的补丁提出修改意见。当时机合适时,您的代码将被合入主线。
如果您的补丁的规模比较小,那么,它将会比较快的被合入主线。如果补丁的规模较大,或者存在一些争议,那么我们需要对其进行进一步的讨论及修改、审查,直到它符合要求。
每个Patch都会经历这么一个过程(这只是一个概述,详细的描述请看后文):
设计:在这个阶段,我们将明确,这个补丁将要实现什么功能,或者是解决什么问题,以及我们将要采用什么样的方式去完成它。通常来说,这项工作是开发者自己完成的。但是,我们建议您,在设计了这个补丁之后,能够把您的设计方案公开,和大家讨论这项工作。 闭门造车容易出错,在与大家沟通的过程中,则能及早的发现设计上的错误,从而节省很多的时间。
代码编写:经过了设计阶段,您已经能够明白自己要实现的到底是一个什么样的东西。您在这个阶段进行代码编写、调试。
代码审查:当完成代码编写后,您可以通过Github发起一个PR,然后您的补丁进入了代码审查阶段。在这一阶段,开发者们,或者是Maintainer会和您进行沟通交流,对您的代码进行评论,以及对发现的问题提出修改建议。
合并主线:如果一切顺利,那么代码将会被合并入主线。若该补丁在合并主线后,被发现具有较大的问题,那么它可能会被回退。重新进入前面的阶段,进行修改。
长期维护:虽然说,代码被合并之后,原来的开发人员可能会在很久一段时间后,不再理会这些代码,但是这种行为可能会给其他开发者们留下不好的印象。其实,当补丁被合并入主线后,其他开发人员会尝试继续维护这些代码,这样能够很大程度的减轻您的维护负担。但是,如果想要这些代码能够长期的被保留下来,持续的发光发热,那么离不开原始开发人员的支持(不然的话,后来的人可能难以理解、维护这些代码),这是一件很有意义的事情。
对于没有参与过开源项目的同学来说,他们可能会想当然的,简单的把上述流程简化成合并主线这一个步骤,这样是不可取的。因为这样会导致很多问题,包括但不限于“写了代码但是效果很差”、“写的东西由于无法满足项目的需求,而不能被合并”。
2.3.开发工具
从上面的描述可以看出,为了让开发人员们能高效地进行协作,那么必须使用版本控制工具来管理这个过程。目前,DragonOS使用Git来进行源代码管理。它是一个非常优秀的工具,这是不必多说的。对于每个开发者而言,Git的使用是一项必备的技能;哪怕您只是想学习DragonOS的源代码,您也需要git来获取、同步最新的代码。虽然Git的使用,对于新手来说,有些困难,但是经过一些时间的学习后,还是可以掌握的。
git的官方网站为https://git-scm.com/
2.4.沟通交流
DragonOS的主要开发工作是通过飞书以及bbs进行的。对于正准备参与的开发者而言,您可以加入我们的交流讨论QQ群,具体的群号可以在 与社区建立联系 一文中找到。
何时使用即时通讯软件? 我们在飞书上创建了工作群,为提高即时沟通的效率,这个群仅供那些真正有意愿、且正在进行或准备进行(能够保证会进行)代码开发的开发者们加入。
何时使用BBS? 对于一些正式的、需要大家广泛参与,或者是能够帮助尚未参与开发的同学了解当前的开发进度的主题,请您在https://bbs.DragonOS.org上,使用类似于写信件一样的,正式的语言,完整地描述清楚您想表达的内容。这样有助于更多的人快速明白您要表达的是什么,也能提高整体的沟通效率。并且,bbs能够长期保存以往的帖子,这样后来者能更方便的了解“当初开发的时候,人们究竟是怎么想的”。
关于交流讨论会 除由于法定节假日放假,或特殊情况以外,我们每周末都会召开线上交流讨论会,同步介绍每周的进展。社区成员可以在这里分享自己的方案设计或是一些操作系统相关的知识(分享的话,需要提前跟会议主持人提出,以便妥善安排)。
如何提问? 下面这些建议能够帮助您与他人开展高效率的对话:
对于具有主题性的问题,在bbs上发帖进行讨论。 这样能够让讨论更具有目标性。当谈论到新的主题的时候,请开一个新的帖子,并在原来的帖子中,添加对特定的子问题所在的主题的链接。
请礼貌的交流。 文明的语言能够减少不必要的冲突。技术意见上的冲突是思维的碰撞,但是如果涉及到了不文明的语言,或者在非技术层面,对他人进行攻击,这将破坏和谐讨论的氛围,这是我们反对的。如果有人试图激怒你,请忽略他的消息,别理他就好了。
在提问之前,请确保您已经搜索了bbs以及互联网上的解决方案,并描述清楚您的问题的上下文情景、您的思考以及网络上的解决方案。 一些开发人员会对“明显没有进行认真思考”的问题,表现出不耐烦的态度(因为未经思考的问题会浪费他们大量的时间)。
当别人向您提问时,请您耐心听他人的问题。如果您认为对方的问题过于简单或是未经思考,还请您为对方指个路,告诉对方,在哪些地方,使用什么样的方式搜索,能够得到对解决问题有帮助的资料。有时候,新手需要的是一个指路人,他会非常感谢您的!
2.5.如何入门开发?
DragonOS原采用C语言进行开发,目前正在用Rust重构原有代码、开发新的模块,也就是说,除非您要进行对C语言代码的BUG修复,否则,其余的开发工作,我们都建议您通过Rust完成。因为,它能从语言层面解决那些让我们头疼的内存安全问题。从长期来看,能够提升开发效率以及软件质量。
如何开发第一个补丁,是一个非常常见的问题。可以理解的是,个人开发者面对这样一个项目,常常会不知道从哪个地方开始入手。这是一件很正常的事情,因此我们建议您通过上文提到的方式,与社区建立联系,了解目前社区正在做什么,以及需要什么。
对于一个新的参与者来说,我们建议您从这样一个步骤开始:
阅读文档,编译、运行DragonOS,并且尝试使用它目前已有的功能。
然后,您可以通过查看DragonOS的GitHub仓库的project面板,看看目前仍有哪些待解决的问题。可以肯定的是,永远不会缺少待解决的问题,您在解决这些问题的过程中,能够获得一些宝贵的经验。
3.早期设计
对于软件开发而言,写代码永远不是第一步。在编写代码之前,进行一些必要的设计(提出架构、技术方案),是项目成功的基础工作。在新的补丁开发的早期,花费一些时间进行计划和沟通,往往能够在接下来的阶段节省更多的时间。
3.1.定义我们要解决的问题
与大多数的工程项目一样,在DragonOS中进行开发,首先需要清晰的描述要解决的问题。只有精准的定义了问题,才能找到正确的解决方案。有时候,我们能很轻易的定义问题,比如“编写串口驱动程序,使得它能把屏幕上打印的字符,输出到串口”。
但是,有时候,我们容易将真正的问题与某个解决方案相混淆,并且还没意识到这一点。
在2022年10月时,我发现,在真机调试的时候,需要频繁的拔插硬盘(先连接到电脑,待数据拷贝完毕后,再连接到测试机)。我对这一过程非常的不满,因为很浪费时间。我的直觉想法是:“有没有一种设备,能够一头连接电脑,另一头连接测试机的SATA接口。从测试机的角度看来,这是一块硬盘;测试机对这块磁盘的操作,都转换为了对我的电脑上面的一个磁盘镜像文件的操作。”我的想法就是:“购买/定制一款设备,能够实现上面的这个功能,那就能解决频繁拔插硬盘的烦恼了!”然后我为了寻找这样的设备,询问了一些硬件公司,他们的开价都在2万元左右。
我在上面的这个过程中,就犯了一个错误:将真正的问题与某个解决方案相混淆了。真正的问题是:“解决需要频繁的拔插硬盘”,但是,在我的思考的过程中,不知不觉间,将问题转换成了“如何实现一款具有硬盘模拟功能的设备”。后面这个问题,只是某个解决方案下,需要解决的问题,并不是我们要解决的根本问题。
对于要解决的根本问题,事实上有更好的解决方案:“制作一个类似于开关一样的转换器,当数据从电脑拷贝到磁盘后,把开关拨向另一边,使得电路与测试机接通”。这个方案的成本估摸着就十几二十块钱。
上面的这个故事,告诉我们的是,在早期设计阶段,我们应当关注的是问题本身——而不是特定的解决方案。
我们需要关注系统的稳定性、长期的可维护性,解决方案必须考虑到这两点。由于系统比较复杂,因此,请您在开始编码之前,与社区的小伙伴讨论您的设计方案,以便您的方案能充分地,从全局的角度,考虑到系统的稳定性、可维护性。
因此,在开发的早期,我们应当对以下三个问题,拥有一个答案:
要解决的本质问题是什么?
这个问题会影响哪些方面/哪些用户?提出的解决方案应当解决哪些用例、场景?
DragonOS目前在解决该问题的方面,具有哪些不足/问题?
只有考虑清楚了上面三个问题,讨论的解决方案才是有意义的。这是一个架构设计的过程,需要进行仔细的思考。尽管我们目前提倡敏捷开发,但是前期的架构设计仍然是非常重要的。
3.2.早期讨论
在实施开发之前,与社区的成员们进行讨论是非常有意义的。这能够通过多种方式节省您的时间,并减少许多不必要的麻烦:
DragonOS可能以您不知道、不理解的方式,已经解决了相关的问题。DragonOS里面的一些特性、功能细节不是很明显,他们不一定会被写进文档。也许这些细节只是在某个不起眼的注释里面提到了,因此您很难注意到这些。这种细节可能只有一些开发人员知道。因此,与社区沟通能够避免您进行重复的工作。
您提出的解决方案中,可能会有一些东西,由于一些原因(比如方案中的一些设计会在将来造成问题、方案的架构设计具有明显缺陷),无法合入主线。
其他的开发人员可能已经考虑过这个问题;他们可能有更好的解决方案,或者是更好的想法。并且,他们可能愿意帮助你一起完善你的解决方案。
Linux文档中提到:闭门造车式的设计和开发,所产生的代码总会有问题,这些问题只有在发布到社区里面的时候才会暴露出来。因此,我们必须吸取前人之鉴,通过与社区开发人员进行早期讨论,从而避免大量的痛苦和额外的工作。
3.3.在何时发布帖子?
如果可能的话,在开发的早期阶段发布您的计划、设计,会是一个不错的选择。发帖的时候,您可以描述您正在解决的问题,以及已经制定的一些计划。包括但不限于:如何将设计付诸实施。您在社区内发布帖子,不仅能够帮助您获得一些有用的建议,也能帮助整个DragonOS社区提供有用的信息,使得社区沟通更高效。
在这个阶段,可能您发布的帖子并不会引来很多评论,这并不一定意味着您做的不好,或者大家对您所做的工作不感兴趣。当然,也不能就此认为您的设计、想法不存在问题。可能只是因为大家比较忙,看了您的帖子之后,了解到了您的工作,但是大家并不一定有时间进行回复。(但是事实上您发布的信息对他人来说是有用的)
在这种情况下,请不要气馁,您最好的做法就是,继续您的工作,并且不时地在您的帖子下分享您的工作,这样能够让社区的成员们随时了解到您的最新进展。
3.4.获得您所在的组织的支持
如果您对DragonOS的开发工作,是在您的公司内完成的。那么,很显然,在您把计划、代码发布到社区论坛之前,您必须取得您的经理或上级的许可。
同时,请注意,根据我们的授权许可,基于DragonOS操作系统的内核、官方开源的用户库而开发的代码,或者为DragonOS操作系统本身而开发的代码,根据开源授权许可,必须同样以GPLv2协议进行授权发布。如果您所在的组织,违背了GPLv2协议中的要求,以除GPLv2以外的协议开放源代码,或者是进行“闭源使用”,那么DragonOS社区对您的公司/组织所授予的使用DragonOS源代码的授权,将会被自动撤销。这将会面临一系列的法律问题。因此,在这个问题上,公司的管理人员、法律人员如果能越早地就公司要在DragonOS中开发的软件项目达成一致,将能促进您的公司在该项目上的进展。
如果您的公司的项目/或者是您研究的项目根据您所在组织的保密规定,不能将其进行过早的披露,那也没有问题。只要您的公司能够确保这部分代码,在其编译而成的二进制产品被发布之时,按照GPLv2协议进行开源,并向开源社区贡献这部分代码即可。
4.如何正确的编写代码
5.发起Pull Request
6.后期跟进
7.另外的一些话题
8.更多信息
9.结语
C语言代码风格
这份文档将会简要的介绍DragonOS的C语言代码风格。每个人的代码风格都各不相同,这是一件非常正常的事情。但是,对于一个开源项目的可维护性而言,我们希望制定一些代码规范,以便包括您在内的每个开发者都能在看代码的时候更加舒服。一个充斥着各种不同代码风格的项目,是难以维护的。
我们在这里提出一些建议,希望您能够尽量遵循这些建议。这些建议与Linux的代码规范相似,但又略有不同。在变量命名上,DragonOS采用Linux的风格;对于缩进,DragonOS采用Microsoft风格。
0. 代码格式化工具
在提出下面的建议之前,我们建议您在开发的时候使用Visual Studio Code的C/C++ Extension Pack
插件作为代码格式化工具。这些插件能为您提供较好自动格式化功能,使得您的代码的基本格式符合DragonOS的要求。
当您在编码时,经常性的按下Ctrl+shift+I
或您设置的代码格式化快捷键,能帮助您始终保持良好的代码格式。
1. 缩进
一个制表符的宽度等于4个空格。代码的缩进是按照制表符宽度(在多数编辑器上为4个字符)进行缩进的。
这样能够使得您的代码变得更加容易阅读,也能更好的看出代码的控制结构。这样能避免很多不必要的麻烦!
举个例子:在switch语句中,将switch和case放置在同一缩进级别。并且将每个case的代码往右推进一个tab。这样能让代码可读性变得更好。
switch (cmd)
{
case AHCI_CMD_READ_DMA_EXT:
pack->blk_pak.end_handler = NULL;
pack->blk_pak.cmd = AHCI_CMD_READ_DMA_EXT;
break;
case AHCI_CMD_WRITE_DMA_EXT:
pack->blk_pak.end_handler = NULL;
pack->blk_pak.cmd = AHCI_CMD_WRITE_DMA_EXT;
break;
default:
pack->blk_pak.end_handler = NULL;
pack->blk_pak.cmd = cmd;
break;
}
2. 分行
我们建议,每行不要超过120个字符。如果超过了,除非有必要的理由,否则应当将其分为两行。
在分行时,我们需要从被分出来的第二行开始,比第一行的起始部分向右进行一个缩进,以表明这是一个子行。使用代码格式化的快捷键能让你快速完成这件事。
对于一些日志字符串而言,为了能方便的检索到他们,我们不建议对其进行分行。
对于代码的分行,请不要试图通过以下的方式将几个语句放置在同一行中,这样对于代码可读性没有任何好处:
// 错误示范(1)
if(a) return 1;
// 错误示范(2)
if(b)
do_a(),do_b();
3. 大括号和空格
3.1 大括号
大括号的放置位置的选择是因人而异的,主要是习惯原因,而不是技术原因。我们推荐将开始括号和结束括号都放置在一个新的行首。如下所示:
while(i<10)
{
++i;
}
这种规定适用于所有的代码块。
这么选择的原因是,在一些编辑器上,这样放置括号,编辑器上将会出现辅助的半透明竖线,且竖线两端均为括号。这样能帮助开发者更好的定位代码块的层次关系。
下面通过一些例子来演示:
在下面这个代码块中,我们需要注意的是,else if
语句需要另起一行,而不是跟在上一个}
后方。这是因为我们规定{
必须在每行的起始位置,并且还要保持缩进级别的缘故。
if (*fmt == '*')
{
++fmt;
}
else if (is_digit(*fmt))
{
field_width = skip_and_atoi(&fmt);
}
当循环中有多个简单的语句的时候,需要使用大括号。
while (condition)
{
if (test)
do_something();
}
当语句只有1个简单的子句时,我们不必使用大括号。
if(a)
return 1;
3.2 空格
对于大部分关键字,我们需要在其后添加空格,以提高代码的可读性。
请您在所有这些关键字后面输入一个空格:
if, switch, case, for, do, while
关键字sizeof、typeof、alignof、__atrribute__的后面则不需要添加空格,因为使用他们的时候,就像是使用函数一样。
对于指针类型的变量,*
号要贴近变量名而不是贴近类型名。如下所示:
char *a;
void *func(char* s, int **p);
在大多数二元和三元运算符周围(在每一侧)使用一个空格,如下所示:
= + - < > * / % | & ^ <= >= == != ? :
这些一元运算符后方没有空格
& * + - ~ ! sizeof typeof alignof __attribute__ defined
特殊的例子,以下运算符的前后都不需要空格:
++ -- . ->
4. 命名
DragonOS中的命名规范不使用诸如TempValue
这样的驼峰命名法的函数名,而是使用tmp
这样言简意赅的命名。
注意,这里指的是我们在整个项目内都不希望使用驼峰命名法。并不意味着程序员可以随便的使用一些难以理解的缩写来作为变量名。
对于全局变量或者全局可见的函数、结构体而言,我们需要遵循以下的命名规定:
名称需要易于理解,且不具有歧义。如:对于一个计算文件夹大小的函数而言,我们建议使用
count_folder_size()
来命名,而不是cntfs()
这样令其他人头大的命名。全局的,非static的名称,除非有特别的必要,命名时需要遵循以下格式:
模块名缩写前缀_函数/变量名
。这样的命名能便于别人区分这个名称位于哪个模块内,也减少了由于命名冲突所导致的麻烦。不需要让其他代码文件可见的全局名称,必须添加
static
修饰符。
对于函数内的局部变量而言,命名规范则是需要言简意赅。过长的名称在局部变量中没有太大的意义。
【文档未完成,待继续完善】
Rust语言代码风格
这篇文档将会介绍DragonOS中的Rust语言代码风格。随着开发的进行,这些风格可能会发生变化,但是我们会尽量保持风格的一致性。
1. 命名
2. 格式
2.1 缩进
请在提交代码之前,使用cargo fmt
命令对代码进行格式化。
2.2 函数返回值
尽管Rust可以返回函数的最后一行的语句的值,但是,这种方式会使代码的可读性变差。因此,我们推荐您在函数的最后一行使用return
语句,而不是直接返回值。
// 不推荐
fn foo() -> i32 {
1 + 2
}
// 推荐
fn foo() -> i32 {
return 1 + 2;
}
2.3 错误处理
DragonOS采用返回Posix错误码作为模块间错误处理的方式。为了确保在模块之间,错误处理代码的一致性,我们推荐在发生错误的时候,返回SystemError
类型,该类型表示posix错误码。这样做的优点尤其体现在跨模块调用函数时,可以直接返回通用的错误码,从而降低错误处理代码的耦合度。
// 函数跨越模块边界时(由其他模块调用当前函数),不推荐
fn foo() -> Result<(), CustomErr> {
if 1 + 2 == 3 {
return Ok(());
} else {
return Err(CustomErr::error);
}
}
// 函数跨越模块边界时(由其他模块调用当前函数),推荐
fn foo() -> Result<(), SystemError> {
if 1 + 2 == 3 {
return Ok(());
} else {
return Err(SystemError::EINVAL);
}
}
在模块内部,您既可以采用返回自定义错误enum的方式,也可以采用返回SystemError
的方式。但是,我们推荐您在模块内部,采用返回自定义错误enum的方式,这样可以使错误处理代码更加清晰。
TODO: 将原有的使用i32作为错误码的代码,改为使用SystemError
。
3. 注释
DragonOS的注释风格与Rust官方的不太一样,我们部分结合了Linux的注释风格。同时,我们推荐您在代码中加入尽可能多的有效注释,以便于其他人理解您的代码。并且,变量、函数等声明,遵守第一节中提到的命名规范,使其能够“自注释”。
3.1 函数注释
函数注释应该包含以下内容:
函数的功能
函数的参数
函数的返回值
函数的错误处理
函数的副作用或者其他的需要说明的内容
函数注释的格式如下:
/// @brief 函数的功能
///
/// 函数的详细描述
///
/// @param 参数1 参数1的说明
///
/// @param 参数2 参数2的说明
///
/// @return 返回值的说明
如果函数的返回值是Result
类型,那么返回值应当这样进行解释:
/// @return Ok(返回值类型) 返回值的说明
///
/// @return Err(错误值类型) 错误的说明
与社区建立联系
社区公共邮箱:contact@DragonOS.org
DragonOS负责人: longjin
工作邮箱: longjin@DragonOS.org
开发交流QQ群: 115763565
DragonOS官网: https://DragonOS.org
发行日志
这里是DragonOS的发行日志,会记录DragonOS的每一个版本的更新内容。
V0.1.5
贡献者名单
DragonOS V0.1.6版本由以下小伙伴贡献代码:
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
万晓兰
David Wen
叶锦毅
林
Albert
slientbard
悟
匿名热心人士
更新内容-内核
softirq: 重构了软中断 (#223)
timer: 重构了系统定时器 (#223)
stdio: 新增tty设备,用于标准输入输出 (#202) (#217)
lib: 第一套键盘扫描码的状态机 (#216) (#219)
syscall: 新增dup,dup2系统调用 (#224)
syscall: 新增SystemError枚举类型,使得错误处理更清晰 (#205)
driver: 新增x87浮点处理器支持 (#212)
driver: VirtIO网卡能够正常发送、接收数据 (#204)
filesystem: 修正了FAT32判断逻辑,解决了文件系统为FAT12/16时系统无法正常启动的问题。 (#211)
filesystem: 新增VFS文档,以及修改文档配置 (#209)
textui: 修复由于textui加锁,更改了preempt_count导致“进程长时间连续输出字符”的情况下,进程调度器不运行的问题。 (#203)
scheduler: 解决由于在中断上下文以外,sched_enqueue时,未关中断导致cpu_queue双重加锁的问题 (#201)
更新内容-用户环境
新增仓库
新增子项目:dsc
移植relibc: DragonOS-relibc
更新内容-其他
build: 添加了qemu使用VNC作为图像输出的选项 (#222)
更新内容-软件移植
无
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/DragonOS-Community/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
国内镜像加速: [https://mirrors.ringotek.cn/] (https://mirrors.ringotek.cn/)
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
rcore-fs - https://github.com/rcore-os/rcore-fs.git - MIT
redox - https://gitlab.redox-os.org/redox-os/redox - MIT
当前版本的所有提交记录
commit bacd691c9ef0502b5cc618aad50517f9e59df5e0
Author: login <longjin@DragonOS.org>
Date: Sun Apr 2 17:09:33 2023 +0800
软中断&定时器重构 (#223)
* 软中断&定时器重构
Co-authored-by: houmkh<houjiaying@DragonOS.org>
* 修改timer的clock()
* 删除debug信息
---------
Co-authored-by: houmkh <1119644616@qq.com>
commit 6d345b774223b0daaf0ee629c7fb595a1912a9e2
Author: HoshuChiu <129569557+HoshuChiu@users.noreply.github.com>
Date: Sun Apr 2 15:55:24 2023 +0800
添加了qemu使用VNC作为图像输出的选项 (#222)
* 添加了qemu使用VNC作为图像输出的选项
* 设置vnc端口为5900
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 2b771e32f5795e0fdda458e3bb2651ef6b9673ac
Author: Gou Ngai <sujintao@dragonos.org>
Date: Sun Apr 2 15:43:53 2023 +0800
Add dup,dup2 (#224)
* dup,dup2
* fix: sys_dup2语义与posix不一致的问题
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit d7b31a969ff091224a4929496f0278d024f78c77
Author: Gou Ngai <sujintao@dragonos.org>
Date: Fri Mar 31 18:23:58 2023 +0800
Patch keyboard capslock alt (#219)
* keyboard-alt-capslock
* 解决键盘输入'%'字符的时候无法回显的bug
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 20e3152e1eea97f87d644c3023391e172bc83c93
Author: login <longjin@DragonOS.org>
Date: Fri Mar 31 12:54:37 2023 +0800
将TTY与stdio进行连接,实现基本的stdio功能 (#217)
* 将stdio与tty接上
commit 5fb12ce447710edf8566f250655a06cb27519fca
Author: Gou Ngai <sujintao@dragonos.org>
Date: Thu Mar 30 18:19:02 2023 +0800
第一套键盘扫描码的状态机 (#216)
第一套键盘扫描码的状态机
---------
Co-authored-by: guanjinquan <1666320330@qq.com>
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 676b8ef62e1a0a1e52d65b40c53c1636a2954040
Author: Mork <91721145+MorkCarpenter@users.noreply.github.com>
Date: Wed Mar 29 21:24:11 2023 +0800
部分函数从返回值为Result<<>,i32>修改为Result<<>,SystemError> (#210)
* 将Result<<>,i32>替换为Result<<>,SystemError>
* bugfix: 显示双缓冲区初始化的时候,连续注册了两次Video Softirq的问题。
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 64aea4b3494bee7375e1c1ee5739c9fab0db0cb7
Author: Gou Ngai <sujintao@dragonos.org>
Date: Tue Mar 28 20:44:26 2023 +0800
增加x87FPU支持 (#212)
* remove `ret_from_syscall`
*修复ps2键盘驱动程序inode在进程fork的时候导致死锁的问题.
*更新: VFS每次拷贝文件描述符的时候,都会去调用inode的open函数
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 2286eda6526ed1b46afd79b47dc701034b9e903d
Author: WaferJay <17383312+WaferJay@users.noreply.github.com>
Date: Mon Mar 27 09:32:43 2023 +0800
修正了FAT32判断逻辑,解决了文件系统为FAT12/16时系统无法正常启动的问题。 (#211)
* fix(fat): fix determination of fat type casue crash if fs is fat12/16
* refactor(fat): split BiosParameterBlock.validate() into BiosParameterBlockFAT32.validate() and BiosParameterBlockLegacy.validate()
* 调整“最大允许的簇号”的常量放置的位置。
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 45b8371173b070028457f7ee64be33f68b4f9ada
Author: login <longjin@DragonOS.org>
Date: Sat Mar 25 14:51:16 2023 +0800
新增VFS文档,以及修改文档配置 (#209)
* 1.新增vfs设计文档
2.修改文档版权标志为"2022-2023, DragonOS Community"
3.修改电脑版文档页面的宽度为90%
* layout.html末尾加空行
commit 73c607aaddf6e4634cad179a81d3f1bc589f7220
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Sat Mar 18 20:43:37 2023 +0800
VirtIO网卡能够正常发送、接收数据 (#204)
* virtio-net小修改
* 移动volatile.rs到libs文件夹
* 使用virtio-drivers 0.3.0
* bugfix: 初始化BAR之后,未正确设置command register的问题
---------
Co-authored-by: longjin <longjin@dragonos.org>
commit 4454d1a2dd1f1078750151c028a794cfd9a04a1b
Author: login <longjin@DragonOS.org>
Date: Sat Mar 18 20:26:05 2023 +0800
新增SystemError枚举类型,使得错误处理更清晰 (#205)
commit 0d48c3c9c21a2dd470d0e1e58b507db60e0887bb
Author: login <longjin@DragonOS.org>
Date: Thu Mar 16 19:48:59 2023 +0800
new: tty设备(尚未与stdio接上) (#202)
commit 790d45764090bce3bbfb96b42b2818100a8cef9a
Author: login <longjin@DragonOS.org>
Date: Wed Mar 15 11:42:41 2023 +0800
修复由于textui加锁,更改了preempt_count导致“进程长时间连续输出字符”的情况下,进程调度器不运行的问题。 (#203)
commit c2e757d8cbeed01b16f48bea48ed8447685e6f1a
Author: login <longjin@DragonOS.org>
Date: Mon Mar 13 22:22:23 2023 +0800
解决由于在中断上下文以外,sched_enqueue时,未关中断导致cpu_queue双重加锁的问题 (#201)
V0.1.5
贡献者名单
DragonOS V0.1.5版本由以下小伙伴贡献代码:
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
万晓兰
David Wen
叶锦毅
林
Albert
slientbard
悟
更新内容-内核
scheduler: doc: 实时进程调度器文档 (#163)
scheduler: rt: RTQueue改用双向链表存储 (#174)
scheduler: load balance: 多核负载均衡 (#193)
Semaphore: new: 新增了rust实现的信号量 (#183)
mm: refactor: 重构了MMIO地址分配器 (#184)
RwLock: new: 新增了rust实现的读写锁 (#186)
driver: update: 完善pci的功能 (#194)
driver: new: VirtIO网卡驱动(仍存在问题) (#194)
driver: refactor: Rust版本的AHCI驱动 (#198)
block io: delete: 移除Block IO 调度器. (#196)
filesystem: refactor: 新版的VFS (#198)
filesystem: refactor: 新版的ProcFS (#198)
filesystem: refactor: 新版的DevS (#198)
filesystem: new: RamFS内存文件系统 (#198)
filesystem: new: FAT12/FAT16/FAT32文件系统 (#198)
filesystem: new: 新的设备、块设备抽象 (#198)
更新内容-用户环境
libc: 调整,将所有的app直接链接到libc.a中,而不是都执行一遍”搜索.o”的过程 (#171)
更新内容-其他
bootstrap: 解决ubuntu2210版本无法正确编译grub,以及正确安装qemu的问题 (#176)
toolchain: 添加rust的bare bone工具链 (#197)
更新内容-软件移植
无
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/DragonOS-Community/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
国内镜像加速: [https://mirrors.ringotek.cn/] (https://mirrors.ringotek.cn/)
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
rcore-fs - https://github.com/rcore-os/rcore-fs.git - MIT
redox - https://gitlab.redox-os.org/redox-os/redox - MIT
当前版本的所有提交记录
commit 84407d360511c7699938a0f245ae33ff76f16b17
Author: login <longjin@DragonOS.org>
Date: Mon Mar 13 00:26:04 2023 +0800
bugfix:解决touch命令失败的问题 (#199)
* bug fix : 解决touch命令失败的问题
commit 004e86ff19727df303c23b42c7a271b9214c6898
Author: login <longjin@DragonOS.org>
Date: Sun Mar 12 22:36:11 2023 +0800
新版文件系统重构完成 (#198)
1.重构:VFS
2. 重构:ProcFS
3. 重构:DevFS
4. 重构:FAT32
5. 重构:AHCI驱动
6. 新增:RamFS
7. 新增:MountFS
8. 新增:FAT12
9. 新增:FAT16
10. 重构:设备抽象
Co-authored-by: guanjinquan <1666320330@qq.com>
Co-authored-by: DaJiYuQia <88259094+DaJiYuQia@users.noreply.github.com>
commit 17041e0e307eaf9e8d8ddbddfa186cd1f10f1bc0
Author: login <longjin@DragonOS.org>
Date: Sun Mar 12 21:04:37 2023 +0800
添加rust的bare bone工具链 (#197)
commit 26d84a31393c50063ff416bc509316e8d342028c
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Sat Mar 11 21:09:50 2023 +0800
新增VirtIO网卡驱动 (#194)
* 修复内存bug与grub安装脚本的错误
* 修改小bug
* PCI增加功能与virtio-net驱动
* little fix
* virtio-net小修改
commit 1d48996375149279a721777b2c600e1b5c3ee1b5
Author: kong <45937622+kkkkkong@users.noreply.github.com>
Date: Sat Mar 11 18:17:35 2023 +0800
多核负载均衡(#193)
* feat(sched):CPU负载检测初步实现
* fix(smp):调整smp中的apic的头文件声明
* fix(smp):简单的负载均衡算法实现
* fix(sched):抽离负载均衡方法
* fix(sched):修改rt中的运行队列bug,调整负载均衡逻辑
* fix(process):移除无用测试代码
* reformat code
commit ef9f9732b09f78d7192f1d0dd3b41be655fb0914
Author: houmkh <100781004+houmkh@users.noreply.github.com>
Date: Thu Mar 9 23:31:25 2023 +0800
修复了mmio buddy的bug (#189)
* 修改buddy_query
commit c1396d277115b371d09ad6d39a1c419f9224ffd0
Author: Gou Ngai <sujintao@dragonos.org>
Date: Mon Mar 6 11:28:32 2023 +0800
Rwlock文档 (#186)
* Rwlock文档
commit a7eb62a47a8d701b90a14f83cc9028cfed07c268
Author: houmkh <100781004+houmkh@users.noreply.github.com>
Date: Mon Mar 6 11:21:29 2023 +0800
修改mmio-buddy代码结构和函数名 (#184)
* 修改mmio-buddy结构和函数名
commit c2481452f81750ec02adec627ab2edbc93d9cd9c
Author: houmkh <100781004+houmkh@users.noreply.github.com>
Date: Sat Mar 4 18:36:55 2023 +0800
rust重构mmio_buddy和mmio (#178)
* rust重构mmio_buddy和mmio
* mmio-buddy文档
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit f1284c35717a2f9f8cee7cecfc835ba1d23a1161
Author: Gou Ngai <sujintao@dragonos.org>
Date: Sat Mar 4 17:47:17 2023 +0800
新增了rust实现的信号量 (#181)
* 新增了rust实现的信号量
---------
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 83b9512c1c1e8289000084adcafddebee6a23f16
Author: Gou Ngai <sujintao@dragonos.org>
Date: Sat Mar 4 16:54:42 2023 +0800
新增了rust实现的信号量 (#183)
* 新增了rust实现的信号量
commit e532a536a0b244f4590e6eb7910084bd63049704
Author: login <longjin@ringotek.cn>
Date: Thu Mar 2 22:50:07 2023 +0800
添加赞助者:FengWangHao (#179)
commit b66beefd4e9ead61ee55f335246ebeb8277d3011
Author: login <longjin@ringotek.cn>
Date: Mon Feb 27 01:00:35 2023 +0800
解决ubuntu2210版本无法正确编译grub,以及正确安装qemu的问题 (#176)
commit 4177d0327c3eacdc606f0b22f99f208fd48cfff3
Author: kong <45937622+kkkkkong@users.noreply.github.com>
Date: Mon Feb 20 17:03:37 2023 +0800
RTQueue改用双向链表存储(#174)
* RTQueue改用双向链表存储
commit 2bf5ee0e3cac3a91dee6a13b71c86a9477c07d9b
Author: login <longjin@ringotek.cn>
Date: Sat Feb 11 13:04:24 2023 +0800
修改libc的编译相关内容(#171)
1.将libc的include文件夹分为export和internal
2.将所有app都直接链接libc.a,而不是都执行一遍"搜索.o"的过程
commit 90b077f9d3ecd48ca46f8bbb32363620db6ddbe6
Author: kong <45937622+kkkkkong@users.noreply.github.com>
Date: Thu Feb 9 15:24:37 2023 +0800
Sched rt doc (#163)
* update
* 完善调度器文档
* 更新RT调度器文档
* 更新实时调度文档
commit 009f92d50fe2e52e425bce397801d3fa204daecd
Author: Satin Wuker <74630829+SatinWuker@users.noreply.github.com>
Date: Tue Feb 7 19:29:09 2023 -0800
fix typos 改正README_EN的错别字和语法错误 (#167)
V0.1.4
贡献者名单
DragonOS V0.1.4版本由以下小伙伴贡献代码:
Gou Ngai sujintao@DragonOS.org
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
David Wen (人民币2000元)
Seele.Clover (人民币500元)
叶锦毅 (人民币100元)
林 (人民币50元)
Albert (人民币9.99元)
TerryLeeSCUT (人民币6.66元)
slientbard (人民币6.66元)
悟 (人民币2.00元)
【其他匿名的热心人士】(人民币1.00元)
更新内容-内核
Spinlock: new: 新增具有守卫的自旋锁SpinLock,支持编译期对锁的使用进行检查。 (#148)
Spinlock: feature: Raw spin lock 增加lock_irqsave、unlock_irqrestore(#151)
Mutex: new: Rust版本的Mutex (#157)
doc: new: Rust代码风格文档 (#161)
WaitQueue: new: Rust版本的WaitQueue (#162)
WaitQueue: update: C版本的wait_queue的唤醒,改为立即唤醒 (#158)
block io: new: Block IO 调度器. 当具有多核时,io调度器在核心1上运行。 (#158)
smp: bugfix: 为AP核启动apic_timer,使其能够运行调度 (#158)
smp: new: 增加kick_cpu功能,支持让某个特定核心立即运行调度器 (#158)
smp: new: 增加进程在核心间迁移的功能 (#158)
scheduler: new: 增加实时进程调度器(支持FIFO、RR策略) (#139)
scheduler: update: CFS调度器为每个核心设置单独的IDLE进程pcb(pid均为0) (#158)
scheduler: bugfix: process_wakeup时,对cfs的进程,重设虚拟运行时间。解决由于休眠的进程,其虚拟运行时间过小,导致其他进程饥饿的问题 (#158)
process: new: pcb中增加migrate_to字段 (#158)
更新内容-用户环境
无
更新内容-其他
无
更新内容-软件移植
无
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/fslongjin/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
国内镜像加速: [https://mirrors.ringotek.cn/] (https://mirrors.ringotek.cn/)
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
rcore-fs - https://github.com/rcore-os/rcore-fs.git - MIT
当前版本的所有提交记录
commit f6ba114bb0420e848ef7fc844c96c0d7a0552d93
Author: houmkh <100781004+houmkh@users.noreply.github.com>
Date: Sat Feb 4 12:31:15 2023 +0800
Block IO Scheduler (#158)
* Block io调度器
* process_wakeup时,对cfs的进程,重设虚拟运行时间。解决由于休眠的进程,其虚拟运行时间过小,导致其他进程饥饿的问题
* 1、为AP核启动apic_timer,使其能够运行调度
2、增加kick_cpu功能,支持让某个特定核心立即运行调度器
3、wait_queue的唤醒,改为立即唤醒。
4、增加进程在核心间迁移的功能
5、CFS调度器为每个核心设置单独的IDLE进程pcb(pid均为0)
6、pcb中增加migrate_to字段
7、当具有多核时,io调度器在核心1上运行。
* io调度器文件位置修改
* 修改io的makefile
* 更新makefile中的变量名
* 修改io调度器函数名
---------
Co-authored-by: login <longjin@ringotek.cn>
commit 151251b50b7ed55596edd32ffec49a4041010e2a
Author: login <longjin@ringotek.cn>
Date: Tue Jan 31 19:27:02 2023 +0800
Patch add rust waitqueue (#162)
* new: rust版本的waitqueue
* new:等待队列的文档
commit 3c369b1430e8d571bcc74a8ef7fefc1c4cae5dd2
Author: login <longjin@ringotek.cn>
Date: Mon Jan 30 15:43:42 2023 +0800
new:新增rust代码风格 (#161)
commit c28bd540ac856cd9d8d5597852af8f2588a660e4
Author: login <longjin@ringotek.cn>
Date: Mon Jan 30 15:10:24 2023 +0800
更新赞助者名单 (#160)
* 更新赞赏者列表
commit 935f40ec174fec217aed4553d45996327443bc0e
Author: login <longjin@ringotek.cn>
Date: Tue Jan 17 21:30:16 2023 +0800
new: Rust版本的Mutex (#157)
commit d8a064128a8a06b90ff4c7b87c193518d9572641
Author: Gou Ngai <94795048+AlbertSanoe@users.noreply.github.com>
Date: Mon Jan 16 19:58:50 2023 +0800
Raw spin lock 增加lock_irqsave、unlock_irqrestore(#151)
Raw spin lock 增加lock_irqsave、unlock_irqrestore
commit 06b09f34ed64a006a80ae8df383e3c8b176f02e0
Author: kong <45937622+kkkkkong@users.noreply.github.com>
Date: Sat Jan 14 22:38:05 2023 +0800
Patch sched rust (#139)
* update
* 添加rt调度器的rust初步实现
* 完善rt调度逻辑
* 调试rt调度器
* 修改sched的返回值
* cargo fmt 格式化
* 删除无用代码,修补rt bug
* 删除无用的代码,和重复的逻辑
* 软中断bugfix
* 删除一些代码
* 添加kthread_run_rt文档
* 解决sphinix警告_static目录不存在的问题
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit ec53d23ed03347854189d92b7e175f309779321b
Author: login <longjin@ringotek.cn>
Date: Sat Jan 14 10:35:49 2023 +0800
new: 新增具有守卫的自旋锁SpinLock,支持编译期对锁的使用进行检查。 (#148)
commit 41474ba3df99b6822ce452dc94dc53a4da62cba1
Author: login <longjin@ringotek.cn>
Date: Tue Jan 10 22:07:41 2023 +0800
更新Readme中关于DragonOS的介绍部分 (#146)
commit 8ad2e358fd3b05eed2919de50640682e51687fb5
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 15:51:59 2023 +0800
更新about app中的版本号 (#145)
* 更新about app中的版本号
commit a8b621c8d1fe77251b8e4eafe258dc0ee7366dd5
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 15:47:44 2023 +0800
修正由于libc中具有crti.S和crtn.S,造成的与x86_64-elf-gcc不兼容的问题 (#144)
commit 9358ff0f6f7daa18d6fab4497de025736b3d6725
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 15:06:52 2023 +0800
Add v0.1.3 changelog (#143)
* new: 0.1.3发行日志
* 新增输出指定时间范围内的贡献者名单的脚本
* 更新bootloader文档
* update: 简介文档
* new: 镜像站文档
* update: 功能特性文档
V0.1.3
贡献者名单
DragonOS V0.1.3版本由以下小伙伴贡献代码:
Gou Ngai sujintao@DragonOS.org
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
David Wen
TerryLeeSCUT
悟
slientbard
更新内容-内核
syscall: new: 增加getpid系统调用 (#120)
signal: update: 对于除了SIGKILL以外的信号,也将他们加入SigQueue (#120)
rtc: refactor: 使用Rust重构RTC驱动 (#118)
doc: new: 新增signal的文档 (#126)
Spinlock: new: 新增rust写的RawSpinlock (#127)
arch: update: 在lib.rs中,将arch模块的路径进行更改,使得其他模块使用arch的代码时,不需要指定arch::x86_64 (#128)
mm: bugfix: 修复页面分配器在初始化时,ZONE_NORMAL_INDEX始终为0的bug (#129)
scheduler: new: 使用Rust重构CFS调度器 (#131)
smp: 删除已经在smp中废弃的HPET中断转发函数 (#131)
process: bugfix: 修复init进程忘记设定fs gs寄存器的问题。 (#132)
vfs: update: 将VFS文件夹重命名为vfs (#133)
lockref: new: 新增rust版本的lockref (#135)
cpu: new: new:Rust封装cpu_relax(),通过pause指令,让cpu休息一会儿,降低空转功耗. (#135)
使用rust重构softirq机制 (#138)
更新内容-用户环境
libc: bugfix: 注册信号处理函数时,总是注册sigkill的问题 (#120)
libc: new: 增加了raise、kill、abort (#120)
libc: new: 新增arch文件夹,在下面新增crt0 crti crtn文件 (#134)
libc: new: 新增
fflush()
,fprintf()
,stdin
,stdout
,stderr
,ferror()
,fopen()
,fclose()
,putchar()
,puts()
(#136)libc: new: 简单添加了
fopen()
对mode参数的处理。请注意,它没有完全遵循posix,也与Linux的不一致,将来使用Rust的时候完善它。 (#141)移植: new: 新增了gmp, mpfr, mpc的移植构建脚本 (#136)
移植: new: 新增了gcc、binutils的交叉编译构建脚本以及gcc-11.3.0, binutils-2.38的补丁(在DragonOS-community下的仓库中)(#136)
compile: update: 更改编译器的Include路径,使得include时不需要加
<libc/src/include/>
前缀 (#124)
更新内容-其他
bugfix: 修复docker安装时异常退出的bug (#116)
new: 新增目标为x86_64-elf的GCC裸机编译器,并使用它来编译DragonOS (#111)
update: 更新Docker编译镜像至版本
dragonos/dragonos-dev:v1.2
, 并支持从Dockerfile构建这个编译镜像 (#111)bugfix: 修复MBR磁盘镜像未设置启动标志的bug (#111)
update: 更新github workflow,增加cache,加快build check的速度
bugfix: 修复下载grub2.06时的提示错误 (#125)
更新内容-软件移植
new: gcc 11.3.0 userland cross-compiler, 提交:
64a5b1cbf28e3305560e166c1b6624e99745c720
, 仓库:https://github.com/DragonOS-Community/gccnew: binutils 2.38 cross-compile tools, 提交:
a0ae560e0065862a9867b9e1f8364749ef38d99e
, 仓库:https://github.com/DragonOS-Community/binutilsnew: gmp 6.2.1, 提交:
dd9eee5778fb6027fafa4fe850aff21b1a71c18e
, 仓库:https://github.com/DragonOS-Community/gmp-6.2.1new: mpfr 4.1.1, 提交:
fa8e30cdc2e838fdd82b60fec31fcfc5e118aad6
, 仓库:https://github.com/DragonOS-Community/mpfrnew: mpc 1.2.1, (无需打补丁即可移植),仓库:https://github.com/DragonOS-Community/mpc
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/fslongjin/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
国内镜像加速: [https://mirrors.ringotek.cn/] (https://mirrors.ringotek.cn/)
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
rcore-fs - https://github.com/rcore-os/rcore-fs.git - MIT
当前版本的所有提交记录
commit a8b621c8d1fe77251b8e4eafe258dc0ee7366dd5
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 15:47:44 2023 +0800
修正由于libc中具有crti.S和crtn.S,造成的与x86_64-elf-gcc不兼容的问题 (#144)
commit 9358ff0f6f7daa18d6fab4497de025736b3d6725
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 15:06:52 2023 +0800
Add v0.1.3 changelog (#143)
* new: 0.1.3发行日志
* 新增输出指定时间范围内的贡献者名单的脚本
* 更新bootloader文档
* update: 简介文档
* new: 镜像站文档
* update: 功能特性文档
commit fd91905f022b3ceaa59e666d1ff42d91fb8d40ef
Author: login <longjin@ringotek.cn>
Date: Sun Jan 8 11:38:59 2023 +0800
解决编译gcc、binutils的脚本中,变量名称错误的问题 (#142)
commit 62e4613978193aaf5d949a331df0398f2d085a30
Author: Gou Ngai <94795048+AlbertSanoe@users.noreply.github.com>
Date: Sat Jan 7 23:15:37 2023 +0800
使用rust重构softirq机制;解决Rtc驱动的编译警告问题 (#138)
* 使用rust重构softirq机制
* 解决Rtc驱动的编译警告问题
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit e9fdc57bf878f1bc5cc5743dfaeeaef743439291
Author: login <longjin@ringotek.cn>
Date: Sat Jan 7 22:36:49 2023 +0800
简单添加了fopen对mode参数的处理。请注意,它没有完全遵循posix,也与Linux的不一致,将来使用Rust的时候完善它。 (#141)
commit 2224c93ea968bc74621f7e124b4aca04875b3e6a
Author: guanjinquan <1666320330@qq.com>
Date: Fri Jan 6 21:29:23 2023 +0800
完善libc,构建了OS-specific工具链,编译了基于gcc-11.3.0的DragonOS userland compiler,移植了mpfr,gmp,mpc库 (#134)
* 修改include路径
* 添加了创建libsysapi.a和/bin/sysroot/usr/include/+lib/的代码
* 修补.gitignore
* 删除多余项
* 优化脚本可读性
* 新增crt0 crti crtn
* 编译binutils所需的东西
* fflush()和fprintf()的简单实现
* 应用程序启动前,调用初始化libc的函数
* 自动创建sysroot
* 添加了stderr的初始化
* 修改了stderr的初始化
* 内核添加对stdio的简略处理
* 格式化代码
* 修正打开stdio文件描述符的问题
* bugfix: 修复fprintf忘记释放buf的问题
* 修复shell错误地把入口设置为main而不是_start的问题
* 新增__cxa_atexit (gcc要求libc提供这个)
* 增加putchar puts
* 更新写入磁盘镜像的脚本,默认无参数时,使用legacy方式安装
* 更新编译脚本
* stdio增加eof的定义
* 新增extern cplusplus
* mpfr gmp mpc 构建脚本
* 更新libsysapi.a为libc.a
* 加上ferror fopen fclose
* 更新移植的软件的构建脚本
* 更改build_gcc_toolchain.sh中的-save参数名为-save-cache
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 61de2cdc3f29cdc6c441f128119e01e003e6f3ca
Author: login <longjin@ringotek.cn>
Date: Tue Jan 3 23:09:25 2023 +0800
新增rust版本的lockref (#135)
* new:Rust封装cpu_relax(),通过pause指令,让cpu休息一会儿。降低空转功耗
* new: Rust版本的lockref
* Rust的RawSpinlock新增is_locked()和set_value()方法。
* lockref文档
commit 2726f101b4cc787bbd36a69afffb0112f3a6567f
Author: login <longjin@ringotek.cn>
Date: Tue Jan 3 21:01:56 2023 +0800
删除无用的cfs.h (#136)
commit 587086d3f299f7394559d547c828191be20cfc11
Author: login <longjin@ringotek.cn>
Date: Sun Jan 1 16:53:57 2023 +0800
1、在文件系统目录下增加mod.rs 2、将VFS的路径改为vfs(#133)
2、将VFS的路径改为vfs
commit 843e442971a47693f37a5f8d3452c383f7325359
Author: login <longjin@ringotek.cn>
Date: Sat Dec 31 18:43:05 2022 +0800
修复init进程忘记设定fs gs寄存器的问题。 (#132)
commit 74bde36e014ff501241bf40dd83653db47a2c8e4
Author: guanjinquan <1666320330@qq.com>
Date: Sat Dec 31 17:35:39 2022 +0800
Patch porting gcc v2 (#124)
* 更改编译器的Include路径,使得include时不需要加`<libc/src/include/>`前缀
* 修改include路径
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit d4f3de93a23e4bd4f000a3663768d47d094bf188
Author: login <longjin@ringotek.cn>
Date: Sat Dec 31 17:26:12 2022 +0800
使用Rust重构CFS调度器 (#131)
* 新建调度器的文件
* 把softirq vector移动到c文件中(原来在.h)
* 将进程切换方式改为“中断返回时切换”
* new:使用rust重构CFS
* 删除已经在smp中废弃的HPET中断转发函数
* 代码格式化
* 删除多余的dunce依赖
commit 156949680c83f2d7e3b21ed68b11698b88eaf396
Author: login <longjin@ringotek.cn>
Date: Sat Dec 31 13:47:49 2022 +0800
bugfix:修复当使用sched()运行调度器,在切换进程的时候,由于不在中断上下文内,导致当前进程的上下文丢失的问题。 (#130)
bugfix:修复当使用sched()运行调度器,在切换进程的时候,由于不在中断上下文内,导致当前进程的上下文丢失的问题。
bugfix:修复切换进程的宏的汇编代码的损坏部分,未声明rax寄存器,从而导致的编译器未定义行为问题。
commit 882f0b7e7498dbff8de527b2b9159b7f6e6359c9
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Wed Dec 28 19:35:17 2022 +0800
修复内存bug与grub安装脚本的错误 (#129)
* 修复内存bug与grub安装脚本的错误
* 修改小bug
commit adc1846b06fb862caed049f435fc0061488a6ff9
Author: login <longjin@ringotek.cn>
Date: Mon Dec 26 13:13:12 2022 +0800
内核:在lib.rs中,将arch模块的路径进行更改,使得其他模块使用arch的代码时,不需要指定arch::x86_64 (#128)
commit ac643d420b22f9d454ecefccd51ed34a9664586b
Author: login <longjin@ringotek.cn>
Date: Sun Dec 25 23:53:35 2022 +0800
new:新增rust写的RawSpinlock (#127)
commit 998390210549b47e6bdcc3fdab49eff4086ad18b
Author: login <longjin@ringotek.cn>
Date: Sat Dec 24 23:30:26 2022 +0800
新增signal文档 (#126)
* 新增signal文档
commit a7f5ca7b67160557abf84a1169dd60093220aeb0
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Sat Dec 24 23:29:36 2022 +0800
修复下载grub2.06时的提示错误 (#125)
* 修复grub下载显示提示显示错误
commit 82762007da41148e1ed1df465211eb5c8ba2c15e
Author: login <longjin@ringotek.cn>
Date: Fri Dec 23 18:11:47 2022 +0800
Update makefile.yml
commit b975025ec8854ca232152f4ee44cc2226891a34c
Author: login <longjin@ringotek.cn>
Date: Fri Dec 23 11:45:19 2022 +0800
Update makefile.yml
commit ad2bb74d949bfcb2935e43ac7b261d7ecce23389
Author: login <longjin@ringotek.cn>
Date: Fri Dec 23 11:21:22 2022 +0800
Update makefile.yml
commit 6b7776d189ab5f19fbab20d6c5c9ed3ab20c7ab6
Author: login <longjin@ringotek.cn>
Date: Fri Dec 23 10:59:15 2022 +0800
修正smp的makefile中没有替换AS的问题
commit beb12a188b6c6bc4196796ac2ae1ecd7d8ed8223
Author: login <longjin@ringotek.cn>
Date: Fri Dec 23 10:57:39 2022 +0800
Update makefile.yml
commit d65c527730e5c8a75f6dad0f996c093040699ee3
Author: login <longjin@ringotek.cn>
Date: Thu Dec 22 22:58:28 2022 +0800
Update makefile.yml (#121)
commit 5ed4cd460200cb19aae8c3c67dfd77e1e9f0e105
Author: guanjinquan <75822481+guanjinquan@users.noreply.github.com>
Date: Thu Dec 22 21:09:12 2022 +0800
Patch gcc toolchain (#111)
* 添加了GCC_cross_compile——tool_chain
* - 解决环境变量路径拼接时,多了`/`的问题
- apt安装时增加-y,不需用户确认
* 解决添加环境变量的命令有误的问题
* 修正编译错误时,还会执行下一步的问题
* new: 编译完成后清理临时文件
* 更新makefile
* 调整:把grub安装在 $HOME/opt/dragonos-grub下
* new: 新增dockerfile
* 将镜像源换成中科大的(原因是清华的总是ban掉用于构建镜像的服务器的ip)
* 修改为基于debian bullseye构建
* 取消指定版本
* 修复MBR磁盘镜像未设置启动标志的bug
* 取消在docker中安装grub
* 安装grub的过程改到客户机上进行
* bootstrap.sh 添加--no-docker
* 使用新版的docker编译镜像
* 修补, 添加了一些关于gcc的check
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit ba0d93d8b26034abc54bcaf3f0ff04863bbd076e
Author: Gou Ngai <94795048+AlbertSanoe@users.noreply.github.com>
Date: Mon Dec 19 15:04:37 2022 +0800
refactor rtc module in rust (#118)
* 用rust重构rtc模块
* refactor the rtc module by rust
* rtc-updated
* rtc-updated-4
* rtc
commit c588d6f77f4b38939701b946228218ea81a7c8dc
Author: login <longjin@ringotek.cn>
Date: Mon Dec 19 15:03:44 2022 +0800
Patch add abort func (#120)
* 对于除了sigkill以外的信号,也加入队列
* bugfix:libc中,注册信号处理函数时,总是注册sigkill的问题
* 增加getpid系统调用
* 增加了raise、kill、abort
commit 47f0d12a1f1a1aa11be8e751ecdbf76f0cb596d9
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Mon Dec 19 14:53:51 2022 +0800
修复docker安装时异常退出的bug (#119)
* 修复docker安装时异常退出的bug
* 修复grub编译脚本的小bug
commit 978043e47d1143ca2d5cf22b20793f032e8eb5a5
Author: login <longjin@ringotek.cn>
Date: Sun Dec 18 15:09:15 2022 +0800
修复当系统中不存在dosfstools时,无法正确格式化磁盘镜像的问题 (#117)
* 修复当系统中不存在dosfstools时,无法正确格式化磁盘镜像的问题
commit f9127772dc372a2e607388fdd6818d3f9c4c6d28
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Sat Dec 17 23:43:23 2022 +0800
修复docker安装时异常退出的bug (#116)
V0.1.2
贡献者名单
DragonOS V0.1.2版本由以下小伙伴贡献代码:
Gou Ngai sujintao@DragonOS.org
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
David Wen
TerryLeeSCUT
悟
slientbard
其中,非常感谢Seele.Clover给予DragonOS项目人民币500元的赞助与支持!我们对于每一笔赞助款项,将仔细登记,并确保其能被妥善的使用。
更新内容-内核
删除rust_helloworld文件 (#113)
Signal: 允许用户注册信号处理函数,能够进入自定义的handler。(#112)
支持kill命令
允许用户自定义信号处理函数
新增2个系统调用:
SYS_SIGACTION
,SYS_RT_SIGRETURN
libc增加
signal()
,sigaction()
函数。暂时只支持旧版的sighandler,即:只有1个参数的
void handler(int signum)
类型的信号处理函数。对于另一种信号处理函数void handler(int signum, siginfo_t *info, void* data)
,尚不支持传递第三个参数。
在内核代码中加入自定义的stdint.h文件 (#109)
调整编译grub的脚本的部分 (#108)
新增32、64位uefi启动 (#105)(#101)
使用编译安装的grub-2.06,解决客户机上grub版本不对导致的编译无法运行的问题。
增加了timekeeping模块 (#106)
bugfix: 修复rtc时钟对BCD码进行转换的时候,忘了处理day字段的问题 (#104)
new: 开发过程文档(完成了一半)
bootstrap.sh解决下载rust慢的问题
更新“构建系统”文档
procfs->status增加显示preempt和虚拟运行时间 (#100)
ffz函数:获取u64中的第一个值为0的bit (#100)
解决由于编译器优化导致local_irq_restore无法获取到正确的rflags的值的问题
使用Rust重构串口驱动 (#99)
更新内容-用户环境
about app: 显示当前构建的git commit sha1以及构建时间(#114)
shell: 修复shell的exec命令对绝对路径的拼接错误问题(#114)
shell: exec命令增加”&”后台运行选项 (#100)
new: 测试signal用的app
将libc目录进行调整,加入cargo作为rust的包管理器
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/fslongjin/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
当前版本的所有提交记录
commit 7a818da88a1c7a1760de7671141b0ce1ca4e3dde
Author: login <longjin@ringotek.cn>
Date: Sat Dec 17 17:49:12 2022 +0800
Patch about auto gen version string (#114)
* new: about app中,显示当前构建的git commit sha1以及构建时间
* bugfix: 修复shell的exec命令对绝对路径的拼接错误问题
commit 83a7aaa46bbc411c43d4fc099c6c8884efbe4771
Author: login <longjin@ringotek.cn>
Date: Sat Dec 17 16:31:50 2022 +0800
删除rust_helloworld文件 (#113)
commit 6efd4740336205c9bfdd8b164e667cee2f38781e
Author: login <longjin@ringotek.cn>
Date: Sat Dec 17 16:27:50 2022 +0800
允许用户自定义信号处理函数 (#112)
* new: 用户注册信号处理函数,能够进入自定义的handler
* 修复忘了传信号的数字给用户的处理函数的bug
* new:sigreturn
* 删除注释
commit 0e0c187484281768391e131495f0655e40d70cf7
Author: login <longjin@ringotek.cn>
Date: Fri Dec 16 16:20:09 2022 +0800
在内核代码中加入自定义的stdint.h文件 (#109)
commit d02e6ea4112ad520aa4090ff73cdf592e14c0a82
Author: login <longjin@ringotek.cn>
Date: Wed Dec 14 20:01:55 2022 +0800
调整编译grub的脚本的部分 (#108)
1、bugfix: 修复编译grub的脚本的部分错误
2、将grub下载源替换为tuna
3、优化写入磁盘镜像的脚本
4、将bios文件夹改名为legacy
commit 38b341b8aa671f75ac26d05059aa2e9a09e653b7
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Wed Dec 14 16:58:49 2022 +0800
新增32位uefi启动 (#105)
* 新增32位uefi启动
* 修复小bug
* 增加grub本地编译安装
* 增加本地grub编译安装脚本
* 修正小错误
* 修复空文件夹不上传的bug
commit 01876902fbf6ed43992cc7d153bd8c505cb5224b
Author: Gou Ngai <94795048+AlbertSanoe@users.noreply.github.com>
Date: Wed Dec 14 15:13:54 2022 +0800
增加了timekeeping模块 (#106)
* 增加了timekeeping模块
* 格式化文档和细节更改
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 728aca308917a7d4d0ba10fe8174e9408d77a9a6
Author: login <longjin@ringotek.cn>
Date: Sun Dec 11 22:59:47 2022 +0800
bugfix: 修复rtc时钟对BCD码进行转换的时候,忘了处理day字段的问题 (#104)
commit 237e95c6ddce72d72ae7fedfeca412fab82b3622
Author: wwc-15172310230 <78997674+wwc-15172310230@users.noreply.github.com>
Date: Sun Dec 11 22:22:10 2022 +0800
调整user下libs的libc目录结构 (#103)
* 调整user下libs的libc目录结构
* 修正.gitignore文件的问题
* 修复无法编译的问题
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 2291ffdece1dc5a703602f79f74df8a4854d215b
Author: login <longjin@ringotek.cn>
Date: Sun Dec 11 20:09:58 2022 +0800
文档更新 (#102)
* new: 开发过程文档(完成了一半)
* bootstrap.sh解决下载rust慢的问题
* 更新“构建系统”文档
commit 7f439c5ddbd2ecffc112149d16983975f523052c
Author: YJwu2023 <119829947+YJwu2023@users.noreply.github.com>
Date: Fri Dec 9 16:08:54 2022 +0800
增加uefi启动 (#101)
* 增加uefi启动
* 修改脚本
* uefi修改
* 删除错误的注释
* 修正写入磁盘镜像的脚本
* 修改X86_64为x86_64
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 1a2eaa402f05f82aaeebe1e03824534a0a425d4d
Author: login <longjin@ringotek.cn>
Date: Thu Dec 8 22:59:51 2022 +0800
signal的处理(kill命令)以及一些其他的改进 (#100)
* 将entry.S中冗余的ret_from_syscall代码删除,改为jmp Restore_all
* new: 增加判断pt_regs是否来自用户态的函数
* new: rust的cli和sti封装
* 将原有的判断pt_regs是否来自用户态的代码,统一改为调用user_mode函数
* ffz函数:获取u64中的第一个值为0的bit
* spinlock增加 spinlock irq spin_unlock_irq
* 临时解决显示刷新线程迟迟不运行的问题
* 更改ffi_convert的生命周期标签
* new: 测试signal用的app
* 解决由于编译器优化导致local_irq_restore无法获取到正确的rflags的值的问题
* new: exec命令增加"&"后台运行选项
* procfs->status增加显示preempt和虚拟运行时间
* 更改引用计数的FFIBind2Rust trait中的生命周期标签
* new: signal处理(kill)
* 更正在review中发现的一些细节问题
commit f8b55f6d3fcbf152a1cb6d6fc722bf1607418b28
Author: TingHuang <92705854+TingSHub@users.noreply.github.com>
Date: Tue Dec 6 22:15:03 2022 +0800
Patch uart (#99)
* 添加UART驱动相关文件
* 添加驱动核心文件,将rust编写的驱动代码加入Package中
* 添加glib.h文件生成rust代码,添加uart驱动代码
* 添加串口发送及接收相关代码
* 添加字符串发送函数,未实现具体功能
* 为调用uart驱动的代码添加rust接口
* 添加字符串发送函数,修改C语言调用接口
* 添加rust串口驱动
* 添加uart.h头文件,将串口端口类型改为enum
* 添加注释,规范代码
commit 036acc52ce9d0fb9e7d92768ff74939a29c07f32
Author: login <longjin@ringotek.cn>
Date: Tue Nov 29 21:46:13 2022 +0800
将entry.S中冗余的ret_from_syscall代码删除,改为jmp Restore_all (#98)
* 将entry.S中冗余的ret_from_syscall代码删除,改为jmp Restore_all
V0.1.1
贡献者名单
DragonOS V0.1.1版本由以下小伙伴贡献代码:
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
David Wen
TerryLeeSCUT
悟
slientbard
其中,非常感谢David Wen给予DragonOS项目人民币1000元的赞助与支持!我们对于每一笔赞助款项,将仔细登记,并确保其能被妥善的使用。
更新内容-内核
新增rust ffi (#77)
port kmalloc and printk to rust
rust下的kdebug kinfo kwarn kBUG kerror宏
bugfix: 修复进程pcb被回收时,未将其从链表中删除的问题
目录结构优化:移动asm.h和cmpxchg.h
signal的发送
procfs:查看进程的status
解决第一次编译时磁盘镜像权限错误的问题
将fork相关代码移动到fork.c
更新内容-用户环境
shell:增加kill命令,可向目标进程发送信号。但由于仍未完善signal机制,因此目标进程暂时不能响应这个信号。
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/fslongjin/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT
当前版本的所有提交记录
commit d65ade9c5909076747bd00966a398fe27fbd290d
Author: DaJiYuQia <88259094+DaJiYuQia@users.noreply.github.com>
Date: Sun Nov 27 14:21:31 2022 +0800
Patch procf (#95)
* debug color problem
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit 6cb769c423b09e88fea1763210200a716477be0a
Author: login <longjin@ringotek.cn>
Date: Sun Nov 27 14:17:36 2022 +0800
将include目录下的rust代码转移到他们应当属于的模块中 (#96)
* 将include目录下的rust代码转移到他们应当属于的模块下。
commit 27a97abd2474b03ad09b562e5ed11e1fdae8eb32
Author: DaJiYuQia <88259094+DaJiYuQia@users.noreply.github.com>
Date: Sat Nov 26 17:34:00 2022 +0800
Patch procf (#90)
* 1234
* 123
* 合并master
* procfs
* 1
* procfs展示进程基本信息
* modified code
* 恢复权限
* 恢复权限
#恢复权限
* modify permission
* 删除run.sh
* 解决第一次编译时磁盘镜像权限错误的问题
* 恢复.vscode/c_cpp_properties.json
* 删除process.c中错误的do_fork
* remake procfs
* 修改一些变量名
* 修改类型
* modified
* data_puts缓冲区溢出后return
Co-authored-by: longjin <longjin@RinGoTek.cn>
commit ad23fcddf893d7f92d2bf3efdb66e969416d2852
Author: login <longjin@ringotek.cn>
Date: Wed Nov 23 21:34:35 2022 +0800
bugfix: 修复进程退出时未释放signal和sighand && 增加赞赏者名单:David Wen (#93)
* bugfix: 修复进程退出时未释放signal和sighand的bug
* 增加赞赏者名单:David Wen
commit 0274cd6eeec01885232e7418a501857cb76da69e
Author: login <longjin@ringotek.cn>
Date: Wed Nov 23 20:43:18 2022 +0800
修正drop signal结构体的box对象的的问题 (#92)
* fix: exit signal and exit sighand
commit c8025a88798dc57ecc5d7f20ad69de695445638f
Author: login <longjin@ringotek.cn>
Date: Wed Nov 23 20:18:22 2022 +0800
new:在fork时拷贝signal和sighand (#91)
* refcount初始化
* new: 实现copy_sighand
del: 删除sighand_struct的wqh, 待将来有需要时,替换成rust版本的
* new: 拷贝signal
bugfix: 解决拷贝sighand时的uaf问题
commit 66f67c6a95b8aad85cfd2146a86e5e3e6a3568e7
Author: login <longjin@ringotek.cn>
Date: Wed Nov 23 11:38:20 2022 +0800
signal的发送(暂时父子进程之间共享信号及相应的结构体) (#89)
* 解决由于spinlock.h中包含preempt_enable()带来的循环include问题
* new: 初步实现signal的数据结构
* new:signal相关数据结构
* fix: 解决bindings.rs报一堆警告的问题
* new: rust下的kdebug kinfo kwarn kBUG kerror宏
* 移动asm.h和cmpxchg.h
* new: signal的发送(暂时只支持父子进程共享信号及处理函数)
commit 3d729e2069e01ee07525ff83167566dac5322a40
Author: login <longjin@ringotek.cn>
Date: Fri Nov 18 17:59:33 2022 +0800
bugfix: 修复进程pcb被回收时,未将其从链表中删除的问题 (#87)
* bugfix: 修复进程pcb被回收时,未将其从链表中删除的问题
new: pcb相关api文档
* 将文档加入目录
commit 0bfe94f46be9bdde1ade81a20e803aa2aafd2964
Author: login <longjin@ringotek.cn>
Date: Fri Nov 18 16:32:15 2022 +0800
new: rust下的kdebug kinfo kwarn kBUG kerror宏 (#86)
* new: rust下的kdebug kinfo kwarn kBUG kerror宏
commit c6174797dcf3427f38bfa0f4bd3e039c319f7c5b
Author: login <longjin@ringotek.cn>
Date: Thu Nov 17 20:29:29 2022 +0800
fix: 解决bindings.rs报了很多警告的问题 (#85)
* fix: 解决bindings.rs报一堆警告的问题
commit cffd7144fbed84f9775e89d7b99602c6ccc5a510
Author: login <longjin@ringotek.cn>
Date: Wed Nov 16 15:18:03 2022 +0800
signal相关数据结构&代码结构优化 (#84)
* 解决由于spinlock.h中包含preempt_enable()带来的循环include问题
* new: 初步实现signal的数据结构
commit fb6c29d01d4cf92368efec08c01e419c2a941f7d
Author: login <longjin@ringotek.cn>
Date: Sun Nov 13 16:43:58 2022 +0800
port kmalloc and printk to rust (#83)
* 暂时移除cbindgen
* 将lib文件夹更名为libs文件夹(解决rust的冲突)
* 实现了全局的allocator
* 实现了printk宏
* new: 完善了printk的颜色
commit 82d2e446a401e7eee57a847f48a6d162931170c3
Author: login <longjin@ringotek.cn>
Date: Sat Nov 12 15:25:54 2022 +0800
new: 暂时移除cbindgen (#82)
commit 2aaf7808efe44ecfaadd51ae4f8892e667108578
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 22:21:44 2022 +0800
在内核中引入cbindgen,生成rust-C的FFI (#81)
* 解决codeql失败问题
* new: 为内核引入cbindgen
commit 2813126e3190c9b3c1a836a647b259a7adbe0cf3
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:35:37 2022 +0800
新增rust ffi (#77)
* 引入cargo
* 取消对Cargo.lock的跟踪
* 解决vscode报错问题
* new: rust的代码能够调用c语言的printk_color
* 1、将原本run.sh的工作拆解,变为几个不同的make命令
2、在docker镜像中编译rust
* 更改workflow
* update workflow
* new: 解决workflow无法通过编译的问题
commit 5e023cf7911333eb05bfe65704dce4b01fa4d0a7
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:21:45 2022 +0800
Update makefile.yml
commit e44795008f7e34d2068cf28dcedbcb91f5ccd66b
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:18:13 2022 +0800
Update makefile.yml (#80)
commit ec5fb84b61c313824cc2199ab64e3af4b7e5f895
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:08:09 2022 +0800
Update makefile.yml
commit 6d9dff5f1ff347ea780a0249e54eef356cdcaaea
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:07:48 2022 +0800
Revert "Update makefile.yml (#78)" (#79)
This reverts commit badc7d238f2341e844a90be3e357e5dd77a447fc.
commit badc7d238f2341e844a90be3e357e5dd77a447fc
Author: login <longjin@ringotek.cn>
Date: Fri Nov 11 15:05:52 2022 +0800
Update makefile.yml (#78)
V0.1.0
前言
DragonOS从2022年1月15日开始开发,到如今已经经历了将近300天。在这么多个日夜里,已经数不清到底花了多少时间在DragonOS的开发之中, 我基本上把所有的空闲时间都给了DragonOS,保守估计总工时已经在1000小时以上。能够发布第一个版本,我感到十分有成就感。
在2022年7月以来,陆陆续续的,有来自6所高校或企业的小伙伴/大佬加入了DragonOS的开发。我当时非常的欣喜,我想,也许在大家的一同努力下,我们能创造出 一个真正具有实用性的操作系统呢!我们累计召开了14次交流讨论会。我相信,在大家的共同努力下,将来,我们一定能创造出真正独立自主的、开放的、面向服务器领域应用的开源操作系统,并在生产环境中得到应用。
尽管DragonOS目前只是一个玩具水平的操作系统,只是“比本科生毕业设计难度略高的”操作系统。但是,请不要小看它,它的内在的架构设计,瞄准了Linux5.18及以后的发行版, 虽尚未能达到Linux的水平,但我们正努力追赶。得益于Linux的相关资料,DragonOS在架构设计之时,学习了Linux的很多设计思想,相关组件都尽量考虑了可扩展性与可移植性。
千里之行,始于足下。DragonOS V0.1.0版本的发布,是一个全新的开始。希望在未来的十年里,我们能与众多伙伴们一同努力,在2032年,将DragonOS建设成为具有实用意义的,能够在服务器领域取得广泛应用的开源操作系统!
百舸争流,奋楫者先;中流击水,勇进者胜。 我相信,在接下来的时间里,在社区开发者们的不断努力下,我们的目标,终将成为现实!
特别鸣谢
在DragonOS V0.1.0版本的发布之际,我想对我的老师、前辈以及学校表示衷心的感谢!
佛山市南海区大沥镇中心小学 姚志城老师: 您是带领我接触计算机,学会编程的领路人。十年前,与您交谈时,您说过:“我们国家目前还没有自主的、成熟的操作系统”。这句话,为我的梦想埋下了种子。您培养了我对计算机的热爱,因此我选择了软件工程这个专业。感谢当年您的教导,师恩难忘!
佛山市南海区石门实验学校: 在石实就读的三年里,非常感谢石实的“扬长教育”理念,在老师们的培养下,让我充分发挥了自己的个性和特长,也取得了不错的成绩。在石实的三年里,我学会了C++、Java以及简单的算法,也自己开发了几个安卓app,积累了将近6千行的代码量。
佛山市南海区石门中学:“任重道远,毋忘奋斗”是石中的校训,我想,这句校训,也应当成为我们每个新时代青年人的座右铭。在石门中学的三年,家国情怀教育对我产生了很大的影响。我想,我们作为新时代的青年,更应当肩负起时代的重任,奋勇拼搏,为祖国的发展,为民族的自强,为人类的未来,努力奋斗!
华南理工大学:“博学慎思,明辨笃行”,在华工,我得到了进一步的学习与发展。开拓了自己的视野,学会了跟很多人打交道。并且,在软件学院,我遇到了一群认真负责的老师。非常感谢学院对我的支持,支持我们成立项目组。我相信,在学院的支持下,能让DragonOS取得更好的发展,走的更远!
华南理工大学软件学院 王国华老师:王老师是我《操作系统》课程的老师,在她的指导下,我对操作系统的原理有了更深的理解,并参加了“泛珠三角+大学生计算机作品赛“,在2022年6月的广东省选拔赛中,DragonOS取得了一等奖、最佳创新奖的好成绩。
华南理工大学软件学院 汤峰老师: 汤老师是我们在校内的项目组的指导老师。在她的悉心指导下,我们将不断前行,保持我们前进的方向,持续构建开源社区。我由衷地感谢汤老师的指导!
Yaotian Feng: 在Bilibili上认识了这位非常厉害的老哥,解答了我的很多问题,很多次在我毫无头绪的debug了几天之后,几句话点醒了我,让我找到解决问题的路径。并且,他也跟我分享了容易踩坑的地方,让我在将要踩坑的时候能够有个心理预期,不至于那么难受哈哈哈哈。
贡献者名单
DragonOS V0.1.0版本的发布,离不开以下小伙伴们的共同努力:
zzy666-hw zzy666@mail.ustc.edu.cn
kkkkkong kongweichao@DragonOS.org
houmkh jiaying.hou@qq.com
wang904 1234366@qq.com
Liric Mechan i@liric.cn
Mustang handsomepd@qq.com
Eugene caima12138@foxmail.com
zhujikuan 1335289286@qq.com
Alloc Alice 1548742234@qq.com
赞助者名单
感谢以下同学的赞赏,我们将不断努力!
TerryLeeSCUT
悟
slientbard
内核
遵循的一些标准规范
启动引导:Multiboot2
系统接口:posix 2008
硬件架构
目前支持在x86-64架构的处理器上运行
Bootloader
使用Grub 2.06作为bootloader
内存管理
实现了基于bitmap的页分配器
实现了slab分配器,用来分配小块的、具有对齐要求的内存
抽象出VMA(虚拟内存区域)
实现VMA反向映射机制
实现MMIO地址空间自动映射机制
多核
支持多核引导。也就是说,在DragonOS启动后,将会启动AP处理器。但是,为了简化其他内核模块的实现,目前AP处理器上,暂时没有任务在运行。
粗略实现了IPI(处理器核间通信)框架
进程管理
支持进程的创建、回收
内核线程
kthread机制
用户态、内核态进程/线程的fork/vfork(注意,用户态的fork和内核态的有一定的区别,内核态的fork更复杂)
exec 让进程去执行一个新的可执行文件
进程的定时睡眠(sleep)(支持spin/rdtsc高精度睡眠、支持上下文切换方式的睡眠)
同步原语
spinlock 自旋锁
mutex 互斥量
atomic 原子变量
wait_queue 等待队列
semaphore 信号量
调度相关
CFS调度器
单核调度(暂时不支持多核负载均衡)
completion “完成”机制,让一个进程能等待某个任务的完成。
IPC进程间通信
匿名管道
文件系统
VFS虚拟文件系统的基本功能
FAT32文件系统(尚不支持删除文件夹)
devfs设备文件系统。目前只将键盘文件注册到其中。
rootfs根文件系统,在真实的磁盘文件系统被挂载前,为其他的伪文件系统提供支持。
挂载点抽象。目前实现了文件系统的挂载,使用类似于栈的方式管理所有的挂载点。(将来需要优化这部分)
异常及中断处理
处理器异常的捕获
对APIC的支持
softirq软中断机制
能够对内核栈进行traceback
内核数据结构
普通的二叉树
kfifo先进先出缓冲区
循环链表
IDR 映射数据结构
IDA ID分配数据组件
屏幕显示
VESA VBE显示芯片驱动
实现了屏幕管理器,支持多个显示框架注册到屏幕管理器中。
实现了TextUI文本界面框架,能够渲染文本到屏幕上。并且预留了上下滚动翻页、多显示窗口的支持。
printk
内核实用库
字符串操作库
ELF可执行文件支持组件
基础数学库
CRC函数库
软件移植
移植了LZ4压缩库(V1.9.3),为将来实现页面压缩机制打下基础。
内核测试
ktest单元测试框架
支持使用串口(COM1)输出屏幕内容到文件之中。
驱动程序支持
IDE硬盘
AHCI硬盘(SATA Native)
ACPI 高级电源配置模块
PCI总线驱动
XHCI主机控制器驱动(usb3.0)
ps/2键盘
ps/2鼠标
HPET高精度定时器
RTC时钟
local APIC定时器
UART串口(支持RS-232)
VBE显示
虚拟tty设备
系统调用
DragonOS目前一共有22个有效的系统调用。
SYS_PUT_STRING 往屏幕上打印字符
SYS_OPEN 打开文件
SYS_CLOSE 关闭文件
SYS_READ 读取文件
SYS_WRITE 写入文件
SYS_LSEEK 调整文件指针
SYS_FORK fork系统调用
SYS_VFORK vfork系统调用
SYS_BRK 调整堆大小为指定值
SYS_SBRK 调整堆大小为相对值
SYS_REBOOT 重启 (将来sysfs完善后,将删除这个系统调用,请勿过度依赖这个系统调用)
SYS_CHDIR 切换进程的工作目录
SYS_GET_DENTS 获取目录中的目录项的元数据
SYS_EXECVE 让当前进程执行新的程序文件
SYS_WAIT4 等待进程退出
SYS_EXIT 退出当前进程
SYS_MKDIR 创建文件夹
SYS_NANOSLEEP 纳秒级睡眠(最长1秒)在小于500ns时,能够进行高精度睡眠
SYS_CLOCK 获取当前cpu时间
SYS_PIPE 创建管道
SYS_MSTAT 获取系统当前的内存状态信息
SYS_UNLINK_AT 删除文件夹或删除文件链接
Rust支持
实现了一个简单的rust语言的hello world,计划在接下来的版本中,逐步转向使用rust进行开发。
用户环境
LibC
LibC是应用程序与操作系统交互的纽带。DragonOS的LibC实现了一些简单的功能。
malloc堆内存分配器
基础数学库
简单的几个与文件相关的函数
pipe
fork/vfork
clock
sleep
printf
Shell命令行程序
基于简单的字符串匹配的解析(不是通过编译课程学的的那一套东西做的,因此比较简单,粗暴)
支持的命令:ls,cd,mkdir,exec,about,rmdir,rm,cat,touch,reboot
用户态驱动程序
用户态键盘驱动程序
源码、发布版镜像下载
您可以通过以下方式获得源代码:
通过Git获取
您可以访问https://github.com/fslongjin/DragonOS/releases下载发布版的代码,以及编译好的,可运行的磁盘镜像。
我们在gitee上也有镜像仓库可供下载:https://gitee.com/DragonOS/DragonOS
通过DragonOS软件镜像站获取
为解决国内访问GitHub慢、不稳定的问题,同时为了方便开发者们下载DragonOS的每个版本的代码,我们特意搭建了镜像站,您可以通过以下地址访问镜像站:
您可以通过镜像站获取到DragonOS的代码压缩包,以及编译好的可运行的磁盘镜像。
开放源代码声明
备注
为促进DragonOS项目的健康发展,DragonOS以GPLv2开源协议进行发布。所有能获得到DragonOS源代码以及相应的软件制品(包括但不限于二进制副本、文档)的人,都能享有我们通过GPLv2协议授予您的权利,同时您也需要遵守协议中规定的义务。
这是一个相当严格的,保护开源软件健康发展,不被侵占的协议。
对于大部分的善意的人们而言,您不会违反我们的开源协议。
我们鼓励DragonOS的自由传播、推广,但是请确保所有行为没有侵犯他人的合法权益,也没有违反GPLv2协议。
请特别注意,对于违反开源协议的,尤其是商业闭源使用以及任何剽窃、学术不端行为将会受到严肃的追责。(这是最容易违反我们的开源协议的场景)。
并且,请注意,按照GPLv2协议的要求,基于DragonOS修改或二次开发的软件,必须同样采用GPLv2协议开源,并标明其基于DragonOS进行了修改。亦需保证这些修改版本的用户能方便的获取到DragonOS的原始版本。
您必须使得DragonOS的开发者们,能够以同样的方式,从公开渠道获取到您二次开发的版本的源代码,否则您将违反GPLv2协议。
关于协议详细内容,还敬请您请阅读项目根目录下的LICENSE文件。请注意,按照GPLv2协议的要求,只有英文原版才具有法律效力。任何翻译版本都仅供参考。
开源软件使用情况
DragonOS在开发的过程中,参考了一些开源项目的设计,或者引入了他们的部分代码,亦或是受到了他们的启发。现将他们列在下面。我们对这些开源项目的贡献者们致以最衷心的感谢!
格式:<项目名> - <链接> - <开源协议>
Linux - https://git.kernel.org/ - GPLv2
skiftOS - https://github.com/skiftOS/skift - MIT
FYSOS - https://github.com/fysnet/FYSOS - FYSOS’ License
LemonOS - https://github.com/LemonOSProject/LemonOS.git - BSD 2-Clause License
LZ4 - https://github.com/lz4/lz4 - BSD 2-Clause license
SerenityOS - https://github.com/SerenityOS/serenity.git - BSD 2-Clause license
MINE - 《一个64位操作系统的设计与实现》田宇; 人民邮电出版社
chcore - 《现代操作系统:设计与实现》陈海波,夏虞斌; 机械工业出版社
SimpleKernel - https://github.com/Simple-XX/SimpleKernel - MIT