当前位置: 首页 > news >正文

网站seo在线诊断上海优化营商环境

网站seo在线诊断,上海优化营商环境,企业公司网站建设ppt,牌具网站广告怎么做Linux#xff08;基础IO#xff09; 前言C语言文件IO什么叫当前路径stdin/stdout/stderr 系统文件IOopenclosewriteread 文件描述符文件描述符的分配规则 重定向输出重定向原理追加重定向原理输入重定向原理dup2添加重定向功能到minishell 缓冲区模拟实现一个缓冲区 理解文件… Linux基础IO 前言C语言文件IO什么叫当前路径stdin/stdout/stderr 系统文件IOopenclosewriteread 文件描述符文件描述符的分配规则 重定向输出重定向原理追加重定向原理输入重定向原理dup2添加重定向功能到minishell 缓冲区模拟实现一个缓冲区 理解文件系统初识inode磁盘的概念磁盘分区与格式化介绍EXT2文件系统的存储方案 软硬链接软链接硬链接 文件的三个时间 前言 我们知道CC中存在各类文件操作但是我们需要理解并不是CC直接去访问这些文件的他们通过系统所提供的的接口进行访问然后进行各种操作。 OS接口只有一套但是不同语言文件访问接口不一样所以就需要对系统接口进行封装因为一旦都使用系统接口编写文件代码就无法在其他平台运行不具备跨平台性了所以这也是我们学校系统文件接口的主要原因更好的理解CC文件操作的底层结构。 C语言文件IO 我们先来回忆一下在C语言中的各类文件操作 文件操作函数功能fopen打开文件fclose关闭文件fputc写入一个字符fgetc读取一个字符fputs写入一个字符串fgets读取一个字符串fprintf格式化写入数据fscanf格式化读取数据fwrite向二进制文件写入数据fread从二进制文件读取数据fseek设置文件指针的位置ftell计算当前文件指针相对于起始位置的偏移量rewind设置文件指针到文件的起始位置ferror判断文件操作过程中是否发生错误feof判断文件指针是否读取到文件末尾 文件打开形式 文件打开方式含义“r”read打开文件进行输入操作。该文件必须存在。“w”write为输出操作创建一个空文件。如果已存在同名文件则丢弃其内容并将该文件视为新的空文件。“a”append打开文件以在文件末尾输出。输出操作总是在文件末尾写入数据并对其进行扩展。忽略重新定位操作fseek、fsetpos、rewind。如果文件不存在则创建该文件。“r”read/update打开一个文件进行更新输入和输出。文件必须存在。“w”write/update创建一个空文件并打开它以进行更新输入和输出。如果同名文件已经存在则将丢弃其内容并且该文件将被视为新的空文件。“a”append/uptate打开一个文件进行更新包括输入和输出所有输出操作都在文件末尾写入数据。重新定位操作fseek、fsetpos、rewind会影响下一个输入操作但输出操作会将位置移回文件末尾。如果文件不存在则创建该文件。 对文件进程写入操作 1 #include stdio.h2 #include string.h3 #include unistd.h4 5 int main()6 {7 FILE *pf fopen(log.txt, w);8 if(pf NULL)9 {10 perror(fpeon);11 return 1;12 }13 const char *s hello world\n;14 fwrite(s, strlen(s), 1, pf); 15 16 fclose(pf);17 return 0;18 }对文件进行读取操作 1 #include stdio.h2 #include string.h3 #include unistd.h46 int main()7 {8 FILE *pf fopen(log.txt, r);9 if(pf NULL)10 {11 perror(fpeon);12 return 1;13 }14 char buffer[64]; 15 fread(buffer, sizeof(buffer), 1, pf);16 printf(%s\n, buffer); 17 fclose(pf); 18 return 0; 19 } 对文件进程追加字符串操作 1 #include stdio.h 2 #include string.h3 #include unistd.h 4 5 int main() 6 { 7 FILE *pf fopen(log.txt, a);8 if(pf NULL)9 { 10 perror(fpeon); 11 return 1; 12 } 13 const char* s hello world\n; 14 fwrite(s, strlen(s), 1, pf); 15 fclose(pf);16 return 0; 17 } 我们需要注意的是fopen以w形式打开文件默认先清空文件在fwrite之前fopen以a形式打开文件是不断像文件中追加内容。 什么叫当前路径 我们知道当fopen以写入的方式打开一个文件时若该文件不存在则会自动在当前路径创建该文件那么这里所说的当前路径指的是什么呢 我们在study_8_22目录下创建mytest可执行程序创建的log.txt就会出现在study_8_22目录下 我们返回上级目录将可执行程序移动到此目录中然后运行 我们会发现依然在此目录下创建了log.txt文件并没有在study_8_22目录下创建。 当该可执行程序运行起来变成进程后我们可以获取该进程的PID然后根据该PID在根目录下的proc目录下查看该进程的信息。 我们可以看到两个软链接文件cwd和execwd就是进程运行时我们所处的路径而exe就是该可执行程序的所处路径。 我们就可以得出结论 我们这里所说的当前路径不是指可执行程序所处的路径而是指该可执行程序运行成为进程时我们所处的路径。 stdin/stdout/stderr 都说Linux下一切皆文件也就是说Linux下的任何东西都可以看作是文件那么显示器和键盘当然也可以看作是文件。我们能看到显示器上的数据是因为我们向“显示器文件”写入了数据电脑能获取到我们敲击键盘时对应的字符是因为电脑从“键盘文件”读取了数据。 那么既然是写入数据和读取数据就要打开相应的文件那么为什么我们没有进行相应打开文件操作呢 打开文件是在进程运行的时候打开的而对于任何进程来说运行的时候都会默认打开标准输入流标准输出流和标准错误流对应到C语言当中就是stdin、stdout以及stderr。 标准输入流对应的是键盘标准输出流和标准错误流对应的都是显示器。 我们查看man手册就可以发现他们三个的返回值都是FILE* 类型的 当我们的C程序运行起来的时候操作系统会默认打开这三个流所以我们才可以进行scanfprintf等一系列的读取与输出的操作stdin/stdout/stderr与我们打开文件获得的文件指针是一个概念。 1 #include stdio.h2 #include string.h3 #include unistd.h4 5 int main()6 {7 fprintf(stdout, hello world\n); 8 return 0;9 }1 #include stdio.h2 #include string.h3 #include unistd.h4 5 int main()6 {7 int a;8 fscanf(stdin, %d, a);9 printf(%d\n, a); 10 return 0;11 }系统文件IO 操作文件除了上述C接口当然C也有接口其他语言也有我们还可以采用系统接口来进行文件访问。 open 系统文件接口中使用open打开文件 int open(const char *pathname, int flags, mode_t mode);open第一个参数 open函数的第一个参数是pathname表示要打开或创建的目标文件。 若pathname以路径的方式给出则当需要创建该文件时就在pathname路径下进行创建。 若pathname以文件名的方式给出则当需要创建该文件时默认在当前路径下进行创建。 open第二个参数 open函数的第二个参数是flags表示打开文件的方式。 其中常用选项有如下几个 参数选项含义O_RDONLY以只读的方式打开文件O_WRNOLY以只写的方式打开文件O_APPEND以追加的方式打开文件O_RDWR以读写的方式打开文件O_CREAT当目标文件不存在时创建文件 打开文件时可以传入多个参数选项当有多个选项传入时将这些选项用“或”运算符隔开。 例如若想以只写的方式打开文件但当目标文件不存在时自动创建文件则第二个参数设置如下 O_WRONLY | O_CREAT系统接口open的第二个参数flags是整型有32比特位若将一个比特位作为一个标志位则理论上flags可以传递32种不同的标志位。 实际上传入flags的每一个选项在系统当中都是以宏的方式进行定义的我们举个例子 1 #include stdio.h2 #include string.h3 #include unistd.h4 5 //用int中不重复的一个bit标识一种状态6 #define ONE 0x1 //0000 00017 #define TWO 0x2 //0000 00108 #define THREE 0x4 //0000 01009 10 void show(int flags)11 {12 if(flags ONE) printf(hello one\n);13 if(flags TWO) printf(hello two\n);14 if(flags THREE) printf(hello three\n); 15 }16 int main()17 {18 show(ONE); //0000 000119 printf(---------------------------\n);20 show(ONE | TWO); // 0000 001121 printf(---------------------------\n);22 show(ONE | TWO | THREE); // 0000 011123 printf(---------------------------\n);24 show(ONE | THREE); // 0000 010125 return 0;26 }运行程序 系统文件接口中也是类似于这种方式实现的 例如O_RDONLY、O_WRONLY、O_RDWR和O_CREAT在系统当中的宏定义如下 #define O_RDONLY 00 #define O_WRONLY 01 #define O_RDWR 02 #define O_CREAT 0100open第三个参数 open函数的第三个参数是mode表示创建文件的默认权限。 1 #include stdio.h2 #include string.h3 #include unistd.h4 #include sys/types.h5 #include sys/stat.h6 #include fcntl.h7 8 int main()9 {11 int fd open(log.txt, O_WRONLY | O_CREAT, 0666);12 if(fd 0)13 { 14 perror(open); 15 }16 printf(open sucess:fd:%d\n, fd); 17 return 0;18 }例如将mode设置为0666则文件创建出来的权限如下 这是因为umask默认值为0002实际创建出来文件的权限为mode(~umask)。当我们设置mode值为0666时实际创建出来文件的权限为0664。 我们此时将umask值设置为0 umask(0);open的返回值 open函数的返回值是新打开文件的文件描述符。 1 #include stdio.h2 #include string.h3 #include unistd.h4 #include sys/types.h5 #include sys/stat.h6 #include fcntl.h7 8 int main()9 {10 umask(0);11 int fd1 open(log1.txt, O_WRONLY | O_CREAT, 0666);12 int fd2 open(log2.txt, O_WRONLY | O_CREAT, 0666);13 int fd3 open(log3.txt, O_WRONLY | O_CREAT, 0666);14 int fd4 open(log4.txt, O_WRONLY | O_CREAT, 0666); 15 int fd5 open(log5.txt, O_WRONLY | O_CREAT, 0666);16 printf(open sucess:fd1:%d\n, fd1);17 printf(open sucess:fd2:%d\n, fd2);18 printf(open sucess:fd3:%d\n, fd3);19 printf(open sucess:fd4:%d\n, fd4);20 printf(open sucess:fd5:%d\n, fd5);21 return 0;22 }运行程序我们可以发现文件描述符是连续且递增的 当我们打开一个没有被创建的文件时 1 #include stdio.h2 #include string.h3 #include unistd.h4 #include sys/types.h5 #include sys/stat.h6 #include fcntl.h7 int main()8 {9 int fd open(test.txt, O_RDONLY);10 printf(%d\n, fd);11 return 0;12 } fd的值就为-1。 实际上这里所谓的文件描述符本质上是一个指针数组的下标指针数组当中的每一个指针都指向一个被打开文件的文件信息通过对应文件的文件描述符就可以找到对应的文件信息。 当使用open函数打开文件成功时数组当中的指针个数增加然后将该指针在数组当中的下标进行返回而当文件打开失败时直接返回-1因此成功打开多个文件时所获得的文件描述符就是连续且递增的。 而Linux进程默认情况下会有3个缺省打开的文件描述符分别就是标准输入0、标准输出1、标准错误2这就是为什么成功打开文件时所得到的文件描述符是从3开始进程分配的。 close 系统接口中使用close函数关闭文件close函数的函数原型如下 使用close函数时传入需要关闭文件的文件描述符即可若关闭文件成功则返回0若关闭文件失败则返回-1。 write 系统接口中使用write函数向文件写入信息write函数的函数原型如下 我们可以使用write函数将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中。 如果数据写入成功实际写入数据的字节个数被返回。如果数据写入失败-1被返回。 操作示例 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 8 int main()9 {10 int fd open(log.txt, O_WRONLY | O_CREAT, 0666);11 if(fd 0)12 { 13 perror(open);14 return 1;15 }16 const char* s hello world\n;17 write(fd, s, strlen(s));18 close(fd);19 return 0;20 }read 系统接口中使用read函数从文件读取信息read函数的函数原型如下 我们可以使用read函数从文件描述符为fd的文件读取count字节的数据到buf位置当中。 如果数据读取成功实际读取数据的字节个数被返回。如果数据读取失败-1被返回。 操作示例 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 8 int main()9 {10 int fd open(log.txt, O_RDONLY);11 if(fd 0)12 {13 perror(open);14 return 1;15 }16 17 char buffer[64];18 read(fd, buffer, sizeof(buffer));19 printf(%s\n, buffer); 20 21 close(fd);22 return 0;23 }文件描述符 文件是在进程运行时打开的一个进程可以打开多个文件系统在任何时刻都会存在大量已经打开的文件而我们操作系统需要对这些已打开的文件进行管理操作系统会为每个已经打开的文件创建各自的struct file结构体然后将这些结构体以双链表的形式连接起来之后操作系统对文件的管理也就变成了对这张双链表的增删查改等操作。 注意 向文件写入数据时是先将数据写入到对应文件的缓冲区当中然后定期将缓冲区数据刷新到磁盘当中。 什么叫做进程运行时会默认打开0 12 0对应的标准输入流就是键盘1对应标准输出流是显示器2对应标准错误流也是显示器我们进程刚开始运行时操作系统就会格局键盘显示器形成相应的struct file并以双链表形式链接起来并且将它的地址分别填入fd_array指针数组下标012的位置由此就默认打开了标准输入流标准输出流标准错误流。 磁盘文件和内存文件 我们将被打开的文件称之为内存文件没有被打开的文件称为磁盘文件磁盘文件又由两部分构成文件内容文件属性磁盘文件和内存文件就像程序与进程之间的关系一样当程序运行起来就变成了进程而磁盘文件被写入内存中就变成了内存文件。 文件描述符的分配规则 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 int main()8 {9 umask(0);10 int fd1 open(log1.txt, O_RDONLY|O_CREAT, 0666);11 int fd2 open(log2.txt, O_RDONLY|O_CREAT, 0666);12 int fd3 open(log3.txt, O_RDONLY|O_CREAT, 0666);13 int fd4 open(log4.txt, O_RDONLY|O_CREAT, 0666);14 int fd5 open(log5.txt, O_RDONLY|O_CREAT, 0666);15 printf(%d\n, fd1);16 printf(%d\n, fd2);17 printf(%d\n, fd3);18 printf(%d\n, fd4);19 printf(%d\n, fd5); 20 return 0;21 }运行程序我们会发现文件描述符按一下规则排列 其中012的位置上已经被标准输入流标准输出流和标准错误流占了所以从3开始分配。 那么如果我们关掉文件描述符为0的文件呢 close(0);我们会发现此时的第一个文件描述符fd是0了。 同样我们关掉0和2试一试 由此我们可以得出一个结论 文件描述符fd是从函数指针数组fd_array下标最小未被使用的开始进行分配的。 重定向 在了解了文件描述符与它的分配原则以后我们就可以进一步探索重定向的问题了其实重定向的本质就是修改文件描述符下标对应的struct file*的内容。 输出重定向原理 输出重定向就是我们本该输出到一个文件的数据输出到另一个文件 我们如果将本该输出到显示器的文件输出到“log.txt”中去我们可以在打开文件之前将文件描述符为1的文件进行关闭这样我们后续打开的“log.txt”文件描述符就为1了。 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 8 int main()9 {10 close(1);11 int fd open(log.txt, O_WRONLY|O_CREAT, 0666);12 if(fd 0)13 {14 perror(open);15 return 1;16 }17 18 printf(hello world\n);19 close(fd);20 return 0; 21 } 运行程序我们会发现此时“hello world”并没有打印到屏幕上我们打开log.txt发现他存在与log.txt中此时就完成了我们的输出重定向。 追加重定向原理 追加重定向其实就是在输出重定向的基础上更改一下我们flags的条件就可以了 int fd open(log.txt, O_WRONLY|O_APPEND|O_CREAT, 0666);输入重定向原理 输入重定向就是将我们本应该从一个文件读取数据现在重定向为从另一个文件读取数据。 我们想让本应该从“键盘文件”读取数据的scanf函数改为从log.txt文件当中读取数据那么我们可以在打开log.txt文件之前将文件描述符为0的文件关闭也就是将“键盘文件”关闭这样一来当我们后续打开log.txt文件时所分配到的文件描述符就是0。 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 8 int main()9 {10 close(0);11 int fd open(log.txt, O_RDONLY);12 if(fd 0)13 {14 perror(open);15 return 1;16 }17 char buffer[64];18 fgets(buffer, sizeof(buffer),stdin); 19 printf(%s, buffer);20 21 close(fd);22 return 0;23 }dup2 我们像上面那种方式进行重定向比较麻烦在Linux操作系统中提供了系统接口dup2我们可以使用该函数完成重定向。dup2的函数原型如下 int dup2(int oldfd, int newfd);函数功能 dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中如果有必要的话我们需要先使用关闭文件描述符为newfd的文件。 函数返回值 dup2如果调用成功返回newfd否则返回-1。 使用dup2时我们需要注意以下两点 如果oldfd不是有效的文件描述符则dup2调用失败并且此时文件描述符为newfd的文件没有被关闭。如果oldfd是一个有效的文件描述符但是newfd和oldfd具有相同的值则dup2不做任何操作并返回newfd。 1 #include stdio.h2 #include unistd.h3 #include sys/types.h4 #include sys/stat.h5 #include fcntl.h6 #include string.h7 8 int main()9 {10 int fd open(log.txt, O_WRONLY|O_CREAT, 0666);11 if(fd 0)12 {13 perror(open);14 return 1;15 }16 17 close(1);18 dup2(fd, 1);19 printf(hello world\n);20 fprintf(stdout, hello Linux\n);21 22 close(fd); 23 return 0;24 }代码运行后我们即可发现数据被输出到了log.txt文件当中 如何理解Linux下一切皆文件 Linux是用C语言写的大佬在设计的过常利用C语言的struct实现了面向对象和多态的功能。 我们在学习C语言的过程中可以发现struct中只能定义成员变量不能定义成员函数但是可以定义函数指针通过在struct中定义函数指针就可以调用函数从而来实现面向对象的功能。 底层是不同的硬件一定对应不同的操作方法但是上层的设备都是外设每个设备的核心访问函数都可以是readwrite所以所有的设备都可以有自己的readwrite但是他们的实现代码肯定是不一样的 添加重定向功能到minishell 1 #includestdio.h2 #includestdlib.h3 #includestring.h4 #includeunistd.h5 #includesys/wait.h6 #includeassert.h7 #includesys/stat.h8 #includesys/types.h9 #includefcntl.h10 11 #define NUM 102412 #define SIZE 3213 #define SEP 14 //保存完整的字符15 char cmd_line[NUM];16 //保存打散之后的字符串17 char* g_argv[SIZE];18 19 //环境变量buffer用来测试20 char g_myval[64];21 22 #define INPUT_REDIR 123 #define OUTPUT_REDIR 224 #define APPEND_REDIR 325 #define NONE_REDIR 026 27 int redir_status NONE_REDIR;28 29 char* CheckRedir(char* start)30 {31 assert(start);32 char* end start strlen(start) - 1;33 while(end start)34 {35 if(*end )36 {37 if(*(end-1) )38 {39 redir_status APPEND_REDIR;40 *(end-1) \0;41 end;42 break;43 }44 redir_status OUTPUT_REDIR;45 *end \0;46 end;47 break;48 }49 else if(*end )50 {51 redir_status INPUT_REDIR;52 *end \0;53 end;54 break;55 }56 else57 {58 end--;59 }60 }61 if(end start)62 {63 return end;64 }65 else66 {67 return NULL;68 }69 }70 int main()71 {72 extern char** environ;73 while(1)74 {75 //1.打印出提示信息[rootlocalhost myshell]# 76 printf([rootlocalhost myshell]# );77 fflush(stdout);78 memset(cmd_line, \0, sizeof cmd_line);79 //2.获取用户输入的各种键盘指令ls -a -l -i80 if(fgets(cmd_line, sizeof cmd_line, stdin) NULL)81 {82 continue;83 }84 cmd_line[strlen(cmd_line) - 1] \0;85 //检查是否有重定向 ls -a -llog.txt - ls -a -l\0log.txt86 char* sep CheckRedir(cmd_line);87 //3.命令行字符串解析la -a -l -i - ls -a -l -i88 g_argv[0] strtok(cmd_line, SEP);89 int index 1;90 if(strcmp(g_argv[0], ls) 0)91 {92 g_argv[index] --colorauto;93 }94 if(strcmp(g_argv[0], ll) 0)95 {96 g_argv[0] ls;97 g_argv[index] -l;98 g_argv[index] --colorauto;99 }100 while(g_argv[index] strtok(NULL, SEP));//第二次解析原始字符串出入NULL101 if(strcmp(g_argv[0], export) 0 g_argv[1] ! NULL)102 {103 strcpy(g_myval, g_argv[1]);104 int ret putenv(g_myval);105 if(ret 0)106 {107 printf(%s export sucess\n, g_argv[1]);108 }109 }110 //4.让父进程自己执行命令111 if(strcmp(g_argv[0], cd) 0)\112 {113 if(g_argv[1] ! NULL)114 {115 chdir(g_argv[1]);116 }117 continue;118 }119 //5. fork()120 pid_t id fork();121 if(id 0)122 {123 perror(fork());124 return 1;125 }126 else if(id 0)127 {128 if(sep ! NULL)129 {130 int fd -1;131 switch(redir_status)132 {133 case INPUT_REDIR:134 fd open(sep, O_RDONLY);135 dup2(fd, 0);136 break;137 case OUTPUT_REDIR:138 fd open(sep, O_WRONLY|O_TRUNC|O_CREAT, 0666);139 dup2(fd, 1);140 break;141 case APPEND_REDIR:142 fd open(sep, O_WRONLY|O_APPEND|O_CREAT, 0666);143 dup2(fd, 1);144 break;145 default:146 printf(bug\n);147 break;148 }149 }150 151 //printf(下面程序是由子进程运行的\n);152 //printf(child, MYVAL:%s\n, getenv(MYVAL));153 //printf(child, PATH:%s\n, getenv(PATH));154 execvp(g_argv[0], g_argv);155 exit(1);156 }157 else158 {159 int status 0; 160 pid_t ret waitpid(-1, status, 0);161 if(ret 0)162 {163 printf(wait sucesss: exit code:%d\n, WEXITSTATUS(status));164 }165 }166 167 }168 return 0;169 }缓冲区 接下来来看一段神奇的代码 1 #include stdio.h2 #include unistd.h3 #include stdlib.h4 #include string.h5 6 int main()7 {8 //C语言提供的9 printf(hello printf\n);10 fprintf(stdout, hello fprintf\n);11 const char *s hello puts\n;12 fputs(s, stdout);13 14 const char *ss hello write\n;15 //操作系统提供的 16 write(1, ss, strlen(ss));17 18 fork();//创建子进程19 return 0;20 }运行程序我们会发现一个很神奇的事情 我们直接运行程序时会发现打印出来只有4行但是我们输出重定向到log.txt文件里面以后打印的却是7行那么这是为什么呢 这里就需要我们来了解缓冲区的概念了仔细观察我们会发现只有C语言提供的接口函数我们是打印了两次操作系统提供的就只打印了一次。 首先我们需要知道缓冲区的刷新方式有三种 立即刷新行刷新行缓冲满刷新。全缓冲 我们平时打印到显示器上的内容就属于行刷新通过对上述程序进行分析我们就可以知道我们所谓的缓冲区并不是操作系统为我们提供的而是C标准库为我们提供的因为我们可以看见只有C语言提供的接口函数我们是打印了两次操作系统提供就只打印了一次如果是操作系统所提供的write接口就不会只打印一次了。 为什么fork以后打印到显示器是3次重定向到log.txt文件时7次呢 我们平时打印到显示器上的内容就属于行刷新出现“\n”就会进行刷新在fork之前函数已经被执行完了并且数据已经被刷新了fork就已经毫无意义了。但是我们重定向到log.txt文件时就变成向文件打印此时的刷新策略也就变成了全缓冲“\n”就没有意义了此时我们所需要输出的内容就被放在了C标准库为我们提供的缓冲区中函数已经执行完毕但数据并没有刷新fork以后创建子进程由于进程之间的独立性此时的数据就会发生写实拷贝父子进程各自拥有一份所以C标准库提供的接口函数输出端内容就会被打印两次这也就是fork以后打印到显示器是3次重定向到log.txt文件时7次的原因。 我们所谓的缓冲区在底层实际上是存放在一个FILE结构体中的 此时我们也就理解了我们的fopen函数到底干了什么了 fopen函数在上层为用户申请FILE结构体变量并返回该结构体的地址(FILE*)在底层通过系统接口open打开对应的文件得到文件描述符fd并把fd填充到FILE结构体当中的_fileno变量中至此便完成了文件的打开操作。 而C语言当中的其他文件操作函数比如fread、fwrite、fputs、fgets等都是先根据我们传入的文件指针找到对应的FILE结构体然后在FILE结构体当中找到文件描述符最后通过文件描述符对文件进行的一系列操作。 模拟实现一个缓冲区 我们要写入数据为例 1 #include stdio.h2 #include unistd.h3 #include stdlib.h4 #include string.h5 #include sys/stat.h6 #include sys/types.h7 #include assert.h8 #include fcntl.h9 10 #define NUM 102411 struct MyFILE_12 {13 int fd;14 char buffer[NUM];15 int end;//当前缓冲区结尾16 };17 18 typedef struct MyFILE_ MyFILE;19 20 MyFILE *_fopen(const char* pathname, const char* mode)21 {22 assert(pathname);23 assert(mode);24 25 MyFILE* fp NULL;26 if(strcmp(mode, w) 0)27 {28 int fd open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);29 if(fd 0)30 {31 fp (MyFILE*)malloc(sizeof(MyFILE));32 memset(fp, 0 , sizeof(MyFILE));33 fp-fd fd;34 }35 }36 37 return fp;38 }39 40 void _fputs(const char *message, MyFILE *fp)41 {42 assert(message);43 assert(fp);44 45 strcpy(fp-bufferfp-end, message);46 fp-end strlen(message);47 48 printf(%s\n, fp-buffer); 49 if(fp-fd 0)50 {51 //标准输入流52 }53 else if(fp-fd 1)54 {55 //标准输出流56 if(fp-buffer[fp-end-1] \n)57 {58 //fprintf(stderr,fflush:%s, fp-buffer);59 write(fp-fd, fp-buffer, fp-end);60 61 fp-end 0;62 }63 }64 else if(fp-fd 2)65 {66 //标准错误流67 }68 else69 {70 //其他流71 }72 }73 74 void _fflush(MyFILE *fp)75 {76 assert(fp);77 78 if(fp-end ! 0)79 {80 write(fp-fd, fp-buffer, fp-end);81 syncfs(fp-fd);82 fp-end 0;83 }84 85 }86 87 void _flose(MyFILE *fp)88 {89 assert(fp);90 _fflush(fp);91 close(fp-fd);92 free(fp);93 }94 95 int main()96 {97 //close(1);98 MyFILE* fp _fopen(./log.txt, w);99 if(fp NULL) 100 { 101 perror(_fopen); 102 return 1; 103 } 104 105 _fputs(one:hello puts, fp); 106 _fputs( two:hello puts, fp); 107 _fputs( three:hello puts\n, fp); 108 //fork(); 109 110 _flose(fp); 111 return 0; 112 }运行程序就会发现 未fork之前我们log.txt文件中所有内容只会打印一次fork以后内容均打印两次 fflush 有了缓冲区的概念我们就可以很好的理解fflush了 1 #includestdio.h2 #includesys/types.h3 #includesys/stat.h4 #includefcntl.h5 #includeunistd.h6 7 8 int main()9 {10 close(1);11 int fd open(log.txt, O_WRONLY|O_TRUNC|O_CREAT, 0666);12 if(fd 0)13 {14 perror(open);15 return 1;16 }17 18 printf(hello world:%d\n, fd);19 //fflush(stdout); 20 21 close(fd);22 return 0;23 }当我们关闭close(1)以后将hello world写入stdout缓冲区中但是尽管此时存在‘\n’也不会刷新出来随后我们在close(fd)数据在缓冲区中相应的fd已经关了数据就刷新不出来了但是我们使用了fflush以后存储在缓冲区的数据立即会刷新出来log.txt文件中也就会打印出来。 理解文件系统 初识inode 磁盘文件由两部分构成分别是文件内容和文件属性。文件内容就是文件当中存储的数据文件属性就是文件的一些基本信息例如文件名、文件大小以及文件创建时间等信息都是文件属性文件属性又被称为元信息。 在命令行当中输入ls -l即可显示当前目录下各文件的属性信息。 在Linux操作系统中文件的元信息和内容是分离存储的其中保存元信息的结构称之为inode因为系统当中可能存在大量的文件所以我们需要给每个文件的属性集起一个唯一的编号即inode号。 也就是说inode是一个文件的属性集合Linux中几乎每个文件都有一个inode为了区分系统当中大量的inode我们为每个inode设置了inode编号。 磁盘的概念 磁盘是一种永久性存储介质在计算机中磁盘几乎是唯一的机械设备。与磁盘相对应的就是内存内存是掉电易失存储介质目前所有的普通文件都是在磁盘中存储的。 磁盘在冯诺依曼体系结构当中既可以充当输入设备又可以充当输出设备。 我们如何来确定信息在磁盘中的读写位置呢 大概分为三步 先找到在哪一个面上在找到在哪一个磁道上最后找到在哪一个扇区上。 磁盘分区与格式化介绍 理解文件系统首先我们必须将磁盘想象成一个线性的存储介质想想磁带当磁带被卷起来时其就像磁盘一样是圆形的但当我们把磁带拉直后其就是线性的。 磁盘分区 磁盘通常被称为块设备一般以扇区为单位一个扇区的大小通常为512字节。我们若以大小为512G的磁盘为例该磁盘就可被分为十亿多个扇区。 计算机为了更好的管理磁盘于是对磁盘进行了分区。磁盘分区就是使用分区编辑器在磁盘上划分几个逻辑部分盘片一旦划分成数个分区不同的目录与文件就可以存储进不同的分区分区越多就可以将文件的性质区分得越细按照更为细分的性质存储在不同的地方以管理文件。 磁盘格式化 当磁盘完成分区后我们还需要对磁盘进行格式化。磁盘格式化就是对磁盘中的分区进行初始化的一种操作这种操作通常会导致现有的磁盘或分区中所有的文件被清除。 简单来说磁盘格式化就是对分区后的各个区域写入对应的管理信息。 EXT2文件系统的存储方案 计算机为了更好的管理磁盘会对磁盘进行分区。而对于每一个分区来说分区的头部会包括一个启动块(Boot Block)对于该分区的其余区域EXT2文件系统会根据分区的大小将其划分为一个个的块组(Block Group)。 其次每个组块都有着相同的组成结构每个组块都由超级块(Super Block)、块组描述符表(Group Descriptor Table)、块位图(Block Bitmap)、inode位图(inode Bitmap)、inode表(inode Table)以及数据表(Data Block)组成。 注意 启动块的大小是确定的而块组的大小是由格式化的时候确定的并且不可以更改。 Super Block 存放文件系统本身的结构信息。记录的信息主要有Data Block和inode的总量、未使用的Data Block和inode的数量、一个Data Block和inode的大小、最近一次挂载的时间、最近一次写入数据的时间、最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏可以说整个文件系统结构就被破坏了。Group Descriptor Table 块组描述符表描述该分区当中块组的属性信息。Block Bitmap 块位图当中记录着Data Block中哪个数据块已经被占用哪个数据块没有被占用。inode Bitmap inode位图当中记录着每个inode是否空闲可用。inode Table 存放文件属性即每个文件的inode。Data Blocks 存放文件内容。 注意 其他块组当中可能会存在冗余的Super Block当某一Super Block被破坏后可以通过其他Super Block进行恢复。磁盘分区并格式化后每个分区的inode个数就确定了。 创建文件系统做了什么 通过遍历inode位图的方式找到一个空闲的inode;在inode表当中找到对应的inode并将文件的属性信息填充进inode结构中;将该文件的文件名和inode指针添加到目录文件的数据块中。 删除文件系统做了什么 将该文件对应的inode在inode位图当中置为无效。将该文件申请过的数据块在块位图当中置为无效。 为什么拷贝文件的时候很慢而删除文件的时候很快 因为拷贝文件需要先创建文件然后再对该文件进行写入操作该过程需要先申请inode号并填入文件的属性信息之后还需要再申请数据块号最后才能进行文件内容的数据拷贝而删除文件只需将对应文件的inode号和数据块号置为无效即可无需真正的删除文件因此拷贝文件是很慢的而删除文件是很快的。 如何理解目录 Linux下一切皆文件目录也是文件他也要自己的inodedata block它的data block中会存放文件名与相应的inode编号的映射关系而目录的inode结构中存储了目录的属性信息比如比如目录的大小、目录的拥有者。 软硬链接 软链接 我们通过以下命令可以来创建一个软链接 [gttVM-28-16-centos study_9_4]$ ln -s mytest mytest-s我们可以发现软链接所产生的文件与源文件的inode编号是不同的软链接产生的文件大小比源文件要小得多 软链接又叫做符号链接软链接文件相对于源文件来说是一个独立的文件该文件有自己的inode号但是该文件只包含了源文件的路径名所以软链接文件的大小要比源文件小得多。软链接就类似于Windows操作系统当中的快捷方式。 但是软链接文件只是其源文件的一个标记当删除了源文件后链接文件不能独立存在虽然仍保留文件名但却不能执行或是查看软链接的内容了。 硬链接 我们通过以下命令可以来创建一个硬链接 [gttVM-28-16-centos study_9_4]$ ln mytest mytest-h我们查看文件inode编号会发现硬链接产生的文件与源文件的inode编号是一样的并且硬链接文件的大小与源文件的大小也是相同的特别注意的是当创建了一个硬链接文件后该硬链接文件和源文件的硬链接数都变成了2。 硬链接文件就是源文件的一个别名一个文件有几个文件名该文件的硬链接数就是几这里inode号为1053442的文件有mytest和mytest-h两个文件名因此该文件的硬链接数为2。 与软连接不同的是当硬链接的源文件被删除后硬链接文件仍能正常执行只是文件的链接数减少了一个因为此时该文件的文件名少了一个。 我们需要注意的是硬链接就是让多个不在或者同在一个目录下的文件名同时能够修改同一个文件其中一个修改后所有与其有硬链接的文件都一起修改了。 为什么刚刚创建的目录的硬链接数是2 因为每个目录创建后该目录下默认会有两个隐含文件.和…它们分别代表当前目录和上级目录因此这里创建的目录有两个名字一个是dir另一个就是该目录下的.所以刚创建的目录硬链接数是2。通过命令我们也可以看到dir和该目录下的.的inode号是一样的也就可以说明它们代表的实际上是同一个文件。 软硬链接的区别 软链接是一个独立的文件有独立的inode而硬链接没有独立的inode。软链接相当于快捷方式硬链接本质没有创建文件只是建立了一个文件名和已有的inode的映射关系并写入当前目录。 文件的三个时间 在Linux当中我们可以使用命令stat 文件名来查看对应文件的信息 这其中包含了文件的三个时间信息 Access 文件最后被访问的时间。Modify 文件内容最后的修改时间。Change 文件属性最后的修改时间。 当我们修改文件内容时文件的大小一般也会随之改变所以一般情况下Modify的改变会带动Change一起改变但修改文件属性一般不会影响到文件内容所以一般情况下Change的改变不会带动Modify的改变。 我们若是想将文件的这三个时间都更新到最新状态可以使用命令touch 文件名来进行时间更新。
http://www.zqtcl.cn/news/17103/

