当前位置: 首页 > 编程日记 > 正文

威纶通宏开机后使用初始化宏指令_【操作系统】我们按下电脑开机键的背后发生了什么?...

作者:CVNot

链接:https://juejin.im/post/5e8844996fb9a03c6675b9d6

操作系统是什么?

操作系统是用来管理计算机硬件的软件,狭义上实现该定义的为操作系统内核;而更加宽泛的操作系统概念为根据内核对外提供了一些OS服务,比如windows的图形化界面等。

操作系统启动过程

既然操作系统是在硬盘或软盘中的程序软件,那么按照冯诺依曼体系--取指执行,我们需要将操作系统从硬盘中加载到内存中,这样CPU才可以取指执行;

2.1 第一个程序:BIOS

按下开机键后,内存(RAM)在刚上电时是空白的,所以CPU中的CS(段地址):IP(段内偏移)指针如何知道运行的第一条代码是什么呢?
段寄存器名称含义指向的初始地址
CS段基址  (16位)0xF000
IP段内偏移(16位)0xFFF0

计算机是在硬件层面解决这个问题,即在CPU刚上电时,将其置为16位模式,启动20根地址线(A0 ~ A19,即内存的寻址空间为20位),并将CS:IP直接置在0xF000:0x0000,即0xFFFF0地址处(计算方法为 CS<<4 + IP,CS为什么要左移四位呢?是因为一共有20位的寻址空间,这样才可以寻址到全部的内存地址空间),而该地址为ROM上BIOS程序的入口地址,这一段是硬件固化好的程序,所以计算机执行的第一段代码为BIOS;

b80a87828e1a663c6181a8b42c4de0d1.png

BIOS不是操作系统,所以它最核心的任务是要将内核代码从磁盘中加载进内存,它是如何做到的呢?

859785f8b0d2def213e68b3061975032.png
BIOS程序首先在内存空间中初始化了中断向量表(用来指向相应中断号的中断程序的CS:IP地址,共1KB),而一个中断向量为4个字节(CS:16位,IP:16位),并且初始化了数据区域;

而接下来BIOS就会通过调用0x19号中断(INT)服务程序加载磁盘中第一个扇区(512Bytes)中的bootsect程序(--内核中的引导程序)到内存0x07C00处,并且jmp到该地址执行;对于素数 p,有:

//bootsect.s
BOOTSEG = 0x07c0 ! original address of boot-sector
9f65cfd5a2956b11415cd461c8f71575.png

2.2 第二个程序:bootsect.s

bootsect是操作系统的引导扇区中的代码,其主要责任是将后续的内核代码(主要是setup代码和system代码)妥善的加载到内存中,并且因为此时计算机只打开了20根地址线,所以内存寻址范围为2^20 = 1MB,所以bootsect代码首先要对内存进行规划。
SETUPLEN = 4				! setup程序代码一共占4个扇区
INITSEG = 0x9000 ! 将bootsect代码移到0x9000(稍后会分析为什么要移)
SETUPSEG = 0x9020 ! setup程序会以 0x9020 为起始地点加载
SYSSEG = 0x1000 ! system(内核)会以 0x10000 为起始地点加载
SYSSIZE = 0x3000 ! 内核模块的长度
ENDSEG = SYSSEG + SYSSIZE ! system(内核)模块截止的位置

内存规划情况如下:

1291191fc07afef117ff69d4bbd0b9c3.png

而迁移bootsect主要的原因在于system(内核)模块之后会迁移到0x00000处,如果不进行迁移,那么bootsect程序就会被覆盖,在说setup模块时会提到;

规划好内存,接下来就会把setup模块和system模块依次通过0x13中断载入到内存的相应位置中,最终效果如上图所示;

在内存中不仅需要规划程序代码内存区域,也要对一些段寄存器进行重新初始化;

这里总结一下会涉及到的段寄存器,而这些段寄存器都处在CPU中:

段寄存器名称含义指向的初始地址
CS段基址  (16位)0x9000
IP段内偏移(16位)[go] 复制bootsect时运行到bootsect程序的位置,这样可以接着执行bootsect
SS内核栈基址指针(16位)0x0000(与CS合为0x90000)
SP内核栈栈顶指针 (16位)0xFF00 (0x9FF00)
DS数据段寄存器(16位)0x0000 (0x90000)

