Unix环境高级编程 读书笔记 第三章 文件IO

概述

对于open、read、write、lseek、close等函数,被称为不带缓冲的I/O。即unbuffered i/O,术语“不带缓冲”指的是每个read或者write都调用内核中的一个系统调用。

文件描述符

  1. 文件描述符是一个非负的整数;
  2. 文件描述符0与进程的标准输入关联;
  3. 文件描述符1与进程的标准输出关联;
  4. 文件描述符2与进程的标准错误关联;
  5. 在unistd.h中定义了文件描述符0、1、2的符号常量:
/* Standard file descriptors.  */
#define	STDIN_FILENO	0	/* Standard input.  */
#define	STDOUT_FILENO	1	/* Standard output.  */
#define	STDERR_FILENO	2	/* Standard error output.  */

函数open与openat

函数的原型为:

#include <fcntl.h>
int open(const char *path, int oflag, .../*mode_t mode*/);
int openat(int fd, const char *path, int oflag, .../*mode_t mode*/);
/*两个函数若成功,返回文件描述符fd,若出错,返回-1*/

函数的参数中,path是要打开或者创建的文件的名字。oflag说明此函数的多个选项。可以是多个常量的“或”运算构成,这些常量在fcntl.h中定义。

/* File access modes for `open' and `fcntl'.  */
#define	O_RDONLY	0	/* Open read-only.  */
#define	O_WRONLY	1	/* Open write-only.  */
#define	O_RDWR		2	/* Open read/write.  */

/* Bits OR'd into the second argument to open.  */
#define	O_CREAT		0x0200	/* Create file if it doesn't exist.  */
#define	O_EXCL		0x0800	/* Fail if file already exists.  */
#define	O_TRUNC		0x0400	/* Truncate file to zero length.  */
#define	O_NOCTTY	0x8000	/* Don't assign a controlling terminal.  */
#define	O_ASYNC		0x0040	/* Send SIGIO to owner when data is ready.  */
#define	O_FSYNC		0x0080	/* Synchronous writes.  */
#define	O_SYNC		O_FSYNC
#ifdef	__USE_MISC
#define	O_SHLOCK	0x0010	/* Open with shared file lock.  */
#define	O_EXLOCK	0x0020	/* Open with shared exclusive lock.  */
#endif
#ifdef __USE_XOPEN2K8
# define O_DIRECTORY	0x00200000	/* Must be a directory.	 */
# define O_NOFOLLOW	0x00000100	/* Do not follow links.	 */
# define O_CLOEXEC	0x00400000      /* Set close_on_exec.  */
#endif
#if defined __USE_POSIX199309 || defined __USE_UNIX98
# define O_DSYNC	0x00010000	/* Synchronize data.  */
# define O_RSYNC	0x00020000	/* Synchronize read operations.	 */
#endif

/* All opens support large file sizes, so there is no flag bit for this.  */
#ifdef __USE_LARGEFILE64
# define O_LARGEFILE	0
#endif

/* File status flags for `open' and `fcntl'.  */
#define	O_APPEND	0x0008	/* Writes append to the file.  */
#define	O_NONBLOCK	0x0004	/* Non-blocking I/O.  */

#ifdef __USE_MISC
# define O_NDELAY	O_NONBLOCK
#endif

通过fd区分了open函数与openat函数:

  1. path参数是绝对路径,则fd参数会被忽略,openat函数等同于open函数;
  2. path参数是相对路径,则fd参数指出了相对路径名在文件系统中的起始地址。fd参数是通过打开相对路径名所在的目录来获取的;
  3. path参数是相对路径,fd参数是AT_FDCWD,则路径名是在当前工作目录中获取。

函数creat

函数原型为:

#include <fcntl.h>
int creat(const char *path, mode_t mode);
/*函数调用成功,则返回打开的文件描述符,若出错,则返回-1*/

此函数的功能也可通过open函数实现,等效于:

open(path, O_WRONLY | O_CREAT | O_TRUNC, mode);

函数close

函数原型:

#include <unistd.h>
int close(int fd);
/*函数调用成功,则返回0,出错返回-1*/

函数lseek

函数原型:

#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*函数调用成功,则返回新的文件偏移量,若出错则返回-1*/
  1. 当前文件偏移量:一个非负整数,用以度量从文件开始处计算的字节数;
  2. 读写操作都是从当前文件偏移量处开始,并使得偏移量增加所读写的字节数;
  3. 当whence参数是SEEK_SET,则将当前文件偏移量设定为距文件开始处offset个字节;
  4. 当whence参数是SEEK_CUR,则将当前文件偏移量设定为当前值加offset,其中offset可正可负;
  5. 当whence参数是SEEK_END,则将当前文件偏移量设定为文件长度加offset,其中offset可正可负;
  6. 可通过以下方式确定打开的文件的当前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
  1. 文件的偏移量可以大于文件的长度,对文件的下一次写,将在文件中产生一个空洞。文件中的空洞并不要求在磁盘上占有存储空间。
  2. 创造空洞文件的范例如下:
