基础 IO 详解


持续更新中
持续更新中
持续更新中

一、接口介绍

以下仅介绍常用部分,更详细的,见 man 2 手册

1、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:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags
		O_RDONLY:只读打开
 		O_WRONLY:只写打开
 		O_RDWR:读写打开
 		O_CREAT:
 			若文件不存在,先创建(通过参数 mode 设置文件的访问权限,例如 0x664)再打开,需要注意的是最终文件的访问权限是 mode & ~umask,其中,umask 值可通过 umask() 进行设置,
 			若文件存在,分两种情况:
 			 1、配合 O_EXCL,打开文件报错 File exists
 			 2、没有 O_EXCL,直接打开文件		 
 		O_APPEND:追加写,每次调用 write() 写数据前,文件偏移量都会被定位到文件末尾,效果等同于调用了 lseek(),而且文件偏移量的调整和写入操作会作为一个原子步骤执行
		O_TRUNC:若文件已存在且为普通文件,并且允许写入(即参数 flags 包含 O_RDWR 或 O_WRONLY),则该文件长度会被截断为 0
        	注意,O_RDONLY | O_TRUNC 是未定义的,测试 Linux(3.10.0-1160.el7.x86_64) 会被截断
 		O_CLOEXEC(Since Linux 2.6.23):替代 open + fcntl(fd, F_SETFD, FD_CLOEXEC),保证 “创建 fd + 设置 FD_CLOEXEC” 为原子操作
 		O_LARGEFILE:64 位的 Linux 用不到
返回值:
	成功返回文件描述符
	失败返回 -1,并设置错误码

2、write

功能:往文件写数据

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
输入型参数:
	fd:open() 返回值
	buf:数据缓冲区
	count:写入的字节数
返回值:
	成功返回写入的字节数
	失败返回 -1,并设置错误码

注意:
	1、如果文件是以 O_APPEND 标志打开的,则在执行写入操作前,文件偏移量会先被设置到文件末尾,而且文件偏移量的调整和写入操作会作为一个原子步骤执行
	2、POSIX 标准要求如果 read() 发生在 write() 返回之后,则该 read() 应读取到最新写入的数据。但并非所有的文件系统都遵守 POSIX 标准
	3write() 成功返回并不保证数据已写入磁盘,要确保数据安全写入,唯一的方法是在写完所有数据后调用 fsync()

3、read

功能:从文件读数据

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
输入型参数:
	fd:open() 返回值
	buf:数据缓冲区
	count:读出的字节数
返回值:
	成功返回读出的字节数,返回 0 表示已到达文件末尾
	失败返回 -1,并设置错误码

4、close

功能:关闭文件描述符

#include <unistd.h>
int close(int fd);
输入型参数:
	fd:open() 返回值
返回值:
	成功返回 0
	失败返回 -1,并设置错误码
注意:
	close() 成功返回也不保证数据已写入磁盘,要确保数据安全写入,唯一的方法是在写完所有数据后调用 fsync()

二、文件描述符

在这里插入图片描述
当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了 file 结构体,表示一个已经打开的文件对象

每个进程都有一个 files 指针,指向一张 files_struct 表,该表最重要的部分是 fd_array 指针数组,其中的每个元素都是一个指向打开文件的指针,所以文件描述符本质上就是该数组的下标,只要拿着文件描述符就可以找到对应的文件

注意

Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入(0)、标准输出(1) 和标准错误(2),分别对应的物理设备一般是键盘、显示器和显示器,所以输入/出还可以采用如下方式:

#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main()
{
  char buf[1024];
  int  rlen;

  rlen = read(0, buf, sizeof(buf));
  if(rlen > 0)
  {
    buf[rlen] = 0;
    write(1, buf, strlen(buf));
    write(2, buf, strlen(buf));
  }

  return 0;
}

三、文件描述符的分配规则

从 files_struct 数组中选取当前未被使用的最小下标作为新的文件描述符

四、重定向

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd;

  close(1);

  fd = open("./myfile", O_WRONLY | O_CREAT, 0x644);
  if(fd < 0)
  {
    perror("open");
    return 1;
  }

  printf("fd: %d\n", fd);
  fflush(stdout);
  close(fd);

  return 0;
}

测试发现,本该输出到显示器上的内容,结果输出到了 myfile 文件中,这种现象叫做输出重定向

常见的重定向见下:

  • >:输出重定向,将命令的标准输出重定向到指定文件,若文件存在,直接覆盖文件原内容;若文件不存在,自动创建该文件
    • command > log 2>&1:将标准输出和标准错误都覆盖写入 log 文件
  • >>:输出追加重定向,将命令的标准输出重定向到指定文件,若文件存在,在文件末尾追加新内容;若文件不存在,自动创建该文件
    • command >> file 2>&1:将标准输出和标准错误都追加写入 log 文件
  • <:输入重定向,将命令的标准输入重定向到指定文件,即命令直接从文件中读取数据,而非等待用户键盘输入

重定向本质

在这里插入图片描述
printf 是 C 库中的 IO 函数,一般往 stdout 中输出,而 stdout 是对 fd:1 的二次封装,此时 fd:1 已经指向了 myfile 文件,所以本该输出到显示器上的内容,结果输出到了 myfile 文件中

/usr/include/stdio.h
extern struct _IO_FILE *stdout;   /* Standard output stream.  */

/usr/include/libio.h
struct _IO_FILE {
  ...
  int _fileno;  /* 封装的的文件描述符 */
  ...
};

五、文件系统

详见理解文件系统

六、静态库和动态库

详见静态库(.a)和动态库(.so)详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值