Linux操作系统

Linux--基础IO]

  • C文件 IO 相关操作
  • 系统调用接口
    • 文件描述符 fd
  • 重定向
    • 重定向的三种情况
    • 缓冲区
    • dup2系统调用
  • 文件系统
  • 动态库和静态库

C文件 IO 相关操作

有关c语言的IO函数
fopen、fread、fwrite、f等等
我们先来看一个简单的例子:打开一个文件

  • fopen函数的两个参数,第一个参数为文件路径,如果我们不加路径,只写文件名则指当前路径 —cwd
    什么是当前路径呢?本质是进程运行时所处的路径
    我们可以通过命令的形式看一看进程究竟在什么路径下
    我们知道除了 axj aux 这样的查看进程命令,还可以通过proc文件夹 来查看进程

我们可以清晰的看到,有一个exe 指 可执行文件所处的路径,cwd 指进程运行时的路径

  • 第二个参数为打开方式,总的来说,有b则是打开二进制文件,有+则一定是可读可写,其中r+为打开w+为新建有b为二进制

这个小小的例子就是打开一个用来写入的文件,要注意操作完要记着关闭文件,接下来我们尝试操作文件

fputs 将字符写入流中

fgets,将流中的内容写到缓冲区中,其中第一个参数为缓冲区,第二个参数则是我们预期输入缓冲区的大小

至此我们完成了大致的c语言文件操作,这里有几个细节
FILE* 是我们打开文件时的返回值,这里的文件类型可以是普通文本文件、二进制文件、设备文件等

还需要注意的是 ,进程运行时,会自动打开三个流文件,分别叫做标准输入、标准输出、标准错误,他们都在头文件中被声明,分别对应的设备为键盘、显示器、显示器。

因此,我们就可以通过fgets和fputs 来模拟printf和scanf

第三点:a和w分别为追加写入和覆盖写入,也就是从文末开始写 与 从文件开头开始写

以上就是大致c语言相关的io操作 ,当然还有fseek、ftell、frewind等等函数,我们这里不深入探究。


系统调用接口

我们上文中所使用的C语言IO函数,也就是c语言的库函数,它通过对系统调用接口的封装来让程序员轻易使用,接下来我们就聊聊更加底层的系统调用接口。
我们先来看看如何打开文件,叫做 open函数

它的参数有三个,第一个为我们所要打开的文件路径和文件名,这个与fopen相同
第二个参数为标志位参数,可以有O_RDONLY , O_WRONLY , O_RDWR 分别对应了只读 、只写、 可读可写。

除此之外,还有其他的标志位,比如 O_CREAT 我们可以发现 我们的系统调用接口与fopen有一些不同,fopen在文件不存在的情况下会自动创建,而open不可以,你需要写标志位O_CREAT 来增加这一功能。否则会打开失败,返回-1

第三个参数为我们创建文件时的权限值,所以如果文件已经存在 ,就不需要第三个参数了。

在此,我们来对第二个参数的标志位进行深入的了解:
我们可以认为有32个不同的标志位,因为我们在传参时,理论上可以传32bit位,标志位的本质就是一位为1,其余31位全部为0,来分别标志不同的状态。这也就解释了我们在进行标志时用 或 (|)这个运算符来处理。
这样比特位传递参数的方式就给我们许多的选线。

最后一个参数也有一些小细节,我们一定要输入四位的权限 ,第一位我们默认为0就好了,否则我们仅仅输入 666这样的码是无法完成权限的赋值的。
其中在赋值权限时,还要注意 umask这样的掩码,我们可以在打开文件前将掩码赋为0。
我们就可以轻易的赋值权限了。

最后我们来谈谈open的返回值,让我们意想不到的是它为int ,而我们fopen的返回值为FILE * 。
open的返回值:成功 返回file descriptor – 文件描述符 ,失败则返回 -1
我们可以打开多个文件,发现他们的fd值是从3开始的连续整数。
有两个问题:1.为什么没有0、1、2
2.为什么它是连续的,很容易让人想到数组

