MIT6.S081 - Lecture1: Introduction and Examples

课程简介

课程目标

  1. 理解操作系统的设计和实现
  2. 通过 XV6 操作系统动手实验,可以扩展或改进操作系统

操作系统的目标

  1. Abstraction:对硬件进行抽象
  2. Multiplex:在多个应用程序之间共用硬件资源
  3. Isolation:隔离性,程序出现故障时,不同程序之间不能相互干扰
  4. Sharing:实现共享,如数据交互或协同完成任务
  5. Security:想分享的时候可以分享,不想分享的时候可以不分享,可以称为 Access Control System
  6. Performance:操作系统至少需要不阻止应用程序获得高性能,甚至需要帮助应用程序获取高性能
  7. Range of oses:对于大部分操作系统,需要支持大量不同类型的应用程序

操作系统结构

系统调用函数

read/write 系统调用

  • readwrite 函数在执行时,不会关心读写的数据格式,操作系统中(内核)copy 程序会按照 8bit 的字节流处理数据
  • readwrite 函数中第一个参数代表文件位置,shell 会确保默认情况下,当一个程序启动时,文件描述符 0 连接到 console 的输入,文件描述符 1 连接到了 console 的输出。

open 系统调用

  • 字节流就是一段连续的数据按照字节的长度读取
  • open("output.txt", O_WRONLY | O_CREATE); 第二个参数用来告诉 open 系统调用在内核中的实现:我们将要创建写入一个文件
  • 文件描述符本质上对应了内核中的一个表单数据。内核维护了每个运行进程的状态,内核会为每一个运行进程保存一个表单,表单的 key 是文件描述符。这个表单让内核知道,每个文件描述符对应的实际内容是什么。这里比较关键的点是,每个进程都有自己独立的文件描述符空间,所以如果运行了两个不同的程序,对应两个不同的进程,如果它们都打开一个文件,它们或许可以得到相同数字的文件描述符,但是因为内核为每个进程都维护了一个独立的文件描述符空间,这里相同数字的文件描述符可能会对应到不同的文件

fork 系统调用

  • fork 可以创建新的进程,该进程(子进程)与原进程(父进程)的指令、数据、堆栈、文件描述符表单完全相同

  • fork 有两个返回值,但是有独立的地址空间(都认为内存从 0 开始增长)

    • 在父进程中,返回子进程的 PID
    • 在子进程中,返回 0
    • 如果创建失败,返回-1

wait 系统调用

  • wait 表示等待一个子进程退出

    • 如果调用者没有子进程,则 wait 立即返回-1
    • 如果父进程不关注子进程的退出时状态,可以传一个 0 地址(或者 NULL 指针)给 wait
    pid = wait((int *)0);   // 只要一个子进程退出,wait 就结束,返回该退出的子进程的 pid 号
    
    • 如果父进程关注子进程的退出时状态,可以使用如下方式,status将保存子进程结束时的状态(子进程退出时exit里的参数会被保存到status中)
    int status;
    wait(&status);
    

exit 系统调用

  • exit 系统调用退出程序,并释放资源。exit 需要一个整数状态参数,0 表示成功,1 表示失败

exec 系统调用

  • exec 系统调用使用新内存映像来替换内存的进程
    • exec 会保留当前的文件描述符
    • 通常 exec 不会返回,只有出错时才会返回
_char _*argv[3]; 
argv[0] = "echo"; 
argv[1] = "hello"; 
argv[2] = 0;      // 标记了数组的结尾,也可以用NULL指针
exec("/bin/echo", argv);   // 相当于echo hello
printf("exec error\n");    // exec出错时返回,否则不会运行到这里了
  • 一般来说 fork 下的子进程中调用 exec 函数来替换子进程,但这样会造成资源浪费,因此内核通过使用虚拟内存技术来进行优化

pipe 系统调用

管道的概念

  1. 以文件描述符对的形式提供给进程,一个表示读端,一个表示写端
  2. 本质是一个伪文件,实质为内核缓冲区
  3. 管道实际为内核使用环形队列机制,借助内核缓冲区(4K)实现

管道的局限性

  1. 必须作用在有血缘关系的进程之间
  2. 数据不能进程自己写,自己读(需要两个进程,一个读,一个写)
  3. 采用半双工通信,数据只能单方向流动
  4. 数据不能反复读取

管道函数

int pipe(int fd[2]) :创建并打开管道

  • 参数:fd[0] 读端fd[1] 写端
  • 返回值:0 成功;-1 失败

