linux systemにおけるinitの最期

2018/12/18import
Linux Advent Calender 2018の18日目です。

元々の予定はボツになった!?ので ちょっと古い話になります。

7日目のsystemd理解のヒント - systemdの概念と歴史と相反するような記事です。
古い情報も必要としている組込もんもいるので、その助けとなりましたら幸いです。

initとは

PID=1として、カーネルの初期化が終わって初めて生成されるプロセスです。
ユーザランドで必要なファイルシステムのマウント・システム設定・サービス(デーモン)やプロセス起動を行います。

kernel parameterに init=でinitコマンドを指定することができます。省略すると /sbin/initなどが参照されます。
init=/etc/preinit
ここでは 組込装置を前提として、busyboxのinitを想定します

設定ファイル

initが起動すると、以下のファイルを参照します。run levelに応じた処理を記述するようになっています。
シリアルコンソールの設定なども、ここに記述することができます。
/etc/inittab
■/etc/inittab
FILE: 06.build/patches/rootfs/etc/inittab
# /etc/inittab
#
# Copyright (C) 2001 Erik Andersen <andersen@codepoet.org>
#
# Note: BusyBox init doesn't support runlevels.  The runlevels field is
# completely ignored by BusyBox init. If you want runlevels, use
# sysvinit.
#
# Format for each entry: <id>:<runlevels>:<action>:<process>
#
# id        == tty to run on, or empty for /dev/console
# runlevels == ignored
# action    == one of sysinit, respawn, askfirst, wait, and once
# process   == program to run

# Startup the system
::sysinit:/bin/mount -t proc proc /proc
::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts
::sysinit:/bin/mkdir -p /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/hostname -F /etc/hostname
# now run any rc scripts
::sysinit:/etc/init.d/rcS

# Put a getty on the serial port
#ttyS0::respawn:/sbin/getty -L ttyS0 115200 vt100 # GENERIC_SERIAL

# Stuff to do for the 3-finger salute
#::ctrlaltdel:/sbin/reboot

# Stuff to do before rebooting
::shutdown:/etc/init.d/rcK
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r

システムの終了

reboot、halt、shutdownといったコマンドを実行すれば、システムを再起動・停止・終了することができますね。
このコマンドを実行したとき、何が行われているのでしょうか。

rebootに対象を絞って、かつ、busybox版で確認していきましょう。

v1.19.4
FILE: init/halt.c
//applet:IF_HALT(APPLET(halt, BB_DIR_SBIN, BB_SUID_DROP))
//applet:IF_HALT(APPLET_ODDNAME(poweroff, halt, BB_DIR_SBIN, BB_SUID_DROP, poweroff))
//applet:IF_HALT(APPLET_ODDNAME(reboot, halt, BB_DIR_SBIN, BB_SUID_DROP, reboot))
rebootは haltのaliasになるようですね。
int halt_main(int argc UNUSED_PARAM, char **argv)
|
...
	sleep(0);
	write_wtmp();  // last.logを記録する
	sync();        // デフォルトはsync(2)を実行する.

	kill(1,SIGTERM)	// initに signalを飛ばす. -f付きなら reboot(2)で 0x01234567 を渡す..
コメントに書いたようにsignalを飛ばすだけでした。
initに処理が移ります:

FILE: init/init.c

FUNC: int init_main(int argc UNUSED_PARAM, char **argv)
		bb_signals(0
			+ (1 << SIGUSR1) /* halt */
			+ (1 << SIGTERM) /* reboot */
			+ (1 << SIGUSR2) /* poweroff */
			, halt_reboot_pwoff);
signal handlerを登録します。bb_*は、Busyboxのライブラリ関数です。
第一引数で指定されたsignalを許可し、第二引数の関数をハンドラとして登録します。