文件描述符 fd

我们回答第一个问题,我们还记得之前说过进程运行时会自动打开3个流,其中0就是 stdin的 fd, 1就是stdout 的 fd, 2 就是 stderr 的 fd。所以我们在打开第四个文件时,其返回值fd会为3 。我们可以想到,这三个流也是被打开的文件,所有文件都会在打开时有一个fd值。我们所谓的文件描述符就是数组下标,之后打开的文件会依次被赋予fd值。当然这是大致的了解。

我们现在就来看看文件描述符的底层逻辑:
我们的文件可以分为两种:1.磁盘文件 2.内存文件

磁盘文件:
就是我们保存在磁盘中的文件,这个文件会在进程调用它的时候进行加载到内存的操作,类似于我们的程序替换这样的加载器。这个文件的组成为:内容+属性,属性比如创建时间、文件大小、所属组、修改日期等等这样的信息 , 也称作 元信息 ,而内容就是我们打开所看到的内容。

内存文件:
当我们进程需要一个打开或是操作一个文件的时候,我们操作系统会为这个文件生成一个struct file 这样的文件描述结构体,其中大多包含了文件的元信息。一个个这样的结构体会形成双链表,让我们管理。在这之前,在进程创建的时候,还会生成一个struct files_struct 这样的结构体,这个结构体被pcb内的指针所指向,用来管理文件。其中这个结构体中有一个部分为一个指针数组,struct file* fd array[32] ,这里的每一个指针就会指向我们文件的struct file。而这里的数组下标就是我们所知道的文件描述符。其中这个文件描述符所在的数组是可以扩展的。所以我们每打开一个文件,都会有文件描述符指针指向它,并且它的指向是有特性的:也就是依次从小到大指。

我们都知道,一个系统中会有无数已经被打开的文件,所以我们通过这样的方式对文件进行管理。
当然这些结构体都处在内存中。

如果我们需要对一个文件进行操作,不是一下将磁盘中的全部文件拷贝到我们的内存中,而是通过缓冲区,延后式的加载到内存中让我们操作。
比如我们打开一个log.txt文件,并被赋予了3号fd , 我们现在read(3,xx,yy),此时执行到read这一步时才会通过我们的文件描述信息struct file 将文件加载到内存中。
此时如果我们close (1)这个stdout文件,那么我们在打开文件时,log.txt会被分配到 1号fd。
以上就是我们文件描述符的底层实现,它的分配规则就是从最小但是没有被使用的fd开始分配。

重定向

重定向的本质:就是修改文件描述符fd下标所对应的 struct file * 的内容。
比如原本 1 号fd 默认打开 stdout ,而现在让其指向了 log .txt ,以至于我们想要printf打印的内容 ,写入到了log.txt中。这就叫做重定向

我们在正式开始重定向的原理之前,我们需要明白一个道理,C语言的文件操作和系统调用的文件操作 ,究竟谁优谁劣

C库函数是一个fp指针,指向一个FILE对象。而这个FILE 对象中 就存在了 fd文件描述符 以及 缓冲区信息
我们经常使用的stdout 就是一个fp指针 ,指向了一个FILE对象,这个FILE对象中 存储了fd =1 这样的信息,然后进程对这个fd进行检索,在file_struct中 找到这个文件。也就是说:
C库函数是无法对fd进行直接操作的,而系统调用可以。
所以我们推荐使用C库函数等语言级别的函数来对文件进行操作,原因如下:

  • 跨平台性
  • 省去对于fd的直接操作

其中,这也就解释了,当我们关闭一个文件,而printf为什么不会依旧打印在stdout“显示器”中,因为stdout,它只认识FILE* ,当我们关闭文件导致 fd =1 指向其它文件,但是stdout所指向的结构体中依旧指向 fd = 1 的文件,因为C语言无法直接改变fd ,所以此时fd = 1 指向了log.txt ,printf 也就会往这个文件中打印信息了。

