MIT6.S081学习历程 --Lec01笔记

简介

Lec01主要讲的是关于本课的一些细节以及主题, 然后稍微讲了一些系统调用, homework也偏简单
这里只对课上一些示例系统调用进行讲解, 其实还有很多系统调用, 大家可以自行阅读XV6参考书籍
这里推荐一篇常见系统调用的概览, 希望能帮到大家
https://zhuanlan.zhihu.com/p/653161495

系统调用

系统调用是操作系统提供的服务的接口, 其实本课贯穿始终的主题就是学习最基本的系统调用, 并且自己动手实现完善一些系统调用
从而不断地扩展以及提升

一些基础的系统调用示例

copy

这个是课堂上老师展示的第一个系统调用, 利用熟悉的C语言编写

可以看出来该系统调用就是读取输入的数据, 然后再输出出来
read() 接受三个参数,
1.文件描述符, 0一般表示连接到console的输入
2.指向某段内存的指针, 用来保存读入的数据
3. 代码想读取的最大长度

open


第二个示例, open传入两个参数, 返回的是文件描述符

  1. 文件名
  2. 标志位, 用来告诉内核open调用的实现, 这里代表我们要创建并且写入文件

现在两个系统调用都涉及到了文件描述符, 其实这就是存储在内核中的一个表单数据, 每一个都对应一个文件描述符, 其实可以理解
为文件指针, 注意每个进程都有属于自己的文件描述符

fork

这是我认为最重要的一个系统调用, 因为他会创建子进程, 多进程是操作系统最具魅力的地方

fork会拷贝当前进程内存, 然后创建一个新的进程
fork系统调用会在两个进程中都会返回, 其中父进程返回的是子进程的PID, 子进程会返回0, 这是我们区分父子进程的重要性质

在XV6中,除了fork的返回值,两个进程是一样的。两个进程的指令是一样的,数据是一样的,栈是一样的,同时,两个进程又有各自独立的地址空间,它们都认为自己的内存从0开始增长,但这里是从不同的内存中从0增长

exec

这个系统调用会从指定的文件中读取并加载指令,并替代当前调用进程的指令。

从示例中可以看出来exec有俩参数

  1. 一个文件指针, 可以打开文件运行其中的指令, 比如这里就是打开存放echo指令的文件
  2. 一个指针数组, 存放的是你想输入的指令, 比如这里就相当于输入echo this is echo, 0表示结尾

所以这里XV6会这样输出

很有意思!
注意:

exec系统调用会保留当前的文件描述符表单。所以任何在exec系统调用之前的文件描述符,例如0,1,2等。它们在新的程序中表示相同的东西。
通常来说exec系统调用不会返回,因为exec会完全替换当前进程的内存,相当于当前进程不复存在了,所以exec系统调用已经没有地方能返回了。

接下来有一个综合例子

解读: 利用fork创建新进程, 子进程会进行exec调用, 输出THIS IS ECHO, 而父进程要等待子进程结束, 然后使用wait系统调用
来获得子进程的结束状态

这里又涉及一个很重要的系统调用wait

wait

int wait(int *status)用于等待一个子进程的退出,并将子进程的退出状态存储在status指针指向的位置。它返回子进程的PID。
这里有一个示例代码来展示如何使用wait来等待子进程终止

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程代码
        printf("This is the child process.\n");
        sleep(2); // 子进程休眠2秒后退出
        return 42;
    } else {
        // 父进程代码
        printf("This is the parent process. Child PID: %d\n", pid);

        int childStatus;
        pid_t terminatedChildPid = wait(&childStatus);
        if (terminatedChildPid == -1) {
            perror("wait failed");
            return 1;
        }

        if (WIFEXITED(childStatus)) {
            printf("Child process %d exited with status: %d\n", terminatedChildPid, WEXITSTATUS(childStatus));
        } else {
            printf("Child process %d terminated abnormally.\n", terminatedChildPid);
        }
    }

    return 0;
}

注意:

需要注意的是,wait()只会等待直接子进程的终止,如果有多个子进程,需要逐个调用wait()来等待它们的终止。此外,如果不关心子进程的退出状态,可以将status参数设置为NULL,这样wait()将不会返回退出状态。

然后再次回到上一个fork与exec的例子, 我们可以稍微修改一下

这里修改了exec的第一个参数指针, 使得exec找不到应该使用的指令, 所以不会返回, 而是继续执行下面的程序
所以会输出failed, 并且返回1
注意这里的1就可以被父进程的wait捕获, 从而知道子进程的退出状态为1, 有问题!

I/O重定向

Shell有一些基础的重定向输入输出的功能, 具体可以参见课上讲解
这里的示例让我们知道了重定向的本质
因为XV6中 1通常作为console的输出文件符, 这里我们直接关闭1这个文件描述符, 然后
转而用open系统调用, 这里1就会重新被分配到output输出中
同时巧妙的是, 我们fork了子进程, 只在子进程中修改文件描述符, 这样的话父进程不会
受到任何影响
这是因为我们之前讲的文件描述符在每个进程中都在独有的一个表单中

注意:

代码第16行的open一定会返回1,因为open会返回当前进程未使用的最小文件描述符序号。因为我们刚刚关闭了文件描述符1,而文件描述符0还对应着console的输入,所以open一定可以返回1。在代码第16行之后,文件描述符1与文件output.txt关联。

至此Lec01的课程就结束了
PS: 感觉最后同学询问的问题挺有意思的

总结

Lec01首先总起了本门课程, 介绍了一些注意事项以及本门课的目的
之后浅尝辄止地介绍了一些系统调用, 让我们对操作系统提供的系统调用有了
一些基础的认知, 感觉真的让人恍然大悟!

posted @ 2023-11-01 21:35  Xingon2356  阅读(1)  评论(0编辑  收藏  举报