"多快好省"——交叉编译构建自己的linux操作系统
“”

术语

  • Host:执行交叉编译、安装动作的主机
  • Target:使用源码交叉编译出来的目标机,会使用Host的交叉编译工具
  • Cross compiler:在Host上编译出来的交叉编译器,在Host上运行,但是它用于编译与Host不兼容的目标体系结构或微处理器。

先决条件和工具

要进行一下操作过程,你需要在Host上有gcc,make,ncurses,perl以及grub工具(尤其是grub-install或者grub2-install)。

  • 为了构建目标系统,你需要为交叉编译下载且编译以下开源包:
    • binutils-2.30.tar.xz
    • busybox-1.28.3.tar.bz2
    • bootscripts-clfs-embedded-1.0-pre5.tar.bz2
    • gcc-7.3.0.tar.xz
    • glibc-2.27.tar.xz
    • gmp-6.1.2.tar.bz2
    • linux-4.16.3.tar.xz
    • mpc-1.1.0.tar.gz
    • mpfr-4.0.1.tar.xz
    • zlib-1.2.11.tar.gz

1.配置环境

  • 1.1在开始之前,需要配置编译的shell环境,开启bash的hash功能
set +h
  • 1.2确保新创建的文件/目录仅由所有者可写(例如,当前登录的用户帐户)
umask 022
  • 1.3您将使用主目录作为主构建目录。(这不是必须)。这里将安装交叉编译工具链和目标映像,并将其放入lj-os子目录中。如果您想在其他地方安装它,请对下面的代码部分进行调整:
export LJOS=~/lj-os
mkdir -pv ${LJOS}
  • 1.4 导入一些剩下的变量
export LC_ALL=POSIX
export PATH=${LJOS}/cross-tools/bin:/bin:/usr/bin
  • 1.5 创建一些创建目标映像的根文件系统层次结构
mkdir -pv ${LJOS}/{bin,boot/{,grub},dev,{etc/,}opt,home,lib/{firmware,modules},lib64,mnt}
mkdir -pv ${LJOS}/{proc,media/{floppy,cdrom},sbin,srv,sys}
mkdir -pv ${LJOS}/var/{lock,log,mail,run,spool}
mkdir -pv ${LJOS}/var/{opt,cache,lib/{misc,locate},local}
install -dv -m 0750 ${LJOS}/root
install -dv -m 1777 ${LJOS}{/var,}/tmp
install -dv ${LJOS}/etc/init.d
mkdir -pv ${LJOS}/usr/{,local/}{bin,include,lib{,64},sbin,src}
mkdir -pv ${LJOS}/usr/{,local/}share/{doc,info,locale,man}
mkdir -pv ${LJOS}/usr/{,local/}share/{misc,terminfo,zoneinfo}
mkdir -pv ${LJOS}/usr/{,local/}share/man/man{1,2,3,4,5,6,7,8}
for dir in ${LJOS}/usr{,/local}; do
    ln -sv share/{man,doc,info} ${dir}
done
  • 1.6 创建交叉编译工具链的目录
install -dv ${LJOS}/cross-tools{,/bin}
  • 1.7 使用/proc/mounts的符号链接来维护/etc/mtab文件中正确挂载的文件系统列表
ln -svf ../proc/mounts ${LJOS}/etc/mtab
  • 1.8创建/etc/passwd文件
cat > ${LJOS}/etc/passwd << "EOF"
root::0:0:root:/root:/bin/ash
EOF
  • 1.9创建/etc/group文件
cat > ${LJOS}/etc/group << "EOF"
root:x:0:
bin:x:1:
sys:x:2:
kmem:x:3:
tty:x:4:
daemon:x:6:
disk:x:8:
dialout:x:10:
video:x:12:
utmp:x:13:
usb:x:14:
EOF
  • 1.10创建目标系统的/etc/fstab
cat > ${LJOS}/etc/fstab << "EOF"
# file system mount-point type options dump fsck
# order

