MIT 操作系统实验lab01 utility
插曲
对于ulib.c的学习,仔细看了下ulib的源代码真的可以学习到很多有用的知识
对于字符串的处理函数, 这里特别要注意很多函数都使用了c字符串一个很不错(也是很头疼)的性质,最后一个字符是’\0’表示字符串结束
char*
strcpy(char *s, const char *t)
{
char *os;
os = s;
while((*s++ = *t++) != 0)
;
return os;
}
int
strcmp(const char *p, const char *q)
{
while(*p && *p == *q)
p++, q++;
return (uchar)*p - (uchar)*q;
}
uint
strlen(const char *s)
{
int n;
for(n = 0; s[n]; n++)
;
return n;
}
// 查找某个元素在字符串中的位置, 如果不存在,返回0
char*
strchr(const char *s, char c)
{
for(; *s; s++)
if(*s == c)
return (char*)s;
return 0;
}
// 读出一行元素的字符串
char*
gets(char *buf, int max)
{
int i, cc;
char c;
for(i=0; i+1 < max; ){
cc = read(0, &c, 1);
if(cc < 1)
break;
buf[i++] = c;
if(c == '\n' || c == '\r')
break;
}
buf[i] = '\0';
return buf;
}
对于内存的函数, 这个函数告诉我们处理指针移动的时候要特别小心,如果源地址在目标地址后面的话,正向复制即可,如果源地址在目标地址前面的话,必须要反向复制。
void* memmove(void* vdst, const void* vsrc, int n)
{
char * dst;
const char *src;
dst = vdst;
src = vsrc;
if (src > dst) {
while (n -- > 0) {
*dst++ = *src++;
}
} else {
dst += n;
src += n;
while (n -- > 0) {
*--dst = *--src;
}
}
return vdst;
}
任务部分
任务一 sleep
任务提示我们可以参考rm.c的代码, 从rm.c中我们可以知道参数是含有该程序自身名称的,比如rm file ,rm 是argv[0], file 是argv[1].
// rm.c
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
if (argc < 2) {
fprintf(2, "Usage:rm file...\n");
exit(1);
}
for (int i = 1; i < argc; i++) {
if (unlink(argv[i]) < 0) {
fprintf(2, "rm:%s failed to delete\n", argv[i]);
break;
}
}
exit(0);
}
照猫画虎我们可以得到
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char *argv[])
{
if(argc < 2){
fprintf(2, "Usage: sleep time...\n");
exit(1);
}
sleep(atoi(argv[1]));
exit(0);
}
任务二 pingpong
完成一个接受发的通信,这里我们需要参考xv6的源码中关于管道的部分,给父进程,子进程分别配置一个管道的读口,一个管道的写口。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int
main(int argc, char* argv[]) {
int p1[2], p2[2];
char byte = 'A';
char buffer[2];
pipe(p1);
pipe(p2);
int pid = fork();
if (pid < 0) {
write(2, "forkerror\n", 10);
}
if (pid == 0) {
// 当前为子进程
close(p1[1]);
read(p1[0], buffer, 1);
if (buffer[0] == byte) {
printf("%d: received ping\n", getpid());
}
close(p1[0]);
close(p2[0]);
write(p2[1], &byte, 1);
close(p2[1]);
exit(0);
} else {
close(p1[0]);
write(p1[1], &byte, 1);
close(p1[1]);
close(p2[1]);
read(p2[0], buffer + 1, 1);
if (buffer[1] == byte) {
printf("%d: received pong\n", getpid());
}
close(p2[0]);
exit(0);
}
}
其中用到了 p i p e ( p ) pipe(p) pipe(p), 这个是通过一个数组创建一个读写管道, 还用到了read(文件描述符,地址, 大小)和write(文件描述符,地址,大小), 这里需要与fread做区别,fread(地址,大小,个数,文件描述符).
任务三 primes
完成打印1-35的素数
这里提示我们不可以每个数都开一个进程,否则会爆进程,我们从kernel/param.h中可以看到最多可以开64个进程,因此我们需要在只有数为素数的时候才开进程,用管道进行读写,否则不开
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int isprime(int n) {
if (n <= 1) return 0; // 0 and 1 are not prime numbers
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
return 0; // Found a divisor, so it's not prime
}
}
return 1; // n is prime
}
int
main(int argc, char* argv[]) {
for (int i = 1; i < 36; i++) {
if (isprime(i)) {
int p[2];
pipe(p);
int buffer;
int pid = fork();
if (pid == 0) {
close(p[1]);
read(p[0], &buffer, 1);
int num = buffer;
printf("prime %d\n", num);
close(p[0]);
exit(0);
}
close(p[0]);
write(p[1], &i, 1);
close(p[1]);
wait((int*)0);
}
}
exit(0);
}
任务四 find函数
这里提示我们可以参考ls的实现, 当目标文件是一个文件类型时直接输出,当目标文件为目录的时候,我们需要读取目录项,将其拼成绝对路径后,在使用state(char* path, struct stat * st)来读取相关信息
// find 函数的实现
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
void find(char* directory, char* file) {
int fd;
struct stat st; // 存节点信息
struct dirent de; // 存目录项
if ((fd = open(directory, 0)) < 0) {
fprintf(2, "cannot open%s\n", directory);
return;
}
if ((fstat(fd, &st)) < 0) {
fprintf(2, "cannot stat %s\n", directory);
close(fd);
return;
}
// 将目录拼接到最前面
char buf[512], *p;
strcpy(buf, directory);
p = buf + strlen(buf);
*p++ = '/';
// 从fd中读取每一个目录项
while(read(fd, &de, sizeof(de)) == sizeof(de)) {
if (de.inum == 0) continue; // 无效目录项
memcpy(p, de.name, strlen(de.name) + 1); // 这里的+1是为了让/0放入
if (stat(buf, &st) < 0) {
printf("cannot stat %s\n", buf);
continue;
}
switch (st.type) {
case T_FILE:
if (!strcmp(de.name, file)) {
printf("%s\n", buf);
}
break;
case T_DIR:
if (strcmp(de.name, ".") && strcmp(de.name, "..")) find(buf, file);
break;
}
}
close(fd);
return;
}
int main(int argc, char* argv[]) {
if (argc < 3) {
write(2, "Usage: find directory file\n", 27);
exit(1);
}
find(argv[1], argv[2]);
exit(0);
}
任务五 xargs
xargs的实现是写了最久的,主要要处理很多I/O,给到的经验就是每次while()判断结束退出后,一定要想一想最后一个元素是不是在while循环中处理过,否则很容易导致出错,其次一定malloc之后要free.
// xargs 实现
#include "kernel/types.h"
#include "kernel/stat.h"
#include "kernel/param.h"
#include "user/user.h"
char* readline() {
char *buf = malloc(1024*sizeof(char));
char *p = buf;
while (read(0, p, 1) != 0) {
if (*p == '\n' || *p == '\0') {
*p = '\0';
return buf;
}
p++;
}
free(buf);
return 0;
}
int main(int argc, char* argv[]) {
if (argc < 2) {
write(2, "Usage: xargs cmd [option param]\n", 32);
exit(1);
}
// 先将所有的先前变量读入
char* nargv[MAXARG];
for (int i = 1; i < argc; i++) {
nargv[i - 1] = argv[i];
}
char* l, *p;
while((l = readline()) != 0) {
p = l;
int nargc = argc - 1;
char* buf = malloc(128 * sizeof(char));
char *bp;
bp = buf;
while (*p != 0) {
if (*p == ' ' && bp != buf) { // 枚举到" "加入该变量
*bp = 0;
nargv[nargc++] = buf;
buf = malloc(128 * sizeof(char));
bp = buf;
} else { //枚举整个变量
*bp = *p;
bp++;
}
p++;
}
// 解决最后一个变量, *p = 0 退出时
if (buf != bp) nargv[nargc++] = buf;
free(l);
int pid = fork();
if (pid == 0) {
exec(nargv[0], nargv);
exit(0);
} else {
wait((int*)0);
}
}
exit(0);
}