Linux应用 标准I/O
标准I/O库
标准I/O库的函数都定义在**头文件<stdio.h>**中,其是建立在文件I/O(open(),close(),write(),read())这些系统调用之上的。
设置标准I/O库的目的是提供比底层系统调用更方便、好用的调用接口。同时标准I/O有更好的移植性。
标准I/O在用户空间维护了自己的stdio缓冲区,所以标准I/O是带缓存的,所以性能和效率上高于文件I/O。
FILE指针
前面说的文件I/O对于文件的操作是基于fd文件描述符的,在标准I/O中,对于文件的操作是基于FILE*进行的。
FILE 是一个结构体数据类型,其包含了标准I/O库函数为管理文件所需要的所有信息。包括文件描述符、指向文件缓冲区的指针、缓冲区的长度等等。
标准输入、输出、错误
标准输入:通常指计算机连接的键盘
标注输出:用于输出标准信息的设备、如显示器
标准错误:用来显示错误信息的设备、也是指显示器
整个流程:
用户通过标准输入设备与系统进行交互,进程将从标准输入(stdin)文件(把握重点)中得到输入数据,将正常输出数据(譬如程序中 printf 打印输出的字符串)输出到标准输出(stdout)文件,而将错误信息(譬如函数调用报错打印的信息)输出到标准错误(stderr)文件。
进程所操作的输入、输出、错误都是基于文件产生的。标准输入对应键盘,标准输出和错误对应显示屏
每个进程默认都会打开这三个文件,得到0(in)、1(out)、2(error)文件标识符,这也就是我们的文件标识符为什么是以3开始的。
上面说的是文件I/O,在标准I/O中也存在宏定义
/* Standard streams. */
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. */
//_IO_FILE就是FILE结构体,它被typedef重命名了
/* C89/C99 say they're macros. Make them happy. */
#define stdin stdin
#define stdout stdout
#define stderr stderr
标准I/O函数
fopen()
对应文件I/O的open
#include <stdio.h>FILE* fopen(const char* path,const char* mode);
//path指向路径
//mode指定读写权限
//失败了返回NULL
mode的种类
1、r 只读打开文件 , r+ :可读可写打开文件
2、w只写打开, w+:可读可写打开文件 w和w+打开文件时,如果文件存在就将文件截断为0,然后打开,如果不存在就创建一个指定路径的文件,然后再打开。
3、a:只写打开文件,a+:可读可写打开文件 ,a不仅仅在文件不存在的时候对文件进行创建,同时打开以后在文件末尾追加内容。
新建文件的权限
fopen函数中,没有要求我们传入新文件的权限,但是它有一个默认值,那就是
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH (0666) 文件拥有者、文件同组人、其他,他们的权限都是可读可写不可执行0x666
fclose()
传入结构体指针即可
#include <stdio.h>
int fclose(FILE *stream);
//关闭文件 回收FILE* 资源
fread()和fwrite()
#include <stdio.h>size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);//ptr:读或者写的缓冲区
//size:读或者写的数据大小
//nmemb:读或者写的数据个数
//调用成功以后会返回实际读或者写的数据个数。失败的话便返回-1
//可以使用ferror()来判断读写过程中发生了什么样的错误。
fseek定位
之前说的系统调用中出现了lssek()用来移动文件的读写指针,那么在标准I/O库中,也有对应的设置文件读写位置偏移量的函数,
#include <stdio.h>
int fseek(FILE* stream,long offset,int whence)
//stream: FILE结构体指针
//offset: 相对于whence的偏移量
//whence:文件的位置标识
//同lseek()函数相同,whence可取
//1、SEEK_SET:文件开头 2、SEEK_END:文件结尾
ftell()
ftell()被用来获取文件当前的读写位置偏移量
#include <stdio.h>
long ftell(FILE *stream);
//调用失败返回-1
//单位是字节
错误检查
feof() 检查end-of-line标志
当使用fread()读文件的时候,如果我们要读的数据比文件本身的数据量大,我们使用fread()无法判断是读出错还是到达文件末尾,这时候我们需要通过查看文件的 end-of-file标志来判断是那种情况。feof()函数就可以检查文件的end-of-file标志
#include <stdio.h>int feof(FILE* stream);
//如果end_of_file标志被设置了,就会返回一个非零值
//如果没有被设置就会返回0
ferror()函数 检查是否出现错误标志
这个函数用来测试指定文件的错误标标志,使用方法同feof()相同,如果有错误,就返回一个非零值,如果没有错误,就返回0;
#include <stdio.h>
int ferror(FILE *stream);
clearerr()函数 清除end-of-line标志和错误标志
用来清除指定文件的end_of_file标志或者是错误标识,通常使用在检查完错误以后
#include <stdio.h>
void clearerr(FILE *stream);
//只要调用了就会清除
//调用fseek()成功以后也会清楚end_of_file标志
格式化I/O
操作格式化控制字符串、可以以调用者指定的格式进行转换输出。
格式化输出
#include <stdio.h>
int 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, ...);
printf()将格式化输出写到标准输出、 fprintf()和dprintf()写到指定的文件 sprintf()和snprintf()将格式化数据写到执行的缓冲区**(要确保缓冲区足够大)**
格式化输入
scanf()函数将用户输入(标准输入)的数据进行格式化转换并进行存储,它从格式化控制字符串format 参数的最左端开始,每遇到一个转换说明便将其与下一个输入数据进行“匹配”,如果二者匹配则继续,否则结束对后面输入的处理。
同上面一样、将printf换成scanf,同样有指定文件读取,指定缓冲区读取。
I/O缓冲
内核缓冲是对于硬盘来说的缓冲,stdio缓冲是对于内核缓冲来说的缓冲
文件 I/O 会直接将数据写入到内核缓冲区进行高速缓存,而标准 I/O 则会将数据写入到 stdio 缓冲区,之后再调用 write()将 stdio 缓冲区中的数据写入到内核缓冲区。
出于速度和效率的考虑,系统 I/O 调用(即文件 I/O,open、read、write 等)和标准 C 语言库 I/O 函数(即标准 I/O 函数)在操作磁盘文件时会对数据进行缓冲。
这样减少了内核访问磁盘的次数,提高了运行效率。
系统调用的缓冲刷新函数
系统调用的缓冲刷新指的是将内存缓冲区的数据写回磁盘,这样可以减少对磁盘的访问次数,提高运算的效率
fsync()函数
#include <unistd.h>
int fsync(int fd);
//将参数fd所指的文件内容和元数据写回磁盘。
//元数据包括文件大小、时间戳、权限等信息
fdatasync()函数
#include <unistd.h>
int fdatasync(int fd);
//仅仅将fd所指文件内容写入磁盘,不包括文件的元数据
sync()函数
#include <unistd.h>
void sync(void);
//刷新所有文件I/O内核缓冲区
ODSYNC标志 自动刷新(不包括元数据)
open时加上这个表示每个 write()调用之后调用 fdatasync()函数进行数据同步
O_SYNC标志 自动刷新 (包括元数据)
每个 write()调用都会自动将文件内容数据和元数据刷新到磁盘设备中
直接I/O、 不使用内核缓冲
Linux 允许应用程序在执行文件 I/O 操作时绕过内核缓冲区,从用户空间直接将数据传递到文件或磁盘设备,把这种操作也称为直接 I/O(direct I/O)或裸 I/O(raw I/O)
直接 I/O 只在一些特定的需求场合
在打开文件时加上O_DIRECT标志。
直接I/O的对齐限制
直接I/O是以数据块为单位的。
应用程序中用于存放数据的缓冲区,其内存起始地址必须以块大小的整数倍进行对齐;
写文件时,文件的位置偏移量必须是块大小的整数倍;
写入到文件的数据大小必须是块大小的整数倍。
直接 I/O 方式每次 write()调用均是直接对磁盘发起了写操作,而普通方式只是将用户空间下的数据拷贝到了文件 I/O 内核缓冲区中,并没直接操作硬件,所以消耗的时间短,硬件操作占用的时间远比内存复制占用的时间大得多直接 I/O 方式效率、性能比较低,绝大部分应用程序不会使用直接 I/O 方式对文件进行 I/O 操作,通常只在一些特殊的应用场合下才可能会使用,那我们可以使用直接 I/O 方式来测试磁盘设备的读写速率,这种测试方式相比普通 I/O 方式就会更加准确。
stdio缓冲
前面的内核缓冲是由内核维护的缓冲,stdio缓冲是用户维护的缓冲。使用stdio缓冲可以减少系统调用的次数,进而提升效率。
标准 I/O 函数会将用户写入或读取文件的数据缓存在 stdio 缓冲区(使用缓冲区是标准I/O读写函数自动执行的),然后再一次性将 stdio 缓 冲区中缓存的数据通过调用系统调用 I/O(文件 I/O)写入到文件 I/O 内核缓冲区或者拷贝到应用程序的 buf 中
stdio缓冲相当于是系统缓冲的缓冲。
设置stdio缓冲
#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
//mode:_IONBF:不缓冲 _IOLB0F:行缓冲 _IOFBF:全缓冲(写满再缓冲)
void setbuf(FILE *stream, char *buf); //等价于setvbuf(stream, buf, buf ? _IOFBF : _IONBF, BUFSIZ);
//BUFSIZ 定义于头文件<stdio.h>中,该值通常为 8192void setbuffer(FILE *stream, char *buf, size_t size); //同setbuf 但可以指定缓冲区大小
// 等价于setvbuf(stream, buf, buf ? _IOFBF : _IONBF, size);
刷新缓冲区
#include <stdio.h>
int fflush(FILE *stream); //将当前文件的stdio缓冲区数据写入内核缓冲区。 如果stream == NULL 表示刷新所有的stdio缓冲区
关闭文件后也会刷新缓冲区 fclose()关闭文件
程序退出以后也会刷新缓冲区
FILE*和fd的转换
#include <stdio.h>
int fileno(FILE *stream);
FILE *fdopen(int fd, const char *mode);