Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【6S.081】起源:初识操作系统以及配置xv6环境

【6S.081】起源:初识操作系统以及配置xv6环境

作者头像
Skrrapper
发布于 2025-06-13 07:28:52
发布于 2025-06-13 07:28:52
14200
代码可运行
举报
文章被收录于专栏:技术分享技术分享
运行总次数:0
代码可运行

【6S.081】学习笔记01:初识xv6以及配置xv6环境

由于6S.081本身就是操作系统类课程,故在学习操作系统的过程中,融合了该门课程的学习。所以在操作系统篇中直接涉猎,这里不再做笔记,仅仅针对xv6的文档、lab以及代码实现进行笔记记录。

注:内容分为文档阅读笔记实验笔记

文档阅读笔记1:第一章 操作系统接口

1.讲解操作系统的构建理念

操作系统的工作是

(1)将计算机的资源在多个程序间共享,并且给程序提供一系列比硬件本身更有用的服务。

(2)管理并抽象底层硬件,举例来说,一个文字处理软件(比如 word)不用去关心自己使用的是何种硬盘。

(3)多路复用硬件,使得多个程序可以(至少看起来是)同时运行的。

(4)最后,给程序间提供一种受控的交互方式,使得程序之间可以共享数据、共同工作。

2.介绍xv6

  • xv6提供Unix操作系统中的基本接口,同时模仿其内部设计
  • 使用了传统的Kernel(内核)概念,一个向其他运行中程序提供服务的特殊程序

  • xv6内核提供了Unix传统系统调用的一部分,即以下部分,注意,请务必熟记,以后会经常用到:

系统调用

描述

fork()

创建进程

exit()

结束当前进程

wait()

等待子进程结束

kill(pid)

结束 pid 所指进程

getpid()

获得当前进程 pid

sleep(n)

睡眠 n 秒

exec(filename, *argv)

加载并执行一个文件

sbrk(n)

为进程内存空间增加 n 字节

open(filename, flags)

打开文件,flags 指定读/写模式

read(fd, buf, n)

从文件中读 n 个字节到 buf

write(fd, buf, n)

从 buf 中写 n 个字节到文件

close(fd)

关闭打开的 fd

dup(fd)

复制 fd

pipe( p)

创建管道, 并把读和写的 fd 返回到p

chdir(dirname)

改变当前目录

mkdir(dirname)

创建新的目录

mknod(name, major, minor)

创建设备文件

fstat(fd)

返回文件信息

link(f1, f2)

给 f1 创建一个新名字(f2)

unlink(filename)

删除文件

  • xv6适用shell,本质上是一个Unix Bourne shell 的简单实现。介绍shell概念:shell是一个普通的用户界面,接受用户输入的命令并且执行它们。它不是内核的一部分,充分说明了系统调用接口的强大。

3.xv6的进程

一个xv6进程由两部分组成,一部分是用户内存空间(指令,数据,栈),另一部分是仅对内核可见的进程状态。

xv6具有分时特性:可以在可用CPU之间不断切换,决定哪一个等待中进程被执行;不被执行的那个进程的CPU寄存器将会被xv6保存,当要被执行时恢复这些寄存器的值。

内核将每个进程与一个**pid(进程标识符)**关联起来。

在了解父子进程前,先了解几个函数的概念:

fork:创建进程

wait:等待进程结束

exec:读取内存镜像,执行可执行文件

exit:退出进程

父进程与子进程:

在某个进程(被称为父进程)中使用fork创建的新进程被称为子进程

fork函数在父进程中返回子进程的pid,在子进程中返回0.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int pid;
pid = fork();
if(pid > 0){//如果pid大于0,则是父进程返回了子进程的pid
    printf("parent: child=%d\n", pid);
    pid = wait();
    printf("child %d is done\n", pid);
} else if(pid == 0){//如果pid等于0,则是子进程返回了0
    printf("child: exiting\n");
    exit();
} else {//否则出错
    printf("fork error\n");
}

父进程与子进程拥有不同的内存空间和寄存器,改变一个进程中的变量不会影响另一个进程。

xv6通常隐式地分配用户内存空间。fork 在子进程需要装入父进程的内存拷贝时分配空间,exec 在需要装入可执行文件时分配空间。一个进程在需要额外内存时可以通过调用 sbrk(n) 来增加 n 字节的数据内存。 sbrk 返回新的内存的地址。

