【基础io】

目录

一.文件操作相关C语言库函数

1.1以"w"的方式写入

1.1.2>输出重定向底层原理

1.2以"a"的方式写入

1.2.1>>追加输出重定向底层原理

二.文件操作相关的系统接口

2.1打开文件open()

2.2关闭文件close()

2.3文件写入write()

2.4标记位

2.5文件描述符

2.5.1文件描述符C语言和系统之间的关系

三.重定向

3.1手动关闭文件打开新文件实现重定向

3.2dup2()不手动关闭文件实现重定向

四、缓冲区

4.1语言级缓冲区的意义

4.2缓冲区的刷新策略

五、完善myshell程序


文件 = 属性 + 内容

即使文件大小为0也要占据磁盘空间,文件对于不同角色的权限、文件大小、文件名称、文件最近一次修改的时间等这些属性,也要保存在磁盘中。

一.文件操作相关C语言库函数

1.1以"w"的方式写入

以"w"的方式向一个不存在的文件写入时,会在当前的工作目录下新建该文件(每创建一个进程都会记录该进程所处的工作目录)

  • strerror是C库函数中的函数,输入一个数就能够打印出对应的错误信息
  • perror("fopen")先打印出"fopen:"然后根据errno打印出对应的错误信息。

"w"的方式打开时,会先清空该文件。

  • 文件大小不为0
  • 以"w"方式再次打开没有写入时文件大小为0

1.1.2>输出重定向底层原理

输出重定向底层实现就是以"w"方式写入

echo "hello world" >log.txt

file.txt 可以用来新建文件

log.txt 可用来清空文件

1.2以"a"的方式写入

  • 以"a"的方式写入,追加写入

1.2.1>>追加输出重定向底层原理

追加输出重定向底层实现就是以"a"方式写入

echo "hello world" >>log.txt

二.文件操作相关的系统接口

  • 向文件写入本质是向磁盘中写入,对于硬件的直接操作只能是操作系统,操作系统给用户提供了接口,调用接口对文件操作。

2.1打开文件open()

  • 第一个参数文件名、路径加文件名都可以;
  • 第二个参数传多个标记位;
  • 成功返回一个文件描述符,失败返回-1并且errno被设置;

【源代码】

【执行结果】

  • 传的O_WRONLY含义是只写、O_CREAT含义是 文件不存在就创建
  • 存在问题:新建的log.txt权限是乱码
  • 乱码原因:在Linux中新建一个文件必须要告诉该文件的起始权限

所以第一个接口只能打开已经存在的文件,如果文件不存在创建文件需要用到第二个接口

int open(const char *pathname, int flags, mode_t mode);第三个参数传起始权限。

系统当中存在着umask权限页码,文件最终权限 = ~起始权限&umask,所以需要先将umask设为0。

【源代码】

【执行结果】

位图思想,通过组合什么标记位从而实现什么功能如下所示(这些标记位是只有一个bite位为1的宏值,彼此之间宏值不重复)

【源代码】

【运行结果】

2.2关闭文件close()

2.3文件写入write()

2.4标记位

O_WRONLY:打开文件后光标定位到文件开头

O_TRUNC:打开文件后将文件清空

O_APPEND:追加写入

2.5文件描述符

打开成功的话返回文件描述符,不成功返回-1。

打印返回结果如下

【源代码】

【测试结果】

系统中同时有很多的进程在运行,一个进程可以打开多个文件,被打开的文件是只读、只写还是追加,那个文件等等都需要知道,所以要对被打开的文件进行管理。所以系统中要有描述文件的结构体。

  • C语言的程序在编译时,编译器添加相关打开键盘文件、显示器文件的代码,所以在执行每一个C语言程序时这两个文件默认是被打开的;
  • 无论读写都必须先让操作系统将磁盘中的内容读到缓冲区中,如果没有读到缓冲区中,让该进程加入到相关硬件的等待队列中,等操作系统将磁盘中的内容加载到缓冲区中,然后唤醒该进程;
  • fd是内核中文件描述符表中指向files类型变量的指针数组的下标。

进程执行open代码时都会干什么?

  1. 创建file;
  2. 开辟文件缓冲区的空间,加载文件数据(可能会延后);
  3. 将file地址填入到表对应的下标中;
  4. 查进程的文件描述符表;
  5. 返回下标。

C语言代码在执行时默认打开的有:

  1. 标准输入:fd=0,打开的是键盘文件,对应的硬件是键盘;
  2. 标准输出:fd=1,打开的是显示器文件,对应的硬件是显示器;
  3. 标准错误:fd=2,打开的是显示器文件,对应的硬件是显示器。

键盘、显示器、网卡、磁盘等其他设备这些硬件属性等都不一样,Linux如何做到对于这些硬件一切皆文件统一处理?

2.5.1文件描述符C语言和系统之间的关系

C语言中的库函数

系统接口

通过对比C语言库函数和系统接口发现,C语言中的库函数是FILE*来确定是对哪个文件操作,系统接口是通过fd来确定是对哪个文件操作的。这里FILE是C语言提供的结构体类型,操作系统是通过fd来确定哪个文件,所以FILE结构体中肯定封装了fd,C语言库中对文件操作相关函数内部实现肯定调用了系统接口。

通过代码证明FILE结构体封装了fd

【源代码】

【测试结果】

C语言为什么对系统接口进行封装,为什么不直接使用系统接口?

复制代码
  系统不同,系统调用接口可能不同,这就造成了代码不具有跨平台性,C语言标准库对不同平台做封装,这就使得语言本身就有了跨平台性。

  所以语言要想具有跨平台性就必须对系统提供的接口进行封装,只不过不同的语言进行封装使得文件接口就有差别了。

三.重定向

【写方式打开文件向文件中写】

