回顾C文件接口
stdin & stdout & stderr
C 默认会打开三个输入输出流,分别是 stdin, stdout, stderr仔细观察发现,这三个流的类型都是 FILE*, fopen 返回值类型,文件指针
系统文件I/O
接口介绍
open
man 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选项,来指明新文件的访问权限
O_APPEND : 追加写O_TRUNC:打开文件时会清空文件内容
返回值:
成功:新打开的文件描述符
失败: - 1
mode_t 理解:直接 man 手册,比什么都清楚。
open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要 open 创建,则第三个参数表示创建文件的默认权限, 否则,使用两个参数的 open 。
hello.c 写文件 :
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);int fd = open("myfile", O_WRONLY | O_CREAT, 0644);if (fd < 0) {perror("open");return 1;}int count = 5;const char* msg = "hello bit!\n";int len = strlen(msg);while (count--) {write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据//默认不会清空文件内容,从头开始写}close(fd);return 0;
}
hello.c 读文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}const char* msg = "hello bit!\n";char buf[1024];while (1) {ssize_t s = read(fd, buf, strlen(msg));//类比writeif (s > 0) {printf("%s", buf);}else {break;}}close(fd);return 0;
}
open函数返回值
在认识返回值之前,先来认识一下两个概念 : 系统调用 和 库函数
上面的 fopen fclose fread fwrite 都是 C 标准库当中的函数,我们称之为库函数( libc )。
而 open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆一下我们讲操作系统概念时,画的一张图
系统调用接口和库函数的关系,一目了然。
所以,可以认为, f# 系列的函数,都是对系统调用的封装,方便二次开发。
文件描述符fd
通过对 open 函数的学习,我们知道了文件描述符就是一个小整数
文件描述符fd的本质是内核的进程中文件映射关系的数组的下标
理解:在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件
0 & 1 & 2
Linux 进程默认情况下会有 3 个缺省打开的文件描述符,分别是标准输入 0 , 标准输出 1, 标准错误 2.
0,1,2 对应的物理设备一般是:键盘,显示器,显示器
所以输入输出还可以采用如下方式:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{char buf[1024];ssize_t s = read(0, buf, sizeof(buf));if (s > 0) {buf[s] = 0;write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}
而现在知道,文件描述符就是从 0 开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file 结构体。表示一个已经打开的文件对象。而进程执行 open 系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表 files_struct, 该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
文件描述符的分配规则
直接看代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}
输出发现是 fd: 3
关闭 0 或者 2 ,在看
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{close(0);//close(2);int fd = open("myfile", O_RDONLY);if (fd < 0) {perror("open");return 1;}printf("fd: %d\n", fd);close(fd);return 0;
}
发现是结果是: fd: 0 或者 (//) fd:2 可见,文件描述符的分配规则:在 files_struct 数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
用库函数而不用系统调用的原因
系统调用:代码不具备跨平台性