rootfs / auto defaults 1 1
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
devpts /dev/pts devpts gid=4,mode=620 0 0
tmpfs /dev/shm tmpfs defaults 0 0
EOF
  • 1.11创建目标系统的/etc/profile,一旦用户登录到目标机器,Almquist shell (ash)将使用目标系统的/etc/profile
cat > ${LJOS}/etc/profile << "EOF"
export PATH=/bin:/usr/bin

if [ `id -u` -eq 0 ] ; then
        PATH=/bin:/sbin:/usr/bin:/usr/sbin
        unset HISTFILE
fi


# Set up some environment variables.
export USER=`id -un`
export LOGNAME=$USER
export HOSTNAME=`/bin/hostname`
export HISTSIZE=1000
export HISTFILESIZE=1000
export PAGER='/bin/more '
export EDITOR='/bin/vi'
EOF
  • 1.12设置目标系统的主机名(这个可以任何时候修改)
echo "ljos-test" > ${LJOS}/etc/HOSTNAME
  • 1.13 设置登录时显示的标识
cat > ${LJOS}/etc/issue<< "EOF"
Linux Journal OS 0.1a
Kernel \r on an \m

EOF
  • 1.14设置init进程配置,由于方便和简单的缘故,这里你将不是使用systemd
cat > ${LJOS}/etc/inittab<< "EOF"
::sysinit:/etc/rc.d/startup

tty1::respawn:/sbin/getty 38400 tty1
tty2::respawn:/sbin/getty 38400 tty2
tty3::respawn:/sbin/getty 38400 tty3
tty4::respawn:/sbin/getty 38400 tty4
tty5::respawn:/sbin/getty 38400 tty5
tty6::respawn:/sbin/getty 38400 tty6

::shutdown:/etc/rc.d/shutdown
::ctrlaltdel:/sbin/reboot
EOF
  • 1.15 由于利用BusyBox简化了一些最常见的Linux系统功能,您将使用mdev而不是udev,后者要求您定义以下/etc/mdev
cat > ${LJOS}/etc/mdev.conf<< "EOF"
# Devices:
# Syntax: %s %d:%d %s
# devices user:group mode

# null does already exist; therefore ownership has to
# be changed with command
null root:root 0666 @chmod 666 $MDEV
zero root:root 0666
grsec root:root 0660
full root:root 0666

random root:root 0666
urandom root:root 0444
hwrandom root:root 0660

# console does already exist; therefore ownership has to
# be changed with command
console root:tty 0600 @mkdir -pm 755 fd && cd fd && for x in 0 1 2 3 ; do ln -sf /proc/self/fd/$x $x; done

kmem root:root 0640
mem root:root 0640
port root:root 0640
ptmx root:tty 0666

# ram.*
ram([0-9]*) root:disk 0660 >rd/%1
loop([0-9]+) root:disk 0660 >loop/%1
sd[a-z].* root:disk 0660 */lib/mdev/usbdisk_link
hd[a-z][0-9]* root:disk 0660 */lib/mdev/ide_links

tty root:tty 0666
tty[0-9] root:root 0600
tty[0-9][0-9] root:tty 0660
ttyO[0-9]* root:tty 0660
pty.* root:tty 0660
vcs[0-9]* root:tty 0660
vcsa[0-9]* root:tty 0660

ttyLTM[0-9] root:dialout 0660 @ln -sf $MDEV modem
ttySHSF[0-9] root:dialout 0660 @ln -sf $MDEV modem
slamr root:dialout 0660 @ln -sf $MDEV slamr0
slusb root:dialout 0660 @ln -sf $MDEV slusb0
fuse root:root 0666

# misc stuff
agpgart root:root 0660 >misc/
psaux root:root 0660 >misc/
rtc root:root 0664 >misc/

# input stuff
event[0-9]+ root:root 0640 =input/
ts[0-9] root:root 0600 =input/

# v4l stuff
vbi[0-9] root:video 0660 >v4l/
video[0-9] root:video 0660 >v4l/

