再说说那个Ubuntu启动需要等2分钟的事

先来说说这是个什么事情,有时候,我们会在Ubuntu14.04或稍微再早一点的系统启动的时候,看见系统在等待,就是会在屏幕上输出

Waiting for network configuration...

然后又有

Waiting up to 60 more seconds for network configuration...

然后又是

Booting system without full network configuration...

这个时候,一般要过去两分钟,12.04的时候甚至3分钟。这个经常是发生在把一个网络端口从DHCP改成了静态IP地址之后发生的事情。

这个事情网上恶评满满。可以这么说,中文网上众说纷纭,很少有看见靠谱的解释和解决方案的。

本博客以前曾经写过一个解决方案,就是把 /etc/init/rc-sysinit.conf 里面的

start on (filesystem and static-network-up) or failsafe-boot

改成

start on filesystem or failsafe-boot

这个修改,对于普通的网线上网的桌面机器来说,是足够了的。但是,这两天发现,这事并不这么简单,对于服务器,路由器等等的设备来说,这个修改虽然看起来解决了问题,但是事实上,它只是把等待的过程放到后台去运行了。并且,如果你登录的足够快的话,你能看到一个网络设备没完全启动的机器。就是某些网卡,特别是USB接口的网卡,没有被ifup的情况。这对于桌面机器可能并不是个大事,但是对于路由器来说,特别是WIFI网卡走USB的路由器来说,这可就是大事了,因为hostapd的正确启动依赖与WIFI网卡的正常ifup,而dnsmasq的启动又依赖hostapd的正确启动。所以,如果路由器上出这个事情的话,那么你就会遭遇机器启动之后,没有SSID,没有DHCP,没有DNS的三无情况,甚至有时候,连NAT都开不起来。需要等待2分钟后,突然间弹出几行字,这才启动完成。

那么,这件事情是怎么发生的呢?

首先,我们先来搞清楚,这些输出到底从哪里来的。Ubuntu14.04的系统启动过程使用的upstart机制。Upstart是基于事件响应机制的,具体的解释网上非常非常多,这里不再细说了。由于是upstart,所以得先看看/etc/init里头的那些conf文件都是怎么回事。到现在还念叨着操作系统课程的inittab的同学你真的可以洗洗睡了。使用grep程序,很容易找到上面的输出来自/etc/init/failsafe.conf,这个文件里头粗略看一下就知道,从头到尾要等120秒,并且,一定注意,最后部分:

initctl emit --no-wait failsafe-boot

也就是说,如果等完了这120秒的话,failsafe.conf这个任务是会发射出failsafe-boot这个事件的。这个事件会造成什么呢?启动rc-sysinit.conf这个任务。而rc-sysinit.conf这个是对以前基于runlevel的启动过程的兼容层,而hostapd/dnsmasq等等一众老服务都是用rc-sysinit.conf发射的runlevel事件,从/etc/init.d里面启动的,启动的时候根据runlevel在/etc/rc*.d里面顺序运行各个脚本。很容易注意到,rc-sysinit.conf这个任务会在两种条件下被启动:一个是刚说的事件failsafe-boot,一个是同时有filesystem和static-network-up两个事件,而本博客上一次讨论这个问题的时候,实际是把后一个条件简单粗暴的改成了filesystem事件即可激发rc-sysinit任务,而没有等待static-network-up事件,这也就造成了很多依赖静态的网络配置才能生效的服务没办法启动,在/var/log/boot.log里头写出一堆红色的fail的结果。那么static-network-up事件是从哪里来的呢?

这个事件并不是从/etc/init/下的那些conf文件里发射出来的,用grep程序只能找到networking.conf和network-interface.conf这两个任务的执行过程能够发射static-network-up,却无法从代码里找到具体发射事件的地方。

具体分析代码,可以猜到,networking.conf和network-interface.conf,应该是调用了ifup来把所有auto或者allow-hotplug了iface定义全给ifup了。那么,继续顺藤摸瓜下去,看看ifup究竟咋回事。

在man ifup以及man interfaces之后,能明白一件事情,那就是ifup -a这个命令是要up所有标了auto的iface,而ifup –allow hotplug是要up所有标了hotplug的iface。并且,这些iface被up的过程中会调用/etc/network/if-*.d里面的各种脚本,同时会传入各种环境变量参数。

很容易找到 /etc/network/if-up.d/upstart 这个脚本,最后部分有这样的一段

if all_interfaces_up "{MARK_DEV_PREFIX}" &&
    mkdir "{MARK_STATIC_NETWORK_EMITTED}" 2>/dev/null; then
    initctl emit --no-wait static-network-up
fi

也就是说,这里是会发射出static-network-up这个事件的!

仔细阅读这个脚本的前面部分,能够明白,发射这个事件的前提是,所有标了auto的iface都被正确up和配置之后,就会发射出static-network-up事件,同时为了表示这个事件的正确发射,会在/run/network下生成一个static-network-up-emitted目录。而一旦static-network-up事件被发射,那么failsafe.conf这个任务就会终止。因为failsafe.conf里面定义了

