6.s081 lab1小结

文章详细解析了xargs命令的工作原理,以及它如何通过读取标准输入、分割参数并分发给子进程执行。同时讨论了fork和exec在进程管理和文件操作中的应用,以及涉及到的系统调用如fstat、stat和memmove。
摘要由CSDN通过智能技术生成

xargs:

//作用: 读取标准输出中的信息,然后将其作为标准输入,拼接到下一个命令后面来执行

#include "kernel/param.h"
#include "kernel/types.h"
#include "user/user.h"
#include "kernel/stat.h"
#include "kernel/fs.h"

int main(int argc, char *argv[]){
    int i;
    int count = 0;
    int k;
    int m = 0;
    char *lineSplit[MAXARG]; //字符串数组,用于存储命令行参数和从标准输入读取的参数
    char buf[32]; //构建参数字符串
    char block[32]; //从标准输入读取数据
    char *p;
    int status;

    p = buf;
    for(i= 1; i< argc; i++){ //除了xargs以外的
        lineSplit[count++] = argv[i];
    }


    while( (k = read(0, block, sizeof(block)))> 0){ //从标准输入读取数据块(也就是管道前的输出)
        //遍历从标准输入读取的每个字符
        for(i = 0; i<k; i++){
            if(block[i] == '\n'){ //是换行符,结束字符串, 执行一次命令行
                buf[m] = 0; //结束字符串
                lineSplit[count ++] = p; 
                lineSplit[count] = 0;
                m = 0;   //归零,为下一次做准备
                p = buf;
                count = argc - 1;

                if(fork() == 0){//子进程中
                    exec(argv[1], lineSplit); //执行
                }else{ //父进程中,等待子进程结束
                    wait(&status); 
                }

            }else if(block[i] == ' '){ //是空格, 结束当前字符串,并接收下一个字符串
                buf[m++] = 0;  //字符串结束, 接受下一个字符串
                lineSplit[count++]= p;
                p = &buf[m]; //指向下一个字符串的开始


            }else{  //不是换行符,也不是空格,而是有效参数,那么就继续往下读
                buf[m++] = block[i]; //存放到参数字符串中
            } 
        }
    }

    exit(0);
}

① “|”为管道的意思,在命令行中使用管道时,实际上是在创建一个从前一个命令到下一个命令的数据流,也就是说,会首先执行管道前的命令,并将管道前命令的输出作为管道后命令的输入

② lineSplit 应该存放两部分,前一部分是命令行参数(也就是管道后的命令,从xargs开始),第二部分是从标准输入得到的(也就是管道前的命令执行的结果),其中第二部分借由遍历存放在buf 里面的部分拼接。而p是一个指向buf的指针,用p赋值给lineSplit, 完成lineSplit的整个拼接

③ 这里区分了换行符, 空格, 和有效参数,比如:假设输入是"arg1 arg2\n",那么代码的执行流程是:

  1. 遇到 "arg1 " 中的字符 arg1 时,执行 else 分支,将字符加入 buf

  2. 遇到空格时,执行 else if 分支,结束当前字符串 "arg1",将其指针添加到 lineSplit

  3. 遇到 "arg2" 的字符 arg2 时,执行 else 分支,将字符加入 buf

  4. 遇到换行符 \n 时,执行 if 分支,结束当前字符串 "arg2",将其指针添加到 lineSplit,然后创建新进程并执行命令。

当从标准输入读取数据时,每个字符会被单独添加到 buf 中,包括 arg1 中的 arg 和 1(逐个字符进行处理),然后,当遇到空格或换行符时,将 buf 中的内容作为一个整体(这里是 arg1)添加到 lineSplit 中,然后清空 buf 以准备接收下一个参数(如 arg2).

④ 这种forkexec的组合允许创建一个新的进程,并在该进程中执行和父进程不同的程序,而不影响父进程的执行。这是Unix-like操作系统多任务处理和进程管理的基础

find:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *path, char *filename);

int main(int argc, char *argv[]){
    if(argc != 3){
        printf("Invalid command input\n");
        exit(1);
    }

    find(argv[1], argv[2]);
    exit(0);
}

