1.open函数:
函数原型:
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int open(const char *pathname,int flaos);
int open(const char *pathname,int flaos,mode_t mode);
函数功能:
- 用于打开或创建文件,在打开或创建文件时可以指定文件的属性及用户的权限等,若目标文件不存在,需要创建文件时,使用三个参数的open函数,否则使用俩个参数的
参数解析:
1.pathname:要打开或创建的目标文件
2.flags:打开文件时,可传入多个参数选项,用以下的一个会多个常量进行“或”运算,构成flags
- O_RDONLY:只读打开
- O_WRONLY:只写打开
- O_RDER:读写打开(这三个常量,必须指定一个)
- O_CREAT:若文件不存在,创建文件(需要用mode选项,来指明新文件的访问权限)
- O_APPEND:追加写
3.mode:用户的访问权限:可用R/W/X表示,也可以用八进制表示
返回值:成功返回打开文件的文件描述符,失败返回-1
2.read函数:
函数原型:
#include<unistd.h>
ssize_t read(int fd,void *buf,size_t count)
函数功能:从文件里读数据
参数解析:
- fd:要读取的文件的文件描述符
- buf:指缓冲区,要有一个缓冲区来接收读取的数据
- count:表示调用一次read,应读取多少数量的字符
返回值:返回读取到的字符数,0表示读到EOF,-1表示出错
3.write函数
1)函数原型:
#include<unistd.h>
ssize_t write(int fd,const void *buf,size_t count);
函数功能:向目标文件里面写数据
参数解析:
- fd:文件描述符
- buf:要项文件里写入的内容
- count:写入多少内容
返回值:成功则返回写入文件的字符数,失败返回-1
4.close函数
函数原型
#include<unistd.h>
int close(int fd);
函数功能:关闭已打开的文件
参数解析:fd:要关闭的文件的文件描述符
返回值 :成功返回0,失败返回-1
系统调用和库函数
- fopen,fclose,fread,fwrite都是c标准库当中的函数,我们称之为库函数
- 而open,close,read,write,lseek都是系统提供的接口,称之为系统调用
- f#系列的函数,都是对系统调用的一种封装,方便二次开发
文件描述符fd:
- 文件描述符就是一个从0开始的小整数。
- Linux默认情况下会有三个缺省打开的文件描述符。0标准输入(stdin),1标准输出(stdout),2标准错误(stderr)
- 0,1,2对应的硬件一般是:键盘,显示器,显示器
- 因为0,1,2这三个文件描述符硬件被占用,所以我们新建一个文件的时候一般都是从3开始分配。从当前最小的未被占用的文件描述符中分配
- 从上图可以看出,文件描述符是从0开始的小整数,当我们打开文件时,操作系统在内存中要创建相应的数据结果来描述目标文件。于是就有了file结构体,表示一个已经打开的文件对象。
- 而进程执行open系统调用,所以必须让进程和文件关联起来
- 每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针
- 所以,本质上,文件描述符就是该数组的下标,只要拿着文件描述符就可以找到对应的文件
Linux通常会给每个进程能打开的文件数量强加一个限制,文件描述符的限制可能会极大的影响性能,当用户用完了所有文件描述符之后,它不能接受新的连接,也就是说,用完文件描述符导致拒绝服务
我们可以尝试使用ulimit -Hsn xxx来修改文件描述符个数
内核表示打开的文件使用的三种数据结构:
内核使用三种数据结构表示打开的文件,他们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响
- 每个进程在进程表中都有一个记录表项,记录表项中包含有一张打开的文件描述符,可以视为一个矢量,每个描述符占用一项
- 文件描述符标志
- 一个指向文件表项的指针
- 内核为所有打开的文件维持一张文件表,每个文件表项包括
- 文件状态:读,写,添加,同步,非阻塞等
- 当前文件的偏移量
- 指向该文件v节点表项的指针
- 每个打开文件都有一个v节点结构,v节点包含了文件类型和对比文件进行各自操作的函数指针,对于大多数文件,v节点还包含了该文件的i节点,这些信息实在打开文件时从磁盘上读入内存的,所以所有关于文件的信息所有可供使用(Linux下没有V节点,而是通过i节点结构,输入俩种实现有所不同,但是在概念上,v节点与i节点是一样的,俩这都指向文件系统特有的i节点结构)
FILE结构体:
c语言的stdio.h头文件中,定义了用于文件操作的结构体FILE,所以我们可以通过fopen返回一个文件指针(指向FILE结构体的指针)来进行文件操作
- FILE结构体中最重要的俩个成员变量就是:文件描述符和缓冲区大小。
- 其中c库的缓冲区分三类:无缓冲,行缓存,全缓存
- 因为IO相关函数与系统调用接口对应,且库函数封装系统调用。所以本质上,访问文件都是通过fd访问的。所以,c库中的FILE结构体,必定封装了fd
FILE结构体的部分成员如下:
struct FLIE
{
char *_ptr;//文件输入的下一个位置
int _cnt;//当前缓冲区的相对位置
char *_base;//指基础位置(文件的起始位置)
int _flag;//文件标志
int _file;//文件的有效性验证
int _charbuf;//检查缓冲区状况
int _bufsize;//文件的大小
char *_tmpfname;//临时文件名
}
代码:
1 #include<stdio.h>
2 #include<string.h>
3
4 int main()
5 {
6 const char* msg0 = "hello printf\n";
7 const char* msg1 = "hello fwrite\n";
8 const char* msg2 = "hello write\n";
9
10 printf("%s",msg0);
11 fwrite(msg1,strlen(msg0),1,stdout);
12 write(1,msg2,strlen(msg2));
13
14 fork();
15 return 0;
16 }
输出结果:
程序的输出结果只有三条,但是当我们写入文件时,结果如下:
结果写入文件时,有五条语句
- 一般c库函数写入文件是全缓冲的(等待缓冲区满或程序退出),而写到屏幕上是行缓冲
- printf,fwrite函数自带缓冲区,而write无缓冲
- 所以写入文件时是全缓冲,放在缓冲区的数据不会立即被刷新,甚至fork之后,而fork之后父进程刷新缓冲区的数据时子进程也有一份同样的数据,随机产生倆份数据。所以写入文件共五条数据