Skip to content

Latest commit

 

History

History
449 lines (375 loc) · 13.9 KB

doc_cn.md

File metadata and controls

449 lines (375 loc) · 13.9 KB

实验报告

课程目的


  • 熟练掌握 Linux 操作系统的使用
  • 了解 Linux 操作系统的运作过程,理解内核与外围支撑系统的关系
  • 通过实验定制 Linux 系统内核与外围支撑系统,加深对于开源操作系统的认识
  • 实现具有特色功能的自启动小系统

实验目标


  • 实现最新版本 Linux kernel 及其配套 RAMDisk 文件系统的定制
  • 要求:kernel < 4 MB,initrd.img < 24 MB
  • 功能:
    • 通过 U 盘加载 kernel 和 img 启动
    • 支持多用户登录(console 界面和 ssh 网络方式)
    • 系统支持通过 ssh 方式访问其他机器
    • 可挂载 U 盘
    • 可访问机器上的 windows 分区(ntfs-3gfs 支持)

实验基于 Ubuntu 21.04、Linux Kernel 5.14.0、VMWare Workstation Pro 进行

实验步骤


1. 定制 initrd

initrd 提供了以下功能:

  1. 提供一个临时的根文件系统
  2. 加载必要驱动(以访问真实的根文件系统)
  3. 挂载根文件系统
  4. 根切换

1.0 GRUB Config

  • 编辑 /etc/default/grub,关闭默认的 hidden 行为。
  • 编辑 /boot/grub/grub.cfg,为定制的 initrd 添加一个新的 Menu Entry。

1.5 获取 Shell

通过 tools/add_cmd.sh 将命令及其依赖添加到当前目录下

# Add commands with the dependencies required
# to the current directory as root.

addto_wd() {
  WORK_DIR=$(pwd)
  root=${1%/*}
  file=${1##*/}

  if [[ ! -d $WORK_DIR$1 ]]; then
    if [[ ! -d $WORK_DIR$root ]]; then
      mkdir -p $WORK_DIR$root
    fi
    cp $1 $WORK_DIR$1
  fi
}

for arg in $@; do
  if [[ $arg =~ "/" ]]; then  # is path
    path=$arg
  else  # is command
    path=$(which $arg)
  fi
  echo $path
  addto_wd $path
  # ldd $path

  for line in $(ldd $path); do
    if [[ ${line:0:1} == '/' ]]; then
      echo $line
      addto_wd $line
    fi
  done
done

将 bash 命令及其依赖添加到定制 initrd 中,创建 init 文件如下:

#!/bin/bash
export PATH=/usr/sbin:/usr/bin:/bin
bash

init 文件需要给予执行权限,否则报错:no working init,进而 kernel panic。

$ chmod 755 init

重新打包 initramfs

$ find . -path ./tools -prune -o -print | cpio -o -H newc | gzip > /boot/initrd.img-5.11.0-25-modified

1.5.5 挂载原始系统

通过 tools/add_mod.sh 将模块及其依赖添加到当前目录下

# Add kernel modules with dependencies
# to the current directory as root.

addto_wd() {
  WORK_DIR=$(pwd)
  root=${1%/*}
  file=${1##*/}

  if [[ ! -d $WORK_DIR$1 ]]; then
    if [[ ! -d $WORK_DIR$root ]]; then
      mkdir -p $WORK_DIR$root
    fi
    cp $1 $WORK_DIR$1
  fi
}

