【Linux】基础IO(上)
目录
1. C文件库函数接口
以下的函数的库函数,头文件都是:
#include<stdio.h>
1.1 fopen 和 fclose 函数
1.1.1 fopen 函数
功能: 打开文件
函数原型:
FILE *fopen(const char *path, const char *mode);
mode 的选择:
w
:若使用 w 打开文件,则不管有没有向文件中写入东西,该文件都会被清空a
: w/a 都是写入,w清空并从头写,a在文件结尾,追加写!
返回值:
- 成功:如果成功打开文件,则返回指向 FILE 结构体的指针,称为文件指针。该文件指针用于后续对文件进行读、写或关闭等操作。
- 失败:如果打开文件失败,则返回 NULL
1.1.2 fclose 函数
函数功能: 关闭文件
函数原型:
int fclose(FILE * stream);
函数参数:
stream为文件流指针。
返回值:
若关文件动作成功则返回0,有错误发生时则返回EOF,并把错误代码存到errno。
1.2 写文件
1.2.1 fwrite 函数
功能: fwrite 函数是向文件中写入内容的函数。
注意: 不要把字符串的 \0 也写入文件中
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明 :
- const void *ptr : 指针指向要写出数据的内存首地址 ;
- size_t size : 要写出数据的 基本单元 的字节大小 , 写出单位的大小 ;
- size_t nmemb : 要写出数据的 基本单元 的个数 ;
- FILE *stream : 打开的文件指针 ;
返回值说明 : size_t 返回值返回的是实际写出到文件的 基本单元 个数 ;
1.2.2 fprintf 函数
功能: fwrite 函数也是向文件中写入内容的函数。
函数原型:
int printf(const char *format, ...); //其实就是向显示器文件中写入内容
int fprintf(FILE *stream, const char *format, ...);
返回值:
printf、fprintf:返回值为实际写入文件中的字节数。如果写错误, 则返回一个负数
例:
int a=1;
char arr[]="hello liunx";
printf("%d %s",a,arr);
fprintf(stdout,"%d %s",a,arr); //stdout: 显示器文件流
1.2.3 写文件的代码
写文件:先以写的方式打开文件--------“w
”,然后用 fwrite
或 fprintf
函数
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("myfile", "w");
if (!fp) {
printf("fopen error!\n");
}
const char* msg = "hello bit!\n";
int count = 5;
while (count--) {
fwrite(msg, strlen(msg), 1, fp); // 不是 strlen(msg)+1,因为\0不需要被写入文件
//或者fprintf(fp,"%s",msg);
}
fclose(fp);
return 0;
}
1.3 读文件
1.3.1 fread 函数
功能: 将 文件内容 读进特定存储空间
函数原型:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
参数说明:
- ptr:指向存储读取数据的缓冲区的指针。
- size:每个数据单元的大小(以字节为单位)。
- nmemb:要读取的数据单元的数量。
- stream:文件指针,指向要读取的文件。
返回值:
fread() 返回成功读取的数据单元数量。如果返回值小于nmemb,则可能是遇到了文件结尾或发生了读取错误。
1.3.2 读文件的代码
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
int main()
{
FILE* fp = fopen("myfile", "r");
if (!fp) {
printf("fopen error!\n");
}
char buf[1024];
const char* msg = "hello bit!\n";
while (1) {
ssize_t s = fread(buf, 1, strlen(msg), fp); // 意思说一次读strlen(msg)长度的字符
if (s > 0) {
buf[s] = 0;
printf("%s", buf);
}
if (feof(fp)) {
break;
}
}
fclose(fp);
return 0;
}
1.4. stdin & stdout & stderr
-
C默认会打开三个输入输出流,分别是stdin, stdout, stderr
-
仔细观察发现,这三个流的类型都是
FILE*
, 即fopen返回值类型,文件指针 -
stdin: fd–>0,键盘文件
stdout: fd–>1,显示器文件
stderr: fd–>2,显示器文件
2. Liunx系统调用接口
- 文件其实是在磁盘上的,磁盘是外部设备,访问磁盘文件其实是访问硬件!
几乎所有的库只要是访问硬件设备,必定要封装系统调用! 所以几乎所有的库函数,都封装了系统调用函数。
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: 文件打开方式
常用选项是:
O_RDONLY(只读);
O_WRONLY(只写);
O_RDWR(可读写);
O_CREAT:文件不存在时,创建该文件;
O_TRUNC :如果文件存在,则清空文件全部内容(即将其长度截短为0)
O_APPEND:每次写操作都写入文件的末尾,即追加写- mode:
当文件第一次被创建时
,必须
使用mode参数确定文件权限。当不是第一次被创建,不要使用mode参数- mode 其实就是4位的权限编码
返回值:
- 打开成功:返回一个int 型正整数(文件描述符);
- 打开失败:返回 -1
2.2 close 函数
功能: 关闭文件。
函数原型:
#include <unistd.h>
int close(int fd);
函数参数说明:
fd:文件描述符
返回值:
成功返回 0,出错返回 -1 并设置errno
2.3 write 函数
函数功能: 向文件中写入内容
函数原型:
#include <unistd.h>
write(int fd, const void *buf, size_t count);
参数说明:
- fd:文件描述符
- buf:无类型的指针,可以存放要写的内容
- count:要写入的内容的字节数
返回值:
如果顺利write()会返回实际写入的字节数(len)。当有错误发生时则返回-1,错误代码存入errno中
2.4 read 函数
功能: 将 文件内容 读进特定存储空间
函数原型:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数说明:
- ptr:指向存储 读取数据 的缓冲区的指针。
- size:每个数据单元的大小(以字节为单位)。
- count: 表示期望从文件中读取的字节数。read函数会尽量读取count个字节的数据,并存放在buf中
返回值:
- 在非阻塞模式下,数据尚未准备好,则返回错误代码EAGAIN或EWOULDBLOCK。
- 文件指针已到达文件末尾,此时返回0。
- 出现错误,返回-1
3. 访问文件的本质
-
打开文件的步骤:先把将该文件地址插入在 该进程对应的
struct file* fd_array[ ]
中;接着,如果双链表中不存在该文件对应的结构体,则将该结构体尾插在双链表中,如果存在该文件的结构体,则将结构体内的count++
; -
关闭文件的步骤:先在进程对应数组
struct file* fd_array[ ]
内,将文件结构体指针对应的位置 置为NULL;接着,在双链表内将该文件的结构体count--
;如果 count >0,则不将该文件结构体从双链表中删除,反之,则删除并释放该结构体。 -
关于
struct file
:- 作用:在操作系统内,用来描述一个被打开文件的信息
- 直接或者间接包含如下属性:
文件在磁盘的位置
文件基本属性,权限,大小,读写位置,谁打开的
文件的内核缓冲区信息
以及struct file *next
指针和引用计数 count
-
fd 即 文件描述符,就是
struct file* fd_array[ ]
数组的下标,是一个正整数。
3.1 再谈C语言中的 FILE
我们之前提到的C语言文件接口都有 FILE
:
FILE *fopen(const char *path, const char *mode);
int fclose(FILE * stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
和系统调用接口进行比较一下:
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
(注意:我们之前就说过C语言文件接口是由系统调用接口封装而来)
那么 FILE 到底是什么呢?
答:FILE 其实 是C库自己封装的结构体! 并且结构体里面必须封装文件描述符 fd !
4. 重定向
4.1 文件描述符的分配规则
从0下标开始,寻找最小的没有没使用的数组位置,它的下标就是新文件的文件描述符。
4.2 重定向
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}
原理如下:
由于关闭了 fd 为1的文件,所以 数组内下标为1 的位置 置为空了,又因为 调用了 open ,所以新文件使用 1 号位,结果就是输出重定向
4.2.1 使用 dup2 系统调用
函数原型:
#include <unistd.h>
int dup2(int oldfd, int newfd);
参数理解:
是由 oldfd 来替换 newfd。两者都是 fd。
返回值:
若dup2调用成功则返回新的文件描述符 newfd,出错则返回-1。
函数原理:
4.2.2 输入重定向、输出重定向
输入重定向---------------->读文件
、符号为 <
,是 fd =0
的dup2运算
输出重定向---------------->写文件
、符号为 >
、>>
,是 fd =1、fd = 2
的dup2运算
4.3 多文件的输入/输出重定向
- 现象引入
- 依旧是上面的代码,如何将
nomal message
和stderr message
重输出在不同文件?
操作如下即可:
- 依旧是上面的代码,如何将
nomal message
和stderr message
重输出在同一个文件?
操作如下即可:
4.4 自制的第二版shell,添加了重定向
代码放在了gitee中:链接
5. 再次理解 “一切皆文件”
而对于系统函数:read();
和 write();
6. 缓冲区
- 显示器的文件的刷新方案是行刷新,所以在printf执行完就会立即遇到\n的时候。将数据进行刷新用户刷新的本质,就是将数据通过
1+write
写入到内核中 - 联想:
exit(); = fflush(stdout); + _exit();
- 缓冲区刷新方式:
-
无缓冲 — 直接刷新
-
行缓冲 —不刷新,直到碰到\n --------->显示器
-
全缓冲 — 缓冲区满了,才刷新 --------->向普通文件写入
-
- 进程退出时,缓冲区会刷新
- 为什么要有这个缓冲区:
- 解决效率问题 – 用户的效率问题
- 配合格式化
- c语言缓冲区在哪里?c语言缓冲区在 FILE 中(FILE:只是c语言自己组装的结构体)。FILE里面还有对应打开文件的 缓冲区字段和维护信息。 注意是语言级缓冲区,不是系统缓冲区。
- 这个FILE对象属于用户昵? 还是操作系统呢? 答:属于用户,是用户创建的。
c语言缓冲区,是不是属于用户级的缓冲区呢? 答:属于,语言都属于用户层。
- 这个FILE对象属于用户昵? 还是操作系统呢? 答:属于用户,是用户创建的。
现象分析:
问题1:
答:现象1:是因为没有 \n
无法刷新缓冲区,并且在进程结束之前,就关闭了1文件,所以进程结束时,无法将c语言缓冲区刷新的内容 通过write(1);
写进系统缓冲区。现象2:直接通过write(1);写进了系统缓冲区。
问题2:
答:因为重定向到普通文件中,c语言缓冲区刷新方式是全缓冲,不是行缓冲。
问题3:
代码相对于问题2,只多了fork();
答:
c 缓冲区是在FILE里,而FILE是 fopen函数
在堆上申请的。
当父进程结束时,父进程会刷新c缓冲区,一份有了;当子进程结束时,子进程也会刷新c缓冲区,这时子进程不可以在父进程的数据上直接进行修改,发生写时拷贝,在内存上有两个FILE-----即有两块空间,有两个缓冲区
。写时拷贝后,子进程刷新缓冲区,另一份就有了,所以有两份。
注意:write();函数
在操作系统内,不牵涉父进程的数据。
7. 自制 FILE(内含缓冲区) 以及_fopen、_fwrite、_fclose函数
模仿的是c语言库stdio。
代码放在了gitee里:链接
原文地址:https://blog.csdn.net/2301_81073406/article/details/142444114
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!