1. 大致内容
熟悉环境以及一些基本的系统调用。
2. read()
2.1. 读管道的几种情况
- 有东西就读
- 没东西就阻塞
- 写端关闭(引用次数为0)且没东西返回 0
3. fork()
3.1. 对于文件描述符是怎么处理的?
父子进程共享文件描述符及其偏移指针,fork() 仅增加引用次数。
1 2 3 4 5 6
|
for(i = 0; i < NOFILE; i++) if(p->ofile[i]) np->ofile[i] = filedup(p->ofile[i]); np->cwd = idup(p->cwd);
|
close(fd) 只是减少一次引用,减到 0 才会关闭。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <stdio.h> #include <unistd.h>
int main() { int fa[2]; pipe(fa);
int tmp = 10; write(fa[1], &tmp, sizeof(tmp));
if (fork() == 0) { sleep(5); close(fa[1]); printf("child wake\n"); } else { close(fa[1]); read(fa[0], &tmp, sizeof(tmp)); printf("fa read %d\n", tmp); int ret = read(fa[0], &tmp, sizeof(tmp)); printf("ret: %d\n", ret); } return 0; }
|
输出如下图,且后两行在 5s 后才输出。已知 read 在管道写端被关闭时返回 0 ,推断分别在父子进程 close() 一次后,写端引用次数减到 0 后关闭,read 不再阻塞, 返回 0 .

4. primes
尽早关闭不用的读 / 写端,避免文件描述符耗尽。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include "kernel/types.h" #include "user/user.h"
void solve(int *fa) { close(fa[1]);
int flag; read(fa[0], &flag, sizeof(flag)); printf("prime %d\n", flag);
int tmp; if (read(fa[0], &tmp, sizeof(tmp))) { int child[2]; pipe(child);
if (fork() == 0) { solve(child); } else { close(child[0]); if (tmp % flag) { write(child[1], &tmp, sizeof(tmp)); }
while (read(fa[0], &tmp, 4)) { if (tmp % flag) { write(child[1], &tmp, sizeof(tmp)); } } close(fa[0]); close(child[1]);
wait(0); } }
exit(0); }
int main(int argc, char *argv[]) { int child[2]; pipe(child);
if (fork() == 0) { solve(child); } else { for (int i = 2; i <= 35; i++) { write(child[1], &i, sizeof(i)); } close(child[1]); wait(0); }
exit(0); }
|
5. xargs
1
| command1 | xargs [options] [command2 [initial-arguments]]
|
作用是将 command1 的输出处理后,使其作为 command2 参数,command2 为可选项,默认为 echo.
实验要求只用实现 -n 1,即一次只从左边取一个参数,而 xargs 的默认参数分隔符为 \n,所以一次取一行作为参数,交由 exec 执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include "kernel/types.h" #include "kernel/param.h" #include "kernel/stat.h" #include "user/user.h"
#define MAXN 1024
int readLine(char *param[MAXARG], int cntp) { char buf[MAXN]; int l = 0, r = 0; while (read(0, buf + r, 1)) { if (buf[r] == '\n') { buf[r] = 0; break; } r++; if (r == MAXN) { fprintf(2, "Too many params!\n"); exit(1); } }
if (r == 0) { return -1; }
while (l < r) { while(l < r && buf[l] == ' ') l++; param[++cntp] = buf + l; while (l < r && buf[l] != ' ') l++; buf[l++] = 0; }
return cntp; }
int main(int argc, char *argv[]) { if (argc < 2) { printf("Usage: xargs [options] [command [initial-arguments]]\n"); exit(1); } char *param[MAXARG];
for (int i = 0; i < argc - 1; i++) { param[i] = malloc(strlen(argv[i + 1]) + 1); strcpy(param[i], argv[i + 1]); }
int cntp = -1; while ((cntp = readLine(param, argc - 2)) != -1) { param[++cntp] = 0; if (fork() == 0) { exec(param[0], param); exit(1); } else { wait(0); } }
exit(0); }
|
6. make grade

7. 遗留问题
头文件的引入顺序不对会导致类型未定义等错误,为什么 Java 中没碰到过?两种语言的模块管理怎么对比来看?
#include 和其他 # 开头的命令(宏定义、条件编译)一样,为预处理命令,在程序编译的第一步预处理(gcc -E)中被执行对应的文本层面的操作,即将对应的头文件内容展开,生成对应的 .i 文件(需要 -o 指定生成文件,否则默认输出到控制台,仍为文本类型可直接查看)。由于展开是按顺序的,如果出现调用先于声明的情况就会出错。
对于 Java 的 import 只是指定了要引用的类,而具体的被引用类的代码则在执行的时候才调入内存,这个步骤是类加载器来实现的。