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

在线学习网站建设开封市城乡建设局网站

在线学习网站建设,开封市城乡建设局网站,定制手机壳网站,视频网站后台管理参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 标准 I/O 库简介 标准 I/O 库是指#xff1a;标准 C 库中用于文件 I/O 操作#xff08;如#xff1a;读、写文件等#xff09;相关的一系列库函数的集合 标准 I/O 库函数相关的函数定义都在头文件 标准 C 库中用于文件 I/O 操作如读、写文件等相关的一系列库函数的集合 标准 I/O 库函数相关的函数定义都在头文件 stdio.h 中标准 I/O 库函数构建于文件 I/Oopen()、read()、write()、lseek()、close()等这些系统调用之上如标准 I/O 库函数 fopen() 就利用系统调用 open() 来执行打开文件的操作 为什么需要标准 I/O 库直接使用文件 I/O 系统调用不是更好吗 设计库函数是为了提供比底层系统调用更为方便、好用的调用接口虽然标准 I/O 构建于文件 I/O 之上但标准 I/O 却有它自己的优势 标准 I/O 和文件 I/O 的区别如下 虽然标准 I/O 和文件 I/O 都是 C 语言函数但是标准 I/O 是标准 C 库函数而文件 I/O 则是 Linux 系统调用标准 I/O 是由文件 I/O 封装而来标准 I/O 内部实际上是调用文件 I/O 来完成实际操作的可移植性标准 I/O 相比于文件 I/O 具有更好的可移植性 通常对于不同的操作系统其内核向应用层提供的系统调用往往都是不同如系统调用的定义、功能、参数列表、返回值等往往都不同而对于标准 I/O 来说由于很多操作系统都实现了标准 I/O 库标准 I/O 库在不同的操作系统之间其接口定义几乎是一样的所以标准 I/O 在不同操作系统之间相比于文件 I/O 具有更好的可移植性 性能、效率标准 I/O 库在用户空间维护了自己的 stdio 缓冲区所以标准 I/O 是带有缓存的而文件 I/O 在用户空间是不带有缓存的所以在性能、效率上标准 I/O 要优于文件 I/O 2. FILE 指针 所有文件 I/O 函数open()、read()、write()、lseek()等都是围绕文件描述符进行的当调用 open()函数打开一个文件时即返回一个文件描述符 fd然后该文件描述符就用于后续的 I/O 操作而对于标准 I/O 库函数来说它们的操作是围绕 FILE 指针进行的当使用标准 I/O 库函数打开或创建一个文件时会返回一个指向 FILE 类型对象的指针FILE *使用该 FILE 指针与被打开或创建的文件相关联然后该 FILE 指针就用于后续的标准 I/O 操作FILE 指针的作用相当于文件描述符FILE 指针用于标准 I/O 库函数中而文件描述符则用于文件 I/O 系统调用中FILE 是一个结构体数据类型它包含了标准 I/O 库函数为管理文件所需要的所有信息包括用于实际 I/O 的文件描述符、指向文件缓冲区的指针、缓冲区的长度、当前缓冲区中的字节数以及出错标志等 FILE 数据结构定义在标准 I/O 库函数头文件 stdio.h 中 3. 标准输入、标准输出和标准错误 用户通过标准输入设备与系统进行交互进程将从标准输入 (stdin) 文件中得到输入数据将正常输出数据如printf 打印输出输出到标准输出(stdout) 文件而将错误信息如函数调用报错输出到标准错误 (stderr) 文件 标准输出文件和标准错误文件都对应终端的显示器而标准输入文件则对应于键盘 每个进程启动之后都会默认打开标准输入、标准输出以及标准错误得到三个文件描述符即 0标准输入、1标准输出、2标准错误在应用编程中可以使用宏 STDIN_FILENO、STDOUT_FILENO 和 STDERR_FILENO 分别代表 0、1、2这些宏定义在 unistd.h 头文件中#define STDIN_FILENO 0 /* Standard input. */ #define STDOUT_FILENO1 /* Standard output. */ #define STDERR_FILENO2 /* Standard error output. */0、1、2 是文件描述符只能用于文件 I/O标准 I/O 中需围绕 FILE 类型指针进行在 stdio.h 头文件定义// struct _IO_FILE 结构体就是 FILE 结构体使用 typedef 进行了重命名 extern struct _IO_FILE *stdin; /* Standard input stream. */ extern struct _IO_FILE *stdout; /* Standard output stream. */ extern struct _IO_FILE *stderr; /* Standard error output stream. *//* C89/C99 say theyre macros. */ #define stdin stdin #define stdout stdout #define stderr stderr4. 打开文件 fopen()、关闭文件 fclose() 标准 I/O 中将使用库函数 fopen() 打开或创建文件#include stdio.h// path参数 path 指向文件路径可以是绝对路径或相对路径 // mode参数 mode 指定了对该文件的读写权限是一个字符串 // 返回值调用成功返回一个指向 FILE 类型对象的指针FILE * FILE *fopen(const char *path, const char *mode);参数 mode 字符串类型可取值如下 虽然调用 fopen()函数新建文件时无法手动指定文件的权限但却有一个默认值S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666)调用 fclose() 库函数可以关闭一个由 fopen() 打开的文件#include stdio.h// 参数 stream 为 FILE 类型指针调用成功返回 0失败将返回 EOF也就是-1 int fclose(FILE *stream);5. 读文件和写文件 可以使用 fread() 和 fwrite() 库函数对文件进行读、写操作#include stdio.h/* ptrfread() 将读取到的数据存放在参数 ptr 指向的缓冲区中sizefread() 从文件读取 nmemb 个数据项每一个数据项大小为 size 个字节所以总共读取 nmemb * size 个字节数据nmemb参数 nmemb 指定了读取数据项的个数streamFILE 指针 */ size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); /* ptr将参数 ptr 指向的缓冲区中的数据写入到文件中size参数 size 指定了每个数据项的字节大小与 fread() 函数的 size 参数意义相同nmemb参数 nmemb 指定了写入的数据项个数与 fread() 函数的 nmemb 参数意义相同streamFILE 指针 */ size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);5.1 使用 fwrite() 将数据写入到文件中 #include stdio.h #include stdlib.hint main(void) {char buff[] Hello World!\n;FILE *fp NULL;if ((fp fopen(./test_file, w)) NULL) {perror(fopen error);exit(-1);} printf(open success\n);if (fwrite(buff, 1, sizeof(buff), fp) sizeof(buff)) {printf(fwrite error\n);fclose(fp);exit(-1);} printf(write success\n);fclose(fp);exit(0); }$ gcc fw.c -o fw $ ./fw open success write success$ cat test_file Hello World!5.2 使用 fread() 从文件中读取数据 #include stdio.h #include stdlib.hint main(void) {char buf[50] {0};FILE *fp NULL;int size;/* 只读方式打开文件 */if ((fp fopen(./test_file, r)) NULL) {perror(fopen error);exit(-1);} printf(文件打开成功!\n);/* 读取 12 * 112 个字节的数据 */if ((size fread(buf, 1, 12, fp)) 12) {if (ferror(fp)) { //使用 ferror 判断是否是发生错误printf(fread error\n);fclose(fp);exit(-1);} /* 如果未发生错误则意味着已经到达了文件末尾 */} printf(成功读取%d 个字节数据: %s\n, size, buf);/* 关闭文件 */fclose(fp);exit(0); }$ gcc fr.c -o fr $ ./fr 文件打开成功! 成功读取12 个字节数据: Hello World!$ cat test_file Hello World!6. fseek 定位 库函数 fseek() 的作用类似系统调用 lseek()用于设置文件读写位置偏移量 调用库函数 fread()、fwrite() 读写文件时文件的读写位置偏移量会自动递增使用 fseek() 可手动设置文件当前的读写位置偏移量 #include stdio.h// streamFILE 指针 // offset与 lseek() 函数的 offset 参数意义相同 // whence与 lseek() 函数的 whence 参数意义相同 int fseek(FILE *stream, long offset, int whence);6.1 使用 fseek() 调整文件读写位置 #include stdio.h #include stdlib.hint main(void) {FILE *fp NULL;char rd_buf[100] {0};char wr_buf[] www.baidu.com\n;int ret;/* 打开文件 */if ((fp fopen(./test_file, w)) NULL) {perror(fopen error);exit(-1);} printf(open seccess\n);/* 写文件 */if (fwrite(wr_buf, 1, sizeof(wr_buf), fp) sizeof(wr_buf)) {printf(fwrite error\n);fclose(fp);exit(-1);} printf(write success\n);/* 将读写位置移动到文件头部 */if (fseek(fp, 0, SEEK_SET) 0) {perror(fseek error\n);fclose(fp);exit(-1);} /* 读文件 */if ((ret fread(rd_buf, 1, sizeof(wr_buf), fp)) sizeof(wr_buf)) {printf(fread error\n);fclose(fp);exit(-1);} printf(成功读取 %d 个字节数据: %s\n, ret, rd_buf);/* 关闭文件 */fclose(fp);exit(0); }$ gcc fseek.c -o fseek $ ./fseek open seccess write success 成功读取 14 个字节数据: www.baidu.com7. ftell() 函数 库函数 ftell() 可用于获取文件当前的读写位置偏移量#include stdio.h// 参数 stream 指向对应的文件函数调用成功将返回当前读写位置偏移量调用失败将返回 -1 long ftell(FILE *stream);7.1 使用 fseek() 和 ftell() 获取文件大小 #include stdio.h #include stdlib.hint main(void) {FILE *fp NULL;int ret;/* 打开文件 */if ((fp fopen(./testApp.c, r)) NULL) {perror(fopen error);exit(-1);}printf(open success\n);/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) 0) {perror(fseek error);fclose(fp);exit(-1);}/* 获取当前位置偏移量也就得到了 testApp.c 整个文件的大小 */if ((ret ftell(fp)) 0) {perror(ftell error);fclose(fp);exit(-1);} printf(file size: %d\n, ret);/* 关闭文件 */fclose(fp);exit(0); }$ gcc ftell.c -o ftell $ ./ftell open success file size: 138. 检查或复位状态 8.1 feof() 函数 库函数 feof() 用于测试参数 stream 所指文件的 end-of-file 标志 如果 end-of-file 标志被设置了则调用 feof() 函数将返回一个非零值如果 end-of-file 标志没有被设置则返回 0 #include stdio.hint feof(FILE *stream);// 当文件的读写位置移动到了文件末尾时end-of-file 标志将会被设置 if (feof(file)) {/* 到达文件末尾 */ } else {/* 未到达文件末尾 */ }8.2 ferror() 函数 库函数 ferror() 用于测试参数 stream 所指文件的错误标志 如果错误标志被设置了则调用 ferror() 函数将返回一个非零值如果错误标志没有被设置则返回 0 #include stdio.hint ferror(FILE *stream);// 当对文件的 I/O 操作发生错误时错误标志将会被设置 if (ferror(file)) {/* 发生错误 */ } else {/* 未发生错误 */ }8.3 clearerr() 函数 库函数 clearerr() 用于清除 end-of-file 标志和错误标志 当调用 feof() 或 ferror() 校验这些标志后通常需要清除这些标志避免下次校验时使用到的是上一次设置的值此时可以手动调用 clearerr() 函数清除标志对于 end-of-file 标志除使用 clearerr() 显式清除外当调用 fseek() 成功时也会清除文件的 end-of-file 标志 #include stdio.hvoid clearerr(FILE *stream);示例 #include stdio.h #include stdlib.hint main(void) {FILE *fp NULL;char buf[20] {0};/* 打开文件 */if ((fp fopen(./testApp.c, r)) NULL) {perror(fopen error);exit(-1);}printf(文件打开成功!\n);/* 将读写位置移动到文件末尾 */if (fseek(fp, 0, SEEK_END) 0) {perror(fseek error);fclose(fp);exit(-1);}/* 读文件 */if (fread(buf, 1, 10, fp) 10) {if (feof(fp))printf(end-of-file 标志被设置, 已到文件末尾!\n);clearerr(fp); // 清除标志}/* 关闭文件 */fclose(fp);exit(0); }9. 格式化 I/O 9.1 格式化输出 C 库函数提供了 5 个格式化输出函数包括printf()、fprintf()、dprintf()、sprintf()、snprintf()#include stdio.hint printf(const char *format, ...);int fprintf(FILE *stream, const char *format, ...); int dprintf(int fd, const char *format, ...);int sprintf(char *buf, const char *format, ...); int snprintf(char *buf, size_t size, const char *format, ...);这 5 个函数都是可变参函数它们都有一个共同的参数 format这是一个字符串称为格式控制字符串用于指定后续的参数如何进行格式转换所以才把这些函数称为格式化输出 printf() 函数用于将格式化数据写入到标准输出 printf(Hello World!\n); printf(%d\n, 5);dprintf() 和 fprintf() 函数用于将格式化数据写入到指定的文件中两者不同之处在于fprintf() 使用 FILE 指针指定对应的文件而 dprintf()则使用文件描述符 fd 指定对应的文件 fprintf(stderr, Hello World!\n); fprintf(stderr, %d\n, 5);dprintf(STDERR_FILENO, Hello World!\n); dprintf(STDERR_FILENO, %d\n, 5);sprintf()、snprintf() 函数可将格式化的数据存储在用户指定的缓冲区 buf 中 char buf[100]; sprintf(buf, Hello World!\n);// 一般会使用这个函数进行格式化转换并将转换后的字符串存放在缓冲区中 // 如将数字 100 转换为字符串 100将转换后得到的字符串存放在 buf 中 // sprintf() 函数会在字符串尾端自动加上一个字符串终止字符 \0 char buf[20] {0}; sprintf(buf, %d, 100);sprintf() 函数可能会造成由参数 buf 指定的缓冲区溢出因为缓冲区溢出会造成程序不稳定甚至安全隐患为解决这个问题引入 snprintf() 函数在该函数中使用参数 size 显式的指定缓冲区的大小 如果写入到缓冲区的字节数大于参数 size 指定的大小超出的部分将会被丢弃如果缓冲区空间足够大snprintf() 函数就会返回写入到缓冲区的字符数 9.2 格式控制字符串 format输出 格式控制字符串由两部分组成普通字符非 % 字符和转换说明 普通字符会进行原样输出每个转换说明都会对应后续的一个参数通常有几个转换说明就需要提供几个参数除固定参数之外的参数使之一一对应用于控制对应的参数如何进行转换 printf(转换说明 1 转换说明 2 转换说明 3, arg1, arg2, arg3);每个转换说明都是以 % 字符开头其格式如下所示使用 [] 括起来的部分是可选的/*flags标志用于规定输出样式,可包含 0 个或多个标志width输出最小宽度表示转换后输出字符串的最小宽度precision精度前面有一个点号 .length长度修饰符type转换类型指定待转换数据的类型 */ %[flags][width][.precision][length]type9.2.1 type 类型 type 用于指定输出数据的类型type 字段使用一个字符字母字符来表示 9.2.2 flags 样式标志 flags 规定输出样式% 后面可以跟 0 个或多个以下标志 9.2.3 width 输出宽度 最小的输出宽度用十进制数来表示输出的最小位数 若实际的输出位数大于指定的输出的最小位数则以实际的位数进行输出若实际的位数小于指定输出的最小位数则可按照指定的 flags 标志补 0 或补空格 9.2.4 precision 精度 精度字段以点号 “.” 开头后跟一个十进制正数可取值如下 9.2.5 length 长度修饰符 长度修饰符指明待转换数据的长度因为 type 字段指定的的类型只有 int、unsigned int 以及 double 等几种数据类型但是 C 语言内置的数据类型不止这几种如16bit 的 short、unsigned short8bit 的 char、unsigned char64bit 的 long long 等为了能够区别不同长度的数据类型于是长度修饰符length应运而生length 长度修饰符也是使用字符字母字符来表示结合 type 字段以确定不同长度的数据类型printf(%hd\n, 12345); // 将数据以 short int 类型进行转换 printf(%ld\n, 12345); // 将数据以 long int 类型进行转换 printf(%lld\n, 12345); // 将数据以 long long int 类型进行转换9.3 格式化输入 C 库函数提供了 3 个格式化输入函数包括scanf()、fscanf()、sscanf()#include stdio.hint scanf(const char *format, ...); int fscanf(FILE *stream, const char *format, ...); int sscanf(const char *str, const char *format, ...);这 3 个格式化输入函数也是可变参函数它们都有一个共同的参数 format同样也称为格式控制字符串用于指定输入数据如何进行格式转换scanf() 函数可将用户输入标准输入的数据进行格式化转换 当程序中调用 scanf() 的时候终端会被阻塞等待用户输入数据此时可以通过键盘输入一些字符如数字、字母或者其它字符输入完成按回车即可 int a, b, c; scanf(%d %d %d, a, b, c);fscanf() 函数从 FILE 指针指定文件中读取数据并将数据进行格式化转换 标准输入文件的数据就是用户输入的数据如通过键盘输入的数据 int a, b, c; fscanf(stdin, %d %d %d, a, b, c);sscanf() 函数从参数 str 所指向的字符串中读取数据并将数据进行格式化转换 char *str 5454 hello; char buf[10]; int a;sscanf(str, %d %s, a, buf);9.4 格式控制字符串 format输入 format 字符串包含一个或多个转换说明每一个转换说明都是以百分号 “%”转换说明格式如下/*width最大字符宽度length长度修饰符与格式化输出函数的 format 相同type指定输入数据的类型 */ %[*][width][length]type %[m][width][length]type% 后面可选择性添加星号 * 或字母 m 如果添加了星号*格式化输入函数会按照转换说明的指示读取输入但是丢弃输入意味着不需要对转换后的结果进行存储所以也就不需要提供相应的指针参数如果添加了 m它只能与 %s、%c 以及 %[ 一起使用调用者无需分配相应的缓冲区来保存格式转换后的数据原因在于添加了 m这些格式化输入函数内部会自动分配足够大小的缓冲区并将缓冲区的地址值通过与该格式转换相对应的指针参数返回出来该指针参数应该是指向 char* 变量的指针。随后当不再需要此缓冲区时调用者应调用 free() 函数来释放此缓冲区 char *buf;scanf(%ms, buf); ......free(buf);9.4.1 type 类型 此 type 字段与格式化输出函数中的 format 参数的 type 字段是同样的意义用于指定输入数据的类型 9.4.2 width 最大字符宽度 是一个十进制表示的整数用于指定最大字符宽度当达到此最大值或发现不匹配的字符时以先发生者为准字符的读取将停止。大多数 type 类型会丢弃初始的空白字符并且这些丢弃的字符不会计入最大字符宽度。对于字符串转换来说scanf() 会在字符串末尾自动添加终止符 “\0”最大字符宽度中不包括此终止符scanf(%4s, buf); // 匹配字符串字符串长度不超过 4 个字符 // 用户输入 abcdefg按回车那么只能将 adcd 作为一个字符串存储在 buf 数组中9.4.3 length 长度修饰符 与格式化输出函数的格式控制字符串 format 中的 length 字段意义相同用于对 type 字段进行修饰扩展识别更多不同长度的数据类型scanf(%hd, var); // 匹配 short int 类型数据 scanf(%hhd, var); // 匹配 signed char 类型数据 scanf(%ld, var); // 匹配 long int 类型数据 scanf(%f, var); // 匹配 float 类型数据 scanf(%lf, var); // 匹配 double 类型数据 scanf(%Lf, var); // 匹配 long double 类型数据10. I/O 缓冲 1、首先应用程序调用标准 I/O 库函数将用户数据写入到 stdio 缓冲区中stdio 缓冲区是由 stdio 库所维护的用户空间缓冲区2、然后针对不同的缓冲模式当满足条件时stdio 库会调用文件 I/O系统调用 I/O将 stdio 缓冲区中缓存的数据写入到内核缓冲区中内核缓冲区位于内核空间3、最终由内核向磁盘设备发起读写操作将内核缓冲区中的数据写入到磁盘或者从磁盘设备读取数据到内核缓冲区 10.1 文件 I/O 的内核缓冲 read() 和 write() 系统调用在进行文件读写操作时并不会直接访问磁盘设备而是仅仅在用户空间缓冲区和内核缓冲区之间复制数据。如调用 write() 函数将 5 个字节数据从用户空间内存拷贝到内核空间的缓冲区中write(fd, Hello, 5); // 写入 5 个字节数据调用 write() 后仅仅只是将这 5 个字节数据拷贝到了内核空间的缓冲区中拷贝完成之后函数就返回了在后面的某个时刻内核会将其缓冲区中的数据写入刷新到磁盘设备中 由此可知系统调用 write() 与磁盘操作并不是同步的write() 函数并不会等待数据真正写入到磁盘之后再返回如果在此期间其它系统调用如 read() 函数读取该文件的这几个字节数据那么内核将自动从缓冲区中读取这几个字节数据返回给应用程序 对于读文件而言亦是如此内核会从磁盘设备中读取文件数据并存储到内核缓冲区中当调用 read() 读取数据时read() 调用将从内核缓冲区中读取数据直至把缓冲区中的数据读完这时内核会将文件的下一段内容读入到内核缓冲区中进行缓存把这个内核缓冲区就称为文件 I/O 的内核缓冲 文件 I/O 的内核缓冲区的设计目的 1. 提高文件 I/O 的速度和效率使得系统调用 read()、write()的操作更为快速 不需要等待磁盘操作将数据写入到磁盘或从磁盘读取出数据磁盘操作通常是比较缓慢的 2. 减少内核操作磁盘的次数 如线程 1 调用 write() 向文件写入 “abcd”线程 2 也调用 write() 向文件写入 “1234”这样数据 “abcd” 和 “1234” 都被缓存在内核缓冲区中稍后内核会将它们一起写入磁盘中只发起一次磁盘操作请求 文件 I/O 的内核缓冲区自然是越大越好内核会分配尽可能多的内核来作为文件 I/O 的内核缓冲区但受限于物理内存的总量操作越大的文件也要依赖于更大空间的内核缓冲区 10.2 刷新文件 I/O 的内核缓冲区 强制将文件 I/O 内核缓冲区中缓存的数据写入刷新到磁盘设备中对于某些应用程序来说是很有必要的 例如应用程序在进行某操作之前必须要确保前面步骤调用 write() 写入到文件的数据已经真正写入到了磁盘中诸如一些数据库的日志进程在 Ubuntu 系统下拷贝文件到 U 盘时文件拷贝完成之后通常在拔掉 U 盘之前需要执行 sync 命令进行同步操作这个同步操作其实就是将文件 I/O 内核缓冲区中的数据更新到 U 盘硬件设备所以如果在没有执行 sync 命令时拔掉 U 盘很可能就会导致拷贝到 U 盘中的文件遭到破坏 10.2.1 控制文件 I/O 内核缓冲的系统调用 fsync() 函数 系统调用 fsync() 将参数 fd 所指文件的内容数据和元数据写入磁盘只有在对磁盘设备的写入操作完成之后fsync() 函数才会返回元数据并不是文件内容本身的数据而是一些用于记录文件属性相关的数据信息如文件大小、时间戳、权限等等信息这里统称为文件的元数据这些信息也是存储在磁盘设备中的 #include unistd.hint fsync(int fd);示例 #include stdio.h #include stdlib.h #include sys/types.h #include sys/stat.h #include fcntl.h #include unistd.h#define BUF_SIZE 4096 #define READ_FILE ./rfile #define WRITE_FILE ./wfilestatic char buf[BUF_SIZE];int main(void) {int rfd, wfd;size_t size;/* 打开源文件 */rfd open(READ_FILE, O_RDONLY);if (rfd 0) {perror(open error);exit(-1);}/* 打开目标文件 */wfd open(WRITE_FILE, O_WRONLY | O_CREAT | O_TRUNC, 0664);if (wfd 0) {perror(open error);exit(-1);}/* 拷贝数据 */while((size read(rfd, buf, BUF_SIZE)) 0)write(wfd, buf, size);/* 对目标文件执行 fsync 同步 */fsync(wfd);/* 关闭文件退出程序 */close(rfd);close(wfd);exit(0); }fdatasync() 函数 系统调用 fdatasync()与 fsync()类似不同之处在于 fdatasync() 仅将参数 fd 所指文件的内容数据写入磁盘并不包括文件的元数据 #include unistd.hint fdatasync(int fd);sync() 函数 系统调用 sync() 会将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中该函数没有参数、也无返回值意味着它不是对某一个指定的文件进行数据更新而是刷新所有文件 I/O 内核缓冲区 #include unistd.hvoid sync(void);10.2.2 控制文件 I/O 内核缓冲的标志 O_DSYNC 标志 在调用 open() 函数时指定 O_SYNC 标志使得每个 write() 调用都会自动将文件内容数据和元数据刷新到磁盘设备中其效果类似于在每个 write() 调用之后调用 fsync() 函数进行数据同步 fd open(filepath, O_WRONLY | O_SYNC);在程序中频繁调用 fsync()、fdatasync()、sync()或者调用 open 时指定 O_DSYNC 或 O_SYNC 标志对性能的影响极大大部分的应用程序是没有这种需求的 10.3 直接 I/O绕过内核缓冲 Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区从用户空间直接将数据传递到文件或磁盘设备把这种操作也称为直接 I/Odirect I/O或裸 I/Oraw I/O 例如某应用程序的作用是测试磁盘设备的读写速率那么在这种应用需要下就需要保证 read/write 操作是直接访问磁盘设备而不经过内核缓冲对于大多数应用程序而言使用直接 I/O 可能会大大降低性能 直接 I/O 只在一些特定的需求场合如磁盘速率测试工具、数据库系统等 可针对某一文件或块设备执行直接 I/O需要在调用 open() 函数打开文件时指定 O_DIRECT 标志 fd open(filepath, O_WRONLY | O_DIRECT);因为直接 I/O 涉及到对磁盘设备的直接访问所以在执行直接 I/O 时必须要遵守三个对齐限制要求 应用程序中用于存放数据的缓冲区其内存起始地址必须以块大小的整数倍进行对齐写文件时文件的位置偏移量必须是块大小的整数倍写入到文件的数据大小必须是块大小的整数倍 以上所说的块大小指的是磁盘设备的物理块大小常见的块大小包括 512 字节、1024 字节、2048 以及 4096 字节如何确定磁盘分区的块大小通常Ubuntu 系统的根文件系统挂载在 /dev/sda1 磁盘分区下 $ sudo tune2fs -l /dev/sda1 | grep Block size Block size: 409610.4 stdio 缓冲 标准 I/O 是 C 语言标准库函数而文件 I/O 是系统调用虽然标准 I/O 是在文件 I/O 基础上进行封装而实现但在效率、性能上标准 I/O 要优于文件 I/O原因在于标准 I/O 维护了自己的缓冲区称为 stdio 缓冲区 文件 I/O 内核缓冲这是由内核维护的缓冲区而标准 I/O 所维护的 stdio 缓冲是用户空间缓冲区当应用程序中通过标准 I/O 操作磁盘文件时标准 I/O 函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区然后再一次性将 stdio 缓冲区中缓存的数据通过调用系统调用 I/O文件 I/O写入到文件 I/O 内核缓冲区或拷贝到应用程序的 buf 中 10.4.1 对 stdio 缓冲进行设置 1. setvbuf() 函数 调用 setvbuf() 库函数可以对文件的 stdio 缓冲区进行设置如缓冲区的缓冲模式、缓冲区的大小、起始地址等 #include stdio.hint setvbuf(FILE *stream, char *buf, int mode, size_t size);stream FILE 指针用于指定对应的文件每一个文件都可以设置它对应的 stdio 缓冲区 buf 如果参数 buf 不为 NULL那么 buf 指向 size 大小的内存区域将作为该文件的 stdio 缓冲区因为 stdio 库会使用 buf 指向的缓冲区所以应该以动态分配在堆内存如 malloc或静态的方式在堆中为该缓冲区分配一块空间而不是分配在栈上的函数内的自动变量局部变量如果 buf 等于 NULL那么 stdio 库会自动分配一块空间作为该文件的 stdio 缓冲区除非参数 mode 配置为非缓冲模式 mode用于指定缓冲区的缓冲类型 _IONBF不对 I/O 进行缓冲无缓冲。意味着每个标准 I/O 函数将立即调用 write() 或者 read()并且忽略 buf 和 size 参数可以分别指定两个参数为 NULL 和 0。标准错误 stderr 默认属于这一种类型从而保证错误信息能够立即输出_IOLBF采用行缓冲 I/O。这种情况下当在输入或输出中遇到换行符 “\n” 时标准 I/O 才会执行文件 I/O 操作 对于输出流在输出一个换行符前将数据缓存除非缓冲区已经被填满当输出换行符时再将这一行数据通过文件 I/O write() 函数刷入到内核缓冲区中对于输入流每次读取一行数据。对于终端设备默认采用的就是行缓冲模式譬如标准输入和标准输出 _IOFBF采用全缓冲 I/O。这种情况下在填满 stdio 缓冲区后才进行文件 I/O 操作read、write 对于输出流当 fwrite 写入文件的数据填满缓冲区时才调用 write() 将 stdio 缓冲区中数据刷入内核缓冲区对于输入流每次读取 stdio 缓冲区大小个字节数据。默认普通磁盘上的常规文件常用这种缓冲模式 size指定缓冲区的大小 返回值成功返回 0失败将返回一个非 0 值并设置 errno 指示错误原因 当 stdio 缓冲区中的数据被刷入到内核缓冲区或被读取之后这些数据就不会存在于缓冲区中了数据被刷入了内核缓冲区或被读走了 2. setbuf() 函数 setbuf() 函数构建于 setvbuf() 之上执行类似的任务#include stdio.hvoid setbuf(FILE *stream, char *buf);setbuf() 调用除了不返回函数结果void外就相当于// 要么将 buf 设置为 NULL 以表示无缓冲 // 要么指向由调用者分配的 BUFSIZ 个字节大小的缓冲区BUFSIZ 定义于头文件 stdio.h 中该值通常为 8192 setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);3. setbuffer() 函数 setbuffer() 函数类似于 setbuf()但允许调用者指定 buf 缓冲区的大小#include stdio.hvoid setbuffer(FILE *stream, char *buf, size_t size);setbuffer() 调用除了不返回函数结果void外就相当于setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);10.4.2 标准输出 printf() 的行缓冲模式测试 标准 printf() 输出测试#include stdio.h #include stdlib.h #include unistd.hint main(void) {printf(Hello World!\n);printf(Hello World!);for ( ; ; )sleep(1); }$ gcc io2.c -o io2 $ ./io2 Hello World! # 只有第一个 printf() 打印的信息显示出来了第二个并没有显示出来 这就是 stdio 缓冲的问题标准输出默认采用的是行缓冲模式printf() 输出的字符串写入到了标准输出的 stdio 缓冲区中只有输出换行符时不考虑缓冲区填满的情况才会将这一行数据刷入到内核缓冲区也就是写入标准输出文件终端设备 第一个 printf 包含了换行符所以已经刷入了内核缓冲区第二个 printf 没有包含换行符所以输出的 “Hello World!” 还缓存在 stdio 缓冲区中需要等待一个换行符才可输出到终端 将标准输出配置为无缓冲模式#include stdio.h #include stdlib.h #include unistd.hint main(void) {/* 将标准输出设置为无缓冲模式 */if (setvbuf(stdout, NULL, _IONBF, 0)) {perror(setvbuf error);exit(0);}printf(Hello World!\n);printf(Hello World!);for ( ; ; )sleep(1); }$ gcc io3.c -o io3 $ ./io3 Hello World! Hello World! 10.4.3 刷新 stdio 缓冲区 无论采取何种缓冲模式在任何时候都可以使用库函数 fflush() 来强制刷新将输出到 stdio 缓冲区中的数据写入到内核缓冲区stdio 缓冲区该函数会刷新指定文件的 stdio 输出缓冲区#include stdio.h// 参数 stream 指定需要进行强制刷新的文件如果该参数设置为 NULL则表示刷新所有的 stdio 缓冲区 int fflush(FILE *stream);使用 fflush() 刷新 stdio 缓冲区#include stdio.h #include stdlib.h #include unistd.hint main(void) {printf(Hello World!\n);printf(Hello World!);fflush(stdout); // 刷新标准输出 stdio 缓冲区for ( ; ; )sleep(1); }$ gcc io4.c -o io4 $ ./io4 Hello World! Hello World!使用库函数 fflush() 是一种强制刷新的手段在一些其它的情况下也会自动刷新 stdio 缓冲区 关闭文件时系统自动刷新 stdio 缓冲区 #include stdio.h #include stdlib.h #include unistd.hint main(void) {printf(Hello World!\n);printf(Hello World!);fclose(stdout); // 关闭标准输出for ( ; ; )sleep(1); }上面的测试程序中在最后都使用了一个 for 死循环让程序处于休眠状态无法退出为什么要这样做呢原因在于程序退出时也会自动刷新 stdio 缓冲区这样的话就会影响到测试结果下面去掉 for 死循环让程序结束 程序退出时系统自动刷新 stdio 缓冲区 #include stdio.h #include stdlib.h #include unistd.hint main(void) {printf(Hello World!\n);printf(Hello World!); }$ gcc io5.c -o io5 $ ./io5 Hello World! Hello World! $如果使用 exit()、return 或像上述示例代码一样不显式调用相关函数或执行 return 语句来结束程序这些情况下程序终止时会自动刷新 stdio 缓冲区但是如果使用 _exit 或 _Exit() 终止程序则不会自动刷新 stdio 缓冲区 11. 文件描述符与 FILE 指针互转 在同一个文件上执行 I/O 操作时可以将文件 I/O系统调用 I/O与标准 I/O 混合使用这个时候就需要将文件描述符和 FILE 指针对象之间进行转换此时可以借助于库函数 fdopen()、fileno() 来完成 库函数 fileno() 可以将标准 I/O 中使用的 FILE 指针转换为文件 I/O 中所使用的文件描述符而 fdopen() 则进行着相反的操作 #include stdio.hint fileno(FILE *stream); FILE *fdopen(int fd, const char *mode);对于 fileno() 函数来说 根据传入的 FILE 指针得到整数文件描述符通过返回值得到文件描述符如果转换错误将返回 -1并且会设置 errno 来指示错误原因得到文件描述符之后便可以使用诸如 read()、write()、lseek()、fcntl()等文件 I/O 方式操作文件 fdopen() 函数与 fileno() 功能相反 给定一个文件描述符得到该文件对应的 FILE 指针之后便可以使用诸如 fread()、fwrite() 等标准 I/O 方式操作文件了 当混合使用文件 I/O 和标准 I/O 时需要特别注意缓冲的问题 文件 I/O 会直接将数据写入到内核缓冲区进行高速缓存标准 I/O 则会将数据写入到 stdio 缓冲区之后再调用 write() 将 stdio 缓冲区中的数据写入到内核缓冲区 #include stdio.h #include stdlib.h #include unistd.hint main(void) {printf(print); // 缺换行符 \nwrite(STDOUT_FILENO, write\n, 6);exit(0); }$ gcc test.c -o test $ ./test write # 先输出了 write 字符串信息接着再输出了 print 字符串信息 print $
http://www.dnsts.com.cn/news/139016.html