其中SS与SP初始化了一个从高地址向低地址压栈的栈结构,从而程序在接下来的指令中可以使用pop与push操作运行一些复杂的数据运算类指令。

1337551645cd3bdaf9d14d9494aace63.png
2.3 第三个程序:setup

我们已经通过bootsect.s将setup模块加载至0x92000处,正好在bootsect.s运行结束的位置,并且system内核模块也已经加载进入内存,而setup主要作用是让操作系统设置起来。

  • 首先操作系统最主要的功能是用来管理硬件,所以setup会通过BIOS提供的中断程序加载内核(system程序)运行所需的机器系统数据,例如:光标位置(0x03)、显示页面等数据,并通过0x41、0x46中断向量所指的内存区域中获取硬盘参数表1和硬盘参数表2,而这些数据(510Bytes)放在bootsect区, 可见操作系统对于空间的利用是十分严格的;

c9b8457969bf691cd365cfb7d65b703b.png
  1. 由20位寻址转变为32位寻址(即由实模式转变为保护模式)因为20位地址空间只有1MB的寻址空间,这对于一个现代操作系统来讲着实捉襟见肘,所以要转变为保护模式;

(1)第一步我们需要关闭中断并且将system内核模块移动到0x00000处;

894789993b92a48b99316d121f69dcb3.png

为什么要关中断,是因为我们会将BIOS中断体系摧毁,BIOS中断体系是不适合32位的操作系统的,所以在32位保护模式中断体系没有建立起来前中断是一直关闭的;

如何关闭中断,将CPU中的标志寄存器(EFLAGS)中的中断允许标志(IF)置0;

为什么要移动system模块,人家不是呆在0x10000处挺好的嘛?如果此时要移动,那为什么不在一开始就直接把system模块放到0x00000处呢?

因为:1)当我们移动至0x00000处时,会覆盖掉BIOS的中断向量表、BIOS数据区、中断服务程序,而这也使得BIOS中断体系被废除掉;2)让内核程序的寻址空间开始地址为0x00000,这样内核的线性地址与物理地址是相同的;3)一开始没有放在0x00000处是因为我们在加载完system模块后仍然需要使用BIOS中断服务。

(2)构建中断描述符表(IDT)和全局描述符表(GDT):IDT与BIOS中断向量表作用相同,都存储着中断处理程序的入口地址,不同的是:IDT的位置是可以改变的,其在内存中的基地址存储在CPU的IDTR寄存器中,这样给操作系统的内存规划带来了灵活性;

GDT中存储段描述符,用于找到数据段、代码段等段地址,在进程状态查询、进程切换中扮演了比较重要的角色;该表在内存中的基地址存储在CPU的GDTR寄存器中;

gdt_48:
.word 0x800 ! gdt limit=2048, 256 GDT entries //GDT的限长
.word 512+gdt,0x9 ! gdt base = 0X9xxxx //GDT表的基地址
027541556694bd592802323a5d7eca45.png

在实模式的CS:IP中,CS中存储的是代码段基址,而在保护模式下,通过GDT确定CPU下一个指令的位置,CS中存储的是GDT表中对应的段选择符,

mov	ax,#0x0001	! protected mode (PE) bit
lmsw ax ! This is it!
jmpi 0,8 ! jmp offset 0 of segment 8 (cs)

其中0表示段内偏移,而8(1000)表示GDT表的选择子,末尾00表示当前为内核态,0表示访问的是GDT表,1表示是GDT表的第2项(从0开始计数)

gdt:
//GDT的第一项 为空
.word 0,0,0,0 ! dummy
//GDT的第二项,内核代码段描述符
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0 //CS下一条指向的代码段位置
.word 0x9A00 ! code read/exec
.word 0x00C0 ! granularity=4096, 386
//GDT的第三项 内核数据段描述符
.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb)
.word 0x0000 ! base address=0
.word 0x9200 ! data read/write
.word 0x00C0 ! granularity=4096, 386

那计算机在保护模式下为什么要通过这种间接的方式寻找段基地址等信息呢?

是因为硬件版本兼容的考虑,CS寄存器只有16位,在20位寻址模式下,CS只要左移四位(最后四位默认为0)即可表示出20位的地址空间;而在32位地址空间下,CS需要表达的信息不仅有段基址,段限长,还需要有权限信息,而这会导致需要64位的段信息,CS表示不了这么多信息,便用作GDT表的索引,去找到该段对应的一个64位信息,并且我们可以看到GDT的每一项都是16个16进制位,即64个二进制位。

