#generic-repos is licensed under the Mulan PSL v2.
#You can use this software according to the terms and conditions of the Mulan PSL v2.
#You may obtain a copy of Mulan PSL v2 at:
# http://license.coscl.org.cn/MulanPSL2
#THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
#PURPOSE.
#See the Mulan PSL v2 for more details.
[OS]
name=OS
baseurl=http://repo.openeuler.org/openEuler-23.09/OS/$basearch/
metalink=https://mirrors.openeuler.org/metalink?repo=$releasever/OS&arch=$basearch
metadata_expire=1h
enabled=1
gpgcheck=1
gpgkey=http://repo.openeuler.org/openEuler-23.09/OS/$basearch/RPM-GPG-KEY-openEuler
[source]
name=source
baseurl=http://repo.openeuler.org/openEuler-23.09/source/
metalink=https://mirrors.openeuler.org/metalink?repo=$releasever&arch=source
metadata_expire=1h
enabled=1
gpgcheck=1
gpgkey=http://repo.openeuler.org/openEuler-23.09/source/RPM-GPG-KEY-openEuler
其中各个配置项的含义如下:
[repoid]中的 repoid 为软件仓库(repository)的 ID 号,所有.repo 配置文件中的各 repoid 不能重复,必须唯一。示例中 repoid 为 OS 和 source。
name 为软件仓库描述的字符串,可以任意取,但是建议取一个有意义的名称,方便用户理解。示例中 name 为 OS 和 source。
// kernel/module.cstaticinlineintcheck_modstruct_version(conststructload_info*info,structmodule*mod){const s32 *crc;/*
* Since this should be found in kernel (which can't be removed), no
* locking is necessary -- use preempt_disable() to placate lockdep.
*/preempt_disable();if(!find_symbol("module_layout",NULL,&crc, true, false)){preempt_enable();BUG();}preempt_enable();returncheck_version(info,"module_layout", mod, crc);}
// kernel/module.cstaticintcheck_modinfo(structmodule*mod,structload_info*info,int flags){constchar*modmagic =get_modinfo(info,"vermagic");int err;if(flags & MODULE_INIT_IGNORE_VERMAGIC)
modmagic =NULL;/* This is allowed: modprobe --force will invalidate it. */if(!modmagic){
err =try_to_force_load(mod,"bad vermagic");if(err)return err;}elseif(!same_magic(modmagic, vermagic, info->index.vers)){pr_err("%s: version magic '%s' should be '%s'\n",
info->name, modmagic, vermagic);return-ENOEXEC;}...return0;}
Linux 对可装载模块采取了两层验证,我们需要分别从 CRC 和 vermagic 两个方面来解决模块校验错误。首先从简单的 vermagic 校验开始。我们需要保证运行的内核版本与模块编译时的内核版本一致,这样才能保证 vermagic 校验通过。首先了解如何查看内核版本以及模块版本信息,然后修改内核模块版本信息。
解决 vermagic 校验错误
如何查看内核版本以及模块版本信息
uname参数功能:
-s, 输出 kernel 名称;
-n, 输出主机名;
-r, 输出 kernel 发行版本号;
-v, 输出操作系统版本;
-m, 输出主机的硬件架构名称;
-p, 输出处理器类型;
-i, 输出硬件平台;
-o, 输出操作系统名称
-a, 输出所有信息
# 输出kernel发行版本号uname -r
6.4.0-10.1.0.20.oe2309.riscv64
# 输出所有信息uname -a
Linux openeuler 6.4.0-10.1.0.20.oe2309.riscv64 #1 SMP Sat Oct 7 06:19:28 UTC 2023 riscv64 riscv64 riscv64 GNU/Linux
/** * Iterate over list of given type * @param pos the type * to use as a loop cursor. * @param head the head for your list. * @param member the name of the list_struct within the struct. */ #define sbi_list_for_each_entry(pos, head, member) \ for (pos = sbi_list_entry((head)->next, typeof(*pos), member); \ &pos->member != (head); \ pos = sbi_list_entry(pos->member.next, typeof(*pos), member))
那么服务 id 如何和相对应的服务绑定的呢?以ecall_time为例,查看其结构体原型struct sbi_ecall_extension :
switch (extid) { case SBI_EXT_0_1_SET_TIMER: sbi_timer_event_start((u64)regs->a0); break; case SBI_EXT_0_1_CONSOLE_PUTCHAR: sbi_putc(regs->a0); break; case SBI_EXT_0_1_CONSOLE_GETCHAR: ret = sbi_getc(); break; // ... };
return ret; }
这就把 id 与相应的服务函数绑定。一个extid对应一个handler。
我们可以在找到SBI_EXT_0_1_CONSOLE_PUTCHAR的值,是与 Linux 内核里定义的值是一致的。
make run make debug make clean # 编译测试用例的前四章 make user CHAPTER=4 LOG=trace # 编译测试用例的第四章 make user CHAPTER=4_only LOG=trace # 只运行测试用例的第四章 make test CHAPTER=4_only
建立页表也就是建立虚拟地址到物理地址的映射关系。也就是给你一个虚拟地址,你需要告诉我如何查到物理地址,实际上这个过程就是建立页表的过程。这个过程也是通过 walk 函数来完成的,从上文我们知道如果页表都建好的情况下 walk 就是不断查页表的过程,那么在没有页表的情况下,walk 还可以建立一个个页表。稍有不同的是,walk 返回的是最后一级页表项的地址,我们需要将物理地址写入这个页表项中。
// 启动时初始化进程表 voidproc_init(void) { structproc *p; for (p = pool; p < &pool[NPROC]; p++) { p->state = UNUSED; // p - pool 是 p 指向的 proc 在 pool 中的下标,因此 p - pool 变化情况是 0, 1, 2, ..., NPROC - 1 p->kstack = (uint64)kstack[p - pool]; p->ustack = (uint64)ustack[p - pool]; p->trapframe = (struct trapframe *)trapframe[p - pool]; /* * LAB1: you may need to initialize your new fields of proc here */ } idle.kstack = (uint64)boot_stack_top; idle.pid = 0; current_proc = &idle; }
p - pool 表示什么? 假设我们有一个名为 pool 的数组,其中包含了多个类型为 struct proc 的元素,并且有一个指针 p 指向其中的某个元素。 当 p 指向 pool 数组的第一个元素时,p - pool 的结果将是 0,因为指针相对于数组首地址的偏移量为 0。 当 p 指向 pool 数组的第二个元素时,p - pool 的结果将是 1,因为指针相对于数组首地址的偏移量为 1。 以此类推,当 p 指向 pool 数组的第 N 个元素时,p - pool 的结果将是 N-1,因为指针相对于数组首地址的偏移量为 N-1。 总结来说,如果 p 是指向 pool 数组中第 N 个元素的指针,那么 p - pool 的结果将是 N-1。
原调度函数每次都会从 pool 数组的第一个元素开始遍历,这样会导致每次都是从第一个进程开始运行,而不是从上次运行的进程开始运行。需要修改为如下:
这是因为csrrw指令是一个特权指令,用于将某个 CSR(Control and Status Register)的值读取到目标寄存器,然后将目标寄存器的值写回到该 CSR 中。在这里,csrrw a0, sscratch, a0指令将sscratch寄存器的值读取到a0寄存器中,同时将a0寄存器中的值写回到sscratch寄存器中,从而实现了两者之间的数据交换。
flowchart TB subgraph entry.S _entry[_entry] end subgraph link_app.S _app_num[_app_num] end subgraph main.c main[main] end subgraph loader.c loader_init[loader_init] run_next_app[run_next_app] load_app[load_app] end
_entry --> main main --> loader_init main --> run_next_app run_next_app --> load_app loader_init --> _app_num