1.首先,路由器的启动有多种不同的阶段。
通电后,最先执行的CFE启动,CFE完成基本初始化后,把系统交给linux内核。
而linux内核也有一个初始化过程,比如文件系统啊,内存管理啊,系统资源调度啊,等等。
Linux内核初始化完成后,就执行系统的第1个进程init。本文所讲的启动流程是从init进程开始执行,路由器是如何运行的。
2.从主函数开始。init进程的主函数是 $(ROUTER)/rc/init.c 文件中的init_main( )函数。
注:为什么是init_main而不是main呢?这是因为init进程实际上链接到rc程序的,运行 ls -l /sbin/init就可以看到的。
从init_main( )开始,基本上就2个过程,先执行一些初始化,比如校验某些关键的nvram参数,加载驱动,设置某些内核参数等。
这些主要是由函数sysinit( )完成的。
然后就是信号集初始化,最后是一个for (;;)死循环,这里面完成各种路由器页面的功能,完成之后等待新的信号到来再次执行不同的服务。
(1)sysinit( )部分。
加载驱动,设置内核参数以及某些相关的初始化就不说了,我们主要关注其中校验nvram参数的部分。与之有关的函数有2个,check_bootnv( )和init_nvram( )。
初看起来,这2个函数里貌似都有对nvram参数的操作,那是否可以随便使用呢?
答案是否定的。经过试验就会发现,如果在check_bootnv( )中修正某些nvram变量的默认值,会不起作用,什么原因呢?
而在init_nvram( )修改VLAN参数,在恢复出厂设置后的第1次启动,VLAN并没有生效,什么原因呢?
答案是:
check_bootnv( )是检查启动相关参数的,而init_nvram( )是修改初始化nvram参数的。
check_bootnv( )和底层关系更紧密一些,init_nvram( )和应用层的关系更紧密。
首先理解:系统恢复出厂设置后第1次启动的时候,nvram存储的只有CFE中的那些明文的nvram变量的,$(ROUTER)/nvram/defaults.c中的那个长长的结构体数组中的很多nvram变量都是没有的,所以需要有一个设置默认值的过程。
eval("nvram", "defaults", "--initcheck");
上面这一句就是设置出厂默认nvram值。这一句在check_bootnv( )之后并且在init_nvram( )之前执行。
追踪到$(ROUTER)/nvram/nvram.c文件的defaults_main( )函数,可以发现:
如果是恢复出厂设置后第1次启动,必然是force = 1的(原因自己看代码)。
既然force = 1,那很多nvram变量就被恢复成.../router/nvram/defaults.c中的默认值了。
至于在init_nvram( )中修改VLAN参数首次启动路由器的时候vlan不生效,那是因为驱动的加载是在init_nvram( )之前。
BCM驱动虽然没有开放源码,但是肯定是会读取某些nvram参数的。
结论:
不要在check_bootnv( )中修改defaults.c中的默认变量,最好是放到init_nvram( )去修改。
但是,如果要在init_nvram( )修改和vlan有关的变量以及系统底层驱动有关的变量,那最好是修改完成后执行nvram_commit( )后多重启1次。
(2)for循环部分。
前面的代码好理解,搞一个switch检测不同的信号值,执行不同的动作。难理解的是最后3句:
1 chld_reap(0); /* Periodically reap zombies. */2 check_services();3 sigwait(&sigset, &state);
第1句是回收某些子进程产生的僵尸进程(init进程是整个系统其他进程的祖宗啊)。
第2句是检测下面4个系统服务,如果异常终止,自动重启。现在就不用奇怪dnsmasq进程为啥kill干不掉了^_^。
而第3句sigwait(&sigset, &state)就很费解了^_^。这个可以自己baidu下。baidu上的那些人讲了很多废话^_^。其实在toamto里就是:监测系统信号集的变化(异步监测),如果信号集中的信号发生变化,就执行相应的信号动作,并且进一步执行返回到for循环开头执行,否则就一直休眠在那里等待直到信号集中的信号到来(这个和socket编程的select机制有点类似吧)。
比如dnsmasq异常退出了(比如被你人为干掉了)系统肯定会有SIGCHLD信号产生的,追踪到SIGCHLD信号的处理函数signal(SIGCHLD, handle_reap)--------------raise(SIGALRM),而SIGALRM是在所监视的信号集合中的。
init进程的信号集定义如下:
static int initsigs[] = { SIGHUP, SIGUSR1, SIGUSR2, SIGINT, SIGQUIT, SIGALRM, SIGTERM};
至此toamto得init启动流程分析完毕,主要分析了其中比较难理解的部分,其它部分代码,基本看函数和变量命名就能猜出干啥的^_^。