重定向的三种情况

输出重定向:

在这份代码中,我们混用了系统调用和c库,这是完全没有问题的
现象:本应该打印到显示其中的内容,打印到了文件中 --这就叫做输出重定向
本质:由于我们的关闭操作,fd = 1 这个 指针,被我们新打开的文件占用了。由于stdout这个 FILE * 没有鉴别能力,致使其指向的结构体依旧指向fd = 1 这个指针。致使printf打印到了文件中。

此时我们需要了解几个问题:
请问fopen究竟做了什么?!
答案:

  1. 给调用的用户申请 struct FILE 结构体变量,并返回FILE* 指针。
    这就是为什么我们总写 FILE * fp = fopen()
  2. 在底层 ,通过open 打开文件,并返回fd,并把fd填充到FILE对象的fileno中
    之后我们对于文件的操作 fread fwrite fclose fputs fgets 都是通过这个返回的指针FILE*,找到fd,并对文件进行操作。

输出重定向:以写的方式打开文件,写到哪? 写到stdout中,我们在底层修改stdout所指的文件。达到输出重定向。

输入重定向:

所有的重定向道理都是一样的,所以我们简单看一下例子就好了。

输入重定向: 这里我们通过读的方式打开文件,从哪读?键盘磁盘,还是文件?这种改变就叫做输入重定向。

追加重定向:
追加重定向是输出重定向的一种,我们只需要在打开文件时添加上 追加标识符 O_APPEND ,其余和输出重定向完全一样

在这里,我们写了三个不同的输出,其中fputs没有格式化输出的概念的,你给他写什么都会原模原样的打印
我们需要在了解一个概念:
我们键盘显示器都叫做字符设备,输入的还是显式的都是字符。而printf的功能就是将其它类型的数据,转换为字符,并打印到显示器。我们所写的%d 之类的,就是告诉编译器 这个数据是整数,你把整数转换成字符在输出吧。 —这就叫做格式化输出
格式化输入也是相同的道理,而我们转换的依据 ,就是ascll 码表

缓冲区

我们先来看一段简单的代码:

当我们不带\n时,现象:休眠3s后才显示我们打印的字符串。
而当我们带上\n后,就是直接显示出来。
结论:不论什么情况,都是按代码步骤,先打印后sleep,但是打印的数据去了哪里,我们并没有看到。这就是缓冲区。

缓冲区有三种:无缓冲、行缓冲、全缓冲

行缓冲:一般用于显示器率刷新数据时的策略:遇到**\n** 或是 一行缓冲区满了 或是 进程结束了,才会刷新出数据,让数据从缓冲区中流出来。
全缓冲:只有当缓冲区文件写满了才会刷新,将数据流入磁盘中。

一个很简单的道理:全缓冲的效率是最高的,一次积攒很多,一起送达目的地,少去了很多来回路上的时间。但是这样虽然高效,我们在看显示器时,无法等待太久,所以采取了折中的方法,行缓冲其效率和可用性是一种平衡

缓冲区究竟在哪里呢?缓冲区是谁提供的呢?接下来我们就来研究这两个问题。

依旧是一份简单的代码

操作:用C库和系统调用写入数据,写入到显示器和文件中,并通过fork 创建进程,使子进程和父进程有相同的代码和数据
现象:当正常打印到显示器中时,且不论有没有fork创建进程,两份代码都是正常打印我们想要的字符。而当我们重定向到文件中同时fork子进程,我们发现 只有系统调用所打印的字符串打印了一遍,C库打印的字符串都打印了两遍
分析:显示器 --行缓冲 文件-- 全缓冲 重定向改变了缓冲方式 ;c接口打印了两次 ,系统接口打印了一次。

