# 安装 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 将换行符和空格作为分隔符,把标准输入分解成一个个命令行参数。

image-20220326142123306

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);
}

image-20220326142644262

代码思路就是对于每一次 exec 新开一个子进程去执行,当子进程执行 exec 命令结束后,再开新的子进程执行 exec。

更新于

请我喝[茶]~( ̄▽ ̄)~*

Kalice 微信支付

微信支付

Kalice 支付宝

支付宝