自学内容网 自学内容网

【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”,然后用 fwritefprintf 函数

#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. 访问文件的本质

?origin_url=.png&pos_id=img-VGfHAvAl-1727086662146)

  • 打开文件的步骤:先把将该文件地址插入在 该进程对应的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 多文件的输入/输出重定向

  1. 现象引入
    在这里插入图片描述
  2. 依旧是上面的代码,如何将 nomal messagestderr message 重输出在不同文件?
    操作如下即可:
    在这里插入图片描述
  3. 依旧是上面的代码,如何将 nomal messagestderr 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语言缓冲区,是不是属于用户级的缓冲区呢?  答:属于,语言都属于用户层。
      在这里插入图片描述

现象分析:
问题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)!