不带缓冲是指直接使用系统调用,在内核与调用者之间没有缓冲,但是在内核与设备之间还是有缓冲高速缓存或页面高速缓存存在的。这与使用标准IO函数不同,在使用标准IO函数时,在调用者与内核之间还存在缓冲。
内核为打开的文件分配一个文件标识符来表示这个打开的文件。用户后续对这个文件的操作不用再通过文件路径名,可以直接使用文件标识符。文件标识符是一个非负整数,而且在打开时总是分配一个最小可用的整数值,关闭时将文件描述符回收。每个进程维护一张文件描述符表,文件描述符表的每一个表项包括打开的文件描述符,对应的文件描述符标志以及指向的文件表指针。这个文件表是由内核维护的,内容包括文件状态标志,当前文件偏移,以及inode指针。inode则存储在硬盘上。同一个进程多次打开或不同的进程同时打开同一个文件,内核会为每一次打开分配一个独立的文件表,所以每次打开都有自己独立的当前文件偏移值。同一个进程内,可以有多个不同的文件描述符指向同一个文件表,也就是说,这些不同的文件描述符有共同的文件状态标志和当前文件偏移值,但是可以有不同的文件描述符标志。我们可以通过dup、dup2、fcntl函数进行文件描述符复制,使他们指向相同的文件表。
最常用的5个文件操作函数原型:
extern int open (__const char *__file, int __oflag, ...) __nonnull ((1));
extern ssize_t write (int __fd, __const void *__buf, size_t __n) __wur;
extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
extern __off_t lseek (int __fd, __off_t __offset, int __whence) __THROW;
extern int close (int __fd);
open函数说明:
1)O_RDONLY,O_WRONLY,O_RDWR三个flag必须且只能指定一个,规定了对文件的操作权限。比如:如果以O_RDONLY的方式打开文件,则后面无法调用write函数对文件写入内容。其他的flag可以有,也可以没有。
2)如果文件不存在,调用open函数时flag没有设置O_CREATE,open返回出错
3)如果设置了O_CREATE,文件不存在,则会创建一个新的文件,这时可以通过mode设置文件的访问权限,否则根据umask值设置文件权限;如果文件存在,则只会打开文件。
4)如果成功打开或创建文件,当前文件访问偏移设置为0,从文件开始处开始读写文件
5)如果同时指定O_CREATE和O_EXCL,当文件存在时打开失败;当文件不存在时,创建文件。
6)O_EXCL必须与O_CREATE结合使用。如果单独使用O_EXCL,结果不确定。
7)O_APPEND标志在write文件之前总会把文件访问偏移值设为文件末尾,lseek设置不会影响write写的位置。而且设置文件偏移与write是原子操作。
8)O_TRUNC标志总会在打开时将文件内容阶段为0,如果文件不存在,则返回出错。
使用建议:
1)如果确定不创建文件,或确定文件已经存在,则不设置O_CREATE标志。这时如果文件存在,则正常打开;如果文件不存在,则返回出错
2)如果希望文件存在时打开文件,文件不存在时创建文件,则设置O_CREATE标志。这时需要注意文件访问偏移值初始值为文件开头,直接写可能覆盖原文件内容。
3)如果只希望创建一个新的文件,则同时设置O_CREATE和O_EXCL标志。如果文件已经存在,则返回出错。
lseek函数说明:
1)文件都有与当前读写位置相关联的当前文件偏移量,对于普通文件,当前文件偏移量为一个非负数。但是在特殊情况下,也可能是负数。
2)read、write都从当前文件偏移处开始读写。读写玩后当前文件偏移量前移读写的数据长度。
3)可以用lseek(fd, 0, SEEK_CUR)获取当前偏移量,也可以用这个函数测试fd是否是管道、FIFP、套接字等特殊文件,这时lseek返回-1
4)不要用返回值小于0的方式判断lseek是否正确,而要用返回值等于-1来判断出错
open函数的oflag可取一下值:
/* open/fcntl - O_SYNC is only implemented on blocks devices and on files
located on a few file systems. */
#define O_ACCMODE 0003
#define O_RDONLY 00
#define O_WRONLY 01
#define O_RDWR 02
#define O_CREAT 0100 /* not fcntl */
#define O_EXCL 0200 /* not fcntl */
#define O_NOCTTY 0400 /* not fcntl */
#define O_TRUNC 01000 /* not fcntl */
#define O_APPEND 02000
#define O_NONBLOCK 04000
#define O_NDELAY O_NONBLOCK
#define O_SYNC 04010000
#define O_FSYNC O_SYNC
#define O_ASYNC 020000
open函数的可变参数为mode_t,新建文件时需要指定,可取值:
#define S_IRUSR __S_IREAD /* Read by owner. */
#define S_IWUSR __S_IWRITE /* Write by owner. */
#define S_IXUSR __S_IEXEC /* Execute by owner. */
#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
其他的几个文件操作函数:
extern int creat(__const char *__file, __mode_t __mode) __nonnull ((1));
extern ssize_t pwrite (int __fd, __const void *__buf, size_t __n, __off_t __offset) __wur;
extern ssize_t pread (int __fd, void *__buf, size_t __nbytes, __off_t __offset) __wur;
extern int fsync (int __fd);
extern int fdatasync (int __fildes);
extern int dup (int __fd) __THROW __wur;
extern int dup2 (int __fd, int __fd2) __THROW;
extern int fcntl (int __fd, int __cmd, ...);
fcntl函数的cmd可取值:
/* Values for the second argument to `fcntl'. */
#define F_DUPFD 0 /* Duplicate file descriptor. */
#define F_GETFD 1 /* Get file descriptor flags. */
#define F_SETFD 2 /* Set file descriptor flags. */
#define F_GETFL 3 /* Get file status flags. */
#define F_SETFL 4 /* Set file status flags. */
#ifndef __USE_FILE_OFFSET64
# define F_GETLK 5 /* Get record locking info. */
# define F_SETLK 6 /* Set record locking info (non-blocking). */
# define F_SETLKW 7 /* Set record locking info (blocking). */
#else
# define F_GETLK F_GETLK64 /* Get record locking info. */
# define F_SETLK F_SETLK64 /* Set record locking info (non-blocking).*/
# define F_SETLKW F_SETLKW64 /* Set record locking info (blocking). */
#endif
#define F_GETLK64 12 /* Get record locking info. */
#define F_SETLK64 13 /* Set record locking info (non-blocking). */
#define F_SETLKW64 14 /* Set record locking info (blocking). */
#if defined __USE_BSD || defined __USE_UNIX98 || defined __USE_XOPEN2K8
# define F_SETOWN 8 /* Get owner (process receiving SIGIO). */
# define F_GETOWN 9 /* Set owner (process receiving SIGIO). */
#endif
create函数说明:
1)如果文件不存在,创建文件;如果文件存在,打开文件,并清空文件内容。
2)等价于 open(pathname, O_WRONLY | O_CREATE | O_TRUNC, mode)
最常用的5个文件操作函数使用示例程序:
#include "unp.h"
#include "apue.h"
#include
#include
#include
#include
#include
//#include
//#include
int main(int argc, char *argv[])
{
int fd;
int fd2;
char buf[100];
char rd_buf[100];
ssize_t wr_size;
ssize_t rd_size;
off_t curr_pos;
//mode_t f_mode;
#if 0
//用O_CREAT 和O_EXCL打开文件,如果文件已经存在,则回返回错误
//S_IRUSR/S_IWUSR用来设置文件所有者的访问权限,如果不设置,程序默认是没有读写权限的,后面的程序就无法读写文件的内容
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_CREAT | O_EXCL | O_RSYNC | O_DSYNC | O_SYNC, S_IRUSR | S_IWUSR)) < 0) {
err_sys("create file error");
}
#endif
#if 0
//O_TRUNC打开文件时文件必须存在,否则报错
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_TRUNC)) < 0) {
err_sys("open file error");
}
#endif
#if 1
//这种方式先检测文件是否存在,如果不存在,则创建一个新文件,如果存在,则回返回错误
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_CREAT | O_EXCL | O_RSYNC | O_DSYNC | O_SYNC, S_IRUSR | S_IWUSR)) < 0) {
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_TRUNC)) < 0) {
err_sys("open file error");
}
}
#endif
printf("file descriptor: %d\n", fd);
strcpy(buf, "hello file\n");
printf("buf string len: %d\n", strlen(buf));
if( (wr_size = write(fd, buf, strlen(buf))) < 0 ) {
puts("write file error");
}
printf("wr_size: %d\n", wr_size);
//为了读文件内容,需要先关闭文件,然后重新打开文件,否则读不到刚刚写入的内容
if(close(fd) < 0) {
puts("close file error");
}
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR )) < 0) {
err_sys("open file error");
}
//printf("sizeof(rd_buf): %d\n", sizeof(rd_buf));
if( (rd_size = read(fd, rd_buf, sizeof(rd_buf))) < 0) {
puts("read file error");
}
else if(rd_size == 0) {
puts("alread reach file end");
}
printf("wr_size: %d\n", rd_size);
printf("rd_buf string len: %d\n", strlen(rd_buf));
printf("rd_buf string : %s", rd_buf);
close(fd);
//
//测试lseek和O_APPEND
//
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR )) < 0) {
err_sys("open file error");
}
//获取当前的文件偏移量
curr_pos = lseek(fd, 0, SEEK_CUR);
//当前文件偏移量在文件的开始处
printf("fd: %d\n", fd);
printf("open without O_APPEN, fd: %d\n", curr_pos);
if((fd2 = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_APPEND )) < 0) {
err_sys("open file error");
}
//获取当前的文件偏移量
curr_pos = lseek(fd, 0, SEEK_CUR);
//当前的文件偏移量在文件的末尾
printf("fd2: %d\n", fd2);
printf("open with O_APPEN, curr_pos: %d\n", curr_pos);
//将文件偏移量值设为末尾值+5
curr_pos = lseek(fd2, 5, SEEK_END);
printf("open with O_APPEN, curr_pos: %d\n", curr_pos);
//将文件偏移量值设为5
curr_pos = lseek(fd2, 5, SEEK_SET);
printf("open with O_APPEN, curr_pos: %d\n", curr_pos);
//虽然当前文件偏移量为5,但是往文件写数据时,还是写到了文件末尾
wr_size = write(fd2, buf, strlen(buf));
curr_pos = lseek(fd2, 0, SEEK_CUR);
printf("after write, curr_pos: %d\n", curr_pos);
close(fd2);
//重新读文件时,打印结果显示内容确实写到了文件末尾
if( (rd_size = read(fd, rd_buf, sizeof(rd_buf))) < 0) {
puts("read file error");
}
else if(rd_size == 0) {
puts("alread reach file end");
}
printf("rd_size: %d\n", rd_size);
printf("rd_buf string len: %d\n", strlen(rd_buf));
printf("rd_buf string : %s", rd_buf);
close(fd);
exit(0);
}
其它几个文件操作函数示例程序:
#include "unp.h"
#include "apue.h"
#include
#include
#include
#include
#include
//#include
//#include
#if 1
//不带缓冲的文件操作函数
int main(int argc, char *argv[])
{
int fd;
ssize_t rd_size;
ssize_t wr_size;
char wr_buf[100];
char rd_buf[100];
int fd2;
int fd3;
int fd_flag;
int fl_flag;
//这种方式先检测文件是否存在,如果不存在,则创建一个新文件,如果存在,则打开它
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_CREAT | O_EXCL | O_RSYNC | O_DSYNC | O_SYNC, S_IRUSR | S_IWUSR)) < 0) {
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR )) < 0) {
err_sys("open file error");
}
}
//原子操作写文件,相当与lseek+write
memcpy(wr_buf, "hello filexxxxxx\n", strlen("hello filexxxxxx\n")+1);
if( (wr_size = pwrite(fd, wr_buf, strlen(wr_buf), 5)) < 0 ) {
puts("pwrite error");
}
/*
fsync(fd);
fdatasync(fd);
*/
//原子操作读文件,相当与lseek+read
if((rd_size = pread(fd, rd_buf, 40, 0)) < 0) {
puts("pread error");
}
else if(rd_size == 0) {
puts("alread reach file end");
}
else {
printf("rd_size: %d\n", rd_size);
printf("read string: %s", rd_buf);
}
close(fd);
// /dev/fd/1相当与标准输出,打开/dev/fd/1后,向fd写数据,相当于向标准输出输出数据
// /dev/fd/0: 标准输入;/dev/fd/1:标准输出;/dev/fd/2:标准出错
if((fd = open("/dev/fd/1",O_WRONLY )) < 0) {
err_sys("open /dev/fd/1 error");
}
memcpy(wr_buf, "hello filexxxxxx\n", strlen("hello filexxxxxx\n")+1);
if( (wr_size = write(fd, wr_buf, strlen(wr_buf))) < 0 ) {
puts("write /dev/fd/1 error");
}
//复制文件描述符,内核为fd2分配一个新的描述符值,但fd2的文件指针指向的文件表是
//文件描述符fd的文件指针指向的文件表,所以向fd2写,就是向标准输出写
fd2 = dup(fd);
printf("fd2: %d\n", fd2);
memcpy(wr_buf, "hello fileyyy\n", strlen("hello fileyyy\n")+1);
if( (wr_size = write(fd2, wr_buf, strlen(wr_buf))) < 0 ) {
puts("write fd2 error");
}
//内核定义了STDIN_FILENO,STDOUT_FILENO,STDERR_FILENO三个常量,分别表示标准输入/标准输出/标准出错的文件标识符
//向文件描述符STDOUT_FILENO写,就是向标准输出写
memcpy(wr_buf, "hello file000\n", strlen("hello file000\n")+1);
if( (wr_size = write(STDOUT_FILENO, wr_buf, strlen(wr_buf))) < 0 ) {
puts("write STDOUT_FILENO error");
}
//指定文件描述符的描述符复制,如果fd3=fd2,回先close fd2
fd3 = fd2 + 1;
//fd3 = fd2;
fd3 = dup2(STDOUT_FILENO, fd3);
printf("fd3: %d\n", fd3);
memcpy(wr_buf, "hello file001\n", strlen("hello file001\n")+1);
if( (wr_size = write(fd3, wr_buf, strlen(wr_buf))) < 0 ) {
puts("write fd3 error");
}
close(fd);
close(fd2);
close(fd3);
//
//fcntl测试
//
//这种方式先检测文件是否存在,如果不存在,则创建一个新文件,如果存在,则打开它
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR | O_CREAT | O_EXCL | O_RSYNC | O_DSYNC | O_SYNC, S_IRUSR | S_IWUSR)) < 0) {
if((fd = open("/home/yang/exam_file/exam_file_op.dat",O_RDWR )) < 0) {
err_sys("open file error");
}
}
//相当于dup
fd2 = fcntl(fd, F_DUPFD, 0);
printf("fd2: %d\n", fd2);
//相当于dup2,不同的是,如果fd3已经打开,会先close fd3,然后打开fd3,这时dup2是原子操作,但fcntl不是原子操作
//fd3 = fd2 + 1;
fd3 = fcntl(fd, F_DUPFD, (fd2 + 1));
printf("fd3: %d\n", fd3);
//get 文件描述符标志
fd_flag = fcntl(fd, F_GETFD);
printf("fd_flag: %d\n", fd_flag);
//set 文件描述符标志为FD_CLOEXEC
fd_flag = fcntl(fd, F_SETFD, FD_CLOEXEC);
printf("set fd_flag: %d\n", fd_flag);
//获取文件表标志值
fl_flag = fcntl(fd, F_GETFL);
switch(fl_flag & O_ACCMODE)
{
case(O_RDONLY):
puts("access mode is read only");
break;
case(O_WRONLY):
puts("access mode is write only");
break;
case(O_RDWR):
puts("access mode is write only");
break;
default:
puts("access mode is error");
}
if(fl_flag & O_APPEND) {
puts("O_APPEND is set");
}
else {
puts("O_APPEND is not set");
}
//以下代码显示没有设置O_APPEND时,可以从指定位置写数据
//原子操作写文件,相当与lseek+write
memcpy(wr_buf, "hello fileO_APP\n", strlen("hello fileO_APP\n")+1);
if( (wr_size = pwrite(fd, wr_buf, strlen(wr_buf), 5)) < 0 ) {
puts("pwrite error");
}
//原子操作读文件,相当与lseek+read
if((rd_size = pread(fd, rd_buf, 40, 0)) < 0) {
puts("pread error");
}
else if(rd_size == 0) {
puts("alread reach file end");
}
else {
printf("rd_size: %d\n", rd_size);
printf("read string: %s", rd_buf);
}
//为文件状态标志设置O_APPEND
fl_flag |= O_APPEND;
fl_flag = fcntl(fd, F_SETFL, fl_flag);
//以下代码显示,设置O_APPEND后,只能从文件末尾写数据
//原子操作写文件,相当与lseek+write
memcpy(wr_buf, "hello fileO_APP\n", strlen("hello fileO_APP\n")+1);
if( (wr_size = pwrite(fd, wr_buf, strlen(wr_buf), 5)) < 0 ) {
puts("pwrite error");
}
//原子操作读文件,相当与lseek+read
if((rd_size = pread(fd, rd_buf, 40, 0)) < 0) {
puts("pread error");
}
else if(rd_size == 0) {
puts("alread reach file end");
}
else {
printf("rd_size: %d\n", rd_size);
printf("read string: %s", rd_buf);
}
close(fd);
exit(0);
}
#endif