当前位置: 首页 > news >正文

基础IO_系统文件IO | 重定向【Linux】

文章目录

  • 一、 理解"文件"
    • 1、狭义理解
    • 2、广义理解
    • 3、文件操作的归类认知
    • 4、系统角度
  • 二、回顾C文件接口
    • 1、hello.c打开文件
    • 2、hello.c写文件
    • 3、hello.c读文件
    • 4、输出信息到显示器,你有哪些方法
    • 5、stdin & stdout & stderr
    • 6、打开文件的方式
  • 三、系统文件I/O
    • 1、一种传递标志位的方法
    • 2、hello.c 写文件:
    • 3、hello.c读文件
    • 4、接口介绍
    • 5、open函数返回值
    • 6、文件描述符fd
      • 6.1、0 & 1 & 2
      • 6.2、文件描述符的分配规则
      • 6.3、重定向
      • 6.4、使用 dup2 系统调用
      • 6.5、在minishell中添加重定向功能

一、 理解"文件"

1、狭义理解

  • 文件在磁盘里
  • 磁盘是永久性存储介质,因此文件在磁盘上的存储是永久性的
  • 磁盘是外设(即是输出设备也是输入设备)
  • 磁盘上的文件 本质是对文件的所有操作,都是对外设的输入和输出 简称 IO

2、广义理解

  • Linux 下一切皆文件(键盘、显示器、网卡、磁盘…… 这些都是抽象化的过程)

3、文件操作的归类认知

  • 对于 0KB 的空文件是占用磁盘空间的
  • 文件是文件属性(元数据)和文件内容的集合(文件 = 属性(元数据)+ 内容)
  • 所有的文件操作本质是文件内容操作和文件属性操作

4、系统角度

  • 对文件的操作本质是进程对文件的操作
  • 磁盘的管理者是操作系统
  • 文件的读写本质不是通过 C 语言 / C++ 的库函数来操作的(这些库函数只是为用户提供方便),而是通过文件相关的系统调用接口来实现的

二、回顾C文件接口

1、hello.c打开文件

#include <stdio.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");} while(1);fclose(fp);return 0;
}

打开的myfile文件在哪个路径下?

xz@xzlinux:~$ ps ajx | head -1;ps ajx | grep catmePPID     PID    PGID     SID TTY        TPGID STAT   UID   TIME COMMAND336450  336595  336595  336435 pts/0     336595 R+    1000   0:02 ./catme336584  336599  336598  336569 pts/1     336598 S+    1000   0:00 grep --color=auto catme
xz@xzlinux:~$ ls /proc/336595 -l
total 0
......
-r--r--r--  1 xz xz 0 May  8 15:34 cpuset
lrwxrwxrwx  1 xz xz 0 May  8 15:34 cwd -> /home/xz/z/IOleran
-r--------  1 xz xz 0 May  8 15:34 environ
lrwxrwxrwx  1 xz xz 0 May  8 15:34 exe -> /home/xz/z/IOleran/catme
dr-x------  2 xz xz 4 May  8 15:34 fd
......

其中:

  • cwd:指向当前进程运行目录的一个符号链接。
  • exe:指向启动当前进程的可执行文件(完整路径)的符号链接。

打开文件,本质是进程打开,所以,进程知道自己在哪里,即便文件不带路径,进程也知道。由此OS就能知道要创建的文件放在哪里。

2、hello.c写文件

#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);} fclose(fp);return 0;
}

3、hello.c读文件

#include <stdio.h>
#include <string.h>int main()
{FILE *fp = fopen("myfile","r");if(!fp){printf("fopen error!\n");return 1;}char buff[1024];const char *msg = "hello bit!\n";while(1){ssize_t s = fread(buff,1,strlen(msg),fp);if(s > 0){buff[s] = 0;printf("%s",buff);}if(feof(fp)){break;}}fclose(fp);return 0;
}

稍作修改,实现简单cat命令:

#include <stdio.h>
#include <string.h>//简单实现cat命令
int main(int argc, char*argv[])
{if (argc != 2){printf("argv error!\n");return 1;}FILE *fp = fopen(argv[1],"r");if(!fp){printf("fopen error!\n");return 2;}char buf[1024];while(1){int s = fread(buf,1,sizeof(buf),fp);if(s > 0){buf[s] = 0;printf("%s",buf);}if(feof(fp)){break;}}fclose(fp);return 0;
}

4、输出信息到显示器,你有哪些方法

#include <stdio.h>
#include <string.h>int main()
{const char *msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout,"hello fprintf\n");return 0;
}

5、stdin & stdout & stderr

#include <stdio.h>extern FILE *stdin;
extern FILE *stdout;
extern FILE *stderr;

6、打开文件的方式

r      Open text file for reading.  The stream is positioned at  the  beginning  of  thefile.r+     Open  for  reading and writing.  The stream is positioned at the beginning of thefile.w      Truncate file to zero length or create text file for writing.  The stream is  po‐sitioned at the beginning of the file.w+     Open  for  reading and writing.  The file is created if it does not exist, other‐wise it is truncated.  The stream is positioned at the beginning of the file.a      Open for appending (writing at end of file).  The file is created if it does  notexist.  The stream is positioned at the end of the file.a+     Open  for reading and appending (writing at end of file).  The file is created ifit does not exist.  Output is always appended to the end of the file.   POSIX  issilent on what the initial read position is when using this mode.  For glibc, theinitial  file  position  for reading is at the beginning of the file, but for An‐droid/BSD/MacOS, the initial file position for reading is at the end of the file.

