深入理解计算机系统(九) ——系统级I/O / Unix下的文件操作

本文深入解析Unix系统下的文件操作,包括打开文件的多种标志如O_RDONLY、O_WRONLY、O_RDWR等,以及文件的读写函数read和write的使用。还介绍了获取文件元数据的stat函数,以及文件的共享机制和标准I/O库的功能。内容涵盖文件描述符、文件状态、权限控制和I/O操作的细节。
摘要由CSDN通过智能技术生成

参考书籍《深入理解计算机系统》


Unix下所有的I/O设备,例如网络、磁盘、终端等都被模型化为文件,所有的输入和输出都被当做对相应文件的读和写来执行。这种架构使得所有的输入和输出都能以一种统一且一致的方式执行。

1 打开和关闭文件

  • 打开文件
//在linux终端通过 man 2 open 查看详情
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

参数:
pathname:需要打开的文件的路径

flags:以按位或的方式进行状态组合,必须包含一个访问模式
1.访问模式标志:
  O_RDONLY:只读
  O_WRONLY:只写
  O_RDWR:可读可写
2.文件创建标志:影响文件打开时的操作
  O_CLOEXEC:启动FD_CLOEXEC标志,避免额外调用fcntl的F_SETFD操作设置FD_CLOEXEC标志。FD_CLOEXEC是在进程执行exec系统调用时关闭此打开的文件描述符,防止父进程泄露打开的文件给子进程。原子操作;
  O_CREAT:如果文件不存在,则创建一个常规文件;
  O_DIRECTORY:如果目标路径不是目录,则打开失败,可以用来测试目标路径是不是目录;
  O_EXCL:确保此次调用会创建新文件,如果存在 O_CREAT 标志,并且目标路径文件已存在,则打开失败,错误为EEXIST;如果文件存在且正在被系统使用,则失败,错误为EBUSY;
  O_NOCTTY:防止打开文件称为程序控制终端;
  O_NOFOLLOW:如果目标路径是一个链接,则打开失败,错误为ELOOP;
  O_TMPFILE:创建一个无名的普通文件,此无名节点不会在目录系统中被创建,除非给这个文件赋予了名字,否则所有写入此文件的输入,都将在此文件关闭后丢失;
  O_TRUNC:如果文件已存在,且是普通文件,则文件内容将被清空(length为0),如果是FIFO或终端,此标志将被忽略;此外其他文件的操作不可预料;
3.文件状态标志:影响后续I/O输入输出操作
  O_APPEND:以附加模式打开文件,默认输入会追加在文件结尾,如果同时有多个进程在附加数据,可能会导致NFS文件系统下的文件损坏;
  O_ASYNC:使用信号驱动I/O,当此文件描述符能够输入/输出时生成一个信号(默认是SIGIO,可以通过fcntl()函数修改信号),此标准只适用于终端(terminals)、伪终端(pseudoterminals)、套接字(sockets)、管道(pipes)和FIFO;
  O_DIRECT:尽量减少缓存影响,这会降低一部分性能;
  O_DSYNC:写操作会同步等待I/O操作完成,但是如果写操作不影响读取刚写入的数据,则不等待文件属性更新;
  O_LARGEFILE:允许大小超过off_t(4字节)但是小于0ff64_t的文件被打开;
  O_NOATIME:不更新文件记录(访问时间等),能提升I/O效率,多用于只读文件;
  O_NONBLOCK / O_NDELAY:采用非阻塞模式打开文件,后续文件描述符的任何操作都不会导致程序阻塞;
  O_PATH:并未真正打开文件,只提供了文件描述符,后续能执行一些描述符层面的操作;
  O_SYNC:写操作会同步等待I/O操作完成,包括write引起的文件属性的更新;

mode:只在开启 O_CREAT 或者 O_TMPFILE 标志时有用,其他时候将被忽略
  S_IRWXU:00700 用户有读、写和驱动权限
  S_IRUSR:00400 用户有读权限
  S_IWUSR:00200 用户有写权限
  S_IXUSR:00100 用户有驱动权限
  S_IRWXG:00070 同组用户有读、写和驱动权限
  S_IRGRP:00040 同组用户有读权限
  S_IWGRP:00020 同组用户有写权限
  S_IXGRP:00010 同组用户有驱动权限
  S_IRWXO:00007 其他用户有读、写和驱动权限
  S_IROTH:00004 其他用户有读权限
  S_IWOTH:00002 其他用户有写权限
  S_IXOTH:00001 其他用户有驱动权限

返回值:
成功则返回一个小的非负的正整数,用来表示打开的文件(称之为文件描述符),返回的文件描述符是在进程中当前没有打开的最小描述符;
失败则返回-1
  • 关闭文件
    注意:文件描述符被成功关闭,并不等价于数据已经被成功写入到磁盘中,内核通常会用一块缓存来推迟写入磁盘。你可以调用fsync()确保数据已经写入磁盘。
#include <unitstd.h>

功能:关闭一个文件描述符,并删除其记录锁(record locks)
原型:
int close (int __fd);

参数:需要被关闭的文件描述符
返回值:成功返回0;失败返回-1,同时失败原因将设置到errno

常见错误:定义于<errno-base.h><errno.h>
#define	EINTR		 4	//close调用被信号中断
#define	EIO			 5	//IO错误
#define	EBADF		 9	//描述符(__fd)不是一个不是打开并且有效的状态
#define	ENOSPC		28	//设备空间不足

2 读和写文件

#include <unitstd.h>

