嵌入式Linux内核的块设备驱动:
对于linux 的根文件系统,目前有三种块设备的驱动可以选择,它们分别是:
a) Blkmem 驱动
b) MTD 驱动
c) RAM
disk 驱动
Blkmem 驱动是专门为嵌入式linux 开发的一种块设备驱动,它是嵌入式linux系统中最为古老和通用的块设备驱动。它原理相对简单但是配置比较复杂,需要根据你即的Flash的分区使用情况来修改代码。当然修改的结果是它可以对一些NOR型的Flash进行读写操作。不过目前支持的Flash类型不够多。如果新加入对一种Flash的支持需要作的工作量比较大。
Linux的MTD驱动是标准Linux的Flash驱动。它支持大量的设备,有足够的功能来定义Flash的分区,进行地址映射等等。使用MTD你可以在一个系统中使用不同类型的Flash。它可以将不同的Flash组合成一个线性的地址让你来使用。
在标准的Linux 2.4内核中MTD有一系列的选项,你可以根据个人系统的需要来选择,定制。
另外一种选择就是RAM disk 驱动。在PC上它经常用于没有硬盘的Linux的启动过程。它和Flash没有直接的关系。不过当Flash上启动的是经过压缩的内核时。RAM disk 可以作为根文件系统。
MTD 驱动提供了对Flash强大的支持,你通过它甚至可以在Flash上运行一个可以读写的真正的文件系统,比如JFFS2。而Blkmem驱动则望尘莫及。
NET4: Linux TCP/IP 1.0 for
NET4.0
调用inet_init [ linux-2.4.22
etipv4Af_inet.c
]时的输出信息, 在启动过程中被socket.c调用到。
IP Protocols: ICMP, UDP, TCP,
IGMP
列出可以支持的IP协议,此处为kernel源代码inet_add_protocol(p);的输出。
在linux启动过程中,都会看到这句的输出。
IP: routing cache hash table of
512 buckets, 4Kbytes
IP路由代码的输出信息。
ip_rt_init [
linux-2.4.22
etipv4\Route.c ],Set
the IP module up,路由缓冲hash表
TCP: Hash tables configured
(established 8192 bind 8192)
TCP协议初始化输出信息。tcp_init [ linux-2.4.22
etipv4Tcp.c ],
NET4: Unix domain sockets
1.0/SMP for Linux NET4.0.
UNIX网络协议信息。
af_unix_init[
linux-2.4.22
etunixAf_unix.c
], 多种连接的一种(IPv4, UNIX domain sockets, IPv6和IrDA). SMP 对称多处理器—Symmetrical Multi Processing,这里主要是指UNIX的一些网络协议.
上面的关于网络的输出信息是在linux启动信息中都会出现的。
加载各种文件系统
cramfs: wrong magic
会出现“cramfs: wrong magic”,别担心这没有什么害处,这个是kernel的书写bug,在2.6中有修改之,它是一个警告信息,用来检查cramfs的superblock超级块的。superblock也是VFS要用到的数据结构。
代码linux-2.4.22fscramfsInode.c:
2.4
cramfs_read_super(。。。)
if
(super.magic != CRAMFS_MAGIC) {
memcpy(&super,
cramfs_read(sb, 512, sizeof(super)), sizeof(super));
if
(super.magic != CRAMFS_MAGIC) {
printk(KERN_ERR
"cramfs: wrong magic
");
goto
out;
}
}
2.6
if (super.magic !=
CRAMFS_MAGIC) {
if
(!silent)
printk(KERN_ERR
"cramfs: wrong magic
");
goto
out;
}
超级块是文件系统的“头部”。它包含文件系统的状态、尺寸和空闲磁盘块等信息。如果损坏了一个文件系统的超级块(例如不小心直接将数据写到了文件系统的超级块分区中),那么系统可能会完全不识别该文件系统,这样也就不能安装它了,即使采用e2fsck 命令也不能处理这个问题。
Cramfs文件系统:
cramfs 是 Linus
Torvalds 本人开发的一个适用于嵌入式系统的小文件系统。由于它是只读的,所以,虽然它采取了 zlib 做压缩,但是它还是可以做到高效的随机读取。 cramfs 不会影响系统读取文件的速度,又是一个高度压缩的文件系统。
我们制作image文件之后,我们还要考虑怎样才能在系统运行的时候,把这个 image 文件 mount 上来,成为一个可用的文件系统。由于这个 image 文件不是一个通常意义上的 block 设备,我们必须采用loopback 设备来完成这一任务,如:
mount -o loop -t cramfs
/usr.img /usr
这样,就可以经由 loopback 设备,把 usr.img 这个 cramfs 的 image 文件 mount 到 /usr 目录上去了。内核中需要对loopback这个设备的支持。
cramfs 的压缩效率一般都能达到将近 50%。
Cramfs通过优化索引节点表的尺寸和除去传统文件系统中文件之间的空间浪费来达到节约空间的目的。它还使用了zlib压缩,实现优于2:1的压缩比例。解压缩过程的系统开销并不是很大,因为Cramfs支持指定单块的解压,而并不必解压缩整个文件。
Cramfs不仅能节省空间,还能避免非正常关机导致的等待fsck或手工进行fsck的麻烦。它通过只读的方式达到这一目的。
RamDisk有三种实现方式:
在Linux中可以将一部分内存mount为分区来使用,通常称之为RamDisk,分为:
Ramdisk, ramfs, tmpfs.
① 第一种就是传统意义上的,可以格式化,然后加载。
这在Linux内核2.0/2.2就已经支持,其不足之处是大小固定,之后不能改变。
为了能够使用Ramdisk,我们在编译内核时须将block device中的Ramdisk支持选上,它下面还有两个选项,一个是设定Ramdisk的大小,默认是4096k;另一个是initrd的支持。
如果对Ramdisk的支持已经编译进内核,我们就可以使用它了:
首先查看一下可用的RamDisk,使用ls
/dev/ram*
首先创建一个目录,比如test,运行mkdir /mnt/test;
然后对/dev/ram0 创建文件系统,运行mke2fs /dev/ram0;
最后挂载 /dev/ram0,运行mount /dev/ram /mnt/test,就可以象对普通硬盘一样对它进行操作了。
② 另两种则是内核2.4才支持的,通过Ramfs或者Tmpfs来实现:
它们不需经过格式化,用起来灵活,其大小随所需要的空间而增加或减少。
Ramfs顾名思义是内存文件系统,它处于虚拟文件系统(VFS)层,而不像ramdisk那样基于虚拟在内存中的其他文件系统(ex2fs)。
因而,它无需格式化,可以创建多个,只要内存足够,在创建时可以指定其最大能使用的内存大小。
如果你的Linux已经将Ramfs编译进内核,你就可以很容易地使用Ramfs了。创建一个目录,加载Ramfs到该目录即可:
# mkdir /testRam
# mount -t ramfs none
/testRAM
缺省情况下,Ramfs被限制最多可使用内存大小的一半。可以通过maxsize(以kbyte为单位)选项来改变。
# mount -t ramfs none /testRAM
-o maxsize=2000 (创建了一个限定最大使用内存为2M的ramdisk)
③ Tmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的Ramdisk,也不同于针对物理内存的Ramfs。
Tmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。
Tmpfs向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。
使用tmpfs,首先你编译内核时得选择"虚拟内存文件系统支持(Virtual memory filesystem support)"。
然后就可以加载tmpfs文件系统了:
# mkdir -p
/mnt/tmpfs
# mount tmpfs /mnt/tmpfs
-t tmpfs
同样可以在加载时指定tmpfs文件系统大小的最大限制:
# mount tmpfs /mnt/tmpfs
-t tmpfs -o size=32m
FAT: bogus logical sector size
21072
具体的文件系统FAT格式。
虚拟逻辑扇区大小为20K,linux-2.4.22fsfatInode.c。
在初始化MS-DOS文件系统时,读MS-DOS文件系统的superblock,函数fat_read_super中输出的上面的信息。
UMSDOS: msdos_read_super
failed, mount aborted.
UMSDOS:一种文件系统,特点容量大但相对而言不大稳定。是Linux 使用的扩展了的DOS文件系统。它在DOS 文件系统下增加了长文件名、 UID/GID、POSIX 权限和特殊文件 (设备、命名管道等)功能,而不牺牲对 DOS 的兼容性。允许一个普通的msdos文件系统用于Linux,而且无须为它建立单独的分区,特别适合早期的硬盘空间不足的硬件条件。
VFS: Mounted root (romfs
filesystem) readonly
虚拟文件系统VFS(Virtual Filesystem Switch)的输出信息。
再次强调一下一个概念。VFS 是一种软件机制,也可称它为 Linux 的文件系统管理者,它是用来管理实际文件系统的挂载点,目的是为了能支持多种文件系统。kernel会先在内存中建立一颗 VFS 目录树,是内存中的一个数据对象,然后在其下挂载rootfs文件系统,还可以挂载其他类型的文件系统到某个子目录上。
Mounted devfs on
/dev
加载devfs设备管理文件系统到dev安装点上。
/dev是我们经常会用到的一个目录。
在2.4的kernel中才有使用到。每次启动时内核会自动挂载devfs。
devfs提供了访问内核设备的命名空间。它并不是建立或更改设备节点,devfs只是为你的特别文件系统进行维护。一般我们可以手工mknod创件设备节点。/dev目录最初是空的,里面特定的文件是在系统启动时、或是加载模组后驱动程序载入时建立的。当模组和驱动程序卸载时,文件就消失了。
Freeing init memory:
72K
释放1号用户进程init所占用的内存。
第三节:加载linux内核完毕,转入cpu_idle进程
系统启动过程中进程情况:
①init进程
一般来说, 系统在跑完 kernel
bootstrapping 内核引导自举后(被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等), 就去运行 init『万process之父』, 有了它, 才能开始跑其他的进程,因此,init进程,它是内核启动的第一个用户级进程,它的进程号总是1。
你可以用进程查看命令来验证
# ps aux
PID Uid VmSize Stat
Command
1 0 SW init
2 0 SW [keventd]
3 0 SWN
[ksoftirqd_CPU0]
4 0 SW [kswapd]
5 0 SW [bdflush]
6 0 SW [kupdated]
7 0 SW [rbwdg]
9 0 SW [mtdblockd]
10 0 SW [khubd]
80 0 SW [loop0]
另外 Linux 有两个 kernel 类的 process 也开始跑了起来,一个是 kflushd/bdflush,另一个是kswapd;
只有这个 init 是完全属于 user 类的进程, 后两者是 kernel假借 process 进程之名挂在进程上。
init有许多很重要的任务,比如象启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。
init 一开始就去读 /etc/inittab (init初始化表),初始化表是按一定格式排列的关于进程运行时的有关信息的。init程序需要读取/etc/inittab文件作为其行为指针。这个 inittab 中对于各个runlevel运行级别要跑哪些 rc 或 spawn 生出什么有很清楚的设定。
一般, 在Linux中初始化脚本在/etc/inittab 文件(或称初始化表)中可以找到关于不同运行级别的描述。inittab是以行为单位的描述性(非执行性)文本,每一个指令行都是固定格式
inittab中有respawn项,但如果一个命令运行时失败了,为了避免重运行的频率太高,init将追踪一个命令重运行了多少次,并且如果重运行的频率太高,它将被延时五分钟后再运行。
② kernel进程
A> 请注意init是1号进程,其他进程id分别是kflushd/ bdflush, kupdate, kpiod and
kswapd。这里有一个要指出的:你会注意到虚拟占用(SIZE)和实际占用(RSS)列都是0,进程怎么会不使用内存呢?
这些进程就是内核守护进程。大部分内核并不显示在进程列表里。守护进程在init之后启动,所以他们和其他进程一样有进程ID,但是他们的代码和数据都存放在内核占有的内存中。在列表中使用中括号来区别与其他进程。
B> 输入和输出是通过内存中的缓冲来完成的,这让事情变得更快,程序的写入会存放在内存缓冲中,然后再一起写入硬盘。守护进程kflushd和kupdate 管理这些工作。kupdate间断的工作(每5秒)来检查是否有写过的缓冲,如过有,就让kflushd把它们写入磁盘。
C> 进程有时候无事可做,当它运行时也不一定需要把其所有的代码和数据都放在内存中。这就意味着我们可以通过把运行中程序不用的内容切换到交换分区来更好的是利用内存。把这些进程数据移入/移出内存通过进程IO管理守护进程kpiod和交换守护进程kswapd,大约每隔1秒,kswapd醒来并检查内存情况。如果在硬盘的东西要读入内存,或者内存可用空间不足,kpiod就会被调用来做移入/移出操作。
D> bdflush -
BUF_DIRTY, 将dirty缓存写回到磁盘的核心守护进程。 对于有许多脏的缓冲区(包含必须同时写到磁盘的数据的缓冲区)的系统提供了动态的响应。它在系统启动的时候作为一个核心线程启动,它叫自己为“kflushd”,而这是你用ps显示系统中的进程的时候你会看得的名字。即定期(5秒)将脏(dirty)缓冲区的内容写入磁盘,以腾出内存;
E>
ksoftirqd_CPUx 是一个死循环, 负责处理软中断的。它是用来对软中断队列进行缓冲处理的进程。当发生软中断时,系统并不急于处理,只是将相应的cpu的中断状态结构中的active 的相应的位,置位,并将相应的处理函数挂到相应的队列,然后等待调度时机来临,再来处理.
ksoftirqd_CPUx是由cpu_raise_softirq()即cpu触发中断,唤醒的内核线程,这涉及到软中断,ksoftirqd的代码参见 [kernel/softirq.c]
F>
keventd,它的任务就是执行 scheduler 调度器队列中的任务,keventd 为它运行的任务提供了可预期的进程上下文。
G>
khubd, 是用来检测USB hub设备的,当usb有动态插拔时,将交由此内核进程来处理。在检测到有hub事件时会有相应的动作(usb_hub_events())
H>
mtdblockd是用来对flash块设备进行写操作的守护进程。
NAND类型的Flash需要MTD(Memory Technology
Devices 内存技术驱动程序)驱动的支持才能被linux所使用。
NAND的特点是不能在芯片内执行(XIP,eXecute In Place),需要把代码读到系统RAM中再执行,传输效率不是最高,最大擦写次数量为一百万次,但写入和擦除的速度很快,擦除单元小,是高数据存储密度的最佳选择。
NAND需要I/O接口,因此使用时需要驱动程序。
I>
loop0 是负责处理loop块设备的(回环设备)。loopback device指的就是拿文件来模拟块设备, 在我们这里,loop设备主要用来处理需要mount到板上的文件系统,类似mount /tmp/rootfs /mnt -o
loop。.我们的实例有:mount -o loop -t cramfs /xxx.bin
/xxx 也就是将xxx.bin这个文件mount到板上来模拟cramfs压缩ram文件系统。loop0进程负责对loop设备进行操作。
loopback设备和其他的块设备的使用方法相同。特别的是,可以在该设备上建立一个文件系统,然后利用mount命令把该系统映射到某个目录下以便访问。这种整个建立在一个普通磁盘文件上的文件系统,就是虚拟文件系统 (virtual file
system)。
总结:
上面的内容是本人为了在实际开发中更加清楚地了解嵌入式linux的启动过程而做的一个总结性的文章。
在对嵌入式linux的启动过程做了一个详细注释后,大家会对涉及到嵌入系统的各个概念有了一个更加明确的认识,并能对嵌入系统的软硬件环境的有关设置更加清楚。当你自己动手结合linux源代码来分析时,将会有一个清楚的全局观。
现在,你如果再回头去看文章前面所列出的启动信息例子中的内容,其中80%的内容,你现在应该能看懂它的来龙去脉了。