一、POSIX文件I/O与ASCI文件I/O
POSIX表示可移植操作系统接口(Portable Operating System Interface of UNIX,缩写为 POSIX ),不带缓存的文件IO操作,于直接调用系统调用(system call)的方式,高效完成文件输入输出。它以文件标识符(整型)作为文件唯一性的判断依据。
ASCI文件I/O:带缓存的文件IO操作,效率低但是易于维护。它以文件指针(FILE*)作为文件唯一性的判断依据。
二、静态文件与动态文件
文件平时存储在块存储设备(类似于硬盘)中的文件系统中(静态文件),open打开一个文件时,linux内核在进程中建立一个打开文件的数据结构,记录打开的文件的信息;内核在内存中申请建立一段内存,并将静态文件的内容从块存储设备读取到特定地址管理存放(动态文件)。
打开文件后对这个文件的读写操作都是针对内存中的动态文件,当对动态文件进行读写后,动态文件和块存储设备中的静态文件不同步,close时关闭动态文件,内核将内存中的动态文件的内容同步到块存储设备的静态文件。
三、操作文件的一般步骤
先open打开一个文件,得到一个文件描述符,然后对文件进行write/read操作(或其他操作),最后close关闭文件即可。
文件描述符其实实质是一个数字,当我们open打开一个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护这个动态文件的这些数据结构挂钩绑定上了,以后我们应用程序如果要操作这一个动态文件,只需要用这个文件描述符进行区分。文件描述符的作用域就是当前进程,出了当前进程这个文件描述符就没有意义了。在遵从POSIX的应用程序中,文件描述符0、1、2分别对应STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO,因此一个应用程序进程中文件描述符总是从3开始的。
3.1 创建/打开/关闭文件
(1)open()函数
POSIX使用open()函数打开一个文件,使用creat()函数创建一个新文件。open()函数在指定一定参数的情况下,会隐含调用creat()函数创建文件。
int open(const char *file_name, int flags)
int open(const char *file_name, int flags, mode_t mode)
- file_name: 欲打开的文件名(可包含路径)
- mode: 创建文件的权限,在嵌入式开发中,一般使用默认权限。
使用四个数字指定创建文件的权限,与linux的权限设置相同,如0755
- flags:指定了打开文件的方式,可以用C语言的“或”操作符指定多个参数
flags | description |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
O_CREAT | 若欲打开的文件不存在,则创建该文件 |
O_APPEND | 追加的方式打开(定位到文件尾部) |
O_TRUNC | 若文件存在,则长度被截为0 |
O_EXCL | 若O_CREAT也设置,此指令检查文件是否存在:若不存在则建立新文件;若存在则报错 |
O_LARGEFILE | 在32bit系统中,支持大于2G的文件的打开 |
O_DIRECTORY | 若打开的文件不是目录,则报错 |
O_NONBLOCK | 对文件的操作使用非阻塞模式。此方式对FIFO,特殊块设备文件,或特殊字符设备文件有效。 |
O_SYNC | 使每次write都等到物理I/O操作完成,包括由write操作引起的文件属性更新所需的I/O。 |
注意:
(1)O_TRUNC与O_APPEND
- O_TRUNC属性去打开文件时,如果这个文件中本来是有内容的,则原来的内容会被全部丢弃。
- O_APPEND属性去打开文件时,如果这个文件中本来是有内容的,则新写入的内容会接续到原来内容的后面。如果O_APPEND和O_TRUNC同时出现,只有O_TRUNC起作用。
(2)O_CREAT与O_EXCL
- open中加入O_CREAT后,不管原来这个文件存在与否都能打开成功。假如我们希望的效果是:如果我CREAT要创建的是一个已经存在的名字的文件,则给我报错,不要去创建,就要靠O_EXCL标志和O_CREAT标志来结合使用。当O_EXCL和O_CREAT联合使用open一个已经存在的文件,则会报错,打开失败。
- open函数在使用O_CREAT标志去创建文件时,可以使用第三个参数mode来指定要创建的文件的权限。
(3)O_NONBLOCK
- 如果一个函数是阻塞式的,则我们调用这个函数时当前进程有可能被卡住(阻塞住,实质是这个函数内部要完成的事情条件不具备,当前没法做,要等待条件成熟),函数被阻塞住了就不能立刻返回;
- 如果一个函数是非阻塞式的那么我们调用这个函数后一定会立即返回,但是函数有没有完成任务不一定。阻塞和非阻塞是两种不同的设计思路,并没有好坏。总的来说,阻塞式的结果有保障但是时间没保障;非阻塞式的时间有保障但是结果没保障。
- 我们打开一个文件默认就是阻塞式的,如果你希望以非阻塞的方式打开文件,则flag中要加O_NONBLOCK标志。
(4)O_SYNC
- write阻塞等待底层完成写入才返回到应用层。
- 无O_SYNC时write只是将内容写入底层缓冲区即可返回,然后底层(操作系统中负责实现open、write这些操作的那些代码,也包含OS中读写硬盘等底层硬件的代码)在合适的时候会将buf中的内容一次性的同步到硬盘中。这种设计是为了提升硬件操作的性能和销量,提升硬件寿命;但是有时候我们希望硬件不好等待,直接将我们的内容写入硬盘中,这时候就可以用O_SYNC标志。
REF:
https://www.cnblogs.com/suzhou/p/5381738.html
(2) creat()函数
creat()函数的作用是创建新文件,这个基本已经弃用,此函数是为了兼容早期UNIX系统的open()函数。
(3) close()函数
#include <unistd.h>
int close(int fd);
关闭文件描述符fd指向的动态文件,并存储文件和刷新缓存。
文件关闭成功返回0,有错误返回-1;
3.2 读写文件
(1) read函数
读取文件内容,并返回实际读取到的字节数
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count)
- fd: 读取的文件的文件描述符
- buf: 暂存区(暂时存储读取到的文件内容)
- count: 要读取的字节数
返回值是零并不代表读取失败,是读到末尾
读取失败是-1
(2) write函数
向文件中写入一定的内容,并返回实际写入文件的字节数
#include <unistd.h>
ssize_t write(int fd, void *buf, size_t count)
- fd: 写入的文件的文件描述符
- buf: 暂存区(将该暂存区的内容写入到文件)
- count: 需要写入的字节数
3.3 文件定位
- 在动态文件中,我们会通过文件指针来表征这个正在操作的位置。所谓文件指针,就是我们文件管理表(打开文件表)这个结构体里面的一个指针。所以文件指针其实是vnode中的一个元素。这个指针表示当前我们正在操作文件流的哪个位置。这个指针不能被直接访问,linux系统用lseek函数来访问这个文件指针。
- 当我们打开一个空文件时,默认情况下文件指针指向文件流的开始。所以这时候去write时写入就是从文件开头开始的。write和read函数本身自带移动文件指针的功能,所以当我write了n个字节后,文件指针会自动向后移动n位。如果需要人为的随意更改文件指针,那就只能通过lseek函数了
- read和write函数都是从当前文件指针处开始操作的,所以当我们用lseek显式的将文件指针移动后,那么再去read/write时就是从移动过后的位置开始的。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence)
修改文件的读写位置,并返回当前文件指针所在的位置
- fd: 写入的文件的文件描述符
- offset: 相对于第三个参数whence的偏移量
- whence:
whence description SEEK_SET 文件开始位置 SEEK_CUR 文件当前指针位置 SEEK_END 文件结束位置
- 返回值:
成功:文件指针相对开始位置的偏移量(bytes)
失败:-1
综合示例:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
int fd = -1; // fd 就是file descriptor,文件描述符
char buf[100] = {0};
char writebuf[20] = "l love linux";
int ret = -1;
// 第一步:打开文件
fd = open("c.txt", O_RDWR|O_CREAT);//
if (-1 == fd) // 有时候也写成: (fd < 0)
{
perror("文件打开错误");
_exit(-1);
}
else
{
printf("文件打开成功,fd = %d.\n", fd);
}
// 写文件
ret = write(fd, writebuf, strlen(writebuf));
if (ret < 0)
{
perror("write失败");
_exit(-1);
}
else
{
printf("write成功,写入了%d个字符\n", ret);
}
//移动文件指针到开头,因为经过写入之后,文件指针是在末尾,直接读的话是读不到内容的
ret = lseek(fd, 0, SEEK_SET);//
// 读文件
ret = read(fd, buf, 5);
if (ret < 0)
{
perror("read失败");
_exit(-1);
}
else
{
printf("实际读取了%d字节.\n", ret);
printf("文件内容是:[%s].\n", buf);
}
//关闭文件
close(fd);
return 0;
}
REF:
https://www.cnblogs.com/Jimmy1988/p/7488709.html
https://blog.51cto.com/9291927/1796527