build initramfs: speedup
cover build initramfs: speedup
This patch series saves 50% (a factor of 2) in real time before final gzip when building initramfs for Fedora 16.
Probably you should apply only the first six patches (1,2,3,4,5,6) because directory symlinks and permissions aren't right after patch 7. [I don't care about directory symlinks now; permissions are 775 instead of 755.] I'm working on a different approach for those; it's not ready. However, the first six patches save 25% (a factor of 4/3),
and patch 7 saves another 25% by replacing most /bin/cp with cpio.
A message that is not in the archive (sent off-list or not yet received): 4E5DBAE4.2000205-Po6cBsTGB2ZWk0Htik3J/[email protected]
Quoted 10 lines · John Reiser
This patch series saves 50% (a factor of 2) in real time before final gzip when building initramfs for Fedora 16.
Probably you should apply only the first six patches (1,2,3,4,5,6) because directory symlinks and permissions aren't right after patch 7. [I don't care about directory symlinks now; permissions are 775 instead of 755.] I'm working on a different approach for those; it's not ready. However, the first six patches save 25% (a factor of 4/3),
and patch 7 saves another 25% by replacing most/bin/cpwith cpio.
Thanks for your work, but your patches are kind of broken.
Either your mail program scrambled them, or you have a weird git or patch implementation.
e.g. first diff hunk in patch 4 says
@@ -863,49 +863,63 @@ filter_kernel_modules_by_path () (
but in fact it should be s.th. like
@@ -863,45 +863,59 @@ filter_kernel_modules_by_path () (
All blank lines are missing in the hunks!
Care to resend?
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 3 lines
... your patches are kind of broken.
Care to resend?
Attached.
From 2e84da8fbb475a75580b8fce4e66eff1ed4f4a1f Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Fri, 26 Aug 2011 13:01:33 -0700
Subject: [PATCH 1/8] inst_simple, inst_dir: make fast case faster
This small stuff saves 1.7% per dropped statement during "dracut --profile". Fixing the comment about /lib -> lib64 is REQUIRED!
--- dracut-functions | 16 +++++----------- 1 files changed, 5 insertions(+), 11 deletions(-)
dracut-functions +5 -11
@@ -266,19 +266,13 @@ check_vol_slaves() { } # Install a directory, keeping symlinks as on the original system. -# Example: if /lib64 points to /lib on the host, "inst_dir /lib/file" +# Example: if /lib points to /lib64 on the host, "inst_dir /lib/file" # will create ${initdir}/lib64, ${initdir}/lib64/file, # and a symlink ${initdir}/lib -> lib64. inst_dir() { - local _file="" - local _oldifs="$IFS" - local _part - local _dir="$1" + [[ -e ${initdir}"$1" ]] && return 0 # already there - # fast out - [[ -e ${initdir}$_dir ]] && return 0 - - _part=${_dir%/*} + local _dir="$1" _part=${_dir%/*} _file while [[ "$_part" != "${_part%/*}" ]] && ! [[ -e "${initdir}${_part}" ]]; do _dir="$_part $_dir" _part=${_part%/*}
@@ -310,9 +304,9 @@ inst_dir() { # Location of the image dir is assumed to be $initdir # We never overwrite the target if it exists. inst_simple() { - local _src target [[ -f $1 ]] || return 1 - _src=$1 target="${2:-$1}" + + local _src=$1 target="${2:-$1}" if ! [[ -d ${initdir}$target ]]; then [[ -e ${initdir}$target ]] && return 0 [[ -h ${initdir}$target ]] && return 0
Signature · 1 line
1.7.6
From e922399ce2f3d49a0cd306b3caf75479142b19e6 Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Sat, 27 Aug 2011 14:43:49 -0700
Subject: [PATCH 2/8] filter_kernel_modules is a specialized
filter_kernel_modules_by_path
--- dracut-functions | 31 +++---------------------------- 1 files changed, 3 insertions(+), 28 deletions(-)
dracut-functions +3 -28
@@ -863,34 +863,9 @@ filter_kernel_modules_by_path () ( done ) -# filter kernel modules to install certain modules that meet specific -# requirements. -# $1 = function to call with module name to filter. -# This function will be passed the full path to the module to test. -# The behaviour of this function can vary depending on whether $hostonly is set. -# If it is, we will only look at modules that are already in memory. -# If it is not, we will look at all kernel modules -# This function returns the full filenames of modules that match $1 -filter_kernel_modules () ( - local _modname _filtercmd - if ! [[ $hostonly ]]; then - _filtercmd='find "$srcmods/kernel/drivers" "$srcmods/extra"' - _filtercmd+=' "$srcmods/weak-updates" -name "*.ko" -o -name "*.ko.gz"' - _filtercmd+=' 2>/dev/null' - else - _filtercmd='cut -d " " -f 1 </proc/modules|xargs modinfo -F filename ' - _filtercmd+='-k $kernel 2>/dev/null' - fi - for _modname in $(eval $_filtercmd); do - case $_modname in - *.ko) "$1" "$_modname" && echo "$_modname";; - *.ko.gz) gzip -dc "$_modname" > $initdir/$$.ko - $1 $initdir/$$.ko && echo "$_modname" - rm -f $initdir/$$.ko - ;; - esac - done -) +filter_kernel_modules () { + filter_kernel_modules_by_path drivers "$1" +} # install kernel modules along with all their dependencies. instmods() {
Signature · 1 line
1.7.6
From 8d8ce7b1e1f149712a85e811c623ba4056ef4818 Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Sun, 28 Aug 2011 13:24:58 -0700
Subject: [PATCH 3/8] install_kmod_with_fw: make fast case faster
--- dracut-functions | 5 +++-- 1 files changed, 3 insertions(+), 2 deletions(-)
dracut-functions +3 -2
@@ -782,13 +782,14 @@ check_module_dir() { # Install a single kernel module along with any firmware it may require. # $1 = full path to kernel module to install install_kmod_with_fw() { - local _modname=${1##*/} _fwdir _found _fw - _modname=${_modname%.ko*} # no need to go further if the module is already installed [[ -e "${initdir}/lib/modules/$kernel/${1##*/lib/modules/$kernel/}" ]] \ && return 0 inst_simple "$1" "/lib/modules/$kernel/${1##*/lib/modules/$kernel/}" \ || return $? + + local _modname=${1##*/} _fwdir _found _fw + _modname=${_modname%.ko*} for _fw in $(modinfo -k $kernel -F firmware $1 2>/dev/null); do _found='' for _fwdir in $fw_dir; do
Signature · 1 line
1.7.6
From 42953a1782333af1899b82b46f9cb5d1fc3b6b7e Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Mon, 29 Aug 2011 13:40:21 -0700
Subject: [PATCH 4/8] instmods: get filenames from stdin if no args; use it
Use bash "[[ string =~ pattern ]]" instead of "egrep -q". Replace control-dominated serial fondling
for var in $(proc1); do proc2 var; done
with data-dominated parallel pipeline
proc1 | while read var; do proc2 var; done
Together this is a large savings.
--- dracut-functions | 57 ++++++++++++++++++++-------- modules.d/40network/module-setup.sh | 18 ++++++-- modules.d/90kernel-modules/module-setup.sh | 11 +++++- modules.d/90multipath/module-setup.sh | 15 +++++-- modules.d/95iscsi/module-setup.sh | 12 ++++- 5 files changed, 83 insertions(+), 30 deletions(-)
dracut-functions +41 -16
96 lines not on this page · raw message
modules.d/40network/module-setup.sh +13 -5
@@ -24,15 +24,23 @@ depends() { installkernel() { # Include wired net drivers, excluding wireless - net_module_test() { + net_module_filter() { local _net_drivers='eth_type_trans|register_virtio_device' local _unwanted_drivers='/(wireless|isdn|uwb)/' - egrep -q $_net_drivers "$1" && \ - egrep -qv 'iw_handler_get_spy' "$1" && \ - [[ ! $1 =~ $_unwanted_drivers ]] + local _fname + while read _fname; do + local _fcont + case "$_fname" in + *.ko) _fcont="$(< $_fname)" ;; + *.ko.gz) _fcont="$(gzip -dc $_fname)" ;; + esac + [[ $_fcont =~ $_net_drivers + && ! $_fcont =~ iw_handler_get_spy|$_unwanted_drivers ]] \ + && echo "$_fname" + done } - instmods $(filter_kernel_modules_by_path drivers/net net_module_test) + find_kernel_modules_by_path drivers/net | net_module_filter | instmods instmods ecb arc4 # bridge modules
modules.d/90kernel-modules/module-setup.sh +10 -1
@@ -9,6 +9,15 @@ installkernel() { egrep -q "$blockfuncs" "$1" } + block_module_filter() { + local _blockfuncs='ahci_init_controller|ata_scsi_ioctl|scsi_add_host|blk_init_queue|register_mtd_blktrans|scsi_esp_register|register_virtio_device' + local _f + while read _f; do case "$_f" in + *.ko) [[ $(< $_f) =~ $_blockfuncs ]] && echo "$_f" ;; + *.ko.gz) [[ $(gzip -dc <$_f) =~ $_blockfuncs ]] && echo "$_f" ;; + esac + done + } hostonly='' instmods sr_mod sd_mod scsi_dh scsi_dh_rdac scsi_dh_emc hostonly='' instmods pcmcia firewire-ohci hostonly='' instmods usb_storage sdhci sdhci-pci
@@ -18,7 +27,7 @@ installkernel() { # install unix socket support hostonly='' instmods unix instmods "=drivers/pcmcia" =ide "=drivers/usb/storage" - instmods $(filter_kernel_modules block_module_test) + find_kernel_modules | block_module_filter | instmods # if not on hostonly mode, install all known filesystems, # if the required list is not set via the filesystems variable if ! [[ $hostonly ]]; then
modules.d/90multipath/module-setup.sh +10 -5
@@ -33,13 +33,18 @@ depends() { } installkernel() { - mp_mod_test() { - local mpfuncs='scsi_register_device_handler|dm_dirty_log_type_register|dm_register_path_selector|dm_register_target' - egrep -q "$mpfuncs" "$1" + mp_mod_filter() { + local _mpfuncs='scsi_register_device_handler|dm_dirty_log_type_register|dm_register_path_selector|dm_register_target' + local _f + while read _f; do case "$_f" in + *.ko) [[ $(< $_f) =~ $_mpfuncs ]] && echo "$_f" ;; + *.ko.gz) [[ $(gzip -dc <$_f) =~ $_mpfuncs ]] && echo "$_f" ;; + esac + done } - instmods $(filter_kernel_modules_by_path drivers/scsi mp_mod_test) - instmods $(filter_kernel_modules_by_path drivers/md mp_mod_test) + ( find_kernel_modules_by_path drivers/scsi; + find_kernel_modules_by_path drivers/md ) | mp_mod_filter | instmods } install() {
modules.d/95iscsi/module-setup.sh +9 -3
@@ -42,11 +42,17 @@ depends() { installkernel() { instmods iscsi_tcp iscsi_ibft crc32c - iscsi_module_test() { + iscsi_module_filter() { local _iscsifuncs='iscsi_register_transport' - fgrep -q "$_iscsifuncs" "$1" + local _f + while read _f; do case "$_f" in + *.ko) [[ $(< $_f) =~ $_iscsifuncs ]] && echo "$_f" ;; + *.ko.gz) [[ $(gzip -dc <$_f) =~ $_iscsifuncs ]] && echo "$_f" ;; + esac + done } - instmods $(filter_kernel_modules_by_path drivers/scsi iscsi_module_test) + find_kernel_modules_by_path drivers/scsi \ + | iscsi_module_filter | instmods } install() {
Signature · 1 line
1.7.6
From a78986abef62dc421aae51e9f31df61419e46d7d Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Mon, 29 Aug 2011 14:46:25 -0700
Subject: [PATCH 5/8] instmods: sanity for _mpargs
--- dracut-functions | 11 +++++++---- 1 files changed, 7 insertions(+), 4 deletions(-)
dracut-functions +7 -4
@@ -892,17 +892,20 @@ instmods() { # This introduces 2 incompatible meanings for =* arguments # to instmods. We need to decide which one to keep. if [[ $_mod = =ata && -f $srcmods/modules.block ]]; then - ( echo -n "$_mpargs"; egrep 'ata|ahci' "${srcmods}/modules.block" ) \ + ( [[ "$_mpargs" ]] && echo $_mpargs + egrep 'ata|ahci' "${srcmods}/modules.block" ) \ | instmods elif [ -f $srcmods/modules.${_mod#=} ]; then - ( echo -n "$_mpargs"; cat "${srcmods}/modules.${_mod#=}" ) \ + ( [[ "$_mpargs" ]] && echo $_mpargs + cat "${srcmods}/modules.${_mod#=}" ) \ | instmods else - ( echo -n "$_mpargs"; find "$srcmods" -path "*/${_mod#=}/*" ) \ + ( [[ "$_mpargs" ]] && echo $_mpargs + find "$srcmods" -path "*/${_mod#=}/*" ) \ | instmods fi ;; - --*) _mpargs+="${_mod##*/}"$'\n' ;; # one _mod per line; lops '--' + --*) _mpargs+=" $_mod" ;; i2o_scsi) return ;; # Do not load this diagnostic-only module *) _mod=${_mod##*/}
Signature · 1 line
1.7.6
From 94d1f80ecae28bfecf38248a28a494b86551e0fa Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Mon, 29 Aug 2011 16:03:35 -0700
Subject: [PATCH 6/8] instmods: factor out egrep of "FATAL: Module .* not
found"
--- dracut-functions | 34 +++++++++++++++++++--------------- 1 files changed, 19 insertions(+), 15 deletions(-)
dracut-functions +19 -15
@@ -820,7 +820,7 @@ install_kmod_with_fw() { for_each_kmod_dep() { local _func=$1 _kmod=$2 _cmd _modpath _options _found=0 shift 2 - modprobe "$@" --ignore-install --show-depends $_kmod 2>"$initdir/modprobe.err" | ( + modprobe "$@" --ignore-install --show-depends $_kmod 2>&$modprobe_stderr | ( while read _cmd _modpath _options; do [[ $_cmd = insmod ]] || continue $_func ${_modpath} || exit $?
@@ -829,9 +829,6 @@ for_each_kmod_dep() { [[ $_found -eq 0 ]] && exit 1 exit 0 ) - egrep -v 'FATAL: Module .* not found.' "$initdir/modprobe.err" | derror - rm -f "$initdir/modprobe.err" - return $? } # filter kernel modules to install certain modules that meet specific
@@ -934,16 +931,23 @@ instmods() { esac } - local _mpargs _ret=0 - if (($# == 0)); then # filenames from stdin - local _mod - while read _mod; do - inst1mod "${_mod%.ko*}" + function instmods_1() { + local _ret=0 _mod _mpargs + if (($# == 0)); then # filenames from stdin + while read _mod; do + inst1mod "${_mod%.ko*}" + done + fi + while (($# > 0)); do # filenames as arguments + inst1mod ${1%.ko*} + shift done - fi - while (($# > 0)); do # filenames as args - inst1mod ${1%.ko*} - shift - done - return $_ret + return $_ret + } + + # Capture all stderr from modprobe onto a new fd $modprobe_stderr, + # and pipe it into egrep. See REDIRECTION in bash manpage. + ( instmods_1 "$@" ) {modprobe_stderr}>&1 \ + | egrep -v 'FATAL: Module .* not found.' | derror + return $? }
Signature · 1 line
1.7.6
From 4645a5d7517c485042c915c51c07fe9e9ca61727 Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Mon, 29 Aug 2011 22:54:59 -0700
Subject: [PATCH 7/8] build initramfs: cpio avoids per-file /bin/cp
Replacing most /bin/cp with one cpio reduces execve and increases parallelism. This is a large savings.
TODO: Fix permissions on directories (cpio 775 should be inst_dir 755.)
--- dracut | 5 +++++ dracut-functions | 20 ++++++++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-)
dracut +5 -0
@@ -500,6 +500,8 @@ if [[ $prefix ]]; then done fi +export cpio_stdin; ( # begin pipe into cpio to avoid per-file 'cp' + if [[ $kernel_only != yes ]]; then for d in bin etc lib "$libdir" sbin tmp usr var usr/bin usr/sbin; do [[ -e "${initdir}${prefix}/$d" ]] && continue
@@ -613,6 +615,9 @@ if [[ $kernel_only != yes ]]; then fi fi +# bash sets cpio_stdin to a new fd >= 10. See REDIRECTION in bash manpage. +) {cpio_stdin}>&1 | cpio -pdmu "${initdir}" + if (($maxloglvl >= 5)); then ddebug "Listing sizes of included files:" du -c "$initdir" | sort -n | ddebug
dracut-functions +16 -4
@@ -306,6 +306,17 @@ inst_dir() { inst_simple() { [[ -f $1 ]] || return 1 + if [[ 1 = $# || $2 = $1 ]]; then + [[ -e ${initdir}$1 ]] && return 0 + + local _hmac="${1%/*}/.${1##*/}.hmac" + [[ -e $_hmac ]] && echo "$_hmac" >&${cpio_stdin} + + # ddebug "Installing $1" + echo "$1" >&${cpio_stdin} + return 0 + fi + local _src=$1 target="${2:-$1}" if ! [[ -d ${initdir}$target ]]; then [[ -e ${initdir}$target ]] && return 0
@@ -313,9 +324,9 @@ inst_simple() { inst_dir "${target%/*}" fi # install checksum files also - if [[ -e "${_src%/*}/.${_src##*/}.hmac" ]]; then - inst_simple "${_src%/*}/.${_src##*/}.hmac" "${target%/*}/.${target##*/}.hmac" - fi + local _hmac="${_src%/*}/.${_src##*/}.hmac" + [[ -e $_hmac ]] && inst_simple "$_hmac" "${target%/*}/.${target##*/}.hmac" + ddebug "Installing $_src" cp -pfL "$_src" "${initdir}$target" }
@@ -820,7 +831,7 @@ install_kmod_with_fw() { for_each_kmod_dep() { local _func=$1 _kmod=$2 _cmd _modpath _options _found=0 shift 2 - modprobe "$@" --ignore-install --show-depends $_kmod 2>&$modprobe_stderr | ( + modprobe "$@" --ignore-install --show-depends $_kmod 2>&${modprobe_stderr} | ( while read _cmd _modpath _options; do [[ $_cmd = insmod ]] || continue $_func ${_modpath} || exit $?
@@ -947,6 +958,7 @@ instmods() { # Capture all stderr from modprobe onto a new fd $modprobe_stderr, # and pipe it into egrep. See REDIRECTION in bash manpage. + export modprobe_stderr ( instmods_1 "$@" ) {modprobe_stderr}>&1 \ | egrep -v 'FATAL: Module .* not found.' | derror return $?
Signature · 1 line
1.7.6
From eeafddf4d891aeb5bffbaffa06a83c5050ee34c9 Mon Sep 17 00:00:00 2001
From: John Reiser <[email protected]>
Date: Tue, 30 Aug 2011 15:02:56 -0700
Subject: [PATCH 8/8] Close original fd after bash-generated high redirection.
--- dracut | 3 ++- dracut-functions | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-)
dracut +2 -1
@@ -616,7 +616,8 @@ if [[ $kernel_only != yes ]]; then fi # bash sets cpio_stdin to a new fd >= 10. See REDIRECTION in bash manpage. -) {cpio_stdin}>&1 | cpio -pdmu "${initdir}" +# Pipe that new fd as stdin into cpio, and ignore the old stdout of subshell. +) {cpio_stdin}>&1 1>&- | cpio -pdmu --quiet "${initdir}" if (($maxloglvl >= 5)); then ddebug "Listing sizes of included files:"
dracut-functions +1 -1
@@ -959,7 +959,7 @@ instmods() { # Capture all stderr from modprobe onto a new fd $modprobe_stderr, # and pipe it into egrep. See REDIRECTION in bash manpage. export modprobe_stderr - ( instmods_1 "$@" ) {modprobe_stderr}>&1 \ + ( instmods_1 "$@" ) {modprobe_stderr}>&1 1>&- \ | egrep -v 'FATAL: Module .* not found.' | derror return $? }
Signature · 1 line
1.7.6
Quoted 10 lines · John Reiser
This patch series saves 50% (a factor of 2) in real time before final gzip when building initramfs for Fedora 16.
Probably you should apply only the first six patches (1,2,3,4,5,6) because directory symlinks and permissions aren't right after patch 7. [I don't care about directory symlinks now; permissions are 775 instead of 755.] I'm working on a different approach for those; it's not ready. However, the first six patches save 25% (a factor of 4/3),
and patch 7 saves another 25% by replacing most/bin/cpwith cpio.
Regarding path 7 and 8:
directory permissions could be copied with:
inst_simple()
....
- echo "$1" >&${cpio_stdin}
+ local _part=$1
+ while [[ "$_part" != "${_part%/*}" ]]; do+ echo "$_part" >&${cpio_stdin}
+ _part=${_part%/*}
+ done
return 0
...
BUT, there is a general problem with this.
1. the check, if the file is already present fails. This changes behaviour for e.g. install "bash" if /bin/sh is not yet there. In our case, of the "dash" dracut module is installed, bash will not be installed.
So, we would have to bookkeep, what we already sent to cpio.
2. postprocessing of installed files. E.g. the lvm modules changes /etc/lvm/lvm.conf. This could be prevented, by pulling out all post processing and call all modules-setup.sh with install_post() after cpio copying.
A message that is not in the archive (sent off-list or not yet received): [email protected]
Quoted 7 lines · Harald Hoyer
Regarding pat[c]h 7 and 8:
directory permissions could be copied with:
inst_simple()
....
Review
Quoted 9 lines
(quoted diff) +5
-1- echo "$1" >&${cpio_stdin} + local _part=$1 + while [[ "$_part" != "${_part%/*}" ]]; do + echo "$_part" >&${cpio_stdin} + _part=${_part%/*} + done return 0
Yes, cpio of a directory agrees with what we want, and can be used as a tool.
Quoted 6 lines
BUT, there is a general problem with this.
1. the check, if the file is already present fails. This changes behaviour for e.g. install "bash" if/bin/shis not yet there. In our case, of the "dash" dracut module is installed, bash will not be installed.
So, we would have to bookkeep, what we already sent to cpio.
Yes, there is an issue. It is a race, because sometimes the cpio (executing in parallel) will finish [enough of] a previous copy soon enough for a following inst_simple to see the result.
Low-level bookkeeping is not that difficult using a bash associative array, but:
1. All created paths must be tracked (symlink, directory, regular file, etc.)
2. Because instmods() may be called from different immediate parent shell processes [forks in different pipelines] then the results cannot be aggregated if done by any function called below instmods(). So the tracking must be done by a filter just before cpio, or by cpio itself: a new option "do not overwrite, regardless of modification times." [Or, tell cpio not to preserve modification times. Then the first copy to a given destination will be newer than any subsequent source for the same destination, so the cpio check "overwrite only if source is newer" always will fail. This achieves the effect we want, at a cost of not preserving timestamps.]
3. Allowing a destination path which does not contain the source path as a tail, causes a complication because cpio cannot do this in one step. (It can be done using a second cpio and chdir to a temporary directory.)
Quoted 3 lines
2. postprocessing of installed files. E.g. the lvm modules changes
/etc/lvm/lvm.conf. This could be prevented, by pulling out all post processing and call allmodules-setup.shwithinstall_post()after cpio copying.
This is another race. I agree that postprocessing should be separated.
Patch 7 illustrates the cost of design decision(s). At a low level, "/bin/cp is inexpensive" is true for a few, but not for 1900 of them. My 3GHz system can do about 200 per second, so right there is 9.5 seconds before moving any data at all. As an end-user installer/upgrader, and as a builder+tester of install media, that time is all waste.
At a higher level, specifying sequential processing (instead of allowing all paths to be handled in parallel) can be costly.
For a while, I'm going to work on other pieces of build initramfs instead.