目录
文件操作
对一个文件来说有两种不同的操作方式,既可以使用由操作系统提供的编程接口(API),即系统调用,也可以使用标准C库提供的标准IO函数
在几百个Linux 系统调用中,有一组函数是专门针对文件操作的,比如打开文件、关闭文件、读写文件等,这些系统调用接口就被称为"系统IO",相应地,在几千个标准C库函数中,有一组函数也是专门针对文件操作的,被称为"标准IO",他们是工作在不同层次,但都是为应用程序服务的函数接口。
下面我们来逐一对系统IO函数和标准IO函数中最重要最常用的接口进行详细剖析,理解他们的异同,以便于在程序中恰当地使用他们。
系统IO
我们首先使用几个简单的函数进行举例
打开、关闭
此表确实一个flags参数,O_CREAT,创建文件,如果使用了这个参数则要指定mode的值
使用系统调用open()需要注意的问题有:
1,flags的各种取值可以用位或的方式叠加起来,比如创建文件的时候需要满足这样的选项:读写方式打开,不存在要新建,如果存在了则清空他。那么此时指定的flags的取值应该是:O_RDWR|O_CREAT|O_TRUNC 。
2,mode是八进制权限,比如0644,或者0755等,具体值有表参考。
3,它可以用来打开普通文件、块设备文件、字符设备文件、链接文件和管道文件,但只能用来创建普通文件,每一种特殊文件的创建都有其特定的其他函数。
4,其返回值就是一个代表这个文件的描述符,是一个非负整数。这个整数将作为以后任何系统IO函数对其操作的句柄,或称入口。
close函数只需要提供一个文件描述即可使用关闭biaozhun件
cpp
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd = open("open.txt",O_CREAT|O_TRUNC|O_WRONLY,0644);
printf("%d\n",fd);
close(fd);
return 0;
}
此处结果打印为3,代表第四个文件,012分别代表标准输入、标准输出、标准出错,通常用STDIN_FILNO、STDOUT_FILENO、STDERR_FILENO进行代替
那么,这个所谓的文件描述符究竟是什么玩意儿呢?其实他是一个数组的下标值,在内核中打开的文件是用 file 结构体来表示的,每一个结构体都会有一个指针来指向他们,这些指针被统一存放在一个叫做 fd_array 的数组当中,而这个数组被存放在一个叫做 files_struct 的结构体中,该结构体是进程控制块 task_struct 的重要组成部分。他们的关系如图所示。
上图中task_struct 被称为进程控制块(Process Control Block),是程序运行时在 内核中的实现形式。它里面包含了一个进程在运行时的所有信息,当然就包括了进程在运行过程中所打开文件的信息,这些信息被一个files指针加以统一的管理,files 指针所指向的结构体files_struct{}里面的数组fd_array[]是一个指针数组, 用户空间每一次调用open()都会使得内核实例化一个file{}结构体并将一个指向该结构体的指针依次存放在fd_array[]中,并且该指针所占据的数组下标将作为所谓的"文件描述符(file descriptor)"返回给用户空间的调用者,这就是为什么文件描述符是非负整数的原因。
读写
这两个函数都非常容易理解,需要特别注意的是:
1,实际的读写字节数要通过返回值来判断,参数count只是一个"愿望值"。
2,当实际的读写字节数小于count时,有以下几种情形:
A)读操作时,文件剩余可读字节数不足count
B)读写操作期间,进程收到异步信号。
3,读写操作同时对f_pos起作用。也就是说,不管是读还是写,文件的位置偏移量(即内核中的f_pos)都会加上实际读写的字节数,不断地往后偏移。因此连续的对同一个目标文件进行写操作和读操作会无法读取数据
调整读写位置
针对上面的第三点问题,我们可以使用下面这个函数对普通文件的读写位置进行手动调整
从效果来看,我们不仅可以通过lseek()来调整当前文件偏移量,甚至还可以可以将位置偏移量调整到文件之外,形成一个空洞,这种特性其实是非常重要的,它提供了可以在不同地方同时写一个文件的可能,对于一个较大的文件而言,我们可以通过在文件中定位到一个指定的地方,让多个进程同时在不同的偏移量处写入文件数据。
复制文件描述符
对于dup来说复制的目标作为返回值返回,dup2则可以通过newfd指定复制后的存放描述符,如果这个指定的描述符已经存在则覆盖
复制原有的文件描述符相当于对文件描述符进行一次重定向
多功能函数
ioctl函数中存放了大量的除了读和写以外的功能,但是因为没有统一规范因此被称为"垃圾桶", 而fcntl函数则是对其进行规范化后的函数,因此不到万不得已不要轻易使用ioctl函数
这两个都是变参函数,先来看下 ioctl(),其 request是一个由底层驱动提供的命令字,一些通用的命令字被放置在**头文件/usr/include/asm-generi/ioctls.h(不同的系统存放 位置也许不同)**中,后面的变参也由前面的request命令字决定。比如调整文件为异步工作模式:
cpp
int on =1;
ioctl(fd, FIOASYNC, &on);
上述代码将fd对应的文件的工作模式设置为所谓的异步方式,FIOASUNC就是其中的一个通用的命令字,而后续的变量on则是其所需要的对应的值。这个操作也可以用fcntl()来达到:
cpp
fcntl(fd, F_SETFL, O_ASYNC);
对于fcntl()而言,其第二个参数命令字cmd有很多,具体查看手册及教程,《LINUX环境编程图文指南》P357
内存映射
该函数在进程的虚拟内存空间中映射出一块内存区域,用于对应指定的一个文件,该内存上的数据跟指定文件的数据一一对应,并在一开始使用文件的内容对这块内存进行初始化