addmod() {
  fileline=$(modinfo $1 | grep filename)
  path=${fileline##* }
  if [[ ! -e $(pwd)$path ]]; then
    echo $1": "$path
    addto_wd $path
  fi
}

addmod_withdeps() {
  depsline=$(modinfo $1 | grep depends)
  depends=${depsline##* }
  for dep in ${depends//,/ }; do
    addmod_withdeps $dep
  done

  addmod $1
}

for mod in $@; do
  addmod_withdeps $mod
done

通过 tools/lsmodinfo.sh 以更便捷的方式显示模块的必要信息

# List infomation about all kernel modules
# with its filename, description and depends.

lsmod | while read line; do
  # echo $line
  if [[ "${line:0:6}" != "Module" ]]; then
    echo $line
    module=${line%% *}
    # echo $module
    echo "  "$(modinfo $module | grep filename)
    echo "  "$(modinfo $module | grep description)
    echo
  fi
done

由于 VMware 给虚拟机提供了虚拟的 scsi 硬盘,为使能挂载该硬盘,只需添加 mptspi 模块及其依赖。添加 insmodmkdirmount 等命令,并编辑 init 如下:

#!/bin/bash
export PATH=/usr/sbin:/usr/bin:/bin

insmod /lib/modules/5.11.0-25-generic/kernel/drivers/message/fusion/mptbase.ko
insmod /lib/modules/5.11.0-25-generic/kernel/drivers/message/fusion/mptscsih.ko
insmod /lib/modules/5.11.0-25-generic/kernel/drivers/scsi/scsi_transport_spi.ko
insmod /lib/modules/5.11.0-25-generic/kernel/drivers/message/fusion/mptspi.ko

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc

mknod /dev/sda3 b 8 3  # Look for major and minor numbers by `ls -l /dev/sdax`
mkfs -t ext4 /dev/sda3
mount /dev/sda3 /root

bash

1.6 udev 设备管理

依赖 sysfs 文件系统,挂载 /sys ;规则位于 /lib/udev;配置位于 /etc/udev;并在 /dev 目录下创建设备节点。

  1. 从解压的原始 initrd 中拷贝 rule.d/lib/modules/xxx-xx-generic/modules.* 到小系统 initrd 中,后者包含了 udevadm 自动添加时所必要的规则等,否则无法自动管理设备。

  2. 通过追踪原始 initrd 的执行,发现其调用了 /scripts 下的脚本以调用 udevadm,最终定位到 scripts/init-top/udev 中的以下几行命令

udevadm trigger --type=subsystems --action=add
udevadm trigger --type=devices --action=add
udevadm settle || true

其中 udevadm settle 等待所有 udev 事件执行完毕,否则由于之后的 mount 命令异步执行,还未完成所有模块的加载便试图挂载硬盘,因此此处需要进行阻塞。

  1. 此外,原始 initrd 中 /lib/systemd/bin/udevadm 的软链接,确保链接的路径正确:systemd-udevd -> ../../bin/udevadm,而非 bin/udevadm

  2. 编辑 init 文件如下:

#!/bin/bash
export PATH=/usr/sbin:/usr/bin:/sbin:/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /run ] || mkdir /run  # for udevd to create /run/udev
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc

# Mount udev
mount -t devtmpfs -o $dev_exec,nosuid,mode=0755 udev /dev

log_level=info
SYSTEMD_LOG_LEVEL=$log_level /lib/systemd/systemd-udevd --daemon --resolve-names=never
udevadm trigger --type=subsystems --action=add
udevadm trigger --type=devices --action=add
udevadm settle || true

mount /dev/sda3 /root

bash

1.7 login 提示

  • 认证体系 PAM 配置位于 /etc/pam.d 目录下,依赖 /lib/security
  • 可以 chroot 到小系统 initrd 根目录下,通过 strace login 查看缺少的文件。
    1. 添加 /etc/shadow/etc/passwd/etc/pam.d/lib/x86_64-linux-gnu/security
    2. 报错 Login incorrect:添加 /etc/nsswitch.conf/lib/x86_64-linux-gnu/libnss_*
    3. 报错 Error in service module/etc/login.defs/etc/security

1.9 /sbin/init (systemd) 系统服务管理

systemd 必须以 PID 1 启动,因此应在 init 文件末尾执行 exec /sbin/init 将当前执行 init 的进程切换到执行 systemd,否则报错 kernel panic。没有查找到任何关于 systemd 的功能机制的文档或说明,因此所有需要添加的命令与文件全靠不断试错,此处应该可以查看 systemd 源码分析。

通过反复尝试,仅需要添加 agetty 一个命令即可,并且需要拷贝 /lib/systemd 到小系统目录下。在此基础上可以通过 man systemd.special 查看 systemd 各单元的描述对 /lib/systemd 进行进一步的裁剪,建议一开始装 Linux 时就应安装无图形化界面版本,此处就可以省去裁剪 gnomegdmgvfs 等相关服务配置文件的操作。

1.9.5 网络连接

添加以下网络配置相关命令 ifconfigpingipsshsshd 等,以及 VMWare 抽象的 Intel e1000 网卡的相应驱动。

$ bash tools/addcmd.sh ifconfig ping dhclient ip ssh sshd
$ bash tools/addmod.sh e1000
$ cp /etc/ssh etc -r
$ sudo cp /etc/ssh/*_key etc/ssh/
网络配置

查看网络接口信息

$ ip link show

开启网络适配器

$ ip link set eth0 up

网络适配器相应固件 /lib/firmware/xxx 可能也需要添加到 initrd 中,否则可能会有相应报错。 配置网络适配器地址

$ ip addr add 192.168.0.99/24 dev eth0

配置默认路由

$ ip route add default via 192.168.0.1 dev eth0
开启 sshd

编辑 /etc/sshd_configPermitRootLogin yes. 若要通过 ssh 连接到当前系统,需要手动执行绝对路径启动 sshd 以监听客户端的连接。此处应该可以通过添加到 systemd 的 target 中自动启动。

$ /sbin/init
终端配置

查看 terminfo 描述

$ infocmp
# Reconstructed via infocmp from file: /lib/terminfo/x/xterm-256color
xterm-256color | xterm with 256 colors

拷贝相应的 terminfo 文件,并且需要将 $TERM 变量设置为相应名称。为了使新建终端会话中自动设定以上变量,编辑 /etc/profile 如下:

export PATH=/usr/sbin:/sbin:$PATH
export TERM=xterm-256color

# generated by ASCII Generator
echo -e \
"     \033[31m__  ___ \033[0m\033[32m_\033[0m         \033[33m_\033[0m  ____  _____\n" \
"   \033[31m/  |/  /\033[0m\033[32m(_)\033[0m\033[34m____\033[0m   \033[33m(_)\033[0m/ __ \/ ___/\n" \
"  \033[31m/ /|_/ /\033[0m\033[32m/ /\033[0m\033[34m/ __ \ \033[0m\033[33m/ /\033[0m/ / / /\__ \ \n" \
" \033[31m/ /  / /\033[0m\033[32m/ /\033[0m\033[34m/ / / /\033[0m\033[33m/ /\033[0m/ /_/ /___/ / \n" \
"\033[31m/_/  /_/\033[0m\033[32m/_/\033[0m\033[34m/_/ /_/\033[0m\033[33m/_/\033[0m \____//____/  \n"

附:脚本使用说明

添加命令及其依赖到当前目录作为根的文件系统中

$ bash tools/addcmd.sh COMMAND...

添加模块及其依赖到当前目录作为根的文件系统中

$ bash tools/addmod.sh MODULE...

从当前目录中重新打包 initrd 镜像

$ bash tools/repack_initrd.sh

相比于 lsmodmodinfo,以更便捷的方式显示当前 Linux Kernel 中所有模块的必要信息

$ bash tools/lsmodinfo.sh

2. 裁剪 Linux 内核

菜单选项

  • General setup
    • Kernel compression mode (XZ)
    • Control Group support
      • Support for eBPF programs attached to cgroups
    • Initial RAM filesystem and RAM disk (initramfs/initrd) support:选中 initrd 对应格式
  • Power management and ACPI options
    • ACPI (Advanced Configuration and Power Interface) Support:若不选中 poweroff 或者 reboot 后机器无法断电
  • General architecture-dependent options
    • Enable seccomp to safely execute untrusted bytecode
  • Executable file formats
    • Kernel support for ELF binaries
    • Write ELF core dumps with partial segments
    • Kernel support for scripts starting with #!
  • Networking support
    • Networking options
      • TCP/IP networking
  • Device Drivers
    • Serial ATA and Parallel ATA drivers (libata)
    • Network device support
      • Ethernet driver support:选中设备网络适配器对应品牌
    • Input device support
      • Keyboards
        • AT keyboard:指 PS/2 接口键盘以及笔记本自带的键盘,不包括 USB 键盘
    • HID support
      • Generic HID driver:包括 USB 键盘
    • USB support
      • xHCI HCD (USB 3.0) support
      • EHCI HCD (USB 2.0) support
      • OHCI HCD (USB 1.1) support
      • USB Mass Storage support
  • File systems
    • The Extended 4 (ext4) filesystem
    • Inotify support for userspace systemd 必需
    • Native language support:支持挂载硬盘的不同格式所需
      • Codepage 437 (United States, Canada)
      • Simplified Chinese charset (CP936, GB2312)
      • ASCII (United States)
      • NLS ISO 8859-1 (Latin 1; Western European Languages)
      • NLS UTF-8
  • Security options:全关
  • Cryptographic API:基本只需保留 systemd requirements 中的项
  • Library routines:能关的全关
  • Kernel hacking:全关

3. U 盘安装 GRUB

安装所需 package

$ sudo su
# apt update && apt upgrade
# apt install git bison libopts25 libselinux1-dev m4 help2man libopts25-dev flex libfont-freetype-perl automake make autotools-dev autopoint libfreetype6-dev texinfo python autogen autoconf libtool libfuse3-3 unifont gettext binutils pkg-config liblzma5 libdevmapper-dev

Bootstrap GRUB

# git clone git://git.savannah.gnu.org/grub.git
# cd grub
# ./bootstrap

编译 GRUB (64 bit UEFI)

# mkdir efi64
# cd efi64
# ../configure --target=x86_64 --with-platform=efi && make

挂载 U 盘

# fdisk -l
# mkdir /mnt/usb
# mount /dev/sdb1 /mnt/usb

安装 GRUB

# cd ../efi64/grub-core
# grub-install -d $PWD --force --removable --no-floppy --target=x86_64-efi --boot-directory=/mnt/usb/boot --efi-directory=/mnt/usb

创建 boot/grub/grub.cfg 如下:

function recordfail {
  set recordfail=1
  if [ -n "${have_grubenv}" ]; then if [ -z "${boot_once}" ]; then save_env recordfail; fi; fi
}

function load_video {
  if [ x$feature_all_video_module = xy ]; then
    insmod all_video
  else
    insmod efi_gop
    insmod efi_uga
    insmod ieee1275_fb
    insmod vbe
    insmod vga
    insmod video_bochs
    insmod video_cirrus
  fi
}

function gfxmode {
	set gfxpayload="${1}"
	if [ "${1}" = "keep" ]; then
		set vt_handoff=vt.handoff=7
	else
		set vt_handoff=
	fi
}

menuentry 'Minimal OS' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-5949ae40-f727-42f6-a4a6-751c22b8b54d' {
        recordfail
        load_video
        gfxmode $linux_gfx_mode
        insmod gzio
        insmod part_gpt
        insmod ext2
        echo 'loading minimal OS ...'
        linux   /boot/vmlinuz-5.14.0-minimal ro text
        echo 'loading initrd ...'
        initrd  /boot/initrd.img-minimal
}

拷贝定制的小系统 initrd 镜像以及编译的 Linux 内核到 grub.cfg 中指定的位置,即可成功从 U 盘中启动定制小系统。🎉