ハンドラを見ていきましょう:
static void halt_reboot_pwoff(int sig)
{
	const char *m;
	unsigned rb;

	/* We may call run() and it unmasks signals,
	 * including the one masked inside this signal handler.
	 * Testcase which would start multiple reboot scripts:
	 *  while true; do reboot; done
	 * Preventing it:
	 */
	reset_sighandlers_and_unblock_sigs();
		// ハンドラをデフォルトに戻して, 有効化

	run_shutdown_and_kill_processes();
		// 後述. run_actrions(),全プロセスにSIGTERM, sync(2),sleep(1sec),SIGKILL,sync(2)

	m = "halt";
	rb = RB_HALT_SYSTEM;
	if (sig == SIGTERM) {
		m = "reboot";
		rb = RB_AUTOBOOT;
	} else if (sig == SIGUSR2) {
		m = "poweroff";
		rb = RB_POWER_OFF;
	}
	message(L_CONSOLE, "Requesting system %s", m);
	pause_and_low_level_reboot(rb);
		// 後述. sleep(1sec), vfork(2)した子で reboot(magic:0x1234567), 親(pid=1)はsleep forever.
	/* not reached */
}
いくつか関数を読んでいるので、引用します。コメントに書いたような処理を行っています。
static void run_shutdown_and_kill_processes(void)
{
	/* Run everything to be run at "shutdown".  This is done _prior_
	 * to killing everything, in case people wish to use scripts to
	 * shut things down gracefully... */
	run_actions(SHUTDOWN);

	message(L_CONSOLE | L_LOG, "The system is going down NOW!");

	/* Send signals to every process _except_ pid 1 */
	kill(-1, SIGTERM);
	message(L_CONSOLE | L_LOG, "Sent SIG%s to all processes", "TERM");
	sync();
	sleep(1);

	kill(-1, SIGKILL);
	message(L_CONSOLE, "Sent SIG%s to all processes", "KILL");
	sync();
	/*sleep(1); - callers take care about making a pause */
}
run_actions(SHUTDOWN);では、 inittabで記述した "::shutdown:"のコマンドを実行していきます。
swapoff, unmount -a -rが特徴的ですね。とくに後者は ファイルシステムをアンマウントすることで、
終了処理中に書き込みを行わせないようにしています(アンマウントできないときは
read onlyでリマウントするオプションが設定されています)。
static void pause_and_low_level_reboot(unsigned magic)
{
	pid_t pid;

	/* Allow time for last message to reach serial console, etc */
	sleep(1);

	/* We have to fork here, since the kernel calls do_exit(EXIT_SUCCESS)
	 * in linux/kernel/sys.c, which can cause the machine to panic when
	 * the init process exits... */
	pid = vfork();
	if (pid == 0) { /* child */
		reboot(magic);
		_exit(EXIT_SUCCESS);
	}
	while (1)
		sleep(1);
}
reboot(2)を実行しているようなので、libcを見ていきましょう。

libc

reboot(2)は以下のようにシステムコールを実行するだけでした。
/* Call kernel with additional two arguments the syscall requires.  */
int
reboot (int howto)
{
  return INLINE_SYSCALL (reboot, 3, (int) 0xfee1dead, 672274793, howto);
}
magic1: 0xfee1dead
magic2: 0x28121969

マジックナンバーが入っているけれども、これはカーネルソースで定義されている値ですね。

FILE: include/linux/reboot.h
/*
 * Magic values required to use _reboot() system call.
 */

#define LINUX_REBOOT_MAGIC1     0xfee1dead
#define LINUX_REBOOT_MAGIC2     672274793
#define LINUX_REBOOT_MAGIC2A    85072278
#define LINUX_REBOOT_MAGIC2B    369367448
#define LINUX_REBOOT_MAGIC2C    537993216

/*
 * Commands accepted by the _reboot() system call.
 *
 * RESTART     Restart system using default command and mode.
 * HALT        Stop OS and give system control to ROM monitor, if any.
 * CAD_ON      Ctrl-Alt-Del sequence causes RESTART command.
 * CAD_OFF     Ctrl-Alt-Del sequence sends SIGINT to init task.
 * POWER_OFF   Stop OS and remove all power from system, if possible.
 * RESTART2    Restart system using given command string.
 * SW_SUSPEND  Suspend system using software suspend if compiled in.
 * KEXEC       Restart system using a previously loaded Linux kernel
 */

#define LINUX_REBOOT_CMD_RESTART        0x01234567
#define LINUX_REBOOT_CMD_HALT           0xCDEF0123
#define LINUX_REBOOT_CMD_CAD_ON         0x89ABCDEF
#define LINUX_REBOOT_CMD_CAD_OFF        0x00000000
#define LINUX_REBOOT_CMD_POWER_OFF      0x4321FEDC
#define LINUX_REBOOT_CMD_RESTART2       0xA1B2C3D4
#define LINUX_REBOOT_CMD_SW_SUSPEND     0xD000FCE2
#define LINUX_REBOOT_CMD_KEXEC          0x45584543


