前言
第一个实验是在XV6系统上实现五个实用程序。虽然不难,但是我写了好久……
实验
sleep
sleep本身不需要实现,只需要运行即可。
源代码
#include "kernel/types.h"
#include "user.h"
int main(int argc,char* argv[]){
if(argc != 2){
printf("Sleep needs one argument!\n"); //检查参数数量是否正确
exit(-1);
}
int ticks = atoi(argv[1]); //将字符串参数转为整数
sleep(ticks); //使用系统调用sleep
printf("(nothing happens for a little while)\n");
exit(0); //确保进程退出
}
编译运行
- 修改Makefile
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\ # 添加sleep
- 运行
sudo make qume
pingpong
要求
实现两个进程在管道两侧来回通信。
父进程将”ping”写入管道,子进程从管道将其读出并打印。子进程从父进程收到字符串后,将”pong“写入另一个管道,然后由父进程从该管道读取并打印。
原理
通过定义两个管道fp和sp,实现父子进程传输消息
注意,进程通常只持有某个管道的读出端或者写入端,因此使用的时候需要将另一端关闭。
源代码
#include "kernel/types.h"
#include "user.h"
int main(int argc,char* argv[])
{
int fp[2],sp[2];
pipe(fp);//父进程写入,子进程读取
pipe(sp);
printf("program start!\n");
int pid = fork();
if(pid<0)
{
printf("error!");
}
else if(pid == 0)
{
/*子进程 */
char *buffer = " ";
close(fp[1]); // 关闭写端
read(fp[0], buffer, 4);//阻塞等待
printf("%d: received %s\n",getpid(),buffer);
close(fp[0]); // 读取完成,关闭读端
char *intput = "pong";
close(sp[0]); // 关闭读端
write(sp[1], intput, 4);
close(sp[1]); // 写入完成,关闭写端
}
else{
/*父进程*/
char *buffer = "ping";
close(fp[0]); // 关闭读端
write(fp[1], buffer, 4);
close(fp[1]); // 写入完成,关闭写端
close(sp[1]); // 关闭写端
read(sp[0], buffer, 4);
printf("%d: received %s\n",getpid(),buffer);
close(sp[0]); // 读取完成,关闭读端
}
exit(0);
}
运行结果
primes
要求
使用管道实现“质数筛选”, 输出2~35之间的而所有质数。
原理
筛选思路:
- 主进程将所有数据输入到管道的左侧
- 子进程从管道读出并筛选出第一个数字x,排除掉x的倍数,其他数字再写入下一管道;
- 重复步骤二,直到管道中没有数字
源代码
#include "kernel/types.h"
#include "user.h"
#include <stddef.h>
void mapping(int n, int fd[])
{
close(n);//关闭文件描述符n,令n映射到fd[n]
dup(fd[n]);
close(fd[0]);
close(fd[1]);
}
void primes()
{
int fd[2];
pipe(fd);
int prime;//当前的质数
int ref = read(0, &prime, sizeof(int));
if(ref == 0)return;//没有质数了
printf("prime %d\n", prime);
int pid = fork();
if(pid == 0)
{
int num;
mapping(1, fd);//将管道映射到1上
while(read(0,&num, sizeof(int)))
{
if(num%prime == 0)continue;
write(1, &num, sizeof(int));
}
}
else
{
wait(NULL);
mapping(0, fd);//将管道映射到0上
primes();
}
}
int main(int argc,char* argv[])
{
int fd[2];
pipe(fd);//父进程写入,子进程读取
printf("program start!\n");
int pid = fork();
if(pid<0)
{
printf("error!");
}
else if(pid == 0)
{
/*子进程 */
mapping(1,fd);
for(int i = 2;i <= 35; i++)//将所有数字塞入管道
write(1, &i, sizeof(int));
}
else{
/*父进程*/
wait(NULL);//等待子进程结束
mapping(0, fd);
primes();
}
exit(0);
}
运行结果
find
要求
在目录树中查找名称与字符串匹配的所有文件,输出文件的相对路径。
原理
借鉴ls指令的写法,读取路径下的文件名,如果是文件,就与寻找的文件名比较,如果是文件夹,就递归继续寻找该文件夹。
源代码
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
char* fmtname(char *path)
{
char *p;
// Find first character after last slash.
for(p=path+strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p;
}
void find(char *path, char *file_name)
{
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;
}
if(st.type != T_DIR)
{
fprintf(2, "find: cannot find in %s\n", path);
close(fd);
return;
}
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){//路径太长了
return;
}
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("find: cannot stat %s\n", buf);
continue;
}
char *name = fmtname(buf);
switch (st.type)
{
case T_FILE:
if(strcmp(name, file_name) == 0)
{
printf("%s\n", buf);
}
break;
case T_DIR:
if(strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
continue;//不能是本层或者上层
find(buf, file_name);
break;
default:
break;
}
}
close(fd);
}
int main(int argc, char *argv[])
{
if(argc < 3){
printf("please input find [path] [file_name]\n");
exit(0);
}
find(argv[1], argv[2]);
exit(0);
}
运行结果
xargs
要求
从标准输入中读取行并 为每行运行一次 指定的命令,且将该行作为命令的参数提供。
原理
这个小实验主要是对于实现效果的理解。
|
命令行中的|
会将左边的命令的输出通过管道传递到右侧,可以直接从标准输入中读取字符串。exec
函数
exec接收的二维参数数组argv,argv[0]必须是该命令本身,最后一个参数argv[size-1]必须为0,否则将执行失败。
char *argv[]={"echo","hello",0};
exec(argv[0],argv);
源代码
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"
int main(int argc, char* argv[]) {
if(argc < 2){
printf("error inputs");
exit(0);
}//参数不够
char *cmd = argv[1];
char *argvs[MAXARG];
for(int i=1;i<argc;i++)
{
argvs[i-1]=argv[i];
}
char buff[1024];
while(read(0,&buff,sizeof(buff)))//读取|右边的命令输出或者等待输出
{
int len = strlen(buff);
argvs[argc-1]=&buff[0];
for(int i=0;i<len;i++)
{
if(buff[i]=='\n')// 默认 -n1
{
if(fork()==0)
{
buff[i]=0;
argvs[argc]=0;//命令的最后一个参数为0
exec(cmd,argvs);//执行每一行命令
}
wait(0);//等待子进程结束
argvs[argc-1]=&buff[i+1];//下一行命令的首地址
}
}
}
exit(0);
}
运行结果
运行命令 xargstest.sh
mkdir a
echo hello > a/b
mkdir c
echo hello > c/b
echo hello > b
find . b | xargs grep hello
结语
第一个实验就耗费了大量的时间,有几个实验还借鉴了别人的写法。考虑到我不是科班出身,之后的实验不如趁早观看别人的写法,能学到东西就好。
如有兴趣,欢迎来访我的个人博客