2023MIT6.828 lab-1
官方地址
一、sleep
实验内容
- 调用sleep(系统调用)编写用户级别程序
- 能暂停特定时常的系统滴答
- 程序保存在user/sleep.c
实验过程
xv6的参数传递
查看官方文档提示的文件中,多采用如下定义:
int main(int argc, char *argv[])
在xv6操作系统中:
1、argc 是一个常见的参数,用于表示传递给程序的命令行参数的数量。
2、argv(argument vector)是一个指向字符指针数组的指针,该数组中的每个元素都是一个指向命令行参数的字符串的指针。
3、程序的内部可通过argc和argv来访问外界输入的参数
注意:
1、argc指示出程序执行时传入的参数个数,常可用来判断输入是否符合要求
2、argv[]为存放字符指针的数组,我们需要的是其指向地址存放的数据,若要当整型数据使用还需进行相应转换
实现代码
#include "kernel/types.h" //调用相应的头文件
#include "kernel/stat.h"
#include "user/user.h"
int main( int argc ,char *argv[]) //通过argc和argv传入参数
{
if(argc!=2) //传入参数数量不符合
{
fprintf(2,"usage:sleep time\n");
exit(1);
}
sleep(atoi(argv[1])); //由于argc[]指向字符型数据,调用atoi()进行转换
fprintf(2,"nothing happens for a little while\n");
exit(0); //退出程序
}
编译准备
在make qemu之前,还需进行将编写的sleep()程序加入编译文件中:
1、进入lab目录下
vi Makefile //打开文件
: ?UPROGS //vim下搜索关键词
2、定位到UPROGS,在最后一处按格式补入sleep():
UPROGS=\
$U/_cat\
$U/_echo\
$U/_forktest\
$U/_grep\
$U/_init\
$U/_kill\
$U/_ln\
$U/_ls\
$U/_mkdir\
$U/_rm\
$U/_sh\
$U/_stressfs\
$U/_usertests\
$U/_grind\
$U/_wc\
$U/_zombie\
$U/_sleep\
3、保存退出
编译运行
make qemu
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ sleep 10
nothing happens for a little while //停顿后显示,达到实验效果
工具测试
在lab目录下执行:
$ ./grade-lab-util sleep
或
$ make GRADEFLAGS=sleep grade
输出:
测试通过
二、pingpong
实验内容
- 在父进程中创建子进程
- 编写用户级程序调用系统调用通过一对pipes在两个进程间实现’‘ping-pong’’ 一个字节
- 父进程给子进程发送一字节,子进程收到后print pid: received ping 这里的pid is its process ID,并通过pipes给父进程传递一字节,然后exit
- 父进程读取来自子进程的字节,并print pid: received pong,然后exit
- 程序保存在user/pingpong.c
实验过程
fork用法
打开/kernel/proc.c查看fork()源码
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
int i, pid;
struct proc *np;
struct proc *p = myproc();
// Allocate process.
if((np = allocproc()) == 0){
return -1;
}
// Copy user memory from parent to child.
if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
freeproc(np);
release(&np->lock);
return -1;
}
np->sz = p->sz;
// copy saved user registers.
*(np->trapframe) = *(p->trapframe);
// Cause fork to return 0 in the child.
np->trapframe->a0 = 0;
// increment reference counts on open file descriptors.
for(i = 0; i < NOFILE; i++)
if(p->ofile[i])
np->ofile[i] = filedup(p->ofile[i]);
np->cwd = idup(p->cwd);
safestrcpy(np->name, p->name, sizeof(p->name));
pid = np->pid;
release(&np->lock);
acquire(&wait_lock);
np->parent = p;
release(&wait_lock);
acquire(&np->lock);
np->state = RUNNABLE;
release(&np->lock);
return pid;
}
该函数的作用:
1、创建一个新进程,并复制父进程的内容
2、为子进程(child process)设置内核栈(kernel stack),以便在子进程执行完毕后,其行为表现得就像是从 fork() 系统调用返回一样
3、父子进程内容相同(除个别数据),但内存空间不同,修改互不影响
4、在父进程中,fork()返回新创建的子进程的进程ID;在子进程中,fork()返回0;如果发生错误,则返回-1
结构体proc的声明
该结构体存储了每一个进程的信息
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
pipes用法
pipe()函数定义
int pipe(int p[]) Create a pipe, put read/write file descriptors in p[0] and p[1].
1、调用pipe函数来创建一个新的管道。如果成功,pipe函数返回0,并将两个文件描述符分别存放在p[0]和p[1]中。如果失败,返回-1。
2、pipe为半双工通信,要根据实际指定通信方向,关闭管道的读或写,保留另一功能
3、可采用两条pipe实现互通
getpid用法
获取当前进程PID
int getpid() Return the current process’s PID.
实现代码
注意:读写顺序
#include "kernel/types.h"
#include "user/user.h"
int main(int argc,int *argv[])
{
int ptc_pipe[2];
int ctp_pipe[2];
pipe(ptc_pipe); //父to子
pipe(ctp_pipe);//子to父
int pid;
pid = fork(); //新建子进程
if(pid==0) //处于子进程
{
//设定管道通信方向
close(ptc_pipe[1]); //子读取
close(ctp_pipe[0]); //子写入
char buff[16];
if( read( ptc_pipe[0],buff,1) ==1 ) //子进程收到父进程的一字节
{
printf("%d: received ping\n",getpid() );
}
write( ctp_pipe[1],"p",1 ); //往父进程发送一字节
exit(0);
}
else //处于父进程
{
//设置管道方向
close( ptc_pipe[0] );// 父写入
close( ctp_pipe[1] );// 父读取
write( ptc_pipe[1],"p",1 ); //往子进程发送一字节
char buff[16];
if( read( ctp_pipe[0],buff,1)==1 )//父进程读取到一字节
{
printf("%d: received pong\n",getpid() );
}
}
exit(0);
}
编译准备
往Makefile中UPROGS=\项目添加 $U/_pingpong\
运行结果
工具测试
make GRADEFLAGS=pingpong grade
测试通过
三、primes
实验内容
1、在xv6中使用pipe编写一个并发的素数筛选程序
2、使用pipe和fork来建立管道
3、第一个进程向管道内输入数字2到35
4、对于每个素数,创建一个进程来从管道左侧读取,并传输给另一条管道右侧
5、受限于xv6的有限文件描述符和进程,第一个进程将在35处停止
6、程序位于user/primes.c
实验过程
过程分析
1、采用pipe进行数字传递,用fork创建下一级,直到结束
2、每级中挑选出一个素数,把剩余经过此素数处理过的传递到下一级
3、传递后父进程要执行等待子进程结束的操作
实现代码
注意:
1、仔细关闭程序不需要的文件描述符,否则在35前xv6的资源会耗尽
2、pipe用完关闭,避免拥塞
3、注意pipe和fork执行顺序
#include "kernel/types.h"
#include "user/user.h"
int children( int ptc_pipe[])
{
//设置管道方向
close( ptc_pipe[1] );//子读取,不需要写入
//对父到子管道进行检查
int num;
if( read( ptc_pipe[0] ,&num,sizeof(num)) == 0 ) //管道空
{
close( ptc_pipe[0]);
exit(0);
}
else
{
printf("prime %d\n",num); //打印素数
}
//创建孙进程
int pid;
int ctg_pipe[2];
pipe( ctg_pipe );//子进程到孙进程管道
pid=fork();
if( pid==0 ) //孙子进程
{
children( ctg_pipe ); //递推
}
else //子进程
{
//设置子到孙管道方向
close( ctg_pipe[0] );//子写孙读
int i;
while( read( ptc_pipe[0],&i,sizeof(i))>0 ) //管道还有数据
{
if( i % num !=0 )//有余数
write( ctg_pipe[1],&i,sizeof(i) );//发送到下一级
}
close( ctg_pipe[1]);//关闭写,避免读拥塞
wait(0); //等待孙进程结束
}
exit(0);//结束返回
}
int main(int argc,int *argv[])
{
int ptc_pipe[2];
//建立第一个管道
pipe(ptc_pipe);
int pid;
pid =fork(); //创建子进程
if( pid==0 ) //位于子进程
{
children(ptc_pipe); //函数反复调用
}
else //位于父进程
{
//管道设置
close(ptc_pipe[0]); //父写入,不需要读取
for(int i=2;i<=35;i++) //往管道输入数字
{
write(ptc_pipe[1],&i,sizeof(i));
}
close( ptc_pipe[1]);//关闭写,避免读拥塞
wait(0);//等待子进程
}
exit(0);
}
编译准备
往Makefile中UPROGS=\项目添加 $U/_primes\
运行结果
工具测试
make GRADEFLAGS=primes grade
测试通过
四、find
实验内容
- 为Xv6编写一个简单版本的UNIX查找程序
- 通过目录树的形式找到特定的名称
- 程序保存在user/find.c
实验过程
线索提示
- 查看user/ls.c如何实现读目录
- 使用递归操作时find能深入子目录
- 使用make clean去获得干净的文件系统,然后make qemu
- 使用C语言的字符串
- 使用strcmp()对比字符串,而不是==
ls.c源码
fmtname用于格式化文件名
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), ' ', DIRSIZ-strlen(p));
return buf;
}
ls为主要部分
void
ls(char *path)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, O_RDONLY)) < 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_DEVICE:
case T_FILE:
printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("ls: 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("ls: cannot stat %s\n", buf);
continue;
}
printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
int i;
if(argc < 2){
ls(".");
exit(0);
}
for(i=1; i<argc; i++)
ls(argv[i]);
exit(0);
}
ls函数中使用的结构体信息
//用于表示一个目录项
struct dirent {
ushort inum; // 本目录下该文件所在的inode号
char name[DIRSIZ]; // 该文件的文件名
};
struct stat { short type; // 文件类型
int dev; // 设备号
uint ino; // inode编号
short nlink; // 链接数
uint size; // 文件大小(字节)
time_t atime; // 最后访问时间
time_t mtime; // 最后修改时间
time_t ctime; // 最后状态改变时间
int uid; // 用户ID
int gid; // 组ID
short rdev; // 特殊设备类型
uint flags; // 文件标志
uint pad[4]; // 填充字段,确保结构大小对齐
};
memmove函数
void *memmove(void *dest, const void *src, size_t n);
//dest:指向目标内存区域的指针,即要将数据移动到的位置。
//src:指向源内存区域的指针,即要从中复制数据的起始位置。
//n:要复制的字节数。
open函数
int open(const char *path, int flags);
//`path`:要打开的文件或设备的路径名。
//`flags`:指定打开文件的方式,例如 `O_RDONLY`(只读)、`O_WRONLY`(只写)、`O_RDWR`(读写)等。
//返回值:非负文件描述符
// 失败返回-1
fstat函数
int fstat(int fd, struct stat *buf);
//fd:文件描述符,即之前通过 open 系统调用获得的用于标识文件的整数。
//buf:一个指向 stat 结构体的指针,该结构体用于存储获取到的文件状态信息。
//返回值:0成功,-1失败
实现代码
实现思路:
- 通过层层搜索
- 遇到文件则判断输出
- 遇到目录则延长到子目录采用递归搜寻
- 在ls.c基础上修改
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
//#include "kernel/fcntl.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++; //+1为'/'后第一个字符
// Return blank-padded name.
if(strlen(p) >= DIRSIZ)
return p;
memmove(buf, p, strlen(p)+1); //注意结束符
return buf;
}
void
findname(char *path,char *findName)
{
char buf[512], *p; //buf用于存储绝对路径,p用于指向buf操作
int fd; //存储文件标识符
struct dirent de; //表示目录项
struct stat st; //存储文件信息
//只读打开path路径
if((fd = open(path, 0)) < 0)
return; //打开错误
if(fstat(fd, &st) < 0) //打开错误
{
close(fd);
return;
}
switch(st.type) //顺利开启
{
case T_DEVICE:
case T_FILE: //path路径为文件
if( strcmp( fmtname(path),findName)==0) //抽取名称并对比,名称一致
{
printf("%s\n", path); //打印路径
}
break;
case T_DIR: //path路径为目录
//目录名过长
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("find: path too long\n");
break;
}
//复制path到buf//buf记录此刻path的绝对路径
strcpy(buf, path);
p = buf+strlen(buf);
*p++ = '/'; //末尾加分隔符//注意此处指针p的指向buf末尾
while(read(fd, &de, sizeof(de)) == sizeof(de)) //读取path路径目录内容//read会遍历一遍此目录下的文件
{
if(de.inum == 0) //inode==0
continue;//不正常的inode,跳出
//跳过. ..目录
if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..")==0 )
continue;
//复制名称到p头地址的空间 //即在buf后面补名字
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0; //地址结束标识符
findname(buf,findName); //递归查询
}
break;
}
close(fd);
}
int
main(int argc, char *argv[])
{
//参数不符合
if(argc <=2)
{
printf("err\n");
exit(0);
}
findname(argv[1],argv[2]);
exit(0);
}
编译准备
往Makefile中UPROGS=\项目添加 $U/_find\
运行结果
make qemu
echo > b
mkdir a
echo > a/b
mkdir a/aa
echo > a/aa/b
find . b
root@QQQ:/home/MIT6.828/xv6-labs-2023# make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ echo > b
mkdir a
echo > a/b
mkdir a/aa
echo > a/aa/b
find . b$ $ $ $ $
./b
./a/b
./a/aa/b
$
符合预期
工具测试
make GRADEFLAGS=find grade
no-common -nostdlib -mno-relax -I. -fno-stack-protector -fno-pie -no-pie -march=rv64g -nostdinc -I. -Ikernel -c user/initcode.S -o user/initcode.o
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e start -Ttext 0 -o user/initcode.out user/initcode.o
riscv64-unknown-elf-objcopy -S -O binary user/initcode.out user/initcode
riscv64-unknown-elf-objdump -S user/initcode.o > user/initcode.asm
riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel/kernel.ld -o kernel/kernel kernel/entry.o kernel/kalloc.o kernel/string.o kernel/main.o kernel/vm.o kernel/proc.o kernel/swtch.o kernel/trampoline.o kernel/trap.o kernel/syscall.o kernel/sysproc.o kernel/bio.o kernel/fs.o kernel/log.o kernel/sleeplock.o kernel/file.o kernel/pipe.o kernel/exec.o kernel/sysfile.o kernel/kernelvec.o kernel/plic.o kernel/virtio_disk.o kernel/start.o kernel/console.o kernel/printf.o kernel/uart.o kernel/spinlock.o
riscv64-unknown-elf-objdump -S kernel/kernel > kernel/kernel.asm
riscv64-unknown-elf-objdump -t kernel/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel/kernel.sym
make[1]: Leaving directory '/home/MIT6.828/xv6-labs-2023'
== Test find, in current directory == find, in current directory: OK (3.1s)
== Test find, recursive == find, recursive: OK (0.9s)
测试通过
五、xargs
实验内容
- 为xv6编写一个简单版本的uinx xargs程序
- 它的参数描述了要运行的命令
- 它从标准输入中读取行
- 它运行每一行的命令
- 将此行文本添加到命令参数中
- 程序存放在user/xargs.c
- 一个实例:
- $ echo hello too | xargs echo bye
- bye hello too
- $
实验过程
实现方法
- 从标准输入中读取命令并执行
- 设置两个数组,一个用于从标准输入中获取每一行的命令及其参数,一个用于将获取的一行指令传递给子程序执行
- 不断读取标准输入,遇到换行符就执行一次命令,直到标准输入中无数据
exec函数
int exec(char *file, char *argv[]) Load a file and execute it with arguments; only returns if error.
- 加载一个文件,并按给定参数执行
实现代码
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/param.h"
#define MAXN 1024
int
main(int argc, char *argv[])
{
if (argc < 2)
{
fprintf(2, "usage: xargs command\n");
exit(1);
}
char *argv_c[MAXARG]; // 存放子进程exec的参数//MAXARG是一个宏定义,用于限制一个进程可以从其命令行参数中获取的最大字节
for (int i = 1; i < argc; i++) //去除xargs,保留余下部分
argv_c[i - 1] = argv[i];
char buf[MAXN]; // 存放从标准输入转化而来的参数
char c = 0;
int stat = 1; // 从标准输入read返回的状态
while (stat) // 标准输入中还有数据
{
int buf_tail = 0; // buf尾指针
int arg_begin = 0; // 当前这个参数在buf中开始的位置
int argv_tail = argc - 1;//argv本身还有参数
while (1) // 读取一行 //每行执行一次
{
stat = read(0, &c, 1); //从标准输入中读取一个字符//标准输入的文件描述符通常为0
if (stat == 0) // 标准输入中没有数据,exit
exit(0);
if (c == ' ' || c == '\n') //此字符为空格或换行//意味着单个参数输入结束或开始换行
{
buf[buf_tail++] = 0; //最后一个参数必需为0,否则会执行失败//先0后自增
argv_c[argv_tail++] = &buf[arg_begin]; //往argv_c中拼接一个参数//字符串操作
arg_begin = buf_tail; //更新下一个参数的起点位置
if (c == '\n') //每此换行执行一次
break;
}
else //拼接读取的单个字符
{
buf[buf_tail++] = c;
}
}
argv_c[argv_tail] = 0; //结束标志
if (fork() == 0)
{
exec(argv_c[0], argv_c); //加载命令并执行
}
else
{
wait(0);
}
}
exit(0);
}
编译准备
往Makefile中UPROGS=\项目添加 $U/_xargs\
运行结果
root@QQQ:/home/MIT6.828/xv6-labs-2023# make qemu
qemu-system-riscv64 -machine virt -bios none -kernel kernel/kernel -m 128M -smp 3 -nographic -global virtio-mmio.force-legacy=false -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0
xv6 kernel is booting
hart 2 starting
hart 1 starting
init: starting sh
$ echo hello too | xargs echo bye
bye hello too
$ find . b | xargs grep hello
hello
hello
hello
$ sh < xargstest.sh
$ mkdir: a failed to create
$ $ mkdir: c failed to create
$ $ $ hello
hello
hello
$ $
工具测试
riscv64-unknown-elf-ld -z max-page-size=4096 -N -e start -Ttext 0 -o user/initcode.out user/initcode.o
riscv64-unknown-elf-objcopy -S -O binary user/initcode.out user/initcode
riscv64-unknown-elf-objdump -S user/initcode.o > user/initcode.asm
riscv64-unknown-elf-ld -z max-page-size=4096 -T kernel/kernel.ld -o kernel/kernel kernel/entry.o kernel/kalloc.o kernel/string.o kernel/main.o kernel/vm.o kernel/proc.o kernel/swtch.o kernel/trampoline.o kernel/trap.o kernel/syscall.o kernel/sysproc.o kernel/bio.o kernel/fs.o kernel/log.o kernel/sleeplock.o kernel/file.o kernel/pipe.o kernel/exec.o kernel/sysfile.o kernel/kernelvec.o kernel/plic.o kernel/virtio_disk.o kernel/start.o kernel/console.o kernel/printf.o kernel/uart.o kernel/spinlock.o
riscv64-unknown-elf-objdump -S kernel/kernel > kernel/kernel.asm
riscv64-unknown-elf-objdump -t kernel/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > kernel/kernel.sym
make[1]: Leaving directory '/home/MIT6.828/xv6-labs-2023'
== Test xargs == xargs: OK (4.2s)
root@QQQ:/home/MIT6.828/xv6-labs-2023#
测试通过