kernel実装

(v4.4.120を参照しています)
システムコールから呼ばれる関数は以下ですね:

FILE: kernel/reboot.c
/*
 * Reboot system call: for obvious reasons only root may call it,
 * and even root needs to set up some magic numbers in the registers
 * so that some mistake won't make this reboot the whole machine.
 * You can also set the meaning of the ctrl-alt-del-key here.
 *
 * reboot doesn't sync: do that yourself before calling this.
 */
SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
		void __user *, arg)
	mutex_lock(&reboot_mutex);
	switch (cmd) {
	case LINUX_REBOOT_CMD_RESTART:
		kernel_restart(NULL);
		break;
..
	mutex_unlock(&reboot_mutex);
	return ret;
}
/**
 *	kernel_restart - reboot the system
 *	@cmd: pointer to buffer containing command to execute for restart
 *		or %NULL
 *
 *	Shutdown everything and perform a clean reboot.
 *	This is not safe to call in interrupt context.
 */
void kernel_restart(char *cmd)
{
	kernel_restart_prepare(cmd);
	migrate_to_reboot_cpu();
	syscore_shutdown();		// syscore_ops_listを走査する
	if (!cmd)
		printk(KERN_EMERG "Restarting system.\n");
	else
		printk(KERN_EMERG "Restarting system with command '%s'.\n", cmd);
	kmsg_dump(KMSG_DUMP_RESTART);
	machine_restart(cmd);
}
EXPORT_SYMBOL_GPL(kernel_restart);
void kernel_restart_prepare(char *cmd)
{
	blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
	system_state = SYSTEM_RESTART;
	usermodehelper_disable();		// timeout: 5sec
	device_shutdown();		// timeoutなし, devices_kset->list を走査する.
}
device_shutdown()と syscore_shutdown()の詳細は追い切れていませんが、
登録されているコールバック関数を呼び出して後処理をしているようです。
loadable moduleは removeされるような挙動を見かけたので、ここで解放されていると思われます(要確認)。

アーキテクチャ依存部: machine_restart()

armの場合は以下のとおり。
さらにプラットフォーム依存・ボード依存部で定義された関数呼び出しへと続きます。

FILE: arch/arm/kernel/reboot.c
void machine_restart(char *cmd)
{
	local_irq_disable();
	smp_send_stop();

	if (arm_pm_restart)
		arm_pm_restart(reboot_mode, cmd);
	else
		do_kernel_restart(cmd);

	/* Give a grace period for failure to restart of 1s */
	mdelay(1000);

	/* Whoops - the platform was unable to reboot. Tell the user! */
	printk("Reboot failed -- System halted\n");
	local_irq_disable();
	while (1);
}
このあたりは使用されているターゲットのコードを追いかけましょう。



参考まで、arm64の場合は以下のとおり。firmwareがUEFI対応であれば、それを使うようです。
FILE: arm64/kernel/process.c
/*
 * Restart requires that the secondary CPUs stop performing any activity
 * while the primary CPU resets the system. Systems with multiple CPUs must
 * provide a HW restart implementation, to ensure that all CPUs reset at once.
 * This is required so that any code running after reset on the primary CPU
 * doesn't have to co-ordinate with other CPUs to ensure they aren't still
 * executing pre-reset code, and using RAM that the primary CPU's code wishes
 * to use. Implementing such co-ordination would be essentially impossible.
 */
void machine_restart(char *cmd)
{
	/* Disable interrupts first */
	local_irq_disable();
	smp_send_stop();

	/*
	 * UpdateCapsule() depends on the system being reset via
	 * ResetSystem().
	 */
	if (efi_enabled(EFI_RUNTIME_SERVICES))
		efi_reboot(reboot_mode, NULL);

	/* Now call the architecture specific reboot code. */
	if (arm_pm_restart)
		arm_pm_restart(reboot_mode, cmd);
	else
		do_kernel_restart(cmd);

	/*
	 * Whoops - the architecture was unable to reboot.
	 */
	printk("Reboot failed -- System halted\n");
	while (1);
}