解释:
向显示器打印,由于我们带了 \n 所以fork 前,这些字符都被打印并且被刷新出来。
此时重定向到文件中,缓冲区变为了全缓冲,也就意味着,我们这C语言的两行字符串在fork前,都没有刷新,全部被打印在了全缓冲区中。缓冲区一直没满且进程也没退出。
此时我们创建子进程,我们需要知道 ,缓冲区是在内存中,所以当我们创建子进程时,子进程会与父进程有相同的数据和代码。所以此时父子进程会有相同的缓冲区,由于进程之间的独立性,当子进程结束时,需要刷新缓冲区,也就是改变内存中的数据,就要发生写时拷贝。子进程会开辟新的空间并把之前的数据拷贝一份。随着进程结束,缓冲区刷新出一份字符串,之后父进程也刷新一份字符串。所以打印出了两份字符串。
大家一定很疑惑,为什么全缓冲区中有两行代码,而不是三行呢?
因为write是没有缓冲区的,只有两个C库函数存在缓冲区,所以write只打印了一份。

我们这里谈到的缓冲区 指 : 用户级缓冲区

结论:
为什么被打印两次:1、全缓冲区机制 2、写时拷贝 (OS)机制

至此,我们解释了为什么会出现上面代码的情况,而为什么系统调用没有缓冲区呢?C为什么有?
这个原因我们之后再谈,但是我们现在可以解决的是,缓冲区是C自带的,而不是系统提供的,因为C是对系统调用的封装,系统都没有缓冲区,你C自创的。

什么是流:数据流入缓冲区 在流出 缓冲区。缓冲区 相当于一个河床。

所以我们现在理解了,为什么FILE 对象,是由 fd 和用户缓冲 组成的了。

我们来看看FILE结构体吧!

刷新的本质:用户级别无法与硬件直接交互,更别提将数据直接刷新到硬件上。冯诺依曼体系决定了,用户层之下,一定有操作系统层,来对下,管理硬件。所以我们的刷新,就是将我们的用户级数据刷新到 kernel级缓冲区中,这就是操作系统的缓冲区,再由操作系统刷新到硬件中。

我又有问题了,为什么我们再写代码的时候,必须要加fflush 才能刷新出数据。
解释:当我们写入数据到文件中,全部写入了用户层缓冲区中,还没有流入系统级别缓冲区,此时进程还没有结束,我们在代码的最后就用close关闭了文件,那系统级缓冲区没了。之后随着进程的结束,用户缓冲区终于准备自动刷新了,可是此时系统文件已经被close了,数据就全部消失了。

所以面对这样的,写入无法刷新的情况,我们通常采用这样方法:

  1. fflush,手动刷新
  2. fclose 关闭,而不是close

这里需要清楚的是close关闭的是系统文件,而不是用户层文件,也就是说,close清空了fd的指向。而fclose 清空了FILE *的内容。fclose 清空fd和用户缓冲区 ,然后数据流入系统缓冲区,fclose还会调用close 对系统文件进行关闭。


至此,我们再来系统了解一下重定向:
文件描述符本质–数组的下标
重定向:用户层不变,也就是FILE* 层不变,改变的是系统层file* 的指向。语言级别感受不到底层的变换。这也就是重定向的根本。

stdout 和stderr 虽然都是显示器,但是两个是不同的fd,也就是说同一份文件,被同一个进程,打开了两次,fd分别对应了1和2

为什么说,linux下一切皆文件

我们进程控制硬件时 , 磁盘有read读取,write 写入,显示器没有read读出,只有write写入,网卡有读出和写入,键盘只有读出数据给进程,而没有写入。那么这么多读写操作,每一个都不同。那我们如何管理呢?我们只写了一份代码 ,一个read和write 就可以控制这么多硬件。本质在于 我们所使用的read 和 write 都是函数指针,指向了底层不同的硬件的read和write。我们只需要管理这个函数指针,就可以完成对所有硬件的操作。而这中函数指针,存在于struct_file 中,也就是元信息中,每一个文件打开后,都有对应的函数指针,指向底层硬件的接口。

