【6.S081】第一讲:Introduction

6.S081 第一讲:概述

课程地址:https://www.bilibili.com/video/BV19k4y1C7kA
民间大佬做的课程资料翻译:http://xv6.dgs.zone/

捏妈妈的,这课我从开始看就一直摆,不能摆了,我火速给他干完!

一、操作系统的目标

  • 对硬件抽象
  • 让许多应用程序复用硬件(可以同时运行多个软件,且不相互干涉)
  • 程序间的隔离
  • 程序间的共享 Sharing
  • 安全/权限系统 security
  • Performance
  • Range of OS,能够支持许多不同的功能

二、操作系统概述

用户空间User:运行软件的空间,例如vim,gcc,shell

Kernel:一个单独的特殊程序,管理计算机硬件资源,电脑启动时Kernel就启动
Kernel中提供了一些内置服务,例如

  • 文件系统(文件名等)访问磁盘
  • 对进程的管理
  • 内存分配

每个正在运行的程序都称作进程,其占用一定的内存
内核也提供对进程的某个操作的权限机制,访问控制

课程主要研究内核以及内核提供的接口

系统调用:内核提供的API,跳转到内核来完成某些操作

课程实验环境:QEMU模拟器,RISC-V微处理器指令集,xv6系统

Shell指令的本质:
例如mkdir指令,实际上它是存在于这台计算机上的一个可执行文件,一个程序,输入mkdir指令时会运行这个程序,来完成一些功能。

在一些pwn题中,拿到靶机的shell后会发现能够使用的指令很少,应该就是因为靶机削减了一些不必要的的指令程序。

只能说第一节课确实没啥含金量的知识点,印象里第一节课老师大概也是教了一下我们这个实验用的 xv6 是怎么个用法。

实验

0x00 启动xv6

按6.S081的指导,下载好xv6的源代码后直接make qemu就可以了

使用的系统是ubuntu 20.04,执行make qemu时发现缺少了一些软件包,只需要使用apt指令进行下载安装即可。

0x01 sleep(Easy😋)

这个实验要求编写一个sleep程序并在xv6中使用
达到使得进程暂停用户所输入的时间数的目的。

我的代码:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void main(int argc, char *argv[]) {
	if(argc <= 1) {
		fprintf(2, "sleep: invaild input.\n");
		exit(0);
	}

	int tick = atoi(argv[1]);
	if(tick < 0) {
		fprintf(2, "sleep: invaild input:%s\n", argv[1]);
		exit(0);
	}
	sleep(tick);
	printf("(nothing happens for a little while)\n");
	exit(0);
}

按照实验要求,将sleep.c文件放入user文件夹

然后在Makefile的这个位置填写相关信息即可:

在这里插入图片描述

