0
点赞
收藏
分享

微信扫一扫

Linux系统中前后端分离项目部署指南

本篇重点:文件描述符,重定向,缓冲区,磁盘结构,文件系统,inode理解文件的增删查改,查找一个文件为什么一定要有路径,动静态库,有的时候为什么找不到库,动态库的加载。

目录

需要大概了解的知识

IO接口简单介绍

read,write和open的初次使用

文件fd

0&1&2

重定向

struct file

磁盘的物理结构和逻辑结构

软硬链接

动静态库

静态库

动态库

2.生成动态库

3.使用动态库

总结


需要大概了解的知识

文件=内容+属性

内容和属性本质上都是数据,都存储在磁盘中

由冯诺依曼可得,文件想要被打开一定要先被加载到内存中

所以文件可被分为被打开文件(内存中)和未被打开的文件(磁盘中)

文件在被加载到内存之前,肯定会在内存中生成对应的描述结构体 struct file,和进程类似

对于被打开的文件我们也要进行管理,怎么管理?先描述,再组织!

一个进程可能打开多个文件,所以进程和被打开文件的关系是1:n

本篇先要大概了解进程和被打开文件的关系

IO接口简单介绍

之前我们学习过c语言的文件操作,但那是语言级别的。由之前所学知道中间肯定需要操作系统参与,所以c语言的文件操作必定封装了系统调用

read,write和open的初次使用

read和write 

 open

这里注意一下mode的使用即可

重点:为什么open的返回值是int???

文件fd

open的返回值int,被成为文件描述符fd

当使用open函数打开文件再将对应的fd打印出来我们可以发现,fd是一个较小的大概率连续的整数

这比较符合日常使用的数组的下标的特征

我们之前说过一个进程要打开一个磁盘中的文件的时候,要先将磁盘中的文件加载到内存中,在这之前我们要先建立对应的struct file 结构体(先将里面默认为有属性和struct file*指针和其他字段),除此之外还要建立一个数组

对应的结构体也要被管理起来?怎么管理?先描述,再组织!

被打开的文件也算一个小的进程了

在进程的对应的描述对象task_struct 结构体中,我们可以发现一个数组指针struct file_struct *file指针一个数组

这个数组默认0,1,2下标被填充。

进程每打开一个文件,对应的struct file就会被生成,然后指向磁盘中的对应部分,该数组从0开始找到未被填充的部分将其填充,这样就能通过数组找到对应的结构体struct file,对应的下标作为返回值返回给fd

这样进程就能通过该数组对struct file实现管理

文件描述符fd的本质:就是数组的下标!!!

那c语言的FILE又是什么鬼?

由上可以推测出FILE是一个结构体,里面必定封装了文件描述符fd

操作系统访问文件只认文件描述符

0&1&2

为什么该数组的0,1,2位置会被默认填充呢?填充的是什么呢?

填充的分别为stdin(键盘),stdout(显示器)和stderr(显示器),目的是为了方便程序员进行读写操作

stderr是什么鬼?又如何进一步理解一切皆文件呢?

struct file里除了属性,还有对应的读写等操作方法集和缓冲区。

因为已经默认填充好0,1,2位置了并且有了对应的struct file,所以当我们敲键盘的时候,本质就是向对应的键盘设备写入数据;当进行打印操作的时候,本质就是向显示器写入数据

stdout和stderr都是显示器,这是为什么呢?为了将代码编辑运行的错误信息和普通的打印信息进行分类,更好地为程序员定位错误位置和写日志 

文件fd的分配规则:从小到大进行寻找没有被使用数据的位置,然后分配给指定的打开的文件

重定向

重定向可以分为清空重定向和追加重定向

1.清空重定向>

第一次写的hello world被第二次写的你好呀覆盖了,这就是清空重定向

2.追加重定向>>

第一次写的hello没有被第二次写的你覆盖,而是追加在hello后面,这就是追加重定向。

重定向本质:上层fd不变,fd指向的内容进行改变

struct file

struct file里主要包含文件的属性,文件的操作方法集和文件缓冲区

磁盘中的文件要加载到内存中,其实就是加载到对应的文件缓冲区中,这一工作由操作系统来做

  • 读数据:要将数据读到内存中——将磁盘中数据拷贝到对应的struct file 里的文件缓冲区
  • 写数据:要将数据读到内存中——将磁盘中数据拷贝到对应的struct file 里的文件缓冲区,然后再进行修改

我们在应用层进行数据读写的本质是什么?将文件缓冲区中的数据进行来回拷贝

缓冲区是什么呢?由上图可得缓冲区是struct file结构体里的一段空间,所以缓冲区本质上就是内存的一部分空间