所有一切皆文件,指一切皆 struct file { } ;


dup2系统调用

我们上文中写了很多重定向,都是用close关闭这样的函数实现的,这也太麻烦了。dup来了。
dup就是通过对fd指针的拷贝,实现重定向。也可以理解为,拷贝之后,有两个fd都指向同一个文件。

我们重点讲dup2,
先来读一段英文: dup2 makes newfd be the copy of oldfd 也就是说newfd是oldfd的拷贝
比如我们想要实现输出重定向,只需要 dup2 (fd ,1),此时 fd 和 1 都指向我们打开的文件。

这就是dup函数的基本使用了。

文件系统

动态库和静态库

(0)

相关推荐

  • Shell:管道与重定向

    转自:TOMORROW 链接:https://reurl.cc/A8vpQE 对shell有一定了解的人都知道,管道和重定向是 Linux 中非常实用的 IPC 机制. 在shell中,我们通常使用符 ...

  • linux操作系统入侵检查流程

    linux操作系统入侵检查流程

  • 深度探索Linux操作系统:系统构建和原理解析

    前言 对于编译内核而言,一条make命令就足够了.构建内核最困难的地方不是编译,而是编译前的配置.配置内核时,通常我们都能找到一些参考. 比如,对于桌面系统,可以参考主流发行版的内核配置,比如,对于嵌 ...

  • stat命令支持的文件格式有哪些?linux操作系统

    stat命令支持的文件格式有哪些?stat命令用于详细显示文件或文件系统的状态信息.stat命令是Linux基础命令之一,是Linux运维人员必须要会的知识点.学习Linux运维就必须要学习Linux ...

  • Linux系统Shell脚本如何运行?linux操作系统

    Shell脚本语言很适合用于处理纯文本类型的数据,而Linux系统中几乎所有的配置文件.日志文件以及绝大多数的启动文件都是纯文本类型的文件.因此,学好Shell脚本语言,能够更好的操作Linux系统. ...

  • Linux操作系统收费吗?

    Linux系统,是当下非常热门的系统,因为具有独特的优势以及特点,广受大家的喜欢.那么Linux系统收费吗? Linux是什么系统呢?Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于 ...

  • 10 大黑客专用的 Linux 操作系统...

    原文整理自[民工哥技术之路] 今天列出一些最常用.最受欢迎的Linux发行版来学习黑客和渗透测试! 1. Kali Linux Kali Linux是最著名的Linux发行版,用于道德黑客和渗透测试. ...

  • Linux操作系统应用领域有哪些?

    Linux操作系统在众多技术中是非常受喜欢的,开放.安全.稳定的特性备受用户认可.那么,Linux操作系统主要应用领域有哪些?小编为大家介绍一下. 1.服务器领域:Linux因为价格低廉.灵活性好,现 ...

  • U盘安装Linux操作系统教程,含收费内容,慎入!

    前几天发完小白科普,什么是Linux系统?要不要装一个来玩玩!这个后,有朋友要求发Linux安装教程,由于视频教程非常费时费力,所以视频教程需要收费,图文教程免费,视频教程获取方式看最后介绍. 使用U ...

  • Linux操作系统支持的文件系统有哪些?

    提及操作系统大家肯定会想到Windows,因为Windows是大家生活和学习中较为常见的系统,它操作简单,也知道Windows支持的文件系统,而对Linux系统很多人就比较陌生,因为Linux比较复杂 ...

  • Cortex-M可以跑Linux操作系统吗?

    单片机.Cortex-M.Linux它们和嵌入式有什么区别? 跑 Linux 操作系统需要什么处理器?ARM9.ARM11? Cortex-M比ARM9更新,为什么不能跑Linux? 相信很多小伙伴都 ...