MIT 6.s081课程实验 Lab1

Lab1
Lab1实现几个简单函数,目的是促进我们对xv6这个小系统的了解

sleep

  1. 在/user文件夹中添加sleep.c文件如下,主要工作是解析参数->调用sleep的系统调用
  2. 在Makefile文件的UPROGS中仿照其他功能添加_sleep选项,这样就能在shell中使用sleep方法
  3. 测试:运行make qemu后,在shell中运行sleep x,就会阻塞x秒,不过什么都不会发生
#include "kernel/types.h"
#include "user/user.h"

int
main(int argc, char *argv[]){
    if(argc != 2){
        fprintf(2, "Usage: sleep sleep_time\n");
        exit(1);
    }
    int sleep_time = atoi(argv[1]);
    sleep(sleep_time);
    exit(0);
}

ping pong

此小实验使用匿名管道pipe进行父子进程通信,管道是半双工通信方法,要实现父子进程信息的相互传输,则需要2个管道,每个管道负责1个方向的信息传输,下标为0表示读端,1表示写端。

#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char *argv[]){
    int p_child_to_parent[2];
    int p_parent_to_child[2];
    //0: read 1: write
    pipe(p_child_to_parent);
    pipe(p_parent_to_child);
    if(fork() == 0){ //child process
        printf("%d: received ping\n", getpid());
        char ch;
        close(p_parent_to_child[1]);
        close(p_child_to_parent[0]);
        read(p_parent_to_child[0], &ch, 1);
        close(p_parent_to_child[0]);
        write(p_child_to_parent[1], &ch, 1);
        close(p_child_to_parent[1]);
        exit(0);
    } else{
        char ch = 'z';
        close(p_child_to_parent[1]);
        close(p_parent_to_child[0]);
        write(p_parent_to_child[1], &ch, 1);
        close(p_parent_to_child[1]);
        wait(0);
        read(p_child_to_parent[0], &ch, 1);
        close(p_child_to_parent[0]);
        printf("%d: received pong\n", getpid());
    }
    exit(0);
}

问题(闲暇时查看)

父子进程为什么共享管道的文件描述符?
为什么要关闭不需要的文件描述符,不关闭会有什么问题?
管道读写方法是阻塞的还是非阻塞的?
阻塞指的是什么?

primes

使用管道和多进程实现了一个简单的线性筛。对于每个素数,进行打印后,为其创建1个管道,只有与此素数互质的数才能“流进“此管道。注意需要wait子进程以回收其资源,并且才能确保所有进程都运行结束,主进程才结束。

#include "kernel/types.h"
#include "user/user.h"
void get_primes(int *from_pipe){
    close(from_pipe[1]);
    // 'read' returns zero when the write-side of a pipe is closed.
    int cur_prime;
    if (read(from_pipe[0], &cur_prime, 4) == 0)
    {
        close(from_pipe[0]);
        exit(0);
    }
    printf("prime %d\n", cur_prime);
    int num;
    int to_pipe[2];
    pipe(to_pipe);
    if  (fork() == 0) {
        get_primes(to_pipe);
    } else {
        close(to_pipe[0]);
        while (read(from_pipe[0], &num, 4))
        {
            if (num % cur_prime)
            {
                write(to_pipe[1], &num, 4);
            }
        }
        close(from_pipe[0]);
        close(to_pipe[1]);
        wait(0); // 若不等待子进程结束,shell提前停止
    }
}
int main(int argc, char *argv[]){
    int p[2];
    pipe(p);

    if(fork() == 0){ //子孙进程
        get_primes(p);
    } else{ //主进程
        close(p[0]);
        for(int i = 2; i <= 35; i ++) write(p[1], &i, 4);
        close(p[1]);
        wait(0);
    }
    exit(0);
}

小小知识点

若read出错,则返回值为-1,否则返回值是读取的字节数,因此若返回0,说明管道已经读取完毕,即写端已经关闭。

find

递归地查找某个文件的路径。可参考ls.c是如何实现的,思路是:

  1. 使用fstat系统调用获得打开文件路径后的文件描述符fd对应的stat,用于描述文件的信息,其定义如下:

    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
    };
    
  2. 根据stat的type,若type是T_FILE,表示普通文件,则对比此路径的文件名是否和目标文件名相同,相同则直接输出完整路径;若type为T_DIR,则使用递归地读取此目录的内容,目录中存放的是子目录或者子文件的目录项,目录项的数据结构如下,利用name继续往下递归。

    struct dirent {
      ushort inum;
      char name[DIRSIZ];
    };
    

