您的位置:首页 > 编程语言

Unix编程:文件I/O操作及文件描述符

2014-08-22 21:59 239 查看
Unix系统中大多数文件I/O需要用到以下五个函数:open,read,write,lseek以及close。这些函数通常被称为不带缓冲的I/O(这些函数都是在内核中执行,它们直接对内核缓存区进行读写)。

文件描述符

对于内核而言,所有打开的文件都通过文件描述符引用,文件描述符是一个非负整数,当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。

open 函数:调用open函数可以打开或创建一个文件

#include <fcntl.h>
int open(const char *pathname, int oflag, .../*mode_t mode*/);
//返回值:若成功则返回文件描述符,若出错则返回-1
/*
pathname:要打开或创建文件的名字
oflags:文件打开的方式
第三个参数仅当open创建新文件时才使用
*/

已打开的文件在内核中,用file结构体表示.

open函数具有处理符号链接(指向一个文件的间接指针,也就是文件的索引的索引)的功能,如果用open打开文件时,如果传递给open函数的路径名指定了一个符号链接,那么open函数跟随链接到达你所指定的文件,若此符号链接所指向的文件并不存在,则open返回出错。

creat 函数:创建一个新文件

#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
//返回值:若成功则返回为只写打开的文件描述符,若出错则返回-1
/*该函数等效于 open(pathname, O_RDWR|O_CREAT|O_TRUNC, mode);
creat 函数存在不足,只能以只写方式打开所创建的文件,现在一般调用open函数来创建*/

close 函数:关闭一个已经打开的文件

#include <unistd.h>
int close(int filedes);
//返回值:若成功则返回0,若出错则返回-1
lseek 函数:

每个打开的文件都有一个与其相关联的“当前文件偏移量”。用以度量从文件开始处计算的字节数。通常,读、写操作都是从当前文件偏移量处开始,并使偏移量增加所读写的字节数。系统默认打开一个文件,偏移量指定为零,除非指定O_APPEND选项

我们可以调用lseek 显式地为一个打开的文件设置其偏移量

#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);
//返回值:若成功则返回新的文件偏移量,若出错则返回-1
/*
参数offset 的解释与参数whence的值有关,偏移量可以为负值,whence的值:
SEEK_SET:距文件开始处offset 个字节
SEEK_CUR:当前值加offset
SEEK_END:文件长度加offset
*/
lseek 仅将当前的文件偏移量记录在内核中,它并不引起任何I/O操作,然后,该偏移量用于下一个读或写操作。

read 函数:从打开文件中读数据

#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);
//返回值:若成功则返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1
/*从filedes关联的文件中读取nbytes的字节数到buf中,然后返回实际读的字节数*/
读操作从文件的当前偏移量处开始,在成功返回之前,该偏移量将增加实际读到的字节数,read 有时实际读到的字节数会少于要求读的字节数。

write 函数:向打开的文件写数据

#include <unistd.h>
ssize_t write(int filedes, const void *buf, size_t nbytes);
//返回值:若成功则返回已写的字节数,若出错则返回-1
/*将buf中nbytes个字节写入到filedes关联的文件中,返回实际写入的字节数*/
对于普通文件,写操作从文件的当前偏移量处开始,成功写之后,该文件偏移量增加实际写的字节数。

上面教科书式的介绍了这几个常用的I/O函数。下面通过几个简单程序来理解这几个函数的应用。

#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>

#define BUFFSIZE 4096

int main(void)
{
int fd;
int n;
char buf[BUFFSIZE];
char data[] = "sunburn";
off_t offset;

if((fd = open("examplefiles", O_RDWR)) == -1)
perror("open error\n");
printf("fd = %d\n", fd);

offset = lseek(fd, 0, SEEK_CUR);
printf("offset = %d\n", offset);

if((n = read(fd, buf, BUFFSIZE)) == -1)
perror("read error\n");

if(write(STDOUT_FILENO, buf, n) != n)//标准输出
perror("write error\n");

offset = lseek(fd, -5, SEEK_CUR);//当前偏移量前移
printf("offset = %d\n", offset);

lseek(fd, 100, SEEK_SET);//设置新的偏移量
write(fd, data, sizeof(data)/sizeof(char));//基于新的偏移量写入数据

close(fd);
return 0;
}
下面是程序验证结果



最终的examplefiles 文件存在空洞

揭秘文件描述符

内核支持的文件描述符数据结构如下图



右侧的表称为 i 节点表,整个系统只有一张。该表可以视为结构体数组,该数组的一个元素对应于一个物理文件。

中间的表称为文件表,整个系统只有一张,该表可以视为结构体数组,一个结构体中有很多字段,其中有3个字段比较重要:

file status flags:用于记录文件被打开时采用的选项,其实记录的就是open 调用中用户指定的第 2个参数
current file offset:用于记录文件的当前读写位置(指针)正是由于此字段的存在,使得一个文件被打开并读取后,下一次读取将从上一次读取的字符后开始读取
v-node ptr:该字段是指针,指向右侧表的一个元素,从而关联了物理文件
右侧的表称为文件描述符表,每个进程有且只有一张,该表可以视为指针数组,数组的元素指向文件表的一个元素,最重要的是数组元素的下标就是文件描述符。

open函数的实际操作就是:新建一个 i 节点表元素,让其对应打开的物理文件,新建一个文件表的元素,根据open的第二个参数设置文件状态标志,将当前文件偏移量置0,将v节点指针指向刚建立的i节点表元素,在文件描述符表中寻找一个上未使用的元素,在该元素中填入一个指针值,让其指向刚建立的文件表元素,该元素下标作为open的返回值,也就是文件描述符。

这样一来,当调用read(write)函数时,根据传入的文件描述符,系统可以找到对应的文件描述符元素,进而找到文件表的元素,找到i节点表元素,从而完成对物理文件的读写。

参考资料:

《Unix 高级环境编程》

《Linux下C语言应用编程》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Unix 基础编程