当前位置: 首页 > 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.lryc.cn/news/623538.html

相关文章:

  • Rust Web 全栈开发(十三):发布
  • 芯片行业主要厂商
  • shell编程——Makefile
  • RocketMQ面试题-未完
  • CentOS7安装部署GitLab社区版
  • 产品设计.Ai产品经理
  • 【学习笔记】面向AI安全的26个缓解措施
  • 炒股术语:“洗盘”
  • 为何她总在关键时“失联”?—— 解密 TCP 连接异常中断
  • Java研学-SpringCloud(五)
  • 【电路笔记 通信】AXI4-Lite协议 FPGA实现 Valid-Ready Handshake 握手协议
  • 报错注入原理与全方法总结
  • Baumer高防护相机如何通过YoloV8深度学习模型实现行人跌倒的检测识别(C#代码UI界面版)
  • 基于Spring Boot+Vue的莱元元电商数据分析系统 销售数据分析 天猫电商订单系统
  • MySQL黑盒子研究工具 strace
  • TensorRT-LLM.V1.1.0rc0:在无 GitHub 访问权限的服务器上编译 TensorRT-LLM 的完整实践
  • Vue中v-show与v-if的区别
  • 负载测试与压力测试详解
  • mac电脑开发嵌入式基于Clion(stm32CubeMX)
  • 【力扣热题100】双指针—— 三数之和
  • Unity进阶--C#补充知识点--【Unity跨平台的原理】了解.Net
  • 44.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--扩展功能--集成网关--网关集成认证(三)
  • 【Java后端】Spring Boot 集成 MyBatis 全攻略
  • 反向代理、负载均衡器与API网关选型决策
  • 【牛客刷题】BM63 跳台阶:三种解法深度解析(递归/DP动态规划/记忆化搜索)
  • Shell脚本-for循环应用案例
  • 小白成长之路-k8s部署discuz论坛
  • HTTP请求参数类型及对应的后端注解
  • B站 韩顺平 笔记 (Day 21)
  • 新的“MadeYouReset”方法利用 HTTP/2 进行隐秘的 DoS 攻击