//寻找指定路径的内容,path为哪个目录下, filename为需要被寻找的文件名称
void find(char *path, char *filename){
    char buf[512];
    char *p;
    int fd; //文件描述符,用于打开目录或者文件
    struct dirent de;  //目录项结构体,用于读取目录中的条目
    struct stat st;   // 看是为文件还是目录,还是设备

    /*打开文件*/
    fd = open(path, 0); //获取文件描述符,以只读模式打开给定的目录
    if(fd < 0){
        printf("open path %s err!\n", path);
        return;
    }

    /*由文件描述符取得文件状态,使用fstat函数*/
    if(fstat(fd, &st) < 0){
        printf("find: cannot stat %s\n", path);
        close(fd);
        return;
    }

    /*如果当前是个文件 或其他的 反正不是目录*/
    if(st.type != T_DIR){ 
        printf("find: %s is not a directory!\n", path);
        close(fd);
        return;
    }

    /*如果所给的是个目录*/
    if(st.type == T_DIR){
        if(strlen(path) + 1 + DIRSIZ + 1 > sizeof(buf)){ //数组过长
            printf("find: path too long\n");
            close(fd);
            return;
        }
        strcpy(buf, path); //将路径拷贝到buf中,并且p指向buf中路径字符串的末尾
        p = buf+strlen(buf); //p是一个指针,指向目录最后那个, buf才是完整名称
        *p++ = '/'; // 在末尾添加一个斜杠,为后续的项目名称留出空间

        //如果长度可以的话,从目录文件描述符fd中循环读取每个目录项,并将每个有效目录项的名字复制到p指向的位置
        // 循环读取指定路径path下的所有目录项
        while(read(fd, &de, sizeof(de)) == sizeof(de)){
            if(de.inum == 0) //文件对应的inode编号为0,则目录项无效, 可能是被删除的或者什么
                continue;
        
            memmove(p, de.name, DIRSIZ); //复制
            p[DIRSIZ] = 0; // 在末尾添加一个‘\0’


            //再获取该子目录项(也就是遍历父目录下的每一个子目录项,看是文件还是目录)的状态
            if(stat(buf, &st) <0){
                printf("find: cannot stat %s\n", path);
                continue;
                
            }

            if(strcmp(p, filename) == 0){ //返回值等于0,意味着p = filename
                printf("%s\n", buf);
            }else if(st.type == T_DIR && strcmp(p, ".")!=0 && strcmp(p, "..")!=0){  //如果被搜索项是目录项
                find(buf, filename); //递归,继续往下搜索
            }
        }


        close(fd);
    }
}

基本上就是ls那个代码,遍历被搜索目录下的每一个子项。如果子项是文件,则直接判断是否名称相符合,符合即打印。如果子项是目录,则递归继续搜索;

①这里注意两个函数:fstat 和 stat, 它们都是用来返回“相关文件状态信息”的,但是,fstat区别于另外其他系统调用的地方在于,fstat系统调用接受的是 一个“文件描述符”fd,而另外两个则直接接受“文件全路径”。文件描述符fd是需要我们用open系统调用后才能得到的,而文件全路经直接写就可以了。

②memmove 和 memcpy函数的区别:

  • memcpy 不会处理内存重叠的情况。如果源内存和目标内存重叠,memcpy 的行为是未定义的,可能会导致数据损坏。
  • memmove 能够处理内存重叠的情况。它会根据源和目标内存的相对位置来判断拷贝的方向,从而避免数据损坏

具体的:memcpy及memmove函数详解_memmove 缺少数据-CSDN博客

primes:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define RD 0
#define WR 1


void primes(int *c2c);

int main(int argc, char *args[]){
    if(argc != 1){
        printf("primes err!\n");
        exit(1);
    }

    int c2c[2];
    int status;
    pipe(c2c);

    

    /*创建子进程*/
    int pid;
    pid = fork();

    if(pid != 0){   /*在主进程中*/
        close(c2c[RD]);

        /*所有数均写入管道*/
        for(int i = 2; i<= 35; i++){
            write(c2c[WR], &i, sizeof(i));
        }

        close(c2c[WR]); /*关闭写端*/
        wait(&status);
        exit(0);

    }else{          /*在子进程中*/
        /*筛选质数*/
        close(c2c[WR]); //关闭写端,因为子进程要读
        primes(c2c);
        close(c2c[RD]); //关闭读端
        exit(0);
    }
}

//子进程的操作(读取上一个管道,并开启下一个管道)
void primes(int *c2c){
    int prime;
    //首先判断管道是否为空,如果空,则直接退出,也就是第一个数
    int len = read(c2c[RD], &prime, sizeof(prime)); 
    if(len == 0){  //管道已经空了,直接退出
        exit(0);
    }
    printf("prime %d\n", prime); //不为空,则打印质数

    int c2c1[2];  //创建子进程
    pipe(c2c1);
    int status;

    int pid1;
    pid1 = fork(); // 创建新的子进程(该子进程下的子子进程)

    if(pid1 != 0){ //父进程,该子进程,也就是主进程
        //使用上一个管道的读,和下一个管道的写
        close(c2c1[RD]);
        //读取之后的所有数,并判断是否可以被整除,送入下一个新的管道
        int readresult;
        while(read(c2c[RD], &readresult, sizeof(readresult)) > 0){    //源源不断的读
            if(readresult % prime != 0){   //不能被整除,则写入新的管道中
                write(c2c1[WR], &readresult, sizeof(readresult));
            }
        }

        close(c2c[WR]);
        close(c2c1[WR]);

        wait(&status);
        exit(0);

    }else{ // 第三个进程下
        close(c2c1[WR]);
        primes(c2c1);
        exit(0);
    }


}

代码有点冗杂,后续考虑优化;

大体思路就是: 管道两端是父进程和子进程,创建读取即可,这里需要谨慎注意管道的读写端的关,每个进程都拥有对管道的读和写权力,注意关闭!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值