赞
踩
接下来大家介绍 Linux 应用编程中最基础的知识,即文件 I/O(Input、Outout),文件 I/O 指的是对文件的输入/输出操作,说白了就是对文件的读写操作;Linux 下一切皆文件,文件作为 Linux 系统设计思想的核心理念,在 Linux 系统下显得尤为重要,所以对文件的 I/O 操作既是基础也是最重要的部分。本章将向大家介绍 Linux 系统下文件描述符的概念,随后会逐一讲解构成通用 I/O 模型的系统调用,譬如打开文件、关闭文件、从文件中读取数据和向文件中写入数据以及这些系统调用涉及的参数等内容。本章将会讨论如下主题内容。
⚫ 文件描述符的概念;
⚫ 打开文件 open()、关闭文件 close();
⚫ 写文件 write()、读文件 read();
⚫ 文件读写位置偏移量
本章主要介绍文件 IO 操作相关系统调用,一个通用的 IO 模型通常包括打开文件、读写文件、关闭文件这些基本操作,主要涉及到 4 个函数:open()、read()、write()以及 close(),我们先来看一个简单地文件读写示例,应用程序代码如下所示:
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(void) { char buff[1024]; int fd1, fd2; int ret; /* 打开源文件 src_file(只读方式) */ fd1 = open("./src_file", O_RDONLY); if (-1 == fd1) return fd1; /* 打开目标文件 dest_file(只写方式) */ fd2 = open("./dest_file", O_WRONLY); if (-1 == fd2) { ret = fd2; goto out1; } /* 读取源文件 1KB 数据到 buff 中 */ ret = read(fd1, buff, sizeof(buff)); if (-1 == ret) goto out2; /* 将 buff 中的数据写入目标文件 */ ret = write(fd2, buff, sizeof(buff)); if (-1 == ret) goto out2; ret = 0; out2: /* 关闭目标文件 */ close(fd2); out1: /* 关闭源文件 */ close(fd1); return ret; }
这段代码实现的功能:从源文件 src_file 中读取 1KB 数据,然后将其写入到目标文件 dest_file 中(这里假设当前目录下这两个文件都是存在的);在进行读写操作之前,首先调用 open 函数将源文件和目标文件打开,成功打开之后再调用 read 函数从源文件中读取 1KB 数据,然后再调用 write 函数将这 1KB 数据写入到目标文件中,至此,文件读写操作就完成了,读写操作完成之后,最后调用 close 函数关闭源文件和目标文件。
调用 open 函数会有一个返回值,譬如上面示例代码 中的 fd1 和 fd2,这是一个 int 类型的数据,在 open函数执行成功的情况下,会返回一个非负整数,该返回值就是一个文件描述符;对于 Linux 内核而言,所有打开的文件都会通过文件描述符进行索引。
当调用 open 函数打开一个现有文件或创建一个新文件时,内核会向进程返回一个文件描述符,用于指代被打开的文件,所有执行 IO 操作的系统调用都是通过文件描述符来索引到对应的文件,譬如上面示例代码,当调用 read/write 函数进行文件读写时,会将文件描述符传送给 read/write 函数,所以在代码中,fb1 就是源文件 src_file 被打开时所对应的文件描述符,而 fd2 则是目标文件 dest_file 被打开时所对应的文件描述符。
一个进程可以打开多个文件,但打开的文件数是有限制的,在linux中一般默认最大为1024。所以对于一个进程来说,文件描述符是一种有限资源,文件描述符是从 0 开始分配的,譬如说进程中第一个被打开的文件对应的文件描述符是 0、第二个文件是 1、第三个文件是 2、第 4 个文件是 3……以此类推,所以由此可知,文件描述符数字最大值为 1023(0~1023)。每一个被打开的文件在同一个进程中都有一个唯一的文件描述符,不会重复,如果文件被关闭后,它对应的文件描述符将会被释放,那么这个文件描述符将可以再次分配给其它打开的文件。
在 Linux 系统中要操作一个文件,需要先打开该文件,得到文件描述符,然后再对文件进行相应的读写操作(或其他操作),最后在关闭该文件;open 函数用于打开文件,当然除了打开已经存在的文件之外,还可以创建一个新的文件,函数原型如下所示:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
函数参数和返回值含义如下:
**pathname:**字符串类型,用于标识需要打开或创建的文件,可以包含路径(绝对路径或相对路径)信息,譬如:“./src_file”(当前目录下的 src_file 文件)、"/home/dengtao/hello.c"等;如果 pathname 是一个符号链接,会对其进行解引用。
**flags:**调用 open 函数时需要提供的标志,包括文件访问模式标志以及其它文件相关标志,这些标志使用宏定义进行描述,都是常量,open 函数提供了非常多的标志,我们传入 flags 参数时既可以单独使用某一个标志,也可以通过位或运算(|)将多个标志进行组合。具体标志可以百度搜索,由于篇幅有限就不贴出来了。
前面我们说过,flags 参数时既可以单独使用某一个标志,也可以通过位或运算(|)将多个标志进行组合,譬如:
open("./src_file", O_RDONLY) //单独使用某一个标志,以只读打开此文件
open("./src_file", O_RDONLY | O_NOFOLLOW) //多个标志组合,只读打开文件,文件若是符号链接则返回错误
**mode:**此参数用于指定新建文件的访问权限,只有当 flags 参数中包含 O_CREAT 或 O_TMPFILE 标志时才有效(O_TMPFILE 标志用于创建一个临时文件)。mode的权限定义请百度搜索。
open 函数使用示例
(1)使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用只读方式打开:
int fd = open("./app.c", O_RDONLY)
if (-1 == fd)
return fd;
(2)使用 open 函数打开一个已经存在的文件(例如当前目录下的 app.c 文件),使用可读可写方式打开:
int fd = open("./app.c", O_RDWR)
if (-1 == fd)
return fd;
(3)使用 open 函数打开一个指定的文件(譬如/home/dengtao/hello),使用可读可写方式,如果该文件是一个符号链接文件,则不对其进行解引用,直接返回错误:
int fd = open("/home/dengtao/hello", O_RDWR | O_NOFOLLOW);
if (-1 == fd)
return fd;
(4)使用 open 函数打开一个指定的文件(譬如/home/dengtao/hello),如果该文件不存在则创建该文件,创建该文件时,将文件权限设置如下:文件所属者拥有读、写、执行权限;同组用户与其他用户只有读权限。使用可读可写方式打开:
int fd = open("/home/dengtao/hello", O_RDWR | O_CREAT, S_IRWXU | S_IRGRP | S_IROTH);
if (-1 == fd)
return fd;
调用 write 函数可向打开的文件写入数据,其函数原型如下所示:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
函数参数和返回值含义如下:
**fd:**文件描述符。关于文件描述符,前面已经给大家进行了简单地讲解,这里不再重述!我们需要将进
行写操作的文件所对应的文件描述符传递给 write 函数。
**buf:**指定写入数据对应的缓冲区。
**count:**指定写入的字节数。
**返回值:**如果成功将返回写入的字节数
对于普通文件,不管是读操作还是写操作,一个很重要的问题是:从文件的哪个位置开始进行读写操作?也就是 IO 操作所对应的位置偏移量,读写操作都是从文件的当前位置偏移量处开始,当然当前位置偏移量可以通过 lseek 系统调用进行设置,关于此函数后面再讲;默认情况下当前位置偏移量一般是 0,也就是指向了文件起始位置,当调用 read、write 函数读写操作完成之后,当前位置偏移量也会向后移动对应字节数,譬如当前位置偏移
量为 1000 个字节处,调用 write()写入或 read()读取 500 个字节之后,当前位置偏移量将会移动到 1500 个字节处。
调用 read 函数可从打开的文件中读取数据,其函数原型如下所示:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
函数参数和返回值含义如下:
**fd:**文件描述符。与 write 函数的 fd 参数意义相同。
**buf:**指定用于存储读取数据的缓冲区。
**count:**指定需要读取的字节数。
**返回值:**如果读取成功将返回读取到的字节数,实际读取到的字节数可能会小于 count 参数指定的字节数,也有可能会为 0,譬如进行读操作时,当前文件位置偏移量已经到了文件末尾。实际读取到的字节数少于要求读取的字节数,譬如在到达文件末尾之前有 30 个字节数据,而要求读取 100 个字节,则 read 读取成功只能返回 30;而下一次再调用 read 读,它将返回 0(文件末尾)。
可调用 close 函数关闭一个已经打开的文件,其函数原型如下所示:
#include <unistd.h>
int close(int fd);
当我们对文件进行 IO 操作完成之后,后续不再对文件进行操作时,需要将文件关闭。
函数参数和返回值含义如下:
**fd:**文件描述符,需要关闭的文件所对应的文件描述符。
**返回值:**如果成功返回 0
除了使用 close 函数显式关闭文件之外,在 Linux 系统中,当一个进程终止时,内核会自动关闭它打开的所有文件,也就是说在我们的程序中打开了文件,如果程序终止退出时没有关闭打开的文件,那么内核会自动将程序中打开的文件关闭。很多程序都利用了这一功能而不显式地用 close 关闭打开的文件。显式关闭不再需要的文件描述符往往是良好的编程习惯,会使代码在后续修改时更具有可读性,也更可靠,进而言之,文件描述符是有限资源,当不再需要时必须将其释放、归还于系统。
我们先来看看 lseek 函数的原型,如下所示:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
函数参数和返回值含义如下:
**fd:**文件描述符。
**offset:**偏移量,以字节为单位。
**whence:**用于定义参数 offset 偏移量对应的参考值,该参数为下列其中一种(宏定义):
⚫ SEEK_SET:读写偏移量将指向 offset 字节位置处(从文件头部开始算);
⚫ SEEK_CUR:读写偏移量将指向当前位置偏移量 + offset 字节位置处,offset 可以为正、也可以为负,如果是正数表示往后偏移,如果是负数则表示往前偏移;
⚫ SEEK_END:读写偏移量将指向文件末尾 + offset 字节位置处,同样 offset 可以为正、也可以为负,如果是正数表示往后偏移、如果是负数则表示往前偏移。
**返回值:**成功将返回从文件头部开始算起的位置偏移量(字节为单位),也就是当前的读写位置;发生错误将返回-1。
使用示例:
(1)将读写位置移动到文件开头处:
off_t off = lseek(fd, 0, SEEK_SET);
if (-1 == off)
return -1;
(2)将读写位置移动到文件末尾:
off_t off = lseek(fd, 0, SEEK_END);
if (-1 == off)
return -1;
(3)将读写位置移动到偏移文件开头 100 个字节处:
off_t off = lseek(fd, 100, SEEK_SET);
if (-1 == off)
return -1
(4)获取当前读写位置偏移量:
off_t off = lseek(fd, 0, SEEK_CUR);
if (-1 == off)
return -1;
函数执行成功将返回文件当前读写位置。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。