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: write
pipe(p_child_to_parent);
pipe(p_parent_to_child);
if(fork() == 0){ //child process
printf("%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 device uint ino; // Inode number short type; // Type of file short nlink; // Number of links to file uint64 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 number
uint inum; // Inode number
int ref; // Reference count
struct sleeplock lock; // protects everything below here
int valid; // inode has been read from disk?
short type; // copy of disk inode
short 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个部分都有了一定了解,接下来就继续看每个小部分了