1.标准IO,Unbuffered IO是什么
标准IO:ANSI C建立的一个标准I/O模型,是一个标准函数包和stdio.h头文件中的定义,具有一定的可移植性。标准IO库处理很多细节。例如缓存分配,以优化长度执行IO等。标准的IO三种类型的缓冲。
- 全缓冲:当填满IO缓冲区后,才会刷新缓冲区
- 行缓冲:当程序遇到刷新符号时,刷新缓冲区
- 无缓冲:没有缓冲区
Unbuffered IO:无缓冲IO。不带缓存指的是,每个read,write都调用内核中的一个系统调用。也就是一般所说的低级I/O——操作系统提供的基本IO服务,与os绑定,特定于linix或unix平台。
2.标准IO和Unbuffered IO使用的函数:
标准IO | Unbuffered IO | |
---|---|---|
打开 | fopen | open |
读 | fread ,fgetc,fgets | read |
写 | fwrite,fputc, fputs | write |
操作位置 | fseek,ftell,rewind | lseek |
关闭 | fclose | close |
- fopen
FILE *fopen(const char *path, const char *mode);
参数解释:
- path:打开的文件名
- mode:打开方式
- 函数返回值:成功返回文件流指针,错误返回NULL,并设置错误
- 打开方式具体有:
r:读文本文件,流指针在文件开始,文件不存在则报错
r+:读写文本文件,流指针在文件开始,文件不存在则报错
w:写文件,不存在,则创建,存在先清空文件,流指针在文件开始
w+:读写文件,不存在,则创建,存在先清空文件,流指针在文件开始
a:追加方式打开,不存在,则创建,流指针在文件末尾
a+:追加方式打开,不存在,则创建,读文件的初始位置是文件的开始,但是输出总是被追加到文件的末尾。流指针在文件末尾
- open
int open(const char *pathname, int flags,mode_t mode);
参数解释:
- pathname:打开的文件名
- flags:以何种方式打开
- mode:权限,新创建出来的文件色湖之权限,以八进制的方式,如0664
- 返回值:成功返回大于等于0的文件描述符,失败返回-1
- 具体打开方式:
O_RDONLY:只读
O_WRONLY:只写
O_RDWD:读写
上述三个宏,必选其一。下面是可选的宏
O_APPEND:追加
O_TRUNC:截断(清空)
O_CREAT:不存在,则创建- 具体的使用方式:必选的和可选之间按位或方式
例如:int fd = open(“test.c”, O_RDWD | O_APPEND,0664);//以可读可写追加的方式 打开test.c,权限为0664
- fread
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数解释:
- ptr:读到的内容保存在ptr中
- size:每一块的大小
- nmemb:总共有多少个块
- stream:文件流指针
- 返回值:成功返回读到块的个数,失败返回0
- 使用时,一般将size设为1,这样返回的就是读取到的字节数
- read
ssize_t read(int fd, void *buf, size_t count);
参数解释:
- fd:文件描述符
- buf:读到的内容放在buf中
- count:读取count个字节的数据
- 返回值:成功返回读到的字节个数,错误返回-1
- fwrite
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
参数解释:
- ptr:从ptr中读取数据
- size:每一块的大小
- nmemb:总共有多少个块
- stream:文件流指针
- 返回值:成功返回写到文件块的个数,失败返回0
- 使用时,一般将size设为1,这样返回的就是写到文件中的字节数
- write
ssize_t write(int fd, const void *buf, size_t count);
参数解释:
- fd:文件描述符
- buf:写入buf中的数据到文件
- count:写入的字节数
- 返回值:成功返回写入的字节数,错误返回-1
- fseek
int fseek(FILE *stream, long offset, int whence);
函数功能:通过文件流指针,设置文件流指针指向
参数解释:
- stream:文件流指针
- offset:偏移量
- whence:
SEEK_SET:文件流指针偏移到文件头部
SEEK_CUR:当前位置
SEEK_END:文件流指针偏移到文件尾部- 返回值:成功返回0,错误返回-1
- 使用时:offset和whence配套使用
例如:fseek(fp, 1, SEEK_SET);//文件流指针在头部偏移一个位置
- ftell
long ftell(FILE *stream);
函数功能:返回当前位置对于起始位置的偏移量
失败返回-1
- rewind
void rewind(FILE *stream);
无返回值,使当前文件流指针返回到文件头部
即等于 fseek(stream, 0, SEEK_SET)操作
- lseek
off_t lseek(int fd, off_t offset, int whence);
参数解释:
- fd:文件描述符
- offset:偏移量
- whence:
SEEK_SET:文件流指针偏移到文件头部
SEEK_CUR:当前位置
SEEK_END:文件流指针偏移到文件尾部- 返回值:返回开始和结束时的偏移量
- fclose
int fclose(FILE *stream);
关闭文件流指针
成功返回0,错误返回EOF
- close
int close(int fd);
关闭文件描述符
成功返回0,错误返回-1
其中的函数操作。实际上,标准IO中的函数都调用了Unbuffered IO中的函数。
如:库函数和系统调用的层次关系
3.具体如何使用这些函数?
- 用Unbuffered IO函数每次读写都要进内核,调一个系统调用比调一个用户空间的函数要慢很多,所以在用户空间开辟IO缓冲区还是必要的,用C标准IO库函数就比较方便,省去了自己管理IO缓冲区的麻烦。
- 用C标准IO库函数要时刻注意IO缓冲区和实际文件有可能不一致,在必要时需调用fflush。
- 我们知道Linux的传统是Everything is a file, IO函数不仅用于读写常规文件,也用于读写设备,比如终端或网络设备。在读写设备时通常是不希望有缓冲的,例如向代表网络设备的文件写数据就是希望数据通过网络设备发送出去,而不希望只写到缓冲区里就算完事儿了,当网络设备接收到数据时应用程序也希望第一时间被通知到,所以网络编程通常直接调用Unbuffered IO函数。
思考:
如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,而C标准库的I/O缓冲区则不具有这一特性。为什么标准IO没有这个特性呢?
标准IO中,是需要缓冲区刷新才能进入文件中,即使一个进程结束,刷新了文件,另一个进程的文件中数据仍然还是缓冲区中,等到缓冲区刷新之后,才能看到数据。
4. 文件描述符
每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor) ,而在操作系统理论中称为进程控制块(PCB, Process Control Block) 。 task_struct中有一个指针指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开的文件的指针。
即示意图:
操作系统会为每一个进程在磁盘下创建一个以进程号命名的文件,在该文件下中的fd,保存的就是该进程打开的文件描述符。
1 #include <stdio.h>
2 #include <unistd.h>
3
4 int main()
5 {
6 FILE* fp=fopen("test.txt","w+");
7 if(fp == NULL)
8 return -1;
9 while(1)
10 {
11 sleep(1);
12 }
13 return 0;
14 }
3号文件描述符,就是打开的文件。
每一次新建一个进程,OS会自动打开3个文件描述符。
- 0:标准输入
- 1:标准输出
- 2:标准错误
文件描述符是什么?
- 文件描述符其实就是内核当中fd_array数组的下标
- 文件描述符是一个正整数,分配原则为最小未占用原则。
文件描述符和文件流指针的区别?
- 文件流指针是fopen函数返回的,文件流指针是C库维护的
- 文件描述符是open函数返回的,文件描述符是内核维护的
结构示意图:
对于不同的文件流指针,在C库中会创建不同的struct _IO_FILE,在_IO_FILE中保存了不同的文件描述符。
文件流指针中包含了文件描述符。
OS中到处都存在着缓冲区,而文件流指针的缓冲区是C库负责维护的。
所以exit函数退出时,会刷新缓冲区,因为操作的就是文件流指针。
_exit函数退出时,不会刷新缓冲区,因为该缓冲区是C库维护的,内核根本不知道有这么个缓冲区。