(3) 打开A20地址线,转变成32位寻址模式:

13d2db1972c280767bdd5e247b018ceb.png

(4)为建立32位中断体系做硬件层面的准备:

setup程序对8259A(可编程中断控制器)进行重新编程,因为此时的中断控制器对应的还是BIOS中断体系,此处需要一定的单片机基础,我们可以对中断控制器进行编程,设置中断条件,中断处理函数等等......

2.4 system模块的head.s开始执行:

head.s程序的主要作用在于为操作系统第一个.c函数main.c的运行做准备工作;

2.4.1 建立页目录与页:

  • 为什么要建立页目录?主要是为了便于内核对内存进行管理,为什么要在初始地址建立页目录?是因为这样在内核中线性地址与物理地址相同,不需要通过MMU表进行从线性地址到物理地址的映射;MMU在进程内存管理中会涉及到,与Java虚拟机中的TLAB作用相似,即给各个进程分配各自的内存空间,这样就不会产生多进程的数据冲突。

  • 如何建立页目录?

b1d8222eefc081c9046957f63396197f.png

在0x000000处开始的4KB内存空间建立页目录表,并将页目录的前四项指向页1、2、3、4,这样页目录表便设置完成了;接着设置页表,页表中一个页表项对应了内存空间中的一个4k的页表,然后按照从高地址向低地址写的特性,从0xFFFFFF~ 0xFFF000这个内存中最后一个4K页的地址存放到页4最后一个页表项中,以此类推,从0xFFFE00开始的4k页由页4倒数第二个页表项引用。

2.4.2 重建GDT表与IDT表

(1) 在setup.s程序执行过程中,我们废除了原有的BIOS中断机制,并重新设置了可编程中断控制器,这样硬件层面的中断已经处理完成,所以我们要在软件层面完成32位中断体系的建立--即中断描述符表(IDT)的建立与中断程序的挂接(与BIOS中断体系一致);IDT表存储在0x054AA处,即紧跟在页4后,一共有256个中断entries入口。

可以品味一下两个中断表名称的差异,中断向量,何为向量,即直接存储中断程序的IP地址;何为中断描述符表,即存储中断描述符,中断描述符中存储了该描述符对应的中断程序的32位IP(EIP)段内偏移地址,16位CS(GDT段选择符)地址,还有一些权限信息--DPL,该页是否存在于内存中(P,为虚拟内存的换页提供信息),段描述符的TYPE等信息,前两者确定了中断程序的32位地址,也侧面说明了我们为什么要重建中断体系的核心原因;而初始中断程序都是挂接在默认中断程序ignore_int上,等后续的main函数运行时再进行挂接具体函数,这样的好处在于如果使用了错误的中断号,也可以获得一个默认的提示信息反馈,知道当前系统没有提供这个中断;

(2) 在setup.s程序执行过程中,我们其实已经新建了一个GDT表,其中有两项,一项指向内核数据区,一项指向内核代码区;那我们为什么要重建呢?需要注意GDT表的位置是0x90200起,而这部分区域在之后的内存规划中是设置为缓冲区的,所以为了防止GDT表被覆盖导致CPU无法找到下一条指令的位置从而崩溃的问题,我们需要将GDT放在安全的地方--0x054B2,即紧跟在IDT表后。

完成上述设置后,内存从0x00000开始的内存空间如下所示:

1a5b797835a6a919d5cb5eeed87dceb9.png

2.4.3 重新设置段寄存器:

因为寻址模式和系统位数(16~32)的改变,CS中不再存储段基址,而是存储对应GDT寻址方式的段选择符,所以我们需要将SS也需要转变为栈段选择符(还是16位),而栈顶指针(SP)因为可能不与CS在同一个段中,故要改为32位的ESP;

34f1ad23763bee15700f4c69a5d159a4.png

2.4.4 跳转到main.c函数执行:

经历了前面一系列的铺垫,终于可以执行C语言函数了,因为C作为一个高级语言,它的编译运行离不开操作系统的支持。

