Lab1
相关资料
实验指导书:https://pdos.csail.mit.edu/6.S081/2020/labs/util.html
教材:https://pdos.csail.mit.edu/6.828/2020/xv6/book-riscv-rev1.pdf
实验一主要是熟悉 xv6 操作系统和它的一些系统调用,因此实验就挑了几个做。同时看看有哪些c的知识需要去进一步学习的。
sleep
为 xv6 系统写一个 sleep 程序,调用系统提供的sleep() 函数,让程序暂停指定数量的时钟tick。
这个练习还是比较简单的。
//user/sleep.c
#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用
//规范的调用方法为:sleep tick数
int main(int argc, char* argv[]){
if( argc != 2){ //提示正确的调用方法
printf( "请使用正确的调用方法: %s <ticks>\n", argv[0]);
exit(1);
}
char* arg = argv[1];
int ticks = atoi(arg); //系统中sleep函数接受的是int参数
printf( "系统将sleep%dticks\n", ticks);
sleep(ticks);//调用已经实现的sleep函数:int sleep(int);
printf("sleep完成!\n");
exit(0);
}
完成后在Makefile文件中找到UPROGS,新增一项$U/_sleep\。
//测试方法1,直接执行以下命令
./grade-lab-util sleep
//得到结果
make: “kernel/kernel”已是最新。
== Test sleep, no arguments == sleep, no arguments: OK (1.1s)
(Old xv6.out.sleep_no_args failure log removed)
== Test sleep, returns == sleep, returns: OK (0.7s)
== Test sleep, makes syscall == sleep, makes syscall: OK (1.0s)
(Old xv6.out.sleep failure log removed)
//测试方法2:重新通过make qemu启动内核,直接执行sleep命令进行测试
$ sleep 12 13
请使用正确的调用方法: sleep <ticks>
$ sleep
请使用正确的调用方法: sleep <ticks>
$ sleep 10
系统将sleep10ticks
sleep完成!
pingpong
进程发送一个字符给子进程,子进程接收到后再发送回父进程,最后父进程接收到子进程的回复并打印消息。
代码如下
#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用
int main(int argc, char* argv[]){
//fd1是父进程向子进程通信,即写端由父进程控制,读端子进程控制
//fd2:子进程向父进程通信,即写端由子进程控制,读端父进程控制
//0用来读,1用来写
int fd1[2], fd2[2];
int ans1 = pipe(fd1);
int ans2 = pipe(fd2);
printf( "管道创建情况为:fd1:%d, fd2:%d\n", ans1, ans2);
//创建子进程
int st = fork();
if(st != 0){
//父进程
//向管道写1个字节
write(fd1[1],"a",1);
close(fd1[1]);
char buf = ' ';
//读取一个字节进入buf
read(fd2[0], &buf, 1);
printf("%d: received pong\n", getpid());
printf("after in parent buf : %c\n",buf);
wait(0);
}
else if( st == 0){
//进入子进程,fork创建的子进程共享父进程的代码和数据
//子进程先读后写
char buf = ' ';
read(fd1[0], &buf, 1);
printf("%d: received ping\n", getpid());
printf("after in son buf : %c\n",buf);
write(fd2[1], &buf, 1);
close(fd2[1]);
}
close(fd1[0]);
close(fd2[0]);
exit(0);
}
测试结果
make: “kernel/kernel”已是最新。
== Test pingpong == pingpong: OK (0.9s)
//////////////////////
管道创建情况为:fd1:0, fd2:0
4: received ping
after in son buf : a
3: received pong
after in parent buf : a
primes
实现管道并发版本的埃拉托斯特尼筛法(Sieve of Eratosthenes)。这个算法的基本思想是从2开始,不断地标记和删除该数的倍数,直到没有更多的数可以标记。这样,剩下的未被标记的数就是质数。正确性用反证法即可证明。
在本实验中的思路:
- 1、一个线程读取带筛数据,将其第一个数据标记为质数,同时筛出所有该数的倍数。
- 2、使用fork新建子线程,将筛完后的数据传入
- 3、重复1、2
这样就形成了一条进程链:主进程:生成 n ∈ [2,35] -> 子进程1:筛掉所有 2 的倍数 -> 子进程2:筛掉所有 3 的倍数 -> .....,最后依链退出即可
本实验的其他难点在指导书中也有说明:
文件描述符耗尽的情况,由于fork创建子进程的操作,会把父进程的文件描述符也都复制给子进程,如果不及时关闭,文件描述符就会耗尽了。解决方法有两种:
- 关闭管道的两个方向中不需要用到的方向的文件描述符(在具体进程中将管道变成只读/只写)。原理在于每个进程从左侧的读入管道中只需要读数据,并且只需要写数据到右侧的输出管道,所以可以把左侧管道的写描述符,以及右侧管道的读描述符关闭,而不会影响程序运行。而文件描述符是进程独立的,比如,父进程的管道pipe[2]在fork作用下复制给了子进程,在子进程中关闭了pipe[1]写端,并不会影响父进程在pipe[1]写数据。管道的写端有一个引用计数,只有当所有指向写端的文件描述符都关闭时,管道的写端才会真正关闭。子进程关闭它的写端只是减少了引用计数但父进程的写端仍然打开,所以管道的写端不会关闭。读端同理。
- 子进程创建后,关闭父进程与祖父进程之间的文件描述符(因为子进程并不需要用到之前的管道)
有关管道的知识:https://blog.csdn.net/puppy_1mo/article/details/145785107;
https://blog.csdn.net/sushhsishdgsusk/article/details/135297011
代码如下:
#include "kernel/types.h"//包含了各种数据类型
#include "user/user.h" //已经实现的各种系统调用
//为了实现数据的传输,接收父进程文件描述符作为参数
void sieve(int fd_left[2]){
int flag;
read( fd_left[0], &flag, sizeof(flag));
if( flag == -1 )
exit(0);
//接收到的第一个数肯定是质数,输出它
printf("prime %d\n", flag);
//往子进程写的管道
int fd_right[2];
pipe(fd_right);
if( fork() == 0){
//子进程
close(fd_right[1]);
close(fd_left[0]);
sieve(fd_right);
}
else{
close(fd_right[0]);
//从左邻居一直读数据,直到接收到终止信号
int buf;
while ( read(fd_left[0], &buf, sizeof(buf)) && buf != -1){
if( buf % flag != 0){ //不是本轮筛掉的数
//传给子进程
write(fd_right[1], &buf, sizeof(buf));
}
}
//flag 为 -1
buf = -1;
write(fd_right[1], &buf, sizeof(buf));
wait(0);
exit(0);
}
}
int main(int argc, char* argv[]){
//主进程,输入2-35
int fd0[2];
pipe(fd0);
if( fork() == 0 ){
//进程链的下一个子进程
close(fd0[1]); //子进程用不到该管道的写端
sieve(fd0);
exit(0);
}
else{
//父进程
close(fd0[0]);//父进程只写
int i = 0;
for(i = 2; i <= 35; ++i){
write(fd0[1], &i, sizeof(i) );
}
//写入结束
i = -1;
write(fd0[1], &i, sizeof(i));
}
wait(0); //等待子进程结束再退出
exit(0);
}
测试结果
$ primes
prime 2
prime 3
prime 5
prime 7
prime 11
prime 13
prime 17
prime 19
prime 23
prime 29
prime 31
find
给定一个初始路径和目标文件名, 要不断递归的扫描找到所有子目录前匹配的文件全路径. 实验手册说可以模仿ls.c的实现来读文件,因为这一点整体来说还是不难。但是很多c文件操作还是不太熟悉。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
void find(char* path, const char* name){
char buf[512], *p; //路径缓冲区
int fd; //文件描述符
struct dirent de; //目录条目结构体
struct stat st; //文件状态结构体
//打开目录
if((fd = open(path, 0)) < 0){
printf( "ls: cannot open %s\n", path);
return;
}
//获取目录状态信息
if(fstat(fd, &st) < 0){
printf( "ls: cannot stat %s\n", path);
close(fd);
return;
}
//分别处理path为文件或者目录的情况
switch(st.type){
//为文件
case T_FILE:
//判断path的最后strlen(name)个字符是否与name相等
if( strcmp(path+strlen(path)-strlen(name), 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);
p = buf + strlen(buf);
*p++ = '/';
//从文件描述符中读取数据
while( read(fd, &de, sizeof(de)) == sizeof(de)){
if( de.inum == 0)
continue;
//跳过 "." 和 ".."
if( strcmp(de.name, ".")==0 || strcmp(de.name, "..")==0 )
continue;
//使用memmove避免内存交织
memmove(p, de.name, DIRSIZ);
p[ DIRSIZ ] = 0;
find(buf, name);
}
break;
}
close(fd);
}
int main(int argc, char* argv[]){
//find 目录 文件名
if( argc != 3){
printf("请正确使用调用方法: find [dictionary] [file name]\n");
exit(1);
}
find( argv[1], argv[2]);
exit(0);
}
测试结果
make: “kernel/kernel”已是最新。
== Test find, in current directory == find, in current directory: OK (0.5s)
== Test find, recursive == find, recursive: OK (1.0s)

浙公网安备 33010602011771号