文件及其操作概念
文件 = 内容 + 属性。空文件也要在磁盘占据空间,因为空文件也有属性
文件操作 = 对内容 + 对属性或者对属性和内容
标定一个文件,必须使用:文件路径+文件名,如果没有加路径则默认在当前路径找文件
对文件接口写好但是程序没有运行前文件没有被操作,对文件的操作本质是进程和被打开文件的关系
一个文件要被访问前提必须要被打开,打开文件是用户去调用接口操作系统去打开。
进程可以打开多个文件,系统中会存在大量被打开的文件,操作系统会以链式结构管理起来会为文件创建对应的内核数据结构标识文件(struct file{})
**任何一个被打开的文件结构体对象struct file对象,不同的文件对应的读写方法不一样,struct file对象里面可以有很多的(readp)()、(writep)()函数指针,通过函数指针指向具体的读写方法
文件系统调用接口
首先要有个共识,所有高级语言的文件调用接口都是对系统的文件调用接口进行封装出来的
open:
打开文件,文件不存在就创建文件
参数一为打开的文件名,参数二为打开方式(只读:O_RDONLY,只写:O_WRONLY,只读:O_RDWR,O_CREAT:文件不存在时创建文件,O_TRUNC:清空文件内容)参数三为新建时默认权限(文件不存在时创建文件必须加上参数三)
open成功时会返回文件描述符,失败返回-1close:关闭文件
参数为文件的描述符
write:
向文件中写入
参数一为文件描述符,参数二为输入的数据,参数三为输入数据的字节数
返回值为写入的字节数
使用这个接口可以用 sprintf(将特定的内容格式化到字符串)接口配合使用
read:
读取文件的数据
参数一为文件描述符,参数二为存储数据的空间,参数三为空间的大小
调用成功则返回读取的字节数,0代表读到文件结尾
文件描述符
C语言中操作文件中的FILE是一个结构体,这里有个字段代表文件描述符,操作系统通过文件描述符标识文件
平常对文件操作时默认是从3依次对文件表示
0 -> stdin
1 -> stdout
2 -> stderr
每个进程都会有一个 file指针去指向一个指针数组,该数组存放指向文件的指针,所以文件描述符的本质就是数组的下标,进程可以直接通过下标找到文件。Linux下stdin、stdout、stderr这三个文件时默认打开的,所以当打开文件时就会从3开始,相对应的如果将0、1、2关闭后再打开文件那么该文件的文件描述符就会从最小没有被占用的下标开始
重定向
重定向的本质就是上层的fd不变,在内核中更改fd对应的struct_file*的地址,也就是改变文件描述符对应的文件
dup2:
将新打开的文件重定向到指定文件,将调用接口的进程所打开的文件的文件描述符的内容拷贝到指定的文件描述符,1=stdout, 3=myfile --> dup2(3, 1) --> 1=myfile, 3=myfile
参数一为需要重定向文件的文件描述符,参数二为被重定向文件的文件描述符
调用失败返回-1,成功返回调用的文件描述符
追加重定向:
只要将打开文件的方式加上追加的选项就可以了
输入重定向:
可以指定从某个文件中读取,重定向0号文件描述符后就不会再在键盘中读取
实现简单的shell
#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<sys/types.h> #include<sys/stat.h> #include<sys/wait.h> #include<assert.h> #include<fcntl.h> #include<string.h> #define NUM 1024 #define OPT_NUM 64 //默认没有重定向 #define NOT_ 0 //输入重定向 #define INPUT_ 1 //追加重定向 #define ADDOUT_ 2 //输出重定向 #define OUTPUT_ 3 //标准错误重定向 #define ERROR_ 4 char Line[NUM]; char* myargv[OPT_NUM]; //记录退出状态 int lastCode = 0; int lastSig = 0; //利用数据表示是哪种重定向 int redirtype = NOT_; //重定向文件的文件名 char* redirFileName = NULL; //判断是否有重定向 void command(char* commands){ assert(commands); //定义字符串的头尾指针 char* start = commands; char* end = commands + strlen(commands); //循环遍历是否有重定向的符号 while(start != end){ if(*start == '>'){ *start = '\0'; ++start; //因为重定向有> 和 >>,所以还要再判断一次 if(*start == '>'){ *start = '\0'; ++start; while(*start == ' ') ++start; redirtype = ADDOUT_; redirFileName = start; }else{ while(*start == ' ') ++start; redirtype = OUTPUT_; redirFileName = start; } break; }else if(*start == '<'){ *start = '\0'; ++start; while(*start == ' ') ++start; //分隔开后就可以得到重定向的信息 redirtype = INPUT_; redirFileName = start; break; }else{ ++start; } } } //通过进程替换编写shell void Shell(){ while(1){ //初始化全局数据 redirFileName = NULL; redirtype = NOT_; printf("用户名@主机名 当前路径#"); //刷新缓冲区 fflush(stdout); //获取输入指令,输入时会带着回车 //需要去掉换行 char* s = fgets(Line, sizeof(Line) - 1, stdin); assert(s != NULL); //清除最后的元素(换行) Line[strlen(Line) - 1] = 0; //考虑到重定向的情况所以需要先对读到的字符串分析一遍 //判断是否会有 > < >> 符号 command(Line); //要把指令和选项分开,以空格为分隔符 //strtok myargv[0] = strtok(Line, " "); int i = 1; //给ls指令加上颜色选项 if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0) myargv[i++] = "--color=auto"; while(myargv[i++] = strtok(NULL, " ")); //需要注意cd指令执行并不需要子进程去执行 //因为子进程去更改当前工作目录对父进程没有影响 //子进程执行完退出后还是没有更改父进程的当前工作路径 //因此遇到cd指令时,使用内置接口chdir去执行 //不需要子进程去执行而是让shell自己执行的命令就叫内建/内置命令 if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0){ if(myargv[1] != NULL) chdir(myargv[1]); continue; } //如果遇到echo指令也不需要子进程 if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0){ if(strcmp(myargv[1], "$?") == 0) printf("Code:%d, Sig:%d\n", lastCode, lastSig); //如果不是$? 则打印echo后面的所有字符串 else{ for(i = 1; myargv[i]; ++i) printf("%s ", myargv[i]); printf("\n"); } continue; } //创建子进程执行指令 pid_t id = fork(); assert(id != -1); if(id == 0){ //因为命令是子进程执行的,所以重定向也是 switch (redirtype) { case NOT_: //不执行操作 break; case INPUT_:{ //输入重定向 int fd = open(redirFileName, O_RDONLY); if(fd < 0){ perror("open"); exit(2); } dup2(fd, 0); break; } case OUTPUT_:{ //输出重定向 int flags = O_WRONLY | O_CREAT; int fd = open(redirFileName, flags, 0666); if(fd < 0){ perror("open"); exit(2); } dup2(fd, 1); break; } case ADDOUT_:{ //追加重定向 int flags = O_WRONLY | O_CREAT | O_APPEND; int fd = open(redirFileName, flags, 0666); if(fd < 0){ perror("open"); exit(2); } dup2(fd, 1); break; } default: break; } execvp(myargv[0], myargv); exit(1); } int status = 0; pid_t ret = waitpid(id, &status, 0); //取到退出状态 assert(ret > 0); lastCode = (status >> 8) & 0xff; lastSig = status & 0x7f; } }