网站打开速度加快怎么做,义乌住房与城乡建设官网,照片做视频的软件 模板下载网站,全球装修公司前十强W...Y的主页 #x1f60a;
代码仓库分享#x1f495; 前言#xff1a;相信大家对文件都不会太陌生、也不会太熟悉。在没有学习Linux操作系统时#xff0c;我们在学习C或C时都学过如何去创建、打开、读写等待文件的操作#xff0c;知道一些语言级别的一些接口与函数。但…
W...Y的主页
代码仓库分享 前言相信大家对文件都不会太陌生、也不会太熟悉。在没有学习Linux操作系统时我们在学习C或C时都学过如何去创建、打开、读写等待文件的操作知道一些语言级别的一些接口与函数。但是我相信对于没有学习操作系统的对文件的认识还很浅今天我们就进入文件好好学习操作系统与文件的关系。相信大家看完文件篇后会对文件有了新的认识和启发。话不多说我们开始学习。
目录
铺垫
代码回顾C文件接口
系统文件I/O
open接口
write read close类比C文件相关接口
文件描述符fd
文件描述符的分配规则 Linux中的重定向 使用 dup2 系统调用
在自治shell中加入重定向 铺垫
1.当我们创建一个文件但不对文件进行写入时需不需要开辟一些空间呢肯定是需要的。因为文件 内容 属性。文件的名称、创建时间、修改时间……对应的信息都需要进行存储。
2.我们访问文件之前都得先打开文件修改文件都是通过代码的方式完成修改的。而我们不能通过代码对磁盘中的文件直接做修改是直接将文件加载到内存中后通过CPU进行对文件的修改。
3.我们在C语言中都知道想要操作一个文件就必须使用fopen函数将文件打开那是谁打开的文件呢当我们在打开文件之前必须要将想要访问文件的程序跑起来所以归根结底是进程打开了文件。
4.一个进程也可以打开多个文件。所以一定时间段内系统中存在多个进程也可能存在更多被打开的文件。OS就要对这些被打开的文件进行管理所以内核中一定要有描述被打开文件的结构体并用其定义对象。
5.系统中不是所有的文件都是被打开的被打开的文件叫内存文件没有被打开的文件叫磁盘文件。
代码回顾C文件接口
hello.c写文件
#include stdio.h
#include string.h
int main()
{FILE *fp fopen(myfile, w);if (!fp){printf(fopen error!\n);}const char *msg hello why!\n;int count 5;while (count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}fopen模式中有很多模式 r 打开文本文件进行阅读流位于文件的开头。 r开放阅读和写作流位于文件的开头。 w截断缩短文件为零长度或创建文本文件进行写入流位于文件的开头。 w开放阅读和写作如果文件不存在则创建该文件否则将被截断流位于文件的开头。 a打开以进行追加写在文件末尾。如果文件不存在则创建该文件流位于文件的末尾。 a打开以供读取和追加在文件末尾写入。如果文件不存在则创建该文件。初始文件位置。对于阅读是在文件的开头但输出始终附加到文件末尾。 #include stdio.h
#include string.h
int main()
{FILE *fp fopen(myfile, r);if (!fp){printf(fopen error!\n);}char buf[1024];const char *msg hello bit!\n;while (1){// 注意返回值和参数此处有坑仔细查看man手册关于该函数的说明size_t s fread(buf, 1, strlen(msg), fp);if (s 0){buf[s] 0;printf(%s, buf);}if (feof(fp)){break;}}fclose(fp);return 0;
} fwrite与fread函数都是C语言提供对文件读写的接口 其就是将对应的内容输入或输出对应的文件流。 fread函数参数 void * buffer 也就是想要把读取的数据到的地方的地址 比如一个叫做a的整形变量的地址 也就是a 又或许是一个结构体对象 假设他叫s 那便是s size_t size 这就是接收数据的变量所占空间的大小 如果他是一个int类型 那就是sizeof(int) size_t count 要写入数据的个数 FILE*stream 这是管理文件操作的指针 一般会在开始对文件开始操作前定义 这个指针会指向我们接下来要操作的文件 fwrite函数参数与fread参数相同只是方向刚好相反。 输出信息到显示器你有哪些方法
#include stdio.h
#include string.h
int main()
{const char *msg hello fwrite\n;fwrite(msg, strlen(msg), 1, stdout);printf(hello printf\n);fprintf(stdout, hello fprintf\n);return 0;
} stdin stdout stderr
C默认会打开三个输入输出流分别是stdin, stdout, stderr 仔细观察发现这三个流的类型都是FILE*, fopen返回值类型文件指针
系统文件I/O
操作文件除了上述C接口当然C也有接口其他语言也有我们还可以采用系统接口来进行文件访问先来直接以代码的形式实现和上面一模一样的代码
写文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
int main()
{umask(0);int fd open(myfile, O_WRONLY | O_CREAT, 0644);if (fd 0){perror(open);return 1;}int count 5;const char *msg hello bit!\n;int len strlen(msg);while (count--){write(fd, msg, len); // fd: 后面讲 msg缓冲区首地址 len: 本次读取期望写入多少个字节的数据。 返回值实际写了多少字节数据}close(fd);return 0;
} 读文件
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include unistd.h
#include string.h
int main()
{
int fd open(myfile, O_RDONLY);
if(fd 0){
perror(open);
return 1;
}
const char *msg hello bit!\n;
char buf[1024];
while(1){
ssize_t s read(fd, buf, strlen(msg));//类比write
if(s 0){
printf(%s, buf);
}else{
break;
}
}
close(fd);
return 0;
}
open接口 #include sys/types.h #include sys/stat.h #include fcntl.h int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); pathname: 要打开或创建的目标文件 flags: 打开文件时可以传入多个参数选项用下面的一个或者多个常量进行“或”运算构成flags。参数: O_RDONLY: 只读打开 O_WRONLY: 只写打开 O_RDWR : 读写打开 这三个常量必须指定一个且只能指定一个 O_CREAT : 若文件不存在则创建它。需要使用mode选项来指明新文件的访问权限 O_APPEND: 追加写 其实我们可以使用|的方式将flags参数进行组合因为他们就是宏。 返回值成功新打开的文件描述符 失败-1 mode的意思就是创建目标文件时所需要的权限。 open 函数具体使用哪个和具体应用场景相关如目标文件不存在需要open创建则第三个参数表示创建文件的默认权限,否则使用两个参数的open。
write read close类比C文件相关接口
在认识返回值之前先来认识一下两个概念: 系统调用 和 库函数
上面的 fopen fclose fread fwrite 都是C标准库当中的函数我们称之为库函数libc。 而 open close read write lseek 都属于系统提供的接口称之为系统调用接口 回忆一下我们讲操作系统概念时画的一张图 系统调用接口和库函数的关系一目了然。 所以可以认为f#系列的函数都是对系统调用的封装方便二次开发。
文件描述符fd 通过学习open函数我们知道在成功打开文件后会返回一个文件描述符而这些文件描述符就是一些整数当我们打开多个文件后这些数字会从3开始一直往后延续那为什么0、1、2没有人用呢
0 1 2
Linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0 标准输出1 标准错误2.0,1,2对应的物理设备一般是键盘显示器显示器
在系统层面我们可以看出这写接口都是以的是系统提供的接口所使用的参数是文件描述符而在语言层面上使用的都是File*指针这个File是C语言分装的一个结构体。 而现在知道文件描述符就是从0开始的小整数。当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针所以本质上文件描述符就是该数组的下标。所以只要拿着文件描述符就可以找到对应的文件 。 上图是源码所以文件描述符的本质就是数组下标
文件描述符的分配规则
直接看代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
int main()
{
int fd open(myfile, O_RDONLY);
if(fd 0){
perror(open);
return 1;
}
printf(fd: %d\n, fd);
close(fd);
return 0;
}
输出发现是 fd: 3
关闭0或者2在看
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
int main()
{
close(0);
//close(2);
int fd open(myfile, O_RDONLY);
if(fd 0){
perror(open);
return 1;
}
printf(fd: %d\n, fd);
close(fd);
return 0;
} 发现是结果是 fd: 0 或者 fd 2 可见文件描述符的分配规则在files_struct数组当中找到当前没有被使用的最小的一个下标作为新的文件描述符。 Linux中的重定向
我们知道重定向可以向文件中写入内容。 但是当我们使用 对文件进行重定向时多次对文件进行写入然后进行查看我们只能看到文件中只有我们最后一次写的内容清空了之前写的所有内容。这不就是我们fopen函数中的‘w’模式。 当我们使用操作符对文件进行重定向时每次对文件写入都不会清空文件中的内容。所以这就类比fopen函数中的‘a’模式。 当我们现在已经知道重定向的含义以及文件描述符的分配规则并且我们知道在语言层面是stdin、stdout、strerr在系统层面就是0、1、2当我们关闭1然后使用写打开一个文件时使用stdout就可以往文件中写入这种现象叫做输出重定向。 输出重定向代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
int main()
{close(1);int fd open(myfile, O_WRONLY | O_CREAT, 00644);if (fd 0){perror(open);return 1;}printf(fd: %d\n, fd);fflush(stdout);close(fd);exit(0);
}
输入重定向代码
#include stdio.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include stdlib.h
int main()
{close(0);int fd open(test.txt, O_RDONLY);if (fd 0){perror(open);return 1;}int a 0;scanf(%d, a);printf(%d\n, a);close(fd);exit(0);
} 追加重定向与输出重定向基本相同唯一不同的就是将open函数中的flags参数改成O_WRONLY | O_CREAT|O_TRUNC即可。 使用 dup2 系统调用 其中oldfd表示待复制的文件描述符newfd表示新的文件描述符。该函数返回新的文件描述符若出错则返回-1。
dup2函数的工作原理相对简单主要包括以下几个步骤 检查oldfd和newfd是否相等若相等则不进行任何操作直接返回newfd检查newfd的合法性若已经打开则先关闭复制oldfd的文件表项到newfd使得两者指向同一个文件表项返回newfd。 所以我们不需要进行close直接使用dup2即可。
#include stdio.h
#include unistd.h
#include fcntl.h
int main()
{int fd open(./log, O_CREAT | O_RDWR);if (fd 0){perror(open);return 1;}close(1);dup2(fd, 1);for (;;){char buf[1024] {0};ssize_t read_size read(0, buf, sizeof(buf) - 1);if (read_size 0){perror(read);break;}printf(%s, buf);fflush(stdout);}return 0;
}
在自治shell中加入重定向
我们在命令行中可以使用echo指令进行重定向我们之前做的简易shell中没有重定向现在我们将代码补充完整
#include stdio.h
#include stdlib.h
#include string.h
#include unistd.h
#include ctype.h
#include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/wait.h#define SIZE 1024
#define MAX_ARGC 64
#define SEP
#define STREND \0// 下面的都和重定向有关
#define NoneRedir -1
#define StdinRedir 0
#define StdoutRedir 1
#define AppendRedir 2#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos; }while(0)int redir_type NoneRedir;
char *filename NULL;char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode 0;// ls -a -l
// ls -a -l log.txtconst char* HostName()
{char *hostname getenv(HOSTNAME);if(hostname) return hostname;else return None;
}const char* UserName()
{char *hostname getenv(USER);if(hostname) return hostname;else return None;
}const char *CurrentWorkDir()
{char *hostname getenv(PWD);if(hostname) return hostname;else return None;
}char *Home()
{return getenv(HOME);
}int Interactive(char out[], int size)
{// 输出提示符并获取用户输入的命令字符串ls -a -lprintf([%s%s %s]$ , UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] 0; //\0, commandline是空串的情况?return strlen(out);
}void CheckRedir(char in[])
{// ls -a -l// ls -a -l log.txt// ls -a -l log.txt// cat log.txtredir_type NoneRedir;filename NULL;int pos strlen(in) - 1;while( pos 0 ){if(in[pos] ){if(in[pos-1] ){redir_type AppendRedir;in[pos-1] STREND;pos;IgnSpace(in, pos);filename inpos;break;}else{redir_type StdoutRedir;in[pos] STREND;IgnSpace(in, pos);filename inpos;//printf(debug: %s, %d\n, filename, redir_type);break;}}else if(in[pos] ){redir_type StdinRedir;in[pos] STREND;IgnSpace(in, pos);filename inpos;//printf(debug: %s, %d\n, filename, redir_type);break;}else{pos--;}}
}void Split(char in[])
{CheckRedir(in);int i 0;argv[i] strtok(in, SEP); // ls -a -lwhile(argv[i] strtok(NULL, SEP)); // 故意将 写成 if(strcmp(argv[0], ls) 0){argv[i-1] (char*)--color;argv[i] NULL;}
}void Execute()
{pid_t id fork();if(id 0){int fd -1;if(redir_type StdinRedir){fd open(filename, O_RDONLY);dup2(fd, 0);}else if(redir_type StdoutRedir){fd open(filename, O_CREAT | O_WRONLY | O_TRUNC);dup2(fd, 1);}else if(redir_type AppendRedir){fd open(filename, O_CREAT | O_WRONLY | O_APPEND);dup2(fd, 1);}else{// do nothing}// 让子进程执行命名execvp(argv[0], argv);exit(1);}int status 0;pid_t rid waitpid(id, status, 0);if(rid id) lastcode WEXITSTATUS(status); //printf(run done, rid: %d\n, rid);
}int BuildinCmd()
{int ret 0;// 1. 检测是否是内建命令, 是 1, 否 0if(strcmp(cd, argv[0]) 0){// 2. 执行ret 1;char *target argv[1]; //cd XXX or cdif(!target) target Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, PWD%s, temp);putenv(pwd);}else if(strcmp(export, argv[0]) 0){ret 1;if(argv[1]){strcpy(env, argv[1]);putenv(env);}}else if(strcmp(echo, argv[0]) 0){ret 1;if(argv[1] NULL) {printf(\n);}else{if(argv[1][0] $){if(argv[1][1] ?){printf(%d\n, lastcode);lastcode 0;}else{char *e getenv(argv[1]1);if(e) printf(%s\n, e);}}else{printf(%s\n, argv[1]);}}}return ret;
}int main()
{while(1){char commandline[SIZE];// 1. 打印命令行提示符获取用户输入的命令字符串int n Interactive(commandline, SIZE);if(n 0) continue;// 2. 对命令行字符串进行切割Split(commandline);// 3. 处理内建命令n BuildinCmd();if(n) continue;// 4. 执行这个命令Execute();}return 0;
}上面就是简易shell完整版代码仅供参考 以上就是本次全部内容感谢大家观看。