标准库IO接口
标准库IO接口:
fopen
fclose
fwrite
fread
fseek
文件流函数:(类型:FILE*
)
stdin
:标准输入stdout
:标准输出stderr
:标准错误输出
fopen
r
:只读r+
:可读可写,每次都在文件首部开始,覆盖式读写。w
:只写,文件不存在则创建,存在则清空内容。
(truncate
:截断,file to zero length
截断点为0
,清空内容再进行写入)w+
:读写,文件不存在则创建,存在则清空内容,覆盖式读写。a
:只写追加,文件不存在则创建,每次写入数据都是写入文件末尾。
因为是只写追加,故不能读取数据,不能使用fseek
回到SEEK_SET
,必须改变方式为a+即可。a+
:可读写追加,文件不存在则创建,读数据的初始位置是在文件起始,写数据一直追加在文件末尾。
其他接口:
fgets
:获取一行数据
printf
:格式化数据,直接打印,即写入标准输出stdout
。
fprintf
:格式化数据,写入指定的文件流指针,给定stdout
就与printf
等效了。
sprintf
:格式化数据,放入一个容器buf
。是一个用于字符串链接的函数。
snprintf
:多了一个size
,格式化时只向buf
中写入固定个数的单位,防止溢出。
sscanf
:把数据字符串按照格式拆解。
perror
:是面向库函数的,库函数中会封装许多系统调用接口,所以一旦出错,不容易确定到底是哪个接口出的问题。
标准库IO
接口操作句柄是:文件流指针FILE*
,文件流指针这个结构体中就包含了文件描述符,当使用标准库接口进行IO
,最终本质是通过文件流指针找到文件描述符,进而对文件进行操作。
fseek
函数原型:fseek(...,...,whence);
其中第3
个参数whence
(偏移量)的选项取值:
SEEK_SET
: 文件起始位置SEEK_CUR
: 当前位置SEEK_END
: 文件末尾位置
系统IO接口
系统调用接口:【open
、write
、read
、lseek
、close
】
open
- 所属头文件
< fcntl.h >
- 函数定义:
int open(const char *pathname,int flags,mode_t mode);
-
pathname
:文件路径名 -
flags
:选项标志 (中间通过 或 “|
” 间隔)- 必选项(必选其一) 含义 fcntl.h
中宏定义O_RDONLY
只读 0 O_WRONLY
只写 1 O_RDWR
可读可写 2 - 可选项 含义 O_CREAT
文件不存在则创建,存在则打开 O_EXCL
与O_CREAT同用时,若文件存在则报错 O_TRUNC
打开文件同时截断文件长度为0处,打开时清空文件内容 O_APPEND
追加写入 -
mode
:创建文件时给定权限。(八进制数字)
- 所属头文件:
<sys/stat.h>
- 函数定义:
mode_t umask(mode_t mask);
最终创建出文件的权限情况计算方法:
mode & (~umask)
当前设定的权限仅仅针对于调用进程,不针对于操作系统。
- 返回值
返回文件描述符 ,其实就是一个正整数,错误时返回-1
。
open函数用法演示
int fd = open("./temp.txt",O_RDWR | O_CREAT | O_TRUNC,0777);
if(fd < 0){
perror("open error");
return -1;
}
write
- 所属头文件
< unistd.h >
- 函数定义:
ssize_t write(int fd,const void *buf,size_t count);
ssize_t
:有符号长整型(signed size_t
)fd
:打开文件所返回的文件描述符buf
:要向文件写入的数据count
:要写入的数据长度- 返回值:实际的写入字节数,错误返回
-1
write函数用法演示
char buf[1024] = "nihao\n";
int ret = write(f,buf,strlen(buf));
if(ret < 0){
peror("write error");
return -1;
}
read
- 函数定义:
ssize_t read(int fd,void *buf,size_t count);
fd
: 打开文件所返回的文件描述符buf
: 对读取到的数据进行储存的位置,是一个地址count
: 要读取的数据长度- 返回值:实际的读取字节数,错误返回
-1
,如果为0
说明读到了文件末尾。
read函数用法演示
memset(buf,0x00,1024);
ret = read(fd,buf,1023); //只读 1023 个字节是为了不出现乱码,因为上一步在最后一个字节手动置了\0
if(ret < 0){
perror("read error");
return -1;
}
printf("read buf:[%s]\n",bug);
close(fd);
lseek
功能:跳转读写位置
- 函数定义:
off_t lseek(int fd,off_t offset,int whence);
fd
:打开文件所返回的文件描述符offset
:偏移量whence
:偏移位置- 返回值:返回当前位置到文件起始位置的偏移量
close
- 函数定义:
int close(int fd);
如何修改调用进程的文件创建权限掩码?
mode_t umask(mode_t mask);
例如:umask(0)
,可以在创建进程之前设置,这样创建出的进程就和设置的参数保持一致。但还是如之前所说,当前设定的权限仅仅针对于调用进程,不针对于操作系统。
另外的一些系统IO接口:
- ftruncate
:通过文件名将文件长度为指定长度
- unlink
:通过文件名称删除一个文件
- fileno
:通过文件流指针,获取文件描述符
文件描述符 fd
文件描述符和文件流指针的关系:
- 标准库接口使用文件流指针
FILE*
- 系统调用接口使用文件描述符
fd
文件流指针这个结构体中包含文件描述符,文件流指针fp
通过文件描述符fd
来读写数据。
- 通过调用库函数向文件中写入数据时:
1. 库函数调用系统调用函数时,先要得到文件描述符。
2. 用文件流指针fp
中找到_fileno
,即fd
。
3. 进行操作,完成通过系统调用来写读写数据。 - 当使用标准库接口进行
IO
,则最终是通过文件流指针找到文件描述符进而对文件进行操作。
系统已定义的文件流指针
标准输入 | 标准输出 | 标准错误 |
---|---|---|
stdin | stdout | stderr |
0 | 1 | 2 |
头文件中的宏:STDIN_FILENO | STDOUT_FILENO | STDERR_FILENO |
printf
打印数据的时候,如果没有刷新缓冲区,数据并不会被直接写入文件而是先写入缓冲区中。每一个文件都有文件缓冲区,缓冲区的描述信息就在文件流指针中。
缓冲区在文件流指针中的描述信息:
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_ptr;
...
缓冲区实际是文件流指针为每个文件所维护的一个缓冲区,是一个用户态的缓冲区。
内核态
:进程运行在内核态:指的是当前完成功能时操作系统内核完成,操作内核空间。
用户态
:进程运行在用户态:指的是当前操作,操作都是在用户空间完成。
- 文件描述符是什么?
是一个正整型数字,实际上是一个数组下标。
【文件流指针_IO_FILE
结构体中的fileno
就是文件描述符】
- 为什么可以通过这个数字操作文件?
当进程每打开一个文件:
- 都会先使用
struct file
结构体来描述这个文件。 - 并且将描述信息加载到
struct files_struct
这个结构中的file结构体数组中fd_array[]
(类型为file*
)] - 并向用户返回数组下标作为文件描述符。
- 用户通过文件描述符对文件进行操作,但内核实际上是通过文件描述符找到文件描述信息,进而操作文件。
- 注:
一个进程运行之后,默认打开3
个文件:标准输入,标准输出,标准错误,所以要从下标为3
开始分配!
struct files_struct
结构体也是PCB
中的描述信息。
描述符个数有限,不能打开了不关闭,会引起资源泄露。
文件描述符分配规则:最小未使用原则
演示:
int main(){
close(1); //关闭标准输出
int fd = open("./env.c",O_RDWR);
printf("%d\n",fd);
fflush(stdout);
close(fd);
return 0;
}
printf
函数通过stdout
,也就是下标为1
的标准输出文件流指针来找文件描述符,此时原1
号标准输出已经关闭,所以目前最小空闲为1
号,所以env.c
就占据了1
号下标,内容写入了这个文件。这也就是重定向的原理,修改数据流向。
所以:结果无法显示,因为标准输出关闭了。
但是env.c
中写入了一个1
,说明将空闲的最小下标写入了这个文件。
dup2()
- 函数定义:
int dup2(int oldfd,int newfd);
针对文件描述符的重定向函数。
- 功能:将
newfd
文件描述符重定向到oldfd
所在的文件。
若newfd
本身已有打开文件,重定向时则关闭已打开文件。
如dup2(fd,1);
让1
也指向了fd
所指向的文件。
演示
int main(){
int fd = open("./env.c",O_RDWR);
dup2(fd,1); //将1号下标描述符中内容改变成为fd下标中的描述信息,即1号也指向了fd。向fd中写入,1也会被写入
fflush(stdout);
close(fd);
return 0;
}
效果:
1->stdout 3(fd)->a.txt
↓
1->a,txt 3->a.txt
对minishell完善
重定向
功能,其流程为:
- 接受标准输入数据
- 解析命令,判断是否包含重定向符号
- 如果包含,则认为需要输出重定向,这时获取重定向符号后的文件名(将
>
截断),将重定向符号替换成\0
- 在子进程中打开文件,将标准输出重定向到这个文件,进行程序替换