底层文件访问
每个运行中的程序被称为进程(process),它有一些与之关联的文件描述符,你可以通过它们访问打开的文件和设备。
当一个程序开始运行时,它一般会有3个已经打开的文件描述符。
- 0:标准输入
- 1:标准输出
- 2:标准错误
write系统调用
将缓冲区buf的前nbytes个字节写入与文件描述符fildes关联的文件中。
原型
size_t write( int fildes, const void *buf, size_t nbytes);
返回(实际写入的字节数)
0 表示未写入任何数据
-1 write调用过程中出现了错误
示例
#include <unistd.h>
#include <stdlib.h>
int main()
{
if ((write(1, "Here is some data\n", 18)) != 18)
write(2, "A write error has occurred on file descriptor 1\n",46);
exit(0);
}
read系统调用
从与文件描述符fildes相关联的文件里读取nbytes个字节的数据,并将其放到数据区buf中。
原型
size_t read( int fildes, void *buf, size_t nbytes);
返回(实际读入的字节数)
0 表示未读入任何数据,到达文件末尾。
-1 read调用过程中出现了错误
示例
#include <unistd.h>
#include <stdlib.h>
int main()
{
char buffer[128];
int nread;
nread = read(0, buffer, 128);
if (nread == -1)
write(2, "A read error has occurred\n", 26);
if ((write(1,buffer,nread)) != nread)
write(2, "A write error has occurred\n",27);
exit(0);
}
运行
使用echo通过管道为程序提供输入
$ echo hello world | ./simple_read
使用文件重定向为程序提供输入
$ ./simple_read < simple_read.c
open系统调用
创建一个新的文件描述符,与对应文件进行关联。
原型
int open( const char *path, int oflags);
int open( const char *path, int oflags, mode_t mode);
返回
如果调用成功,它将返回一个可以被read、write和其他系统调用使用的文件描述符。
如果调用失败,返回-1。
这个文件描述符是唯一的,它不会与任何其他运行的进程共享。如果两个进程同时打开同一个文件,它们会分别得到两个不同的文件描述符。
访问模式oflags
- O_RDONLY 以只读方式打开
- O_WRONLY 以只写方式打开
- O_RDWR 以读写方式打开
close系统调用
终止文件描述符fildes与对应文件之间的关联。
原型
int close( int fildes);
底层系统调用为什么效率非常低
执行系统调用时,Linux必须从运行用户代码切换到执行内核代码,然后再返回用户代码。减少这种开销的一个好方法是,在程序中尽可能减少系统调用的次数,并且让每次系统调用完成尽可能多的工作。例如,每次读写大量的数据而不是每次仅读写一个字符。
文件复制程序(逐字复制)
源码
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
char c;
int in, out;
in = open("file.in", O_RDONLY);
out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
while(read(in,&c,1) == 1)//每次读取一个字节写入一个字节
write(out,&c,1);
exit(0);
}
运行
$ gcc -o copy_system copy_system.c
$ TIMEFORMAT="" time ./copy_system
输出
0.02user 5.21system 0:05.73elapsed 91%CPU (0avgtext+0avgdata 892maxresident)k
0inputs+2048outputs (0major+48minor)pagefaults 0swaps
文件复制程序(块复制)
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
char block[1024];
int in, out;
int nread;
in = open("file.in", O_RDONLY);
out = open("file.out", O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR);
while((nread = read(in,block,sizeof(block))) > 0)
write(out,block,nread);
exit(0);
}
运行
$ gcc -o copy_system copy_block.c
$ TIMEFORMAT="" time ./copy_block
输出
0.00user 0.01system 0:00.03elapsed 32%CPU (0avgtext+0avgdata 884maxresident)k
0inputs+2048outputs (0major+47minor)pagefaults 0swaps
标准I/O库
标准I/O库提供输出缓冲功能,你可以高效地写任意长度的数据块,库函数则在满足数据块长度时安排执行底层系统调用,这就极大降低了系统调用的开销。
标准I/O库(stdio)及其头文件stdio.h为底层I/O系统调动提供了一个通用的接口。
fopen函数
fopen打开filename参数指定的文件,并把它与一个文件流关联起来。
原型
FILE * fopen(const char * path,const char * mode);
返回
成功时,返回一个非空的FILE *指针。
失败时,返回NULL。
fread函数
fread库函数用于从一个文件流里读取数据。数据从文件流stream读到由ptr指向的数据缓冲区里。size参数指定每个数据记录的长度,计数器nitems给出要传输的记录个数。
原型
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
返回
返回值是成功读到数据缓冲区里的记录个数(不是字节数)。当到达文件末尾时,它的返回值可能会小于nitems,甚至可以是零。
fwrite函数
从指定的数据缓冲区ptr里取出数据记录,并把它们写到输出流stream中。
原型
size_t fwrite(const void *ptr, size_t size, size_t nitems, FILE *stream);
fclose函数
关闭指定的文件流stream,使所有尚未写出的数据都写出。因为stdio库会对数据进行缓冲,所有使用fclose是很重要的。
原型
int fclose(FILE *stream);
fflush函数
把文件流里的所有未写出数据立刻写出。注意,调用fclose函数隐含执行了一次fflush操作。
原型
int fflush(FILE *stream);
fgetc函数
fgetc函数从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件末尾或者出现错误时,它返回EOF。你必须通过ferror或feof来区分这两种情况。
原型
int fgetc(FILE *stream);
fputc函数
fgetc函数把一个字符写到一个输出文件流中。它返回写入的值,如果失败,则返回EOF。
原型
int fputc(int c, FILE *stream);