回顾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));//类比write
if (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
数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
用库函数而不用系统调用的原因
系统调用:代码不具备跨平台性