【读方式打开文件读取文件内容】

  • 通过文件描述符或者指定路径获取指定文件的属性;
  • 定义一个struct stat类型的变量,传给stat(),stat()会将该文件相关属性赋给输出型变量;
  • stat()返回值是成功读取字符个数。
  • 在向文件写入时没有将'\0'写入,所以读取的内容并没有'\0',在打印时也要加上'\0'。
  • 之前在向log.txt写入的字符串是"hello Linux\n",字符'\n'也写入了,printf("%s\n",file_buffer);所以最终会换行两次。

3.1手动关闭文件打开新文件实现重定向

复制代码
   结果显示没有输出到显示器上,而是输出到了log.txt文件中。原因是因为C语言中的程序最开始会默认打开标准输入、标准输出、标准错误,其在Linux内核中的文件描述符表中的fd对应的是0、1、2。close(1);将标准输出文件关闭,open("log.txt"...);打开log.txt文件,对于Linux内核来说:新file地址插入到文件描述符表中空位的最小下标出,所以log.txt对应的fd是1。FILE是C语言层面上的结构体,C语言中的stdout->_fileno始终是1。C语言库函数printf()、fprintf(stdout...)将内容输出到fd=1的文件中,所以最终输出到log.txt文件中。

  这里为什么要通过C语言中的库函数fflush()来刷新缓冲区,如果不刷新结果是:内容不会输出到log.txt文件中。

【原因】:通过C语言层面打开一个文件,会创建一个FILE类型的结构体变量,在FILE类型的结构体内部会通过malloc()在堆空间中申请一块空间。向文件写入是先写入到C语言层的缓冲区,通过C语言层的fflush()将内容从C语言层的缓冲区刷新内核缓冲区中,或者程序结束后自动刷新到内核缓冲区,内核缓冲区是由内核来操作管理的。所以在没有通过fflush()刷新C语言层的缓冲区,而且在程序执行结束之前关闭了文件就无法将内容刷新到内核缓冲区中,也就无法输出到log.txt文件中。

  • 程序结束时并没有关闭log.txt文件。

3.2dup2()不手动关闭文件实现重定向

  • 完成文件描述符中数组下标对应的内容的拷贝,将oldfd对应的内容拷贝到newfd对应的内容

【重定向】

【追加重定向】

四、缓冲区

4.1语言级缓冲区的意义

复制代码
   想要从文件读取内容、向写入内容,是先要从内核缓冲区中读取、向内核缓冲区写入,直接从内核缓冲读取、向内核缓冲区写入需要系统内核来操作。由于系统很繁忙,调用系统接口系统并不会立刻响应,	需要程序执行停下来等待系统,而且调用系统接口需要成本。如果没有缓冲区的话就需要次次读取、写入,这样不仅会造成代码的效率低,而且对于系统来说有很大的成本。有了缓冲区之后先将内容放到C语言层的缓冲区中,继续执行后面的代码,等待缓冲区满了刷新、需要的时候强制刷新、程序结束时刷新,这样大大减少语言层与系统层的交互,提高了系统和代码的执行效率。

   预言级缓冲区是语言层面维护的。

4.2缓冲区的刷新策略

  1. 强制刷新;
  2. 遇到换行符'\n'才刷新(针对显示器才会有这个刷新策略);
  3. 缓冲区写满才刷新(普通文件);
  4. 进程退出时系统会自动刷新。

【C语言层缓冲区立即刷新】

【系统级缓冲区立即刷新】

  • 强制将内核缓冲区中的内容刷新到外设。
  • 调用printf()、fprintf()向文件写入会存在语言层面的缓冲区,而调用系统接口write()不存在语言层面的缓冲区。

    复制代码
      ./test是向显示器输入,按行刷新,./test > log.txt是重定向到普通文件中,缓冲区刷新发生变化,是缓冲区满了再刷新。
    
     分析第二段代码重定向到log.txt文件执行过程。printf("printf\n"); fprintf(stdout, "fprintf\n");先刷新到C语言层的缓冲区中。write(1, message, strlen(message));直接写入到内核缓冲区中,在执行fork()时系统内核已经刷新到磁盘中。fork();创建子进程,子进程共享父进程的数据和代码。子进程结束时刷新C语言层面缓冲区中的内容也就是"printf\n" "fprintf\n",刷新到内核缓冲区中,接着刷新到外设上。父进程结束时操作系统刷新C语言层面缓冲区中的内容也就是"printf\n" "fprintf\n",刷新到内核缓冲区中,接着刷新到外设上。

五、完善myshell程序

  • 进程的替换不会影响该进程中已经打开的文件
相关推荐
阿部多瑞 ABU36 分钟前
`chenmo` —— 可编程元叙事引擎 V2.3+
linux·人工智能·python·ai写作
徐同保1 小时前
nginx转发,指向一个可以正常访问的网站
linux·服务器·nginx
HIT_Weston1 小时前
95、【Ubuntu】【Hugo】搭建私人博客:_default&partials
linux·运维·ubuntu
实心儿儿2 小时前
Linux —— 基础开发工具5
linux·运维·算法
oMcLin2 小时前
如何在SUSE Linux Enterprise Server 15 SP4上通过配置并优化ZFS存储池,提升文件存储与数据备份的效率?
java·linux·运维
王阿巴和王咕噜6 小时前
【WSL】安装并配置适用于Linux的Windows子系统(WSL)
linux·运维·windows
布史6 小时前
Tailscale虚拟私有网络指南
linux·网络
水天需0106 小时前
shift 命令详解
linux
wdfk_prog6 小时前
[Linux]学习笔记系列 -- 内核支持与数据
linux·笔记·学习
Xの哲學7 小时前
深入剖析Linux文件系统数据结构实现机制
linux·运维·网络·数据结构·算法