本节所说明的函数经常称之为不带缓冲区的I/O(unbuffered I/O),ANSI C提供的标准I/O库称为高级I/O,通常也称为带缓冲的I/O
文件描述符:对内核而言,所有打开文件都由文件描述符引用(ulimit -a 查看系统资源,如一个进程能打开的文件描述符个数或ulimit -n),文件描述符是一个非负整数。其中有三个默认打开的文件描述符0(STDIN_FILENO)、1(STDOUT_FILENO)、2(STDERR_FILENO)
一个系统可以支持的文件的个数用cat /proc/sys/fs/file-max查看,这其实和内存有关的
1. 打开、关闭函数
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
/*仅当创建新文件时,才使用第三个参数。*/
下面看open函数的参数:
O_RDONLY:只读打开
O_WRONLY:只写打开
O_RDWR:读、写打开
O_APPEND:写追加到文件末尾
O_CREAT:创建文件时需要,选择此项需要说明第三个参数mode
O_EXCL:如果同时指定了O_CREAT,而文件已存在,则出错。
O_TRUNC:如果此文件存在,且只读或只写打开,则将其长度截断为0
O_NONBLOCK:如果pathname指的是一个FIFO,一个块特殊文件或一个字符特殊文件,选择此项为此文件的本次打开操作和后续的I/O操作设置为非阻塞
O_SYNC:使每次write都等到物理I/0操作完成,及将数据从缓冲区同步到磁盘
由open函数返回的文件描述符一定是最小的未用描述符数字。
看一个栗子
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/*常用,似的宏总能以你想的方式工作*/
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(void)
{
umask(0);//避免受到shell的umask影响
int fd;
fd = open("test.txt", O_WRONLY | O_CREAT | O_EXCL, 0666);
if (fd == -1)
ERR_EXIT("open error");
printf("open succ\n");
return 0;
}
关于UNIX,函数出错常常返回一个负值,并会置errno,文件
#include <string.h>
char *strerror(int errnum);//此函数映射为一个出错信息字符串,并返回
#include <stdio.h>
void perror(const char* msg);//输出由msg指向的字符串
对于errno应知道两条:
(1)如果没有出错,则其值不会被一个例程清除;
(2)任何一个函数都不会将errno置为0
int close(int fd);//关闭
当一个进程终止时,内核自动关闭它所打开的文件,因此也有很多程序不close。
2. 文件读写
ssize_t read(int fd, void *buf, size_t count);
/*成功返回读到的字节数,字节数一般小于count,若已到文件尾返回为0,出错返回-1*/
ssize_t write(int fd, const void *buf, size_t count);
/*其返回值通常与参数值count的值相同,否则表示出错。write出错的一个常见原因:磁盘已写满,或超越一个给定进程的文件长度限制*/
看一个栗子,将标准输入复制到标准输出
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define BUFFSIZE 8192
int main(void)
{
int n;
char buf[BUFFSIZE];
while ( (n=read(STDIN_FILENO,buf,BUFFSIZE)) > 0)
{
if (write(STDOUT_FILENO,buf,n) != n)
ERR_EXIT("write error");
if (n < 0)
ERR_EXIT("read error");
}
exit(0);
}
为什么BUFSIZE是8192呢?你可以测试不同的缓存长度,比较程序的循环次数,运行时间等。
注意:读取成功表明读取到缓存区了,但写操作成功不代表已经写到文件中了,仅仅是表示数据已经拷贝到内核缓存区了,不代表已经同步到文件中了
数据到文件的过程如:数据->内核缓存区->磁盘
延迟写:当数据往文件上写时,数据一般先复制到内核缓存区,若内核缓存区未满,则不将其排入到输出队列,而是等待 其写满或当内核需要重新用该缓存以便存放其他磁盘数据时,再将缓存排入到输出队列。延迟写减少了磁盘的读写次数,但是却降低了文件的更新速度,很可能欲写到文件的数据会丢失,因此可以用fsync函数来同步磁盘的内容。比较一下fsync和O_SYNC:当调用fsync时,它更新文件的内容,而对于O_SYNC,每次对文件调用write函数时,则会更新文件的内容。
3. lseek函数
/*成功返回新的文件位移,出错返回-1*/
off_t lseek(int fd, off_t offset, int whence);
/*
offset:位移量
whence如下
SEEK_SET:文件位移量被设置为据文件开始处offset个字节
SEEK_CUR:被设置为其当前值加offset,offset可正可负
SEEK_END:被设置为文件长度加上offset,offset可正可负
*/
off_t currpos;
currpos = lseek(fd,0,SEEK_CUR)
/*测试文件是否可以设置位移量,如果文件描述符引用的是一个管道或FIFO,则lseek返回-1,并将errno设置为EPIPE*/
每个打开的文件都有一个与其相关联的“当前位移量”,它是一个非负整数,用于度量从文件开始处计算的字节数,通常,读写操作都从当前文件位移量开始,并使位移量增加所读或所写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND选项,否则该位移量被设置为0,注意lseek函数只修改文件的当前位移量,没有进行任何I/O操作,文件位移量可以大于文件的当前长度
下面看一个栗子,空洞文件
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/*
do ... while(0)宏定义常用,作用:使用此结构构造的宏定义不会受到大括号,分号等的影响,使得宏总是会按你的期望运行
*/
#define err_exit(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
#define FILE_MODE 0666
char buf1[] = "abcdefghij";
char buf2[] = "ABCDEFGHIJ";
int main(void)
{
int fd;
if ( (fd=creat("file.hole",FILE_MODE)) < 0)
err_exit("creat error");
if (write(fd,buf1,10) != 10)
err_exit("buf1 write error");
/*offset now=10*/
if (lseek(fd,1024*1024,SEEK_SET) == -1)
err_exit("lseek error");
/*offset now=40*/
if (write(fd,buf2,10) != 10)
err_exit("write error");
/*offset now=50*/
exit(0);
}
通过以下三种方式查看
#大小约为1.1M
ll -h file.hole
#大小约为8k
du -h file.hole
#od可查看到文件中的空格
od -c file.hole
一个文件的大小不等于其在磁盘上占有的空间,如空洞文件,还有,可以写几个字节到文件中,可以发现其占用磁盘的就是4k.