# 安装 xv6
按照官方给的教程一步步安装就行了。
进入 xv6: make qemu
退出 xv6:按住 Ctrl + a
,抬起然后再按 x
。
# sleep
捏麻麻地,完全忘了 C 该怎么写了,真的难受啊。
这个热身题让我惊出一身冷汗,真的脸都不要了。
#include "kernel/types.h" | |
#include "kernel/stat.h" | |
#include "user/user.h" | |
int | |
main(int argc, char *argv[]){ | |
if (argc != 2){ | |
fprintf(2, "please give me one number\n"); | |
exit(1); | |
} | |
fprintf(1, "nothing happen just sleep\n"); | |
sleep(atoi(argv[1])); | |
exit(0); | |
} |
# pingpong
#include "kernel/types.h" | |
#include "kernel/stat.h" | |
#include "user/user.h" | |
int | |
main(int argc, char *argv[]){ | |
int ping[2]; | |
int pong[2]; | |
char buf[5]; | |
if ('\0'){ | |
fprintf(1, "hello"); | |
} | |
pipe(ping); | |
pipe(pong); | |
if (fork() == 0){ | |
close(ping[1]); | |
close(pong[0]); | |
read(ping[0], buf, 5); | |
close(ping[0]); | |
fprintf(1, "ps[%d] recived ping.\n", getpid()); | |
write(pong[1], "pong", 5); | |
close(pong[1]); | |
exit(0); | |
}else{ | |
close(ping[0]); | |
close(pong[1]); | |
write(ping[1], "ping", 5); | |
read(pong[0], buf, 5); | |
fprintf(1, "ps[%d] recived pong.\n", getpid()); | |
close(pong[0]); | |
close(ping[1]); | |
exit(0); | |
} | |
} |
这个有一个坑点,需要使用两个管道来实现 pingpong,我之前只用了一个管道来实现,父进程的代码:
write(p[1], "ping", 5); | |
read(p[0], buf, 5); |
可以看到父进程向管道写了数据后,自己马上读了,然后就只打印:
ps[3] recived pong.
这是因为我们使用了 sh
,sh 通过 fork
创建了第一个 pingpong 进程,并且等待该进程的结束,而这个 pingpong 进程结束后,sh 也结束了,不会让子进程在写标准输出 1。
因此这里需要用两个管道,父进程写 ping,子进程写 pong。
# char 数组
重新学习了 c 中的 char 数据。
数据初始化比如: char buf[5]
。此时 buf 这个 char 数据中的每一个元素都被初始化为 \0
。
C 中对于字符串是将其作为字符数组来处理的,也就是对于 ping
这个字符串,实际上是 char[5]
。因为会默认在后面补个 \0
。
如果初始化 char ch[2] = "01"
这样也能初始化成功,但是请不要这样做,因为数组 ch
实际上就是指针,但我们打印 ch 时候实际上是这样做的:
for(i = 0; fmt[i]; i++){ |
当 fmt[i]
为 \0
时候就会自动结束,如果字符数组没有结尾 \0
,这个循环就不会结束,最后可能导致栈溢出。
# Primes
这道题的坑点就是,xv6 可分配的文件描述符很少,所以,判断素数的条件应该为:
number < factor * factor
,而不是: number == factor
。这样开的子进程少,分配的文件描述符就够用。
#include "kernel/types.h" | |
#include "kernel/stat.h" | |
#include "user/user.h" | |
int main(int argc, char *argv[]) | |
{ | |
int s; | |
if (argc != 2){ | |
s = 35; | |
}else{ | |
s = atoi(argv[1]); | |
} | |
int clid; | |
int factor = 2; | |
int p[2]; | |
pipe(p); // pipe r/w | |
char rbuf[3]; | |
int hasfork = 0; | |
int pid = fork(); | |
if (pid > 0) | |
{ | |
close(p[0]); | |
for (int i = 2; i <= s; i++) | |
{ | |
char tmp[2]; | |
tmp[0] = '0' + i / 10; | |
tmp[1] = '0' + i % 10; | |
write(p[1], tmp, 2); | |
} | |
close(p[1]); | |
wait(&clid); | |
exit(0); | |
} | |
else | |
{ | |
close(p[1]); | |
while (read(p[0], rbuf, 2)) | |
{ | |
if (!hasfork) | |
{ | |
int p1[2]; | |
factor++; | |
close(p[1]); | |
pipe(p1); | |
if (fork()) | |
{ | |
hasfork = 1; | |
factor--; | |
p[1] = dup(p1[1]); | |
close(p1[0]); | |
close(p1[1]); | |
} | |
else | |
{ | |
p[0] = dup(p1[0]); | |
close(p1[1]); | |
close(p1[0]); | |
} | |
} | |
if (!hasfork){ | |
continue; | |
} | |
int number = atoi(rbuf); | |
if (number < factor * factor) | |
{ | |
printf("prime %d\n", number); | |
} | |
else if (number % factor != 0) | |
{ | |
write(p[1], rbuf, 2); | |
} | |
} | |
close(p[1]); | |
close(p[0]); | |
wait(&clid); | |
exit(0); | |
} | |
} |
需要注意的是,父进程需要用 wait
等待子进程结束才能结束。
# find
这里可以参考:https://www.bookstack.cn/read/bash-tutorial/docs-archives-commands-find.md ,详细描述了 linux 下的 find 教程。
注意这个命令: find . -name * -exec ls -a {} \;
The
\;
is a;
fed to the program (find) by the\
escape preventing it from be handled by the shell (normally would separate commands). The-exec
argument interprets everything as a command up to that inserted;
that ends the-exec
stuff. Within the-exec
stuff an argument of{}
means "insert the file name here". So if the files were "foo" and "bar" it would execute "ls -a foo" then "ls -a bar". So all that meaning only means that because-exec
is there.
{}
在这里用做占位符,将 find 查找的结果插入到 {}
, \;
就是将 ;
转义,因为 bash 中, ;
表示一个命令的结束。
这个指令的作用就是对 find 每个结果执行 ls -a。
#include "kernel/types.h" | |
#include "kernel/stat.h" | |
#include "user/user.h" | |
#include "kernel/fs.h" | |
void fmtname(char *path, char *buf1) | |
{ | |
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; | |
memmove(buf, p, strlen(p)); | |
memset(buf + strlen(p), ' ', DIRSIZ - strlen(p)); | |
strcpy(buf1, buf); | |
} | |
void find(char *name, char *path) | |
{ | |
char buf[512], *p; | |
int fd; | |
struct dirent de; | |
struct stat st; | |
fd = open(path, 0); | |
fstat(fd, &st); | |
char buf1[DIRSIZ + 1]; | |
char buf2[DIRSIZ + 1]; | |
switch (st.type) | |
{ | |
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); | |
fmtname(path, buf1); | |
fmtname(name, buf2); | |
if (strcmp(buf1, buf2) == 0) | |
{ | |
fprintf(1, "%s\n", path); | |
} | |
*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; | |
char tmp[512]; | |
strcpy(tmp, buf); | |
find(name, tmp); // 递归查找 | |
} | |
break; | |
case T_FILE: | |
fmtname(path, buf1); | |
fmtname(name, buf2); | |
if (strcmp(buf1, buf2) == 0) | |
{ | |
fprintf(1, "%s\n", path); | |
} | |
default: | |
break; | |
} | |
close(fd); | |
} | |
int main(int argc, char *argv[]) | |
{ | |
if (argc != 3) | |
{ | |
printf("please give correct args.\n"); | |
exit(1); | |
} | |
find(argv[2], argv[1]); | |
exit(0); | |
} |
我的代码逻辑就是,如果 path 打开是 dir 类型就递归查找,如果是 file 类型就直接进行比较。
需要注意:
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)); | |
memset(buf + strlen(p), ' ', DIRSIZ - strlen(p)); | |
return buf | |
} |
原本的代码是这样的,那个这个代码有个很难察觉的错误,因为 fmtname
只能返回 char 指针,而如果要返回 char 数组,那么这个 char 数据要么是全局变量,要么是 static 修饰了局部变量。这个 fmtname 返回的函数实际上是 const char *
。也就是说 char 指针指向的内存空间不会改变,这就导致每一次调用这个 fmtname 函数都会覆盖之间的值。
举个栗子:
char * a = fmtname("a") | |
char * b = fmntname("b") | |
strcmp(a,b) |
这个 strcmp
将会永远返回 true,因为无论 a 还是 b 都是 const char *
指针,指向的都是同一个地址空间。
# xargs
阮一峰大佬 yyds!https://www.ruanyifeng.com/blog/2019/08/xargs-tutorial.html
xargs
说白了就是将标准输入转换为命令行参数。默认执行 echo,并且默认情况下, xargs
将换行符和空格作为分隔符,把标准输入分解成一个个命令行参数。
find
命令有一个特别的参数 -print0
,指定输出的文件列表以 null
分隔。然后, xargs
命令的 -0
参数表示用 null
当作分隔符。
$ find /path -type f -print0 | xargs -0 rm
上面命令删除 /path
路径下的所有文件。由于分隔符是 null
,所以处理包含空格的文件名,也不会报错。
还有一个原因,使得 xargs
特别适合 find
命令。有些命令(比如 rm
)一旦参数过多会报错 "参数列表过长",而无法执行,改用 xargs
就没有这个问题,因为它对每个参数执行一次命令。
$ find . -name "*.txt" | xargs grep "abc"
上面命令找出所有 TXT 文件以后,对每个文件搜索一次是否包含字符串 abc
。
还有两个参数也很重要:
使用 -L
参数,指定每行作为一个命令行参数,就不会报错。
$ xargs -L 1 find -name | |
"*.txt" | |
./foo.txt | |
./hello.txt | |
"*.md" | |
./README.md |
-n
参数指定每次将多少项,作为命令行参数。
$ xargs -n 1 find -name
上面命令指定将每一项( -n 1
)标准输入作为命令行参数,分别执行一次命令( find -name
)
实现代码:
#include "kernel/types.h" | |
#include "kernel/stat.h" | |
#include "user/user.h" | |
#include "kernel/fs.h" | |
int main(int argc, char *argv[]) | |
{ | |
char buf[512]; | |
read(0, buf, 512); // 读取指令 | |
for (int i = 0; buf[i]; i++) | |
{ | |
char arg[33]; | |
arg[0] = 0; | |
char *p = arg; | |
while (buf[i] && buf[i]!='\"' && buf[i]!='\n') | |
{ | |
if(buf[i] == '\\' && buf[i+1]=='n'){ | |
i++; | |
break; | |
} | |
*p = buf[i]; | |
i++; | |
p++; | |
} | |
if (!arg[0])continue; | |
if (!fork()) | |
{ | |
char *eargs[10]; | |
int j = 0; | |
for (int i = 1; i < argc; i++) | |
{ | |
eargs[j++] = argv[i]; | |
} | |
eargs[j++] = arg; | |
exec("echo", eargs); | |
} | |
else | |
{ | |
int tmp; | |
wait(&tmp); | |
if (tmp) | |
{ | |
fprintf(2, "what happened? we need to know!"); | |
exit(tmp); | |
} | |
} | |
} | |
exit(0); | |
} |
代码思路就是对于每一次 exec 新开一个子进程去执行,当子进程执行 exec 命令结束后,再开新的子进程执行 exec。