Alpine Linux Porting (一点十?)

为了更理解Alpine initramfs的眉角,今天持续来看一下Alpine的mkinitfs套件里面的 nlplug-findfs 这只helper tool。

然而这边有些先备知识需要说,在Linux的世界观里,为了因应动态插拔装置的状况,有整套相辅相成的框架/机制,分别是 Kobjectuevent

简单来说(我们不提Bus/Class/Device......等等深入的技术细节、以我们的需求只讲概念就好),今天在kernel对应的init stage进行平台init、或着有装置被动态放进系统时,driver在进行probe(),会建立出对应的kobject、挂上一颗Linux在开机时逐步打造出来的kobject树状结构之中。在装置是被动态放进来时,就会发生像某个起床气很重的巫妖王诞生时一样,整个罗德隆森林都在呼喊他的名讳:阿萨 — — 咳咳错棚,该装置对应的kobject建立完会顺便呼叫 kobject_uevent_env ,将这个装置诞生的喜讯通知整个世界,包含了userspace的hotplug管理程序。

在过去、或现代在编译kernel时如果有设定 UEVENT_HELPER_PATH,亦或着打开 UEVENT_HELPER尔後再透过 echo 把管理程序fullpath写到/sys/kernel/uevent_helper 中,那麽kernel就会在uevent产生时,事敝躬亲地去fork、exec该管理程序,并且把装置名称、路径、各式大小资讯排在环境变数中、交给hotplug管理程序自己决定要怎麽建立 /dev/ 底下的char/block dev;而且同时也会在sysfs底下建立一份「留存」。

话说到这里,有个鸡蛋问题,在kernel刚刚累死人地把第一支 process (initramfs时的/init)拉起来时,装置的kobject数早就建完了,可是/dev/底下还是空的、因为当时不会有人可以接uevent(事实上kernel init在建tree时也不会打),那麽、要怎麽建出 /dev 底下那票device file呢?

答案是— —手动戳 sysfs底下的装置的 uevent,然後他就会打了uevent上来给人接了。

长篇大论讲这麽多,其实就是要带出 nlplug-findfs 的目的,一如前几偏一直强调的,Alpine的rootfs在一般预设下,是boot time去抓alpine_repo指向的地方、透过套件管理系统一个一个装套件装出来的,如果是走网路、那就归另外一条,但是如果今天Alpine Init需要的东西、或着alpine_repo指向block dev呢?以一些distro的设计哲学,可能会选择一条道路,那就是直接用 busyboxmdev直接下一道 mdev -s,此时mdev就会去遍历 /sys 、一个一个把 uevent 戳起来,全部按照udev rules拉到 /dev 底下,再看要怎麽处理。

但是Alpine的设计理念是不做多余的事情、也不要建立不必要的档案,最小、最轻量化。Alpine Init呼叫的方式为: nlplug-findfs -p /sbin/mdev -d -n -a /tmp/apkovls

简单意义是,开始去/sys底下,有智慧性地找device node,然後戳它、叫他喷ADD uevent上来,但要拿 /sbin/mdev 来帮我接 uevent (其实 nlplug-findfs 自己有一些handle_uevent的逻辑、不过最小effort嘛,有busybox那就叫它作就好)

static void trigger_uevent_cb(struct recurse_opts *opts, void *data)
{
	size_t oldlen;
	int fd;

	if (!recurse_push(opts, &oldlen, "uevent"))
		return;

	fd = open(opts->path, O_WRONLY | O_CLOEXEC);
	if (fd >= 0) {
		write(fd, "add", 3);
		close(fd);
	}
	recurse_pop(opts, oldlen);
}

在茫茫kobject中这样不断的戳uevent起来ADDD,直到帮我找到某个有档案系统的地方,含有apkovls.tar.gz这个档案

static void scandev_cb(struct recurse_opts *opts, void *data)
{
	struct scandevctx *ctx = data;
	struct ueventconf *conf = ctx->conf;

	if (opts->is_dir) {
		size_t oldlen;
		int ok = 0;
		if (recurse_push(opts, &oldlen, ".boot_repository")) {
			ok = access(opts->path, F_OK) == 0;
			recurse_pop(opts, oldlen);
		}
		if (ok) {
			dbg("added boot repository %s to %s", opts->path, conf->bootrepos);
			append_line(conf->bootrepos, opts->path);
			ctx->found |= FOUND_BOOTREPO;
		}
	} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
		dbg("found apkovl %s", opts->path);
		append_line(conf->apkovls, opts->path);
		ctx->found |= FOUND_APKOVL;
	}
}

然後把他mount好、并且把档案系统的path写到 /tmp/apkovls 之中,而那个apkovl.tar.gz,会带有repo套件库、跟必要的档案,以供後续让Alpine Init拼出rootfs。

下一篇,我们应该会正式进入继续手把手组出可以用的开机流程路上~


<<:  [从0到1] C#小乳牛 练成基础程序逻辑 Day 16 - switch case 条件判断 + break

>>:  Day 29-制作购物车之Redux 4

[ Day 15 ] React Hooks 中的 useState

昨天 Day 14 跟大家介绍了 React Hooks 的基本概念之後,今天就要马上带大家来看第...

Day 20:让我来为您服务.由Koin管理的Android App

Keyword:Koin,Koin Compent 到Day20 使用Koin管理依赖注入显示在An...

[Day3] 驱动OpenGL

今日目标 安装GLAD 画出第一个三角形 GLAD 还记得以前大学的时候,课堂上使用的是glut,当...

django入门(一) — 介绍与安装开发环境

介绍 Django是一个开放原始码的Web应用框架,由Python写成。 采用了MTV(model–...

JS Library 学习笔记:首先当然来试试 jQuery (一)

要撰写前端功能,直接使用JavaScript是绝对可行的,但要更有效率、具有良好开发体验的话,使用L...