【基础IO下】磁盘/软硬链接/动静态库
前言:
文件分为内存文件和磁盘文件。磁盘文件是一个特殊的存在,因为磁盘文件不属于冯·诺依曼体系,而是位于专门的存储设备中。因此,磁盘文件存在的意义是将文件更好的存储起来,一边后续对文件进行访问。在高效存储磁盘文件这件事上,前辈研究出了十分巧妙的管理手段及操作方法,而这些手段和方法共同构成了我们今天所谈的文件系统。
文件系统是操作系统中负责管理持久数据的子系统,简单点就是负责把用户的文件存到磁盘硬件中,因为即使计算机断电了,磁盘里的数据并不会丢失,所以可以持久的保存起来。
文件系统的基本数据单位是文件,它的目的是对磁盘上的文件进行组织管理,组织方式的不同,就会形成不同的文件系统。
1.认识磁盘
1.1 物理结构
磁盘属于外设,是一个机械结构,所以相对于CPU和内存而言,磁盘的速度非常慢。
如上图所示:
主轴和马达电机:在主轴上套着多张盘片,它们和轴相固定,通过马达电机来驱动这些盘片一起转动。
磁头:每一张盘片都有两个盘面,每一个盘面上都有一个磁头,该磁头是用来向磁盘中读写数据的。多个磁头也是叠放在一起的,它们是整体一起移动的。计算机在寻址时,不是一个磁头在一个盘面上寻找,而是一摞磁头在所有盘面上、同样的磁道上寻找。
音圈马达:该马达驱动磁头组进行摆动,它可以从盘片的内圈滑到外圈,再结合盘片自身的转动,从而向磁盘读写数据。
机械设备的控制是需要时间的,因此导致机械硬盘读写数据的速度相对于CPU和内存来说比较慢。
1.2 存储结构
1.2.1 数据存储
数据是以0和1的方式进行存储的,常见的存储介质有:强信号与弱信号、高电平与低电平、波峰与波谷、南极与北极等,而盘面上比较合适的是南极与北极。
当磁头移动到指定位置时,向磁盘中写入数据:N->S,删除磁盘中的数据:S->N。磁盘中数据读写的本质:更改基本元素的南北极、读取南北极。
注意:磁头并非与盘面进行直接接触,而是以15纳米的超低距离进行磁场更改。
这个距离相当于一架民航客机距离地面1米左右的距离进行超低空飞行,所以如果磁头制作工艺不够精湛,可能会导致磁头在写入/读取数据时,与盘面发生摩擦(高度旋转)发热,从而导致磁场消失,该扇区失效,数据丢失。
所以机械硬盘不能进入灰尘,也不能在其运行时随意移动,因为角度的偏转也有可能导致发生摩擦,造成数据丢失,更不能用力拍打机械硬盘。
1.2.2 存储结构
在磁盘的盘面上,磁盘被一个个的同心圆以及射线进行分割,从而出现了:磁道、扇面、扇区。
- 扇区:被一个个的同心圆以及射线分割出的一个个扇形区域。
- 扇面:两条相邻的射线之间夹的所有扇区构成的扇面。
- 磁道:盘面上半径相同的扇区构成一个磁道。
- 柱面:由于现实世界中磁盘的立体结构,所以把空间中所有半径相同的磁道定义为一个柱面。即磁盘的符时图中,所有同心的磁道叫做柱面。柱面等价于磁道。
磁盘寻址时,基本单位不是bit,也不是byte,而是扇区。每个扇区的大小都是512byte。由于扇区是最小的存储单元,所以在硬件的角度:一个文件(内容+属性)无非是占用一个或多个扇区进行数据的存储。
那么在硬件上磁盘是怎么定位一个扇区呢?----答案是CHS定位法!cylinder柱面、head磁头、sector扇区。
- 磁盘中的磁头是有编号的,我们首先根据扇区所在的盘面确定使用几号磁头。
- 每个扇区都有自己所在的磁道,根据扇区所在的磁道就可以确定磁头的偏移位置。
- 每个扇区所在的扇面上都已经被编好了号码,磁头最后根据扇面所在的号码确定扇区。
磁道的周长不一样(越靠近外圈,磁道越长),但是每个磁道存储的数据量是一样的。磁头来回摆动时, 就是确认在哪个磁道。盘片旋转时,就是让磁头定位所在的扇区。
我们既然能够通过CHS定位一个扇区,那么也能定位多个扇区,从而将文件能够从硬件的角度进行读取和写入。
1.3 磁盘的逻辑抽象
由上文的内容,我们知道能够通过CHS去定位一个文件的基本单元,但是操作系统是不是采用这种方式去定位磁盘中的数据呢?不是。
这主要有以下两点原因:
1、操作系统是软件,磁盘是硬件,硬件通过CHS定位扇区,操作系统如果采用和硬件一样的定位方式就会和硬件之间产生很大的耦合关系,如果我们的硬件变了(例如:机械磁盘变为固态硬盘),那么操作系统也要进行变化,这并不是一个好的情况。
2、扇区的大小是512byte,以扇区为单位进行IO时的数据量太小了,在进行大量IO时会极大的影响到运行的速度。
操作系统实际进行IO时,是以4kb为单位的(这个大小是可以调整的)4kb=512*8byte,因此将8个扇区定义为一个块,操作系统按照一个块的单位进行IO。
磁盘片的物理结构是一个圆环型结构,假设我们能够将每一个盘面按照磁道进行拉伸展开(就像使用胶带一样),那不就变成了一个线性结构了吗?如下图所示。
展开以后对应每一个磁道里面都有很多扇区,这些扇区组合起来便可以被抽象为一个数组。
但是这个数组太大了,而且每一个单位的数据量有点太小了,我们还要对其进行抽象,我们将8个扇区组成一个块,这样数组的长度就缩短了8倍。经过这一层抽象后,由原来的扇区数组变成块数组。
其中逻辑块的数组下标被定义为逻辑块地址(LAB地址),现在操作系统像访问具体的扇区时,只需要通过起始扇区的地址+偏移量就可以获取LBA地址,然后通过特定的手段转为CHS地址,交给外设进行访问即可。LBA和CHS的转换操作的原理类似于指针的解引用,具体可参考LBA和CHS的转换。
于是操作系统通过LBA地址进行访问存储的数据,这就是操作系统对磁盘等存储硬件的逻辑抽象。因此对于外设中文件的管理,经过先描述、再组织后,变成了对数组的管理,这个数据就是task_strcut中的struct block。
最后我们就能理解为什么IO的基本单位是4KB了,因为直接读取一个数据块(4KB),这样可以提高IO效率(内存对齐)。
2.磁盘信息
2.1 具体结构
经过上面的抽象我们操作系统便拿到了一个逻辑块的大数组,但是这个数组太大了,我们对于这个大数组的直接管理还是太过于困难了。操作系统可以对这个大数组进行分区管理(类似于windows的分盘),当我们管理好了一个分区,就可以将一个分区的管理方法复制到其他分区的管理中去,从而实现全局的数据管理。
但是每个分区的数据还是太大了,操作系统还要对每一个分区进行分组,通过分组再次降低管理的难度,每个分区里面有很多分组,其中每个分区其内部的结构如下图:
Boot Block:里面存放的是与操作系统启动相关的内容,诸如:分区表、操作系统镜像的地址等,一般这个区域被放在磁盘的0号磁头、0号磁道、1号扇区中,如果这个区域的内容受到破坏,那么操作系统将无法启动。
超级块(SuperBlock):SuperBlock超级块属于整个分区,那么超级块怎么放在了Block group 0的内部呢?在文件系统里可能会有一定比例的Block group是以Super Block开头的,即分组里Super Block不是必须的,可以不要它。Super group属于整个文件系统,正常情况应该放在Boot Block的位置,在整个分区的最开始的位置。那么为什么Super Block在多个分组中都存在呢?都存在意味着备份,Super Block被保存在了不同的分组里面,内容都是一样的。如果某个Super Block损坏了,再把其他分组的Super Block拷贝过来,至此就完成了文件的恢复。
超级块存放系统本身的结构信息,记录的信息主要有:block和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。
GDT(Group Descriptor Table):块组描述符,描述块组属性信息。比如:该分组一共有多少个inode、数据块以及被使用了多少。
块位图(Block Bitmap):Block Bitmap中记录着Data Blocks中哪个数据块已经被占用,哪个数据块没有被占用。假如Data Blocks中总共有10000个数据块,那么块位图中就对应有10000个比特位,两者之间是一一对应关系。哪个数据块被占用,那么块位图中对应的比特位就由0置为1;哪个数据块被释放,块位图中对应的比特位就由1置为0。比如:要创建一个文件,假如需要10个数据块,那么就在块位图中找到对应的比特位,并将0置为1,代表将该数据块给你了,那么就可以将数据往对应的数据块中存放了。
inode位图(inode Bitmap):每个比特位表示对应的inode是否空闲可用。
inode Table:一般来说,一个文件内部的所有属性被保存在一个inode节点中(一个inode节点的大小一般是128字节),一个分区会存在大量的文件,所以一个分区中会有大量的inode节点,每一个inode节点都有自己的编号,这个编号也属于文件属性。为了更好的管理inode节点,就要有一个专门的区域存放所有的inode节点,这个区域就是inode Table,其内部可以看成一个数组结构。
在创建文件时,需要有查找功能。比如查找哪个inode以及数据块有没有被占用。inode Bitmap是inode对应的位图结构,inode Table中的每一个inode都与位图中的比特位一一对应。位图中比特位的位置代表的是第几个inode,比特位为0代表该inode没有被占用,为1代表该inode被占用。假如inode Table中有1000个inode,那么inode Bitmap中就有1000个比特位,可使用该1000个比特位来表示哪些inode被占用、哪些inode没有被占用。我们创建文件时,首先就在inode Bitmap中寻找第一个不为1的比特位,找到之后,将该比特位由0置为1,再拿比特位的偏移位置,再在inode Table中找到对应的inode,再将文件属性填写进去。
数据区(Data blocks):里面是大量的数据块,每一个数据块都可以用来存放文件内容。
细节注意要点:
1、每一个块组都有Block Bitmap、inode Bitmap、inode Table、Data block,其他部分某些块组可能没有;
2、Super Block在每一个块组里都可能存在也可能不存在,但至少要有一个块组存在超级块!而且每一个存在超级块的块组里面的超级块是一样的,并且所有存在超级块的块组其里面的超级块是联动更新的;
3、超级块存在多份的意义是:万一其中一个超级块损坏,还有其他超级块可以使用,并且可用利用其他完好的超级块去修复以及损坏的超级块。不至于一个超级块损坏导致整个分区的文件系统直接损坏;
4、inode节点中有一个数组,这个数组里面存放了对应文件使用的数据块的编号;
5、inode编号不能跨分区使用,每一个inode编号在一个分区内唯一有效;
6、根据inode可用确定一个分区的分组。
假如我知道某个文件的inode编号,首先查找inode Bitmap,确认inode Bitmap的比特位是0还是1,是1说明该inode是有效的。然后查找inode Table,对比发现该文件的inode编号与inode Table中的编号是一样的,所以这个文件的属性就拿到了。
2.2 重新认识目录
在Linux的命令行中,我们可用使用ls -li命令查看文件的inode编号:
其实在Linux中,系统对于文件只认识inode编号,并不认识文件名,那么为什么我们平时一直使用的都是文件名,没有使用过inode编号,但是我们依旧可以操作文件呢?
这其实和目录有关,我们打开的任意一个文件都是在一个目录里面打开的,而且目录本身也是文件,目录也有inode编号,目录里面也有内容,也需要数据块,目录里面的内容是:该目录下文件名与该文件的inode映射关系。
因此当我们使用文件名时,目录会自动帮我们找到对应的inode编号,完成相应的要求。
例如我们在Linux下使用cat xxx.xx命令,其大致的执行过程是:
1、在目录下找到log.txt的inode编号;
2、利用inode编号在inode Table中找到inode;
3、根据inode找到xxx.xx文件使用的数据块的编号;
4、根据数据块的编号找到数据块,将内容提取出来并刷新到显示器上面。
注:如果xxx.xx文件使用的数据块的编号有4个,那么就有4个数据块,将该4个数据块进行组合,该文件的内容就找到了。
3.理解文件系统中的增删改查
查:见上一章节中的cat xxx.xx文件的例子。
删:
1、根据当前要删除的文件名到目录中找到对应的inode编号;
2、根据inode编号到inode Table中找到inode节点;
3、根据inode节点中的内容找到该文件对应的Block Bitmap,然后将对应的比特位进行置0表示内容的删除;
4、根据inode编号将inode bitmap对应的比特位置0表示属性的删除;
5、将当前目录中inode编号与文件名的映射关系进行删除。
在任意文件系统里,要删除一个文件,根本不需要将文件的属性和内容清空,而是采用惰性删除的方式,只要找到该文件的inode在inode Bitmap中的比特位,将该比特位由1置为0,文件就删除了。
增:(创建一个内容为空的文件)
1、操作系统在inode bitmap中从低向高依次扫描,将找到的第一个比特位为0的位置置为1;
2、然后在inode Table中的对应位置写入新的属性;
3、然后向当前目录中增加新的inode编号与文件名的映射关系。
改:
1、根据当前的文件名到目录中找到对应的inode编号;
2、根据inode编号到inode Table中找到inode节点;
3、计算需要的数据块的个数,在Block bitmap中找到未使用的数据块,并将相应的比特位由0置成1;
4、将分配给文件的数据块的编号填入inode中;
5、将数据写入到数据块中。
补充细节:
1、如果文件被误删了,该怎么办?数据应该怎么样被恢复?(我们在这里只讨论大致的原理)
答案是:最好什么都不要做,因为Block bitmap被置为0以后,相应的数据块就已经不受保护了,此时再创建新文件就有可能覆盖原来的文件。
2、数据恢复的原理是:Linux系统为我们提供了一个日志,这个日志里面的数据会根据时间定期刷新,所有被删除的文件的inode编号在这里都有记录,通过被删除文件的inode编号,先把inode bitmap相应位置的比特位由0置为1,然后根据inode编号到inode Table中找到对应的数据块编号,然后到Block bitmap中将相应位置的比特位由0置为1。
3、上面我们说的分区、分组,填写系统属性是谁在做?什么时候做呢?
答案是:是操作系统在做!是在格式化的时候做的。在我们操作系统分区完成以后,为了能让分区能够正常使用,需要对分区进行格式化,格式化的本质就是:操作系统向分区写入文件系统管理属性的信息。
4、inode里面只是用数组来与数据块进行单纯的一一映射吗?
答案:并不是。如果一个inode里面存放数据块编号的数组大小是15,如果只是单纯的一一映射的关系,那么一个文件只能存储15*4KB=60KB的内容,这显然是不合理的。
所以inode里面存放数据块编号的数组被规定它的前几个下标是直接索引,中间几个是二级索引,后面几个是三级索引,...
直接索引:直接指向数据块;
二级索引:指向一个数据块,这个数据块里面的内容是直接索引;
三级索引:指向一个数据块,这个数据块里面的内容是二级索引。
二级索引对应的数据量单位:4KB/4*4KB=4MB。
三级索引对应的数据单位:(4KB/4)^2*4KB=4G。
有没有一种可能,一个分组,数据块没用完,inode没了,或者inode没用完,数据块用完了?
答案:有可能的,如果我们一直创建空文件,就可能导致inode使用完了,而数据块没有使用完;如果我们将所有的内容都放在一个文件中,就可能导致inode没有使用完,而数据块使用完了。
4.软硬链接
4.1 软链接
当有某个文件在一个很深的目录中时,我们要去使用这个文件时会很不方便。那么有没有一种方式能够让我们很轻松的找到这个文件并使用它呢?有的,那便是软链接。软链接非常类似于window中的快捷方式。
我们可以在当前目录中使用命令 ln -s 文件名 软链接名 建立一个软连接,其中软链接名可以自定义,如下图所示,mytest文件的软链接soft_mytest.link指向了文件mytest,文件类型为l,l即为链接文件。文件mytest的inode值为1182315,文件mytest的软链接soft_mytest.link的inode值为1182317,一个文件一个inode、一个inode一个文件,即soft_mytest.link有自己独立的inode,则是一个独立的文件。
那么软链接有什么用呢?
如上如所示,mytest文件的在路径 /home/userlq/dir3/dir_20/dir11下,如果我在目录userlq下该如何执行mytest可执行程序呢?如下图所示,可以通过该可执行程序的路径执行,但是该方式带了一长串的路径,如前文所述该可执行程序使用起来很不方便。
此外,创建一个软链接,可以直接在目录userlq下执行该可执行程序。
4.2 硬链接
生成硬链接文件就更简单了,对文件mytest进行硬链接,生成硬链接文件hard_myfile.link,硬链接名可以自己定义。
ln 文件名 硬链接名 不带参数默认是硬链接
可以看到,执行硬链接与执行源可执行程序没有任何区别:
而且硬链接的inode、属性、拥有者、所属组、文件大小、时间与源文件一模一样。
如下图所示,给myfile.txt文件创建了一个软链接、一个硬链接,源文件与硬链接的文件大小等属性都一样,文件大小均为0,软链接的文件大小为10。当往myfile.txt文件中写入数据后,源文件与硬链接的文件大小均变为了13,软链接大小依然为10。
此时硬链接和源文件中的内容均相同。
那么建立硬链接究竟做了什么?
建立硬链接根本没有创建新文件!因为没有给硬链接分配独立的inode。既然建立硬链接没有创建文件,那么硬链接一定没有自己的属性和内容集合,硬链接使用的一定是源文件的inode和内容。如下图所示:
目录dir12中会保存文件名和inode之间的映射关系,myfile.txt指向的inode是1182318,所以我们就可以根据inode找到文件的内容。
创建硬链接时,只是在对应的目录下创建了硬链接文件名hard_myfile.link与文件myfile.txt的inode之间的映射关系。所以创建硬链接的本质就是在指定的路径下,新增文件名个inode编号的映射关系。
如上图所示,在未创建硬链接时,myfile文件的硬链接数为1,当创建硬链接后,myfile和hard_myfile.link文件的硬链接数变为2。
文件名在当前路径下是唯一的,新建了一组映射关系,相当于有了一个新的文件名指向了源文件的inode,所以inode可能会被多个文件指向。inode有自己的计数器cont,如果有一个文件名指向inode时,计数器会++,变成1,再有文件名指向inode时,计数器会++,变成2。count引用计数表征的是有多少文件名指向我,这个引用计数叫做硬链接数。
如上图所示,当我们删除文件myfile.txt时,myfile.txt文件的硬链接hard_myfile.link的硬链接数变为了1。那么一个文件怎么样才算被真正删除呢?当一个文件的硬链接数变为0时,这个文件才算真正被删除。如下图所示,硬链接的内容依然还是 hello 中国,则证明文件myfile.txt没有被删除。
4.3 软链接和硬链接之间的区别
当我们创建一个文件时,在文件权限后面会有一个数字,这个数字就是硬链接数。我们查看一下它们的inode编号:
我们发现它们的编号并不相同,源程序的编号与硬链接编号一样,但是软链接就不一样。
区别一:
1、软链接文件的inode编号与源文件不同(独立存在),软链接文件比源文件小得多,软链接的内容就是自己所指向的文件的路径;
2、硬链接文件与源文件共用一个inode编号(源文件的别名),硬链接文件与源文件一样大,并且硬链接文件与源文件的链接数变成了2。
我们再给myfile文件创建一个硬链接,并且可以发现文件的硬链接数+1了,由2变成了3,再次证明了硬链接与源文件inode一样。
实现原理:
为什么源文件的inode编号与硬链接inode一样,并且源文件硬链接数会+1呢?与实现原理有关:
当我们创建硬链接时,操作系统在当前目录里面建立新的映射关系,操作系统把源文件的inode编号与硬链接建立映射关系,此时一个inode编号就有了两个文件名,同时inode节点中会有一个引用计数的变量ref_count。当我们建立一个硬链接时,这个引用计数的变量就会自增一下,表示硬链接数目+1:
软链接又称为符号链接,它是一个单独存在的文件,拥有属于自己的inode属性及相应的文件内容,不过在软链接的Data block中存放的是源文件的地址,因此软链接很小,并且非常依赖于源文件。
删除源文件,观察软硬链接有什么区别:
区别二:
1、当我们将源文件删除后,软链接失效,因为软链接文件依赖于源文件;
2、当我们将源文件删除后,硬链接仍然有效,因为硬链接文件是源文件的别名。
原理:
此时,我们发现1182330还存在,但是硬链接数变成1了。证明我们刚刚删除了一组映射关系,则引用计数就变成1了。
一个文件怎么样才能算被真正删除呢?当一个文件的硬链接数变成0时,这个文件才算是被真正删除。
删除myfile文件时,此时由于软链接所指向的文件被删除了,所以软链接失效了。执行./soft_myfile.link时,报错该文件不存在。实际上该文件还存在,软链接标定一个文件时,不是使用目标文件的inode标定的,而使用的是目标文件的文件名来标定的。软链接是一个独立的文件,有自己独立的inode和对应的文件内容。将文件myfile删除之后,软链接就找不到了,说明软链接有查找目标文件的方式。在树状结构中,是通过路径查找文件的。软链接是一个独立的文件,有自己的数据块,该数据块保存的是软链接所指向的目标文件的路径。删除软链接不会影响源文件,软链接相当于windows系统下的快捷方式。
当删除一个文件时,目录会正常帮我们删除文件名与inode的映射关系,但是操作系统不一定会帮我们删除该文件。操作系统会将该文件对应的inode节点中的引用计算变量自减一下,如果减完之后等于0就删除文件,否则只是修改了引用计数变量。
这也就解释了为什么删除源文件后,硬链接文件不受任何影响,仅仅只是硬链接数-1。同理,删除硬链接文件,也不会影响源文件。
为什么新建目录的硬链接数为2?
如上图所示,为什么新建目录的链接数为2,而新建的普通文件的链接数为1?
因为一个普通文件本身就有一个文件名和自己的inode,两者具有一个映射关系,所以默认的链接数就是1。
进入到empty目录里,里面有 . 和 .. 两个目录。empty目录的inode是1182334,. 目录的inode值也是1182334。. 目录叫做当前目录,即empty目录。. 目录就是empty目录的硬链接。
因为有两组文件名,即目录empty和 . 目录去映射inode,故empty目录的硬链接数为2。
我们在empty文件中创建了一个目录dir,我们发现目录dir的硬链接数是2,但是目录empty的硬链接数变成3了。
empty目录的inode是1182334,进入到empty目录下,该目录下的 . 目录的inode也是1182334。在empty目录下有个dir目录,该目录下有个 .. 目录,该目录的inode也是1182334。所以empty目录的硬链接数为3。
cd .. 称为回到上级目录,那么cd .. 为什么能够回到上级目录呢?因为dir目录下的 .. 指向的dir目录的上级目录empty。
在一个树状结构中,每个目录里面都有 . 目录和 .. 目录,其中 . 目录指向该目录自己,.. 目录指向的是该目录的上级目录。这就是为什么新建目录,在该新建目录下再新建目录,该目录的硬链接数会发生变化的原因。
Linux中的目录结构为多叉树,即当前节点(目录)需要与父节点(上级目录)、子节点(下级目录)建立链接关系,并且还得知道当前目录的地址,否则就会导致切换目录时出现错误。
由上图所示,操作系统拒绝了指令,操作系统不允许给一个目录建立硬链接,因为给目录建立硬链接可能导致环路路径问题。
为了避免因为用户的误操作而导致的目录环状问题,规定用户不能手动给目录建立硬链接关系,只能由操作系统自动建立硬链接。比如建立新目录后,默认上级目录和当前目录建立硬链接文件,在当前目录下创建新目录后,当前目录的硬链接数+1。
4.4 取消链接
取消链接的方式有两种:
1、直接删除链接文件;
2、通过unlink命令取消链接关系。
5.ACM时间
每个文件都有三个时间:访问Access、修改属性Change、修改内容Modify,简称ACM时间。可以通过stat查看指定文件的ACM时间信息。
可以通过stat指令查看指定文件的ACM时间信息。
这三个时间的刷新策略如下:
Access:最近一次查看文件内容的时间,具体实现取决于操作系统;
Change:最近一次修改文件属性的时间;
Modify:最近依次修改文件内容的时间(文件内容修改后,属性也会跟着修改)。
Access是高频操作,如果每次查看都更新的话,会导致IO效率变低,因此实际变化取决于刷新策略:查看N次后刷新。
注意:修改文件内容一定会导致文件的属性时间被修改,但不一定会导致访问时间被修改。因为可以在不打开文件的前提下,对文件进行操作。比如直接重定向到文件:echo "..." xxx.xx。
6.动静态库
6.1 什么是库
简单来说:库是一些可重定向的二进制文件,这些文件在链接时可以与其他的可重定向的二进制文件一起链接形成可执行程序。
一般来说库被分为静态库和动态库,它们是由不同的后缀来进行区分的。
对于C/C++来说,其库的名称也是有规范要求的,例如在Linux下:一般要求是 lib+库的真实名称+(版本号) + .so / .a + (版本号),版本号是可以省略不写的。 比如:
- libstdc++.so.6,去掉前缀和后缀,最终库名为stdc++。
- libc-2.17.so,去掉前缀和后缀,最终库名为c。
有了上面的一点基础知识,我们就能够去见一见库了,Linux系统安装时已经为我们预装C&C++的头文件和库文件。
对于C/C++头文件,在Linux操作系统中一般在/usr/include目录下面存放C/C++头文件:
对于C/C++的库文件,一般在/usr/lib64和/lib64里面,/lib64里面给的是root和内核所需so或者a之类的库文件,而/usr/lib64是普通用户能够使用的。
6.2 库的作用
提高开发效率。
系统已经预装了C/C++的头文件和库文件,头文件提供说明,库文件提供方法的实现。
1、头文件提供方法说明,库提供方法的实现,头和库是有对应关系的,是要组合在一起使用的;
2、头文件是在预处理阶段就引入的,程序在链接时链接的本质就是链接库。
如果没有库文件,那么在开发时,需要自己手动将printf等高频函数编写出来,因此库文件可以提高我们的开发效率。比如python中就有很多现成的库函数可以使用,效率很高。
1、我们在使用像VS2019这样的编译器时要下载并安装开发环境,这其中是在下载什么?安装编译器软件,安装要开发的语言配套的库和头文件。
2、我们在使用编译器,都会有语法的自动提醒功能,但是都需要先包含头文件,这是为什么?
语法提醒的本质是编译器或者编辑器,它会自动的将用户输入的内容,不断的在被包含的头文件中进行搜索,自动提醒功能是依赖头文件而来的。
3、我们在写代码时,我们的环境怎么知道我们的代码中有哪些地方有语法报错?哪些地方定义变量有问题?
编译器有命令行的模式,还有其他自动化的模式,编辑器或集成开发环境可以在后台不断的帮我们调用编译器检查语法而不生成可执行文件,从而达到语法检查的效果。
6.3 制作一个静态库
库的使用能够提高我们的开发效率,接下来我们制作一个库。
//my_add.h
#pragma once
int my_add(int x, int y);//my_add.c
#include "my_add.h"
int my_add(int x int y)
{return x + y;
}//my_sub.h
#pragma once
int my_sub(int x, int y);//my_sub.c
#include "my_sub.h"
int my_sub(int x, int y)
{return x - y;
}//main.c
#include <stdio.h>
#include "my_add.h"
#include "my_sub.h"int main()
{int a = 10, b = 20;printf("%d + %d = %d\n", a, b, my_add(a, b));printf("%d - %d = %d\n", a, b, my_sub(a, b));return 0;
}
程序经过预处理、编译、汇编、链接,等过程形成可执行程序。快速形成对应的文件,选项叫做esc。其中-c属于第三步,形成二进制文件,这个二进制文件无法执行,因为缺少链接的过程,.o文件为可重定位目标二进制文件。如下图所示。
通过-o命令,将所有的.o文件链接起来,形成一个可执行程序mymath。
gcc -o mymath main.c my_add.c my_sub.c 将所有的.c文件形成.o文件之后,再将.o文件链接起来。该命令的.c文件都是独立编译的,形成.o文件之后再链接。
gcc -o mymath main.o my_sub.o my_add.o 是直接将.o文件链接起来。两者是没有区别的。其中,gcc -o mymath main.o my_sub.o my_add.o 是 gcc -o mymath main.c my_add.c my_sub.c 的最后一步。如下图所示,成功执行。
我们将main.c、my_add.o、my_sub.o文件放在同一个文件夹中,my_add.h、my_sub.h两个头文件放在另外的一个文件夹中。
把main.c文件编译形成main.o文件,再将main.o与my_add.o、my_sub.o文件链接起来,形成可执行程序。此时,我们发现直接执行gcc -c main.c 指令时出错了,并提示我们找不到头文件。
编译文件时的第一步是预处理,要进行头文件的展开。main.c文件中的代码中包含了my_sub.h和my_add.h头文件,所以就使用了my_sub.c和my_add.c中的方法。由于main.c编译时,需要将my_sub.h和my_add.h头文件在main.c的源代码中展开,才能编译通过。而此时,只有my_add.o、my_sub.o文件,所以 gcc -c main.c 编译不通过。所以将头文件拷贝到test目录中,此时链接形成可执行程序mymath,并成功执行。如下图所示。
结论:如果我们想让其他人调用自己程序的一些功能,但是不想把源代码交给其他人,则可以把自己的程序经过预处理、编译、汇编,生成.o文件,即可重定位目标二进制文件,交给别人使用。
未来我们可以给对方提供.o文件(方法的实现)、.h文件(方法的声明),对方就可以进行编译了。我们可以尝试将所有的 ".o文件" 打一个包,将 ".o文件" 打好的包叫做库文件,我们给对方提供一个库文件即可。
打包就是将多个.o文件合并形成一个文件,这个文件就称为库。在打包时,根据采用的打包工具和打包方式的不同,就有了对应的动态库和静态库,库的本质就是.o文件的集合。
上面整个过程就是我们制作静态库的基本流程,当然这样的制作其实还是有缺陷的,当我们的项目文件过于庞大时,我们要给一个.c文件十几个这样的.o文件,而且文件过于分散了,不利于管理。于是我们就需要将多个这样的.o文件打成一个包,我们将这个包直接给别人,别人就能直接使用了。
打包的命令是:ar -rc [lib库名.a] [*.o]
ar:该命令用于建立或修改备存文件,或是从备存文件中抽取文件。可集合许多文件,成为单一的备存文件,在备存文件中,所有成员文件皆保有原来的属性与权限。
r:如果打包好的xxx.a库中没有xxx.o那么就会把模块xxx.o添加到库的末尾,如果有的话就会替换之(位置还是原来的位置)。
c:建立备存文件。
成功编译并成功发布。
编译通过,并成功发布。此时,头文件中包含了两个.h文件,库文件中包含了.a文件。此时,就将库成功发布了。
但是,库发布出来之后,怎么才能让用户去使用呢?
使用 tar czf mylib.tgz mylib 命令将mylib文件打个包形成文件 mylib.tgz,然后将该库放到yum资源中,用户就可以使用yum来下载该库并使用了。
用户将 mylib.tgz 下载并解压,此时就有了mylib库了,就可以使用了。
6.4 静态库的使用
6.4.1 通过指定路径使用静态库
在我们实际使用库时,我们一般将头文件放在一个目录里面,将库放到另外一个文件里面,这样便于我们进行分类管理。我们也按照这种标准化的做法,来整理一些我们的目录结构,如下图所示。
此时,我要编译文件main.c时,发现找不到头文件。为什么呢?因为gcc也是一款编译器,编译器在搜索头文件时,默认 ①在当前目录下搜索;②在系统默认的指定路径下搜索。当前路径就是和源代码在同级的路径下,而这里的头文件在当前目录的mylib目录下,此时编译器找不到对应的头文件,在系统的头文件中也找不到。
此时,可以给gcc指定搜索路径,-I 后面的路径包括所有的.h头文件。后面依然报错,而且是链接报错。出链接报错就说明已经完成了预处理、编译和汇编的过程。头文件已经找到了,但是库文件并没有找到。在形成可执行程序时,要使用库文件时,要告诉我要使用的库文件所在的路径。
-L ./mylib/lib 告诉gcc编译器,库文件在 ./mylib/lib 路径下。这里依然会报错, 如果要链接第三方的库,必须指明库名称。就是你得告诉编译器我要链接的是哪个路径下的哪个库。比如:./mylib/lib 路径下的 libmymath.a。我们以前写代码时,从来没有指明过库名称。这是因为我们以前写的代码没有使用过任何的第三方库,用的只是C和C++语言提供的标准库。gcc和g++默认就能确定你要链接的是哪个路径下的哪个库(C/C++提供的库)。
即,别人提供的第三方库,我们在使用时,不仅要指定路径,还要指明库的名称。C/C++语言提供的库我们不需要指明名称就可以编译。
-l 库文件名(这里的库文件名指真实名称),库文件名为 mymath。去掉前缀lib和后缀.a,剩余的就是库文件名。
这串命令可以带空格,也可以不带空格,如上图所示。但是一般情况下,不带空格。
形成一个可执行程序,可能不仅仅依赖一个库。假如形成一个可执行程序要依赖100个库,其中70个是动态库、30个是静态库。请问怎么链接?
gcc默认是动态链接的(gcc的建议行为),对于一个特定的库,究竟是动态链接、还是静态链接,取决于你提供的是动态库还是静态库。假如给gcc提供一个静态库,那么gcc只能将该静态库拷贝到对应的可执行程序中。假如动、静态库都提供呢?那么选择权就在gcc上了,gcc想怎么链接就怎么链接。
假如在链接时,既有动态库、又有静态库。只要有一个动态库,我们的软件就是动态链接的。
6.4.2 将头文件和静态库文件安装至系统目录中
除了这种比较麻烦的指定路径编译外,我们还可以将头文件与动态库文件直接安装在系统目录中,直接使用,无需指定路径。(需要指定静态库名)
所谓的安装软件,就是将自己的文件安装到系统目录下。
以上操作就叫做安装,安装的本质就是拷贝。
注意:将自己写的文件安装到系统目录下是一件危险的事(会导致系统环境被污染),用完记得手动删除。
总结:第三方库的使用
1、需要指定的头文件和库文件。
2、如果没有默认安装到系统gcc、g++默认的搜索路径下,用户必须指明对应的选项,告知编译器:①头文件在哪里;②库文件在哪里;③库文件具体是谁。
3、将我们下载下来的库文件和头文件拷贝到系统默认路径下,在Linux下就是安装库。对于任何软件而言,安装的本质就是拷贝到系统特定的路径下。
4、如果我们安装的是第三方的库,我们要正常使用,即便是已经全部安装到了系统默认路径下,gcc、g++必须用 -l 指明具体库的名称。
5、无论我们是从网络中直接下载的库,还是源代码(编译方法)。都会提供一个make install安装的命令,这个命令所做的就是安装到系统中的工作。我们安装大部分指令、库等等都是需要sudo提权的。
6.5 制作一个动态库
动态库:动态库不同于静态库,动态库中的函数代码不需要加载到源文件中,而是通过 与位置无关码,对指定函数进行链接使用。程序在运行时才会去链接动态库的代码,多个程序共享使用库的代码。
动态库的打包也同样分为两步:
①编译源文件,生成二进制可链接文件,即将所有的源代码变成.o文件。与形成静态库的.o文件相比,此时需要加上 -fPIC ,-fPIC的含义是在形成.o文件时,生成与位置无关码。
②形成.o文件后,再将其打包。静态库就是通过ar归档工具,将.o文件进行归档。
1、将源码文件编译形成.o二进制文件,此时需要带上 -fPIC 与位置无关码
2、借助gcc/g++,将所有的.o文件打包为动态库
gcc加上-shared默认帮我们形成动态库,加上-shared表示我们不想形成可执行程序,而是想形成动态库。
6.6 动态库的使用
下面我们尝试用动态库去链接形成可执行程序:
注意:我们自己写的库是属于第三方库,我们编译时要指明:头文件路径、库文件路径、库文件名(真实名称)。
在运行mymath可执行程序时,发生了错误。系统提示我们程序运行时,没有办法找到动态库,这是为什么呢?
这就和动态库的特性有关了,由于采用动态库的程序在运行时才去链接动态库的代码。多个程序共享使用库的代码,所以运行的程序必须要知道去哪里链接我们的库。对于动态库,在编译期间我们要告诉编译器去哪里链接库进行编译,在运行期间我们要告诉操作系统去哪里链接库进行运行。所以,在编译期间不仅要告诉编译器gcc库在哪里,保证编译链接能够形成可执行程序。同时,在运行时也要告诉操作系统库在哪里。
静态库不需要链接是因为:静态库在编译链接期间将用户使用的二进制代码直接拷贝到目标可执行程序中,编译后的程序是一个完成的程序,不需要在运行时再使用静态库了。
操作系统查找动态库的方法有三种:
①设置环境变量:LD_LIBRARY_PATH;
②在系统指定路径下建立软链接,指向对应的库;
③配置文件。
1、设置环境变量
在Linux操作系统下有一个环境变量:LD_LIBRARY_PATH,操作系统会去该环境变量下的路径中搜索动态库,我们可以将我们自己写的第三方库的路径添加到这个环境变量中,然后我们再运行mymath可执行程序时,就能运行成功了。
将 libmymath.so 库所在的路/home/userlq/dir3/test/mylib/lib添加到环境变量LD_LIBRARY_PATH中。
这种将库路径添加到环境变量的方法,临时用来做测试没有问题。但是当重新登陆操纵系统后,该路径就失效了,需要重新添加路径才能成功执行。
2、更改配置文件
在Linux操作系统中有一个配置文件目录/etc/ld.so.conf.d,在这个目录中我们可以任意创建一个配置文件,并将动态库libmymath.so 的路径写进该配置文件中,这样操作系统就能在该路径下搜索到动态库。
现在我们在 /etc/ld.so.conf.d/路径下创建一个配置文件100.conf,并在该配置文件内写入所需库的路径,如下图所示。
更改完配置文件后,我们发现依然无法执行mymath可执行程序,而且依然找不到libmymath.so动态库,如下图所示:
其实,在更改完配置文件后,需要让该配置文件生效,使用指令ldconfig命令更新一下缓存。此时,能够正常执行mymath可执行程序了。
3、建立软链接
操作系统在搜索动态库时,默认直接能够在当前路径下搜索。所以我们可以通过在当前路径下建立我们要使用的动态库libmymath.so的软链接,让操作系统能够找到对应的libmymath.so动态库。
如果我们不想在当前目录下建立软链接,那么可以将软链接建立在对应的系统路径下。我们知道Linux操作系统中,C/C++的默认路径是/usr/lib64或者lib64,这也是系统搜索库的默认路径,我们可以将我们的第三方库在以上任意一个路径中建立一个软链接(不推荐直接将第三方库拷贝到默认库路径/usr/lib64或者/lib64下面),这样我们也能够正常使用了。
通过ldd查看程序链接情况:
因为软链接是一个正常的文件,永远保存在磁盘上,所以我们退出后再次登录时,程序依然可以正常运行。
注意:后两种方式都可以做到永久生效(因为存入了系统目录中),但是方法2在使用完后最好删除新创建的配置文件100.conf,避免污染系统环境。
6.7 动静态库的加载
6.7.1 静态库的加载
在形成可执行程序的链接期间,静态库中的代码会被直接拷贝一份进入程序内。在程序运行期间,就不再依赖于该静态库了,所以在程序运行期间静态库可以理解为不会被加载,或者说静态库和程序一起被加载。
假如多个进程都要调度printf函数,都要将printf函数的代码拷贝进来,并加载到内存。此时,在内存中会存在大量的重复代码,导致内存资源的浪费。
将静态库中的代码拷贝到我自己写的程序中,是拷贝到哪里呢?答案:是将静态库中的代码展开,拷贝至程序的代码区。
静态库中的printf函数的代码拷贝到我们自己写的代码里面,链接形成可执行程序时,会拷贝到哪里呢?我们自己写的程序在编译时,就已经以虚拟地址的方式帮我们把我们自己写的程序编译好了。即我们自己写的程序,在没有被加载到内存时,已经有了代码段、初始化数据区、未初始化数据区等区域。
每个进程都有自己独立的进程地址空间,实际上形成的可执行程序,也要按照进程地址空间的规则来排布它的代码和数据,即有相应的代码区、初始化数据区、未初始化数据等。所以将静态库中的代码拷贝到我们自己写的程序中时,就是将静态库中的代码展开拷贝至我们自己写的程序的代码区。
那么将静态库的代码拷贝至我们自己写的程序的代码区,所以未来静态库中printf函数的实现在加载到内存时,在进行进程映射时,只能够映射到当前进程的代码区?答案:是的。未来从静态库中加载进来的printf函数的实现,必须通过相对确定的地址位置来进行访问。当这部分加载到内存形成地址空间,玩不程序访问时,只能在代码区中找到对应的printf函数的实现。加载到我们自己写的程序中的静态库的代码和我们自己写的代码和数据的编码方式是一样的。
从静态库中拷贝到我们自己写的程序中的代码,必须通过相对确定的地址位置来进行访问。静态库的函数的代码拷贝到我们自己写的程序中,这个函数的入口地址就必须按照我们自己写的程序一样,从0000到FFFF这种地址的方式来进行编译。编译之后,printf函数在什么位置必须确定,这就是绝对编址的方案。
6.7.2 动态库的加载
1.加载的过程
当使用动态库编译形成了一个可执行文件后,该可执行文件存储在磁盘中,并在运行时加载到内存中。如下图:
那么动态库和可执行程序之间是什么关系呢?
动态链接并没有把动态库中的printf函数的实现拷贝到可执行程序里,而是将printf函数的地址,写入到可执行程序中。
动态库中的特定函数在编址时,采用的start+偏移地址的方式(start:偏移地址),start不确定。将动态库中的printf函数的地址写到可执行程序里,start可以相当于动态库的名称,偏移量就相当于相对编址。 printf函数相对于动态库的起始位置就可以视为printf函数的偏移量。将动态库中函数的地址填到可执行程序中,填写的就是该函数的偏移量。
程序被加载到内存后就变成了进程,操作系统会在内存中创建对应的task_struct、mm_struct、页表。在执行程序中自己写的代码时,正常执行。当需要执行动态库中的代码时,操作系统会先在内存中搜寻动态库是否存在,如果存在就直接将动态库通过页表映射到进程的进程地址空间中的共享区中。否则就会将磁盘中的动态库加载到内存中,然后再通过页表映射到虚拟地址空间的共享区中。详细步骤如下。
通过页表读取时,发现可执行程序中printf函数的代码实现并不存在,只有printf的函数地址。编译时在可执行程序中标识清楚了,这个地址属于外部地址。我们需要访问动态库来拿到该函数的代码,此时就识别到了要访问的库了。此时操作系统就停止执行程序,而是将动态库加载进来,并将动态库中的内容经过页表映射到进程的共享区中。映射到共享区之后,就确定了这个库在虚拟地址中的起始地址,就有了虚拟地址。
在链接时,可执行程序中已经填写好了printf函数在库中的偏移量。当调用printf函数时,动态库一旦完成动态加载、映射的过程之后,我们就可以在进行地址空间的共享区找到库的起始地址,并结合链接时形成的printf函数在库中的偏移量,就找到了printf函数在动态库(共享库)中的位置,并调用该函数,调用完成之后,再返回代码区继续向后执行。至此,我们就完成了动态库的加载与访问的过程。
换句话说,只要把库加载到内存,映射到进程的地址空间后,进程执行库中的方法,就依旧还是在自己的地址空间内进行函数跳转即可。
2.动态库的理解
在程序编译链接形成可执行程序时,可执行程序内部就已经有地址了,地址一共有两类,分别是绝对地址与相对地址。我们知道被编译好的程序内部是有地址的。动态库内部的地址并不是绝对地址,而是偏移量!(相对地址)
动态库必定面临一个问题:不同的进程,运行程度不同,需要使用的第三方库是不同的,这就注定了每一个进程的共享区中的空闲位置是不确定的。如果采用了绝对编址,在一个进程使用了多个库时就有可能造成地址冲突!因此,动态库中函数的地址,绝对不能使用绝对编址,动态库中的所有地址都是偏移量,默认从0开始。简单来说,库中的函数只需要记录自己在该库中的偏移量,即相对地址就可以了。
当一个库真正的被映射到进程地址空间时,它的起始位置才能够真正的确定,并且被操作系统管理起来。操作系统本身管理库,所以操作系统知道我们调用库中函数时,使用的是哪一个库,这个库的地址是什么。当需要执行库中的函数时,只需要拿到库的起始地址,加上对应函数在该库中的偏移量,就能够调用对应的函数了。
借助函数在库中的相对地址,无论库被加载到了共享区的哪一个位置,都不影响我们准确的找到对应函数,也不会与其他库产生冲突了。所以这种库被称为动态库,动态库中地址偏移量被称为与位置无关码。
3.理解与位置无关码
6.8 动态库知识补充
当同时拥有静态库和动态库时,默认采用动态链接。
如果想要使用静态链接,则需要在编译时加上-static命令选项。
如果只有静态库,但又不指定静态链接,会发生什么?
会默认使用动态链接,而对于C语言库等,也是默认使用动态链接。
可以看看以上三种方式生成的可执行程序的大小:
静态链接生成的程序比动态链接大得多,并且内含静态库的动态链接程序,也比纯粹的动态链接程序大,说明程序不是 非静即动 ,可以同时使用动态库与静态库。
6.9 总结
关于动静态库的优缺点可以看看下面的表格。
相关文章:
【基础IO下】磁盘/软硬链接/动静态库
前言: 文件分为内存文件和磁盘文件。磁盘文件是一个特殊的存在,因为磁盘文件不属于冯诺依曼体系,而是位于专门的存储设备中。因此,磁盘文件存在的意义是将文件更好的存储起来,一边后续对文件进行访问。在高效存储磁盘…...
JAVA练习题(1) 卖飞机票
import java.util.Scanner; public class Main {public static void main(String[] args) {Scanner scnew Scanner(System.in);System.out.println("请输入飞机的票价:");int pricesc.nextInt();System.out.println("请输入月份:");…...
SpringBoot框架开发网络安全科普系统开发实现
概述 基于SpringBoot框架的网络安全科普系统开发指南,该系统集知识科普、案例学习、在线测试等功能于一体,本文将详细介绍系统架构设计、功能实现及技术要点,帮助开发者快速构建专业的网络安全教育平台。 主要内容 系统功能架构 本系统采…...
机器学习 day02
文章目录 前言一、TF-IDF特征词重要度特征提取二、无量纲化处理1.最大最小值归一化2.normalize归一化3.StanderScaler标准化 前言 通过今天的学习,我掌握了TF-IDF特征词重要度特征提取以及无量纲化处理的相关知识和用法 一、TF-IDF特征词重要度特征提取 机器学习算…...
《AI大模型应知应会100篇》第53篇:Hugging Face生态系统入门
第53篇:Hugging Face生态系统入门 ——从模型获取到部署的全流程实战指南 📌 摘要 在人工智能快速发展的今天,Hugging Face已成为自然语言处理(NLP)领域最具影响力的开源平台之一。它不仅提供丰富的预训练模型、强大…...
计网学习笔记———网络
🌿网络是泛化的概念 网络是泛化的概念 🍂泛化理解 网络的概念在生活中无处不在举例:社交网络、电话网路、电网、计算机网络 🌿网络的定义 定义: 离散的个体通过通讯手段连成群体,实现资源的共享与交流、个…...
Vue3 怎么在ElMessage消息提示组件中添加自定义icon图标
1、定义icon组件代码: <template><svg :class"svgClass" aria-hidden"true"><use :xlink:href"iconName" :fill"color"/></svg> </template><script> export default defineComponen…...
17.Excel:实用的 VBA 自动化程序
一 excel 设置 开始-选项 二 批量创建工作表 某工作簿用于保存31天的东西,手动创建31个工作表不方便。 A1单元格输入内容,或者空着。从A2单元格开始,一定要以字符形式的,不能以数值和日期形式。12345这是数值形式,1月…...
Kubernetes生产实战(十六):集群安全加固全攻略
Kubernetes集群安全加固全攻略:生产环境必备的12个关键策略 在容器化时代,Kubernetes已成为企业应用部署的核心基础设施。但根据CNCF 2023年云原生安全报告显示,75%的安全事件源于K8s配置错误。本文将基于生产环境实践,系统讲解集…...
Cadence学习笔记之---导入PCB板框、网表
目录 01 | 引 言 02 | 环境描述 03 | 导入PCB板框 04 | 自画PCB板框 05 | 导入PCB网表 06 | 总 结 01 | 引 言 在上一篇小记中讲述了创建PCB工程的操作步骤、PCB工程中的类与子类,以及Cadence颇具特色的颜色管理器。 本篇小记主要记述如何导入PCB板框、自画…...
嵌入式硬件篇---麦克纳姆轮(简单运动实现)
文章目录 前言1. 麦克纳姆轮的基本布局X型布局O型布局 2. 运动模式实现原理(1) 前进/后退前进后退 (2) 左右平移向左平移向右平移 (3) 原地旋转顺时针旋转(右旋)逆时针旋转(左旋) (4) 斜向移动左上45移动 (5) 180旋转 3. 数学原理…...
en33网络配置文件未托管
从 nmcli device status 的输出可以看到,所有网络设备(包括 ens33)都处于 "未托管"(unmanaged)状态,这导致 NetworkManager 和传统的 network.service 都无法管理网络接口,从而引发 n…...
嵌入式学习--江协51单片机day4
昨天周五没有学习,因为中午没有睡觉,下午和晚上挤不出整块的时间。周日有考试今天也没有学很多啊,但以后周末会是学一天,另一天休息和写周总结。 今天学了串口通信和LED点阵屏,硬件原理是真的很迷,一但想搞…...
Hadoop 2.x设计理念解析
目录 一、背景 二、整体架构 三、组件详解 3.1 yarn 3.2 hdfs 四、计算流程 4.1 上传资源到 HDFS 4.2 向 RM 提交作业请求 4.3 RM 调度资源启动 AM 4.4 AM运行用户代码 4.5 NodeManager运行用户代码 4.6 资源释放 五、设计不足 一、背景 有人可能会好奇…...
diy装机成功录
三天前,我正式开启了这次装机之旅,购入了一颗性能强劲的 i5-12400 CPU,一块绘图能力出色的 3060ti 显卡,还有技嘉主板、高效散热器、16G 内存条、2T 固态硬盘,以及气派的机箱和风扇,满心期待能亲手打造一台…...
睿思量化小程序
睿思量化小程序是成都睿思商智科技有限公司最新研发和运营的金融数据统计分析工具,旨在通过量化指标筛选与多策略历史回测,帮助用户科学配置基金资产,成为个人投资者与机构用户的“智能化财富管家”。 核心功能:数据驱动决策&…...
STM32实现九轴IMU的卡尔曼滤波
在嵌入式系统中,精确的姿态估计对于无人机、机器人和虚拟现实等应用至关重要。九轴惯性测量单元(IMU)通过三轴加速度计、陀螺仪和磁力计提供全面的运动数据。然而,这些传感器数据常伴随噪声和漂移,单独使用无法满足高精…...
JS DOM操作与事件处理从入门到实践
对于前端开发者来说,让静态的 HTML 页面变得生动、可交互是核心技能之一。实现这一切的关键在于理解和运用文档对象模型 (DOM) 以及 JavaScript 的事件处理机制。本文将带你深入浅出地探索 DOM 操作的奥秘,并掌握JavaScript 事件处理的方方面面。 目录 …...
Hive表JOIN性能问
在处理100TB的Hive表JOIN性能问题时,需采用分层优化策略,结合数据分布特征、存储格式和计算引擎特性。以下是系统性优化方案: 1. 数据倾斜优化(Skew Join) 1.1 识别倾斜键 方法:统计JOIN键的分布频率&…...
关键点检测--使用YOLOv8对Leeds Sports Pose(LSP)关键点检测
目录 1. Leeds Sports Pose数据集下载2. 数据集处理2.1 获取标签2.2 将图像文件和标签文件处理成YOLO能使用的格式 3. 用YOLOv8进行训练3.1 训练3.2 预测 1. Leeds Sports Pose数据集下载 从kaggle官网下载这个数据集,地址为link,下载好的数据集文件如下…...
2025年客运从业资格证备考单选练习题
客运从业资格证备考单选练习题 1、从事道路旅客运输活动时,应当采取必要措施保证旅客的人身和财产安全,发生紧急情况时,首先应( )。 A. 抢救财产 B. 抢救伤员 C. 向公司汇报 答案:B 解析:…...
QMK自定义4*4键盘固件创建教程:最新架构详解
QMK自定义4*4键盘固件创建教程:最新架构详解 前言 通过本教程,你将学习如何在QMK框架下创建自己的键盘固件。QMK是一个强大的开源键盘固件框架,广泛用于DIY机械键盘的制作。本文将详细介绍最新架构下所需创建的文件及其功能。 准备工作 在…...
获取conan离线安装包
1、获取conan离线安装包 # apt-get install python3.12-venv pip #缓存的安装存放在/var/cache/apt/archives目录 # mkdir /myenv && cd /myenv #创建虚拟环境目录 # python3 -m venv myenv #创建虚拟环境 # source myenv/bin/activate #激活虚拟环境ÿ…...
【Java ee初阶】网络原理
应用层 由于下面的四层都是系统已经实现好了的,但是应用层是程序员自己写的,因此应用层是程序员最重要的一层。 应用层中,程序员通常需要定义好数据传输格式,调用传输层api(socket api)进行真正的网络通信…...
Makefile中 链接库,同一个库的静态库与动态库都链接了,生效的是哪个库
Makefile中 链接库,同一个库的静态库与动态库都链接了,生效的是哪个库 在 Makefile 中同时链接同一个库的静态库(.a)和动态库(.so)时,具体哪个库生效取决于链接顺序和编译器行为。以下是详细分析…...
【AI提示词】金字塔模型应用专家
提示说明 专业运用金字塔原理优化信息结构与逻辑表达,实现高效精准的思维传达。 提示词 # Role: 金字塔模型应用专家 ## Profile - **language**: 中文/英文 - **description**: 专业运用金字塔原理优化信息结构与逻辑表达,实现高效精准的思维传…...
电子电器架构 --- 车载以太网拓扑
我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 钝感力的“钝”,不是木讷、迟钝,而是直面困境的韧劲和耐力,是面对外界噪音的通透淡然。 生活中有两种人,一种人格外在意别人的眼光;另一种人无论…...
使用FastAPI微服务在AWS EKS上实现AI会话历史的管理
架构概述 本文介绍如何使用FastAPI构建微服务架构,在AWS EKS上部署两个微服务: 服务A:接收用户提示服务B:处理对话逻辑,与Redis缓存和MongoDB数据库交互 该架构利用AWS ElastiCache(Redis)实现快速响应,…...
Flutter PIP 插件 ---- 为iOS 重构PipController, Demo界面,更好的体验
接上文 Flutter PIP 插件 ---- 新增PipActivity,Android 11以下支持自动进入PIP Mode 项目地址 PIP, pub.dev也已经同步发布 pip 0.0.3,你的加星和点赞,将是我继续改进最大的动力 在之前的界面设计中,还原动画等体验一…...
vue开发用户注册功能
文章目录 一、开发步骤二、效果图三、搭建页面创建views/Login.vue在App.vue中导入Login.vue 四、数据绑定五、表单校验六、访问后端 API 接口,完成注册七、完整的Login.vue代码八、参考资料 一、开发步骤 二、效果图 三、搭建页面 创建views/Login.vue 完整内容在…...
Qt中的RCC
Qt资源系统(Qt resource system)是一种独立于平台的机制,用于在应用程序中传输资源文件。如果你的应用程序始终需要一组特定的文件(例如图标、翻译文件和图片),并且你不想使用特定于系统的方式来打包和定位这些资源,则可以使用Qt资源系统。 最…...
muduo源码解析
1.对类进行禁止拷贝 class noncopyable {public:noncopyable(const noncopyable&) delete;void operator(const noncopyable&) delete;protected:noncopyable() default;~noncopyable() default; }; 2.日志 使用枚举定义日志等级 enum LogLevel{TRACE,DEBUG,IN…...
Qt QCheckBox 使用
1.开发背景 Qt QCheckBox 是勾选组件,具体使用方法可以参考 Qt 官方文档,这里只是记录使用过程中常用的方法示例和遇到的一些问题。 2.开发需求 QCheckBox 使用和踩坑 3.开发环境 Window10 Qt5.12.2 QtCreator4.8.2 4.功能简介 4.1 简单接口 QChec…...
【工具记录分享】提取bilibili视频字幕
F12大法 教程很多 但方法比较统一 例快速提取视频字幕!适用B站、AI字幕等等。好用 - 哔哩哔哩 无脑小工具 哔哩哔哩B站字幕下载_在线字幕解析-飞鱼视频下载助手 把链接扔进去就会自动生成srt文件 需要txt可以配合: SRT转为TXT...
设计模式【cpp实现版本】
文章目录 设计模式1.单例模式代码设计1.饿汉式单例模式2.懒汉式单例模式 2.简单工厂和工厂方法1.简单工厂2.工厂方法 3.抽象工厂模式4.代理模式5.装饰器模式6.适配器模式7.观察者模式 设计模式 1.单例模式代码设计 为什么需要单例模式,在我们的项目设计中&…...
Python数据分析案例74——基于内容的深度学习推荐系统(电影推荐)
背景 之前都是标准的表格建模和时间序列的预测,现在做一点不一样的数据结构的模型方法。 推荐系统一直是想学想做的,以前读研时候想学没多少相关代码,现在AI资源多了,虽然上班没用到这方面的知识,但是还是想熟悉一下…...
C PRIMER PLUS——第8节:字符串和字符串函数
目录 1. 字符串的定义与表示 2. 获取字符串的两种方式 3.字符串数组 4. 字符串输入函数 4.1 gets()(不推荐使用,有缓冲区溢出风险) 4.2 fgets()(推荐使用) 4.3 scanf() 4.4 gets_s()(C11 标准&…...
Dia浏览器:AI驱动浏览网页,究竟怎么样?(含注册申请体验流程)
名人说:博观而约取,厚积而薄发。——苏轼《稼说送张琥》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、Dia浏览器简介1. 什么是Dia浏览器2. 开发背景与公司简介3. 与传统浏览器的区别 …...
milvus+flask山寨复刻《从零构建向量数据库》第7章
常规练手,图片搜索山寨版。拜读罗云大佬著作,结果只有操作层的东西可以上上手。 书中是自己写的向量数据库,这边直接用python拼个现成的milvus向量数据库。 1. 创建一个向量数据库以及对应的相应数据表: # Milvus Setup Argume…...
【大数据技术-HBase-关于Hmaster、RegionServer、Region等组件功能和读写流程总结】
Hmaster的作用 负责命名空间、表的创建和删除等一些DDL操作、region分配和负载均衡,并不参与数据读写,相比与其他大数据组件,如hdfs的namenode,在hbase中,Hmaster的作用是比较弱化的,即使挂掉,也暂时不影响现有表的读写。 RegionServer的作用 一个机器上一个regionse…...
用c语言实现——一个交互式的中序线索二叉树系统,支持用户动态构建、线索化、遍历和查询功能
知识补充:什么是中序线索化 中序遍历是什么 一、代码解释 1.结构体定义 Node 结构体: 成员说明: int data:存储节点的数据值。 struct Node* lchild:该节点的左孩子 struct Node* rchild:该节点的右孩子…...
Pale Moon:速度优化的Firefox定制浏览器
Pale Moon是一款基于Firefox浏览器的定制版浏览器,专为追求速度和性能的用户设计。它使用开放源代码创建,经过高度优化,适用于现代处理器,提供了更快的页面加载速度和更高效的脚本处理能力。Pale Moon不仅继承了Firefox的安全性和…...
广东省省考备考(第七天5.10)—言语:逻辑填空(每日一练)
错题 解析 第一空,搭配“各个环节”,根据“我国已经形成了相对完善的中药质量标准控制体系”可知,横线处应体现“包含”之意,C项“涵盖”指包括、覆盖,D项“囊括”指把全部包罗在内,均与“各个环节”搭配得…...
Gartner《Container发布与生命周期管理最佳实践》学习心得
近日,Gartner发布了《Best Practices for Container Release and Life Cycle Management》, 报告为技术专业人士提供了关于容器发布和生命周期管理的深入指导。这份报告强调了容器在现代应用开发和部署中的核心地位,并提供了一系列最佳实践&…...
内存、磁盘、CPU区别,Hadoop/Spark与哪个联系密切
1. 内存、磁盘、CPU的区别和作用 1.1 内存(Memory) 作用: 内存是计算机的短期存储器,用于存储正在运行的程序和数据。它的访问速度非常快,比磁盘快几个数量级。在分布式计算中,内存用于缓存中间结果、存储…...
SpringCloud之Eureka基础认识-服务注册中心
0、认识Eureka Eureka 是 Netflix 开源的服务发现组件,后来被集成到 Spring Cloud 生态中,成为 Spring Cloud Netflix 的核心模块之一。它主要用于解决分布式系统中服务注册与发现的问题。 Eureka Server 有必要的话,也可以做成集群…...
MySQL 中如何进行 SQL 调优?
在MySQL中进行SQL调优是一个系统性工程,需结合索引优化、查询改写、性能分析工具、数据库设计及硬件配置等多方面策略。以下是具体优化方法及案例说明: 一、索引优化:精准提速的关键 索引类型选择 普通索引:加速频繁查询的列&…...
Linux平台下SSH 协议克隆Github远程仓库并配置密钥
目录 注意:先提前配置好SSH密钥,然后再git clone 1. 检查现有 SSH 密钥 2. 生成新的 SSH 密钥 3. 将 SSH 密钥添加到 ssh-agent 4. 将公钥添加到 GitHub 5. 测试 SSH 连接 6. 配置 Git 使用 SSH 注意:先提前配置好SSH密钥,然…...
Android平台FFmpeg音视频开发深度指南
一、FFmpeg在Android开发中的核心价值 FFmpeg作为业界领先的多媒体处理框架,在Android音视频开发中扮演着至关重要的角色。它提供了: 跨平台支持:统一的API处理各种音视频格式完整功能链:从解码、编码到滤镜处理的全套解决方案灵…...
QSFP+、QSFP28、QSFP-DD接口分别实现40G、100G、200G/400G以太网接口
常用的光模块结构形式: 1)QSFP等效于4个SFP,支持410Gbit/s通道传输,可通过4个通道实现40Gbps传输速率。与SFP相比,QSFP光模块的传输速率可达SFP光模块的四倍,在部署40G网络时可直接使用QSFP光模块…...