LFS文件准备
LFS项目官方网站:https://www.linuxfromscratch.org/
查找宿主系统必须安装的软件包
https://www.linuxfromscratch.org/lfs/downloads/stable/LFS-BOOK-11.1-NOCHUNKS.html
安装依赖:
#先来看看我此处的Yum仓库环境:
CentOS-Base.repo
CentOS-CR.repo
CentOS-Debuginfo.repo
CentOS-fasttrack.repo
CentOS-Media.repo
CentOS-Sources.repo
CentOS-Vault.repo
epel.repo
epel-testing.repo
#安装依赖
yum install -y binutils bison coreutils diffutils findutils gawk gcc grep gzip m4 make patch perl python3 sed tar texinfo xz gcc-c++
然后复制脚本运行查看软件包是否符合标准
bash version-check.sh
#可以看到出现一个not found yacc,说明yacc软件包没有下载下来
补充:yacc是个语法分析器的工具在CentOS7.5中默认没有这个软件包,那我们呢可以用GNU的bsion软件来替代,由于上面我们呢已经下载好了bsion,我们只需要做个软连接即可。
如下操作:
ln -sv /usr/bin/bison /usr/bin/yacc
准备Linux各个部件的源代码
在上面我们把实验环境和依赖环境搞好了,下面来下载Linux各个组件的源码。
下载位置:
1、从各个项目的原始位置下载:
《3.2 All Packer》
https://www.linuxfromscratch.org/lfs/downloads/stable/LFS-BOOK-11.1-NOCHUNKS.html#ch-materials-packages
2、从LFS官网下载
这个时LFS为了方便用户实验,把所有的源码包和补丁全部集中在了一起,并且搞了个大包,我们为了省事,直接下载大包。
位置:官网首页的LFS—>DownLoad:
(https://www.linuxfromscratch.org/lfs/download.html)
http://ftp.lfs-matrix.net/pub/lfs/
[
进入之后可以看到最新的版本:这里暂选11.1版本
一直拉到最下方找到11.1版本的打包进行下载:
下载这个就行,我们可以通过MD5校验一下下载包的完整性:
[root@node2 ~]# md5sum lfs-packages-11.1.tar
9d0d9afebdf99dc90552a8f79b454ad5 lfs-packages-11.1.tar
3、根据官网创建相应存在源码的目录
参考手册《3.1 Introduction》
export LFS=/mnt/lfs
mkdir -v $LFS/sources #存放解压后的源码
mkdir -v $LFS/download #存放下载的源码包
chmod -v a+wt $LFS/sources
tar -xf lfs-packages-11.1.tar
mv 11.1/* .
切换账户做实验
1、root权限很大, 如果用root来登录系统,并且进行软件包的编译、文件的调整、修改等操作,犯一个小错误,就有可能将系统给破坏掉了。
所以,建议制作Linux时使用一个全新的普通用户,并对其进行必要的设置。并在安装过程中使用这个用户。
这一步参考手册:《4.3.Adding the LFS User》
groupadd lfs
useradd -s /bin/bash -g lfs -m -k /dev/null lfs
passwd lfs
chown -v lfs $LFS/tools
chown -v lfs $LFS/sources
chown -v lfs $LFS/download
su - lfs
-bash-4.2$ pwd #可以看到这里头部显示不完美,这是因为上面useradd -k参数的原因,导致环境变量缺失,因此下一步设置环境变量
/home/lfs
2、参考《4.4. Setting Up the Environment》
#创建.bash_profile文件,来设置环境变量
cat > ~/.bash_profile << "EOF"
exec env -i HOME=$HOME TERM=$TERM PS1='\u:\w\$ ' /bin/bash
EOF
#再次创建.bashrc 文件
cat > ~/.bashrc << "EOF"
set +h
umask 022
LFS=/mnt/lfs
LC_ALL=POSIX
LFS_TGT=$(uname -m)-lfs-linux-gnu
PATH=/usr/bin
if [ ! -L /bin ]; then PATH=/bin:$PATH; fi
PATH=$LFS/tools/bin:$PATH
CONFIG_SITE=$LFS/usr/share/config.site
export LFS LC_ALL LFS_TGT PATH CONFIG_SITE
EOF
exit
lfs:~$ ls
3、在编译时我们需要知道每个包花费了多少时间,这是有个标准的,以等会的编译的第一个软件包Binutils所用作为一个标准编译单位(SBU)
其它软件生成时间都以其为标准参考
也可以通过time命令来确定执行的时间
4、在编译之前我们先来个测试文件:
#创建测试文件
lfs:~$ cat > test.c << "EOF"
#include <stdio.h>
main()
{
printf("Hello World\n");
return 0;
}
EOF
#正常情况下此命令没有输出,会在此目录下产生个a.out可执行文件
gcc test.c
#执行
lfs:~$ ./a.out
Hello World
知识准备
术语说明
1、宿主系统
制作Linux系统并不是在一无所有的逻辑上完成的,需要一个可以帮助我们制作系统的系统,这个系统就是宿主系统,我们制作的系统是依靠宿主系统来逐步完成的,因此选择宿主系统很关键。
2、目标系统
就是我们要制作的系统。
3、临时系统
在制作目标系统的过程中需要有一个小型的过渡的系统,这个系统在帮助完成目标系统制作好以后就不再使用了
4、编译工具:gcc…等编译器
5、工具链:编译器,汇编器,链接器、库函数和一些工具组成
- 原工具链:就是安装CentOS7.5时最小化安装过程中安装的工具链
- 预工具链:用于生成临时工具链的工具链
- 临时工具链:用于生成临时系统的工具链
- 目标工具链:用于生成目标系统的工具链
6、辅助工具:除了工具链用到的用具以外,还需要其它的工具,比如说:make
7、运行环境:不明白
源代码编译过程
make编译的处理过程
案例:vim make源代码的编译过程
目前,大多数Linux系统下的二进制程序文件都会采用共享库的运行方式。这样可以减少程序文件本身的代码量,使程序占用的空间更小。
程序运行时的过程中所需要的共享为称为该程序依赖的共享库。这些依赖的共享库必须存在于系统中,并且能在系统找到。
#查看vim的位置
whereis vim
vim: /usr/bin/vim /usr/share/vim /usr/share/man/man1/vim.1.gz
#使用ldd命令查看vim依赖的共享库
[root@node2 ~]# ldd /usr/bin/vim
linux-vdso.so.1 => (0x00007fffb28b4000)
libm.so.6 => /lib64/libm.so.6 (0x00007fb808138000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fb807f11000)
libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007fb807ce7000)
libacl.so.1 => /lib64/libacl.so.1 (0x00007fb807ade000)
libgpm.so.2 => /lib64/libgpm.so.2 (0x00007fb8078d7000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fb8076d3000)
libperl.so => /usr/lib64/perl5/CORE/libperl.so (0x00007fb807345000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fb807129000)
libc.so.6 => /lib64/libc.so.6 (0x00007fb806d5b000)
/lib64/ld-linux-x86-64.so.2 (0x00007fb80843a000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fb806af9000)
libattr.so.1 => /lib64/libattr.so.1 (0x00007fb8068f4000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fb8066da000)
libnsl.so.1 => /lib64/libnsl.so.1 (0x00007fb8064c0000)
libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fb806289000)
libutil.so.1 => /lib64/libutil.so.1 (0x00007fb806086000)
libfreebl3.so => /lib64/libfreebl3.so (0x00007fb805e83000)
#说明:
linux-vdso.so.1 => (0x00007fffb28b4000) 前面时所依赖的库,后面是库所在内存的地址
libattr.so.1 => /lib64/libattr.so.1 (0x00007fb8068f4000) 后面是库的路径
2、工具链制作关系
前面我们说过,gcc和binutils都是依赖于glibc库的,为glibc库有两种:外部,内部。我们想想既然我们需要制作一个目标系统,那么这个目标系统就应该是纯净的,并且在完全脱离宿主系统,也能运行的目标系统,那这样我们这个目标系统就需要由自己的内部Glibc库,因此我们制作内部的Glibc,Gcc,Binutils库。
下面我们逐步分析目标系统工具链时如何制作完成的:
(1)通过宿主原生态的glibc_A,gcc_A,binutils_A来制作gcc_B,binutils_B,glibc_B
先基于原始的glibc_A生成gcc_B,binutils_B
上图补充:此时gcc_A,binutils_A使用glibc_A称为内部glibc_A依赖,而gcc_B,binutils_B使用glibc_A称为外部glibc_A依赖。
然后基于原始的glibc_A生成glibc_B:
这样以上步骤就把临时工具链生产出来了。
(2)然后我们可以通过临时工具链来编译安装应用程序了:
既然利用临时工具链(gcc_B,binutils_B,glibc_B)可以编译安装程序B,那在利用临时工具(gcc_B,binutils_B,glibc_B)创建gcc_C,,binutils_C,当然也可以了:
那么这样一来目标系统的工具链就完成了:
因此下面的目标就明确了:先生成临时工具链B,在生成目标工具链C。
定制Linux系统制作步骤
我们可以对Linux系统的制作过程进行流程分解,整个制作过程分成3个阶段:
- 预工具链阶段
- 临时系统阶段
- 目标系统阶段
这三个阶段按顺序依次进行,每一个阶段都在为下一阶段做准备,而且三个阶段是相互交叉的。
预工具链:负责生成临时系统的glibc,就是上一节中的glibc_B。
临时工具链:将预工具链以来方式调整,将gcc_B,binutils_B指向glibc_B从而形成临时工具链,临时工具链+富足工具=临时系统。
特别说明下辅助命令:目标系统的辅助命令是通过临时工具链提供的,那临时系统的辅助命令啥时候可以不需要?就是在目标系统建立完成后,并且目标系统有了自己内部的辅助命令之后,那么目标系统此时就可以脱离临时系统的辅助命令了
在目标系统的工具链,辅助命令,其它命令搞好之后,最后我们还需要那操纵系统独立起来我们需要对启动过程进行相应的处理,可能需要用grub等引导加载器把系统引导加载起来
Linux操作系统制作前的分区准备
参考手册:《2.3Building LFS in Stages》中的:
2.3.1. Chapters 1–4
- 以root用户来运行
- 为root用户来设置LFS环境变量
2.3.2. Chapter 5–6
- 必须挂载/mnt/lfs分区
- 所有指令必须由用户lfs来执行
2.3.3. Chapter 7–10
- 必须挂载/mnt/lfs分区
- 当进入chroot后,必须为root设置LFS环境变量
- 必须挂载虚拟文件系统
1、分区准备:
准备40GB磁盘,Swap:8G,ext4:32GB
#第一步正常分区,一个8G,一个32G
#将/dev/sdb1变为Swap分区
mkswap /dev/sdb1
#格式化/dev/sdb2
mkfs.ext4 /dev/sdb2
2、设置环境变量,参考手册:《2.6 Setting The $LFS Variable》
export LFS=/mnt/lfs #这一步可以写入到~/.bash_profile
vi .bash_profile
export LFS=/mnt/lfs
mkdir -pv $LFS
mount /dev/sdb2 $LFS #这一步可以写在/etc/fstab中
vi /etc/fstab
/dev/sdb2 /mnt/lfs ext4 defaults 0 0
#测试
umount /mnt/lfs
mount $LFS
df -TH
制作Linux的目录结构
对应手册:《4.2》
- 创建$LFS/tools文件夹
- 添加LFS用户
- 配置LFS的环境
- 软件包的编译时间
- 软件包的测试
我们会在$LFS中新建一个文件夹用于临时工具的安装,增加一个非特权用户用于降低风险,并为该用户创建合适的构建环境。
本次使用创建$LFS/tools文件夹,这是很重要的。
#来一遍环境变量
export LFS=/mnt/lfs
mkdir -v $LFS/tools
ln -sv $LFS/tools /
‘/tools’ -> ‘/mnt/lfs/tools’
构建临时系统
参考手册:《5》
一切准备工作做好了之后,我们需要先构建一个临时的系统。它是一个迷你的Linux系统,仅包含一个基本够用的工具集。该工具集要与宿主系统分离、不依赖宿主系统。
如图所示,制作临时大致的过程可以分解为:
- 利用宿主系统来安装生成“预工具链” 预工具链有两个:gcc_B,binutils_B(汇编编译器)
- 用“预工具链”来生成“临时工具链”
- 用“临时工具链+辅助命令”生成“临时系统。
来看看预工具链所需要的软件:
再来看看临时工具链所需要的软件:
其余剩下的软件就是辅助命令。
制作Linux的正确姿势
构建临时系统,需要编译30多个包,每个包的编译过程大同小异。“标准”姿势是这样的:
- 进入到LFS的的源文件目录,即用sources目录
- 以lfs用户身份,解压要编译的软件包
- 进入到解压后创建的目录中。
- 根据指南说明编译、检查、安装软件包
- 回退到源文件目录
- 除非特别说明,删除解压出来的目录和所有编译过程中生成的目录
要注意,其中有些包编译时间比较长。如果是使用ssh、telnet连接实验环境,一但网络中断会比较麻烦,所以我们需要使用screen来应对这种问题。
screen的使用
1、安装:
yum install -y screen
2、screen语法:
常用参数:
screen组合键:
第一遍构建Binutils
参考手册:《5.2.1. Installation of Cross Binutils》
Binutils软件包包括了一个链接器、汇编器和其它处理目标文件的工具。
第一次编译Binutils软件包很重要,因为Glibc和GCC会对可用的链接器和汇编器执行各种测试以决定启用它们自己的哪些功能。
同时,LFS的作者将Binutils的编译时间作为一个基准值–SBU,后续包的编译安装时间与参照这个基准值。
su - lfs
cd $LFS/sources
tar xvf ../download/binutils-2.38.tar.xz
#对于Binutils,最好单独挡在一个目录中编译
mkdir build
cd build/
../configure --prefix=$LFS/tools \
--with-sysroot=$LFS \
--target=$LFS_TGT \
--disable-nls \
--disable-werror
make
make install
第一遍构建GCC
GCC软件包是GNU编译器集合的一部分,其中包括C和C++的编译器。
参考文档:《5.3. GCC-11.2.0 - Pass 1》
lfs:/mnt/lfs/sources$ tar xvf ../download/gcc-11.2.0.tar.xz
lfs:/mnt/lfs/sources/gcc-11.2.0$ tar -xf $LFS/download/mpfr-4.1.0.tar.xz
lfs:/mnt/lfs/sources/gcc-11.2.0$ mv -v mpfr-4.1.0 mpfr
lfs:/mnt/lfs/sources/gcc-11.2.0$ tar -xf $LFS/download/gmp-6.2.1.tar.xz
lfs:/mnt/lfs/sources/gcc-11.2.0$ mv -v gmp-6.2.1 gmp
lfs:/mnt/lfs/sources/gcc-11.2.0$ tar -xf $LFS/download/mpc-1.2.1.tar.gz
lfs:/mnt/lfs/sources/gcc-11.2.0$ mv -v mpc-1.2.1 mpc
case $(uname -m) in
x86_64)
sed -e '/m64=/s/lib64/lib/' \
-i.orig gcc/config/i386/t-linux64
;;
esac
mkdir -v build
cd build
../configure \
--target=$LFS_TGT \
--prefix=$LFS/tools \
--with-glibc-version=2.35 \
--with-sysroot=$LFS \
--with-newlib \
--without-headers \
--enable-initfini-array \
--disable-nls \
--disable-shared \
--disable-multilib \
--disable-decimal-float \
--disable-threads \
--disable-libatomic \
--disable-libgomp \
--disable-libquadmath \
--disable-libssp \
--disable-libvtv \
--disable-libstdcxx \
--enable-languages=c,c++
make
make install
cd ..
cat gcc/limitx.h gcc/glimits.h gcc/limity.h > \
`dirname $($LFS_TGT-gcc -print-libgcc-file-name)`/install-tools/include/limits.h
安装Linux内核的API头文件
在编译glibc的时候需要用到内核的相关信息,为了能让glibc顺利编译,我们需要在编译glibc之前安装内核相关的头文件,内核相关头文件是放在内核的源代码中的,这个操作是从内核源代码中提取所需要的各种头文件,通过这部分头文件,可以让编译器了解如何调用内核来提供的各种各样的功能,通过这个接口的调用来编译需要运行的程序。
参考手册:《5.4.1. Installation of Linux API Headers》
#在lfs用户的/mnt/lfs/sources目录下操作
lfs:/mnt/lfs/sources$ tar xf ../download/linux-5.16.9.tar.xz
#清除内核中多余的代码,从而得到干净的内核
cd linux-5.16.9/
make mrproper
#从源代码中提取用户可见的内核头。
make headers
lfs:/mnt/lfs/sources/linux-5.16.9$ find usr/include -name '.*' -delete
lfs:/mnt/lfs/sources/linux-5.16.9$ rm usr/include/Makefile
lfs:/mnt/lfs/sources/linux-5.16.9$ cp -rv usr/include $LFS/usr