# load drivers for usb devices
usbdev[0-9].[0-9] root:root 0660 */lib/mdev/usbdev
usbdev[0-9].[0-9]_.* root:root 0660
EOF
  • 1.16 为目标机的grub bootloarder创建/boot/grub/grub.cfg
cat > ${LJOS}/boot/grub/grub.cfg<< "EOF"

set default=0
set timeout=5

set root=(hd0,1)

menuentry "Linux Journal OS 0.1a" {
        linux /boot/vmlinuz-4.16.3 root=/dev/sda1 ro quiet
}
EOF
  • 1.17初始化日志文件并给它们适当的权限
touch ${LJOS}/var/run/utmp ${LJOS}/var/log/{btmp,lastlog,wtmp}
chmod -v 664 ${LJOS}/var/run/utmp ${LJOS}/var/log/lastlog

交叉编译器相关预处理

  • 如果您还记得的话,交叉编译器是一个由各种编译工具组成的工具链,它是为正在其上执行的系统而构建的,但其设计用于编译的体系结构或微处理器不一定与您所使用的系统兼容。在我的环境中,我运行64位x86体系结构(x86-64),并将交叉编译为通用的x86-64目标体系结构。当然,考虑到我所处的环境,这一节有些多余,但是本教程旨在确保您能够为x86-64目标构建,而不管您使用的是什么机器类型(例如PowerPC、ARM、x86等等)。

  • 取消CFLAGS,CXXFLAGS(当前是使用x86-64目标体系结构,这一步可有可不有)

unset CFLAGS
unset CXXFLAGS
  • 定义创建交叉编译器工具链和目标镜像所需的host/target的最重要部分
export LJOS_HOST=$(echo ${MACHTYPE} | sed "s/-[^-]*/-cross/")
export LJOS_TARGET=x86_64-unknown-linux-gnu
export LJOS_CPU=k8
export LJOS_ARCH=$(echo ${LJOS_TARGET} | sed -e 's/-.*//' -e 's/i.86/i386/')
export LJOS_ENDIAN=little

内核头文件预处理

  • 先解压linux-4.16.3.tar.xz,进入目录
tar xvf linux-4.16.3.tar.xz
cd linux-4.16.3/
  • 执行以下命令
