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 qemuxv6 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 “: received ping” 这里的 is its process ID,并通过pipes给父进程传递一字节,然后exit
- 父进程读取来自子进程的字节,并print “: 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 statevoid *chan; // If non-zero, sleeping on chanint killed; // If non-zero, have been killedint xstate; // Exit status to be returned to parent's waitint 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 stackuint64 sz; // Size of process memory (bytes)pagetable_t pagetable; // User page tablestruct trapframe *trapframe; // data page for trampoline.Sstruct context context; // swtch() here to run processstruct file *ofile[NOFILE]; // Open filesstruct inode *cwd; // Current directorychar 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
测试通过