不带缓冲是指直接使用系统调用,在内核与调用者之间没有缓冲,但是在内核与设备之间还是有缓冲高速缓存或页面高速缓存存在的。这与使用标准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