相关文章:

  • 焦作做网站推广邢台市建设银行网站
  • 服装代销的网站源码公司报备网站
  • 北京网站建设公司哪个最好wordpress农业站模板
  • 佛山建网站价格企业广告
  • 阿里云网站地图是怎么做的网站需要写哪些内容吗
  • 用云空间制作网站wordpress 留言 seo no
  • 网站跳出率因素如何在自己电脑上建设网站
  • 买完域名以后怎么做网站wordpress 装饰公司
  • 专业网站建设分类标准wordpress 统计字数
  • 网站建设必须买数据库么如何做新网站
  • 知名网站用的技术wordpress调用分类目录文章
  • 建设银行信用卡卡网站怎样做可以连接服务器的网站
  • 加强网站政务服务建设微信小程序开发公司排名
  • 织梦 网站搬家常德建设网站
  • 湖南网络公司网站建设pc网站原型设计工具
  • 适合做网站的图片学编程的孩子有什么好处
  • 微信小程序+网站开发网站建设的总体设计思想
  • 泉州做网站优化的公司平台贷款无力偿还怎么办
  • 公司做网站需要给百度交钱吗正能量餐饮品牌设计
  • 湖南企业网站制作公司广东网站建站系统哪家好
  • 福州网站制作建设网站建设续费的回访话术
  • 建设京东商城网站先做网站还是先解析
  • 海外域名注册网站网站建设需求说明书怎么写
  • 个人做哪方面网站零售管理系统哪个软件好
  • 重庆h5网站建设模板网站制作器
  • 做网站用什么软件设计好河南5G网站基站建设信息
  • 博物馆网站建设方案报价评价一个网站
  • 一起做网站女装夏季裙网站流量渠道
  • 怎么把网站整站下载百度指数明星搜索排名
  • 株洲建设网站制作给网站做网络安全的报价