MIT S081 Lab 1

Lab 0 Utilitites

题目1:primes

题目:(写一个程序,找质数)
Your goal is to use pipe and fork to set up the pipeline. The first process feeds the numbers 2 through 35 into the pipeline. For each prime number, you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe. Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

Some hints:

Be careful to close file descriptors that a process doesn’t need, because otherwise your program will run xv6 out of resources before the first process reaches 35.
Once the first process reaches 35, it should wait until the entire pipeline terminates, including all children, grandchildren, &c. Thus the main primes process should only exit after all the output has been printed, and after all the other primes processes have exited.
Hint: read returns zero when the write-side of a pipe is closed.
It’s simplest to directly write 32-bit (4-byte) ints to the pipes, rather than using formatted ASCII I/O.
You should create the processes in the pipeline only as they are needed.
Add the program to UPROGS in Makefile.
Your solution is correct if it implements a pipe-based sieve and produces the following output:

    $ make qemu
    ...
    init: starting sh
    $ primes
    prime 2
    prime 3
    prime 5
    prime 7
    prime 11
    prime 13
    prime 17
    prime 19
    prime 23
    prime 29
    prime 31
    $

解答:本程序是要用Linux的pipe和fork写出一个寻找质数的程序。先把2-35都输入到管道,第一个fork出来的process 1中,将能被自己整除的数都抛弃,不能被整除的数通过pipe传给下一个process。

注意:fork() 后返回0的表示该进程是子进程

题目2:find

题目内容:实现linux的find函数,find dir name 调用该命令。要能够递归的检查dir目录的子目录,但是不要对“.”和“…”进行递归。
思路:首先搞明白 linux 是怎么获取目录的。

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


// get file name
char* fmtname(char *path)
{
  static char buf[DIRSIZ+1]; // DIRSIZ = 14
  char *p;

  // find the 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), ' ', DIRSIZ-strlen(p));
  return buf;
}


// file path is viewed as a special file type in linux system
void findFile(char *path, char *name)
{
  char buf[512];    // buffer to stoll file path    
  struct dirent de; // directory struct
  struct stat st;   // file struct
  int fd;           // file descriptor

  // 1 open file path as only read mode
  fd = open(path, 0);
  if(fd < 0){
    // fd 2 means error output
    fprintf(2, "find: cannot open %s\n", path);
    return;
  }

  // 2 get file status "st" from file descriptor "fd"
  if(fstat(fd, &st) < 0) {
    fprintf(2, "find: cannout stat %s\n", path);
    return;
  }

  // 3 confirm file type (file or directory)
  switch(st.type){
    case T_DEVICE: // device type
    case T_FILE:   // file type
      if(strcmp(fmtname(path), name) == 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);// copy directory path to buffer
      char *p = buf + strlen(buf);
      *p++='/';
      // ** read directory info into "de" from file descriptor "fd"
      while(read(fd, &de, sizeof(de)) == sizeof(de)){
        if(de.inum == 0) // directory "de" is empty directory!
          continue;
        memmove(p, de.name, DIRSIZ); // concatenate file name and directory
        p[DIRSIZ] = 0;
        if(stat(buf, &st) < 0) { // get new file stat in directory 'path'
          printf("find: cannout stat %s\n", buf);
          continue;
        }
        if(st.type == T_DEVICE || st.type == T_FILE) {
	  if(strcmp(fmtname(buf), name) == 0) {
	    printf("%s\n", buf);
 	  }	
	} else if(st.type == T_DIR && strcmp(fmtname(buf), ".") && strcmp(fmtname(buf), "..")) {
	  // if st.type is directory and it is not "." nor ".."
	  // continue find name in the child directory
	  findFile(buf, name);
	}
      }
      break;
    default:
      break;
  }
  close(fd);
  return;
}


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

  if(argc != 3) {
    printf("not enough args num%d!\n", argc);
    exit(0);
  }

  filePath = argv[1];
  name = argv[2];

  findFile(filePath, name);
  exit(0);
}

20240730 qemu运行后,mkdir创建目录,进入该目录后执行ls,mkdir等等命令都提示exec failed,不知道为什么,待定位

XV6 gdb调试器使用

XV6 gdb使用教程
XV6 GDB调试