通过这个小实验,我们初步了解了操作系统的文件系统构成,文件包括普通文件/设备/目录,每个文件都有1个目录项,目录项中存放其路径和inum,inum其实是文件inode的下标,inode是每个文件的唯一索引,其定义如下,用来存储文件的元数据信息,如ref引用计数,表示有多少个文件描述符指向此文件;valid表示此文件是否已经从磁盘加载到内存中等等。

// in-memory copy of an inode
struct inode {
  uint dev;           // Device number
  uint inum;          // Inode number
  int ref;            // Reference count
  struct sleeplock lock; // protects everything below here
  int valid;          // inode has been read from disk?

  short type;         // copy of disk inode
  short major;
  short minor;
  short nlink;
  uint size;
  uint addrs[NDIRECT+1];
};

完整代码:

#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));
  buf[strlen(p)] = 0;
  return buf;
}
void
find(char *path, char *target_file)
{
  char buf[512], *p;
  int fd;
  struct dirent de;
  struct stat st;

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

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

  switch(st.type){
  case T_FILE:
    if(strcmp(fmtname(path), target_file) == 0)
        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 || strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) continue;
      memmove(p, de.name, DIRSIZ);
      p[DIRSIZ] = 0;
      find(buf, target_file);
    }
    break;
  }
  close(fd);
}

int
main(int argc, char *argv[])
{

  if(argc != 3){
    fprintf(2, "Usage: find <dirname> <filename>\n");
    exit(1);
  }
  find(argv[1], argv[2]);
  exit(0);
}

xargs

题意是说 xargs后面紧跟着一个命令:xargs [command],当然这个命令可能不只一个单词,而是包括多个单词组成的命令,如xargs echo bye。紧接着,从标准输入中逐行为此命令读取附加的参数arg1 arg2 ...,然后执行命令[command] arg1 arg2 ...。对于从标准输入中的每一行,都要执行一次命令。
提示1:可以使用forkexec,即当从标准输入读到一行,则fork出1个子进程去exec该拼接命令;
提示2:可逐个读取字符,当字符为\n则为一行结束标识;但其实参考sh.c,在user/ulib.c中有1个gets函数,可以用来从标准输入中读取一行,至于为什么会想到去sh.c查看参考,是因为sh.c是shell命令,是和标准输入交互最多的模块。
提示3:使用MAXARG来定义1个参数数组。

#include "kernel/types.h"
#include "kernel/param.h"
#include "user/user.h"
#define MAXARGS 10
int main(int argc, char* argv[])
{
    // 保存可执行命令
    char* command[MAXARG];
    for (int i = 1; i < argc; i ++)
    {
        command[i - 1] = argv[i];
    }
    char buf[100];
    while (gets(buf, 100) && buf[0] != 0) //从标准输入中读取到一行
    {
        int argv_idx = argc - 1;
        //根据空格划分每个参数
        int len = strlen(buf), start_pos = 0;
        buf[len] = ' ';
        for (int i = 0; i <= len; i ++)
        {
            if (buf[i] == ' ')
            {
                if (i - start_pos > 1) //有效参数产生
                {
                    int argv_len = i - start_pos;
                    command[argv_idx] = (char*)malloc(sizeof(char) * argv_len);
                    memset(command[argv_idx], 0, sizeof(command[argv_idx]));
                    memcpy(command[argv_idx], buf + start_pos, argv_len - 1);
                    argv_idx ++;
                }
                start_pos = i + 1;
            }
        }
        if (fork() == 0)
        {
            //将char *[10]强转为char **和exec参数类型匹配
            exec(command[0], command); //exec的参数是这样不 comand数组最后一个字符串是不是空串
            for (int i = argc; i < argv_idx; i ++)
            {
                free(command[i]);
            }
            exit(0);
        }
        wait(0);
    }
    exit(0);
}

总结

实验1是最简单的,让我们先对文件系统,进程通信,系统调用,内存管理这4个部分都有了一定了解,接下来就继续看每个小部分了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值