如上,是文件相关操作。还有 fseek ftell rewind 的函数,在C部分已经有所涉猎。

三、系统文件I/O

打开文件的方式不仅仅是fopen,ifstream等流式,语言层的方案,其实系统才是打开文件最底层的方案。不过,在学习系统文件IO之前,先要了解下如何给函数传递标志位,该方法在系统文件IO接口中会使用到:

1、一种传递标志位的方法

#include <stdio.h>
#include <string.h>#define ONE   0001  //0000 0001
#define TWO   0002  //0000 0001
#define THREE 0004  //0000 0001void func(int flags)
{if (flags & ONE) printf("flags has ONE!");if (flags & TWO) printf("flags has TWO!");if (flags & THREE) printf("flags has THREE!");printf("\n");
}int main()
{func(ONE);func(THREE);func(ONE | THREE);func(ONE | THREE | TWO);return 0;
}

操作文件,除了上面的C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问, 先来直接以系统代码的形式,实现和上面一模一样的代码:

2、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 xz!\n";int len = strlen(msg);while (count--) {write(fd, msg, len);//fd: 后面讲, msg:缓冲区首地址。//len: 本次读取,期望写入多少个字节的数据。 //返回值:实际写了多少字节数据} close(fd);return 0;
}

3、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_tssize_t s = read(fd, buf, strlen(msg));//类比writeif (s > 0) {printf("%s", buf);}else {break;}} close(fd);return 0;
}

4、接口介绍

#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: 追加写
返回值:成功:新打开的⽂件描述符失败:-1
  • mode_t理解:直接 man 手册,比什么都清楚。
  • open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
  • write read close lseek ,类比C文件相关接口。

5、open函数返回值

在认识返回值之前,先来认识一下两个概念: 系统调用库函数

  • 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数
    (libc)。
  • open close read write lseek 都属于系统提供的接口,称之为系统调用接口
  • 回忆一下我们讲操作系统概念时,画的一张图
    在这里插入图片描述

系统调用接口和库函数的关系,一目了然。
所以,可以认为, f# 系列的函数,都是对系统调用的封装,方便二次开发。

6、文件描述符fd

  • 通过对open函数的学习,我们知道了文件描述符就是一个小整数

6.1、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,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件。

对于以上原理结论我们可通过内核源码验证:

首先要找到 task_struct 结构体在内核中为位置,地址为: /usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/sched.h(3.10.0-1160.71.1.el7.x86_64是内核版本,可使用 uname -a 自行查看服务器配置, 因为这个文件夹只有一个,所以也不用刻意去分辨,内核版本其实也随意)

  • 要查看内容可直接用vscode在windows下打开内核源代码
  • 相关结构体所在位置

struct task_struct/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/sched.h
struct files_struct/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fdtable.h
struct file/usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h
在这里插入图片描述

6.2、文件描述符的分配规则

直接看代码:

#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>
#inc
lude <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数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

6.3、重定向

那如果关闭1呢?看代码:

#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);
}

此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有: > , >> , <

那重定向的本质是什么呢?

6.4、使用 dup2 系统调用

函数原型如下:

#include <unistd.h>int dup2(int oldfd, int newfd); 

示例代码

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() 
{int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0) {perror("open");return 1;}close(1);dup2(fd, 1);for (;;) {char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0) {perror("read");break;} printf("%s", buf);fflush(stdout);} return 0;
}

printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了myfile的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

6.5、在minishell中添加重定向功能

重定向myshell—https://gitee.com/xiaozhi

http://www.dtcms.com/a/335825.html

相关文章:

  • 《程序员修炼之道》第三四章读书笔记
  • 《算法导论》第 27 章 - 多线程算法
  • scikit-learn/sklearn学习|套索回归Lasso解读
  • Ansible 核心功能进阶:自动化任务的灵活控制与管理
  • 自由职业数据科学:从细分定位到规模化的实战路线
  • 记忆翻牌游戏 greenfoot 开发
  • 机器人经验学习1 杂记
  • 电子电气架构 --- 自动驾驶汽车的下一步发展是什么?
  • Python自学10-常用数据结构之字符串
  • 机器学习算法篇(十三)------词向量转化的算法思想详解与基于词向量转换的文本数据处理的好评差评分类实战(NPL基础实战)
  • 深度解析 Tomcat ProtocolHandler 工作原理
  • 安装 Docker 支持 NVIDIA 显卡的依赖
  • AI Search进化论:从RAG到DeepSearch的智能体演变全过程
  • JavaScript性能优化实战(三):DOM操作性能优化
  • 计算机网络 HTTP1.1、HTTP2、HTTP3 的核心对比及性能分析
  • 【LLM】文献阅读-ISOLATE GPT:基于大语言模型的执行隔离架构
  • 第16节:自定义几何体 - 从顶点构建3D世界
  • 检查xrdp远程连接桌面卡顿的问题(附解决sh脚本)
  • Oracle查看历史会话信息视图介绍
  • 【大语言模型 04】Cross-Attention vs Self-Attention实战对比:解码器中的双重注意力机制
  • NumPy 库介绍:核心 API 详解
  • MYSQL-175. 组合两个表
  • Java 学习笔记(基础篇4)
  • Java学习笔记:IDEA简单使用技巧
  • 安卓14系统应用收不到开机广播
  • HTTP请求参数类型及对应的后端注解
  • AMBA-AXI and ACE协议详解(七)
  • 【学习笔记】面向AI安全的26个缓解措施
  • API网关实施中典型陷阱
  • 【数据结构与算法】单调队列的定义和运用