在一个窗口运行 make qemu-gdb,在另外一个串口运行 gdb 或者 ( riscv64-linux-gnu-gdp

常用命令:
b:设置断点,例如

b _entry

设置断点:break <location>
location 可以是内存地址或者函数名字 Locations can be memory addresses (“*0x7c00”) or names (“monbacktrace”, “monitor.c:71”).
c: continue
bt: backtrace
s: step, 往下运行一步
si:(step instruction) 跳到下个汇编指令
在这里插入图片描述p: print,打印指定表达式的值

为了使调试更简单,执行

make CPUS=1 qemu-gdb

CPUS=1使代码只运行在一个CPU上
qemu-gdb运行
注意这里打开的TCP端口是26000。另开一个Terminal窗口,切到xv6-labs-2020目录下,切到util分支,执行

riscv64-unknown-elf-gdb kernel/kernel

然后在gdb环境中执行下行命令监听该TCP端口

(gdb) target remote localhost:26000

在这里插入图片描述
执行 b _entry 给程序设置断点

b _entry

20240804 用gdb调试后,发现进入新建的文件夹后,exec执行任何命令,扫描当前文件夹的inode值都是0,都会返回失败,不知道为什么

输入 layout split ,显示代码运行到哪一行

linux syscall的所有系统调用号在syscall.h中
系统调用号
kernel/sysfile.c 中包含了SYS_exec函数的实现

kernel/exec.c 代码分析

exec 系统调用用于加载并运行一个新的程序,它接收一个程序路径和一组参数,并用这个新程序替换当前进程的执行上下文。下面是对这段代码的详细分析:

#include "types.h"
#include "param.h"
#include "memlayout.h"
#include "riscv.h"
#include "spinlock.h"
#include "proc.h"
#include "defs.h"
#include "elf.h"

static int loadseg(pde_t *pgdir, uint64 addr, struct inode *ip, uint offset, uint sz);

// exec函数参数:
// path:字符串指针,指向要执行的程序路径
// argv:字符串指针 数组, 每个元素指向一个字符串,代表一个命令行参数。最后一个元素为 NULL 表示参数列表的结束
int
exec(char *path, char **argv)
{
  char *s, *last;
  int i, off;
  uint64 argc, sz = 0, sp, ustack[MAXARG+1], stackbase;
  struct elfhdr elf;
  struct inode *ip;
  struct proghdr ph;
  pagetable_t pagetable = 0, oldpagetable;
  struct proc *p = myproc();

  begin_op();
  
  // 首先通过namei函数打开path路径指定的文件
  // 如果文件不存在或无法打开, 则exec函数 return -1
  // 成功打开则锁定文件的inode[ip]
  if((ip = namei(path)) == 0){
    end_op();
    return -1;
  }
  ilock(ip);  // 锁定文件的inode

  // 读取ELF header**1,检查文件是否正确 
  // Check ELF header
  if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;
  if(elf.magic != ELF_MAGIC)
    goto bad;

  // 为新程序分配页表。
  if((pagetable = proc_pagetable(p)) == 0)
    goto bad;

  // Load program into memory.
  // 从 ELF 程序头部中读取程序段并将其加载到内存中。
  // 对于每个程序段,调用 loadseg 函数加载数据到相应的虚拟地址。
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz)) == 0)
      goto bad;
    sz = sz1;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  iunlockput(ip);
  end_op();
  ip = 0;

  p = myproc();
  uint64 oldsz = p->sz;

  // 为新程序分配足够的堆栈空间。
  // Allocate two pages at the next page boundary.
  // Use the second as the user stack.
  sz = PGROUNDUP(sz);
  uint64 sz1;
  if((sz1 = uvmalloc(pagetable, sz, sz + 2*PGSIZE)) == 0)
    goto bad;
  sz = sz1;
  uvmclear(pagetable, sz-2*PGSIZE);
  sp = sz;
  stackbase = sp - PGSIZE;

  // 将参数字符串复制到堆栈上,并记录参数的地址
  // Push argument strings, prepare rest of stack in ustack.
  for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp -= strlen(argv[argc]) + 1;
    sp -= sp % 16; // riscv sp must be 16-byte aligned
    if(sp < stackbase)
      goto bad;
    if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[argc] = sp;
  }
  ustack[argc] = 0;

  // push the array of argv[] pointers.
  sp -= (argc+1) * sizeof(uint64);
  sp -= sp % 16;
  if(sp < stackbase)
    goto bad;
  if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
    goto bad;

  // arguments to user main(argc, argv)
  // argc is returned via the system call return
  // value, which goes in a0.
  p->trapframe->a1 = sp;

  // Save program name for debugging.
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(p->name, last, sizeof(p->name));
    
  // Commit to the user image.
  oldpagetable = p->pagetable;
  p->pagetable = pagetable;
  p->sz = sz;
  // 设置初始的程序计数器(PC)和堆栈指针(SP)。
  // 更新 trapframe 中的 PC 和 SP。
  p->trapframe->epc = elf.entry;  // initial program counter = main
  p->trapframe->sp = sp; // initial stack pointer
  proc_freepagetable(oldpagetable, oldsz);

  return argc; // this ends up in a0, the first argument to main(argc, argv)

 bad:
  if(pagetable)
    proc_freepagetable(pagetable, sz);
  if(ip){
    iunlockput(ip);
    end_op();
  }
  return -1;
}

// Load a program segment into pagetable at virtual address va.
// va must be page-aligned
// and the pages from va to va+sz must already be mapped.
// Returns 0 on success, -1 on failure.
static int
loadseg(pagetable_t pagetable, uint64 va, struct inode *ip, uint offset, uint sz)
{
  uint i, n;
  uint64 pa;

  if((va % PGSIZE) != 0)
    panic("loadseg: va must be page aligned");

  for(i = 0; i < sz; i += PGSIZE){
    pa = walkaddr(pagetable, va + i);
    if(pa == 0)
      panic("loadseg: address should exist");
    if(sz - i < PGSIZE)
      n = sz - i;
    else
      n = PGSIZE;
    if(readi(ip, 0, (uint64)pa, offset+i, n) != n)
      return -1;
  }
  
  return 0;
}

[1] ELF header: ELF (Executable and Linkable Format) header 是 ELF 文件格式的一部分,它是 ELF 文件的起始部分,包含了一系列元数据,用于描述整个 ELF 文件的结构和属性。ELF 文件格式是一种广泛使用的二进制文件格式,用于存储可执行文件、共享库和目标文件(如 .o 文件)。
[2] inode: inode(索引节点)是 Unix 和类 Unix 操作系统(如 Linux)中用于存储文件和目录元数据信息的一种**数据结构。**每个文件和目录在文件系统中都有一个唯一的 inode 与之关联。inode 包含了与文件相关的重要信息,这些信息有助于操作系统管理和访问文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值