那是如何跳转的呢?我们知道程序的运行离不开数据结构-栈,C语言实现的函数调用,本质上是封装了通过共享栈完成参数的传递、被调用函数的执行与返回结果的过程;

所以我们调用main函数也是通过push与pop完成的。

786dcb9d61fc9dc10bc6391be0e24665.png

当head.s完成GDT、IDT、页表的设置后,会跳转到setup_paging执行,而后再通过往内核栈里压入调用main()函数的参数--envp、argc、argv[]和L6标号以及当前程序执行到ret(返回指令)后,需要调用的函数--main函数的地址;

为什么要通过这种方式,我们知道main的地址,直接jmp不好吗?是因为安全性的考虑,mian()函数是不应该退出的,如果退出则说明出现了异常,则会返回到L6,而在这个标号的程序处,我们可以做一些系统调用,给与用户一些提示信息,并维持进程的轮转,而不至于完全崩溃。总结

至此,计算机便算是本启动起来,而操作系统也会在main()函数中初始化一些数据结构用来管理硬件资源、设置各种中断程序、初始化进程等等操作

欢迎关注交流探讨!

deff688f3d2c1f675bc526eb11b8ca6f.png

相关文章:

Linux常用压缩与解压缩命令

.tar 解包&#xff1a;tar xvf FileName.tar打包&#xff1a;tar cvf FileName.tar DirName&#xff08;注&#xff1a;tar是打包&#xff0c;不是压缩&#xff01;&#xff09;———————————————.gz解压1&#xff1a;gunzip FileName.gz解压2&#xff1a;gzip -d…

【Kubernetes】如何使用Kubeadm部署K8S集群

一 . 准备机器 本次环境采用华为云ECS弹性云服务器部署&#xff08;也可以使用VMware&#xff09; vm01&#xff08;2V4G&#xff09;&#xff1a; Ubuntu_18.04作为K8S master节点 vm02&#xff08;1V1G&#xff09;&#xff1a; Ubuntu_18.04作为K8S node节点 备注: 以下所有…

解决ORA-28000: the account is locked

在oracle中&#xff0c;连续十次尝试登陆不成功&#xff0c;那么此账户将会被锁定&#xff08;lock&#xff09;。当使用被锁定的账户登录时&#xff0c;系统会报错&#xff1a;ORA-28000: the account is locked。查询FAILED_LOGIN_ATTEMPTS参数默认值&#xff0c;这个参数限制…

Android sudio Day01-1

今天我开始学习Android studio的第二天&#xff0c;主题是Android studio的安装。 之前的学校学习都是使用Android开发者工具&#xff08;Android development tools&#xff0c;ADT&#xff09;&#xff0c;而ADT作为一个Android开发工具&#xff0c;它是通过内置于Eclipse的…

学习用C#在Unity中创建一个2D Metroidvania游戏

学习用C#在Unity中创建一个2D Metroidvania游戏 你会学到: 构建2D Unity游戏 用C#编程 玩家统计&#xff0c;水平提升&#xff0c;米尔和远程攻击 敌方人工智能系统 制定级别和级别选择 Learn To Create A 2D Metroidvania Game in Unity With C# MP4 |视频:h264&#xff0c;…

3.27课·········悬浮动态分层导航与隐藏导航

例1:分层导航 <title>分层导航</title> <script src"../JavaScript/jquery-1.4.2.min.js">//引用外部JS代码 </script> <style> #apDiv1 {position: fixed;left: auto;top: auto;bottom: auto;width: 237px;height: auto;z-index: 2;m…

.sh是什么语言_shell的重生历史:从sh到bash

shell 门派之争Linux 中的 shell 有很多类型&#xff0c;其中最常用的几种是&#xff1a;Bourne shell (sh)、C shell (csh) 和 Korn shell (ksh)&#xff0c;它们各有优缺点&#xff0c;用户则萝卜青菜&#xff0c;各有所爱。Bourne shell 出师不利Bash&#xff1a;Bourne aga…

【Docker】容器的几种网络模式

当你使用Docker时&#xff0c;你会发现需要了解很多关于网络的知识。Docker作为目前最火的轻量级容器引擎&#xff0c;因此&#xff0c;我们有必要深入了解Docker的网络知识&#xff0c;以满足更高的网络需求。本文介绍了Docker的4种网络模式。 1、首先我们先简单描述一下容器…

