2. 文件I/O系统调用及文件描述符
2.1 文件I/O系统调用
(1)主要函数
函数 |
功能 |
函数 |
功能 |
|
open() |
打开文件 |
read() |
读取文件 |
|
creat() |
创建文件 |
write() |
写入文件 |
|
close() |
关闭文件 |
lseek() |
文件定位 |
|
注意 |
这些不带缓存的函数都是内核提供的系统调用。它们不是ANSI C的组成部分,但是POSIX的组成部分 |
(2)系统调用与C库
(3)文件操作方式
①标准库函数:遵守ISO标准,基于流的I/O,对文件指针(FILE结构体)进行操作。
②系统调用(API方式):兼容POSIX标准,基于文件描述符的I/O,对文件描述符进行操作。
2.2 文件描述符
(1)概念:
①对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,用open或creat返回的文件描述标识该文件,将其作为参数传递给read或write。
②在POSIX应用程序中,整数0、1、2被替换成符号常数STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO。这些常量都定义在头文件<unistd.h>中。
③文件描述符的范围是0-OPEN_MAX。早期的UNIX版采用的上限值是19(允许每个进程打开20个文件)。现在很多系统则将其增加至63,Linux为1024。
(2)文件描述符与文件指针
①标准文件指针:stdin(0)、stdout(1)、stderr(2)
②将文件描述符转为文件指针:FILE* fdopen(int fd, const char* mode)
③将文件指针转为文件描述符:int fileno(FILE* stream);
2.3 文件I/O系统调用
(1)open函数
头文件 |
#include<sys/types.h> //位于/usr/include目录下 #include<sys/stat.h> //文件状态信息,如大小,创建时间,UID等。 #include<fcntl.h> //file control |
函数 |
int open(const char* pathname, int flags); int open(const char* pathname,int flags, mode_t mode); |
返回值 |
若成功为文件描述符,若出错为-1 |
功能 |
打开或创建一个文件 |
参数 |
(1)pathname:要打开或者创建的文件路径 (2)flags:用来说明此函数的多个选择项 ①O_RDONLY 只读 ②O_WRONLY 只写 ③O_RDWR 读写 ④O_APPEND:以追加模式打开文件,每次写时都加到文件的尾端,但在网络文件系统进行操作时却没有保证。 ⑤O_CREAT:如果指定文件不存在,则按照mode参数指定的文件权限来创建文件。 ⑥O_EXCL:如果同时指定了O_CREAT,而文件己经存在,则出错(这可测试一个文件是否存在)。但在网络文件系统进行操作时却没有保证。 ⑦O_DIRECTORY:如果参数pathname不是一个目录,则open出错。 ⑧O_TRUNC:如果此文件存在,而且为只读或只写成功打开,则将其长度截短为0。 ⑨O_NONBLOCK:如果pathname指的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为此文件的本次打开操作和后续的I/O操作设置非阻塞方式。 (3)mode:新建文件的访问根限(如0777),对于open函数而言,仅当创建新文件时才使用第三个参数。 |
(2)creat函数
头文件 |
#include<sys/types.h> //位于/usr/include目录下 #include<sys/stat.h> //文件状态信息,如大小,创建时间,UID等。 #include<fcntl.h> //file control |
函数 |
int creat(const char* pathname, mode_t mode); |
返回值 |
若成功为文件描述符,若出错为-1 |
功能 |
创建一个新文件 |
备注 |
(1)此函数等效于:open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode); (2)creat的一个不足之处是它以只写方式打开所创建的文件。 |
(3)close函数
头文件 |
#include<unistd.h> //unix std文件 |
函数 |
int close(int fd); |
返回值 |
若成功为0,若出错为-1 |
功能 |
关闭一个打开的文件 |
参数 |
fd:己打开的文件描述符; |
备注 |
当一个进程终止时,它所有的打开文件都由内核自动关闭。 |
(4)read函数
头文件 |
#include<unistd.h> |
函数 |
ssize_t read(int fd, void* buf, size_t count); |
返回值 |
读到的字节数,若己到文件尾为0,若出错为-1 |
功能 |
从打开文件中读数据 |
参数 |
(1)fd:读取文件的文件描述符。 (2)buf:存放读取数据的缓存。 (3)count:要求读取一次数据的字节数。 |
备注 |
(1)有多种情况可使实际读到的字节数少于要求读取的字节数。 ①读普通文件时,在读到要求字节数之前己经到达文件尾端。 ②当从终端设备读时,通常一次最多读一行。 ③当从网络读时,网络中的缓存机构可能造成返回值小于所要求读的字节数。 ④某些面向记录的设备,例如磁带,一次最多返回一个记录。 ⑤进程由于信号造成中断。 (2)读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读得的字节数。 |
(5)write函数
头文件 |
#include<unistd.h> |
函数 |
ssize_t write(int fd, void* buf, size_t count); |
返回值 |
成功为己写的字节数,若出错为-1 |
功能 |
向打开文件中写数据 |
参数 |
(1)fd:写入文件的文件描述符。 (2)buf:存放待写数据的缓存。 (3)count:要求写入一次数据的字节数。 |
备注 |
(1)其返回值通常与参数count的值相同,否则表示出错。 (2)write出错的一个常见原因是:磁盘己写满,或者超过了对一个给定进程的文件长度限制。 (3)对于普通文件,写操作从文件的当前位移量开始。如果在打开该文件时,指定了O_APPEND选项时,则在每次写操作之前,将文件位移量设置在当前文件的结尾处。在一次成功写入后,该文件位移量增加实际写的字节数。 |
【编程实验】读写文件及判断文件大小
//io.h
#ifndef __IO_H__ #define __IO_H__ extern void copy(int fdin, int fdout); #endif
//io.c
#include "io.h" #include <unistd.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> //单独编译命令:gcc -o obj/io.o -Iinclude -c src/io.c #define BUFFER_LEN 1024 //与分区文件块大小一致。可以通过 //tune2fs -l /dev/sda1命令查看 void copy(int fdin, int fdout) { char buffer[BUFFER_LEN]; ssize_t size; //保证从文件开始处复制 lseek(fdin, 0L, SEEK_SET); lseek(fout, 0L, SEEK_SET); while((size = read(fdin, buffer, BUFFER_LEN)) > 0){ if(write(fdout, buffer, size) != size) { fprintf(stderr, "write error: %s ", strerror(errno)); exit(1); } } if (size < 0 ) { fprintf(stderr, "read error: %s ",strerror(errno)); exit(1); //return 1; } }
//copy_test.c
#include "io.h" #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> //程序功能:实现文件的复制 //编译命令:gcc -o bin/cp -Iinclude obj/io.o src/copy_test.c int main(int argc, char* argv[]) { if(argc != 3){ fprintf(stderr, "usage: %s srcfile destfile ", argv[0]); exit(1); } int fdin, fdout; //打开一个待读取的文件 fdin = open(argv[1], O_RDONLY); if(fdin < 0) { fprintf(stderr, "open error:%s ", strerror(errno)); exit(1); }else{ printf("open file: %d ", fdin); } //输出源文件的大小(利用lseek系统调用) printf("source file length: %ld ", lseek(fdin, 0L, SEEK_END)); //创建一个新文件,并指定文件的权限为0777 fdout = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0777); if(fdout < 0) { fprintf(stderr, "open error:%s ", strerror(errno)); exit(1); }else{ printf("open file: %d ", fdout); } //利用自定义的copy函数实现文件的拷贝 copy(fdin, fdout); //输出目录文件的大小 printf("destination file length: %ld ", lseek(fdin, 0L, SEEK_END)); close(fdin); close(fdout); return 0; }
(6)lseek函数
头文件 |
#include<sys/types.h> #include<unistd.h> |
函数 |
off_t lseek(int fd, off_t offset, int whence); |
返回值 |
若成功则返回新的文件位移量(绝对偏移量),若出错为-1 |
功能 |
定位一个己打开的文件(会改变文件的当前偏移量) |
参数 |
(1)fd:己打开文件的文件描述符。 (2)offset:位移量。 (3)whence:定位的位置 ①SEEK_SET:将该文件的位移量设置为文件开始处offset个字节处。offset只能为正。 ②SEEK_CUR:将该文件的位移量设置为当前值加offset,offset可正可负。 ③SEEK_END:将该文件的位移量设置为文件长度加offset,offset可正可负。当offset为正时,表示在定位到文件末尾再加offset字节处,其中会出现一些“空洞”,这种文件也叫“空洞”文件。 |
备注 |
(1)lseek也可用来确定所涉及的文件是否可以设置位移量。如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE。 (2)每个打开文件都有一个与其相关联的“当前文件偏移量”。它是一个非负整数,用以度量从文件开始处计算的字节数。通常读、写操作都从当前文件偏移量处开始,并使偏移量增加所读或写的字节娄和。按系统默认,当打开一个文件时,除非指定O_APPEND选项,否则该位置被设置为0。 (3)“空洞文件大小”是含空洞部分的大小 ,但实际占用的物理磁盘块大小为整个文件除去空洞部分以外的内容占用的空间大小) |
【编程实验】创建“空洞”文件
//hole_file.c
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> #include <string.h> //生成一个“空洞”文件(可用“od -c 文件名”命令来查看这个文件!) char* buffer = "0123456789"; int main(int argc, char* argv[]) { if(argc < 2){ fprintf(stderr, "usage: %s [file] ", argv[0]); exit(1); } int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0777); if(fd < 0){ fprintf(stderr, "open error: %s ", strerror(errno)); exit(1); } //计算要写入的字符串所占空间大小 size_t size = strlen(buffer)* sizeof(char); //将buffer中的内容写入文件 if(write(fd, buffer, size) != size){ perror("write error"); exit(1); } //定位到文件尾部的10个字节处(注意,不是文件尾部,而是末尾再跳过10字节) //即当中出现了“空洞” if(lseek(fd, 10L, SEEK_END) < 0){ perror("lseek error"); } //再次写入上述字符串 if(write(fd, buffer, size) != size) { perror("write error"); exit(1); } close(fd); return 0; }