文章目录
文件系统编程
什么是文件 ---- 一种通用的接口
“文件”这个名词不陌生,什么是文件?
系统资源(内存、硬盘、一般设备、进程间通信的通道等)的一个抽象
对系统资源进行访问的一个通用接口。
采用这种“文件”的方式有什么好处?
对资源提供通用的操作接口,可以极大地简化系统编程接口的设计。
既然文件是一个通用的接口,由于系统资源多种多样,是不是意味着文件类型也多种多样?
常用文件类型
常见的文件类型(可以通过文件来访问的系统资源)有:
· 普通文件
一般意义上的文件,作为数据存储在磁盘中,可以随机访问文件的内容。Linux系统中的文件是面向字节的,文件的内容以字节为单位进行存储和访问。
· 目录
目录是一种特殊的文件,目录可以像普通文件一样打开、关闭以及进行相应的操作。
· 管道
管道是Linux中的一种进程间通信的机制。
· 设备文件
设备文件没有具体的内容,对设备文件的读写操作实际上与某个设备的输入输出操作关联在一起。
· 符号链接
符号链接的内容是指向另一个文件的路径。当对符号链接进行操作时,系统会根据情况将这个操作转移到它所指向的文件上去,而不是对它本身进行操作。
· socket
socket也是一种进程间通信的方式,与管道不同的是,它们可以在不同的主机上进行通信,也就是网络通信。
如何表示文件?
所有执行I/O操作的系统调用使用文件描述符来表示打开的文件。
· 文件描述符是一个非负整数。
· 文件描述符可以表示各种类型的打开的文件。
· 对文件的操作只要使用文件描述符即可指定所操作的文件。
如何获得文件描述符?获得文件描述符以后如何进行文件操作?
· 打开文件,打开成功后,应用程序将获得文件描述符。
· 应用程序使用文件描述符对文件进行读写等操作。
· 全部操作完毕后,应用程序需要将文件关闭以释放用于管理打开文件的内存。
文件描述符和打开文件之间的关系
内核使用三种数据结构表示一个打开的文件
· 文件描述符表(每个进程都有)
(1) 文件描述符标志(file descriptor flags),如:close_on_exec。
(2) 指向一个文件表项(file table entry)的指针。
· 打开文件表(open file table)
(1) 文件状态标志(file status flags),如读、写、非阻塞等。
(2) 当前文件偏移量(file offset)。
(3) 文件访问模式(read-only, write-only, or read-write)。
(4) 指向i-node表项的指针。清楚上述问题,首先需要了解内核如何表示打开的文件
· 文件系统i-node表
(1) 文件类型(regular file, socket, or FIFO)和权限。
(2) 指向文件表的指针。
(3) 文件的属性(file size等)。
系统调用
LINUX系统是通过文件描述符来管理文件的(类似于通过地址管理内存)
1、每一个程序运行 默认打开三个文件
文件 | 文件指针 | 文件描述符(数字) | |
---|---|---|---|
标准输入文件 | 从键盘获取数据 | stdin | 0 |
标准输出文件 | 在屏幕上显示正确的数据 | stdout | 1 |
标准错误文件 | 在屏幕上显示错误的数据 | stderror | 2 |
成功打开文件会返回文件描述符,选取当前最小的文件描述符返回
失败打开文件会返回 -1
通过linux系统内核提供的全局变量 errno 可查看 | 错误号 |
---|---|
通过库提供的函数perror来查看 | 错误信息 |
查看帮助手册
命令 | man 2 函数名 |
---|---|
打开文件
系统函数: | open |
---|---|
函数原型 | int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); |
关闭文件
系统函数: | close |
---|---|
函数原型 | int close(int fd); |
读文件
系统函数: | read |
---|---|
函数原型 | ssize_t read(int fd, void *buf, size_t count); |
写文件
系统函数: | write |
---|---|
函数原型 | ssize_t write(int fd, const void *buf, size_t count); |
改变文件偏移量
lseek系统调用可以改变文件偏移量(File Offset)。文件偏移量是一个整数,表示距文件起始处的字节数。 ===> 改变文件指针
系统函数: | lseek |
---|---|
函数原型 | off_t lseek(int fd, off_t offset, int whence); |
复制文件描述符表中的文件指针
系统函数: | dup 和 dup2 |
---|---|
函数原型 | int dup(int oldfd); int dup2(int oldfd, int newfd); |
标准库函数
IO缓冲
C标准库提供了操作文件的标准I/O函数库,与系统调用相比,主要差别是实现了一个跨平台的用户态缓冲的解决方案。
例如:
为什么要采用这种缓冲机制?
为了提高系统进行I/O操作的效率!
系统调用要请求内核的服务,会引发CPU模式的切换,期间会有大量的堆栈数据保存操作,开销比较大。如果频繁地进行系统调用,会降低应用程序的运行效率。有了缓冲机制以后,多个读写操作可以合并为一次系统调用,减少了系统调用的次数,将大大提高程序的运行效率。
所谓的标准I/O函数实际上是对底层系统调用的封装,最终读写设备或文件的操作仍需调用系统I/O函数来完成。
标准I/O函数使用文件指针操作文件
标准I/O函数并不直接操作文件描述符,而是使用文件指针。文件指针和文件描述符是一一对应的关系,这种对应关系由标准I/O库自己内部维护。文件指针指向的数据类型为FILE型,但应用程序无须关心它的具体内容。
在标准I/O中,一个打开的文件称为流(stream),流可以用于读(输入流)、写(输出流)或读写(输入输出流)。每个进程在启动后就会打开三个流,分别对应:stdin(标准输入流)、stdout(标准输出流)以及stderr(标准错误输出流)。
文件操作
查看帮助手册
命令 | man 3 函数名 |
---|---|
打开文件
标准库函数 | fopen |
---|---|
函数原型 | FILE *fopen(const char *path, const char *mode); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream); |
“r"或"rb”: | 以只读方式打开。 |
---|---|
“w"或"wb”: | 以只写方式打开,并把文件长度截短为零。 |
“a"或"ab”: | 以写方式打开,新内容追加在文件尾。 |
"r+"或"rb+“或"r+b”: | 以更新方式打开(读和写)。 |
"w+"或"wb+“或"w+b”: | 以更新方式打开,并把文件长度截短为零。 |
"a+"或"ab+“或"a+b”: | 以更新方式打开,新内容追加在文件尾。 |
注:字母b表示文件是一个二进制文件而不是文本文件。
关闭文件
标准库函数 | fclose |
---|---|
函数原型 | int fclose(FILE *fp); |
读文件
标准库函数 | fread |
---|---|
函数原型 | size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); |
###· 写文件
标准库函数 | fwrite |
---|---|
函数原型 | size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream); |
###· 清空缓冲区
标准库函数 | fflush |
---|---|
函数原型 | int fflush(FILE *stream); |
###· 改变文件指针下次读写操作的位置
标准库函数 | fseek |
---|---|
函数原型 | int fseek(FILE *stream, long offset, int whence); |
//读文件
fgetc()从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件结尾或出现错误时,返回EOF。getc()和fgetc()一样,但它有可能被实现为一个宏。getchar()相当于getc(stdin)。
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
fgets : 一行一行的读
//写文件
fputc()把一个字符写到一个输出文件流中,它返回写入的值,如果失败,则返回EOF。类似fgetc()和getc(),putc()的作用也相当于fputc(),但它可能被实现为一个宏。putchar()相当于putc(c, stdout),它把单个字符写到标准输出。
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
注意:putchar和getchar都是把字符当作int类型而不是char类型来使用的,这就允许文件结尾EOF取值为-1。