微信推送模板消息的PHP代码整理

本文为本人原创&#xff0c;未经许可&#xff0c;不可转载。 博主长期从事微信开发&#xff0c;微信开发相关问题和业务请联系qq 2580234897 最近做过一个需要推送消息的系统&#xff0c;就研究了一下微信的模板消息的推送。由于认证过的微信号&#xff0c;就用测试号做的&…

Android studio Day01-23

AndroidDay01-2&#xff08;Android studio安装&#xff09; 步骤如下&#xff1a; &#xff08;1&#xff09;下载Android studio下载地址&#xff1a;www.developer.android.com./sdk/installing/studio.html &#xff08;2&#xff09;双击安装&#xff0c;建议在studio安…

Unity 3D学习视觉脚本无需编码即可创建高级游戏

在本课程中&#xff0c;您将学习如何在Unity中使用可视化脚本(以前称为Bolt)以及如何在不编写一行代码的情况下创建自己的高级游戏所需的一切。本课程将教你如何掌握可视化脚本&#xff0c;即使你以前没有任何关于unity或编程的经验。 课程获取&#xff1a;Unity 3D学习视觉脚…

树莓派siri homekit_利用树莓派Zero自制一款Homekit摄像头,看上去挺酷,手痒了吗?...

虽然最近相继有多款兼容homekit商用摄像头上市&#xff0c;如果您也和小编一样&#xff0c;喜欢动手&#xff0c;那么DIY一款Homekit摄像头&#xff0c;然后自己3D打印个外壳支架&#xff0c;是不是很酷&#xff1f;HKCam项目Home 4开发者Matthias提供了一个开源项目&#xff0…

如何释放电脑被限制的20%网速?

很多朋友不管是看电影还是玩游戏&#xff0c;总觉得自己的网速慢&#xff0c;这跟自己所办网络的带宽有一定关系&#xff0c;但我们也要知道&#xff0c;我们的电脑在买来时&#xff0c;默认是限制了20%网速的&#xff0c;如何释放这20%的网速&#xff0c;提高用户体验呢&#…

【Docker】registry部署docker私有镜像仓库

Docker Hub作为Docker默认官方公共镜像仓库&#xff0c;但是如果我们不想使用怎么办&#xff0c;第一我们可以替换默认镜像仓库为我们国内的一些镜像仓库&#xff0c;第二就是如我们自己搭建一个自己的私有镜像仓库&#xff0c;官方也提供docker registry镜像&#xff0c;使得搭…

JAVA 面向对象

1&#xff1b;什么叫面向对象&#xff1a;1&#xff1b;面向对象和面向过程是一种思想2&#xff1b;面向过程&#xff1a;强调的是功能行为3&#xff1b;面向对象&#xff1a;将功能进行封装&#xff0c;强调具备了功能的对象2&#xff1b;面向对象的特征&#xff1a;1&#xf…

Android studio Day02-1

AndroidDay02-1&#xff08;project&#xff09; 新建一个project&#xff0c;并选择一个自己project的存贮的位置 Android studio 2.3.1默认的最小的API为15 第一次使用并建立自己的project&#xff0c;软件进行相应的sdk其他的组建的安装 点击next进入下一个界面&#xff0c…

Unity Pro builder创建模块化仓库建筑学习教程

Unity内部的专业3D编辑工作室 你会学到: 直接在Unity内部学习3D建模 使用专业构建器的专业方法 为您的游戏创建模块化资产 了解如何为您的三维模型设置纹理 三维资产的模块化布局 专业后期制作和轻烤 Unity Pro Builder Warehouse MP4 |视频:h264&#xff0c;1280720 |音频:AA…

C++拾遗(五)语句相关

前缀格式与后缀格式 对于表达式&#xff1a;后缀如 i 表达式的值仍是 i&#xff0c;在遇到下一个顺序点后再将 i 加1。前缀 i 表达式的值就是&#xff08;i1&#xff09;&#xff0c;先计算表达式的值&#xff0c;不需要等待      顺序点。 对于类&#xff1a;前缀函数效…

github里的默认域_GitMAD 一款扫描Github上的敏感信息和数据泄漏工具