(被水印挡住的部分是$U/_sleep\

这里就是仅新增了一个条目:$U/_sleep\

然后来测试一下这个实验有没有通过,使用指令

$ ./grade-lab-util sleep

在这里插入图片描述

直接薄纱!

0x02 pingpong(Easy😋)

实验要求:

编写一个使用了unix系统调用的程序来实现两个进程之间的通信
父进程向子进程发送字节,子进程打印"<pid>: received ping",其中<pid>是进程ID
然后子进程在管道中写入字节发送给父进程并推出
父进程读取从子进程发送来的字节然后打印"<pid>: received pong"并推出

我的源代码:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

void main(int argc, char *argv[]) {
	int fd[2];
	int stat;
	pipe(fd);
	char a;
	if(fork() == 0) {
		//son
		while(read(fd[1], &a, 1) == 0);
		printf("%d: received ping\n", getpid());
		write(fd[1], "a", 1);

	}else {
		//father
		write(fd[1], "a", 1);
		wait(&stat);
		while(read(fd[0], &a, 1) == 0);
		printf("%d: received pong\n", getpid());

	}
	exit(0);
}

运行测试指令:

$ ./grade-lab-util pingpong

在这里插入图片描述

薄纱!

0x03 Primes(Moderate/Hard😨)

这个实验有点小离谱,使用进程间通信实现素数算法
然后我代码写的也挺离谱的,一个 unsigned long long 就嗯拆成俩int

我的源代码:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

typedef unsigned long long piper;

piper newPipe() {
    piper p;
    pipe(((int *)&p));
    return p;
}
void pipeWriteInt(piper pipe, int toWrite) {
    write(((int *)&pipe)[1], &toWrite, 4);
}
int pipeReadInt(piper pipe) {
    int toRet;
    int result = read(((int *)&pipe)[0], &toRet, 4);
    return result == 0 ? 0 : toRet;
}
void pipeClose(piper p) {
    close(((int *)&p)[1]);
    close(((int *)&p)[0]);
}
void pipeCloseRead(piper p) {
    close(((int *)&p)[0]);
}
void pipeCloseWrite(piper p) {
    close(((int *)&p)[1]);
}
int getPipeFirst(piper pipe) {
    int t = pipeReadInt(pipe);
    if(t == 0)exit(0);
    fprintf(2, "prime %d\n", t);
    return t;
}
void pipe_sender(piper lpipe, piper rpipe, int first) {
    int i;
    while((i = pipeReadInt(lpipe)) != 0) {
        if(i % first != 0) {
            pipeWriteInt(rpipe, i);
        }
    }
    pipeCloseRead(lpipe);
    pipeCloseWrite(rpipe);
}
void prime(piper lpipe) {
    pipeCloseWrite(lpipe);
    piper rpipe = newPipe();
    int first = getPipeFirst(lpipe);
    pipe_sender(lpipe, rpipe, first);
    if(fork() == 0) {
        prime(rpipe);
    }else {
        wait(0);
    }
}
void main(int argc, char *argv[]) {
    piper p = newPipe();
    int i;
    for(i = 2; i <= 35; i++) {
        pipeWriteInt(p, i);
    }
    if(fork() == 0) {
        prime(p);
    }else {
        pipeClose(p);
        wait(0);
    }
    exit(0);
}

虽然代码离谱,但是运行了测试指令

$ ./grade-lab-util primes

之后,

在这里插入图片描述

仍然薄纱!

0x04 find(Moderate😨)

1. 如何遍历目录啊,你▢▢▢▢的

在欣赏user/ls.c之前,先听首歌放松放松吧

华语乐坛最强歌手丁真新作 I got smoke

显然,只需要使用open函数,向open函数输入一个字符串即可得到对应文件/文件夹的文件描述符。

创建一个struct stat结构体对象,这个对象能保存一个文件描述符描述的对象的一些信息,使用fstat即可将相关信息写入这个结构体对象。

通过fstat可以看到对应文件描述符指向的是一个普通文件还是一个文件夹,如果要遍历文件夹,还需要用到一个struct direct结构体……

等等,你不会觉得我要亲自从头写一遍这个byd代码吧,直接从ls.c的基础上改不就完事了?

user/ls.c还是挺容易懂的,然而懒得自己写了就直接在这个基础上改了。

2. 开始薄纱!

我的代码如下:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char *
fmtname(char *path)
{
  static char buf[DIRSIZ + 1];
  char *p;

  // Find first character after last slash.
  for (p = path + strlen(path); p >= path && *p != '/'; p--)
    ;
  p++;

  // Return blank-padded name.
  if (strlen(p) >= DIRSIZ)
    return p;
  memmove(buf, p, strlen(p));
  memset(buf + strlen(p), '\0', DIRSIZ - strlen(p));
  return buf;
}

void find(char *path, char *name)
{
  char buf[512], *p;
  char *ptr;
  int fd;
  struct dirent de;
  struct stat st;

  if ((fd = open(path, 0)) < 0)
  {
    fprintf(2, "ls: cannot open %s\n", path);
    return;
  }

  if (fstat(fd, &st) < 0)
  {
    fprintf(2, "ls: cannot stat %s\n", path);
    close(fd);
    return;
  }

  switch (st.type)
  {
  case T_FILE:
    printf("%s\n", path);
    break;

  case T_DIR:
    if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf)
    {
      printf("find: path too long\n");
      break;
    }
    strcpy(buf, path);
    p = buf + strlen(buf);
    *p++ = '/';
    while (read(fd, &de, sizeof(de)) == sizeof(de))
    {
      if (de.inum == 0)
        continue;
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      if (stat(buf, &st) < 0)
      {
        printf("find: cannot stat %s\n", buf);
        continue;
      }
      ptr = fmtname(buf);
      if(!strcmp(name, ptr)) {
        printf("%s\n", buf);
      }
      if (st.type == T_DIR && ptr[0] != '.')
      {
        find(buf, name);
      }
    }
    break;
  }
  close(fd);
}

int main(int argc, char *argv[])
{
  int i;
  if (argc < 3)
  {
    find(".", argv[2]);
    exit(0);
  }
  for (i = 2; i < argc; i++)
    find(argv[1], argv[i]);
  exit(0);
}

运行一下测试指令

./grade-lab-util find

薄纱!

在这里插入图片描述

0x05 xargs(moderate🥱)

1. 基本

然而本fw并不知道unix下的xargs指令是怎么玩的,令人感叹。

大概看明白了。首先,对于unix shell的|符号,是这个情况:

例如:

$ echo flag | cat

这个指令将输出flag
为什么会输出flag呢?当我们直接使用cat指令时,我们就可以往程序里输入数据,我们输入什么,程序就输出什么。