make mrproper
make ARCH=${LJOS_ARCH} headers_check && make ARCH=${LJOS_ARCH} INSTALL_HDR_PATH=dest headers_install
cp -rv dest/include/* ${LJOS}/usr/include

Binutils预处理

  • Binutils包含处理已编译的对象文件所需的链接器、汇编器和其他工具。解压缩Binutils。然后创建binutils-build目录,并进入到其中
tar xvf binutils-2.30.tar.xz
mkdir binutils-build
cd binutils-build/
  • 执行以下命令
../binutils-2.30/configure --prefix=${LJOS}/cross-tools --target=${LJOS_TARGET} --with-sysroot=${LJOS} --disable-nls --enable-shared --disable-multilib
make configure-host && make
ln -sv lib ${LJOS}/cross-tools/lib64
make install
  • 将下面的头文件复制到目标文件系统
cp -v ../binutils-2.30/include/libiberty.h ${LJOS}/usr/include
gcc(静态)
  • 在构建最终的交叉编译器工具链之前,您首先必须构建一个静态编译的工具链,来构建最终GCC交叉编译器来连接的C库(glibc)
  • 解压gcc的压缩包,然后解压下面的包,并放入gcc的根目录中
tar xvf gcc-7.3.0.tar.xz.tar
tar xvf gmp-6.1.2.tar.bz2
mv gmp-6.1.2 gcc-7.3.0/gmp
tar xvf mpfr-4.0.1.tar.xz
mv mpfr-4.0.1 gcc-7.3.0/mpfr
tar xvf mpc-1.1.0.tar.gz
mv mpc-1.1.0 gcc-7.3.0/mpc
  • 创建gcc-static目录,并进入
mkdir gcc-static
cd gcc-static/
  • 运行以下命令

不要删除这些目录;您需要在GCC的最终版本时,回过头来看它们

AR=ar LDFLAGS="-Wl,-rpath,${LJOS}/cross-tools/lib" \
../gcc-7.3.0/configure --prefix=${LJOS}/cross-tools \
--build=${LJOS_HOST} --host=${LJOS_HOST} \
--target=${LJOS_TARGET} \
--with-sysroot=${LJOS}/target --disable-nls \
--disable-shared \
--with-mpfr-include=$(pwd)/../gcc-7.3.0/mpfr/src \
--with-mpfr-lib=$(pwd)/mpfr/src/.libs \
--without-headers --with-newlib --disable-decimal-float \
--disable-libgomp --disable-libmudflap --disable-libssp \
--disable-threads --enable-languages=c,c++ \
--disable-multilib --with-arch=${LJOS_CPU}

make all-gcc all-target-libgcc && make install-gcc install-target-libgcc
ln -vs libgcc.a `${LJOS_TARGET}-gcc -print-libgcc-file-name | sed 's/libgcc/&_eh/'`
glibc
  • 解压glibc的压缩包,创建目录,并进入
tar xvf glibc-2.27.tar.xz.tar
mkdir glibc-build
cd glibc-build/
  • 配置编译标志
echo "libc_cv_forced_unwind=yes" > config.cache
echo "libc_cv_c_cleanup=yes" >> config.cache
echo "libc_cv_ssp=no" >> config.cache
echo "libc_cv_ssp_strong=no" >> config.cache
  • 运行以下命令
BUILD_CC="gcc" CC="${LJOS_TARGET}-gcc" \
AR="${LJOS_TARGET}-ar" \
RANLIB="${LJOS_TARGET}-ranlib" CFLAGS="-O2" \
../glibc-2.27/configure --prefix=/usr \
--host=${LJOS_TARGET} --build=${LJOS_HOST} \
--disable-profile --enable-add-ons --with-tls \
--enable-kernel=2.6.32 --with-__thread \
--with-binutils=${LJOS}/cross-tools/bin \
--with-headers=${LJOS}/usr/include \
--cache-file=config.cache

make && make install_root=${LJOS}/ install
GCC (最终)
  • 如前所述,现在您将构建最终的GCC交叉编译器,该编译器将链接到上一步中构建和安装的C库。创建目录,并进入
mkdir gcc-build
cd gcc-build/
  • 运行以下命令
AR=ar LDFLAGS="-Wl,-rpath,${LJOS}/cross-tools/lib" \
../gcc-7.3.0/configure --prefix=${LJOS}/cross-tools \
--build=${LJOS_HOST} --target=${LJOS_TARGET} \
--host=${LJOS_HOST} --with-sysroot=${LJOS} \
--disable-nls --enable-shared \
--enable-languages=c,c++ --enable-c99 \
--enable-long-long \
--with-mpfr-include=$(pwd)/../gcc-7.3.0/mpfr/src \
--with-mpfr-lib=$(pwd)/mpfr/src/.libs \
--disable-multilib --with-arch=${LJOS_CPU}

make && make install
cp -v ${LJOS}/cross-tools/${LJOS_TARGET}/lib64/libgcc_s.so.1 ${LJOS}/lib64
  • 现在你已经构建了交叉编译器,您需要调整和导出以下变量
export CC="${LJOS_TARGET}-gcc"
export CXX="${LJOS_TARGET}-g++"
export CPP="${LJOS_TARGET}-gcc -E"
export AR="${LJOS_TARGET}-ar"
export AS="${LJOS_TARGET}-as"
export LD="${LJOS_TARGET}-ld"
export RANLIB="${LJOS_TARGET}-ranlib"
export READELF="${LJOS_TARGET}-readelf"
export STRIP="${LJOS_TARGET}-strip"

编译目标镜像

  • 困难的部分现在完成了——您有了交叉编译器。现在,让我们重点构建将安装在目标映像上的组件。这包括各种库和实用程序,当然还有Linux内核本身。
BusyBox
  • BusyBox是我最喜欢的开源项目之一。该项目宣传自己是开源工具的瑞士军刀,这可能是对该项目最好的描述。BusyBox将大量最常用的Linux实用程序的小版本组合到一个分布式包中。这些工具包括常见的二进制文件、文本编辑器和命令行shell、文件系统和网络实用程序、流程管理工具等等。
  • 解压busybox的压缩包,进入其目录,载入默认配置模板
make CROSS_COMPILE="${LJOS_TARGET}-" defconfig
  • 默认配置模板将支持编译一组默认定义的实用程序和库。你可以通过运行menuconfig来启用/禁用任何你认为合适的组件
make CROSS_COMPILE="${LJOS_TARGET}-" menuconfig
  • 编译安装软件包
make CROSS_COMPILE="${LJOS_TARGET}-"
make CROSS_COMPILE="${LJOS_TARGET}-" CONFIG_PREFIX="${LJOS}" install
  • 安装perl脚本,编译内核的时候需要他
cp -v examples/depmod.pl ${LJOS}/cross-tools/bin
chmod 755 ${LJOS}/cross-tools/bin/depmod.pl
Linux内核
  • 进入内核的目录,载入默认配置模板
make ARCH=${LJOS_ARCH} CROSS_COMPILE=${LJOS_TARGET}- x86_64_defconfig
  • 这将为编译过程定义最小模块集和设置集。你很可能需要针对目标机器的环境进行适当的调整。这包括为存储和网络控制器等启用模块。你可以用menuconfig选项做到这一点

例如,我将在一个VirtualBox虚拟机中运行这个目标映像,它将依赖于一个Intel e1000网络模块(在defconfig中缺省设置)和一个用于操作系统驱动器的LSI mpt2sas存储控制器。为了简单起见,将这些模块配置为静态编译到内核映像中—也就是说,将其设置为*而不是m。一定要检查你的目标机需要什么并启用它,否则目标环境在引导时将无法正常运行。

make ARCH=${LJOS_ARCH} CROSS_COMPILE=${LJOS_TARGET}- menuconfig
  • 编译安装内核
    • 这里可能需要安装一下elfutils-libelf-devel(Centos7):yum install -y elfutils-libelf-devel
make ARCH=${LJOS_ARCH} CROSS_COMPILE=${LJOS_TARGET}-
make ARCH=${LJOS_ARCH} CROSS_COMPILE=${LJOS_TARGET}- INSTALL_MOD_PATH=${LJOS} modules_install
  • 你需要为grub拷贝一些文件到/boot目录
cp -v arch/x86/boot/bzImage ${LJOS}/boot/vmlinuz-4.16.3
cp -v System.map ${LJOS}/boot/System.map-4.16.3
cp -v .config ${LJOS}/boot/config-4.16.3
  • 然后运行BusyBox包提供的先前安装的Perl脚本
${LJOS}/cross-tools/bin/depmod.pl -F ${LJOS}/boot/System.map-4.16.3 -b ${LJOS}/lib/modules/4.16.3
Bootscripts
  • Cross Linux From Scratch (CLFS)项目(原始LFS项目的一个分支)提供了一组非常棒的引导脚本,为了简单起见,我在这里使用这些脚本。
  • 解压缩包并更改到它的目录中。这个目录的makefile之一包含可能与当前工作shell不兼容的代码。将以下更改应用于目录的根Makefile,以确保在包安装过程中不会遇到任何问题(这个步骤不是必须)
@@ -19,7 +19,9 @@ dist:
        rm -rf "dist/clfs-embedded-bootscripts-$(VERSION)"

 create-dirs:
- install -d -m ${DIRMODE} ${EXTDIR}/rc.d/{init.d,start,stop}
+ install -d -m ${DIRMODE} ${EXTDIR}/rc.d/init.d
+ install -d -m ${DIRMODE} ${EXTDIR}/rc.d/start
+ install -d -m ${DIRMODE} ${EXTDIR}/rc.d/stop

 install-bootscripts: create-dirs
        install -m ${CONFMODE} clfs/rc.d/init.d/functions ${EXTDIR}/rc.d/init.d/
  • 运行以下的命令,来安装配置目标运行环境
make DESTDIR=${LJOS}/ install-bootscripts
ln -sv ../rc.d/startup ${LJOS}/etc/init.d/rcS
Zlib(安装你所需要的别的第三方软件包)
  • 本教程的最后一个包。Zlib不是必需的,但是对于你可能希望为你的环境安装其他包来说,它是一个很好的指南。如果你想格式化和配置物理或虚拟硬盘,可以跳过这一步。
sed -i 's/-O3/-Os/g' configure
./configure --prefix=/usr --shared
make && make DESTDIR=${LJOS}/ install
  • 因为有些包可能会在/lib目录中而不是/lib64目录中查找Zlib库,所以应用以下更改
mv -v ${LJOS}/usr/lib/libz.so.* ${LJOS}/lib
ln -svf ../../lib/libz.so.1 ${LJOS}/usr/lib/libz.so
ln -svf ../../lib/libz.so.1 ${LJOS}/usr/lib/libz.so.1
ln -svf ../lib/libz.so.1 ${LJOS}/lib64/libz.so.1

安装目标镜像

  • 所有的交叉编译都完成了。现在您已经拥有了将整个交叉编译的操作系统安装到物理驱动器或虚拟驱动器所需的一切,但是在此之前,不要通过复制原始目标构建目录来篡改它,做一份拷贝
cp -rf ${LJOS}/ ${LJOS}-copy
  • 在本教程的其余部分中使用此副本。删除一些现在不需要的目录
rm -rfv ${LJOS}-copy/cross-tools
rm -rfv ${LJOS}-copy/usr/src/*
  • 然后是删除现在不需要的静态编译库文件(如果有的话)
FILES="$(ls ${LJOS}-copy/usr/lib64/*.a)"
for file in $FILES; do
rm -f $file
done
  • 现在从安装的二进制文件中删除所有调试符号。这将减少总体文件大小,并将目标映像的总体占用空间保持在最小(使用root权限)
sudo chown -R root:root ${LJOS}-copy
sudo chgrp 13 ${LJOS}-copy/var/run/utmp ${LJOS}-copy/var/log/lastlog
sudo mknod -m 0666 ${LJOS}-copy/dev/null c 1 3
sudo mknod -m 0600 ${LJOS}-copy/dev/console c 5 1
sudo chmod 4755 ${LJOS}-copy/bin/busybox
  • 进入拷贝的目录,创建整个操作系统镜像的压缩包
cd ${LJOS}-copy/
sudo tar cfJ ../ljos-build-manjingliu.tar.xz *
  • 查看整个根文件系统的大小,该操作系统可以装在一个少于60MB的磁盘上
sudo du -h|tail -n1
58M  
  • 压缩过后的大小少于20MB
ls -lh ljos-build-manjingliu.tar.xz
-rw-r--r-- 1 root root 18M Apr 21 15:31 ljos-build-manjingliu.tar.xz
  • 查看目标硬盘的大小以及分区、格式化
# 先使用fdisk进行分区
sudo mkfs.ext4 /dev/sdb1
sudo mkdir tmp
sudo mount /dev/sdb1 tmp/
cd tmp/
  • 将操作系统的压缩包解压到该分区中
sudo tar xJf ../ljos-build-manjingliu.tar.xz
  • 运行grub-install或者grub2-install命令安装bootloader
# --root-directory参数定义了staging目录的绝对路径,而最后一个参数是没有分区标签的块设备。
sudo grub-install --root-directory=/home/manjingliu/ljos/tmp/ /dev/sdb

启动制作好的系统

  • 将制作好的系统所在的硬盘作为第一启动项启动
    "多快好省"——交叉编译构建自己的linux操作系统

  • 启动成功
    "多快好省"——交叉编译构建自己的linux操作系统

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2021-09-27
  • 2022-01-12
  • 2022-01-31
  • 2022-12-23
  • 2021-04-16
  • 2021-10-03
猜你喜欢
  • 2021-07-26
  • 2021-12-18
  • 2021-09-04
  • 2021-03-31
  • 2021-07-08
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案