管道程序举例

int p[2]; 
char *argv[2]; 
argv[0] = "wc"; 
argv[1] = 0; 
pipe(p);            // 创建并打开管道
if (fork() == 0)   // 子进程
{ 
    close(0);     // 释放文件描述符0(标准输入)
    dup(p[0]);    // 使得文件描述符0引用了管道读端p[0]
    close(p[0]);  // 关闭管道读端
    close(p[1]);  // 关闭管道写端 
    exec("/bin/wc", argv);  // wc相当于从管道读数据(这里其实不太理解,管道不是关上了吗)
} 
else             // 父进程
{ 
    close(p[0]);   // 读写只能有一个,父进程将读端关闭
    write(p[1], "hello world\n", 12);  // 向管道写入数据
    close(p[1]);  // 写完将管道关闭
}

管道的读写行为

  1. 读管道:
  • 管道中有数据,read 返回实际读到的字节数

  • 管道中无数据:

    • 管道写端全部关闭read 返回 0
    • 管道写端没有全部关闭read 阻塞等待
  1. 写管道:
  • 管道读端全部关闭,进程异常终止(SIGPIPE 信号导致的)

  • 管道读端没有全部关闭

    • 管道已满,write 阻塞
    • 管道未满,write 将数据写入,并返回实际写入的字节数

File system

文件系统

  1. xv6 文件系统包含了数据文件(拥有字节数组)和目录(拥有对数据文件和其他目录的命名引用)
  2. / 开头的如 /a/b 表示根目录 /a 目录中名为 b 的文件或目录(绝对路径),不以 / 开头的是相对于当前目录的路径(相对路径)

chdir 系统调用

通过 chdir 系统调用可以改变进程的当前目录,如下两个 open 打开了同一个文件

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

创建文件和目录

mkdir 可以创建一个新的目录open 使用 O_CREATE 可以创建新的数据文件mknod 可以创建一个新的设备文件

mkdir("/dir");
fd = open("/dir/file", O_CREATE | O_WRONLY);
close(fd);

mknod("/console", 1, 1);  // 主设备号,次设备号,唯一标识一个内核设备

当一个进程打开设备文件后,内核会将系统的读写调用转移到内核设备实现上,而不是将它们传递给文件系统

文件链接

  • inode 是操作系统用来储存除了文件名和实际数据的数据结构,它是用来连接实际数据和文件名的;每个 inode 保存着文件的元数据,包括文件的类型(文件 or 目录 or 设备)、长度、内容在磁盘的位置以及文件的链接数量
  • fstat 系统调用从文件描述符引用的 inode 中检索信息
// int fstat(int fd, struct stat*),stat为接收inode信息的结构体,如fstat(fd, &st)
#define T_DIR     1   // Directory
#define T_FILE    2   // File
#define T_DEVICE  3   // Device

struct stat {
  int dev;     // File system's disk device
  uint ino;    // Inode number
  short type;  // Type of file
  short nlink; // Number of links to file
  uint64 size; // Size of file in bytes
};
  • link 系统调用创建了一个引用同一个 inode 的文件
open("a", O_CREATE | O_WRONLY);
link("a", "b");  // 创建文件b,且a和b指向同一个inode
  • unlink 系统调用会从文件系统中删除一个文件名,只有当文件链接数为且没有文件描述符引用它时,文件 inode 和存放其内容的磁盘空间才会被释放
unlink("a");   // 删除a,此时只有b引用inode

// 创建临时文件的一种惯用方式,它创建一个无名称的inode,在进程关闭fd或退出时删除文件
fd = open("/tmp/x", O_CREATE | O_WRONLY);
unlink("/tmp/x");

Linux 命令

xargs 命令

  • 作用:将标准输入转为命令行参数;xargs 的作用在于,大多数命令(比如 rmmkdirls)与管道一起使用时,都需要 xargs 将标准输入转为命令行参数。
$ echo "hello world" | xargs echo
hello world

上面的代码将管道左侧的标准输入,转为命令行参数 hello world,传给第二个 echo 命令

$ echo "one two three" | xargs mkdir

上面的代码等同于 mkdir one two three。如果不加 xargs 就会报错,提示 mkdir 缺少操作参数

  • 命令格式如下:
$ xargs [-options] [command]

真正执行的命令,紧跟在 xargs 后面,接受 xargs 传来的参数

posted @ 2024-04-15 21:03  jll133688  阅读(58)  评论(0编辑  收藏  举报