FILE: kernel/reboot.c
/**
 *	do_kernel_restart - Execute kernel restart handler call chain
 *
 *	Calls functions registered with register_restart_handler.
 *
 *	Expected to be called from machine_restart as last step of the restart
 *	sequence.
 *
 *	Restarts the system immediately if a restart handler function has been
 *	registered. Otherwise does nothing.
 */
void do_kernel_restart(char *cmd)
{
	atomic_notifier_call_chain(&restart_handler_list, reboot_mode, cmd);
}

奥が深いですね...(ぇ

2014/11/26(水)[ARM][Linux]copy_{from|to}_user()

Linux Advent Calendar 2014の19日目に登録させていただきました。

copy_to_user()

kernel空間からusesr空間への安全なメモリコピー、と見てもらえれば良いでしょう。
単純なメモリコピーは、memcpy()を使いますが、これはユーザ空間のアドレスを
指定できることに意義があるもの。

ユーザ空間のメモリは必ずしも物理メモリに割りつけられているとは限らない。
まぁこれだけではないので、swap未使用であったりメモリが多くても使いましょう。
NAME
 copy_to_user - Copy a block of data into user space.

SYNOPSIS
 unsigned long copy_to_user(void __user * to, const void * from, unsigned long n);

ARGUMENTS
 to
   Destination address, in user space.

 from
   Source address, in kernel space.

 n
   Number of bytes to copy.

CONTEXT
 User context only. This function may sleep.

DESCRIPTION
 Copy data from kernel space to user space.
 Returns number of bytes that could not be copied. On success, this will be zero.


実装を追う

実際にコードを追った順序と記載順序が異なるので、変な感じです。

関数の実態は以下にあります。
アライメントを考慮した最適な転送を行うためのテンプレでコードが書かれており、
そこで使用するデータ転送の実態をcopy_to_userとcopy_from_userでマクロ定義し、
同一templateでコードを生成させています。
FILE: arch/arm/lib/copy_to_user.S
FILE: arch/arm/lib/copy_template.S

で、ここからが本題。strusrマクロでは、abort引数がついています。
これが何しているのか、何となく想像はつきますが、理解できなかった。
バイナリを逆アセンブルすると普通のstr命令が並ぶだけで、memcpyとの差異が判らず。
実コードと併せて定数テーブルを作っていることに気がつき、
このセクション名で検索したところ仕組みがわかったという流れでした。

FILE: copy_to_user.S
	.macro str1w ptr reg abort
	strusr	\reg, \ptr, 4, abort=\abort
	.endm
FILE: archinc/asm/assembler.h
	.macro	strusr, reg, ptr, inc, cond=al, rept=1, abort=9001f
	usracc	str, \reg, \ptr, \inc, \cond, \rept, \abort
	.endm

	.macro	usracc, instr, reg, ptr, inc, cond, rept, abort, t=TUSER()
	.rept	\rept
9999:
	.if	\inc == 1
	\instr\cond\()b\()\t \reg, [\ptr], #\inc
	.elseif	\inc == 4
	\instr\cond\()\t \reg, [\ptr], #\inc
	.else
	.error	"Unsupported inc macro argument"
	.endif

	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort
	.popsection
	.endr
	.endm

usracc str, \reg, \ptr, \inc(=4), \cond(=none,al), \rept(=none,1), \abort(=pop)
	stral reg, [ptr], #inc
	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort

セクション "__ex_table"に、{exceptionが発生するアドレス、返りアドレス}を設定する。
ldscriptにて、同名のセクションを定義、その前後のアドレスをを拾えるようにしてある。
FILE: /kernel/linux-stable/arch/arm/kernel/vmlinux.lds.S
	. = ALIGN(4);
	__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {
		__start___ex_table = .;
#ifdef CONFIG_MMU
		*(__ex_table)
#endif
		__stop___ex_table = .;

テーブルの先頭が変数名として判ったので、コレを探す。
これを参照しているのは、以下の関数。
/* Given an address, look for it in the exception tables. */
const struct exception_table_entry *search_exception_tables(unsigned long addr)
さらにこれを使っているところを探すと、アーキ依存部のpage fault処理部分が見つかる。

FILE: kernel/linux-stable/arch/arm/mm/extable.c
int fixup_exception(struct pt_regs *regs)
{
	const struct exception_table_entry *fixup;

	fixup = search_exception_tables(instruction_pointer(regs));
	if (fixup) {
		regs->ARM_pc = fixup->fixup;
#ifdef CONFIG_THUMB2_KERNEL
		/* Clear the IT state to avoid nasty surprises in the fixup */
		regs->ARM_cpsr &= ~PSR_IT_MASK;
#endif
	}

	return fixup != NULL;
}
さらにさらに、これを使っているところを探すと、
アーキ依存部のpage fault処理部分が見つかる。


FILE: kernel/linux-stable/arch/arm/mm/fault.c
/*
 * Oops.  The kernel tried to access some page that wasn't present.
 */
static void
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
		  struct pt_regs *regs)
{
	/*
	 * Are we prepared to handle this kernel fault?
	 */
	if (fixup_exception(regs))
		return;
ここに来れば faultが発生しても、Oopsを吐かずに指定されたアドレスへ
帰って行くという仕組みになっている。

copy_to_user()に関しては、エラーハンドリングは特に行っていない。
失敗すればコピーを中断して返る、という実装になっている。
そのため、返値はコピーバイト数の残り、ということにしたのであろう。

で、この__do_kernel_fault()はどこから来たのかなぁと見ていくと、
fault handlerのほうへと流れ着くことになる。
ここらへんまできて、ちょっと深みにはまりそうだな、と感じたので
別記事にすることにします。

FILE: kernel/linux-stable/arch/arm/mm/fault.c
static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

linux kernel debug(小技)

2014/07/25linux::debugimport

kernel debug

driver毎のデバッグ出力

pr_debug()は"DEBUG"が定義されているときしか出力されない。
全域で表示させると、UBIなどの大量に吐き出してしまうモノがいるので厄介。
ファイルごとであれば、includeまえに
#define DEBUG (1)
とか、宣言を追加しても良いし、Makefileを編集しても良い。

ブロック単位?であれば、"FILE: driver/of/Makefile"に以下の一行を追加する。
ccflags-y := -DDEBUG
Flattened device tree関連のデバッグ出力が付与されて気持ちよく動作が見える。

参考資料

Documentation/kbuild/makefiles.txt
*1

*1 : このへんとか.. http://lxr.free-electrons.com/source/Documentation/kbuild/makefiles.txt

2013/07/14(日)LInux開発入門(外部URL参照)

初学者のためのLinux起動シーケンス概要

題名はもっともらしいですが、とりあえずURL貼っておくだけで。。。
Linux kernelが起動するまで、と、起動してからユーザランドに渡るまで、
ユーザランドの起動処理、くらいに分類できるかな、と思います。
最後のユーザランド起動処理の手前までは、Androidも一緒でしょう。(未確認)

京都マイクロコンピュータ様のblogリンクが多いのは、それを見てまとめておこうと感じたからです(^^;
他にもネタが出てきたら追加しいこうと思います。オレオレメモってやつですね(ぉ

起動シーケンス(uboot/barebox)

ちょうどこの切り替わりに遭遇していました。
昔からのマイコンは、外部メモリからbootしていましたよね。
ワンタイムROM、UVEPROM、EEPROM・・・と来ていますね。

昨今は原低のため、外部記憶にSD card/NAND Flashのみという構成を取るようになってきているので、
SoC内のMaskROMに埋め込まれているbootloaderから起動してくれます。
このへんの設定は、SoC次第ですし、boot modeでNORからの起動するようなものもいます。
かならずMaskROMからしか起動しないのもきた気がする。
U-bootのデバッグ

ユーザランドの処理(Linux版)

initrd有効の場合は、initramの/linuxrcから起動したはず。
process id=1として、最初に起動されるのは、kernel parameterにinit=で指定したもの、
それがなければ、/sbin/initの順にチェックして、rootfsがmount出来なかったり、init実行ファイルが
なければ、OOPS吐いて止まります。ポーティング作業とか疲れてくるとよく見かけます(謎

ユーザランドの処理(Android版)

Androidのinit処理の流れ。Linuxの普通?のinitとはフローが違うので参考に。
いきなり渡されて流用できるとは思っていなかったからなぁ。こちらと別のblogを参考にさせていただいた記憶が…。
Androidのinit

その他紹介したい記事

「組み込みエンジニアのためのLinux入門 仮想メモリ編」

kernel documentも見ておきましょう。最後はソースコードが正義です。。

LInux開発入門

2013/06/09linux::入門編import
冒頭から逃げを書いておきますが・・・
手探り状態、読者対象が具体的に見えない状況下で記載を始めます。
昨今、マトモな出版物も増えてきているので、需要がないかもしれませんが、少しでも道標になれば幸いです。

Linux OSを用いた開発

以前までは、組み込みといえばuITRON系のRTOSが幅をきかせていましたが、
プロセッサ能力向上・メモリ単価の減少、要求要件の拡大などの要因により、
見直されてきているように思います。
最も影響があるのはNetwork Protocol Stackや、複数プラットフォームで共通的に使えるソフトでしょう。

もちろん、ITRON系のリソースも現役です。少ないROM/RAM、軽量なマイコンでも動くので、住み分けができるでしょう。
より低機能であればOSレスもありますし、畑は広大です。

さておき、ひとことでLinux OS開発といっても、OSはもよとり、コンパイラ・アセンブラ・リンカその他ツール類すべてがOpen Source Softwareを使用します。自らソースコードを元に開発環境や実行環境を用意することができます。MicrosoftのVisual Cのように、コンパイラに対価を支払うのとは対照的ですが、何か問題が起きたとき、起きそうなとき、それを知るための方法を、どのように確保するのか、が課題となります。これを商売にしている会社もありますね。

構成

以下に、クロス開発環境を前提としたソフトの構成例を記します。
開発ツールを買えば、裏ではこれらのソフトを準備してくれたりします。
MSDOSやWindows、商用UNIXでやってきた方には、高い金でコンパイラや統合開発環境を買った記憶があることでしょう。

Linux_SW_001.png


黄色い部分が、ターゲットに載せるべきものです。
右下にある、libraryを除く開発ツールは、必須ではありません。必要に応じてセルフ開発環境やデバッグツールを取り込むのがよいでしょう。

ubuntoやdebianの各arch向けディストリを使う場合は、セルフ環境も必要になりそうな気はします。いや、基本はバイナリインストールになっているしょうか。

ベンダ提供

あえてドライバ・アプリのところへ、ベンダ提供〜を記載しました。
実務で使っていると、必ずしもすべてをopenにしているのでは無いですから、ね。各社の製造ソフトに関しても、GPL伝播していないものは公開義務を負いませんから公開されることは無いです。GPLv2までの場合は、その再配布責任を追うことから、なんらかの手段で製品で使用したtarballなどの提供を受けることができます。
液晶テレビ・レコーダ・STB・そのほかそれっぽい家電の説明書を眺めてみれば、Open Source使用の旨と、その製品で使用したものの入手方法が記載されているでしょう。
尤も、オンラインでさくっと手に入るところもあれば、電話問い合わせ・郵送による配送もあります。
エンドユーザから公開の依頼が頻繁にはないのでしょう、かねぇ?

ライセンスの話が出てきましたが、これだけで深いネタになります。
僕も把握しきれておらず、法的な問題になる場合は判例を調べた上で判断したほうが良いでしょう。
個人で楽しむ分にも、価値が認められ、公開義務を追うような作りをした場合は、開示を迫る人がでてくるかもしれません。

toolchain

いわゆるコンパイル環境、です。ソースコードをターゲットで動くバイナリに変換します。
最近は LLVMというのが注目を集めているようです?

汎用性を考慮されたソフトの配布では、Makefileを利用していることでしょう。
また、autoconfや独自のconfigrationツールを用意して、buildするホスト環境差分を自動的に検出・適切に変更するような手段を提供しています。

しばらくしてわかったこと

何事も過去の経緯を知ることが理解への早道に繋がることがあります。同じようなものがなぜ複数存在するのか、ディストリビューションのそれぞれの目的、本当に全てが無料なのか。製品に搭載するために必要なことは?などなど。いまだにわからない事だらけですが、所詮個人がわかる範囲なんてのはしれてるわけで。

必要なとき、必要なところで、短時間でほしい情報に辿りつけるハナ・直感力を持てるようにしておくのが、対策となろうかと思います。もしくは大まかに畑を分割して、担当範囲を分けておくか、です。ですよね・・・?
後半は妄想でしかないので...