相关文章:

  • 国内个人网站吴江住宅城乡建设局网站
  • 苏州园区已经烂掉了网站搜索引擎优化主要方法
  • c2c代表网站有哪些怎么开个网站
  • 天津重型网站建设推荐编程的基础知识
  • win7 iis asp网站配置文件怎么做一个企业的网站
  • 如何借助网站打广告品牌推广方案设计
  • 建站服务器缤纷销客crm
  • 博客网站开发网站如何做延迟加载
  • 个人网站建设心得体会wordpress随机文本
  • 网站建设网易工业产品外观设计
  • led照明企业网站模板中国域名门户网站
  • o2o网站建站如何让各大搜索引擎识别新建网站
  • 杭州专业网站制作永州市建设局网站
  • 装饰网站开发背景工商营业执照咨询电话
  • 如何做自己微网站网站模板一般用什么软件做
  • wordpress做一个网站404引导大宗交易平台
  • 南通网站seo报价健身网站开发过程中遇到的麻烦
  • 房产网站如何做柳州建网站
  • 广州建设网站怎么做wordpress最新列表
  • 全flash网站制作教程简约个人主页
  • 广西建设厅网站公布国外大气的网站
  • 网站建设评比考核报告100%能上热门的短视频素材
  • 如何做电影网站狼视听wordpress如何安装模板
  • 网站视觉首页怎么做网站开发图片压缩
  • 网站引导制作wordpress qq邮箱订阅
  • 沈阳网站建设syfzkj江苏seo和网络推广
  • 怎么做网站旅游宣传公司建设一个网站
  • 深圳网站建设公司公司深圳音乐制作公司
  • 个人网站命名网站建设教材下载
  • 决定网站打开的速度吗杭州网站公司哪家服务好