课程实验 Lab1"/>
MIT 6.s081课程实验 Lab1
Lab1
Lab1实现几个简单函数,目的是促进我们对xv6这个小系统的了解
sleep
- 在/user文件夹中添加sleep.c文件如下,主要工作是解析参数->调用sleep的系统调用
- 在Makefile文件的UPROGS中仿照其他功能添加_sleep选项,这样就能在shell中使用sleep方法
- 测试:运行
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: writepipe(p_child_to_parent);pipe(p_parent_to_child);if(fork() == 0){ //child processprintf("%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是如何实现的,思路是:
-
使用
fstat
系统调用获得打开文件路径后的文件描述符fd对应的stat,用于描述文件的信息,其定义如下:struct stat {int dev; // File system's disk deviceuint ino; // Inode numbershort type; // Type of fileshort nlink; // Number of links to fileuint64 size; // Size of file in bytes };
-
根据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 numberuint inum; // Inode numberint ref; // Reference countstruct sleeplock lock; // protects everything below hereint valid; // inode has been read from disk?short type; // copy of disk inodeshort 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:可以使用fork
和exec
,即当从标准输入读到一行,则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个部分都有了一定了解,接下来就继续看每个小部分了
更多推荐
MIT 6.s081课程实验 Lab1
发布评论