前几天在FriendlyARM的 mini2440板子上跑Linux的时候碰到了一个很费解的现象,按照教程上说,是可以利用NFS的方式来挂载Host开发机上的根目录来开发的。教程中给的kernel_cmd_line 是:
param set linux_cmd_line "console=ttySAC0 root=/dev/nfs nfsroot=192.168.1.111:/opt/FriendlyARM/mini2440/root_qtopia ip=192.168.1.70:192.168.1.111:192.168.1.111:255.255.255.0:sbc2440.arm9.net:eth0:off"
后面很长的部分主要是ip地址设置和nfs的目录,这里首先碰到的问题是它写了console=ttySAC0。这个不是很理解为什么,ttySAC0不是应该是windows上的超级终端么,我是在Fedora13下面用minicom连接的,系统里面显示的设备名字是/dev/ttyUSB0。但是奇怪的是这个参数是对的,我改成ttyUSB0之后反而启动的时候就收不到串口的数据了。
之后就是正常启动,然后到VFS mount之后突然说 unable to initial console之类的,然后串口就失去控制了,但是板子上的系统还是可以正常启动,而且起来之后我尝试着打开终端然后往串口发送数据居然成功了。也就是说通信驱动之类的问题应该都是好的,只是设备文件名弄的不太对之类的问题。手动建立个这个设备应该就可以了。网上找到有人说在/dev下面 mknod –m 600 console c 5 1就可以了,我本来觉得是不行的,结果死马当活马医的试了试居然行了,我觉得很奇怪。按照我的理解,虽然我在host上面在mknod了这个设备,但是系统启动的时候会重新挂载一个/dev文件系统在/dev下面啊,应该会有个脚本执行了类似于mount –t /dev 之类的命令,那么原来的创建的文件不是应该就看不见了么?不理解是为什么。
我还是想知道这个设备启动的过程中在何时由哪个函数创建的。查了些资料以及内核的代码,大概有以下的理解:
首先真正意义上的Linux开始启动是在bootloader将系统的控制权交给Linux Kernel的时候开始的,也就是在start_kernel()这个函数中。这个函数定义在init/main.c里。然后你会发现在这个函数干了一大堆初始化的事情,其中有一个函数叫做console_init(),
/* * HACK ALERT! This is early. We're enabling the console before * we've done PCI setups etc, and console_init() must be aware of * this. But we do want output early, in case something goes wrong. */ console_init(); if (panic_later) panic(panic_later, panic_param);
这应该是个对我们有用的函数,就像注释里面说的,This is early,我们先记着这回子事,等一会儿再说它,先往后看。
正常人都知道start_kernel()这个函数如果返回了的话导致的唯一结果必定是系统会挂掉,所以它一定会在哪个函数中一直跑下去。然后看来看去发现最后一个函数:rest_init() 函数貌似比较像,
/* Do the rest non-__init'ed, we're now alive */ rest_init();
人家注释里面也写着呢,we’re now alive,看来应该是进到这个里面去了,看看这个函数:
static noinline void __init_refok rest_init(void) __releases(kernel_lock) { int pid;
rcu_scheduler_starting(); /* * We need to spawn init first so that it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */ kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); numa_default_policy(); pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); rcu_read_unlock(); complete(&kthreadd_done); unlock_kernel();
/* * The boot idle thread must execute schedule() * at least once to get things moving: */ init_idle_bootup_task(current); preempt_enable_no_resched(); schedule(); preempt_disable();
/* Call into cpu_idle with preempt disabled */ cpu_idle(); }
我看到这个函数先注意它的最后,这丫调用了cpu_idle(),那说明开始死循环了。果然前面调用了schedule()开启了调度器,开始任务调度。看到这里心里就踏实了,事儿肯定是在这个之前干的,这之后没东西了。于是我们往前看,发现它在这里开始分支了。用的是kernel_thread(),产生两个内核线程,一个是kernel_init(),一个是kthreadd,内核线程的守护线程。我们关注的事情肯定是在kernel_init()里面,再往下找到kernel_init()函数,发现了令人惊喜的东西:
/* Open the /dev/console on the rootfs, this should never fail */ if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0) printk(KERN_WARNING "Warning: unable to open an initial console./n");
我们发现红的那一句就是我们见到的提示信息,然后看了一下大概意思应该是说,如果在这个时候能够成功打开/dev/console这个文件,就没事,否则就有那个提示。也就是说到这一刻,应该已经有/dev/console这个设备创建好了,所以我要关注的一定是在这前面的某个地方。
未完待续,还是没找到到底是哪里创建的。