GitMAD是一个用于发现Github上的敏感信息和数据泄漏的工具。通过给定关键字或域&#xff0c;GitMAD便会搜索Github上托管的代码&#xff0c;以查找是否存在匹配项。一旦找到了匹配项&#xff0c;GitMAD将克隆存储库并在文件中搜索一系列可配置的正则表达式。然后&#xff0c;Gi…

【Docker】Docker的三大核心组件

镜像&#xff08;Image&#xff09;、容器&#xff08;Container&#xff09;、仓库&#xff08;Repository&#xff09;是我们常说的Docker的三大组件&#xff0c;接下来就让我们一起详细地探索一番吧。 一、镜像&#xff08;Image&#xff09; 什么是Docker镜像&#xff1f;…

很高兴开始博客之旅

来到博客园&#xff01;&#xff0c;开启我的博客之旅&#xff0c;感觉棒棒哒转载于:https://www.cnblogs.com/pbnull/p/4562230.html

Android Studio Day02-2

AndroidDay02-2&#xff08;AVD&#xff09; Android APP编译的过程之中总是会需要进行相应的功能的调试&#xff0c;以及界面的布局设置在不同手机上的效果的展示。相应的Android平台都包含有Android虚拟设备管理器&#xff0c;Android虚拟设备管理器允许用户自己创建自己的虚…

网络增强现实开发简介 Introduction to Web AR development

搭配webXR、mindAR、three.js和tensorflow.js 你会学到: 获得构建不同类型的网络增强现实应用程序的实践经验&#xff0c;包括图像效果、人脸效果和世界效果 获得关于增强现实如何在网络浏览器中工作的基本理解 掌握使用WebXR、mind-ar-js和threejs构建web AR应用程序 学习使用…

umi脚手架搭建的项目_15天零成本搭建静态博客,托管于Github Page

博客地址技术栈概览前台&#xff1a;Umi(路由) Antd(视图) TypeScript(增加项目可维护性以及规范性)后台&#xff1a;Umi(路由) Antd(视图) TypeScript(增加项目可维护性以及规范性) Rematch(数据管理)服务&#xff1a;Egg.js(基于koa的下一代企业级应用框架) MongoDB搭建…

[maven] 使用问题及思考汇总

&#xff08;1&#xff09;Maven坐标 maven坐标可以唯一标识一个项目&#xff0c;包含四个元素 groupId , artifactId, packaging, version。 groupId&#xff1a;一般为团体,公司,项目。如 oceanic-web, oceanic-dal, oceanic-biz 同属一个 groupId。 artifactId&#xff1a;在…

VS调试时提示此项目已经过期

问题出因&#xff1a; 1.先前卸载VS重新安装时不全面 解决办法&#xff1a; 1.VS应安装两个C的组件 2.首先在“生成”-》 “生成解决方案”完成编译&#xff0c;问题解决。

(DBA之路【五】)关于锁的故事

首先很抱歉&#xff1a;这篇文章我其实整合了很多别人的文章&#xff0c;但是因为太多&#xff0c;一开始被没留意出处所以很难声明来源&#xff0c;很抱歉&#xff0c;但是这篇文章只用来作为学习笔记&#xff0c;作为新手&#xff0c;我以后会注意的。&#xff08;一&#xf…

Android Studio Day03-1(Android studio 系统界面简介)

IDE&#xff08;integrated Development Environment&#xff09;的主要的目的就是用来编辑文本的。 在界面中的分布如下&#xff08;以下的两张图片均为的《Android studio实战快速高效地构建Android应用》一书中的&#xff09;

全流程游戏模型制作学习教程

尤金彼得罗夫|时长:36小时 |视频:H264 19201080 |音频:AAC 44&#xff0c;1 kHz 2ch |大小解压后 35 GB 含课程文件 |语言:英语 (无字幕&#xff0c;) 在本教程中&#xff0c;我将介绍为现代FPS视频游戏创建游戏就绪武器资产的整个过程。我将展示我的工作管道&#xff0c;使用…

requirednew基于xml配置日志不回滚_Elasticsearch配置IK分词器的远程词库

在生活中很多很多地方都涉及到了全文检索&#xff0c;最常见的就好比日常使用到的百度搜索等搜索引擎&#xff0c;也都是基于全文检索来实现的&#xff1b;全文检索种类较多&#xff0c;就好比Elasticsearch、Sorl等。为Ealsticsearch配置词库&#xff0c;可以很好的解决生活中…