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()) {// Parentclose(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 {// Childclose(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 $sstatusbacktrace 回看栈中的数据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 statevoid *chan; // If non-zero, sleeping on chanint killed; // If non-zero, have been killedint xstate; // Exit status to be returned to parent's waitint 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 stackuint64 sz; // Size of process memory (bytes)pagetable_t pagetable; // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context; // swtch() here to run processstruct file *ofile[NOFILE]; // Open filesstruct inode *cwd; // Current directorychar name[16]; // Process name (debugging)
};
/kernel/proc.c 进程相关函数实现
在fork()系统调用中实现mask的赋值
// copy mask for tracenp->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->a0p->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]]
微内核实际上需要在内核态与用户态中不断地切换从而实现功能
微内核的性能如何提升,大多是宏内核的