lab0
Google 翻译
第零章 操作系统接口 | xv6 中文文档 (gitbooks.io)
6.1810 / Fall 2022 (mit.edu)
sleep(easy)
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]){
if(argc <= 2) sleep(atoi(argv[1]));
return 0;
}
pingpong (easy)
pipe与dup的讲解:
(209条消息) xv6中pipe&dup_xv6 dup函数的含义_G129558的博客-CSDN博客
pipe: 创建一个通道使得两个进程能够进行通信
dup:复制一个稳健描述符,文件描述符的生成以最小数开始
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char *argv[]) {
int parent_fd[2], child_fd[2];
pipe(parent_fd);
pipe(child_fd);
char buf[64];
if (fork()) {
// Parent
close(child_fd[1]);
write(parent_fd[1], "ping", strlen("ping"));
read(child_fd[0], buf, 4);
printf("%d: received %s\n", getpid(), buf);
close(parent_fd[1]);
close(parent_fd[0]);
close(child_fd[0]);
} else {
// Child
close(parent_fd[1]);
read(parent_fd[0], buf, 4);
printf("%d: received %s\n", getpid(), buf);
write(child_fd[1], "pong", strlen("pong"));
close(child_fd[1]);
close(child_fd[0]);
}
exit(0);
}
lab1
Using gdb
在第一个terminal调用 make qemu-gdb
在另一个terminal执行 gdb-multiarch
通过gdb实现寄存器的查看
p /x $sstatus
backtrace 回看栈中的数据
b *0x000000008000215a 对具体的地址进行打断点
System call tracing
思路:增加一个系统调用sys_trace,此时我们需要在所有的进程中添加一个mask变量来记录我们需要跟踪的指令,随后在syscall中打印出来
/user/usys.pl :perl脚本 实现对系统调用的汇编语言的生成
在其中添加
entry("trace");
/user/user.h : 系统调用以及用户常见的函数的定义
int trace(int);
/kernel/proc.h 进程相关的结构体函数的定义
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
**int mask;** // 添加一个 mask 用来进行记录
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
/kernel/proc.c 进程相关函数实现
在fork()系统调用中实现mask的赋值
// copy mask for trace
np->mask = p->mask;
kernel/syscall.c 系统调用函数实现
添加一个表
static const char*syscall_names[]={
[SYS_fork] "sys_fork",
[SYS_exit] "sys_exit",
[SYS_wait] "sys_wait",
[SYS_pipe] "sys_pipe",
[SYS_read] "sys_read",
[SYS_kill] "sys_kill",
[SYS_exec] "sys_exec",
[SYS_fstat] "sys_fstat",
[SYS_chdir] "sys_chdir",
[SYS_dup] "sys_dup",
[SYS_getpid] "sys_getpid",
[SYS_sbrk] "sys_sbrk",
[SYS_sleep] "sys_sleep",
[SYS_uptime] "sys_uptime",
[SYS_open] "sys_open",
[SYS_write] "sys_write",
[SYS_mknod] "sys_mknod",
[SYS_unlink] "sys_unlink",
[SYS_link] "sys_link",
[SYS_mkdir] "sys_mkdir",
[SYS_close] "sys_close",
[SYS_trace] "sys_trace"
};
添加判断命令
void
syscall(void)
{
int num;
struct proc *p = myproc();
num = p->trapframe->a7;
if(num > 0 && num < NELEM(syscalls) && syscalls[num]) {
// Use num to lookup the system call function for num, call it,
// and store its return value in p->trapframe->a0
p->trapframe->a0 = syscalls[num]();
// printf("%s -> %d\n", )
if(p->mask & (1 << num)){
printf("%d: syscall %s -> %d\n", p->pid, syscall_names[num], p->trapframe->a0);
}
} else {
printf("%d %s: unknown sys call %d\n",
p->pid, p->name, num);
p->trapframe->a0 = -1;
}
}
/kernel/sysproc 系统调用实现
添加trace命令
uint64 sys_trace(void){
int mask;
struct proc *now_proc = myproc();
argint(0, &mask);
now_proc -> mask = mask;
return 0;
}
在进行系统调用函数的使用时,所有的参数会依次存入到寄存器 a0 a1 a2 a3 a4 a5
Sysinfo
实现Sysinfo的系统调用
步骤:
step1:/user/usys.pl中添加
entry("sysinfo")
step2: /user/user.h中添加
struct sysinfo;
step3: 在sysproc中添加sys_sysinfo
uint64 sys_sysinfo(void){
uint64 addr;
argaddr(0, &addr);
struct sysinfo info;
info.freemem = get_freemem();
info.nproc = get_freeproc();
struct proc *p = myproc();
if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
return -1;
return 0;
}
在上述代码中为什么使用argaddr?
void
argaddr(int n, uint64 *ip)
{
*ip = argraw(n);
}
因为地址是无符号型
为什么使用copyout?
copyout能够Copy from kernel to user
step4: /kernel/kalloc.v
freelist表示一个空闲页表链,每一个空闲页表的大小为4096byte
int get_freemem(){
struct run *p;
int freemem = 0;
acquire(&kmem.lock);
p = kmem.freelist;
while(p){
if(p) freemem += PGSIZE;
p = p -> next;
}
release(&kmem.lock);
return freemem;
}
为什么要加锁?
为了能够节约CPU资源
防止内存访问出现问题
step 5: /kernel/proc.c
进程中维护了一个进程表,把进程表中没有利用使用的进程记录下来即可
int get_freeproc(){
struct proc *p;
int free_num = 0;
for(p = proc; p < &proc[NPROC]; p++) {
if(p->state != UNUSED){
free_num += 1;
}
}
return free_num;
}
MIT课程
Lecture 3 - OS Organization and System Calls_哔哩哔哩_bilibili
操作系统的结构:
隔离性 内核/用户模式 系统调用
操作系统与应用之间不会出现很大的隔离
例如在使用fork()系统调用时,会出现对CPU进行分配任务,这种抽象实际上就是避免用户直接对CPU进行操作从而实现隔离
通过时间复用从而实现不同进程的使用
内存读写在linux中都是通过文件进行映射的,然后操作系统自己对内存进行映射
操作系统必须具有防守性 防御性编程
应用不能够对OS进行影响
用户内核模式 与 虚拟内存 能够为防御性编做工作
用户内核模式:用户模式 内核模式 机器模式
在内核模式CPU能够执行特权指令 : 直接对硬件进行操作 设置时间中断 对页表的处理
在用户模式CPU只能够实现非特权指令
存在一个寄存器能够实现对特权级别的更改
每个进程维护自己的页表,页表定义了内存的布局 将所有的进程与用户进行隔离
每一个进程在不同的页表中维护,从而实现了不同的程序可能起始地址都是0 不能够存在内存的相互访问
fork() 调用 ecall 给定系统调用标号 从而实现对应的功能
宏内核设计与微内核设计 区别在于是否在内核段存放大量的代码
![[截图_20230709095928.png]]
微内核实际上需要在内核态与用户态中不断地切换从而实现功能
微内核的性能如何提升,大多是宏内核的