功能:从描述符(__fd)的当前文件位置,拷贝至多__nbytes个字节到__buf指定位置
原型:
ssize_t read (int __fd, void *__buf, size_t __nbytes);

返回值:成功返回读到的字节数;失败则返回-1,同时失败原因将设置到errno

常见错误:定义于<errno-base.h><errno.h>
#define	EINTR		 4	//close调用被信号中断
#define	EIO			 5	//IO错误
#define	EBADF		 9	//描述符(__fd)不是一个不是打开并且有效的状态
#define	EAGAIN		11	//再试一次,同EWOULDBLOCK,常见于套接字(socket),文件描述符被设置为非阻塞(O_NONBLOCK),通常代表此次读取没有数据
#define	EFAULT		14	//__buf为不可访问地址
#define	EISDIR		21	//描述符对象是一个目录
#define	EINVAL		22	//无效参数,如:描述符不是一个可以读的对象、__buf是特殊地址等
#include <unitstd.h>

功能:从__buf指定地址的赋值__n个字节到__fd描述的对象中去
原型:
ssize_t write (int __fd, const void *__buf, size_t __n);

返回值:成功返回写入的字节数;失败则返回-1,同时失败原因将设置到errno

常见错误:定义于<errno-base.h><errno.h>
#define	EPERM		 1	//操作不被允许
#define	EINTR		 4	//close调用被信号中断
#define	EIO			 5	//IO错误
#define	EBADF		 9	//描述符(__fd)不是一个不是打开并且有效的状态
#define	EAGAIN		11	//再试一次,同EWOULDBLOCK,常见于套接字(socket),文件描述符被设置为非阻塞(O_NONBLOCK)
#define	EFAULT		14	//__buf为不可访问地址
#define	EINVAL		22	//无效参数,如:描述符不是一个可以读的对象、__buf是特殊地址等
#define	EFBIG		27	//要写入的字节太大了
#define	ENOSPC		28	//设备空间不足
#define	EDESTADDRREQ	89	//描述符(__fd)指向尚未connect设置通信地址的datagram套接字
#define	EDQUOT		122	//磁盘块配额已用完
#define	EPIPE		32	//被关闭的管道(pipe)

3 读取文件信息(元数据)

应用程序能通过以下接口,检索到文件的信息,包括文件的字节数大小等。

#include <unistd.h>
#include <sys/stat.h>

函数原型:
int stat (const char *__restrict __file, struct stat *__restrict __buf);
int fstat (int __fd, struct stat *__buf);

stat定义(x86_64为例)struct stat
  {
    __dev_t st_dev;		/* Device.  */
    __ino_t st_ino;		/* File serial number.	*/
    __nlink_t st_nlink;		/* Link count.  */
    __mode_t st_mode;		/* File mode.  */
    __uid_t st_uid;		/* User ID of the file's owner.	*/
    __gid_t st_gid;		/* Group ID of the file's group.*/
    int __pad0;
    __dev_t st_rdev;		/* Device number, if device.  */
    __off_t st_size;			/* Size of file, in bytes.  */
    __blksize_t st_blksize;	/* Optimal block size for I/O.  */
    __blkcnt_t st_blocks;		/* Number 512-byte blocks allocated. */
    __time_t st_atime;			/* Time of last access.  */
    __syscall_ulong_t st_atimensec;	/* Nscecs of last access.  */
    __time_t st_mtime;			/* Time of last modification.  */
    __syscall_ulong_t st_mtimensec;	/* Nsecs of last modification.  */
    __time_t st_ctime;			/* Time of last status change.  */
    __syscall_ulong_t st_ctimensec;	/* Nsecs of last status change.  */
    __syscall_slong_t __glibc_reserved[3];
  };

4 共享文件

  • 内核表示打开的文件的方式
    1.描述符:每个进程都有独立的描述符表,其表项由进程打开的文件描述符来索引,每个打开的描述符表项指向文件表中的一个表项;
    2.文件表:打开的文件的集合是由一张文件表来表示的,所有的进程共享这张表,每个文件表的表项组成包括当前文件位置、引用计数、一个指向v-node表中对应表项的指针。关闭一个描述符会减少相应的文件表表项中的引用计数,内核直到其引用计数为零,才会删除这个文件表项;
    3.v-node表:所有进程共享此表,每个表项包含stat结构中的大多数信息。
  • 原理:如下图,多个描述符通过不同的文件表表项来引用同一个文件
    在这里插入图片描述

5 标准I/O

  • 简介
    ANSIC定义了一组高级输入输出函数,称为标准I/O库,为程序员提供了Unix I/O的较高级别接口。标准I/O库将一个打开的文件模型化为一个流,对于程序员而言,一个流就是一个指向类型为FILE结构的指针,每个ANSI C程序开始时都有三个打开的流:stdin、stdout、stderr,分别对应与标准输入、标准输出和标准错误。
  • 接口
#include <stdio.h>
打开文件
FILE *fopen (const char *__restrict __filename, const char *__restrict __modes);
FILE *fdopen (int __fd, const char *__modes);

关闭文件
int fclose (FILE *__stream);

读
size_t fread (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream);
int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...);
int sscanf (const char *__restrict __s, const char *__restrict __format, ...);
char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream);

写
size_t fwrite (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __s);
int fprintf (FILE *__restrict __stream, const char*__restrict __format, ...);
int sprintf (char *__restrict __s, const char *__restrict __format, ...);
int fputs (const char *__restrict __s, FILE *__restrict __stream);

更新用户空间缓存数据
int fflush (FILE *__stream);

搜索流中的某个位置
int fseek (FILE *__stream, long int __off, int __whence);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值