#include "apue.h"
#include <fcntl.h>

char buf1[] = "abcdefg";
char buf2[] = "ABCDEFG";

int main(void)
{
	int fd;
	
	if((fd = creat("file.hole", FILE_MODE)) < 0)
		err_sys("creat error");
	
	if(write(fd, buf1, 7) != 7)
		err_sys("buf1 write error");
	
	if(lseek(fd, 100, SEEK_SET) == -1)
		err_sys("lseek error");
	
	if(write(fd, buf2, 7) != 7)
		err_sys("buf2 write error");
	
	exit(0);
}

函数read

函数原型:

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t nbytes);
/*返回读到的字节数,若到文件尾端,返回0,若出错,返回-1*/

存在多种情况导致实际读取到的字节数少于要求读取的字节数。
ssize_t表示带符号的整型,而size_t表示不带符号的整型。

函数write

函数原型:

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);
/*若成功,返回已写的字节数,若出错,返回-1*/

函数的返回值一般与nbytes的值相同,否则表示出错。

文件共享

在书中,打开文件的内核数据结构如下:

在这里插入图片描述

两个独立的进程各自打开同一文件,其内核数据结构的表关系如下:
在这里插入图片描述

  1. 在每完成write操作后,在文件表项中的当前文件偏移量即增加所写入的字节数;
  2. 如果用O_APPEND标志打开一个文件,则相应标志也被设置到文件表项的文件状态标志中。每次对这种具有追加写标志的文件执行写操作时,文件表项中的当前文件偏移量首先会被设置为i节点表项中的文件长度;
  3. 若一个文件用lseek定位到文件当前的尾端,则文件表项中当前文件偏移量被设置为i节点表项中的当前文件长度;
  4. lseek函数只修改文件表项中的当前文件偏移量,不进行任何实际的IO操作;
  5. 可能有多个文件描述符指向同一个文件表项;
  6. 每个进程都有其自己的文件表项,因而也有其自己的文件偏移量。

文件描述符与文件状态标志的区别:
文件描述符只用于一个文件的一个描述符,而文件状态标志应用于指向该给定文件表项的任何进程中的所有描述符。

函数pread与pwrite

函数原型:

#include <unistd.h>
ssize_t pread(int fd, void *buf, size_t nbytes, off_t offset);
/*返回读到的字节数,若到文件尾端,返回0,若出错,返回-1*/

ssize_t pwrite(int fd, const void *buf, size_t nbytes, off_t offset);
/*若成功,返回已写的字节数,若出错,返回-1*/

函数pread与pwrite相当于调用lseek后再调用read或者write,但是pread与pwrite函数具有原子操作的特性。

函数dup和dup2

函数原型:

#include <unistd.h>
int dup(int fd);
int dup2(int fd, int fd2);
/*若成功,返回新的文件描述符,若出错,返回-1*/

这两个函数的作用是复制一个现有的文件描述符。
由dup返回的新文件描述符一定是当前可用的文件描述符中的最小数值。
使用dup2可以用fd2参数指定新的描述符值。
这些函数返回的新的文件描述符与参数fd共享同一个文件表项,如下图:
在这里插入图片描述

函数sync、fsync和fdatasync

函数由来背景:
为了提高系统的运行效率,在向文件写入数据时,内核通常先将数据复制到缓冲区,即设定的缓冲区高速缓存或者页高速缓存,晚些时候才真正的写入磁盘中。这种方式被称为延迟写。
当内核需要重用缓冲区来存放其他磁盘块数据时,内核会把所有延迟写数据块写入磁盘,为了保证磁盘上实际文件系统与缓冲区中的内容一致,Unix系统提供了sync、fsync和fdatasync函数。
函数的原型为:

#include <unistd.h>
int fsync(int fd);
int fdatasync(int fd);
/*若成功,返回0,若出错,返回-1*/
void sync(void);

sync函数只是将所有修改过的块缓冲区排入写队列,然后立即返回,并不等待实际写磁盘操作结束。
fsync函数对文件描述符fd指定的文件起作用,函数在同步更新文件的属性,等待写磁盘操作结束后才返回。
fdatasync函数类似fsync函数,但是只影响文件的数据部分。

函数fcntl

函数fcntl可以改变已经打开文件的属性。函数原型为:

#include <fcntl.h>
int fcntl(int fd, int cmd, ... /*int arg*/);
/*若成功,返回值依赖于cmd,若出错,返回-1*/

函数的功能:

  1. 复制一个已有的描述符,cmd=F_DUPFD或者F_DUPFD_CLOEXEC
  2. 获取或设置文件描述符标志,cmd=F_GETFD或者F_SETFD
  3. 获取或设置文件状态标志,cmd=F_GETFL或者F_SETFL
  4. 获取或设置异步IO所有权,cmd=F_GETOWN或者F_SETOWN
  5. 获取或设置记录锁,cmd=F_GETLK或者F_SETLK或者F_SETLKW

猜你喜欢

转载自blog.csdn.net/jiangzhangha/article/details/85455406