4.I/O与内存描述符

如果我们要了解xv6系统的核心要领,那么I/O接口和文件描述符是不得不去理解的。

文件描述符

我们首先来看文件描述符,它是一个小整数(small integer),它表示进程可以读取或写入的由内核管理的对象。

我们在xv6的源代码中可以找到以下数据:

这些都是与文件描述符有关的代码。

文件描述符是一个由内核管理的非负整数本质是进程访问I/O资源的句柄

进程通过文件描述符操作文件、管道、设备等资;而内核通过文件描述符表为每个进程维护独立的I/O上下文。

文件描述符标准约定:

  • 0:stdin——标准输入
  • 1:stdout——标准输出
  • 2:stderr——标准错误

以上规定在user/sh.c中可以找到

readwriteopenclose

read(fd,buf,n)

解释:第一个参数fd是文件描述符,第二个参数buf是要复制到的位置,第三个参数n是fd最多可以读取多少字节。

作用:从当前偏移量读取数据,读取完成后偏移量增加n字节,返回读取的字节数

write(fd,buf,n)

解释:将buf中的n字节写入文件描述符,并返回写入的字节数。

作用:返回实际写入的字节数(仅错误时小于n

open("input.txt", O_RDONLY

解释:以某种方式打开文件,如下:

宏定义

功能说明

O_RDONLY

只读

O_WRONLY

只写

O_RDWR

可读可写

O_CREATE

如果文件不存在则创建文件

O_TRUNC

将文件截断为零长度

close(int fd)

解释:关闭文件描述符,使其可以被未来使用的open、pipe或dup系统调用重用。

I/O重定向的实现机制

xv6通过forkexec的分离设计实现灵活的重定向:

在这两个调用之间,shell有机会对子进程进行I/O重定向,而不会干扰主shell的I/O设置。

文件偏移量的共享与独立性

dup

dup 的核心是通过复制文件描述符,实现资源的共享和重定向

dup系统调用复制一个现有的文件描述符,返回一个引用自同一个底层I/O对象的新文件描述符。两个文件描述符共享一个偏移量,就像fork复制的文件描述符一样。

如果两个文件描述符是通过一系列forkdup调用从同一个原始文件描述符派生出来的,那么它们共享一个偏移量。否则,文件描述符不会共享偏移量,即使它们来自于对同一文件的打开调用。

文件描述符与内核的关系
  • 权限隔离:用户态进程通过系统调用(如openread)访问内核管理的文件对象,内核在管理模式(Supervisor Mode)验证权限并执行特权操作(如修改偏移量)
  • 资源抽象:文件描述符背后可能对应磁盘文件(通过文件系统层)、管道(通过日志层)或设备(如控制台驱动),但用户进程无需感知底层差异

5.管道

管道定义

我们看管道(pipes)的定义:

管道是作为一对文件描述符公开给进程的小型内核缓冲区,一个用于读取,一个用于写入。将数据写入管道的一端使得这些数据可以从管道的另一端读取。管道为进程提供了一种通信方式。

仔细看这句话:将数据写入管道的一端使得这些数据可以从管道的另一端读取。

嗯,管道跟队列很像。

我们来看一个程序,来分析它的功能:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);
if (fork() == 0) {
    close(0);
    dup(p[0]);
    close(p[0]);
    close(p[1]);
    exec("/bin/wc", argv);
} else {
    close(p[0]);
    write(p[1], "hello world\n", 12);
    close(p[1]);
}

创建管道

pipe(p) 创建一个管道,并将两个文件描述符存储在数组 p 中。p[0] 是管道的读端,p[1] 是管道的写端。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int p[2];
pipe(p);

设置 argv

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
char *argv[2];
argv[0] = "wc";
argv[1] = 0;

设置 argv 数组,用于传递给 exec 函数。

创建子进程

fork() 创建一个子进程。如果 fork() 返回 0,则表示当前是子进程。

子进程的操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
close(0);
dup(p[0]);
close(p[0]);
close(p[1]);
exec("/bin/wc", argv);
  • close(0); 关闭标准输入(文件描述符 0)。
  • dup(p[0]); 将管道的读端复制到文件描述符 0(标准输入)。
  • close(p[0]); 关闭管道的读端。
  • close(p[1]); 关闭管道的写端。
  • exec("/bin/wc", argv); 使用 exec 执行 [wc](vscode-file://vscode-app/d:/Microsoft VS Code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 程序,wc 程序将从标准输入读取数据。

父进程的操作

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
close(p[0]);

write(p[1], "hello world\n", 12);

close(p[1]);
  • close(p[0]); 关闭管道的读端。
  • write(p[1], "hello world\n", 12); 向管道的写端写入数据 "hello world\n"
  • close(p[1]); 关闭管道的写端。
管道(pipe)的作用和特点

作用

  • 管道用于在父子进程之间传递数据。它提供了一种简单的进程间通信(IPC)机制。

特点

  1. 单向通信
    • 管道是单向的,即数据只能从写端流向读端。如果需要双向通信,需要创建两个管道。
  2. 文件描述符
    • 管道使用文件描述符来标识其读端和写端。pipe(p) 创建的管道会返回两个文件描述符,p[0] 是读端,p[1] 是写端。
  3. 阻塞行为
    • 如果管道的读端没有被打开,写操作会导致进程阻塞,直到有进程打开读端。
    • 如果管道的写端没有被打开,读操作会导致进程阻塞,直到有进程打开写端。
  4. 自动同步
    • 管道提供了自动同步机制,确保数据按顺序传递。
  5. 有限缓冲区
    • 管道有一个有限的缓冲区。如果缓冲区满了,写操作会阻塞,直到有数据被读取。

通过上述代码示例,可以看到管道在父子进程间传递数据的作用。父进程通过管道写入数据,子进程从管道读取数据,并将其作为标准输入传递给 wc 程序,从而实现了进程间的数据传递和通信。

另外,实际上我们看到管道不过是一种传输形式,但是它相比临时文件又有些什么优势呢?

  • 首先,管道会自动清理自己;在文件重定向时,shell使用完/tmp/xyz后必须小心删除
  • 其次,管道可以任意传递长的数据流,而文件重定向需要磁盘上足够的空闲空间来存储所有的数据。
  • 第三,管道允许并行执行管道阶段,而文件方法要求第一个程序在第二个程序启动之前完成。
  • 第四,如果实现进程间通讯,管道的阻塞式读写比文件的非阻塞语义更高效。

6.文件系统

xv6中,有一个完备且齐全的文件系统,也包含很多与文件有关的操作。

Xv6 文件系统管理 数据文件(存储字节数据)和 目录(存储文件/目录引用),形成 树形结构,从 /(根目录)开始。

路径解析方式如下:

  • 绝对路径(如 /a/b/c):从根目录开始
  • 相对路径(如 b/c):基于进程的 当前工作目录,可用 chdir() 更改
1. 重要的文件和目录操作

操作

说明

mkdir("/dir")

创建新目录

`open(“/dir/file”, O_CREATE)

创建新文件

mknod("/console", 1, 1)

创建设备文件,指定主设备号和次设备号

chdir("/a"); chdir("b")

改变当前目录到 /a/b

open("c", O_RDONLY)

访问 c(基于当前目录)

open("/a/b/c", O_RDONLY)

直接访问 /a/b/c

2.Inode 与文件元数据
  • 文件名和文件本身是分离的,文件的核心是 inode(索引结点)
  • Inode 结构体 struct stat(在stat.h中)包含:
    • dev:所属磁盘设备
    • ino:inode 编号(唯一标识文件)
    • type:文件类型(目录/文件/设备)
    • nlink:硬链接数
    • size:文件大小
image-20250306195955667
image-20250306195955667
3.link与unlink

硬链接(link)

  • link("a", "b")b 也指向 ainode
  • fstat() 结果显示相同的 inonlink 计数增加
  • 读取 ab 等效

删除文件(unlink)

  • unlink("a") 仅删除 a 这个名字,文件本体不变(如果 nlink > 0
  • 只有 nlink=0 没有进程打开文件时,inode 才释放
  • unlink("/tmp/xyz") 可创建 无名称临时文件,在进程关闭 fd 或退出时自动清理

7.总结

xv6的主要目标是简单明了,同时提供一个简单的类unix系统调用接口。

文件系统和文件描述符是强大的抽象,然而,正是这样的抽象,使得文件描述符与文件、管道、shell语法等结合使用才造就了xv6甚至Unix。

本书研究了xv6如何实现其类Unix接口,但这些思想和概念不仅仅适用于Unix。任何操作系统都必须在底层硬件上复用进程,彼此隔离进程,并提供受控制的进程间通讯机制。在学习了xv6之后,你应该去看看更复杂的操作系统,以及这些系统中与xv6相同的底层基本概念。


以上部分就是关于xv6文档的第一章的阅读。接下来是对于lab的分析。

请跳转:【6S081】Lab1详解:Xv6 and Unix utilities- 掘金 同时提供一个简单的类unix系统调用接口。

文件系统和文件描述符是强大的抽象,然而,正是这样的抽象,使得文件描述符与文件、管道、shell语法等结合使用才造就了xv6甚至Unix。

本书研究了xv6如何实现其类Unix接口,但这些思想和概念不仅仅适用于Unix。任何操作系统都必须在底层硬件上复用进程,彼此隔离进程,并提供受控制的进程间通讯机制。在学习了xv6之后,你应该去看看更复杂的操作系统,以及这些系统中与xv6相同的底层基本概念。


以上部分就是关于xv6文档的第一章的阅读。接下来是对于lab的分析。

请跳转:【6S081】Lab1详解:Xv6 and Unix utilities- 掘金

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-06-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【翻译】XV6-DRAFT as of September 3,2014 第0章 操作系统接口
操作系统接口 操作系统的任务是让多个程序共享计算机(资源),并且提供一系列基于计算机硬件的但更有用的服务。操作系统管理并且把底层的硬件抽象出来,举例来说,一个文字处理软件(例如word)不需要关心计算机使用的是哪种类型的磁盘。操作系统使得硬件可以多路复用,允许许多程序共同使用计算机并且在同一时间上运行。最后,操作系统为程序间的互动提供受控的方法,因此多个程序可以共享数据、协同工作。 计算机操作系统通过接口向用户程序提供服务。设计一个好的接口是一件困难的事情。一方面,我们希望设计出来的接口足够简单且功能单一(
Tencent JCoder
2018/07/02
6980
MIT 6.S081 (BOOK-RISCV-REV1)教材第一章内容 --- 操作系统接口
操作系统的任务是在多个程序之间共享一台计算机,并提供比硬件本身支持的更有用的服务。操作系统管理和抽象底层硬件,例如:
大忽悠爱学习
2023/10/11
4050
MIT 6.S081 (BOOK-RISCV-REV1)教材第一章内容 --- 操作系统接口
【6S.081】Lab1 Xv6 and Unix utilities
我们看要求中,让我们去看user中的调用参数示例,这也是我们后续使用的很多的一个范式。
Skrrapper
2025/06/14
1210
【6S.081】Lab1 Xv6 and Unix utilities
从零实现操作系统-Lab 1: Unix utilities
实现 UNIX 程序 的sleep,使进程睡眠若干个滴答周期(滴答是 xv6 内核定义的时间概念,即来自定时器芯片的两次中断之间的时间。)。代码在 user/sleep.c 中实现。
嵌入式与Linux那些事
2021/09/28
1.2K0
xv6(19) SHELL交互程序
$shell$ 诸位应该很熟悉,它获取控制台的输入,然后执行一定的任务,实现人机交互。本文主要通过 $xv6$ 来看看如何实现一个简单的 $shell$,$shell$ 实现分为两个主要步骤,一解析输入的命令字符串,二执行命令。
rand_cs
2023/12/10
5020
MIT_6.s081_Lab1:Xv6 and Unix utilities
输入 file ./kernel/kernel载入符号表,然后target remote loaclhost:26000即可:
用户7267083
2022/12/08
8980
MIT_6.s081_Lab1:Xv6 and Unix utilities
MIT 6.S081 Lab One -- Util
xv6中的sleep函数本质就是软件定时器的实现,但是其思路并不是在每次时钟中断发生时,唤醒所有到期的定时任务,而是直接唤醒所有睡眠的任务,让其自身去检查是否睡够了,如果没睡够,那么就继续接着睡。
大忽悠爱学习
2023/10/11
6470
MIT 6.S081 Lab One -- Util
6.S081/6.828: 1 Lab Xv6 and Unix utilities
第一个Lab是实现几个shell工具,每个工具都是一个可以独立运行的main函数,会调用系统调用,但其本身并不是系统调用。
冰寒火
2022/10/20
1.1K0
[mit6.s081] 笔记 Lab1: Unix utilities | Unix 实用工具
This lab will familiarize you with xv6 and its system calls.
Miigon
2022/10/27
1.3K0
MIT6.828实验2 —— Lab Shell
xv6中提供有sh.c的实现,除了重定向和管道,还对括号、列表命令、后台命令等做了支持,且整体设计较为复杂。所以我们无需过多参考已有代码,可以选择简单的思路来满足需求,在完成后再去阅读xv6的shell实现。
zhayujie
2020/12/10
1.8K0
Mit6.S081-实验1-Xv6 and Unix utilities
参考:https://pdos.csail.mit.edu/6.828/2020/labs/util.html
全栈程序员站长
2022/11/10
8430
Mit6.S081-实验1-Xv6 and Unix utilities
MIT6.828实验1 —— Lab Utilities
在实验之前,推荐阅读一下官网LEC1中提供的资料。其中Introduction是对该课程的的概述,examples则是几个系统编程的样例,这两部分快速浏览一遍即可。对于xv6 book的第一章,则建议稍微细致地阅读一遍,特别是对fork()、exec()、pipe()、dup()这几个系统调用的介绍,会在后面实验中用到。
zhayujie
2020/06/07
2.5K0
【操作系统】进程间的通信——管道
进程间的通信—管道 管道 进程间的通信(IPC-Inter-Process Communication)有多种方式,管道是其中最基本的方式。 管道是半双工的,即是单向的。 管道是FIFO(先进先出)的。 在实际的多进程间通信时,可以理解为有一条管道,而每个进程都有两个可以使用管道的"端口",分别负责进行数据的读取与发送。 单进程中的管道:int fd[2] 使用文件描述符fd[1],向管道写数据。 使用文件描述符fd[0],从管道中读数据。 注意: 单进程中的管道无实际用处,管道用于多进程间
半生瓜的blog
2023/05/13
7800
【操作系统】进程间的通信——管道
linux系统编程之管道(一):匿名管道和pipe函数
该文介绍了Linux环境下C++编写高性能可扩展的进程通信程序的设计和实现,主要包括管道、命名管道、信号、共享内存、消息队列、互斥量等进程间通信方式,以及通过Boost.Asio库实现的高性能TCP/UDP通信,并结合实际应用场景给出了性能测试和对比分析。
s1mba
2017/12/26
2.6K0
linux系统编程之管道(一):匿名管道和pipe函数
MIT_6.s081_Lab
输入 file ./kernel/kernel载入符号表,然后target remote loaclhost:26000即可:
用户7267083
2023/03/20
1.4K0
MIT_6.s081_Lab
xv6(13) 文件系统:文件描述符&系统调用
本文需要接着系统调用,也是接着 $xv6$ 文件系统的最后一层,讲述各种具体的文件系统调用是怎么实现的,文件描述符,$inode$,文件之间到底有什么关系,创建打开关闭删除文件到底是何意义,文件删除之后数据就不存在了吗,链接又作何解释等等问题,看完本文相信你能找到答案。
rand_cs
2023/12/06
7580
Linux 的进程间通信:管道
本文介绍了管道(pipe)在Linux系统中的实现方式,从三个方面进行了详细阐述:管道的原理,命名管道,以及通过匿名管道进行的进程间通信。同时,文章还探讨了管道在Linux系统中的实际应用,包括shell脚本、cron任务以及Linux中的各种守护进程等。
邹立巍
2017/07/31
8.7K3
Linux 的进程间通信:管道
【进程间通信】IPC、管道pipe、命名管道FIFO
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
mindtechnist
2024/08/08
3940
【进程间通信】IPC、管道pipe、命名管道FIFO
xv6(17) 进程三:代码部分
本文接着上文深入理解进程之数据结构篇来讲述有关进程的一些操作,主要就是创建,调度切换,加载程序,休眠唤醒,等待退出等等,一个一个来看
rand_cs
2023/12/07
5080
Unix-Linux编程实践教程-chapter10-io
Unix默认规定程序从文件描述符0读取数据,写数据到文件描述符1,将 错误信息输出到文件描述符2.这三个文件描述符称为标准输入,标准输出 和标准错误输出
零式的天空
2022/03/02
6240
相关推荐
【翻译】XV6-DRAFT as of September 3,2014 第0章 操作系统接口
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验