为什么要有缓冲区呢?缓冲区就等于现实生活中的菜鸟驿站,我们要把一个东西送给朋友,有了菜鸟驿站就不用亲自开车去送,而是到驿站去寄快递,到驿站寄了以后,就会说已经把东西送了出去了,此时东西也不再属于我们了。所以缓冲区的最主要作用就是提高效率——提高使用者的效率

因为有了缓冲区的存在,可以积累一部分数据然后统一发送,减少了IO的次数,提高了发送效率

缓冲区因为可以暂存数据,必定有一定的刷新方式

一般策略,特殊情况

一般对于显示器文件——行缓冲

对于磁盘上的文件—— 全缓冲(重定向后往往由行缓冲变成全缓冲)

一段样例

#include<stdio.h>
#include<string.h>
#include<unistd.h>

int main()
{
    fprintf(stdout,"C:hello fprintf\n");
    printf("C:hello printf\n");
    fputs("C: hello fputs\n",stdout);
    const char* str="system call:hello write\n";
    write(1,str,strlen(str));
    
    fork();
    return 0;
}

测试结果

1.当我们向显示器文件进行打印的时候,显示器文件的缓冲区是行缓冲,每一个字符串都有\n,fork之前所有数据都已经被刷新,有第一次的结果出现

2.当进行重定向后,磁盘文件的缓冲区是全缓冲,遇到\n不再刷新,也意味着缓冲区变大,这些字符串不足以将缓冲区写满让其刷新,fork执行的时候,数据仍然存在缓冲区中

3.我们目前谈的缓冲区和OS无关,只和c语言有关

4.当进程退出的时候,一般都要刷新缓冲区,哪怕没有满足对应的条件

5.fork之后立马退出,当一个进程退出的时候,刷新缓冲区,此时就要发生写时拷贝

缓冲区实际上分为用户缓冲区(C语言提供)和文件缓冲区

上面所谈以及日常使用最多的其实都是C/C++提供的语言级别的缓冲区,上面所说的刷新规则也都是C语言缓冲区的刷新规则

上文所说的刷新,就是把C缓冲区的数据拷贝到文件缓冲区

假如我们调用printf的函数,将要打印的数据拷贝到C语言缓冲区,这一函数就返回了

然后进行刷新,调用write函数,将C缓冲区的数据拷贝到文件缓冲区

如果直接调用系统调用write,它没有语言缓冲区,所以是直接写到文件缓冲区的

至于从文件缓冲区拷贝到磁盘这一刷新,就要视系统而定了

那么C语言的缓冲区具体是在哪?当调用C的文件操作的时候,返回值都是FILE,所以FILE结构体里不仅含有fd,还必定含有缓冲区

以上就是对打开文件的大概介绍,接下来谈谈在磁盘中存储的文件

虽然磁盘中的文件没有被打开,那要不要对其进行管理呢?答案是肯定需要的

对于这部分文件,最核心的工作就是对其进行快递定位,怎么做到?路径

在了解如何管理磁盘文件中,我们要先大概了解一下磁盘的物理结构抽象成逻辑结构

磁盘的物理结构和逻辑结构

众所周知,磁盘是一种硬件结构,那么如何存储数据呢?

下面是磁盘的物理结构示意图

一个盘面可以有很多的同心磁道,一圈磁道可以有很多相同的扇区

扇区是最小的存储单元,通常为512字节,即4kb

我们想要向一个扇区进行写入,那么要怎么寻址呢?

我们可以将磁盘结构进行逻辑抽象,这样对磁盘文件的管理就变成了数组的管理

所用的寻址方法是CHS

而文件系统通常以文件块为基本单位,一个文件块通常为4kb

对磁盘中数据的管理和上面类似

管理工作当然没有那么简单,我们肯定要对磁盘进行分类,就和C盘D盘一样

假设总共磁盘有500GB,对其进行分区分为100,100, 100, 200GB的内容

各个分区中又由2GB大小的group组成以及一个启动块Boot Block

这样只要我们管理好着2GB空间,就管理好一个分区,进一步管理好了整个磁盘文件

怎么管理好2GB大小的空间呢?当然是要了解这个块里的组成

文件的属性和数据分开存储,属性存在iNode中,数据存在Data Block中

这里以新建一个文件的过程为例子,讲解每个区域的大概作用

新建一个文件,首先要在磁盘中的一个分区中找到一个group,利用iNode Tables查找struct iNode

 的使用情况,使用了对应位置为1,没使用为0,找到一个或者多个未被使用的struct iNode进行填充属性信息

关于存储的数据,通过块位图知道哪些文件块未被使用,将数据内容填充到对应的文件块,并将文件块下标赋值到iNode里的Blocks数组,这样通过iNode就能知道哪些文件块被使用,哪些文件块没被使用

虽然Blocks数组只有15个内容,但是其中下标0-12为直接映射,13是间接映射,14为三级映射,所以能记录的数据块是很多的