start on filesystem and net-device-up IFACE=lo
stop on static-network-up or starting rc-sysinit

换言之,failsafe.conf这个任务的作用是:在filesystem事件发射,并且,net-device-up IFACE=lo事件发射之后,给系统最多2分钟的时间,等待static-network-up事件,而static-network-up事件一旦发出,failsafe.conf就自动终止,而同时rc-sysinit.conf满足条件,开始执行。而如果static-network-up事件一直都没有,那么就在最后发射failsafe-boot事件,强行启动rc-sysinit.conf任务。

然后,问题是:为什么2分钟的时间都等不来static-network-up呢?既然static-network-up事件是从/etc/if-up.d/upstart里发射的,而这个脚本又是在有iface被up了以后才执行的,而ifup -a会尝试up所有auto了iface,那么显然,问题应该出在/etc/network/interfaces里头那些定义了auto的iface定义上。这也恰恰是几乎所有涉及等两分钟问题发生之前做过的事情:因为各种原因,主要是静态IP,改了/etc/network/interfaces文件里的iface定义。

到目前为止,似乎是:绕了一大圈,又回到原点。但是这一大圈的好处是,我们现在知道了整个过程的机制,并且这个过程里头涉及的关键的脚本程序都已经仔细研究过了。我们先把rc-sysinit恢复原状,然后等两分钟,让系统启动,然后看一下/run/network/下的内容,很明显的看到static-network-up事件并没发射,也就是说upstart脚本是异常终止的。那么什么造成了upstart脚本异常终止呢?除了interfaces文件,没有其他的可能了。

现在,我们来仔细看看这个找麻烦的interfaces文件吧。我把这个文件中找了麻烦的那个部分贴出来了。一眼看上去,似乎没啥明显的问题。

# The primary network interface
auto wan
iface wan inet static
      address 192.168.xxx.xxx
      netmask 255.255.255.0
      gateway 192.168.xxx.1
      dns-nameservers 192.168.xxx.xxx

# Lan interface
auto lan
iface lan inet static
      address 10.xxx.1.1
      netmask 255.255.255.0
      gateway 10.xxx.1.1
      dns-nameservers 10.xxx.1.1

# Wifi System
auto wifi-base
allow-hotplug wifi-base
iface wifi-base inet static
      hwaddress xx:xx:xx:xx:xx:xx
      address 172.xx.1.1
      netmask 255.255.255.0
      gateway 172.xx.1.1
      dns-nameservers 172.xx.1.1

auto wifi-guest
allow-hotplug wifi-guest
iface wifi-guest inet static
      address 172.xx.2.1
      netmask 255.255.255.0
      gateway 172.xx.2.1
      dns-nameservers 172.xx.2.1

而有趣的是,这个配置文件里头的wifi-base和wifi-guest都同时标记了auto和hotplug。值得提到的是:这台机器的硬件上使用的是一个RT5572的USB无线网卡,并使用hostapd来虚拟出多个SSID。那么,至少wifi-guest是不应该被auto的,因为ifup -a的时候,rc-sysinit还没被执行,hostapd还没启动,系统里根本就不可能有一个叫wifi-guest的设备!所以wifi-guest只能被标记allow-hotplug,而不能是auto。那么wifi-base能不能被auto呢,这是一个USB网卡,从网上查找一下USB网卡启动的流程就会知道USB网卡启动的时候会有udev的内核插入事件发生,如果这个被标记了auto的话,那么就必须保证ifup的时候内核已经知道了wifi-base的存在,而wifi-base这个名字本身是在/etc/udev/rules.d/70-persitent-net里面改的,也就是说要是内核已经知道了wifi-base的话,那么这个网卡应该已经被udev机制ifup了,而不应该再被up一次,所以,这个也不应该用auto标记。

我们把这两个auto标记行都注释掉,两个#敲完,保存,然后重启。结果就是一秒都没等,系统完全正常启动,所有wifi卡,静态ip设置,全都正常的启动了。/run/network/下也看到了正常发射的static-network-up。

进一步仔细的搜索互联网和RTFM(Read The Fucking Manual)以后,总结如下:interfaces配置里面的auto标记如果使用的是静态IP地址,必须保证这个网卡是接入了网线的有线网卡,或者是配置了DHCP的有线网卡,或者是可以被wpasupplicant 程序自动配置的走PCI的客户端模式的无线网卡。对于其他情况,包括但不限于:要配置成master模式的无线网卡(无论是否走USB),走USB的无线网卡(无论是否master模式),没插网线还要静态IP的有线网卡(插了网线但是网线另一端的设备断电也算没插网线),应该统统标记allow-hotplug,这样才能保证static-network-up事件的正常发射。

此条目发表在软件使用与程序设计分类目录。将固定链接加入收藏夹。