标签:映射 www 包含 ima dev 区块 位置 journal 厂商
http://www.mamicode.com/info-detail-1538676.html
我们知道,一个Linux系统的主要组成是由liunx内核核心和一些支持模块组合而成的。但是在某些场合中,需要某项功能,而当前内核的核心或者模块不支持此功能,那么就需要对内核进行一个升级或者重新编译内核添加相应的功能,以此提供了对此功能的支持。
编译前的准备
认识kernel
所为kernel,就是一种操作系统的核心,当然也是一个文件,而这种核心提供了对一些硬件的支持,一般来说其中包含了一些对常见硬件核心驱动的核心代码。启动系统时会通过加载MBR中的bootloader,然后将此核心文件加载到内存中。因为只是保留了核心,所有一般来说,kernel的大小也不会很大。一般kernel文件会保存/boot目录在,格式为:
/boot/vmlinuz-XXX,如CentOS7中的:
[root@localhost ~]# ls -l /boot/vmlinuz-3.10.0-327.el7.x86_64 -rwxr-xr-x. 1 root root 5156528 Nov 19 2015 /boot/vmlinuz-3.10.0-327.el7.x86_64
内核模块(kernel module)
kernel文件中已经包含了一些最基本的硬件探测与驱动模块,但是其中的内容并不能完全是由于所有的硬件驱动,所有,对于一些不常用、较新的驱动,linux中会提供一种模块化的机制,及接需要硬件或者新功能时,可以通过加载这些核心模块来提供对新功能的支持。当然核心模块的各种模块文件是独立存在的,一般存放在/lib/modules/$(uname -r)/kernel/下:
[root@localhost ~]# ls /lib/modules/3.10.0-327.el7.x86_64/kernel/ arch crypto drivers fs kernel lib mm net sound
为什么要编译内核
需要新功能的支持:
在生产环境中,突然会需要使用一些新的功能,但是原有的内核并不支持此功能,这是就需要重新编译出一个新的内核来提供对此新功能的支持,比如虚拟化、iptables功能必须要在2.4.X以上版本的内核中才支持。
原核心太过于臃肿:
原来安装的内核中,有很多不常用甚至基本上用不到的功能支持,而这些功能又会使内核文件变得很大,这时要将此变成一个简化切功能够用的内核就需要进行重新编译。
与硬件搭配的稳定性:
很多情况下,系统内核中默认一般默认支持的CPU类型为Inter,但在其它厂商,如AMD或者是较新的CPU硬件上使用此系统,可能就不能很好的支持,且不稳定。因此就需要重新编译内核来支持最新的硬件驱动。
其它特殊需求:
linux除了在pc机上运行,当然也能支持一些其它设备,比如嵌入式的APM、智能手机等,这时或许就要进行重新编译支持的CPU架构来支持这些特殊需求。
总结一点:kernel的作业就是所需要的硬件支持,因此在此基础上只加需要的功能,来实现内核文件的短小精干。
源码包解析
源码包解压及位置放置:
下载kernel内核源码包,这里我下载到/root目录下
#查看文件的压缩格式,发现为.xz格式
[root@localhost ~]# ls -l /root/linux-3.10.89.tar.xz -rw-r--r--. 1 root root 73306144 Sep 13 08:09 /root/linux-3.10.89.tar.xz
#一般kernel核心文件一般放置在下/usr/src/kernels
[root@localhost ~]# ls /usr/src/kernels/ 3.10.0-327.el7.x86_64
解析:这里是现在正在使用的内部版本为3.1.0版本,这里我们我们解压到kernel目录下。
#解压linux-3.10.89.tar.xz 内核源码到/usr/src/kernels目录下
[root@localhost ~]# tar -Jxf /root/linux-3.10.89.tar.xz -C /usr/src/kernels/
#查看目录是否已经解压成功,这里linux-3.1.0.89为刚才解压的目录
[root@localhost ~]# ls -l /usr/src/kernels/ total 8 drwxr-xr-x. 22 root root 4096 Sep 7 16:14 3.10.0-327.el7.x86_64 drwxrwxr-x. 23 root root 4096 Sep 21 2015 linux-3.10.89
#为了方面操作,切换到/usr/src/kernels目录,并给此目录添加一个符号链接
[root@localhost kernels]# ln -sv ./linux-3.10.89/ ./linux ‘./linux’ -> ‘./linux-3.10.89/’ [root@localhost kernels]# ls -l ./linux lrwxrwxrwx. 1 root root 16 Sep 13 08:35 ./linux -> ./linux-3.10.89/
#进入此目录
[root@localhost kernels]# cd ./linux [root@localhost linux]# pwd -L /usr/src/kernels/linux [root@localhost linux]# pwd -P /usr/src/kernels/linux-3.10.89
内核模块内部目录解析:
#查看所有目录
[root@localhost linux]# ls -d ./*/ ./arch/ ./crypto/ ./drivers/ ./fs/ ./init/ ./kernel/ ./mm/ ./samples/ ./security/ ./tools/ ./virt/ ./block/ ./Documentation/ ./firmware/ ./include/ ./ipc/ ./lib/ ./net/ ./scripts/ ./sound/ ./usr/
#核心原始码下的此目录作用说明:
arch:与硬件平台有关的项目,大部分指的是CPU的类型,例如x86,x86_64,Xen虚拟支持等。
block:与成组设备较相关的设定数据,区块数据通常指一些大量存储媒体,还包括类似ext3等文件系统的支持是否允许等。
crypto:核心所支持的加密技术,如md5、des、sha512等。
Documentation:与核心有关的一堆说明文件,其中包括了对上面所有目录里的说明。
firmware:一些旧式硬件的微脚步数据。
fs:内核所支持的filesystems(文件系统),例如ext系列、ntfs、reisefs等。
include:一些可让其它过程调用的标头(header)定义数据。
init:一些初始化的定义功能,包括挂载和init 程序的呼叫等。
ipc:定义Linux操作系统内各程序进程间的通信。
kernel:定义核心的程序、核心状态、线程、程序的排程(schedule)、程序的讯号(signle)等。
lib:一些函数库。
mm:与内存单元有关的各项数据,包括swap与虚拟内存等。
net:与网络有关的各项协议数据,还有防火墙模块(net/ipv4/netfilter/*) 等。
security:包括selinux等在内的安全性设定。
sound:与音效有关的各项模块。
virt:与虚拟化机器有关的信息,目前核心支持的是KVM( Kernel base Vitual Machine )。
注意:这里目录说明,对于将来要通过patch模块补丁文件来升级模块的新功能有很多作业,知道了相应的作业去查找对应位置才会成功更新模块。
内核编译前的准备
1、查看当前目标主机的硬件设备相关信息:
使用的命令有关:lscpu(cpu详细信息)、lspci(pci插槽)、lsusb(usb接口)、lsblk(块设备)、hal-device(所有硬件详细信息)。
注:上面的命令如果是最小化安装的系统,可能会没有这些工具,因此安装对应工具包即可
#安装命令需要的工具包
[root@localhost linux]# yum install -y pciutils usbutils util-linux
#查看cpu型号
[root@localhost linux]# lscpu |sed-n‘s/^Model [^[:space:]]\+:[[:space:]]\+\([^[:space:]]\+\)/\1/gp‘ Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
#查看pci
[root@localhost linux]# lspci | grep -o ‘[A-Z].*‘ | uniq -c | tr -s ‘ ‘ | xargs -I {} echo -e ‘\e[032m‘ {} ‘\e[0m‘
#查看usb接口
[root@localhost linux]# lsusb
清空源代码的其它信息
注:下载下来的源码包一般情况下不确定是否已经编译过,或者还残留有生成的一起文件,这里为了编译时不会出现未知的错误,进行清理。
#对内核源码目录进行清理
[root@localhost linux]# pwd -P /usr/src/kernels/linux-3.10.89 [root@localhost linux]# make mrproper
核心功能的挑选
注:在我们当前已经安装的系统中的/boot目录下存在一个名为conf-xxx 的文件,那个文件其实就是核心功能列表选择文件,如:
[root@localhost linux]# ls -l /boot/config-3.10.0-327.el7.x86_64 -rw-r--r--. 1 root root 126426 Nov 19 2015 /boot/config-3.10.0-327.el7.x86_64
解析:在通过核心功能挑选结束后,会在源码核心目录下生成一个.config的隐藏文件,其中就是/boot/config-xxx文件。
#下面验证当前系统下的conf-xxx文件是否就是其对应.config文件:
[root@localhost linux]# diff /boot/config-`uname -r` /usr/src/kernels/`uname -r`/.config [root@localhost linux]# echo $? 0
解析:这里并是由diff文件比较命令,并没有比较出有什么不同,所有,可以推断config-xxx文件就是对应的内核版本目录下的.config文件。
编码内核选择界面(生成.config的方法):
make help: #支持“更新模式进行配置”。 make menuconfig: #基于curses的文本窗口界面 make gconfig: #基于GTK(GOME)环境窗口界面 make xconfig: #基于QT(KDE) 环境的窗口界面 make config: #老旧的命令行遍历方式逐一配置每个可配置的选项 make oldconfig: #透过已经存在的./.config文件内容,并使用该文件内设定值为默认值,只将新版本核心的新功能列出让用户选择,可以简化核心功能挑选过程。对与升级内核很好选择。 make defconfig: #基于内核为目标平台执行提供的“默认”配置进行配置 make allyesconfig: #所有选项均回答为”yes” make allnoconfig: #所有选项均回答为”no”
根据已有的设定来处理核心项目与功能的选择:
#拷贝当前系统内核中的config-xxx文件到需要编译的内核目录下
[root@localhost linux]# cp /boot/config-`uname -r` ./.config [root@localhost linux]# ls -l ./.config -rw-r--r--. 1 root root 126426 Sep 13 10:10 ./.config
注意:这里拷贝之后,一定不要再使用make mrproper,不然会清除刚才拷贝的.config文件。
使用make menuconfig 命令进行菜单化界面来选择内核功能:
[root@localhost linux]# make menuconfig HOSTCC scripts/basic/fixdep HOSTCC scripts/kconfig/conf.o *** Unable to find the ncurses libraries or the *** required header files. *** ‘make menuconfig‘ requires the ncurses libraries. *** *** Install ncurses (ncurses-devel) and try again. *** make[1]: *** [scripts/kconfig/dochecklxdialog] Error 1 make: *** [menuconfig] Error 2
解析:这里出现了错误,根据上面的提示说需要安装ncurses库文件。并提示了其库文件的软件包名为ncurses-devel。
#于是安装ncuress-devel包
[root@localhost linux]# yum install ncurses-devel -y
#同时检查是否有编译环境,虽然已经安装了gcc,但是为了保险起见,还是安装一个开发工具包组”Development Tools”
[root@localhost ~]# rpm -q gcc gcc-4.8.5-4.el7.x86_64
#再次使用make menuconfig进入功能选择菜单
解析:根据界面头部的信息提示可知:
左右箭头键:用来移动选择最下面的select、exit、help、save、load几个按钮
上下箭头键:可以移动上面的菜单来选中对应的功能选项,选项后与--> 表示内部有子菜单需要选择。
选定项目:使用上下键选择设定的项目,并使用左右键选择select按钮,然后按回车就可进入项目中的子菜单选择。
挑选功能:在项目的功能名称前面,有[]或者<>才可以使用 空格键 来选择。
挑选说明:显示为<*>[*]表示编译进核心;若为<M>表示编译成模块。
离开选择项:左右键选择Exit按回车即可
功能选择的建议:
1、核心一定要的功能,直接编译进核心内;
2、可能以后会用到的功能,尽量编译成模块;
3、不指定功能的具体作业,按help显示帮助也不明显,保留其默认值或编译为模块。
各个项目菜单选项的介绍:
Generl setup基本的linux核心功能,最相关的驱动程序、版本说明 核心说明代码。
()Cross-compiler toolprefix #是否使用交叉编译 (meng-1-1.1) Local version - append to kernel release #自定义一个版本号 [*]Automatically append version information to the version string #是否自动追加版本号 Kernel compression mode (Bizp2) ---> #这里进入子菜单建议选择bizp2,压缩比高 ((none)) Default hostname <M> Kernel .config support [ ] Enable access to .config through /proc/config.gz (NEW) #是否生成config文件保留配置 (20) Kernel log buffer size (16 => 64KB, 17 => 128KB) #登录文件的缓存文件默认值即可 [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support #支持开启启动加载initrd文件 [ ] Optimize for size #是否减低核心文件大小,这里不需要 Configure standard kernel features (expert users) ---> [ ] Embedded system #是否支持嵌入式系统,否
loadable module 模块加载和卸载
[*] Enable loadable module support #启用才能进入子菜单 --- Enable loadable module support [*]Forced module loading #是否强制加载模块 [*] Module unloading #模块卸载 [*] Forced module unloading #强制卸载模块 [*] Module versioning support #支持模块多版本 [*] Source checksum for all modules #是否让检查原有所有模块校验和 [*] Module signature verification [ ] Require modules to be validly signed [*] Automatically sign all modules #模块功能的自动调用 Which hash algorithm should modules be signed with? (Sign modules with SHA-256) #选SHA256
block layer 块设备的支持
--Enablethe blocklayer -*- Block layer SG support v4 #块层对SGv4的支持 [*]BlocklayerSG support v4 helperlib #块层对SGv4的辅助支持 [*]Block layer data integritysupport #块层完整性的支持 [*]Block layer bio throttling support #块层节流此支持 Partition Types ---> #分区类型的支持 [*] Macintosh partition map support #苹果系统分区结果的支持 [*] PC BIOS (MSDOS partition tables) support #基于mbr的分区,一般为必选 [*]Windows Logical Disk Manager (Dynamic Disk) support #windows逻辑卷动态管理器支持 [*]SGI partition support #SGI分区结构支持 [*]EFI GUID Partition support #EFI及对与GPT分区结构的支持 IO Schedulers ---> #磁盘阵列的处理方式 [*]Deadline I/O scheduler #最后期限调度 <*> CFQ I/O scheduler #CFQ磁盘 [*] CFQ Group Scheduling support Default I/O scheduler (Deadline) ---> #默认磁盘整列处理方式,一般为Deadline
Processor type and features 处理器类型和其功能选择
Symmetric multi-processing support #多处理器的支持 Linux guest support ---> #Linux 虚拟化的支持 [*]Enable paravirtualization code #启用半虚拟化代码 [*]Paravirtualization layer for spinlocks #半虚拟化 [*] Xen guest support #高级虚拟化 [*]KVM Guest support (including kvmclock) #虚拟化锁 [*]Paravirtual steal time accounting Processor family (Generic-x86-64) ---> #通用的处理器结构,如果不是很老的处理器就选择 [*]Enable Maximum number of SMP Processors and NUMA Nodes #启动SMP处理器和NUMA节点最大数量 [*]Multi-core scheduler support #多核心处理器调用 Preemption Model (Voluntary Kernel Preemption (Desktop)) ---> #选择优先模式,推荐为Server Timer frequency (300 HZ) --->#表示处理器的回应,如果为桌面模式,可以设置1000HZ
Power management and ACPI options 电源管理和ACPI选项
[*]ACPI (Advanced Configuration and Power Interface) Support ---> #高级配置和接口 [*]CPU Frequency scaling ---> #CPU频率扩展 <M> CPU frequency translation statistics Default CPUFreq governor (ondemand) ---> #一般选择ondemand会更好,下面默认即可 -*- ‘performance‘ governor <*> ‘powersave‘ governor <*> ‘userspace‘ governor for userspace frequency scaling -*- ‘ondemand‘ cpufreq policy governor <*> ‘conservative‘ cpufreq governor x86 CPU frequency scaling drivers --->
Bus options (PCI etc.) 总线选项和PCI插槽
[*] PCI support #PCI支持 [*] Support mmconfig PCI config space access #PCI内存访问空间 [*] PCI Express support #PCI-E支持 <*> PCI Express Hotplug driver #子项目默认值即可 [*] PCI Debugging #如果需要虚拟化,此选项必选
Executable file formats / Emulations 编译后执行档的格式
-*- Kernel support for ELF binaries #支持扩展二进制文件 [*] Write ELF core dumps with partial segments #部分转储片段写入扩展核心 <*> Kernel support for scripts starting with #! #内核脚步支持 <M> Kernel support for MISC binaries #misc程序支持 [M] IA32 Emulation #模拟32功能,为了兼容而已 <M> IA32 a.out support #32为输出格式支持 [*] x32 ABI for 64-bit mode #f仿32位的64模式
Networking support 核心网络功能
[*] TCP/IP networking #TCP/IP网络协议支持 [*] IP: multicasting #IP多播传输 [*] IP: advanced router #高级路由功能 [*] FIB TRIE statistics #静态TRIE [*] IP: policy routing #路由规则 [*] IP: equal cost multipath #多路径平衡传输 [*] IP: verbose route monitoring #路由监控 [*] IP: broadcast GRE over IP #IP广播 [*] IP: multicast routing #多播路由 [*] IP: multicast policy routing #多播路由规则 [*] IP: PIM-SM version 1 support #PIM-SM支持 [*] IP: PIM-SM version 2 support [*] IP: TCP syncookie support #数据同步 [*] Network packet filtering framework (Netfilter) ---> #网络防火墙 [*]Core Netfilter Configuration ---> #里面都编译成模块 [*] QoS and/or fair queueing ---> #里面都编译成模块 [*]Network testing ---> #默认值即可
Device Drivers 设备驱动
Serial ATA and Parallel ATA drivers ---> #里面有对IDE/SATA硬盘的支持 [*] Multiple devices driver support (RAID and LVM) ---> #raid整列和lvm逻辑卷的支持 -*- Network device support ---> #网络设备,网卡等驱动 <M> Bonding driver support --> #多网卡绑定功能驱动,要选 <M>Ethernet team driver support ---> #team网卡组支持,要选 <M>Virtio network driver --> #虚拟网卡 --- Ethernet driver support #以太网卡类型,选择10G卡 [*] Brocade devices <M> Brocade 1010/1020 10Gb Ethernet Driver support <M> Calxeda 1G/10G XGMAC Ethernet driver <M> Chelsio 10Gb Ethernet support <M> Intel(R) PRO/10GbE support <M> PPP (point-to-point protocol) support #拨号上网协议 USB Network Adapters ---> #移动网络适配器,全打成模块 [*] Wireless LAN ---> #无线网卡 <*> xHCI HCD (USB 3.0) support <*> EHCI HCD (USB 2.0) support <*> OHCI HCD support <*> UHCI HCD (most Intel and VIA) support [*] Virtualization drivers ---> #虚拟化驱动程序 Virtio drivers ---> #虚拟化项目程序驱动 Hardware Spinlock drivers ---> #跟虚拟化有关
File systems 文件系统的支持
<M> Second extended fs support #ext2的支持 <M> Ext3 journalling file system support #ext3的支持 [*] Default to ‘data=ordered‘ in ext3 (NEW) [*] Ext3 extended attributes (NEW) [*] Ext3 POSIX Access Control Lists <M> The Extended 4 (ext4) filesystem #ext4必选 <M> XFS filesystem support #xfs必选 [*] XFS Quota support #xfs文件系统对quota的支持 [*] XFS POSIX ACL support #xfs中访问控制列表支持 <M> Btrfs filesystem support #btrufs文件系统 [*] Quota support #磁盘配额驱动模块 CD-ROM/DVDFilesystems---> #iso镜像都考它 DOS/FAT/NT Filesystems ---> #dos文件系统 <M> MSDOS fs support #dos分区文件系统 <M> VFAT (Windows-95) fs support #vfat/fat32 (950) Default codepage for FAT #950 支持中文 (uft-8) Default iocharset for FAT #支持中文 <M> NTFS file system support #支持NTFS文件系统 [*] NTFS write support #支持对NTFS分区系统可写 Pseudo filesystems ---> #对文件系统设备的映射 --- Native language support #选择utf8为预设的语言 (utf8) Default NLS Option
解析:因为是linux-3.10.89,所有相比其它低版本kernel的功能需要选择有所不同,这里以centos7为例,所有一些重要的功能还是要选择的。
#选择完成后选择第一个界面的SAVE保存按钮,保存到.config文件。
编译安装具体步骤:
1、核心和模块编译级别模块安装
[root@localhost linux]# make -j 4 clean [root@localhost linux]# make -j 4 bzImage Setup is 16752 bytes (padded to 16896 bytes). System is 4594 kB CRC 9dced73c Kernel: arch/x86/boot/bzImage is ready (#1)
解析:最后出现这些表示显示成功。
#查看bzImage内核是否已经生成
[root@localhost linux]# ll arch/x86/boot/bzImage -rw-r--r--. 1 root root 4720464 Sep 13 16:30 arch/x86/boot/bzImage
#编译模块
[root@localhost linux]# make -j 4 modules
#安装模块
[root@localhost linux]# make modules_install
#查看安装好的模块文件
[root@localhost linux]# ll -ld /lib/modules/*/ drwxr-xr-x. 7 root root 4096 Sep 7 16:25 /lib/modules/3.10.0-327.el7.x86_64/ drwxr-xr-x. 3 root root 4096 Sep 13 16:40 /lib/modules/3.10.89meng-1-1.1/
解析:这里的3.10.89meng-1.1就是刚才我们安装好的模块。
2、开始配置安装新核心与多核心group菜单
注:实际上之后直接执行make install就能直接安装内核了,但是为进一步认识centos7中的grub2新机制,这里我们模拟实现make install的工作,手动添加内核及grub配置
模拟make install的文件拷贝工作
#移动新内核到/boot目录下
[root@localhost linux]# basename /lib/modules/3.10.89meng-1-1.1/ 3.10.89meng-1-1.1
#拷贝内核文件到/boot目录下
[root@localhost linux]# cp arch/x86/boot/bzImage /boot/vmlinuz-`basename /lib/modules/3.10.89meng-1-1.1/`
#将.config备份文件也靠过来,当做个备份
[root@localhost linux]# cp .config /boot/config-`basename /lib/modules/3.10.89meng-1-1.1/`
#给vmlinuz内核文件加执行权限
[root@localhost linux]# chmod a+x /boot/vmlinuz-3.10.89meng-1-1.1
#拷贝系统内核映射文件
[root@localhost linux]# cp System.map /boot/System.map-`basename /lib/modules/3.10.89meng-1-1.1/`
#拷贝内核模块列表
[root@localhost inux]# gzip -c Module.symvers > /boot/symvers-`basename /lib/modules/3.10.89meng-1-1.1/`.gz
#重新还原/boot目录下文件的安全上下文
[root@localhost linux]# restorecon -Rv /boot restorecon reset /boot/System.map-3.10.89meng-1-1.1 context unconfined_u:object_r:boot_t:s0->unconfined_u:object_r:system_map_t:s0
安装新版本的initrd/initramfs文件
#生成对应版本的initramfs文件
[root@localhost linux]# dracut -v /boot/initramfs-`basename /lib/modules/3.10.89meng-1-1.1/`
#更新grub.cfg配置,加入新内核记录
[root@localhost linux]# grub2-mkconfig -o /boot/grub2/grub.cfg Generating grub configuration file ... Found linux image: /boot/vmlinuz-3.10.89meng-1-1.1 Found linux image: /boot/vmlinuz-3.10.0-327.el7.x86_64 Found initrd image: /boot/initramfs-3.10.0-327.el7.x86_64.img Found linux image: /boot/vmlinuz-0-rescue-fb8d28a0639e4047894f5d177a5d8be3 Found initrd image: /boot/initramfs-0-rescue-fb8d28a0639e4047894f5d177a5d8be3.img done
#查看/boot/grub2/grub.cfg下啊是否生成了新的菜单
[root@localhost linux]# grep -o -e ‘linux16.*meng‘ -oe ‘^menuentry.*meng.*)‘ /boot/grub2/grub.cfg menuentry ‘CentOS Linux (3.10.89meng-1-1.1) 7 (Core) linux16 /vmlinuz-3.10.89meng
#查看当前grub里的menuentry菜单,新内核排在第一个
[root@localhost linux]# grep -o "^menuentry [^[:space:]]\+.*)" /boot/grub2/grub.cfg menuentry ‘CentOS Linux (3.10.89meng-1-1.1) 7 (Core) menuentry ‘CentOS Linux (3.10.0-327.el7.x86_64) 7 (Core) menuentry ‘CentOS Linux (0-rescue-fb8d28a0639e4047894f5d177a5d8be3) 7 (Core)
#查看新内核的grub2.conf配置详细信息
注意:上面好像出现了问题,没有将initramfs.img文件读入其中,那么就需要手动添加此行。这必须必须的啊,不然怎么找到/目录。
#在linux16及kernel内核指定行下加一行为initrd16表示initramfs路径
#重启机器,测试内核是否有问题
[root@localhost linux]# shutdown -r now
标签:映射 www 包含 ima dev 区块 位置 journal 厂商
原文地址:https://www.cnblogs.com/SDYiHeng/p/9655180.html