1 sleep
1.1 实验提示
- 阅读xv6book第一章关于系统调用的部分
- 阅读echo.c这些源码,了解如何使用命令行给函数输入参数
- 如果没有输入参数应该有相应的错误处理机制
- 参数输入的是字符串应该使用atoi转为对应的整数
- 本部分主要使用sleep系统调用,原理是通过用户态切换至内核态调用sleep
- 主函数最后exit(0)表示正常的程序退出
1.2 sleep
关键点:传入参数的处理和使用系统调用sleep
参数处理的情况:
- 无参、参数过多、含有非法字符
系统调用:
- 参数为整型,需要用到atoi将传进来的字符转换为整型
#include "kernel/types.h"
#include "user/user.h" //该头文件对kernel/types.h头文件有类型依赖,故要放在kernel/types.h之后。
int main(int argc, char *argv[]){
if(argc < 2)
printf("no argument\n");
else if(argc > 2)
printf("Excessive argument\n");
else{
int tag = 1;
char *p = argv[1];
while(*p){
if(*p < '0' || *p > '9'){
tag=0;
break;
}
p++;
}
if(tag)
sleep(atoi(argv[1]));
else
printf("Illegal argument\n");
}
exit(0);
}
1.3 pingpong
关键点:系统调用的灵活运用包括pipe()、fork()、read()、write()
思路:
1.进程的创建:既然是进程间的通信,那么就至少有两个进程,选择fork()系统调用创建子进程
2.管道的创建:通信就必须要有介质,这里的通信介质就是管道,可以利用pipe()系统调用创建两个管道,parentPipe和childPipe,一个用于父进程写,子进程读;另一个用于子进程写,父进程读,如下图所示。
3.依赖关系:为了避免父子进程同时写和读,则需要对他们二者的读写依赖关系进行一定的限制。即,父进程写之前关闭childPipe写窗口和parentPipe的读窗口,写完之后再关闭parentPipe的写窗口;同理,子进程读之前关闭parentPipe的写窗口和childPipe的读窗口,读完之后,对childPipe进行写入操作。
4.代码:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]){
int parentPipe[2], childPipe[2];
pipe(parentPipe);
pipe(childPipe);
char buf[1];
if(fork() == 0){ // 子进程先进行读再进行写
close(parentPipe[1]); // 关闭父管道的写,避免父进程写而子进程无法读
close(childPipe[0]); // 关闭子进程的读,让父进程能够写
if(read(parentPipe[0],buf,1))
printf("%d: received ping\n",getpid());
write(childPipe[1],"B",1); // 子进程读完之后开始写
close(childPipe[1]); // 写完之后关闭写窗口方便父进程读
} else { // 父进程同理
close(parentPipe[0]);
close(childPipe[1]);
write(parentPipe[1],"A",1);
if(read(childPipe[0],buf,1))
printf("%d: received pong\n",getpid());
close(parentPipe[1]);
}
exit(0);
}
1.4 素数筛(困难)
涉及的系统调用:pipe()、fork()、read()、write()、exit()
涉及的技术:并发编程
原理:参考地址:[Bell Labs and CSP Threads (swtch.com)]
通信顺序进程:有很多提议的方法被用于多处理器编程的通信和同步。共享内存是最常见的通信机制,信号量、临界区和监视器是同步机制中的一部分。解决通信和同步的问题:同步通信机制。由于通道是无缓冲的,发送操作阻塞,直到值已经传输给接收者,从而提供了一个同步机制。
开启进程和写入管道的示意图如下:
主要思路:整条数据传送链是基于素数的传递,且每个进程输出一个素数。整个数据传送过程是并发执行的。当一个进程从上一个进程的管道读素数,需要完成两个任务:1.读取第一个素数作为自己的素数并输出;2.读取其他的素数传送给接下来的进程;
一些细节:
1.接收上一个素数的结束状态是读取到了空字节,那么就要关闭自己写的通道了,因为此时没有读的数据了,避免通道的读端一直等待;
2.传送给下一个进程素数的条件之前要先fork一个子进程然后为两个进程之间创建一个管道进行通信,有数据就放进管道没有数据就关闭管道的读端;
3.进程退出的条件是,管道中没有数据可读了且下一个进程退出了。作为最后一个进程因为没有创建子进程所以可以直接退出然后通过递归的方式一层一层的将自己的退出状态返回给前面的进程,最后退出整个程序。
#include "kernel/types.h"
#include "user/user.h"
void run_process(int fd){
int cur_pipes[2]; // current pipe
int my_number = 0; // I will print my prime
int forked = 0; // If is 0, I haven't fork my child process.
int read_number = 0;
// read number from last pipe
while(1){
int read_bytes = read(fd, &read_number, 4);
// no number to read, close fd, wait my child process and exit
if(read_bytes == 0){
close(fd);
close(cur_pipes[1]);
if(forked){
int child_pid;
wait(&child_pid);
}
exit(0);
}
// I haven't read my prime
if(my_number == 0)
{
my_number = read_number;
printf("prime %d\n",my_number);
continue; // my prime not need pass to the next pipe
}
// I will filte the rest prime
if(read_number % my_number != 0){
if(!forked){
pipe(cur_pipes);
forked = 1;
int c_pid = fork();
if(c_pid == 0){
close(fd);
close(cur_pipes[1]); // my child process close the write pipe
run_process(cur_pipes[0]);
}
else{
close(cur_pipes[0]); // I haven't write a number to the pipe, so it can't read.
}
}
write(cur_pipes[1], &read_number, 4);
}
}
}
int main(int argc, char *argv[]){
int product_pipes[2];
pipe(product_pipes);
// write 2-35 numbers to the first pipe
for(int i = 2; i <= 35; i++){
write(product_pipes[1], &i ,4); // write 4 bytes to the first pipe
}
close(product_pipes[1]);
run_process(product_pipes[0]); // make the next process to read its prime of the pipe
exit(0);
}
1.5 find
1.阅读ls.c文件,参照如何读取目录
(1)函数fmtname(char *path):函数接受一个文件路径,并返回路径中的文件名。它确保返回的文件名长度与DIRSIZ匹配,不足的部分会用空格填充。若输入的路径为/home/user/xv6,则返回的文件名称为xv6
(2)de结构体读取目录的条目,若目录a下有’.‘、’…‘、b、c、d/等文件和目录,则会循环读取将条目的名称存储到de。若de失效则de.num = 0。否则会依次读取b、c、d/
(3)st结构体判断文件的状态,主要分为文件类型T_FILE、目录类型T_DIR、设备类型T_DEVICE,遇到目录类型则继续递归。**注:**读取目录条目时会读取到当前目录’.‘和父目录’…',遇到该目录直接跳过
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
char* get_name(char* path){//获取当前文件名
char * p;
for(p = path+strlen(path); p >= path && *p != '/'; p--);
p++;
return p;
}
void find(char *path, char* str){//类Unix系统中,目录被视为一种特殊类型的文件
char buf[512];//存储路径
struct dirent de;//目录结构体
struct stat st;//文件结构体
int fd = open(path, 0);//0表示以标准模式(读写模式)打开
if(fd < 0){//打开失败
fprintf(2, "find: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){//通过文件描述符将对应的文件信息放入文件结构体stat中,若失败则返回-1
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){//判定打开类型
case T_DEVICE://判定为设备文件
case T_FILE://判定为普通文件
if(!strcmp(str, get_name(path)))
printf("%s\n",path);
break;
case T_DIR://判定为目录
strcpy(buf, path);
char *p = buf + strlen(buf);
*p = '/';
p++;
while(read(fd, &de, sizeof de) == sizeof de){//使用read从目录文件中读取目录条目,处理目录中文件
if(de.inum == 0)//该目录条目为空或未使用
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if(stat(buf, &st) < 0){//通过文件路径将对应的文件信息放入文件结构体stat中,若失败则返回-1
printf("ls: cannot stat %s\n", buf);
continue;
}
if(st.type == T_DEVICE || st.type == T_FILE){//判断为非目录文件
if(!strcmp(str, get_name(buf)))
printf("%s\n",buf);
}
else if(st.type == T_DIR && strcmp(".", get_name(buf)) && strcmp("..", get_name(buf)))//判定为子目录,递归处理,注意不要重复进入本目录以及父目录
find(buf, str);
}
break;
}
close(fd);
return;
}
int main(int argc, char *argv[]){
if(argc == 3)
find(argv[1], argv[2]);
else
printf("argument error\n");
exit(0);
}
1.6 xargs
需求:编写一个简单版本的 UNIX xargs 程序:它的参数描述要运行的命令,它从标准输入中读取行,并为每一行运行命令,将该行附加到命令的参数中。您的解决方案应该在 user/xargs.c 文件中
输入命令解读:
echo hello too | xargs echo bye # argv = 2, argv[]={"echo","bye"},读入参数"hello too"
#echo 首先输出bye,然后再读入参数"hello too",通过argv[1]中的命令echo接着输出"hello too"
find . b | xargs grep hello # 先运行grep hello,然后运行greb (find . b的结果)
构造一个char\*数组将需要传给exec的数据放入其中即可
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/param.h"
int main(int argc, char *argv[]){
char *p[MAXARG];
int i;
for(i=1; i < argc; i++)
p[i-1] = argv[i];
p[argc-1] = malloc(512);
p[argc] = 0;
while(gets(p[argc-1],512)){ //gets函数一次读取一行
if(p[argc-1][0] == 0)break; //已读完
if(p[argc-1][strlen(p[argc-1])-1]=='\n') //该函数会将末尾换行保留,故需去掉换行符。
p[argc-1][strlen(p[argc-1])-1] = 0;
if(fork()==0)
exec(argv[1],p);
else
wait(0);
}
exit(0);
}