计算机概念之内存与存储

266 阅读17分钟

序言:

2022年,毕业也快2年了。首次决定在互联网分享一些自己的学习总结与感悟,当然有很多摘录出于书籍或者其他人的博客,我会尽可能表明出处(选择掘金的原因是看同事好像都有写,无脑抄作业哈哈)。其实我从高中时代起就一直有笔记/错题整理的习惯,各种漂亮的笔记本,现在还有几本珍藏呢。本科期间也还是有写纸质笔记的习惯,不过比较有价值的就是汇编笔记了吧,其他高数/电路/数模电/C/控制都很拉跨(乖巧的好学生/理工女)。读研之后意识到自己得走计算机方向了(本科太迷茫了,如果能重来ε=(´ο`*)))唉),重新学习C/C++/数据结构,跟着视频敲demo学java的红色笔记本算是最后纸质笔记了。工作后,从事了Android Framework 方向,和刚开始以为的写业务的程序员完全不同,大量的时间都在解决BUG,一直在学习深挖各种知识,很快就开始记云笔记,各种链接/CV文字。最开始是公司的XX平台,然后有些不想公开的就用了有道云笔记,后来又转战公司的XX平台(类似Notion,低配版本哈哈),然后意识到互联网行业不可能在一家公司呆多久呀,虽然现在不太想跑,不算舒适圈,但工作比较有挑战性,也算有发展?(好怕35,哦不,说不定性别为女会让我更早下岗o(╥﹏╥)o),所以比起杂乱的云笔记,索性整理一些有价值的文章公开了~

第一篇博客还是谈谈近期感悟最深的,对内存与存储概念的一些认识吧。起因就是室友某天和我说突然发现不能在函数里return局部对象,我很震惊,当然不行啊(超出作用域了),当时就给她分享了最简单的JAVA堆栈的内存模型视频,她也理解了(虽然她写C的)。当时达成的共识就是,简单分成堆、栈、方法区(特殊堆,对应C++中也是存放静态变量,常量类似的)。栈是线程独有,每个函数执行完就释放了,包括构造函数的生成与释放;堆就是new出来的对象(非基本数据类型)呆的地方,通过赋值建立联系,java中是JVM自动释放,而C/C++得手动delete(malloc得free)

然后她就疑惑我们写代码申请的内存空间对应的实物是什么?RAM?因为近来狂补的操作系统基础,自以为了解后给她科普一波(比较合适的说法是虚拟内存,非得说实物,那就是主存+硬盘呀,由OS调度分配),结果她还是一脸懵逼(还是我没理解呀),所以很想以内存与存储两个概念为起点梳理梳理近来接触到的很多知识。不过在此之前,先简单回忆梳理一波计组基础概念^_^

1 计算机硬件组成

image.png • CPU

主要包括算数逻辑单元(ALU),控制单元(CU),寄存器,缓存(Cache)和存储管理单元(MMU)

•  内存储器

(1)RAM Random Access Memory 主存,和CPU直接交互,掉电易丢失

(2)ROM Read Only Memory 只读存储器,存放BIOS,用于启动

• 外存储器

磁盘,包括硬盘和固态等

• 输入输出设备

键盘,显示器,打印机,联网通讯设备……

上述的硬件模块都是我们人眼所见的,很清晰的可以构建基本模型。那具体是如何工作的呢?用How a CPU works视频讲解为例。

image.png

CPU与RAM之间通过三类总线相连接。其中,地址线用于索引,数据线中存放以下几种:

• 数据

• 指令

这里的指令集基于不同CPU架构而言,我觉得可以从两个角度来理解

(1)早先的汇编语言就是人所能理解的对应指令集,比如8086的六大类92条。汇编代码是机器代码的文本表示,给出程序中的每一条指令。CSAPP中有写到我们的源代码都是文本文件,其他都是可执行文件,而文本字符普遍使用ASCII标准;汇编代码是机器代码的文本表示,给出程序中的每一条指令,然后通过汇编器和链接器生成可执行文件。因此,这里汇编虽然也是文本,但是对应的机器码应该是由汇编器以及具体CPU啥等决定的,与ASCII无关

(2)当然我们现在各种高级语言,主要分为编译型和解释型和脚本型三大类。对于常见的编译型语言,比如C/C++,通过编译(预处理,编译(生成汇编语言),汇编(解释汇编语言得到目标文件.o),链接(库文件))的产物直接是可执行文件(window上的.exe,Linux上的.elf)。了解基本流程基础上,然后我们可以利用一些反编译/反汇编的工具来更加了解程序的运行,比如一些NE问题的分析(虽然我两次连addr2line都没用成功,因为发现直接搜函数名更香哈哈)。我们android相关的字节码文件,需要依赖jvm(.class)或art/davik(.dex)虚拟机来解释执行,我们也可以利用javap等来分析字节码文件。另外还有一种对apk文件的反编译概念,通常是说将压缩包中的dex字节码反编译成java源文件。总体上都是一种逆向工程的思想,不过都没咋用过,只是了解一点点概念,所以写的有点乱(不能再纠结了,还是得先会用啊o(╥﹏╥)o)

最近看到对解释型语言的定义,即不能由cpu直接运行且依赖运行环境(一个运行程序的程序)。这也就说明了为media一般都是底层用c/c++写 ,因为快呀。毕竟解释型语言先执行的是解释器(运行时环境)的程序,然后解释器再执行你写的程序。突然想起来之前专门搜过JAVA是编译型语言还是解释型语言,现在更偏向于理解是编译( javac 将高级语言转换成低级语言)+ 解释运行(java 依赖JVM) 总之!一切皆软件,都是用程序写的呀!特别是最近才意识到原来所谓的虚拟机,编译器等等,和OS一样是程序写的!不过OS确实也是有更多和硬件直接打交道的工作,以及他封装抽象syscall的API给我们用~还有前几天看到的一个段子,我们不生产代码,我们只是BUG搬运工(Doge),怪不得OS只相信自己,认为所有应用程序都是不定时炸弹,因此分内核用户态,设置各种权限哈哈哈

• 地址

视频中说是用于跳转到外设地址输出,不过感觉就是我们“指针”的来源呀,以及C++中的引用(指针常量,指向的地址是不可改变的,但地址里的内容可以变,所以引用得初始化),以及JAVA引用(类似指针?)

• 字符

视频说的很直接,人规定的。其实就是各种编码规范,比如通用的ASCII,UTF-8等

当然简单来理解,内存中就是存放的是数据和代码啦

image.png

2 计算机软件分类

2.1 系统软件

(1)操作系统

(2)语言处理程序 汇编以及各种语言的编译,解释程序

(3)数据库管理系统 界面工具程序

2.2 办用软件

3 操作系统

那么什么是OS?

3.1 从功能角度的定义

(1)从应用程序角度 OS是一个控制软件;控制应用程序如何执行;为应用程序提供各种服务(IO,网卡等);杀死应用程序。总之,使得不同应用程序很好很方便的执行完成工作

(2)对内实现而言(kernel) 资源分配的分配器;管理外设,分配资源(CPU、内存、IO)。总之,作为管理者对底层的管理、控制与提供服务

image.png

在计算机内部,OS将CPU虚拟化成进程,磁盘虚拟化成文件,内存虚拟化成地址空间,来给我们应用程序使用(这个总结绝了,有关从CPU说起引出进程与线程相关概念的文章,很形象~)

附另两句对于进程解释:一段静态的程序,通过OS在内存中利用CPU的动态执行过程;一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程

3.2 从OS kernel角度,对硬件管理

(1) CPU调度器

CPU的管理,进程、线程的管理

(2) 物理内存

可以记忆储存的封装元器件(这是我个人依据博客里寄存器与内存的诞生强行定义,看看就好,哈哈)

(3) 虚拟内存

使得应用程序认为它有连续的可用的内存(一个连续完整的地址空间)。实际上,它通常是被划分成多个物理内存碎片,还有部分暂时存储在磁盘的存储器上,在需要时进行数据交换。

附:手机上每个进程在虚拟机上的堆内存大小可以通过adb shell getprop dalvik.vm.heapsize  

(4)文件系统

用于持久化存储的系统抽象,让数据更方便的存放在硬盘上

(5)中断处理与设备驱动

4 内存层次

4.1 内存的层次结构

尽管由于OS的管理,我们程序所看到的是连续的逻辑地址空间,比如以下所示我们的源代码是如何执行的示意图。内存中的指令是从磁盘中保存的可执行程序加载(Loader)过来的,磁盘中可执行程序是编译器生成的。

image.png 那么内存,或者说CPU所访问的指令/数据到底存储在什么地方呢?如下所示,可以看到CPU内部的寄存器和Cache最快(这里CPU应该是三级缓存),但是容量小;而主存(物理内存)容量虽然大,但是CPU运算极快,当多个程序申请的内存很大,则需要将部分程序放在硬盘上,这时就需要OS进行一定的管理工作;当然由于主存掉电易丢失,持久化的数据也得写入硬盘才能保存

image.png 4.2 java内存层次

上述是实际计算机体系的内存缓存机制,从java语言开发的角度来说 在理解堆,栈的基础上,把握一点点的缓存机制就好。因为在java并发三大特性中,“可见性”就是针对这里缓存而言的,比如demo:

    static boolean run = true;
    public static void main(String[] args) {
        new Thread(()->{
            while (run){
//                System.out.print("a  ");<<<看实现!!print的write中被sychronized修饰了!!!!我说一开始咋停了
//                try {
//                    Thread.sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }

        }).start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        run = false;  

    }

会发现上述的子线程并没有停止!原因就是Java中的缓存机制,具体如下:

image.png

(1)初始状态, 子线程刚开始从主内存读取了 run 的值到工作内存;

(2)因为子线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中,减少对主存中 run 的访问,提高效率

(3)1 秒之后,main 线程修改了 run 的值,并同步至主存,而子线程是从自己工作内存中的高速缓存中读取这个变量的值,结果永远是旧值(这里的解析也照搬的,应该不是过时的技术把==)

对于这里的问题的解决,其实只要加一个关键字volatile(可见+有序)就可以,sychronized(可见+有序+原子)当然也行,另外原子类ok(原本以为不行,因为重点不是多线程访问,而是变量本身的读取问题,结果发现它的实现就是updated atomically,因为用了volatile修饰value),还有其他合适加锁,线程安全问题还得整理todo

5 磁盘的分区与挂载

首先说说对于挂载,分区,文件系统,目录这几个概念简单理解。linux操作系统将所有的设备都看作文件(有着比如/dev/sda的名字,也就是某一块磁盘分区的名字),我们要访问存储设备中的文件,必须将文件所在的分区挂载到一个已存在的目录上,然后通过访问这个目录来访问存储设备。也就是说通过挂载实现目录树与文件系统相结合。\

挂载时使用mount命令【决定不同路径对应到哪个设备的机制】

格式:mount [-参数] [设备名称] [挂载点]

同一个设备可以有多个挂载点,同一个挂载点同时只能加载一个设备

mk /mnt/winc mount -t vfat /dev/hda1 /mnt/winc

其中,-t<文件系统类型> 指定设备的文件系统类型

其实一直隐隐有点疑惑三个问题,咨询了底层小伙伴,答复如下。

Q1:为什么非得挂载?到底要干啥?(其实因为是一直看到开机启动中vold输出log,对一些目录有操作,为啥?不过小伙伴的解释太理论==)

A:不挂载就是个存在磁盘上的binary,也可以读,但是不友好,就是二进制的读取,mount之后,就有了文件系统,通过文件系统来读取磁盘上的文件,你才能读到目录啊、文件啊这些东西。kernel也可以直接读取磁盘,不过没啥实际意义,磁盘上的信息是按照某个文件系统的管理形式组织的,把磁盘挂载一下,指定下文件系统,通过文件系统来读取磁盘上的数据,才能读到有意义的数据,才能看到目录、文件,这些文件系统中的概念,不然磁盘上的数据就仅仅是二进制的binary

Q2:什么系统分区?为什么我df -h在mount on 上没有看到“/system”,但是看到了“/cust”等,应该就是大家常说的cust分区什么的,所以系统分区是不是就是哪个"/",不是说根目录必须要挂载么?

A:在开启动态分区的设备上,system是super分区的一个逻辑分区,super分区跟上面的cust是一个概念,在分区表里单独存在的,super分区下面又有system、system_ext、product、vendor和odm这几个

个人附:结合PKMS在构造函数中的“system partitions”实际扫描的是以上几个分区,大致理解这里的系统分区也只是一个抽象概念(手机系统)

此外,如果从ls可cd目录的角度,会发现挂载成功后,我们的“/system”就是系统分区啦,两个角度来考虑吧,大概。

Q3:inode相当与是一个文件的标识一样的,存储一些元信息啥的,既然也是数字,为什么又要用fd来操作呢,每次open啥对应的不都是fd么?

A:inode是底层具体的文件系统用来管理文件系统的数据结构,fd是vfs(也可能是操作系统层面?)使用的,通过fd对文件操作,经过具体的底层文件系统实际上还是通过需要经过inode的

个人附:据小伙伴说,文件系统是磁盘之上,kernel之下的一个分层概念;其实我本来想再问文件系统不就是你kernel的一部分么o(╥﹏╥)o,后来又看了一些博客视频,目前倾向于理解为:

Linux对内的实现(内核空间)中,对计算机中的数据硬件资源的管理都是以文件来进行的(一切皆文件),反映在Linux上的文件形式包括:普通文件、目录文件(文件夹)、设备文件、链接文件、管道文件、套接字文件等等。而所有应用对文件的读写访问都是通过VFS(虚拟文件系统,存在于内存中,linux文件系统的核心)来抽象实现的。OS就是将复杂的东西归纳抽象出API(open close),提供给用户,用户拿到这些接口,针对不同的文件系统使用,屏蔽底层具体文件系统的差异性。我们的应用程序直接open(fd,...)就可以,不用关心具体的文件系统类别。类似HAL也是一样的思想~

6 Android 存储相关概念

预警:

以下内容基本为google权限应用数据和文件,以及网上其他一些总结的强行摘录,所以涉及逻辑不一致的地方请看原文~

Android中内存存储与外部存储概念

在4.4之前,手机自带的存储卡是内置存储,外部的扩展SD卡为外部存储。从4.4开始我们手机的”存储空间“扩展到16G,32G,乃至现在的128G,256G,此时的这里的机身存储从概念上别分成了”内部存储internal” 和”外部存储external” 两部分,此时再插入sd卡则会有其他路径(可能是storage/XXX/,不过现在手机都不需要了)了,看这里有一些介绍。

image.png 外部存储空间中的公共目录

外部存储空间已经为用户默认分类出一些公共目录【就是我们文件管理app”手机“栏下的显示”内部存储设备“各个文件夹】基本包括以下目录

1. Alarms  AppTimer Download Movies Notifications Podcasts  dctp ramdump

2. Android DCIM   XXX    Music  Pictures      Ringtones miad sogou  

从可cd目录(以硬/软链接到同一目录下)来看

(1)/storage/emulated/0   (2)/sdcard (3)/mnt/runtime/XXX/emulated/0  ,其中XXX对应有default,full,read,write不同权限 (4)/data/media/0 (5)/mnt/sdcard ....

一旦开启分身空间后,上述0所在会相应有10等目录(多用户),有关为啥会有这么多目录,see后半

Media简述

• 指可共享的媒体文件(图片、音频文件、视频)

• 访问的方法是MediaStore API

• 在Q+版本上访问其他应用的文件需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 权限

• 其他应用需要 READ_EXTERNAL_STORAGE 权限来访问 • 相关集合

(1)图片(包括照片和屏幕截图)

MediaStore.Images  存储在 DCIM/ 和 Pictures/

(2)视频 

MediaStore.Video 存储在 DCIM/、Movies/ 和 Pictures/ 目录中

(3)音频文件 

MediaStore.Audio  存储在 Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/ 和 Ringtones/ 目录中,以及位于 Music/ 或 Movies/ 目录中的音频播放列表中

(4)下载的文件  

MediaStore.Downloads ( API 级别 28(P)及更低版本中不可用) 存储在 Download/ 目录中\

Android 存储类型概览

(1)内部App私有数据

/data/data/{pkg}/... 可以使用 getFilesDir() 或 getCacheDir() 方法

(2)外部App私有数据

/sdcard/Android/data/{pkg}/...

可以使用 getExternalFilesDir() 或 getExternalCacheDir() 方法\

(3)共享数据 /sdcard/Downloads/...

数据和文件存储概览

与其他应用共享特定文件\

Android 中的权限****

主要受限意图

(1)受限数据,例如系统状态和用户的联系信息

(2)受限操作,例如连接到已配对的设备并录制音频

权限类型

(1)安装时权限

包括普通权限签名权限

其中,Normal包含了网络,震动,NFC,指纹,传感器等等,Normal权限只要在app的Manifest里声明,在安装时就会被赋予

(2)运行时权限

也称为危险权限,应用需要先请求运行时权限,用户授予后才能访问受限数据或执行受限权限

其中,Dangerous权限包含了读写外置存储,打开相机,蓝牙,定位,短信,电话,联系人等等

具体实现上,就是通过/mnt/runtime/XXX/emulated/这几个目录,实现对app读写sdcard的(运行时)权限的控制 (3)与平台和oem相关的特殊权限

附加:

当然有一些应用不得声明不需要或不使用的权限,如声明权限的替代方案

Android 10存储行为变更

引入分区存储(Scoped Storage)概念

以Q+版本为目标平台的应用,其访问权限范围限定为分区存储。此类应用无需请求任何与存储相关的用户权限,即可查看外部存储设置内以下类型文件:

• 特定于应用的目录中的文件(通过getExternalFilesDir()访问)

• 应用创建的照片、视频和音频片段(通过媒体库API访问)media  

但应用可以在manifest中声明requestLegacyExternalStorage停用分区存储\

Android 11存储行为变更

增强了分区存储(默认开启FUSE)

• 强制对R+版本为目标平台的应用执行分区存储(即在R+平台上,targetSdk为30的app,上述的“requestLegacyExternalStorage”会被忽略,强制分区存储了)

• 禁止应用访问其他应用的专有数据(Android/data下),其他限制

• 支持通过媒体库 File API创建照片、视频和音频片段

• 禁止应用向sdcard共享存储目录写入任何文件(Android S+)

其他

Android支持的文件系统类型

ext4 vfat f2fs sdcardfs fuse

sdcard权限管控实现原理

Mount namespaces

FUSE(File system in userspace)

FUSE(Filesystem in Userspace)\

7 计算机系统的抽象与缓存 image.png 最后附上书中给出的抽象概念。文件是对I/O设备的抽象;虚拟内存是对程序存储器的抽象;进程是对一个正在运行的程序的抽象;虚拟机是对整个计算机的抽象。类似面向对象的三大特性中,抽象(封装),继承与多态,抽象也是一门基础与艺术啊,而缓存这个概念,小到我们自己的一个hashmap,大到CPU内部的工作机制,真是计算机科学与技术呀~

有错误希望大家给出宝贵意见,感谢感谢~

reference

《操作系统》课程

《鸟哥的Linux私房菜》

《深入理解计算机系统》

原来磁盘分区是这么回事,这回就都懂了

Android中的内部存储与外部存储

Android 存储用例和最佳做法