iNode编号在一个分区内具有唯一性,一个分区大概有32000个iNode,文件识别系统不认文件名,只认iNode编号

这里有一个很关键的问题,一开始你怎么知道文件在哪个分区呢?通过路径的前缀判断在哪个分区

上面说的是文件,那要怎么理解目录呢?

目录本质上也是一个文件,存储的是文件名和iNode编号的映射关系

当一个文件被建立好后,对应的目录也要进行记录

软硬链接

我们先对一个目录中的文件进行硬软链接操作,得到以下结果

可以发现,硬链接的iNode编号和原本文件的iNode编号是一样的,软链接的iNode编号和原本文件的iNode编号是一样的

可以得出硬链接不是独立的文件,软链接是独立的文件

当对一个文件建立硬链接的时候,iNode里的引用计数会增加,删除的话会减少,当引用计数为0时磁盘对应文件内容被释放,即相应的iNode BitMap对应的位置改为0

软链接则是一个独立文件,因为iNode编号不同,内容保存的是指向的目标文件的路径,类似Windows的快捷方式

用户无法对目录新建硬链接操作,因为建立一个目录并进去后,发现默认有.和..两个文件表示当前目录和上级目录,这里的.就相当于已经对目录建立硬链接了

动静态库

在理解什么叫做动静态库之前,应该先理解什么叫做库?

库在日常生活中随处可见,我们之前学习的数据结构unordered_map,unoredered_set就需要包含对应的库文件——库其实就是写来给人提供服务的,里面包含各种函数从而实现了强大的功能

根据不同用法又将库分为静态库和动态库

静态库

静态库简单来说就是将库里面的代码拷贝到可执行程序的代码区,程序运行的时候就不再需要静态库了,因为程序里已经有对应的代码了

由于是直接拷贝库的内容,所以静态库就会很大

要是一个库被重复使用很多次,还是静态库,那么很多个可执行程序里都有相同的代码,造成空间的浪费

动态库

动态库简单来说就是可执行程序拿到动态库的首地址,在可执行程序运行的时候再去拿地址去链接库文件进行使用

动态链接的程序加载的时候,不仅程序要被加载,被链接的库也要进行加载,被映射进虚拟地址空间的共享区

动态库的本质:将重复代码放到一个地方,以节省空间

动态库因为不是直接在可执行程序里的,所以链接的时候就要去找库

本人理解:感觉动态库就和指针一样,需要用的时候通过它去找,代码只存在一份,不会占用空间

                  静态库是直接拷贝的,占用空间更大

静态库把方法加载到可执行程序中,加载几个可执行程序就在内存中有几个静态库的内容,动态库第一次调用加载到内存中,以后在继续使用这个库的可执行程序可以直接找到

静态库和动态库的生成

静态库的生成

写出mylib.h代码

#ifndef __MYLIB_H__
#define __MYLIB_H__ 
#include<stdio.h> 
void print();

#endif

写出mylib.c代码

#include"mylib.h"
void print()
{
    printf("Hello Linux!\n");
}

写出main.c代码

#include"mylib.h"
int main()
{
    print();
    return 0;
}

生成静态库第一步,先生成目标文件: gcc -c mylib.c -o mylib.o

第二步,用目标文件生成静态库:ar -rc libmylib.a mylib.o
        ar是gnu归档工具,rc表示(replace and create)

可以查看静态库中的目录列表:ar -tv libmylib.a

t:列出静态库中的文件      v:verbose 详细信息

第三步运行main.c得到结果。 gcc main.c -L. -lmylib

-L 指定库路径
-l 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行。

生成动态库

还是上面的代码为例子

生成动态库第一步,先生成目标文件: gcc -fPIC -c mylib.c

2.生成动态库
  • shared: 表示生成共享库格式
  • fPIC:产生位置无关码(position independent code)
  • 库名规则:libxxx.so

第二步,用目标文件生成动态库:gcc -shared -o libmylib.so *.o

3.使用动态库
  •  编译选项
  • I:链接动态库,只要库名即可(去掉lib以及版本号)。
  • L:链接库所在得路径。

第三步运行main.c得到结果。 gcc main.c -o main -L. -lmylib

可以看看这篇文章:https://blog.csdn.net/qq_44918090/article/details/118185830?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522170859497116800180637536%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=170859497116800180637536&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-28-118185830-null-null.nonecase&utm_term=linux&spm=1018.2226.3001.4450

一些补充:

总结

以上就是今天要讲的内容,本文仅仅简单介绍了进场基础IO的使用,而系统编程提供了大量能使我们快速便捷地处理数据的函数和方法,我们务必掌握。这篇文章写了挺久,如有错误欢迎讨论,也请大家多多支持!!!

举报

相关推荐

0 条评论