2023MIT6.828 lab-1
一、sleep
实验内容
- 调用sleep(系统调用)编写用户级别程序
- 能暂停特定时常的系统滴答
- 程序保存在user/sleep.c
实验过程
xv6的参数传递
查看官方文档提示的文件中,多采用如下定义:
c
int main(int argc, char *argv[])
在xv6操作系统中:
1、argc 是一个常见的参数,用于表示传递给程序的命令行参数的数量。
2、argv(argument vector)是一个指向字符指针数组的指针,该数组中的每个元素都是一个指向命令行参数的字符串的指针。
3、程序的内部可通过argc和argv来访问外界输入的参数
注意:
1、argc指示出程序执行时传入的参数个数,常可用来判断输入是否符合要求
2、argv[]为存放字符指针的数组,我们需要的是其指向地址存放的数据,若要当整型数据使用还需进行相应转换
实现代码
c
#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()程序加入编译文件中:
c
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、保存退出
编译运行
c
make qemu
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ sleep 10
nothing happens for a little while //停顿后显示,达到实验效果
工具测试
c
在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()源码
c
// 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的声明
该结构体存储了每一个进程的信息
c
// 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()函数定义
c
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
c
int getpid() Return the current process's PID.
实现代码
注意:读写顺序
c
#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);
}
编译准备
c
往Makefile中UPROGS=\项目添加 $U/_pingpong\
运行结果
工具测试
c
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执行顺序
c
#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);
}
编译准备
c
往Makefile中UPROGS=\项目添加 $U/_primes\
运行结果
工具测试
c
make GRADEFLAGS=primes grade
测试通过