在这里插入图片描述

使用|符号,实际上就是把echo指令的 stdout重定向catstdin,也就是说,echo flag | cat就相当于我们输入了一个cat指令之后再在下面输入flag

同样的,

$ echo test2 | grep t

将会输出:

在这里插入图片描述

继续测试一个:

#include<stdio.h>
#include<stdlib.h>

int main() {
        int i;
        scanf("%d", &i);
        printf("%d\n", i);
        return 0;
}

使用gcc编译,输出了一个a.out程序,来运行它:

$ echo 1888|./a.out

在这里插入图片描述

这个scanf没等我们自己输入一个数字,而是直接使用了echoa.outstdin输出的字符串转化为的数字。

2. 开始薄纱!

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

char *newArg[256];
char buf[512];

int readLine(char *b) {
    int i = 0;
    char buffer;
    while(read(0, &buffer, 1) == 1 && buffer != 10) {
        b[i] = buffer;
        i++;
    }
    b[i] = '\0';
    return i;
}

void main(int argc, char *argv[]) {
    int i, k;
    if(argc < 2) {
        fprintf(2, "xargs: too few arguments!\n");
        exit(0);
    }
    while(readLine(buf) != 0) {
        for(i = 0; i < argc - 1; i++) {
            newArg[i] = argv[i + 1];
        }
        newArg[argc - 1] = buf;
        if(fork() == 0) {
            exec(argv[1], newArg);
            exit(0);
        }else {
            wait(&k);
            continue;
        }
    }
    exit(0);
}

运行一下测试指令:

$ ./grade-lab-util xargs

在这里插入图片描述

薄纱!

0x06 实验成绩与提交实验

首先在实验目录创建一个time.txt文件,里面填写一个整数,表示做实验用的时间。

然后尝试用make grade指令测试一下自己的实验成绩:

在这里插入图片描述

满昏,赢!

首先使用以下指令应用我们对实验文件做出的更改:

$ git commit -am "ready to submit my lab"

然后,使用提交网站来提交作业

使用自己的邮箱注册6.S081的网站,然后你就会收到一个邮件,里面包含你的api-key

运行

$ make handin

指令来提交作业。

在这里插入图片描述

初次提交会要求我们输入我们的api-key

可能会因为电脑上缺少一个叫做curl的工具而运行失败,手动apt安装即可

在这里插入图片描述

提交成功!

0x07 实验总结

  • fork()函数:创建一个新的进程,这个新的进程是完全复制当前进程的,且其也执行到父进程执行完毕fork()函数的位置
    并且,在父进程中,fork()函数返回子进程的id,而在子进程中,返回0,因此,可以利用这样的写法使得父进程与子进程的代码执行分离开:

    if(fork() == 0) {
        //子进程的代码
    }else {
        //父进程的代码
    }
    
  • pipe(int pip[2])函数:创建一个管道,管道可以理解成一种特殊的文件,它不存在于硬盘上,只存在于内存中。pipe函数的参数是一个长度为2的int数组,这个函数会为数组中的两个元素赋值,是两个文件描述符。其中,pip[0]是用于向管道中读取数据的文件描述符,pip[1]是用于向管道写入数据的文件描述符。管道机制可以用于进程之间的通信。

    • wait()函数:使一个进程停止,除非它的子进程消失,否则这个进程则会一直卡在执行到wait函数的位置。
  • 遍历文件的方法:

    • open函数可以接受一个字符串,然后返回对应的文件描述符
    • 有一个fstat函数,向里面传进一个文件描述符与一个struct stat对象的指针,即可向这个stat结构体对象中写入有关这个文件描述符的一些数据。
    • st.type保存一个值,表示对应文件描述符的类型,宏定义的常量T_FILE表示这是个文件,T_DIR表示这是个文件夹
    • 之后的兄弟我也看不明白了,反正跟着user/ls.c走就完事了
  • 关于shell中的|符号
    我们平常使用Linux的时候,经常进行这样的操作:ls | grep aaa
    实际上,如果当我们直接输入 grep aaa,程序就会进入一个让我们不断输入内容的模式,
    在这里插入图片描述

    当我们输入的字符串含有aaa,则程序会输出这个字符串,并且把aaa给标红
    执行ls | grep aaa时,实际上ls已经被执行了,但是用这种写法,lsstdout流会被重定向grepstdin流,因此,grep会从ls输出的那些内容中进行读取来完成它自己的任务,其效果就与我们执行grep aaa后,手动把一个目录里的全部文件的名字敲上去一样。


好累好累~
听首歌放松一下吧

[https://music.163.com/song?id=25892489&userid=1344834927](

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值