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",那么代码的执行流程是:
-
遇到
"arg1 "
中的字符a
、r
、g
、1
时,执行else
分支,将字符加入buf
。 -
遇到空格时,执行
else if
分支,结束当前字符串"arg1"
,将其指针添加到lineSplit
。 -
遇到
"arg2"
的字符a
、r
、g
、2
时,执行else
分支,将字符加入buf
。 -
遇到换行符
\n
时,执行if
分支,结束当前字符串"arg2"
,将其指针添加到lineSplit
,然后创建新进程并执行命令。
当从标准输入读取数据时,每个字符会被单独添加到 buf
中,包括 arg1
中的 a
、r
、g
和 1(逐个字符进行处理),
然后,当遇到空格或换行符时,将 buf
中的内容作为一个整体(这里是 arg1
)添加到 lineSplit
中,然后清空 buf
以准备接收下一个参数(如 arg2
).
④ 这种fork
和exec
的组合允许创建一个新的进程,并在该进程中执行和父进程不同的程序,而不影响父进程的执行。这是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);
}
}
代码有点冗杂,后续考虑优化;
大体思路就是: 管道两端是父进程和子进程,创建读取即可,这里需要谨慎注意管道的读写端的关,每个进程都拥有对管道的读和写权力,注意关闭!