版权声明:本文为本文为博主原创文章,转载请注明出处,博客地址:https://www.cnblogs.com/wsg1100/。若有错误,欢迎指正。
前端
ubuntu-base 是Ubuntu官方构建的ubuntu最小文件系统,包含debain软件包管理器,基础包大小一般只有几十兆,其背后有整个ubuntu软件源支持,ubuntu软件通常稳定性比较好,基于ubuntu-base按需安装Linux软件,深度可定制......,经常使用于嵌入式rootfs构建。node
嵌入式常见的几种文件系统构建方法:busybox、yocto、builroot,我以为它们都不如Ubuntu方便,强大的包管系统,有强大的社区支持,能够直接apt-get install来安装新软件包。本文介绍了如何基于Ubuntu-base构建完整的ubuntu 系统。对于安装过archlinux或者构建过LFS的朋友,对该方式再熟悉不过了。linux
ubuntu支持不少架构,arm、X8六、powerpc、ppc等,本文主要基于X86_64为例,arm、arm64也给出了简单的制做步骤,其余架构操做相似。sql
从ubuntu最小文件系统ubuntu-base开始,构建一个具备X-windows的完整系统。shell
ubuntu-base-16.04.6-base-amd64.tar.gz
/dev/sda1 #UEFI分区 500MB /dev/sda2 #宿主系统根分区 20GB /dev/sda3 #用来构建ubuntu-base 5GB
环回(loopback)文件系统是Linux类系统中很是有趣的部分。咱们一般是在设备上(例如磁盘分区)建立文件系统。这些存储设备可以以设备文件的形式来使用,好比 /dev/device_name。为了使用存储设备上的文件系统,咱们须要将其挂载到一些被称为挂载点(mount point)的目录上。环回文件系统是指那些在文件中而非物理设备中建立的文件系统。咱们能够将这些文件做为文件系统挂载到挂载点上。这实际上可让咱们在物理磁盘上的文件中建立逻辑磁盘。ubuntu
咱们的目的:建立一个回环文件模拟一个大小10GB的物理磁盘,并在该回环镜像中分两个区:vim
也就是建立一个大小10G的文件。windows
$dd if=/dev/zero of=ubuntu_base.img bs=1G count=10 记录了10+0 的读入 记录了10+0 的写出 10737418240 bytes (11 GB, 10 GiB) copied, 84.1916 s, 128 MB/s
你会发现建立好的文件大小超过了1GB。这是由于硬盘做为块设备,其分配存储空间时是按照块大小的整数倍来进行的。此时能够直接对这个文件做为一整个分区格式化并使用,操做以下。bash
用mkfs
命令将1GB的文件格式化成ext4文件系统:服务器
$mkfs.ext4 ubuntu_base.img
可以使用下面的命令就可看到已是文件系统了:
$ file ubuntu_base.img loobackfile.img: Linux rev 1.0 ext4 filesystem data, UUID=3be1775c-8976-445d-9134-8daabb2bade7 (extents) (64bit) (large files) (huge files)
如今就能够挂载环回文件了:
$sudo mkdir /mnt/loopback $sudo mount -o loop ubuntu_base.img /mnt/loopback
-o loop
用来挂载环回文件系统。
这其实是一种快捷的挂载方法,咱们无需手动链接任何设备。可是在内部,这个环回文件链接到了一个名为/dev/loop1
或loop2
的设备上。
咱们也能够手动来操做:
$sudo losetup /dev/loop1 ubuntu_base.img $sudo mount /dev/loop1 /mnt/loopback
使用下面的方法进行卸载(umount):
$sudo umount /mnt/loopback
也能够用设备文件的路径做为umount命令的参数:
$sudo umount /dev/loop1
以上是将整个文件做为一个分区使用的方法,但咱们的目的是在该文件内分多个区,继续。
创建分区可使用fdisk
或parted
工具,本人比较喜欢用parted
,对于设置启动分区很是方便。
$ sudo parted ubuntu_base.img GNU Parted 3.2 使用 /home/work/loobackfile.img 欢迎使用 GNU Parted! 输入 'help'可得到命令列表. (parted)
新建UEFI分区:
对于UEFI启动方式,首先要设置建立label为msdos;
(parted) mklabel msdos
建立分区大小500M:
(parted)mkpart primary fat32 0 500MB
查看新建立的分区,该分区编号为1:
(parted) p Model: (file) 磁盘 /home/work/ubuntu_base.img: 10.7GB Sector size (logical/physical): 512B/512B 分区表:msdos Disk Flags: 数字 开始: End 大小 类型 文件系统 标志 1 512B 500MB 500MB primary fat32 lba (parted)
将该分区设置为boot(UEFI启动)分区:
(parted) set 1 boot on (parted) p Model: (file) 磁盘 /home/work/ubuntu_base.img: 10.7GB Sector size (logical/physical): 512B/512B 分区表:msdos Disk Flags: 数字 开始: End 大小 类型 文件系统 标志 1 512B 500MB 500MB primary fat32 启动, lba (parted)
设置后经过p
命令能够看到,标志处多了启动(boot)
标志。
将剩余空间建立根分区:
(parted) mkpart primary ext4 500MB 100%
到此两个分区建立完毕,使用p
查看,q
保存退出:
(parted) p Model: (file) 磁盘 /home/work/ubuntu_base.img: 10.7GB Sector size (logical/physical): 512B/512B 分区表:msdos Disk Flags: 数字 开始: End 大小 类型 文件系统 标志 1 512B 500MB 500MB primary fat32 启动, lba 2 500MB 10.7GB 10.2GB primary ext4 lba
镜像文件已经被分为两个区,可是尚未格式化,要对内部的两个分区进行格式化就须要先将两分区挂载到设备。
有一个更快的方法能够挂载镜像中的全部分区——kpartx
。它并无安装默认在系统中,你得使用软件包管理器进行安装:
$sudo apt-get install kpartx
执行如下命令自动挂载镜像文件中的分区:
$sudo kpartx -v -a ubuntu_base.img add map loop0p1 (253:0): 0 976562 linear 7:0 1 add map loop0p2 (253:1): 0 19994624 linear 7:0 976896
这条命令在磁盘镜像中的分区与/dev/mapper
中的设备之间创建了映射,随后即可以格式化/挂载这些设备。
格式化两个分区:
$ sudo mkfs.fat -F 32 /dev/mapper/loop0p1 #loop0p1 回环设备的分区1 $ sudo mkfs.ext4 /dev/mapper/loop0p2 #loop0p1 回环设备的分区2
格式换完成后就能够挂载两个分区:
$ sudo mkdir /mnt/loopback $ sudo mount /dev/mapper/loop0p2 /mnt/loopback #挂载根分区 $ sudo mkdir -p /mnt/loopback/boot/efi $ sudo mount /dev/mapper/loop0p1 /mnt/loopback/boot/efi #挂载uefi启动分区
将目标分区格式化后挂载到/mnt
目录,若是挂载的是其余目录后面的全部命令均需更改:
sudo mkfs.ext4 /dev/sda3 sudo mount /dev/sda3 /mnt
将ubuntu-base-16.04.6-base-amd64.tar.gz
解压到/mnt
目录:
sudo tar -xpvf ubuntu-base-16.04.6-base-amd64.tar.gz -C /mnt
注意:须要保留ubuntu-base中的文件权限及全部者,解压时须要root权限或者sudo操做,且使用-p
参数保留权限。
ubuntu-base中有默认ubuntu官方源,若是链接上互联网且访问官方源速度不受限制的话不须要更换。配置文件/etc/apt/sources.list
更换源,本文宿主系统与要构建的系统硬件架构及版本一致,因此直接拷贝便可。
sudo cp /etc/apt/sources.list /mnt/etc/apt/
进入目标环境须要联网,须要先配置DNS,拷贝宿主系统文件/etc/resolv.conf
到/mnt/etc/
目录下:
sudo cp /etc/resolv.conf /mnt/etc/
ubuntu-base默认只有root用户,若是须要像普通ubuntu那样可随意建立普通用户,须要向Ubuntu-bae里添加用户默认配置文件夹/etc/skel
,该文件夹内包含用户建立时的默认配置文件如.bashrc、.profile
等,若没有该文件夹,在构建出的文件系统中执行adduser
添加的用户会有各类问题,因此将宿主系统/etc/skel
拷贝至ubuntu-base:
sudo cp -R /etc/skel /mnt/etc/
方法1:使用原始的方法,来进入chroot环境
挂载和激活 /dev:一般激活 /dev 目录下设备的方式是在 /dev 目录挂载一个虚拟文件系统(好比 tmpfs),而后容许在检测 到设备或打开设备时在这个虚拟文件系统里动态建立设备节点。这个一般是在启动过程当中由 udev 完成。因为咱们的ubuntu-base新系统尚未 udev,也没有被引导,有必要手动挂载和激活 /dev 这能够经过绑定挂载宿主机系统的/dev 目录来实现。绑定挂载是一种特殊的挂载模式,它容许在另外的位置建立某个目录或挂载点的镜像。运行下面的命令来实现:
sudo mount -v --bind /dev /mnt/dev
挂载虚拟文件系统:
sudo mount -vt devpts devpts /mnt/dev/pts -o gid=5,mode=620 sudo mount -vt proc proc /mnt/proc sudo mount -vt sysfs sysfs /mnt/sys sudo mount -vt tmpfs tmpfs /mnt/run
进入chroot环境:
chroot /mnt
方法2:使用arch-chroot
linux发行版archlinux提供了一个自动化chroot的脚本arch-chroot
,包含自动配置DNS文件、自动挂载虚拟文件系统等操做,用来维护linux系统很是方便,chroot时无需挂载等操做直接执行:
sudo arch-chroot /mnt
arch-chroot
是方法1的封装,除此以外有会对目标系统进行检测并预先配置,其源码见附录。
chroot 后为root用户,直接执行操做:
更新软件包列表并升级:
apt-get update apt-get upgrade apt-get locales
apt-get install linux-headers-4.4.0-164-generic linux-image-4.4.0-164-generic linux-modules-4.4.0-164-generic
echo "LANG=en_US.UTF-8" > /etc/locale.conf
自动补全工具bash-completion
.
apt-get install bash-completion
apt-get install grub-efi-amd64-bin
apt-get x-window-system-core
apt-get qt5-default libsqlite3-dev
到此该系统可运行基本的图形程序。
按需ubuntu各类桌面环境,任选其一,也可同时安装多个,经过systemd选择开机启动的登陆管理器来登陆对应的桌面。
GDM-GNOME登陆管理器;
SDDM - 基于QML的显示管理器和KDM的后继者; 推荐用于 Plasma和 LXQt;
XDM - X显示管理器,支持XDMCP;
LightDM - 跨桌面显示管理器,可使用任何工具包中编写的各类前端,Ubuntu16.04默认使用该管理器。
sudo apt-get install lubuntu-desktop
其登陆管理器为gdm,须要先安装xdn。
sudo apt-get install gdm sudo -y --no-install-recommends ubuntu-gnome-desktop
xfce 是一款轻量级桌面.其登陆管理器为xdm,须要先安装xdn。
apt-get install -y xdm
安装桌面环境
apt-get install -y --no-install-recommends xubuntu-desktop
开机启动桌面管理器。
systemctl enable xdm
sudo apt-get install ubuntu-mate-core sudo apt-get install ubuntu-mate-desktop
.......
arm下构建流程与上面相似,能够不使用换回镜像,直接使用一个空文件夹rootfs
,在该文件夹内进行Ubuntu rootfs构建,构建完成后再将该文件夹下的全部文件拷贝到SD卡已格式格式化后的rootfs分区内便可,也可直接挂载SD卡内的rooyfs分区操做。
目前通常arm的板子都支持从SD卡启动,同时SD卡内有两个分区,一个Fat32的启动分区内存放u-boot,另外一个ext4分区为根文件系统。对于nand flash启动的板子,将roofs按文件系统类型制做烧写镜像,烧写nandflash便可。
因为arm板子的开放性,板子外设、接口等硬件资源不尽相同,不一样的厂家区别很大,不像X86由硬件厂商经过BIOS(UEFI)屏蔽底层差别,兼容性强(不过如今arm这方面已改善,在arm服务器领域已与X86同样使用UEFI标准了,树莓派安装win10就是UEFI应用的一个例子,此外设备树也是借鉴了UFEI ACPI,关于UEFI,可经过https://www.zhihu.com/topic/19573354/top-answers了解)。因此完成rootfs构建后,还须要移植好Linux内核和u-boot、设备树等。
以ubuntu16为例,下载arm架构的ubuntu-base压缩包,能够看到有几类之后缀armXXX结尾的,它们的含义以下:
下面以armhf为例。
sudo apt-get install multistrap qemu qemu-user-static binfmt-support dpkg-cross
将ubuntu-base包解压到准备的rootfs文件夹,这里为/mnt
,下面命令根据实际状况更换。
$sudo tar -xpvf ubuntu-base-16.04.4-base-armhf.tar.gz -C /mnt
拷贝qemu-arm-static
到刚刚解压出来的目录/mnt/usr/bin/
:
$sudo cp /usr/bin/qemu-arm-static /mnt/usr/bin
如果arm64则拷贝qemu-aarch64-static
:
$sudo cp /usr/bin/qemu-arm64-static /mnt/usr/bin
接下来的一切都和2.3 进入chroot环境,一致了。更新源并安装须要的软件。
apt-get update apt-get install net-tools vim bash-completion ...
也可经过chroot直接执行某个命令,例如修改root密码,,其中/mnt
是咱们的rootfs目录:
$sudo chroot /mnt passwd
直接安装软件:
$sudo LC_ALL=C LANGUAGE=C LANG=C chroot /mnt apt-get install packagename
安装内核,将内核和设备树保存到rootfs中的boot目录,即/mnt/boot/
下。nand flash除外。
与普通文件系统烧写一致,制做烧写镜像,烧写SD卡或nandflash。
请遵照相关开源协议。
#!/bin/bash shopt -s extglob # generated from util-linux source: libmount/src/utils.c declare -A pseudofs_types=([anon_inodefs]=1 [autofs]=1 [bdev]=1 [binfmt_misc]=1 [cgroup]=1 [cgroup2]=1 [configfs]=1 [cpuset]=1 [debugfs]=1 [devfs]=1 [devpts]=1 [devtmpfs]=1 [dlmfs]=1 [fuse.gvfs-fuse-daemon]=1 [fusectl]=1 [hugetlbfs]=1 [mqueue]=1 [nfsd]=1 [none]=1 [pipefs]=1 [proc]=1 [pstore]=1 [ramfs]=1 [rootfs]=1 [rpc_pipefs]=1 [securityfs]=1 [sockfs]=1 [spufs]=1 [sysfs]=1 [tmpfs]=1) # generated from: pkgfile -vbr '/fsck\..+' | awk -F. '{ print $NF }' | sort declare -A fsck_types=([cramfs]=1 [exfat]=1 [ext2]=1 [ext3]=1 [ext4]=1 [ext4dev]=1 [jfs]=1 [minix]=1 [msdos]=1 [reiserfs]=1 [vfat]=1 [xfs]=1) out() { printf "$1 $2\n" "${@:3}"; } error() { out "==> ERROR:" "$@"; } >&2 warning() { out "==> WARNING:" "$@"; } >&2 msg() { out "==>" "$@"; } msg2() { out " ->" "$@";} die() { error "$@"; exit 1; } ignore_error() { "$@" 2>/dev/null return 0 } in_array() { local i for i in "${@:2}"; do [[ $1 = "$i" ]] && return 0 done return 1 } chroot_add_mount() { mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") } chroot_maybe_add_mount() { local cond=$1; shift if eval "$cond"; then chroot_add_mount "$@" fi } chroot_setup() { CHROOT_ACTIVE_MOUNTS=() [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' trap 'chroot_teardown' EXIT chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro && ignore_error chroot_maybe_add_mount "[[ -d '$1/sys/firmware/efi/efivars' ]]" \ efivarfs "$1/sys/firmware/efi/efivars" -t efivarfs -o nosuid,noexec,nodev && chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid && chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 && chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,strictatime,nodev,nosuid } chroot_teardown() { if (( ${#CHROOT_ACTIVE_MOUNTS[@]} )); then umount "${CHROOT_ACTIVE_MOUNTS[@]}" fi unset CHROOT_ACTIVE_MOUNTS } try_cast() ( _=$(( $1#$2 )) ) 2>/dev/null valid_number_of_base() { local base=$1 len=${#2} i= for (( i = 0; i < len; i++ )); do try_cast "$base" "${2:i:1}" || return 1 done return 0 } mangle() { local i= chr= out= local {a..f}= {A..F}= for (( i = 0; i < ${#1}; i++ )); do chr=${1:i:1} case $chr in [[:space:]\\]) printf -v chr '%03o' "'$chr" out+=\\ ;; esac out+=$chr done printf '%s' "$out" } unmangle() { local i= chr= out= len=$(( ${#1} - 4 )) local {a..f}= {A..F}= for (( i = 0; i < len; i++ )); do chr=${1:i:1} case $chr in \\) if valid_number_of_base 8 "${1:i+1:3}" || valid_number_of_base 16 "${1:i+1:3}"; then printf -v chr '%b' "${1:i:4}" (( i += 3 )) fi ;; esac out+=$chr done printf '%s' "$out${1:i}" } optstring_match_option() { local candidate pat patterns IFS=, read -ra patterns <<<"$1" for pat in "${patterns[@]}"; do if [[ $pat = *=* ]]; then # "key=val" will only ever match "key=val" candidate=$2 else # "key" will match "key", but also "key=anyval" candidate=${2%%=*} fi [[ $pat = "$candidate" ]] && return 0 done return 1 } optstring_remove_option() { local o options_ remove=$2 IFS=, read -ra options_ <<<"${!1}" for o in "${!options_[@]}"; do optstring_match_option "$remove" "${options_[o]}" && unset 'options_[o]' done declare -g "$1=${options_[*]}" } optstring_normalize() { local o options_ norm IFS=, read -ra options_ <<<"${!1}" # remove empty fields for o in "${options_[@]}"; do [[ $o ]] && norm+=("$o") done # avoid empty strings, reset to "defaults" declare -g "$1=${norm[*]:-defaults}" } optstring_append_option() { if ! optstring_has_option "$1" "$2"; then declare -g "$1=${!1},$2" fi optstring_normalize "$1" } optstring_prepend_option() { local options_=$1 if ! optstring_has_option "$1" "$2"; then declare -g "$1=$2,${!1}" fi optstring_normalize "$1" } optstring_get_option() { local opts o IFS=, read -ra opts <<<"${!1}" for o in "${opts[@]}"; do if optstring_match_option "$2" "$o"; then declare -g "$o" return 0 fi done return 1 } optstring_has_option() { local "${2%%=*}" optstring_get_option "$1" "$2" } dm_name_for_devnode() { read dm_name <"/sys/class/block/${1#/dev/}/dm/name" if [[ $dm_name ]]; then printf '/dev/mapper/%s' "$dm_name" else # don't leave the caller hanging, just print the original name # along with the failure. print '%s' "$1" error 'Failed to resolve device mapper name for: %s' "$1" fi } fstype_is_pseudofs() { (( pseudofs_types["$1"] )) } fstype_has_fsck() { (( fsck_types["$1"] )) } usage() { cat <<EOF usage: ${0##*/} chroot-dir [command] -h Print this help message -u <user>[:group] Specify non-root user and optional group to use If 'command' is unspecified, ${0##*/} will launch /bin/bash. Note that when using arch-chroot, the target chroot directory *should* be a mountpoint. This ensures that tools such as pacman(8) or findmnt(8) have an accurate hierarchy of the mounted filesystems within the chroot. If your chroot target is not a mountpoint, you can bind mount the directory on itself to make it a mountpoint, i.e. 'mount --bind /your/chroot /your/chroot'. EOF } chroot_add_resolv_conf() { local chrootdir=$1 resolv_conf=$1/etc/resolv.conf [[ -e /etc/resolv.conf ]] || return 0 # Handle resolv.conf as a symlink to somewhere else. if [[ -L $chrootdir/etc/resolv.conf ]]; then # readlink(1) should always give us *something* since we know at this point # it's a symlink. For simplicity, ignore the case of nested symlinks. resolv_conf=$(readlink "$chrootdir/etc/resolv.conf") if [[ $resolv_conf = /* ]]; then resolv_conf=$chrootdir$resolv_conf else resolv_conf=$chrootdir/etc/$resolv_conf fi # ensure file exists to bind mount over if [[ ! -f $resolv_conf ]]; then install -Dm644 /dev/null "$resolv_conf" || return 1 fi elif [[ ! -e $chrootdir/etc/resolv.conf ]]; then # The chroot might not have a resolv.conf. return 0 fi chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind } while getopts ':hu:' flag; do case $flag in h) usage exit 0 ;; u) userspec=$OPTARG ;; :) die '%s: option requires an argument -- '\''%s'\' "${0##*/}" "$OPTARG" ;; ?) die '%s: invalid option -- '\''%s'\' "${0##*/}" "$OPTARG" ;; esac done shift $(( OPTIND - 1 )) (( EUID == 0 )) || die 'This script must be run with root privileges' (( $# )) || die 'No chroot directory specified' chrootdir=$1 shift [[ -d $chrootdir ]] || die "Can't create chroot on non-directory %s" "$chrootdir" if ! mountpoint -q "$chrootdir"; then warning "$chrootdir is not a mountpoint. This may have undesirable side effects." fi chroot_setup "$chrootdir" || die "failed to setup chroot %s" "$chrootdir" chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf" chroot_args=() [[ $userspec ]] && chroot_args+=(--userspec "$userspec") echo "${chroot_args[@]}" echo "$chrootdir" echo "$@" SHELL=/bin/bash unshare --fork --pid chroot "${chroot_args[@]}" -- "$chrootdir" "$@"