/kernel/kernel载入符号表,然后target remote loaclhost:26000即可: Lab1_1:Boot xv6 运行并安全退出xv6系统: 运行的方法很简单:cd进xv6的文件夹里面...(中文版xv6 书籍) 查看user/中的其他一些程序 (例如,user/echo.c、user/grep.c和user/rm.c)以了解如何获取传递给程序的命令行参数。...Lab1_3 pingpong 编写一个程序,使用 UNIX 系统调用在两个进程之间通过一对管道“乒乓”一个字节,每个管道一个。...,河流分成了两叉,这两叉都从分叉口开始继续往下流.在这里分叉口就像fork()调用,调用生成了两个分叉,两个分叉都从fork()调用结束后继续往下走....Lab1_5 find 编写一个简单版本的 UNIX 查找程序:查找目录树中具有特定名称的所有文件。给定对应的文件名以及文件名在目录,找到文件名的位置.
另一方面,我们可能倾向于为应用程序提供许多复杂的特性。 解决这个问题的诀窍在于设计接口时,依赖一些可结合的机制,以此来提供更好的通用性。 本书使用单一的操作系统作为具体的例子来说明操作系统的概念。...---- xv6的shell使用上述调用为用户运行程序。shell的主要结构很简单,请参见main(*user/sh.c:145*)。...因为sh.c的目标是尽可能的简单,所以它不会试图避免创建内部进程。...Xv6与POSIX不兼容: 它缺少许多系统调用(包括lseek等基本系统调用),并且它提供的许多系统调用与标准不同。 我们xv6的主要目标是简单明了,同时提供一个简单的类unix系统调用接口。...为了运行基本的Unix程序,有些人扩展了xv6,增加了一些系统调用和一个简单的c库。 然而,现代内核比xv6提供了更多的系统调用和更多种类的内核服务。
所以,现代的类Unix操作系统有许多种shell可以选择,每种shell都有其自身的用户界面与脚本特性。XV6的shell 是Unix Bourne shell的一个简单实现。...如果wc指向一个管道的写端口,那么wc就永远看不到eof了。 xv6 shell使用了与上面代码类似的方法,实现了如grep fork sh.c | wc -l这样的管道(在8450行)。...管道的右端可能也是一个带有管道的命令(例如 a|b|c),它fork两个新的子进程(一个b,一个c)。...文件系统 xv6 文件系统提供数据文件与目录,文件就是一个简单的字节数组,目录包含了指向文件或其他目录的引用。Xv6把目录作为特殊的文件来处理。目录构成了一棵树,树根为一个称为root的特殊目录。...路径a/b/c指向了一个名为c的文件或目录,c在文件目录b下,而目录b又处于目录a下,a又是处于root目录之下。
尽管上下文切换的思想很简单,但它的实现是xv6中最不透明的代码之一。 第二,如何以对用户进程透明的方式强制切换?Xv6使用标准技术,通过定时器中断驱动上下文切换。...需要小心避免导致唤醒通知丢失的竞争。 Xv6试图尽可能简单地解决这些问题,但结果代码很复杂。...因此,如果要打印xv6切换线程处的行号,将观察到以下简单模式:(kernel/proc.c:475),(kernel/proc.c:509),(kernel/proc.c:475),(kernel/proc.c...睡眠和唤醒是一种简单有效的同步方法,但还有很多其他方法。所有这些问题中的第一个挑战是避免我们在本章开头看到的“丢失唤醒”问题。...proc结构体,而不是allocproc中的线性时间搜索;xv6使用线性扫描是为了简单起见。
sleep sleep功能为使进程睡眠若干个时钟周期(xv6中一个tick为100ms),首先创建user/sleep.c源文件,引入user.h头文件,系统调用和工具函数都定义在该文件里。...= 0): write n to right // 不能被p整除则向右输出 } 还需要注意两点: 文件描述符溢出: xv6限制fd的范围为0~15,而每次pipe()都会创建两个新的...这里使用重定向到标准I/O的方式来避免生成新的fd,首先close()关闭标准I/O的fd,然后使用dup()复制所需的管道fd(会自动复制到序号最小的fd,即关闭的标准I/O),随后对pipe两侧fd...xv6系统调用流程 Lab中对system call的使用很简单,看起来和普通函数调用并没有什么区别,但实际上的调用流程是较为复杂的。我们很容易产生一些疑问:系统调用的整个生命周期具体是什么样的?...3.内核态执行 完成进程切换后,调用trap.c/usertrap(),接着进入syscall.c/syscall(),在该方法中根据system call number拿到数组中的函数指针,执行系统调用函数
常用命令实现 本节来看看在 $xv6$ 里面一些常见的命令是如何实现的,它们都是用户程序,封装系统调用而成,大多数都很简单一眼过去就能懂那种,来看: echo $echo$ 命令将紧跟其后的参数当作字符串打印出来...来看 $xv6$ 里的实现 首先是两个匹配函数 int matchhere(char* re, char* text); int matchstar(int c, char* re, char* text...0 次的情况,后续再次执行循环体的话就是匹配多次的情况 上述就是匹配的主要函数,稍微有些困难的地方可能就是 * 元字符,找几个简单例子模拟一下应该也多大问题,到此还有个 ^ 匹配开头没有处理,来看:...上述就是 $xv6$ 中一些命令的实现,很简单,大多数就是调用现成的系统调用就能完成工作,配上详细的注释应该是一眼就能懂什么意思。...稍微困难些的就是 $grep$ 命令,涉及到递归思想的是稍微要困难点,多看几遍问题应该也不大 好了本节就这样吧,有什么问题还请批评指正,也欢迎大家来同我讨论交流学习进步。
属性字段辨析 这里来解决前文遗留的一些问题,在前文 inode部分 我们聊过 $inode$ 的一些属性字段,当时有些地方只是简单提了几句,说是后面详述。...这里简单的再过一下系统调用,$xv6$ 的系统调用使用 INT 64 指令来实现的,触发一个 $64$ 号中断陷入内核,根据向量号 $64$ 去获取执行中断服务程序。.../b/c/.$ 之类的,而 $Linux$ 下是无法 $unlink$ 一个目录的。...如果使用绝对路径,那么这个软链接放在任何地方,只要源文件没问题,那么通过软链接操作文件是没有问题的。...我做了一个简单的测试: int main(){ int fd; fd = open("unlink_test.txt", O_RDWR|O_CREAT); //打开创建一个文件
函数bmap(kernel/fs.c:378)从简单的情况开始:前面的NDIRECT个块在inode本身中列出(kernel/fs.c:383-387)中。...---- 代码:系统调用 通过使用底层提供的函数,大多数系统调用的实现都很简单(请参阅kernel/sysfile.c)。有几个调用值得仔细看看。...与UNIX V6一样,Xv6的buffer cache使用简单的最近最少使用(LRU)替换策略;有许多更复杂的策略可以实现,每种策略都适用于某些工作场景,而不适用于其他工作场景。...真正的日志系统解决了所有这些问题。 日志记录不是提供崩溃恢复的唯一方法。...Xv6对于磁盘故障的解决很初级:如果磁盘操作失败,Xv6就会调用panic。
系统调用通俗的讲就是是用户态下的程序托内核办事,既然是托人办事那得告诉人家你要办什么事对吧。这个告诉人家具体要办什么事就是要给内核传递系统调用号,问题是怎么传呢?...函数名就相当于一个地址,name: 后面的代码就是这个函数具体要做的事,就像 c 语言编写函数时的函数体,只不过这里是用汇编写的而已。 所以这个函数做了什么事?...这个被调用函数就是用户接口,举个例子如果调用 $write(fd, buf, size)$,则在这之前一定会将参数 $size, buf, fd$ 按照这个顺序压栈,再 $call$ 调用函数,只是在...$c$ 语言中这个过程可能看起来不是那么真切,如果是用汇编来写,或者查看编译之后的程序,会有下面的大致过程: push size push buf push fd call write 在 call...,也就是用户接口 $write(fd, buf, size)$ 的 $buf$ 地址值,并将其赋给 $p$。
xv6 在这方面实现的比较简单,只实现了字符转化,一些功能控制键,我们来看看。...$ 个字符,光标的当前位置加上当先行剩余可容纳字符数就是将光标移动到下一行行首了,简单的一个数学加减法,应该没什么问题。...} 这一个 $if$ 语句块来简单粗暴的处理滚屏操作,一屏最多使用 $25$ 行,$xv6$ 里面当要写第 24 行的时候就开始执行滚屏操作,最后一行没有使用。...,这里就直接来看用户态下如何实现 $printf$ 函数,首先打印单个字符的函数: static void putc(int fd, char c) { write(fd, &c, 1); } $putc...state = '%'; //记录状态为% } else { //如果当前字符不是% putc(fd, c); //调用putc向fd指向的文件写字符c
;对系统调用的异常进行处理 xv6中提供有sh.c的实现,除了重定向和管道,还对括号、列表命令、后台命令等做了支持,且整体设计较为复杂。...所以我们无需过多参考已有代码,可以选择简单的思路来满足需求,在完成后再去阅读xv6的shell实现。 Shell本质上是一个用户程序,在用户和操作系统间建立连接。...,基于 gets() 函数来接收标准输入,直接参考sh.c即可。...xv6中的shell实现 xv6中的shell实现在user/sh.c中,大致思路和我们的nsh相似,都是实现了对用户命令的循环读取、解析、执行,不过支持的命令类型更多且涉及更复杂。...**1.主体逻辑** sh.c将命令解析和命令执行独立开来,首先递归地构造出结构化的命令树,然后又递归地去遍历树中的命令并执行。
/kernel/kernel载入符号表,然后target remote loaclhost:26000即可: Lab1_1:Boot xv6 运行并安全退出xv6系统: 运行的方法很简单:cd进xv6的文件夹里面...,河流分成了两叉,这两叉都从分叉口开始继续往下流.在这里分叉口就像fork()调用,调用生成了两个分叉,两个分叉都从fork()调用结束后继续往下走....文件notxv6 / ph.c包含一个简单的哈希表,该哈希表从单个线程使用时是正确的,但从多个线程使用时则是错误的。...kernel/net.c和kernel/net.h包含了一个简单的包含IP、UDP、ARP协议的网络栈。这些文件页包含了一个灵活的数据结构来持有数据包,叫做mbuf。...一旦你完成了 E1000 驱动程序的工作,kernel/net.c 就会处理这个问题。
Attention: 在开始编码之前,请阅读xv6手册的第4章(特别是4.6),以及可能要修改的相关文件: kernel/trap.c kernel/vm.c kernel/sysproc.c...---- 代码解析 这个实验很简单,就仅仅改动sys_sbrk()函数即可,将实际分配内存的函数删除,而仅仅改变进程的sz属性 uint64 sys_sbrk(void) { int addr;...: page table 0x0000000087f55000 ..0: pte 0x0000000021fd3c01 pa 0x0000000087f4f000 .. ..0: pte 0x0000000021fd4001...pa 0x0000000087f52000 .. .. ..510: pte 0x0000000021fd58c7 pa 0x0000000087f56000 .. .. ..511: pte 0x0000000020001c4b...所以,对于uvmunmap中第一个continue而言,主要解决的问题是一二级页表中存在Lazy Alloction的pte还未建立映射关系,所以walk函数会返回0: 对于第二个continue
/数据区的大小 int freeblock = nmeta; //数据区的起始块号 上述的一些元信息的计算应该还是很简单的,不多解释,注意文件系统的基本单位是块,块数就是大小,块号就是位置即可。...y; a[0] = x; a[1] = x >> 8; a[2] = x >> 16; a[3] = x >> 24; return y; } 运用移位运算来转化大小端,应该还是很简单的...这类函数从哪开始写,写到哪儿,一次性能写多少,变量有些多,有点扰人,不过逻辑不复杂,耐心点应该没什么问题。...(fd); //关闭该参数文件 } 这部分将主机上编译好的可执行文件写进磁盘文件的根目录,首先为每个可执行文件比如说 $cat$ 分配一个 $inode$,然后在根目录下安装目录项,最后将主机上的...好了本节就这样吧,有什么问题还请批评指正,也欢迎大家来同我讨论交流学习进步。
,返回地址改变了,所以说这下pc就变成之前调用swtch的进程调用swtch的PC.这听上去很绕,简单的说就是反悔的PC不是这个进程调用之前的那个PC,而是上个进程调用之前的PC....我们需要记录当前的进程指针来获取信息,一般来说,如果你的机器是一个核的,我们可以设置一个全局变量,但是我们的机器是多核的,每个核执行不同的进程,这个方案就有一定的问题....is still open int writeopen; // write fd is still open }; 在xv6,我们使用pipe这个数据结构,首先我们看到一个锁结构,还有一个数据buffer.....导致这个pid和havekids出现问题....for(int fd = 0; fd < NOFILE; fd++){ if(p->ofile[fd]){ struct file *f = p->ofile[fd];
.这个中断可以简称访管中断.....第二个问题就是xv6的内核态和用户态页表是不一样的....所有的xv6关于地址的处理全部放在vm.c这个文件中....7:Lock 许多操作系统内核,包括xv6都保持着多线程多进程执行,首先是因为这个xv6有许多个微处理器,这些处理器(CPU)是独立地执行一段代码,共享物理内存,这个时候就会有问题,就是在一个CPU读取数据的时候...Xv6允许同时运行多个CPU核,多核CPU上的等待队列实现相当复杂,因此使用自旋锁是相对比较简单且能正确执行的实现方案。
本文主要通过 $xv6$ 来看看如何实现一个简单的 $shell$,$shell$ 实现分为两个主要步骤,一解析输入的命令字符串,二执行命令。...fd[0] = fd0; //赋值给参数 fd[1] = fd1; return 0; //返回0表正确 } 如果操作都都没出问题的话就将分配的文件描述符赋给参数,因为是传指针...下面讲述实际的 $shell$ 程序,$xv6$ 的 $shell$ 是 $Bourne\ shell$ 的简单实现,而 $Bourne\ Shell$ 就是 $bash$ 的前身。...另外管道是属于内核的一片内存区域,$xv6$ 的机制很简单,回收管道资源就是依靠 $close$ 系统调用,它会调用 $fileclose$ 来减少文件结构体的引用数,当文件结构体的引用数减少到 0 的时候就会调用...$xv6$ $shell$ 退出之后又会重建,因为在 $init$ 进程中: /*******init.c*******/ int main(){ for(;;){ /******略******
本课程前置知识主要涉及: C语言(建议阅读C程序语言设计—第二版) RISC-V汇编 推荐阅读: 程序员的自我修养-装载,链接与库 前面两节,我们介绍了xv6 文件系统教材上的相关小节说明,从本节开始...在XV6中,使用的数据结构非常简单,因为XV6是专门为教学目的创建的。真实的文件系统通常会更加复杂。但是它们都是磁盘上保存的数据结构,我们在今天的课程会重点看这部分。 最后一个有趣的话题是性能。...XV6这里极其简单,基本是按照最早的Uinx实现方式来的,不过你可以实现更复杂的结构。...出于简单和更容易解释的目的,XV6使用了这里这种非常简单的数据结构。...ialloc函数位于fs.c文件中。 以上就是ialloc函数,与XV6中的大部分函数一样,它很简单,但是又不是很高效。
实际上XV6允许在执行内核代码时触发中断,如果你查看trap.c中的代码你可以发现,如果XV6正在执行内核代码时发生了定时器中断,中断处理程序会调用yield函数并出让CPU。...在我回到XV6代码之前,让我演示一下如何在UART驱动中使用刚刚介绍的sleep和wakeup函数。这基本上是重复前一节的内容,不过这次我们使用刚刚介绍的稍微简单的接口。 首先是定义done标志位。...所以我们需要在两个函数中加锁来避免对于done标志位和硬件的竞争访问。 现在的问题是,我们该在哪个位置加锁?在中断处理程序中较为简单,我们在最开始加锁,在最后解锁。...当它还在执行代码,它就不能释放正在使用的资源。所以我们需要一种方法让线程能释放最后几个对于运行代码来说关键的资源。 记住这两个问题。 XV6有两个函数与关闭线程进程相关。.... // 关闭当前进程打开的所有文件 for(int fd = 0; fd < NOFILE; fd++){ if(p->ofile[fd]){ struct file *f
,使进程睡眠若干个滴答周期(滴答是 xv6 内核定义的时间概念,即来自定时器芯片的两次中断之间的时间。)。...效果: $ make qemu ... init: starting sh $ pingpong 4: received ping 3: received pong $ 2.2 分析 要解决这个问题,...下面看下,xv6 book中对管道的定义。 管道是一个小的内核缓冲区,它以文件描述符对的形式提供给进程,一个用于写操作,一个用于读操作。从管道的一端写的数据可以从管道的另一端读取。...将 32 位int写入管道是最简单的,而不是使用格式化的 ASCII I/O。...exit(0); } for(i=1; i<argc; i++) ls(argv[i]); //ls 和我们熟知的linux的ls是不太相同的,xv6的ls是可以接受多个目录作为参数的
领取专属 10元无门槛券
手把手带您无忧上云