﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-icecream-文章分类-EBOS</title><link>http://www.cppblog.com/icecream/category/10157.html</link><description>我的代码备份！</description><language>zh-cn</language><lastBuildDate>Thu, 09 Apr 2009 17:26:37 GMT</lastBuildDate><pubDate>Thu, 09 Apr 2009 17:26:37 GMT</pubDate><ttl>60</ttl><item><title>嵌入式Linux启动流程分析</title><link>http://www.cppblog.com/icecream/articles/79374.html</link><dc:creator>icecream</dc:creator><author>icecream</author><pubDate>Thu, 09 Apr 2009 12:49:00 GMT</pubDate><guid>http://www.cppblog.com/icecream/articles/79374.html</guid><wfw:comment>http://www.cppblog.com/icecream/comments/79374.html</wfw:comment><comments>http://www.cppblog.com/icecream/articles/79374.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/icecream/comments/commentRss/79374.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/icecream/services/trackbacks/79374.html</trackback:ping><description><![CDATA[<p>转自：<a href="http://www.cnitblog.com/zouzheng/archive/2008/03/07/40649.html">http://www.cnitblog.com/zouzheng/archive/2008/03/07/40649.html</a><br>当Bootloader将控制权交给内核的引导程序时，第一个执行的程序就是head.S，它完成了加载内核的大部分工作；misc.c则提供加载内核所需要的子程序，其中解压内核的子程序是head.S调用的重要程序，另外内核的加载还须知道系统的硬件信息，该硬件信息在hardware.h中定义并被head.S所引用。本系统中内核的启动流程如图1所示。 </p>
<p><img onmousewheel="return bbimg(this)" style="CURSOR: pointer" onclick=javascript:window.open(this.src); alt="" src="http://www.ithao123.com/upimg/allimg/070906/2252080.gif" onload=rsimg(this,300)><br>本系统中，head.S首先配置S3C4510B的系统寄存器SYSCFG、初始化系统的Flash、SDRAM以及总线控制寄存器，将Flash和SDRAM的地址范围分别设置为0x0-0x1fffff和0x1000000-0x1ffffff；根据本系统的功能特点，重新定义了中断优先级以及I/O口的配置；为了提高内核的运行速度，将2M的内核映像文件从Flash拷贝到SDRAM；通过操作一些系统寄存器，进行系统的存储器重映射，将Flash和SDRAM的地址区间分别重映射为0x1000000-0x11fffff和0x0-0xffffff；然后初始化系统堆栈；接着调用misc.c中的函数decompress_kernel，对拷贝到SDRAM的内核映像文件进行解压缩；最后跳转到执行调用内核函数call_kernel，调用call_kernel函数实际上是执行main.c中的start_kernel函数，该函数完成的功能包括处理器结构的初始化、中断的初始化、定时器的初始化、进程相关的初始化以及内存初始化等初始化工作；最后内核创建一个init线程，在该线程中调用init进程，完成系统的启动<br></p>
<p><br>&nbsp;当用户打开PC的电源，BIOS开机自检，按BIOS中设置的启动设备(通常是硬盘)启动，接着启动设备上安装的引导程序lilo或grub开始引导Linux，Linux首先进行内核的引导，接下来执行init程序，init程序调用了rc.sysinit和rc等程序，rc.sysinit和rc当完成系统初始化和运行服务的任务后，返回init；init启动了mingetty后，打开了终端供用户登录系统，用户登录成功后进入了Shell，这样就完成了从开机到登录的整个启动过程。</p>
<p>--&gt;power on--&gt;BIOS--&gt;Lilo/Grub--&gt;Kernerl boot--&gt;init(rc.sysinit, rc) <br>--&gt;mingetty(login)--&gt;Shell----&gt;</p>
<p>下面就将逐一介绍其中几个关键的部分：</p>
<p>　　第一部分：内核的引导(核内引导)</p>
<p>　　Red Hat9.0可以使用lilo或grub等引导程序开始引导Linux系统，当引导程序成功完成引导任务后，Linux从它们手中接管了CPU的控制权，然后CPU就开始执行Linux的核心映象代码，开始了Linux启动过程。这里使用了几个汇编程序来引导Linux，这一步泛及到Linux源代码树中的&#8220;arch/i386/boot&#8221;下的这几个文件：bootsect.S、setup.S、video.S等。</p>
<p>　　其中bootsect.S是生成引导扇区的汇编源码，它完成加载动作后直接跳转到setup.S的程序入口。setup.S的主要功能就是将系统参数（包括内存、磁盘等，由BIOS返回）拷贝到特别内存中，以便以后这些参数被保护模式下的代码来读取。此外，setup.S还将video.S中的代码包含进来，检测和设置显示器和显示模式。最后，setup.S将系统转换到保护模式，并跳转到 0x100000。</p>
<p>　　那么0x100000这个内存地址中存放的是什么代码？而这些代码又是从何而来的呢？</p>
<p>　　0x100000这个内存地址存放的是解压后的内核，因为Red Hat提供的内核包含了众多驱动和功能而显得比较大，所以在内核编译中使用了&#8220;makebzImage&#8221;方式，从而生成压缩过的内核，在RedHat中内核常常被命名为vmlinuz，在Linux的最初引导过程中，是通过"arch/i386/boot/compressed/"中的head.S利用misc.c中定义的decompress_kernel()函数，将内核vmlinuz解压到0x100000的。</p>
<p>　　当CPU跳到0x100000时，将执行"arch/i386/kernel/head.S"中的startup_32，它也是vmlinux的入口，然后就跳转到start_kernel()中去了。start_kernel()是"init/main.c"中的定义的函数，start_kernel()中调用了一系列初始化函数，以完成kernel本身的设置。start_kernel()函数中，做了大量的工作来建立基本的Linux核心环境。如果顺利执行完start_kernel()，则基本的Linux核心环境已经建立起来了。</p>
<p>　　在start_kernel()的最后，通过调用init()函数，系统创建第一个核心线程，启动了init过程。而核心线程init()主要是来进行一些外设初始化的工作的，包括调用do_basic_setup()完成外设及其驱动程序的加载和初始化。并完成文件系统初始化和root文件系统的安装。</p>
<p>　　当do_basic_setup()函数返回init()，init()又打开了/dev/console设备，重定向三个标准的输入输出文件stdin、stdout和stderr到控制台，最后，搜索文件系统中的init程序（或者由init=命令行参数指定的程序），并使用 execve()系统调用加载执行init程序。到此init()函数结束，内核的引导部分也到此结束了，</p>
<p>第二部分：运行init</p>
<p>　　init的进程号是1，从这一点就能看出，init进程是系统所有进程的起点，Linux在完成核内引导以后，就开始运行init程序，。init程序需要读取配置文件/etc/inittab。inittab是一个不可执行的文本文件，它有若干行指令所组成。在Redhat系统中，inittab的内容如下所示(以&#8220;###"开始的中注释为笔者增加的)：</p>
<p>　　#<br>　　# inittab&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; This file describes how the INIT process should set up<br>　　#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; the system in a certain run-level.<br>　　#<br>　　# Author:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Miquel van Smoorenburg, &lt;<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#109;&#105;&#113;&#117;&#101;&#108;&#115;&#64;&#100;&#114;&#105;&#110;&#107;&#101;&#108;&#46;&#110;&#108;&#46;&#109;&#117;&#103;&#110;&#101;&#116;&#46;&#111;&#114;&#103;"><u><font color=#0000ff>miquels@drinkel.nl.mugnet.org</font></u></a>&gt;<br>　　#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Modified for RHS Linux by Marc Ewing and Donnie Barnes<br>　　#</p>
<p>　　# Default runlevel. The runlevels used by RHS are:<br>　　#&nbsp;&nbsp; 0 - halt (Do NOT set initdefault to this)<br>　　#&nbsp;&nbsp; 1 - Single user mode<br>　　#&nbsp;&nbsp; 2 - Multiuser, without NFS (The same as 3, if you do not havenetworking)<br>　　#&nbsp;&nbsp; 3 - Full multiuser mode<br>　　#&nbsp;&nbsp; 4 - unused<br>　　#&nbsp;&nbsp; 5 - X11<br>　　#&nbsp;&nbsp; 6 - reboot (Do NOT set initdefault to this)<br>　　#<br>　　###表示当前缺省运行级别为5(initdefault)；<br>　　id:5:initdefault:</p>
<p>　　###启动时自动执行/etc/rc.d/rc.sysinit脚本(sysinit)<br>　　# System initialization.<br>　　si::sysinit:/etc/rc.d/rc.sysinit</p>
<p>　　l0:0:wait:/etc/rc.d/rc 0<br>　　l1:1:wait:/etc/rc.d/rc 1<br>　　l2:2:wait:/etc/rc.d/rc 2<br>　　l3:3:wait:/etc/rc.d/rc 3<br>　　l4:4:wait:/etc/rc.d/rc 4<br>　　###当运行级别为5时，以5为参数运行/etc/rc.d/rc脚本，init将等待其返回(wait)<br>　　l5:5:wait:/etc/rc.d/rc 5<br>　　l6:6:wait:/etc/rc.d/rc 6</p>
<p>　　###在启动过程中允许按CTRL-ALT-DELETE重启系统<br>　　# Trap CTRL-ALT-DELETE<br>　　ca::ctrlaltdel:/sbin/shutdown -t3 -r now</p>
<p>　　# When our UPS tells us power has failed, assume we have a few minutes<br>　　# of power left.&nbsp; Schedule a shutdown for 2 minutes from now.<br>　　# This does, of course, assume you have powerd installed and your<br>　　# UPS connected and working correctly.<br>　　pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down"</p>
<p>　　# If power was restored before the shutdown kicked in, cancel it.<br>　　pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled"</p>
<p>　　###在2、3、4、5级别上以ttyX为参数执行/sbin/mingetty程序，打开ttyX终端用于用户登录，<br>　　###如果进程退出则再次运行mingetty程序(respawn)<br>　　# Run gettys in standard runlevels<br>　　1:2345:respawn:/sbin/mingetty tty1<br>　　2:2345:respawn:/sbin/mingetty tty2<br>　　3:2345:respawn:/sbin/mingetty tty3<br>　　4:2345:respawn:/sbin/mingetty tty4<br>　　5:2345:respawn:/sbin/mingetty tty5<br>　　6:2345:respawn:/sbin/mingetty tty6</p>
<p>　　###在5级别上运行xdm程序，提供xdm图形方式登录界面，并在退出时重新执行(respawn)<br>　　# Run xdm in runlevel 5<br>　　x:5:respawn:/etc/X11/prefdm -nodaemon</p>
<p>以上面的inittab文件为例，来说明一下inittab的格式。其中以#开始的行是注释行，除了注释行之外，每一行都有以下格式：</p>
<p>　　id:runlevel:action:process</p>
<p>　　对上面各项的详细解释如下：</p>
<p>　　1. id</p>
<p>　　id是指入口标识符，它是一个字符串，对于getty或mingetty等其他login程序项，要求id与tty的编号相同，否则getty程序将不能正常工作。</p>
<p>　　2. runlevel</p>
<p>　　runlevel是init所处于的运行级别的标识，一般使用0－6以及S或s。0、1、6运行级别被系统保留：其中0作为shutdown动作，1作为重启至单用户模式，6为重启；S和s意义相同，表示单用户模式，且无需inittab文件，因此也不在inittab中出现，实际上，进入单用户模式时，init直接在控制台（/dev/console）上运行/sbin/sulogin。在一般的系统实现中，都使用了2、3、4、5几个级别，在Redhat系统中，2表示无NFS支持的多用户模式，3表示完全多用户模式（也是最常用的级别），4保留给用户自定义，5表示XDM图形登录方式。7－9级别也是可以使用的，传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值，以匹配多个运行级别，对大多数action来说，仅当runlevel与当前运行级别匹配成功才会执行。</p>
<p>　　3. action</p>
<p>　　action是描述其后的process的运行方式的。action可取的值包括：initdefault、sysinit、boot、bootwait等：</p>
<p>　　initdefault是一个特殊的action值，用于标识缺省的启动级别；当init由核心激活以后，它将读取inittab中的initdefault项，取得其中的runlevel，并作为当前的运行级别。如果没有inittab文件，或者其中没有initdefault项，init将在控制台上请求输入runlevel。</p>
<p>　　sysinit、boot、bootwait等action将在系统启动时无条件运行，而忽略其中的runlevel。</p>
<p>　　其余的action（不含initdefault）都与某个runlevel相关。各个action的定义在inittab的man手册中有详细的描述。</p>
<p>　　4. process</p>
<p>　　process为具体的执行程序。程序后面可以带参数。</p>
<p>　　第三部分：系统初始化</p>
<p>　　在init的配置文件中有这么一行：</p>
<p>　　si::sysinit:/etc/rc.d/rc.sysinit</p>
<p>　　它调用执行了/etc/rc.d/rc.sysinit，而rc.sysinit是一个bash shell的脚本，它主要是完成一些系统初始化的工作，rc.sysinit是每一个运行级别都要首先运行的重要脚本。它主要完成的工作有：激活交换分区，检查磁盘，加载硬件模块以及其它一些需要优先执行任务。</p>
<p>　　rc.sysinit约有850多行，但是每个单一的功能还是比较简单，而且带有注释，建议有兴趣的用户可以自行阅读自己机器上的该文件，以了解系统初始化所详细情况。由于此文件较长，所以不在本文中列出来，也不做具体的介绍。</p>
<p>　　当rc.sysinit程序执行完毕后，将返回init继续下一步。</p>
<p>第四部分：启动对应运行级别的守护进程</p>
<p>　　在rc.sysinit执行后，将返回init继续其它的动作，通常接下来会执行到/etc/rc.d/rc程序。以运行级别3为例，init将执行配置文件inittab中的以下这行：</p>
<p>　　l5:5:wait:/etc/rc.d/rc 5</p>
<p>　　这一行表示以5为参数运行/etc/rc.d/rc，/etc/rc.d/rc是一个Shell脚本，它接受5作为参数，去执行/etc/rc.d/rc5.d/目录下的所有的rc启动脚本，/etc/rc.d/rc5.d/目录中的这些启动脚本实际上都是一些链接文件，而不是真正的rc启动脚本，真正的rc启动脚本实际上都是放在/etc/rc.d/init.d/目录下。而这些rc启动脚本有着类似的用法，它们一般能接受start、stop、restart、status等参数。</p>
<p>　　/etc/rc.d/rc5.d/中的rc启动脚本通常是K或S开头的链接文件，对于以以S开头的启动脚本，将以start参数来运行。而如果发现存在相应的脚本也存在K打头的链接，而且已经处于运行态了(以/var/lock/subsys/下的文件作为标志)，则将首先以stop为参数停止这些已经启动了的守护进程，然后再重新运行。这样做是为了保证是当init改变运行级别时，所有相关的守护进程都将重启。</p>
<p>　　至于在每个运行级中将运行哪些守护进程，用户可以通过chkconfig或setup中的"System Services"来自行设定。常见的守护进程有：</p>
<p>　　amd：自动安装NFS守护进程<br>　　apmd:高级电源管理守护进程<br>　　arpwatch：记录日志并构建一个在LAN接口上看到的以太网地址和IP地址对数据库<br>　　autofs：自动安装管理进程automount，与NFS相关，依赖于NIS<br>　　crond：Linux下的计划任务的守护进程<br>　　named：DNS服务器<br>　　netfs：安装NFS、Samba和NetWare网络文件系统<br>　　network：激活已配置网络接口的脚本程序<br>　　nfs：打开NFS服务<br>　　portmap：RPC portmap管理器，它管理基于RPC服务的连接<br>　　sendmail：邮件服务器sendmail<br>　　smb：Samba文件共享/打印服务<br>　　syslog：一个让系统引导时起动syslog和klogd系统日志守候进程的脚本<br>　　xfs：X Window字型服务器，为本地和远程X服务器提供字型集<br>　　Xinetd：支持多种网络服务的核心守护进程，可以管理wuftp、sshd、telnet等服务</p>
<p>　　这些守护进程也启动完成了，rc程序也就执行完了，然后又将返回init继续下一步。</p>
<p>第五部分：建立终端</p>
<p>　　rc执行完毕后，返回init。这时基本系统环境已经设置好了，各种守护进程也已经启动了。init接下来会打开6个终端，以便用户登录系统。通过按Alt+Fn(n对应1-6)可以在这6个终端中切换。在inittab中的以下6行就是定义了6个终端：</p>
<p>　　1:2345:respawn:/sbin/mingetty tty1<br>　　2:2345:respawn:/sbin/mingetty tty2<br>　　3:2345:respawn:/sbin/mingetty tty3<br>　　4:2345:respawn:/sbin/mingetty tty4<br>　　5:2345:respawn:/sbin/mingetty tty5<br>　　6:2345:respawn:/sbin/mingetty tty6</p>
<p>　　从上面可以看出在2、3、4、5的运行级别中都将以respawn方式运行mingetty程序，mingetty程序能打开终端、设置模式。同时它会显示一个文本登录界面，这个界面就是我们经常看到的登录界面，在这个登录界面中会提示用户输入用户名，而用户输入的用户将作为参数传给login程序来验证用户的身份。</p>
<p>　　第六部分：登录系统，启动完成</p>
<p>　　对于运行级别为5的图形方式用户来说，他们的登录是通过一个图形化的登录界面。登录成功后可以直接进入KDE、Gnome等窗口管理器。而本文主要讲的还是文本方式登录的情况：</p>
<p>　　当我们看到mingetty的登录界面时，我们就可以输入用户名和密码来登录系统了。</p>
<p>　　Linux的账号验证程序是login，login会接收mingetty传来的用户名作为用户名参数。然后login会对用户名进行分析：如果用户名不是root，且存在/etc/nologin文件，login将输出nologin文件的内容，然后退出。这通常用来系统维护时防止非root用户登录。只有/etc/securetty中登记了的终端才允许root用户登录，如果不存在这个文件，则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制，如果不存在这个文件，则没有其他限制。</p>
<p>　　在分析完用户名后，login将搜索/etc/passwd以及/etc/shadow来验证密码以及设置账户的其它信息，比如：主目录是什么、使用何种shell。如果没有指定主目录，将默认为根目录；如果没有指定shell，将默认为/bin/bash。</p>
<p>　　login程序成功后，会向对应的终端在输出最近一次登录的信息(在/var/log/lastlog中有记录)，并检查用户是否有新邮件(在/usr/spool/mail/的对应用户名目录下)。然后开始设置各种环境变量：对于bash来说，系统首先寻找/etc/profile脚本文件，并执行它；然后如果用户的主目录中存在.bash_profile文件，就执行它，在这些文件中又可能调用了其它配置文件，所有的配置文件执行后后，各种环境变量也设好了，这时会出现大家熟悉的命令行提示符，到此整个启动过程就结束了。</p>
<img src ="http://www.cppblog.com/icecream/aggbug/79374.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/icecream/" target="_blank">icecream</a> 2009-04-09 20:49 <a href="http://www.cppblog.com/icecream/articles/79374.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>嵌入式Linux启动相关代码分析</title><link>http://www.cppblog.com/icecream/articles/79366.html</link><dc:creator>icecream</dc:creator><author>icecream</author><pubDate>Thu, 09 Apr 2009 12:02:00 GMT</pubDate><guid>http://www.cppblog.com/icecream/articles/79366.html</guid><wfw:comment>http://www.cppblog.com/icecream/comments/79366.html</wfw:comment><comments>http://www.cppblog.com/icecream/articles/79366.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/icecream/comments/commentRss/79366.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/icecream/services/trackbacks/79366.html</trackback:ping><description><![CDATA[转自：<a href="http://blog.chinaunix.net/u1/35574/showart_273353.html">http://blog.chinaunix.net/u1/35574/showart_273353.html</a><br>本文通过整理之前研发的一个项目(ARM7TDMI + uCLinux)，分析内核启动过程及需要修改的文件，以供内核移植者参考。整理过程中也同时参考了众多网友的帖子，在此谢过。由于整理过程匆忙，难免错误及讲解的不够清楚之处，请各位网友指正，这里提前谢过。本文分以下部分进行介绍： <br>1. Bootloader及内核解压 <br>2. 内核启动方式介绍 <br>3. 内核启动地址的确定 <br>4. arch/armnommu/kernel/head-armv.S分析 <br>5. start_kernel()函数分析
<p><br><br>1. Bootloader及内核解压 <br>Bootloader将内核加载到内存中，设定一些寄存器，然后将控制权交由内核，该过程中，关闭MMU功能。通常，内核都是以压缩的方式存放，如zImage，这里有两种解压方法： <br>使用内核自解压程序。 <br>arch/arm/boot/compressed/head.S或arch/arm/boot/compressed/head-xxxxx.S <br>arch/arm/boot/compressed/misc.c <br>在Bootloader中增加解压功能。 <br>使用该方法时内核不需要带有自解压功能，而使用Bootloader中的解压程序代替内核自解压程序。其工作过程与内核自解压过程相似：Bootloader把压缩方式的内核解压到内存中，然后跳转到内核入口处开始执行。 <br></p>
<p><br>2. 几种内核启动方式介绍 <br>XIP (EXECUTE IN PLACE) 是指直接从存放代码的位置上启动运行。 <br>2.1 非压缩，非XIP <br>非XIP方式是指在运行之前需对代码进行重定位。该类型的内核以非压缩方式存放在Flash中，启动时由Bootloader加载到内存后运行。 <br>2.2 非压缩，XIP <br>该类型的内核以非压缩格式存放在ROM/Flash中，不需要加载到内存就能运行，Bootloader直接跳转到其存放地址执行。Data段复制和BSS段清零的工作由内核自己完成。这种启动方式常用于内存空间有限的系统中，另外，程序在ROM/Flash中运行的速度相对较慢。 <br>2.3 RAM自解压 <br>压缩格式的内核由开头一段自解压代码和压缩内核数据组成，由于以压缩格式存放，内核只能以非XIP方式运行。RAM自解压过程如下：压缩内核存放于ROM/Flash中，Bootloader启动后加载到内存中的临时空间，然后跳转到压缩内核入口地址执行自解压代码，内核被解压到最终的目的地址然后运行。压缩内核所占据的临时空间随后被Linux回收利用。这种方式的内核在嵌入式产品中较为常见。 <br>2.4 ROM自解压 <br>解压缩代码也能够以XIP的方式在ROM/Flash中运行。ROM自解压过程如下：压缩内核存放在ROM/Flash中，不需要加载到内存就能运行，Bootloader直接跳转到其存放地址执行其自解压代码，将压缩内核解压到最终的目的地址并运行。ROM自解压方式存放的内核解压缩速度慢，而且也不能节省内存空间。 <br></p>
<p><br>3. 内核启动地址的确定 <br>内核自解压方式 <br>Head.S/head-XXX.S获得内核解压后首地址ZREALADDR，然后解压内核，并把解压后的内核放在ZREALADDR的位置上，最后跳转到ZREALADDR地址上，开始真正的内核启动。 <br><br>arch/armnommu/boot/Makefile，定义ZRELADDR和 ZTEXTADDR。ZTEXTADDR是自解压代码的起始地址，如果从内存启动内核，设置为0即可，如果从Rom/Flash启动，则设置ZTEXTADDR为相应的值。ZRELADDR是内核解压缩后的执行地址。 <br>arch/armnommu/boot/compressed/vmlinux.ld,引用LOAD_ADDR和TEXT_START。 <br>arch/armnommu/boot/compressed/Makefile, 通过如下一行： <br>SEDFLAGS = s/TEXT_START/$(ZTEXTADDR)/;s/LOAD_ADDR/$(ZRELADDR)/; <br>使得TEXT_START = ZTEXTADDR，LOAD_ADDR = ZRELADDR。 <br><br>说明： <br>执行完decompress_kernel函数后,代码跳回head.S/head-XXX.S中,检查解压缩之后的kernel起始地址是否紧挨着kernel image。如果是,beq call_kernel,执行解压后的kernel。如果解压缩之后的kernel起始地址不是紧挨着kernel image,则执行relocate,将其拷贝到紧接着kernel image的地方,然后跳转,执行解压后的kernel。 <br><br>Bootloader解压方式 <br>Bootloader把解压后的内核放在内存的TEXTADDR位置上，然后跳转到TEXTADDR位置上，开始内核启动。 <br>arch/armnommu/Makefile，一般设置TEXTADDR为PAGE_OFF+0x8000，如定义为0x00008000, 0xC0008000等。 <br>arch/armnommu/vmlinux.lds，引用TEXTADDR <br></p>
<p><br>4. arch/armnommu/kernel/head-armv.S <br>该文件是内核最先执行的一个文件，包括内核入口ENTRY(stext)到start_kernel间的初始化代码，主要作用是检查CPU ID，Architecture Type，初始化BSS等操作，并跳到start_kernel函数。在执行前，处理器应满足以下状态： <br>r0 - should be 0 <br>r1 - unique architecture number <br>MMU - off <br>I-cache - on or off <br>D-cache &#8211; off <br><br>/* 部分源代码分析 */ <br>/* 内核入口点 */ <br>ENTRY(stext) <br>/* 程序状态，禁止FIQ、IRQ，设定SVC模式 */ <br>mov r0, #F_BIT | I_BIT | MODE_SVC@ make sure svc mode <br>/* 置当前程序状态寄存器 */ <br>msr cpsr_c, r0 @ and all irqs disabled <br>/* 判断CPU类型，查找运行的CPU ID值与Linux编译支持的ID值是否支持 */ <br>bl __lookup_processor_type <br>/* 跳到__error */ <br>teq r10, #0 @ invalid processor? <br>moveq r0, #'p' @ yes, error 'p' <br>beq __error <br>/* 判断体系类型，查看R1寄存器的Architecture Type值是否支持 */ <br>bl __lookup_architecture_type <br>/* 不支持，跳到出错 */ <br>teq r7, #0 @ invalid architecture? <br>moveq r0, #'a' @ yes, error 'a' <br>beq __error <br>/* 创建核心页表 */ <br>bl __create_page_tables <br>adr lr, __ret @ return address <br>add pc, r10, #12 @ initialise processor <br>/* 跳转到start_kernel函数 */ <br>b start_kernel <br><br>__lookup_processor_type这个函数根据芯片的ID从proc.info获取proc_info_list结构，proc_info_list结构定义在include/asm-armnommu/proginfo.h中，该结构的数据定义在arch/armnommu/mm/proc-arm*.S文件中，ARM7TDMI系列芯片的proc_info_list数据定义在arch/armnommu/mm/proc-arm6,7.S文件中。函数__lookup_architecture_type从arch.info获取machine_desc结构，machine_desc结构定义在include/asm-armnommu/mach/arch.h中，针对不同arch的数据定义在arch/armnommu/mach-*/arch.c文件中。 <br>在这里如果知道processor_type和architecture_type,可以直接对相应寄存器进行赋值。 <br></p>
<p><br>5. start_kernel()函数分析 <br>下面对start_kernel()函数及其相关函数进行分析。 <br>5.1 lock_kernel() <br>/* Getting the big kernel lock. <br>* This cannot happen asynchronously, <br>* so we only need to worry about other <br>* CPU's. <br>*/ <br>extern __inline__ void lock_kernel(void) <br>{ <br>if (!++current-&gt;lock_depth) <br>spin_lock(&amp;kernel_flag); <br>} <br>kernel_flag是一个内核大自旋锁，所有进程都通过这个大锁来实现向内核态的迁移。只有获得这个大自旋锁的处理器可以进入内核，如中断处理程序等。在任何一对lock_kernel／unlock_kernel函数里至多可以有一个程序占用CPU。 进程的lock_depth成员初始化为-1，在kerenl/fork.c文件中设置。在它小于0时（恒为 -1），进程不拥有内核锁；当大于或等于0时，进程得到内核锁。 <br><br>5.2 setup_arch() <br>setup_arch()函数做体系相关的初始化工作，函数的定义在arch/armnommu/kernel/setup.c文件中，主要涉及下列主要函数及代码。 <br>5.2.1 setup_processor() <br>该函数主要通过 <br>for (list = &amp;__proc_info_begin; list &lt; &amp;__proc_info_end ; list++) <br>if ((processor_id &amp; list-&gt;cpu_mask) == list-&gt;cpu_val) <br>break; <br>这样一个循环来在.proc.info段中寻找匹配的processor_id，processor_id在head_armv.S文件 <br>中设置。 <br><br>5.2.2 setup_architecture(machine_arch_type) <br>该函数获得体系结构的信息，返回mach-xxx/arch.c 文件中定义的machine结构体的指针，包含以下内容： <br>MACHINE_START (xxx, &#8220;xxx&#8221;) <br>MAINTAINER ("xxx") <br>BOOT_MEM (xxx, xxx, xxx) <br>FIXUP (xxx) <br>MAPIO (xxx) <br>INITIRQ (xxx) <br>MACHINE_END <br><br>5.2.3内存设置代码 <br>if (meminfo.nr_banks == 0) <br>{ <br>meminfo.nr_banks = 1; <br>meminfo.bank[0].start = PHYS_OFFSET; <br>meminfo.bank[0].size = MEM_SIZE; <br>} <br>meminfo结构表明内存情况，是对物理内存结构meminfo的默认初始化。 nr_banks指定内存块的数量，bank指定每块内存的范围，PHYS _OFFSET指定某块内存块的开始地址，MEM_SIZE指定某块内存块长度。PHYS _OFFSET和MEM_SIZE都定义在include/asm-armnommu/arch-XXX/memory.h文件中，其中PHYS _OFFSET是内存的开始地址，MEM_SIZE就是内存的结束地址。这个结构在接下来内存的初始化代码中起重要作用。 <br><br>5.2.4 内核内存空间管理 <br>init_mm.start_code = (unsigned long) &amp;_text; 内核代码段开始 <br>init_mm.end_code = (unsigned long) &amp;_etext; 内核代码段结束 <br>init_mm.end_data = (unsigned long) &amp;_edata; 内核数据段开始 <br>init_mm.brk = (unsigned long) &amp;_end; 内核数据段结束 <br><br>每一个任务都有一个mm_struct结构管理其内存空间，init_mm 是内核的mm_struct。其中设置成员变量* mmap指向自己， 意味着内核只有一个内存管理结构，设置 pgd=swapper_pg_dir， <br>swapper_pg_dir是内核的页目录，ARM体系结构的内核页目录大小定义为16k。init_mm定义了整个内核的内存空间，内核线程属于内核代码，同样使用内核空间，其访问内存空间的权限与内核一样。 <br><br>5.2.5 内存结构初始化 <br>bootmem_init(&amp;meminfo)函数根据meminfo进行内存结构初始化。bootmem_init(&amp;meminfo)函数中调用reserve_node_zero(bootmap_pfn, bootmap_pages) 函数，这个函数的作用是保留一部分内存使之不能被动态分配。这些内存块包括： <br>reserve_bootmem_node(pgdat, __pa(&amp;_stext), &amp;_end - &amp;_stext); /*内核所占用地址空间*/ <br>reserve_bootmem_node(pgdat, bootmap_pfn&lt;&lt;PAGE_SHIFT, bootmap_pages&lt;&lt;PAGE_SHIFT) <br>/*bootmem结构所占用地址空间*/ <br><br>5.2.6 paging_init(&amp;meminfo, mdesc) <br>创建内核页表，映射所有物理内存和IO空间，对于不同的处理器，该函数差别比较大。下面简单描述一下ARM体系结构的存储系统及MMU相关的概念。 <br>在ARM存储系统中，使用内存管理单元(MMU)实现虚拟地址到实际物理地址的映射。利用MMU，可把SDRAM的地址完全映射到0x0起始的一片连续地址空间，而把原来占据这片空间的FLASH或者ROM映射到其他不相冲突的存储空间位置。例如，FLASH的地址从0x0000 0000～0x00FFFFFF，而SDRAM的地址范围是0x3000 0000～0x3lFFFFFF，则可把SDRAM地址映射为0x0000 0000～0xlFFFFFF，而FLASH的地址可以映射到0x9000 0000～0x90FFFFFF(此处地址空间为空闲，未被占用)。映射完成后，如果处理器发生异常，假设依然为IRQ中断，PC指针指向0xl8处的地址，而这个时候PC实际上是从位于物理地址的0x3000 0018处读取指令。通过MMU的映射，则可实现程序完全运行在SDRAM之中。在实际的应用中．可能会把两片不连续的物理地址空间分配给SDRAM。而在操作系统中，习惯于把SDRAM的空间连续起来，方便内存管理，且应用程序申请大块的内存时，操作系统内核也可方便地分配。通过MMU可实现不连续的物理地址空间映射为连续的虚拟地址空间。操作系统内核或者一些比较关键的代码，一般是不希望被用户应用程序访问。通过MMU可以控制地址空间的访问权限，从而保护这些代码不被破坏。 <br>MMU的实现过程，实际上就是一个查表映射的过程。建立页表是实现MMU功能不可缺少的一步。页表位于系统的内存中，页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度即是一个字的长度(在ARM中，一个字的长度被定义为4Bytes)。页表项除完成虚拟地址到物理地址的映射功能之外，还定义了访问权限和缓冲特性等。 <br>MMU的映射分为两种，一级页表的变换和二级页表变换。两者的不同之处就是实现的变换地址空间大小不同。一级页表变换支持1 M大小的存储空间的映射，而二级可以支持64 kB，4 kB和1 kB大小地址空间的映射。 <br><br>动态表(页表)的大小＝表项数＊每个表项所需的位数，即为整个内存空间建立索引表时，需要多大空间存放索引表本身。 <br>表项数＝虚拟地址空间/每页大小 <br>每个表项所需的位数＝Log(实际页表数)+适当控制位数 <br>实际页表数 ＝物理地址空间/每页大小 <br><br>下面分析paging_init（）函数的代码。 <br>在paging_init中分配起始页（即第0页）地址： <br>zero_page = 0xCXXXXXXX <br><br>memtable_init(mi); 如果当前微处理器带有MMU，则为系统内存创建页表；如果当前微处理器不支持MMU，比如ARM7TDMI上移植uCLinux操作系统时，则不需要此类步骤。可以通过如下一个宏定义实现灵活控制，对于带有MMU的微处理器而言，memtable_init(mi)是paging_init()中最重要的函数。 <br>#ifndef CONFIG_UCLINUX <br>/* initialise the page tables. */ <br>memtable_init(mi); <br>&#8230;&#8230;（此处省略若干代码） <br>free_area_init_node(node, pgdat, 0, zone_size, <br>bdata-&gt;node_boot_start, zhole_size); <br>} <br>#else /* 针对不带MMU微处理器 */ <br>{ <br>/*****************************************************/ <br>定义物理内存区域管理 <br>/*****************************************************/ <br>unsigned long zone_size[MAX_NR_ZONES] = {0,0,0}; <br><br>zone_size[ZONE_DMA] = 0; <br>zone_size[ZONE_NORMAL] = (END_MEM - PAGE_OFFSET) &gt;&gt; PAGE_SHIFT; <br><br>free_area_init_node(0, NULL, NULL, zone_size, PAGE_OFFSET, NULL); <br>} <br>#endif <br><br>uCLinux与其它嵌入式Linux最大的区别就是MMU管理这一块，从上面代码就明显可以看到这点区别。下面继续讨论针对带MMU的微处理器的内存管理。 <br>void __init memtable_init(struct meminfo *mi) <br>{ <br>struct map_desc *init_maps, *p, *q; <br>unsigned long address = 0; <br>int i; <br>init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE); <br>/*******************************************************/ <br>其中map_desc定义为： <br>struct map_desc { <br>unsigned long virtual; <br>unsigned long physical; <br>unsigned long length; <br>int domain:4, // 页表的domain <br>prot_read:1, // 读保护标志 <br>prot_write:1, // 写保护标志 <br>cacheable:1, // 是否使用cache <br>bufferable:1, // 是否使用write buffer <br>last:1; //空 <br>};init_maps /* map_desc是区段及其属性的定义 */ <br><br>下面代码对meminfo的区段进行遍历，在嵌入式系统中列举所有可映射的内存，例如32M SDRAM, 4M FLASH等，用meminfo记录这些内存区段。同时填写init_maps 中的各项内容。meminfo结构如下： <br>struct meminfo { <br>int nr_banks; <br>unsigned long end; <br>struct { <br>unsigned long start; <br>unsigned long size; <br>int node; <br>} bank[NR_BANKS]; <br>}; <br>/********************************************************/ <br><br>for (i = 0; i &lt; mi-&gt;nr_banks; i++) <br>{ <br>if (mi-&gt;bank.size == 0) <br>continue; <br><br>p-&gt;physical = mi-&gt;bank.start; <br>p-&gt;virtual = __phys_to_virt(p-&gt;physical); <br>p-&gt;length = mi-&gt;bank.size; <br>p-&gt;domain = DOMAIN_KERNEL; <br>p-&gt;prot_read = 0; <br>p-&gt;prot_write = 1; <br>p-&gt;cacheable = 1; //使用Cache <br>p-&gt;bufferable = 1; //使用write buffer <br>p ++; //下一个区段 <br>} <br><br>/* 如果系统存在FLASH,执行以下代码 */ <br>#ifdef FLUSH_BASE <br>p-&gt;physical = FLUSH_BASE_PHYS; <br>p-&gt;virtual = FLUSH_BASE; <br>p-&gt;length = PGDIR_SIZE; <br>p-&gt;domain = DOMAIN_KERNEL; <br>p-&gt;prot_read = 1; <br>p-&gt;prot_write = 0; <br>p-&gt;cacheable = 1; <br>p-&gt;bufferable = 1; <br><br>p ++; <br>#endif <br><br>/***********************************************************/ <br>接下来的代码是逐个区段建立页表 <br>/***********************************************************/ <br>q = init_maps; <br>do { <br>if (address &lt; q-&gt;virtual || q == p) { <br><br>/*******************************************************************************/ <br>由于内核空间是从某个地址开始，如0xC0000000，所以0xC000 0000 以前的页表项全部清空 <br>clear_mapping在mm-armv.c中定义，其中clear_mapping()是个宏，根据处理器的不同，可以被展开为如下代码 <br>cpu_XXX_set_pmd(((pmd_t *)(((&amp;init_mm )-&gt;pgd+ (( virt) &gt;&gt; 20 )))),((pmd_t){( 0 )})); <br>其中init_mm为内核的mm_struct，pgd指向 swapper_pg_dir，在arch/arm/kernel/init_task.c中定义。cpu_XXX_set_pmd定义在proc_armXXX.S文件中，参见ENTRY(cpu_XXX_set_pmd) 处代码。 <br>/*********************************************************************************/ <br>clear_mapping(address); <br><br>/* 每个表项增加1M */ <br>address += PGDIR_SIZE; <br>} else { <br><br>/* 构建内存页表 */ <br>create_mapping(q); <br><br>address = q-&gt;virtual + q-&gt;length; <br>address = (address + PGDIR_SIZE - 1) &amp; PGDIR_MASK; <br><br>q ++; <br>} <br>} while (address != 0); <br><br>/ * create_mapping函数也在mm-armv.c中定义 */ <br>static void __init create_mapping(struct map_desc *md) <br>{ <br>unsigned long virt, length; <br>int prot_sect, prot_pte; <br>long off; <br><br>/*******************************************************************************/ <br>大部分应用中均采用1级section模式的地址映射，一个section的大小为1M，也就是说从逻辑地址到物理地址的转变是这样的一个过程： <br>一个32位的地址，高12位决定了该地址在页表中的index，这个index的内容决定了该逻辑section对应的物理section； 低20位决定了该地址在section中的偏移（index）。例如：从0x0～0xFFFFFFFF的地址空间总共可以分成0x1000（4K）个section（每个section大小为1M），页表中每项的大小为32个bit，因此页表的大小为0x4000（16K）。 <br><br>每个页表项的内容如下: <br>bit: 31 20 19 12 11 10 9 8 5 4 3 2 1 0 <br>content: Section对应的物理地址 NULL AP 0 Domain 1 C B 1 0 <br>最低两位（10）是section分页的标识。 <br>AP：Access Permission，区分只读、读写、SVC＆其它模式。 <br>Domain：每个section都属于某个Domain，每个Domain的属性由寄存器控制。一般都只要包含两个Domain，一个可访问地址空间； 另一个不可访问地址空间。 <br>C、B：这两位决定了该section的cache＆write buffer属性，这与该段的用途(RO or RW)有密切关系。不同的用途要做不同的设置。 <br><br>C B 具体含义 <br>0 0 无cache，无写缓冲，任何对memory的读写都反映到总线上。对 memory 的操作过程中CPU需要等待。 <br>0 1 无cache，有写缓冲，读操作直接反映到总线上。写操作CPU将数据写入到写缓冲后继续运行，由写缓冲进行写回操作。 <br>1 0 有cache，写通模式，读操作首先考虑cache hit；写操作时直接将数据写入写缓冲，如果同时出现cache hit，那么也更新cache。 <br>1 1 有cache，写回模式，读操作首先考虑cache hit；写操作也首先考虑cache hit。 <br><br>由于ARM中section表项的权限位和page表项的位置不同， 以下代码根据struct map_desc 中的保护标志，分别计算页表项中的AP, Domain和CB标志位。 <br>/*******************************************************************************/ <br><br>prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY | <br>(md-&gt;prot_read ? L_PTE_USER : 0) | <br>(md-&gt;prot_write ? L_PTE_WRITE : 0) | <br>(md-&gt;cacheable ? L_PTE_CACHEABLE : 0) | <br>(md-&gt;bufferable ? L_PTE_BUFFERABLE : 0); <br><br>prot_sect = PMD_TYPE_SECT | PMD_DOMAIN(md-&gt;domain) | <br>(md-&gt;prot_read ? PMD_SECT_AP_READ : 0) | <br>(md-&gt;prot_write ? PMD_SECT_AP_WRITE : 0) | <br>(md-&gt;cacheable ? PMD_SECT_CACHEABLE : 0) | <br>(md-&gt;bufferable ? PMD_SECT_BUFFERABLE : 0); <br><br>/********************************************************************/ <br>设置虚拟地址，偏移地址和内存length <br>/********************************************************************/ <br>virt = md-&gt;virtual; <br>off = md-&gt;physical - virt; <br>length = md-&gt;length; <br><br><br>/********************************************************************/ <br>建立虚拟地址到物理地址的映射 <br>/********************************************************************/ <br>while ((virt &amp; 0xfffff || (virt + off) &amp; 0xfffff) &amp;&amp; length &gt;= PAGE_SIZE) { <br>alloc_init_page(virt, virt + off, md-&gt;domain, prot_pte); <br><br>virt += PAGE_SIZE; <br>length -= PAGE_SIZE; <br>} <br><br>while (length &gt;= PGDIR_SIZE) { <br>alloc_init_section(virt, virt + off, prot_sect); <br><br>virt += PGDIR_SIZE; <br>length -= PGDIR_SIZE; <br>} <br><br>while (length &gt;= PAGE_SIZE) { <br>alloc_init_page(virt, virt + off, md-&gt;domain, prot_pte); <br><br>virt += PAGE_SIZE; <br>length -= PAGE_SIZE; <br>} <br>/*************************************************************************/ <br>create_mapping的作用是设置虚地址virt 到物理地址virt + off_set的映射页目录和页表。 <br>/*************************************************************************/ <br><br>/* 映射中断向量表区域 */ <br>init_maps-&gt;physical = virt_to_phys(init_maps); <br>init_maps-&gt;virtual = vectors_base(); <br>init_maps-&gt;length = PAGE_SIZE; <br>init_maps-&gt;domain = DOMAIN_USER; <br>init_maps-&gt;prot_read = 0; <br>init_maps-&gt;prot_write = 0; <br>init_maps-&gt;cacheable = 1; <br>init_maps-&gt;bufferable = 0; <br><br>create_mapping(init_maps); <br><br>中断向量表的虚地址init_maps，是用alloc_bootmem_low_pages分配的，通常是在PAGE_OFF+0x8000前面的某一页， vectors_base()是个宏，ARM规定中断向量表的地址只能是0或0xFFFF0000，所以上述代码映射一页到0或0xFFFF0000，中断处理程序中的部分代码也被拷贝到这一页中。 <br><br>5.3 parse_options() <br>分析由内核引导程序发送给内核的启动选项，在初始化过程中按照某些选项运行，并将剩余部分传送给init进程。这些选项可能已经存储在配置文件中，也可能是由用户在系统启动时敲入的。但内核并不关心这些，这些细节都是内核引导程序关注的内容，嵌入式系统更是如此。 <br><br>5.4 trap_init() <br>这个函数用来做体系相关的中断处理的初始化，在该函数中调用__trap_init((void *)vectors_base())函数将exception vector设置到vectors_base开始的地址上。__trap_init函数位于entry-armv.S文件中，对于ARM处理器，共有复位、未定义指令、SWI、预取终止、数据终止、IRQ和FIQ几种方式。SWI主要用来实现系统调用，而产生了IRQ之后，通过exception vector进入中断处理过程，执行do_IRQ函数。 <br>armnommu的trap_init（）函数在arch/armnommu/kernel/traps.c文件中。vectors_base是写中断向量的开始地址，在include/asm-armnommu/proc-armv/system.h文件中设置，地址为0或0XFFFF0000。 <br><br>ENTRY(__trap_init) <br>stmfd sp!, {r4 - r6, lr} <br><br>mrs r1, cpsr @ code from 2.0.38 <br>bic r1, r1, #MODE_MASK @ clear mode bits /* 设置svc模式，disable IRQ,FIQ */ <br>orr r1, r1, #I_BIT|F_BIT|MODE_SVC @ set SVC mode, disable IRQ,FIQ <br>msr cpsr, r1 <br><br>adr r1, .LCvectors @ set up the vectors <br>ldmia r1, {r1, r2, r3, r4, r5, r6, ip, lr} <br>stmia r0, {r1, r2, r3, r4, r5, r6, ip, lr} /* 拷贝异常向量 */ <br><br>add r2, r0, #0x200 <br>adr r0, __stubs_start @ copy stubs to 0x200 <br>adr r1, __stubs_end <br>1: ldr r3, [r0], #4 <br>str r3, [r2], #4 <br>cmp r0, r1 <br>blt 1b <br>LOADREGS(fd, sp!, {r4 - r6, pc}) <br>__stubs_start到__stubs_end的地址中包含了异常处理的代码，因此拷贝到vectors_base+0x200的位置上。 <br><br>5.5 init_IRQ() <br>void __init init_IRQ(void) <br>{ <br>extern void init_dma(void); <br>int irq; <br><br>for (irq = 0; irq &lt; NR_IRQS; irq++) { <br>irq_desc[irq].probe_ok = 0; <br>irq_desc[irq].valid = 0; <br>irq_desc[irq].noautoenable = 0; <br>irq_desc[irq].mask_ack = dummy_mask_unmask_irq; <br>irq_desc[irq].mask = dummy_mask_unmask_irq; <br>irq_desc[irq].unmask = dummy_mask_unmask_irq; <br>} <br>CSR_WRITE(AIC_MDCR, 0x7FFFE); /* disable all interrupts */ <br>CSR_WRITE(CAHCNF,0x0);/*Close Cache*/ <br>CSR_WRITE(CAHCON,0x87);/*Flush Cache*/ <br>while(CSR_READ(CAHCON)!=0); <br>CSR_WRITE(CAHCNF,0x7);/*Open Cache*/ <br><br>init_arch_irq(); <br>init_dma(); <br>} <br>这个函数用来做体系相关的irq处理的初始化，irq_desc数组是用来描述IRQ的请求队列，每一个中断号分配一个irq_desc结构，组成了一个数组。NR_IRQS代表中断数目，这里只是对中断结构irq_desc进行了初始化。在默认的初始化完成后调用初始化函数init_arch_irq，先执行arch/armnommu/kernel/irq-arch.c文件中的函数genarch_init_irq()，然后就执行include/asm-armnommu/arch-xxxx/irq.h中的inline函数irq_init_irq，在这里对irq_desc进行了实质的初始化。其中mask用阻塞中断；unmask用来取消阻塞；mask_ack的作用是阻塞中断，同时还回应ack给硬件表示这个中断已经被处理了，否则硬件将再次发生同一个中断。这里，不是所有硬件需要这个ack回应，所以很多时候mask_ack与mask用的是同一个函数。 <br>接下来执行init_dma（）函数，如果不支持DMA，可以设置include/asm-armnommu/arch-xxxx/dma.h中的MAX_DMA_CHANNELS为0，这样在arch/armnommu/kernel/dma.c文件中会根据这个定义使用不同的函数。 <br><br>5.6 sched_init() <br>初始化系统调度进程，主要对定时器机制和时钟中断的Bottom Half的初始化函数进行设置。与时间相关的初始化过程主要有两步：（1）调用init_timervecs()函数初始化内核定时器机制；（2）调用init_bh()函数将BH向量TIMER_BH、TQUEUE_BH和IMMEDIATE_BH所对应的BH函数分别设置成timer_bh()、tqueue_bh()和immediate_bh()函数 <br><br>5.7 softirq_init() <br>内核的软中断机制初始化函数。调用tasklet_init初始化tasklet_struct结构，软中断的个数为32个。用于bh的tasklet_struct结构调用tasklet_init()以后，它们的函数指针func全都指向bh_action()。bh_action就是tasklet实现bh机制的代码，但此时具体的bh函数还没有指定。 <br><br>HI_SOFTIRQ用于实现bottom half，TASKLET_SOFTIRQ用于公共的tasklet。 <br><br>open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); /* 初始化公共的tasklet_struct要用到的软中断 */ <br>open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); /* 初始化tasklet_struct实现的bottom half调用 */ <br><br>这里顺便讲一下软中断的执行函数do_softirq()。 <br>软中断服务不允许在一个硬中断服务程序内部执行，也不允许在一个软中断服务程序内部执行，所以通过in_interrupt()加以检查。h-&gt;action 就是串行化执行软中断，当bh 的tasklet_struct 链入的时候，就能在这里执行，在bh里重新锁定了所有CPU，导致一个时间只有一个CPU可以执行bh 函数，但是do_softirq()是可以在多CPU 上同时执行的。而每个tasklet_struct在一个时间上是不会出现在两个CPU上的。另外，只有当Linux初始化完成开启中断后，中断系统才可以开始工作。 <br><br>5.8 time_init() <br>这个函数用来做体系相关的timer的初始化，armnommu的在arch/armnommu/kernel/time.c。这里调用了在include/asm-armnommu/arch-xxxx/time.h中的inline函数setup_timer，setup_timer（）函数的设计与硬件设计紧密相关，主要是根据硬件设计情况设置时钟中断号和时钟频率等。 <br>void __inline__ setup_timer (void) <br>{ <br>/*----- disable timer -----*/ <br>CSR_WRITE(TCR0, xxx); <br><br>CSR_WRITE (AIC_SCR7, xxx); /* setting priority level to high */ <br>/* timer 0: 100 ticks/sec */ <br>CSR_WRITE(TICR0, xxx); <br><br>timer_irq.handler = xxxxxx_timer_interrupt; <br>setup_arm_irq(IRQ_TIMER, &amp;timer_irq); /* IRQ_TIMER is the interrupt number */ <br><br>INT_ENABLE(IRQ_TIMER); <br>/* Clear interrupt flag */ <br>CSR_WRITE(TISR, xxx); <br><br>/* enable timer */ <br>CSR_WRITE(TCR0, xxx); <br>} <br><br>5.9 console_init() <br>控制台初始化。控制台也是一种驱动程序，由于其特殊性，提前到该处完成初始化，主要是为了提前看到输出信息，据此判断内核运行情况。很多嵌入式Linux操作系统由于没有在/dev目录下正确配置console设备，造成启动时发生诸如unable to open an initial console的错误。 <br><br>/*******************************************************************************/ <br>init_modules()函数到smp_init()函数之间的代码一般不需要作修改， <br>如果平台具有特殊性，也只需对相关函数进行必要修改。 <br>这里简单注明了一下各个函数的功能，以便了解。 <br>/*******************************************************************************/ <br>5.10 init_modules() <br>模块初始化。如果编译内核时使能该选项，则内核支持模块化加载/卸载功能 <br><br>5.11 kmem_cache_init() <br>内核Cache初始化。 <br><br>5.12 sti() <br>使能中断，这里开始，中断系统开始正常工作。 <br><br>5.13 calibrate_delay() <br>近似计算BogoMIPS数字的内核函数。作为第一次估算，calibrate_delay计算出在每一秒内执行多少次__delay循环，也就是每个定时器滴答（timer tick）―百分之一秒内延时循环可以执行多少次。这种计算只是一种估算，结果并不能精确到纳秒，但这个数字供内核使用已经足够精确了。 <br>BogoMIPS的数字由内核计算并在系统初始化的时候打印。它近似的给出了每秒钟CPU可以执行一个短延迟循环的次数。在内核中，这个结果主要用于需要等待非常短周期的设备驱动程序――例如，等待几微秒并查看设备的某些信息是否已经可用。 <br>计算一个定时器滴答内可以执行多少次循环需要在滴答开始时就开始计数，或者应该尽可能与它接近。全局变量jiffies中存储了从内核开始保持跟踪时间开始到现在已经经过的定时器滴答数， jiffies保持异步更新，在一个中断内——每秒一百次，内核暂时挂起正在处理的内容，更新变量，然后继续刚才的工作。 <br><br>5.14 mem_init() <br>内存初始化。本函数通过内存碎片的重组等方法标记当前剩余内存, 设置内存上下界和页表项初始值。 <br><br>5.15 kmem_cache_sizes_init() <br>内核内存管理器的初始化，也就是初始化cache和SLAB分配机制。 <br><br>5.16 pgtable_cache_init() <br>页表cache初始化。 <br><br>5.17 fork_init() <br>这里根据硬件的内存情况，如果计算出的max_threads数量太大，可以自行定义。 <br><br>5.18 proc_caches_init(); <br>为proc文件系统创建高速缓冲 <br><br>5.19 vfs_caches_init(num_physpages); <br>为VFS创建SLAB高速缓冲 <br><br>5.20 buffer_init(num_physpages); <br>初始化buffer <br><br>5.21 page_cache_init(num_physpages); <br>页缓冲初始化 <br><br>5.22 signals_init(); <br>创建信号队列高速缓冲 <br><br>5.23 proc_root_init(); <br>在内存中创建包括根结点在内的所有节点 <br><br>5.24 check_bugs(); <br>检查与处理器相关的bug <br><br>5.25 smp_init(); <br><br>5.26 rest_init(); 此函数调用kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)函数。 <br><br>5.26.1 kernel_thread()函数分析 <br>这里调用了arch/armnommu/kernel/process.c中的函数kernel_thread，kernel_thread函数中通过__syscall(clone) 创建新线程。__syscall(clone)函数参见armnommu/kernel目录下的entry-common.S文件。 <br><br>5.26.2 init()完成下列功能： <br>Init()函数通过kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL)的回调函数执行，完成下列功能。 <br>do_basic_setup() <br>在该函数里，sock_init()函数进行网络相关的初始化，占用相当多的内存，如果所开发系统不支持网络功能，可以把该函数的执行注释掉。 <br>do_initcalls()实现驱动的初始化, 这里需要与vmlinux.lds联系起来看才能明白其中奥妙。&#61514; <br>static void __init do_initcalls(void) <br>{ <br>　　initcall_t *call; <br><br>　　call = &amp;__initcall_start; <br>　　do { <br>　　　(*call)(); <br>　　　call++; <br>　　} while (call &lt; &amp;__initcall_end); <br><br>　　/* Make sure there is no pending stuff from the initcall sequence */ <br>　　flush_scheduled_tasks(); <br>} <br><br>查看 /arch/i386/vmlinux.lds，其中有一段代码 <br>　__initcall_start = .; <br>　.initcall.init : { *(.initcall.init) } <br>　__initcall_end = .; <br>其含义是__initcall_start指向代码节.initcall.init的节首，而__initcall_end指向.initcall.init的节尾。 <br><br>do_initcalls所作的是系统中有关驱动部分的初始化工作，那么这些函数指针数据是怎样放到了.initcall.init节呢？在include/linux/init.h文件中有如下3个定义： <br>1. #define __init_call　　 __attribute__ ((unused,__section__ (".initcall.init"))) <br>__attribute__的含义就是构建一个在.initcall.init节的指向初始函数的指针。 <br>2. #define __initcall(fn) static initcall_t __initcall_##fn __init_call = fn <br>##意思就是在可变参数使用宏定义的时候构建一个变量名称为所指向的函数的名称，并且在前面加上__initcall_ <br>3. #define module_init(x) __initcall(x); <br>很多驱动中都有类似module_init(usb_init)的代码，通过该宏定义逐层解释存放到.initcall.int节中。 <br><br>blkmem相关的修改(do_initcalls()初始化驱动时执行此代码) <br>在blkmem_init()函数中，调用了blk_init_queue()函数，blk_init_queue()函数调用了blk_init_free_list()函数，blk_init_free_list()函数又调用了blk_grow_request_list()函数，在这个函数中会kmem_cache_alloc出nr_requests个request结构体。 <br>这里如果nr_requests的值太大，则将占用过多的内存，将造成硬件内存不够，因此可以根据实际情况将其替换成了较小的值，比如32、16等。 <br><br>free_initmem <br>这个函数在arch/armnommu/mm/init.c文件中，其作用就是对init节的释放，也可以通过修改代码指定为不释放。 <br><br>5.26.3 init执行过程 <br>在内核引导结束并启动init之后，系统就转入用户态的运行，在这之后创建的一切进程，都是在用户态进行。这里先要清楚一个概念：就是init进程虽然是从内核开始的，即在前面所讲的init/main.c中的init()函数在启动后就已经是一个核心线程，但在转到执行init程序（如/sbin/init）之后，内核中的init()就变成了/sbin/init程序，状态也转变成了用户态，也就是说核心线程变成了一个普通的进程。这样一来，内核中的init函数实际上只是用户态init进程的入口，它在执行execve("/sbin/init",argv_init,envp_init)时改变成为一个普通的用户进程。这也就是exec函数的乾坤大挪移法，在exec函数调用其他程序时，当前进程被其他进程&#8220;灵魂附体&#8221;。 <br>　　除此之外，它们的代码来源也有差别，内核中的init()函数的源代码在/init/main.c中，是内核的一部分。而/sbin/init程序的源代码是应用程序。 <br>init程序启动之后，要完成以下任务：检查文件系统，启动各种后台服务进程，最后为每个终端和虚拟控制台启动一个getty进程供用户登录。由于所有其它用户进程都是由init派生的，因此它又是其它一切用户进程的父进程。 <br>　　init进程启动后，按照/etc/inittab的内容进程系统设置。很多嵌入式系统用的是BusyBox的init，它与一般所使用的init不一样，会先执行/etc/init.d/rcS而非/etc/rc.d/rc.sysinit。 <br></p>
<p><br>小结： <br>本想多整理一些相关资料，无奈又要开始新项目的奔波，start_kernel()函数也刚好差不多讲完了，分析的不是很深入，希望对嵌入式Linux移植的网友们有一些帮助。最后列举下面几处未整理的知识点，有兴趣的网友可作进一步探讨。 <br>text.init和data.init说明 <br>__init标示符在gcc编译器中指定将该函数置于内核的特定区域。在内核完成自身初始化之后，就试图释放这个特定区域。实际上，内核中存在两个这样的区域，.text.init和.data.init――第一个是代码初始化使用的，另外一个是数据初始化使用的。另外也可以看到__initfunc和__initdata标志，前者和__init类似，标志初始化专用代码，后者则标志初始化专用数据。 <br>System.map内核符号表 <br>irq的处理过程 <br>Linux内核调度过程&nbsp;&nbsp;</p>
<img src ="http://www.cppblog.com/icecream/aggbug/79366.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/icecream/" target="_blank">icecream</a> 2009-04-09 20:02 <a href="http://www.cppblog.com/icecream/articles/79366.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>arm 嵌入式LINUX启动过程</title><link>http://www.cppblog.com/icecream/articles/79363.html</link><dc:creator>icecream</dc:creator><author>icecream</author><pubDate>Thu, 09 Apr 2009 11:54:00 GMT</pubDate><guid>http://www.cppblog.com/icecream/articles/79363.html</guid><wfw:comment>http://www.cppblog.com/icecream/comments/79363.html</wfw:comment><comments>http://www.cppblog.com/icecream/articles/79363.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/icecream/comments/commentRss/79363.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/icecream/services/trackbacks/79363.html</trackback:ping><description><![CDATA[首先，porting&nbsp;linux的时候要规划内存影像，如小弟的系统有64m&nbsp;SDRAM,&nbsp;<br>地址从0x&nbsp;0800&nbsp;0000&nbsp;-0x0bff&nbsp;ffff,32m&nbsp;flash,地址从0x0c00&nbsp;0000-0x0dff&nbsp;ffff.&nbsp;<br>规划如下：bootloader,&nbsp;linux&nbsp;kernel,&nbsp;rootdisk放在flash里。&nbsp;<br>具体从&nbsp;0x0c00&nbsp;0000开始的第一个1M放bootloader，&nbsp;<br>0x0c10&nbsp;0000开始的2m放linux&nbsp;kernel,从&nbsp;0x0c30&nbsp;0000开始都给rootdisk。&nbsp;<br><br><br><br>启动：&nbsp;<br>首先，启动后arm920T将地址0x0c00&nbsp;0000映射到0（可通过跳线设置），&nbsp;<br>实际上从0x0c00&nbsp;0000启动，进入我们的bootloader，但由于flash速度慢，&nbsp;<br>所以bootloader前面有一小段程序把bootloader拷贝到SDRAM&nbsp;中的0x0AFE0100，&nbsp;<br>再从0x&nbsp;0800&nbsp;0000&nbsp;运行bootloader，我们叫这段小程序为flashloader,&nbsp;<br>flashloader必须要首先初始化SDRAM,不然往那放那些东东：&nbsp;<br><br><br><br><br><br>.equ&nbsp;SOURCE,&nbsp;0x0C000100&nbsp;bootloader的存放地址&nbsp;<br>.equ&nbsp;TARGET,&nbsp;0x0AFE0100&nbsp;目标地址&nbsp;<br>.equ&nbsp;SDCTL0,&nbsp;0x221000&nbsp;SDRAM控制器寄存器&nbsp;<br>//&nbsp;size&nbsp;is&nbsp;stored&nbsp;in&nbsp;location&nbsp;0x0C0000FC&nbsp;<br><br><br>.global&nbsp;_start&nbsp;<br>_start:&nbsp;//入口点&nbsp;<br><br><br>//;***************************************&nbsp;<br>//;*&nbsp;Init&nbsp;SDRAM&nbsp;<br>//;***************************************&nbsp;<br><br><br><br>//&nbsp;***************&nbsp;<br>//&nbsp;*&nbsp;SDRAM&nbsp;<br>//&nbsp;***************&nbsp;<br><br><br>LDR&nbsp;r1,&nbsp;=SDCTL0&nbsp;//&nbsp;<br><br><br>//&nbsp;&nbsp;Set&nbsp;Precharge&nbsp;Command&nbsp;<br>LDR&nbsp;r3,&nbsp;=0x92120200&nbsp;<br>//ldr&nbsp;r3,=0x92120251&nbsp;<br>STR&nbsp;r3,&nbsp;[r1]&nbsp;<br><br><br>//&nbsp;&nbsp;Issue&nbsp;Precharge&nbsp;All&nbsp;Commad&nbsp;<br>LDR&nbsp;r3,&nbsp;=0x8200000&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br><br><br>//&nbsp;&nbsp;Set&nbsp;AutoRefresh&nbsp;Command&nbsp;<br>LDR&nbsp;r3,&nbsp;=0xA2120200&nbsp;<br>STR&nbsp;r3,&nbsp;[r1]&nbsp;<br><br><br>//&nbsp;&nbsp;Issue&nbsp;AutoRefresh&nbsp;Command&nbsp;<br>LDR&nbsp;r3,&nbsp;=0x8000000&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br><br><br>//&nbsp;&nbsp;Set&nbsp;Mode&nbsp;Register&nbsp;<br>LDR&nbsp;r3,&nbsp;=0xB2120200&nbsp;<br>STR&nbsp;r3,&nbsp;[r1]&nbsp;<br><br><br>//&nbsp;&nbsp;Issue&nbsp;Mode&nbsp;Register&nbsp;Command&nbsp;<br>LDR&nbsp;r3,&nbsp;=0x08111800&nbsp;//;&nbsp;Mode&nbsp;Register&nbsp;value&nbsp;<br>LDR&nbsp;r2,&nbsp;[r3]&nbsp;<br><br><br>//&nbsp;&nbsp;Set&nbsp;Normal&nbsp;Mode&nbsp;<br>LDR&nbsp;r3,&nbsp;=0x82124200&nbsp;<br>STR&nbsp;r3,&nbsp;[r1]&nbsp;<br><br><br>//;***************************************&nbsp;<br>//;*&nbsp;End&nbsp;of&nbsp;SDRAM&nbsp;and&nbsp;SyncFlash&nbsp;Init&nbsp;*&nbsp;<br>//;***************************************&nbsp;<br><br><br><br>//&nbsp;copy&nbsp;code&nbsp;from&nbsp;FLASH&nbsp;to&nbsp;SRAM&nbsp;<br><br><br>_CopyCodes:&nbsp;<br>ldr&nbsp;r0,=SOURCE&nbsp;<br>ldr&nbsp;r1,=TARGET&nbsp;<br>sub&nbsp;r3,r0,#4&nbsp;<br>ldr&nbsp;r2,[r3]&nbsp;<br><br><br>_CopyLoop:&nbsp;<br>ldr&nbsp;r3,[r0]&nbsp;<br>str&nbsp;r3,[r1]&nbsp;<br>add&nbsp;r0,r0,#4&nbsp;<br>add&nbsp;r1,r1,#4&nbsp;<br>sub&nbsp;r2,r2,#4&nbsp;<br>teq&nbsp;r2,#0&nbsp;<br>beq&nbsp;_EndCopy&nbsp;<br>b&nbsp;_CopyLoop&nbsp;<br><br><br>_EndCopy:&nbsp;<br>ldr&nbsp;r0,=TARGET&nbsp;<br>mov&nbsp;pc,r0&nbsp;<br><br><br><br>上回书说到flashloader把bootloader&nbsp;load到0x0AFE0100，&nbsp;然回跳了过去，&nbsp;<br>其实0x0AFE0100&nbsp;就是烧在flash&nbsp;0x0C000100中的真正的bootloader:&nbsp;<br><br><br>bootloader&nbsp;有几个文件组成，先是START.s，也是唯一的一个汇编程序，其余的都是C写成的，START.s主要初始化堆栈：&nbsp;<br><br><br>_start:&nbsp;<br>ldr&nbsp;r1,=StackInit&nbsp;<br>ldr&nbsp;sp,[r1]&nbsp;<br>b&nbsp;main&nbsp;<br>//此处我们跳到了C代码的main函数，当C代码执行完后，还要调用&nbsp;<br>//下面的JumpToKernel0x跳到LINXU&nbsp;kernel运行&nbsp;<br><br><br><br>.equ&nbsp;StackInitvalue,&nbsp;__end_data+0x1000&nbsp;//&nbsp;4K&nbsp;__end_data在连结脚本中指定&nbsp;<br><br><br>StackInit:&nbsp;<br>.long&nbsp;StackInitvalue&nbsp;<br><br><br>.global&nbsp;JumpToKernel&nbsp;<br><br><br>JumpToKernel:&nbsp;<br>//&nbsp;jump&nbsp;to&nbsp;the&nbsp;copy&nbsp;code&nbsp;(get&nbsp;the&nbsp;arguments&nbsp;right)&nbsp;<br>mov&nbsp;pc,&nbsp;r0&nbsp;<br><br><br>.global&nbsp;JumpToKernel0x&nbsp;<br>//&nbsp;r0&nbsp;=&nbsp;jump&nbsp;address&nbsp;<br>//&nbsp;r1-r4&nbsp;=&nbsp;arguments&nbsp;to&nbsp;use&nbsp;(these&nbsp;get&nbsp;shifted)&nbsp;<br>JumpToKernel0x:&nbsp;<br>//&nbsp;jump&nbsp;to&nbsp;the&nbsp;copy&nbsp;code&nbsp;(get&nbsp;the&nbsp;arguments&nbsp;right)&nbsp;<br>mov&nbsp;r8,&nbsp;r0&nbsp;<br>mov&nbsp;r0,&nbsp;r1&nbsp;<br>mov&nbsp;r1,&nbsp;r2&nbsp;<br>mov&nbsp;r2,&nbsp;r3&nbsp;<br>mov&nbsp;r3,&nbsp;r4&nbsp;<br>mov&nbsp;pc,&nbsp;r8&nbsp;<br>.section&nbsp;".data.boot"&nbsp;<br>.section&nbsp;".bss.boot"&nbsp;<br><br><br>下面让我们看看bootloader的c代码干了些什么。main函数比较长，让我们分段慢慢看。&nbsp;<br><br><br>int&nbsp;main()&nbsp;<br>{&nbsp;<br>U32&nbsp;*pSource,&nbsp;*pDestin,&nbsp;count;&nbsp;<br>U8&nbsp;countDown,&nbsp;bootOption;&nbsp;<br>U32&nbsp;delayCount;&nbsp;<br>U32&nbsp;fileSize,&nbsp;i;&nbsp;<br>char&nbsp;c;&nbsp;<br>char&nbsp;*pCmdLine;&nbsp;<br>char&nbsp;*pMem;&nbsp;<br><br><br>init();&nbsp;//初始化FLASH控制器和CPU时钟&nbsp;<br><br><br>EUARTinit();&nbsp;//串口初始化&nbsp;<br>EUARTputString("\n\nDBMX1&nbsp;Linux&nbsp;Bootloader&nbsp;ver&nbsp;0.2.0\n");&nbsp;<br>EUARTputString("Copyright&nbsp;(C)&nbsp;2002&nbsp;Motorola&nbsp;Ltd.\n\n");&nbsp;<br>EUARTputString((U8&nbsp;*)cmdLine);&nbsp;<br>EUARTputString("\n\n");&nbsp;<br><br><br>EUARTputString("Press&nbsp;any&nbsp;key&nbsp;for&nbsp;alternate&nbsp;boot-up&nbsp;options&nbsp;...&nbsp;");&nbsp;<br><br><br><br><br><br>小弟的bootloader主要干这么几件事:init();&nbsp;初始化硬件，打印一些信息和提供一些操作选项：&nbsp;<br>0.&nbsp;Program&nbsp;bootloader&nbsp;image&nbsp;<br>1.&nbsp;Program&nbsp;kernel&nbsp;image&nbsp;<br>2.&nbsp;Program&nbsp;root-disk&nbsp;image&nbsp;<br>3.&nbsp;Download&nbsp;kernel&nbsp;and&nbsp;boot&nbsp;from&nbsp;RAM&nbsp;<br>4.&nbsp;Download&nbsp;kernel&nbsp;and&nbsp;boot&nbsp;with&nbsp;ver&nbsp;0.1.x&nbsp;bootloader&nbsp;format&nbsp;<br>5.&nbsp;Boot&nbsp;a&nbsp;ver0.1.x&nbsp;kernel&nbsp;<br>6.&nbsp;Boot&nbsp;with&nbsp;a&nbsp;different&nbsp;command&nbsp;line&nbsp;<br><br><br>也就是说，可以在bootloader里选择重新下载kernel,rootdisk并写入flash,&nbsp;<br>下载的方法是用usb连接，10m的rootdisk也就刷的一下。关于usb下载的讨论请参看先前的贴子&#8220;为arm开发平台增加usb下载接口&#8220;。&nbsp;<br>如果不选，直接回车，就开始把整个linux的内核拷贝到SDRAM中运行。&nbsp;<br><br><br>列位看官，可能有人要问，在flashloader中不是已经初始化过sdram控制器了吗？怎么init();&nbsp;中还要初始化呢，各位有所不知，小弟用的是syncflash，&nbsp;<br>可以直接使用sdram控制器的接口，切记：在flash中运行的代码是不能初始化连接flash的sdram控制器的，不然绝对死掉了。所以，当程序在flash中运行的时候，去初始化sdram,而现在在sdram中运行，可放心大胆地初始化flash了，主要是设定字宽，行列延时，因为缺省都是最大的。&nbsp;<br><br><br>另外，如果列位看官的cpu有足够的片内ram，完全可以先把bootloader放在片内ram，干完一切后再跳到LINUX，小弟着也是不得已而为之啊。&nbsp;<br><br><br><br>如果直接输入回车，进入kernel拷贝工作：&nbsp;<br><br><br><br>EUARTputString("Copying&nbsp;kernel&nbsp;from&nbsp;Flash&nbsp;to&nbsp;RAM&nbsp;...\n");&nbsp;<br>count&nbsp;=&nbsp;0x200000;&nbsp;//&nbsp;2&nbsp;Mbytes&nbsp;<br>pSource&nbsp;=&nbsp;(U32&nbsp;*)0x0C100000;&nbsp;<br>pDestin&nbsp;=&nbsp;(U32&nbsp;*)0x08008000;&nbsp;<br>do&nbsp;<br>{&nbsp;<br>*(pDestin++)&nbsp;=&nbsp;*(pSource++);&nbsp;<br>count&nbsp;-=&nbsp;4;&nbsp;<br>}&nbsp;while&nbsp;(count&nbsp;&gt;&nbsp;0);&nbsp;<br>}&nbsp;<br><br><br>EUARTputString("Booting&nbsp;kernel&nbsp;...\n\n");&nbsp;<br><br><br>这一段没有什么可说的，运行完后kernel就在0x08008000了，至于为什么要&nbsp;<br>空出0x8000的一段，主要是放kelnel的一些全局数据结构，如内核页表，arm的页目录要有16k大。&nbsp;<br><br><br>我们知道，linux内核启动的时候可以传入参数，如在PC上，如果使用LILO,&nbsp;<br>当出现LILO：，我们可以输入root=/dev/hda1.或mem="128M"等指定文件系统的设备或内存大小，在嵌入式系统上，参数的传入是要靠bootloader完成的，&nbsp;<br><br><br>pMem&nbsp;=&nbsp;(char&nbsp;*)0x083FF000;&nbsp;//参数字符串的目标存放地址&nbsp;<br>pCmdLine&nbsp;=&nbsp;(char&nbsp;*)&amp;cmdLine;&nbsp;//定义的静态字符串&nbsp;<br>while&nbsp;((*(pMem++)=*(pCmdLine++))&nbsp;!=&nbsp;0);//拷贝&nbsp;<br><br><br>JumpToKernel((void&nbsp;*)0x8008000,&nbsp;0x083FF000)&nbsp;//跳转到内核&nbsp;<br><br><br>return&nbsp;(0);&nbsp;<br>JumpToKernel在前文中的start.S定义过：&nbsp;<br><br><br>JumpToKernel:&nbsp;<br>//&nbsp;jump&nbsp;to&nbsp;the&nbsp;copy&nbsp;code&nbsp;(get&nbsp;the&nbsp;arguments&nbsp;right)&nbsp;<br>mov&nbsp;pc,&nbsp;r0&nbsp;<br><br><br>.global&nbsp;JumpToKernel0x&nbsp;<br>//&nbsp;r0&nbsp;=&nbsp;jump&nbsp;address&nbsp;<br>//&nbsp;r1&nbsp;=&nbsp;arguments&nbsp;to&nbsp;use&nbsp;(these&nbsp;get&nbsp;shifted)&nbsp;<br><br><br>由于arm-GCC的c参数调用的顺序是从左到右R0开始，所以R0是KERNKEL的地址，&nbsp;<br>r1是参数字符串的地址：&nbsp;<br><br><br><br>到此为止，为linux引导做的准备工作就结束了，下一回我们就正式进入linux的代码。&nbsp;<br><br><br><br>好，从本节开始，我们走过了bootloader的漫长征途，开始进入linux的内核：&nbsp;<br>说实话，linux宝典的确高深莫测，洋人花了十几年修炼，各种内功心法层处不穷。有些地方反复推敲也领悟不了其中奥妙，炼不到第九重啊。。&nbsp;<br><br><br>linux的入口是一段汇编代码，用于基本的硬件设置和建立临时页表，对于&nbsp;<br>ARM&nbsp;LINUX是&nbsp;linux/arch/arm/kernle/head-armv.S,&nbsp;走！&nbsp;<br><br><br>#if&nbsp;defined(CONFIG_MX1)&nbsp;<br>mov&nbsp;r1,&nbsp;#MACH_TYPE_MX1&nbsp;<br>#endif&nbsp;<br><br><br>这第一句话好像就让人看不懂，好像葵花宝典开头的八个字：欲练神功。。。。&nbsp;<br><br><br>那来的MACH_TYPE_MX1？其实，在head-armv.S&nbsp;<br>中的一项重要工作就是设置内核的临时页表，不然mmu开起来也玩不转，但是内核怎么知道如何映射内存呢？linux的内核将映射到虚地址0xCxxx&nbsp;xxxx处，但他怎么知道把哪一片ram映射过去呢？&nbsp;<br><br><br>因为不通的系统有不通的内存影像，所以，LINUX约定，内核代码开始的时候，&nbsp;<br>R1放的是系统目标平台的代号，对于一些常见的，标准的平台，内核已经提供了支持，只要在编译的时候选中就行了，例如对X86平台，内核是从物理地址1M开始映射的。如果老兄是自己攒的平台，只好麻烦你自己写了。&nbsp;<br><br><br>小弟拿人钱财，与人消灾，用的是摩托的MX1,只好自己写了，定义了#MACH_TYPE_MX1，当然，还要写一个描述平台的数据结构：&nbsp;<br><br><br>MACHINE_START(MX1ADS,&nbsp;"Motorola&nbsp;MX1ADS")&nbsp;<br>MAINTAINER("SPS&nbsp;Motorola")&nbsp;<br><br><br>BOOT_MEM(0x08000000,&nbsp;0x00200000,&nbsp;0xf0200000)&nbsp;<br><br><br>FIXUP(mx1ads_fixup)&nbsp;<br>MAPIO(mx1ads_map_io)&nbsp;<br>INITIRQ(mx1ads_init_irq)&nbsp;<br>MACHINE_END&nbsp;<br><br><br>看起来怪怪的，但现在大家只要知道他定义了基本的内存映象：RAM从0x08000000开始，i/o空间从0x00200000开始，i/o空间映射到虚拟地址空间&nbsp;<br>0xf0200000开始处。摩托的芯片i/o和内存是统一编址的。&nbsp;<br>其他的项，在下面的初始化过程中会逐个介绍到。&nbsp;<br><br><br>好了好了，再看下面的指令：&nbsp;<br><br><br><br>mov&nbsp;r0,&nbsp;#F_BIT&nbsp;|&nbsp;I_BIT&nbsp;|&nbsp;MODE_SVC&nbsp;@&nbsp;make&nbsp;sure&nbsp;svc&nbsp;mode&nbsp;//设置为SVC模式，允许中断和快速中断&nbsp;<br>//此处设定系统的工作状态，arm有7种状态&nbsp;<br>//每种状态有自己的堆栈&nbsp;<br><br><br>msr&nbsp;cpsr_c,&nbsp;r0&nbsp;@&nbsp;and&nbsp;all&nbsp;irqs&nbsp;diabled&nbsp;<br>bl&nbsp;__lookup_processor_type&nbsp;<br><br><br>//定义处理器相关信息，如value,&nbsp;mask,&nbsp;mmuflags，&nbsp;<br>//放在proc.info段中&nbsp;<br>//__lookup_processor_type&nbsp;取得这些信息，在下面&nbsp;<br>//__lookup_architecture_type&nbsp;中用&nbsp;<br><br><br>这一段是查询处理器的种类，大家知道arm有arm7,&nbsp;arm9等类型，如何区分呢？&nbsp;<br>在arm协处理器中有一个只读寄存器，存放处理器相关信息。__lookup_processor_type将返回如下的结构：&nbsp;<br><br><br>__arm920_proc_inf&nbsp;<br>.long&nbsp;0x41009200&nbsp;//CPU&nbsp;id&nbsp;<br>.long&nbsp;0xff00fff0&nbsp;//cpu&nbsp;mask&nbsp;<br>.long&nbsp;0x00000c1e&nbsp;@&nbsp;mmuflags&nbsp;<br>b&nbsp;__arm920_setup&nbsp;<br>.long&nbsp;cpu_arch_name&nbsp;<br>.long&nbsp;cpu_elf_name&nbsp;<br>.long&nbsp;HWCAP_SWP&nbsp;|&nbsp;HWCAP_HALF&nbsp;|&nbsp;HWCAP_26BIT&nbsp;<br>.long&nbsp;cpu_arm920_info&nbsp;<br>.long&nbsp;arm920_processor_functions&nbsp;<br><br><br><br>第一项是CPU&nbsp;id，将与协处理器中读出的id作比较，其余的都是与处理器相关的&nbsp;<br>信息，到下面初始化的过程中自然会用到。。&nbsp;<br><br><br>查询到了处理器类型和系统的内存映像后就要进入初始化过程中比较关键的一步了，开始设置mmu，但首先要设置一个临时的内核页表，映射4m的内存，这在初始化过程中是足够了：&nbsp;<br><br><br>//r5=0800&nbsp;0000&nbsp;ram起始地址&nbsp;r6=0020&nbsp;0000&nbsp;io地址，r7=f020&nbsp;0000&nbsp;虚io&nbsp;<br>teq&nbsp;r7,&nbsp;#0&nbsp;@&nbsp;invalid&nbsp;architecture?&nbsp;<br>moveq&nbsp;r0,&nbsp;#'a'&nbsp;@&nbsp;yes,&nbsp;error&nbsp;'a'&nbsp;<br>beq&nbsp;__error&nbsp;<br>bl&nbsp;__create_page_tables&nbsp;<br><br><br>其中__create_page_tables为：&nbsp;<br>__create_page_tables:&nbsp;<br>pgtbl&nbsp;r4&nbsp;<br>//r4=0800&nbsp;4000&nbsp;临时页表的起始地址&nbsp;<br>//r5=0800&nbsp;0000,&nbsp;ram的起始地址&nbsp;<br>//r6=0020&nbsp;0000,&nbsp;i/o寄存器空间的起始地址&nbsp;<br>//r7=0000&nbsp;3c08&nbsp;<br>//r8=0000&nbsp;0c1e&nbsp;<br><br><br>//the&nbsp;page&nbsp;table&nbsp;in&nbsp;0800&nbsp;4000&nbsp;is&nbsp;just&nbsp;temp&nbsp;base&nbsp;page,&nbsp;when&nbsp;init_task's&nbsp;sweaper_page_dir&nbsp;ready,&nbsp;<br>//&nbsp;the&nbsp;temp&nbsp;page&nbsp;will&nbsp;be&nbsp;useless&nbsp;<br>//&nbsp;the&nbsp;high&nbsp;12&nbsp;bit&nbsp;of&nbsp;virtual&nbsp;address&nbsp;is&nbsp;base&nbsp;table&nbsp;index,&nbsp;so&nbsp;we&nbsp;need&nbsp;4kx4&nbsp;=&nbsp;16k&nbsp;temp&nbsp;base&nbsp;page,&nbsp;<br><br><br>mov&nbsp;r0,&nbsp;r4&nbsp;<br>mov&nbsp;r3,&nbsp;#0&nbsp;<br>add&nbsp;r2,&nbsp;r0,&nbsp;#0x4000&nbsp;@&nbsp;16k&nbsp;of&nbsp;page&nbsp;table&nbsp;<br>1:&nbsp;str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;@&nbsp;Clear&nbsp;page&nbsp;table&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;<br>teq&nbsp;r0,&nbsp;r2&nbsp;<br>bne&nbsp;1b&nbsp;<br>/*&nbsp;<br>*&nbsp;Create&nbsp;identity&nbsp;mapping&nbsp;for&nbsp;first&nbsp;MB&nbsp;of&nbsp;kernel.&nbsp;<br>*&nbsp;This&nbsp;is&nbsp;marked&nbsp;cacheable&nbsp;and&nbsp;bufferable.&nbsp;<br>*&nbsp;<br>*&nbsp;The&nbsp;identity&nbsp;mapping&nbsp;will&nbsp;be&nbsp;removed&nbsp;by&nbsp;<br>*/&nbsp;<br><br><br>//&nbsp;由于linux编译的地址是0xC0008000,load的地址是0x08008000,我们需要将虚地址0xC0008000映射到0800800一段&nbsp;<br>//同时，由于部分代码也要直接访问0x08008000，所以0x08008000对应的表项也要填充&nbsp;<br>//&nbsp;页表中的表象为section,AP="11"表示任何模式下可访问，domain为0。&nbsp;<br>add&nbsp;r3,&nbsp;r8,&nbsp;r5&nbsp;@&nbsp;mmuflags&nbsp;+&nbsp;start&nbsp;of&nbsp;RAM&nbsp;<br>//r3=0800&nbsp;0c1e&nbsp;<br>add&nbsp;r0,&nbsp;r4,&nbsp;r5,&nbsp;lsr&nbsp;#18&nbsp;<br>//r0=0800&nbsp;4200&nbsp;<br>str&nbsp;r3,&nbsp;[r0]&nbsp;@&nbsp;identity&nbsp;mapping&nbsp;<br>//*0800&nbsp;4200&nbsp;=&nbsp;0800&nbsp;0c1e&nbsp;0x200表象&nbsp;对应的是0800&nbsp;0000&nbsp;的1m&nbsp;<br>/*&nbsp;<br>*&nbsp;Now&nbsp;setup&nbsp;the&nbsp;pagetables&nbsp;for&nbsp;our&nbsp;kernel&nbsp;direct&nbsp;<br>*&nbsp;mapped&nbsp;region.&nbsp;We&nbsp;round&nbsp;TEXTADDR&nbsp;down&nbsp;to&nbsp;the&nbsp;<br>*&nbsp;nearest&nbsp;megabyte&nbsp;boundary.&nbsp;<br>*/&nbsp;<br>//下面是映射4M&nbsp;<br><br><br>add&nbsp;r0,&nbsp;r4,&nbsp;#(TEXTADDR&nbsp;&amp;&nbsp;0xfff00000)&nbsp;&gt;&gt;&nbsp;18&nbsp;@&nbsp;start&nbsp;of&nbsp;kernel&nbsp;<br>//r0&nbsp;=&nbsp;r4+&nbsp;0x3000&nbsp;=&nbsp;0800&nbsp;4000&nbsp;+&nbsp;3000&nbsp;=&nbsp;0800&nbsp;7000&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;@&nbsp;PAGE_OFFSET&nbsp;+&nbsp;0MB&nbsp;<br>//*0800&nbsp;7004&nbsp;=&nbsp;0800&nbsp;0c1e&nbsp;<br>add&nbsp;r3,&nbsp;r3,&nbsp;#1&nbsp;&lt;&lt;&nbsp;20&nbsp;<br>//r3=0810&nbsp;0c1e&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;@&nbsp;PAGE_OFFSET&nbsp;+&nbsp;1MB&nbsp;<br>//*0800&nbsp;7008&nbsp;=&nbsp;0810&nbsp;0c1e&nbsp;<br>add&nbsp;r3,&nbsp;r3,&nbsp;#1&nbsp;&lt;&lt;&nbsp;20&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;<br>//*0800&nbsp;700c&nbsp;=&nbsp;0820&nbsp;0c1e&nbsp;@&nbsp;PAGE_OFFSET&nbsp;+&nbsp;2MB&nbsp;<br>add&nbsp;r3,&nbsp;r3,&nbsp;#1&nbsp;&lt;&lt;&nbsp;20&nbsp;<br>str&nbsp;r3,&nbsp;[r0],&nbsp;#4&nbsp;@&nbsp;PAGE_OFFSET&nbsp;+&nbsp;3MB&nbsp;<br>//*0800&nbsp;7010&nbsp;=&nbsp;0830&nbsp;0c1e&nbsp;<br><br><br><br><br><br>bic&nbsp;r8,&nbsp;r8,&nbsp;#0x0c&nbsp;@&nbsp;turn&nbsp;off&nbsp;cacheable&nbsp;<br>//r8=0000&nbsp;0c12&nbsp;@&nbsp;and&nbsp;bufferable&nbsp;bits&nbsp;<br>mov&nbsp;pc,&nbsp;lr&nbsp;//子程序返回。&nbsp;<br>下一回就要开始打开mmu的操作了&nbsp;<br><br><br>上回书讲到已经设置好了内核的页表，然后要跳转到__arm920_setup，&nbsp;<br>这个函数在arch/arm/mm/proc-arm929.s&nbsp;<br><br><br>__arm920_setup:&nbsp;<br>mov&nbsp;r0,&nbsp;#0&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c7,&nbsp;c7&nbsp;@&nbsp;invalidate&nbsp;I,D&nbsp;caches&nbsp;on&nbsp;v4&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c7,&nbsp;c10,&nbsp;4@&nbsp;drain&nbsp;write&nbsp;buffer&nbsp;on&nbsp;v4&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c8,&nbsp;c7&nbsp;@&nbsp;invalidate&nbsp;I,D&nbsp;TLBs&nbsp;on&nbsp;v4&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r4,&nbsp;c2,&nbsp;c0&nbsp;@&nbsp;load&nbsp;page&nbsp;table&nbsp;pointer&nbsp;<br>mov&nbsp;r0,&nbsp;#0x1f&nbsp;@&nbsp;Domains&nbsp;0,&nbsp;1&nbsp;=&nbsp;client&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c3,&nbsp;c0&nbsp;@&nbsp;load&nbsp;domain&nbsp;access&nbsp;register&nbsp;<br>mrc&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c1,&nbsp;c0&nbsp;@&nbsp;get&nbsp;control&nbsp;register&nbsp;v4&nbsp;<br>/*&nbsp;<br>*&nbsp;Clear&nbsp;out&nbsp;'unwanted'&nbsp;bits&nbsp;(then&nbsp;put&nbsp;them&nbsp;in&nbsp;if&nbsp;we&nbsp;need&nbsp;them)&nbsp;<br>*/&nbsp;<br>@&nbsp;VI&nbsp;ZFRS&nbsp;BLDP&nbsp;WCAM&nbsp;<br>bic&nbsp;r0,&nbsp;r0,&nbsp;#0x0e00&nbsp;<br>bic&nbsp;r0,&nbsp;r0,&nbsp;#0x0002&nbsp;<br>bic&nbsp;r0,&nbsp;r0,&nbsp;#0x000c&nbsp;<br>bic&nbsp;r0,&nbsp;r0,&nbsp;#0x1000&nbsp;@&nbsp;...0&nbsp;000.&nbsp;....&nbsp;000.&nbsp;<br>/*&nbsp;<br>*&nbsp;Turn&nbsp;on&nbsp;what&nbsp;we&nbsp;want&nbsp;<br>*/&nbsp;<br>orr&nbsp;r0,&nbsp;r0,&nbsp;#0x0031&nbsp;<br>orr&nbsp;r0,&nbsp;r0,&nbsp;#0x2100&nbsp;@&nbsp;..1.&nbsp;...1&nbsp;..11&nbsp;...1&nbsp;<br><br><br>#ifdef&nbsp;CONFIG_CPU_ARM920_D_CACHE_ON&nbsp;<br>orr&nbsp;r0,&nbsp;r0,&nbsp;#0x0004&nbsp;@&nbsp;....&nbsp;....&nbsp;....&nbsp;.1..&nbsp;<br>#endif&nbsp;<br>#ifdef&nbsp;CONFIG_CPU_ARM920_I_CACHE_ON&nbsp;<br>orr&nbsp;r0,&nbsp;r0,&nbsp;#0x1000&nbsp;@&nbsp;...1&nbsp;....&nbsp;....&nbsp;....&nbsp;<br>#endif&nbsp;<br>mov&nbsp;pc,&nbsp;lr&nbsp;<br><br><br>这一段首先关闭i,d&nbsp;cache,清除write&nbsp;buffer&nbsp;，然后设置页目录地址，设置&nbsp;<br>domain的保护，在上节中，注意到页目录项的domain都是0，domain寄存器中&nbsp;<br>的domain&nbsp;0&nbsp;对应的是0b11，表示访问模式为manager,不受限制。&nbsp;<br><br><br>接下来设置控制寄存器，打开d,i&nbsp;cache和mmu&nbsp;<br>注意arm的d&nbsp;cache必须和mmu一起打开，而i&nbsp;cache可以单独打开&nbsp;<br><br><br>其实，cache和mmu的关系实在是紧密，每一个页表项都有标志标示是否是&nbsp;<br>cacheable的，可以说本来就是设计一起使用的&nbsp;<br><br><br>最后，自函数返回后，有一句&nbsp;<br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c1,&nbsp;c0&nbsp;<br>使设置生效。&nbsp;<br><br><br><br>上回我们讲到arm靠初始化完成了，打开了cache,&nbsp;<br>到此为止，汇编部分的初始化代码就差不多了，最后还有几件事情做：&nbsp;<br><br><br>1。初始化BSS段，全部清零，BSS是全局变量区域。&nbsp;<br>2。保存与系统相关的信息：如&nbsp;<br>.long&nbsp;SYMBOL_NAME(compat)&nbsp;<br>.long&nbsp;SYMBOL_NAME(__bss_start)&nbsp;<br>.long&nbsp;SYMBOL_NAME(_end)&nbsp;<br>.long&nbsp;SYMBOL_NAME(processor_id)&nbsp;<br>.long&nbsp;SYMBOL_NAME(__machine_arch_type)&nbsp;<br>.long&nbsp;SYMBOL_NAME(cr_alignment)&nbsp;<br>.long&nbsp;SYMBOL_NAME(init_task_union)+8192&nbsp;<br>不用讲，大家一看就明白意思&nbsp;<br><br><br>3。重新设置堆栈指针，指向init_task的堆栈。init_task是系统的第一个任务，init_task的堆栈在task&nbsp;structure的后8K,我们后面会看到。&nbsp;<br><br><br>4。最后就要跳到C代码的start_kernel。&nbsp;<br>b&nbsp;SYMBOL_NAME(start_kernel)&nbsp;<br><br><br>现在让我们来回忆一下目前的系统状态：&nbsp;<br>临时页表已经建立，在0X08004000处，映射了4M，虚地址0XC000000被映射到0X08000000.&nbsp;<br>CACHE,MMU都已经打开。&nbsp;<br>堆栈用的是任务init_task的堆栈。&nbsp;<br>如果以为到了c代码可以松一口气的话，就大错特措了，linux的c也不比汇编好懂多少，相反到掩盖了汇编的一些和机器相关的部分，有时候更难懂。其实作为编写操作系统的c代码，只不过是汇编的另一种写法，和机器代码的联系是很紧密的。&nbsp;<br><br><br><br>start_kernel在&nbsp;/linux/init/main.c中定义：&nbsp;<br><br><br>asmlinkage&nbsp;void&nbsp;__init&nbsp;start_kernel(void)&nbsp;<br>{&nbsp;<br>char&nbsp;*&nbsp;command_line;&nbsp;<br>unsigned&nbsp;long&nbsp;mempages;&nbsp;<br>extern&nbsp;char&nbsp;saved_command_line[];&nbsp;<br>lock_kernel();&nbsp;<br>printk(linux_banner);&nbsp;<br>setup_arch(&amp;command_line);&nbsp;//arm/kernel/setup.c&nbsp;<br>printk("Kernel&nbsp;command&nbsp;line:&nbsp;%s\n",&nbsp;saved_command_line);&nbsp;<br>parse_options(command_line);&nbsp;<br><br><br>trap_init();&nbsp;//&nbsp;arm/kernle/traps.c&nbsp;install&nbsp;<br>。。。。。。。。。&nbsp;<br><br><br>start_kernel中的函数个个都是重量级的，首先用printk(linux_banner);打出&nbsp;<br>系统版本号，这里面就大有文章，系统才刚开张，你让他打印到哪里去呢？&nbsp;<br>先给大家交个底，以后到console的部分自然清楚，printk和printf不同，他首先输出到系统的一个缓冲区内，大约4k,如果登记了console，则调用console-&gt;wirte函数输出，否则就一直在buffer里呆着。所以，用printk输出的信息，如果超出了4k，会冲掉前面的。在系统引导起来后，用dmesg看的也就是这个buffer中的东东。&nbsp;<br><br><br><br>下面就是一个重量级的函数：&nbsp;<br>setup_arch(&amp;command_line);&nbsp;//arm/kernel/setup.c&nbsp;<br>完成内存映像的初始化，其中command_line是从bootloader中传下来的。&nbsp;<br><br><br><br>void&nbsp;__init&nbsp;setup_arch(char&nbsp;**cmdline_p)&nbsp;<br>{&nbsp;<br>struct&nbsp;param_struct&nbsp;*params&nbsp;=&nbsp;NULL;&nbsp;<br>struct&nbsp;machine_desc&nbsp;*mdesc;&nbsp;//arch&nbsp;structure,&nbsp;for&nbsp;your&nbsp;ads,&nbsp;defined&nbsp;in&nbsp;include/arm-asm/mach/arch.h&nbsp;very&nbsp;long&nbsp;<br>struct&nbsp;meminfo&nbsp;meminfo;&nbsp;<br>char&nbsp;*from&nbsp;=&nbsp;default_command_line;&nbsp;<br><br><br>memset(&amp;meminfo,&nbsp;0,&nbsp;sizeof(meminfo));&nbsp;<br><br><br>首先把meminfo清零，有个背景介绍一下，从linux&nbsp;2.4的内核开始，支持内存的节点（node），也就是可支持不连续的物理内存区域。这一点在嵌入式系统中很有用，例如对于SDRAM和FALSH,性质不同，可作为不同的内存节点。&nbsp;<br><br><br><br>meminfo结构定义如下：&nbsp;<br><br><br>/******************************************************/&nbsp;<br>#define&nbsp;NR_BANKS&nbsp;4&nbsp;<br>//define&nbsp;the&nbsp;systen&nbsp;mem&nbsp;region,&nbsp;not&nbsp;consistent&nbsp;<br>struct&nbsp;meminfo&nbsp;{&nbsp;<br>int&nbsp;nr_banks;&nbsp;<br>unsigned&nbsp;long&nbsp;end;&nbsp;<br>struct&nbsp;{&nbsp;<br>unsigned&nbsp;long&nbsp;start;&nbsp;<br>unsigned&nbsp;long&nbsp;size;&nbsp;<br>int&nbsp;node;&nbsp;<br>}&nbsp;bank[NR_BANKS];&nbsp;<br>};&nbsp;<br>/******************************************************/&nbsp;<br><br><br>下面是：ROOT_DEV&nbsp;=&nbsp;MKDEV(0,&nbsp;255);&nbsp;<br><br><br>ROOT_DEV是宏，指明启动的设备，嵌入式系统中通常是flash&nbsp;disk.&nbsp;<br>这里面有一个有趣的悖论：linux的设备都是在/dev/下，访问这些设备文件需要设备驱动程序支持，而访问设备文件才能取得设备号，才能加载驱动程序，那么第一个设备驱动程序是怎么加载呢？就是ROOT_DEV，&nbsp;不需要访问设备文件，直接指定设备号。&nbsp;<br><br><br><br>下面我们准备初始化真正的内核页表，而不再是临时的了。&nbsp;<br>首先还是取得当前系统的内存映像：&nbsp;<br><br><br>mdesc&nbsp;=&nbsp;setup_architecture(machine_arch_type);&nbsp;<br>//find&nbsp;the&nbsp;machine&nbsp;type&nbsp;in&nbsp;mach-integrator/arch.c&nbsp;<br>//the&nbsp;ads&nbsp;name,&nbsp;mem&nbsp;map,&nbsp;io&nbsp;map&nbsp;<br><br><br>返回如下结构：&nbsp;<br>mach-integrator/arch.c&nbsp;<br><br><br>MACHINE_START(INTEGRATOR,&nbsp;"Motorola&nbsp;MX1ADS")&nbsp;<br>MAINTAINER("ARM&nbsp;Ltd/Deep&nbsp;Blue&nbsp;Solutions&nbsp;Ltd")&nbsp;<br>BOOT_MEM(0x08000000,&nbsp;0x00200000,&nbsp;0xf0200000)&nbsp;<br>FIXUP(integrator_fixup)&nbsp;<br>MAPIO(integrator_map_io)&nbsp;<br>INITIRQ(integrator_init_irq)&nbsp;<br>MACHINE_END&nbsp;<br><br><br>我们在前面介绍过这个结构，不过这次用它可是玩真的了。&nbsp;<br><br><br><br>书接上回，&nbsp;<br>下面是init_mm的初始化，init_mm定义在/arch/arm/kernel/init_task.c：&nbsp;<br>struct&nbsp;mm_struct&nbsp;init_mm&nbsp;=&nbsp;INIT_MM(init_mm);&nbsp;<br><br><br><br>从本回开始的相当一部分内容是和内存管理相关的，凭心而论，操作系统的&nbsp;<br>内存管理是很复杂的，牵扯到处理器的硬件细节和软件算法，&nbsp;<br>限于篇幅所限制，请大家先仔细读一读arm&nbsp;mmu的部分，&nbsp;<br>中文参考资料：linux内核源代码情景对话，&nbsp;<br>linux2.4.18原代码分析。&nbsp;<br><br><br>init_mm.start_code&nbsp;=&nbsp;(unsigned&nbsp;long)&nbsp;&amp;_text;&nbsp;<br>内核代码段开始&nbsp;<br>init_mm.end_code&nbsp;=&nbsp;(unsigned&nbsp;long)&nbsp;&amp;_etext;&nbsp;<br>内核代码段结束&nbsp;<br>init_mm.end_data&nbsp;=&nbsp;(unsigned&nbsp;long)&nbsp;&amp;_edata;&nbsp;<br>内核数据段开始&nbsp;<br>init_mm.brk&nbsp;=&nbsp;(unsigned&nbsp;long)&nbsp;&amp;_end;&nbsp;<br>内核数据段结束&nbsp;<br><br><br>每一个任务都有一个mm_struct结构管理任务内存空间，init_mm&nbsp;<br>是内核的mm_struct，其中设置成员变量*&nbsp;mmap指向自己，&nbsp;<br>意味着内核只有一个内存管理结构，设置*&nbsp;pgd=swapper_pg_dir，&nbsp;<br>swapper_pg_dir是内核的页目录，在arm体系结构有16k，&nbsp;<br>所以init_mm定义了整个kernel的内存空间，下面我们会碰到内核&nbsp;<br>线程，所有的内核线程都使用内核空间，拥有和内核同样的访问&nbsp;<br>权限。&nbsp;<br><br><br>memcpy(saved_command_line,&nbsp;from,&nbsp;COMMAND_LINE_SIZE);&nbsp;<br>//clear&nbsp;command&nbsp;array&nbsp;<br><br><br>saved_command_line[COMMAND_LINE_SIZE-1]&nbsp;=&nbsp;'\0';&nbsp;<br>//set&nbsp;the&nbsp;end&nbsp;flag&nbsp;<br><br><br>parse_cmdline(&amp;meminfo,&nbsp;cmdline_p,&nbsp;from);&nbsp;<br>//将bootloader的参数拷贝到cmdline_p，&nbsp;<br><br><br><br>bootmem_init(&amp;meminfo);&nbsp;<br>定义在arm/mm/init.c&nbsp;<br>这个函数在内核结尾分一页出来作位图，根据具体系统的内存大小&nbsp;<br>映射整个ram&nbsp;<br><br><br>下面是一个非常重要的函数&nbsp;<br>paging_init(&amp;meminfo,&nbsp;mdesc);&nbsp;<br>定义在arm/mm/init.c&nbsp;<br>创建内核页表，映射所有物理内存和io空间，&nbsp;<br>对于不同的处理器，这个函数差别很大，&nbsp;<br><br><br>void&nbsp;__init&nbsp;paging_init(struct&nbsp;meminfo&nbsp;*mi,&nbsp;struct&nbsp;machine_desc&nbsp;*mdesc)&nbsp;<br>{&nbsp;<br>void&nbsp;*zero_page,&nbsp;*bad_page,&nbsp;*bad_table;&nbsp;<br>int&nbsp;node;&nbsp;<br><br><br>//static&nbsp;struct&nbsp;meminfo&nbsp;meminfo&nbsp;__initdata&nbsp;=&nbsp;{&nbsp;0,&nbsp;};&nbsp;<br><br><br>memcpy(&amp;meminfo,&nbsp;mi,&nbsp;sizeof(meminfo));&nbsp;<br><br><br>/*&nbsp;<br>*&nbsp;allocate&nbsp;what&nbsp;we&nbsp;need&nbsp;for&nbsp;the&nbsp;bad&nbsp;pages.&nbsp;<br>*&nbsp;note&nbsp;that&nbsp;we&nbsp;count&nbsp;on&nbsp;this&nbsp;going&nbsp;ok.&nbsp;<br>*/&nbsp;<br><br><br>zero_page&nbsp;=&nbsp;alloc_bootmem_low_pages(PAGE_SIZE);&nbsp;<br>bad_page&nbsp;=&nbsp;alloc_bootmem_low_pages(PAGE_SIZE);&nbsp;<br>bad_table&nbsp;=&nbsp;alloc_bootmem_low_pages(TABLE_SIZE);&nbsp;<br><br><br>分配三个页出来，用于处理异常过程，在armlinux中，得到如下&nbsp;<br>地址：&nbsp;<br>zero_page=0xc0000000&nbsp;<br>bad&nbsp;page=0xc0001000&nbsp;<br>bad_table=0xc0002000&nbsp;<br><br><br><br>上回我们说到在paging_init中分配了三个页：&nbsp;<br><br><br>zero_page=0xc0000000&nbsp;<br>bad&nbsp;page=0xc0001000&nbsp;<br>bad_table=0xc0002000&nbsp;<br><br><br>但是奇怪的很，在更新的linux代码中只分配了一个&nbsp;<br>zero_page，而且在源代码中找不到zero_page&nbsp;<br>用在什么地方了，大家讨论讨论吧。&nbsp;<br><br><br>paging_init的主要工作是在&nbsp;<br>void&nbsp;__init&nbsp;memtable_init(struct&nbsp;meminfo&nbsp;*mi)&nbsp;<br>中完成的，为系统内存创建页表：&nbsp;<br><br><br>meminfo结构如下：&nbsp;<br><br><br>struct&nbsp;meminfo&nbsp;{&nbsp;<br>int&nbsp;nr_banks;&nbsp;<br>unsigned&nbsp;long&nbsp;end;&nbsp;<br>struct&nbsp;{&nbsp;<br>unsigned&nbsp;long&nbsp;start;&nbsp;<br>unsigned&nbsp;long&nbsp;size;&nbsp;<br>int&nbsp;node;&nbsp;<br>}&nbsp;bank[NR_BANKS];&nbsp;<br>};&nbsp;<br><br><br>是用来纪录系统中的内存区段的，因为在嵌入式&nbsp;<br>系统中并不是所有的内存都能映射，例如sdram只有&nbsp;<br>64m,flash&nbsp;32m,而且不见得是连续的，所以用&nbsp;<br>meminfo纪录这些区段。&nbsp;<br><br><br>void&nbsp;__init&nbsp;memtable_init(struct&nbsp;meminfo&nbsp;*mi)&nbsp;<br>{&nbsp;<br>struct&nbsp;map_desc&nbsp;*init_maps,&nbsp;*p,&nbsp;*q;&nbsp;<br>unsigned&nbsp;long&nbsp;address&nbsp;=&nbsp;0;&nbsp;<br>int&nbsp;i;&nbsp;<br><br><br>init_maps&nbsp;=&nbsp;p&nbsp;=&nbsp;alloc_bootmem_low_pages(PAGE_SIZE);&nbsp;<br><br><br>其中map_desc定义为：&nbsp;<br><br><br>struct&nbsp;map_desc&nbsp;{&nbsp;<br>unsigned&nbsp;long&nbsp;virtual;&nbsp;<br>unsigned&nbsp;long&nbsp;physical;&nbsp;<br>unsigned&nbsp;long&nbsp;length;&nbsp;<br>int&nbsp;domain:4,&nbsp;//页表的domain&nbsp;<br>prot_read:1,&nbsp;//保护标志&nbsp;<br>prot_write:1,&nbsp;//写保护标志&nbsp;<br>cacheable:1,&nbsp;//是否cache&nbsp;<br>bufferable:1,&nbsp;//是否用write&nbsp;buffer&nbsp;<br>last:1;&nbsp;//空&nbsp;<br>};init_maps&nbsp;<br><br><br>map_desc是区段及其属性的定义，属性位的意义请&nbsp;<br>参考ARM&nbsp;MMU的介绍。&nbsp;<br><br><br>下面对meminfo的区段进行遍历，同时填写init_maps&nbsp;<br>中的各项内容：&nbsp;<br>for&nbsp;(i&nbsp;=&nbsp;0;&nbsp;i&nbsp;&lt;&nbsp;mi-&gt;nr_banks;&nbsp;i++)&nbsp;{&nbsp;<br>if&nbsp;(mi-&gt;bank.size&nbsp;==&nbsp;0)&nbsp;<br>continue;&nbsp;<br><br><br>p-&gt;physical&nbsp;=&nbsp;mi-&gt;bank.start;&nbsp;<br>p-&gt;virtual&nbsp;=&nbsp;__phys_to_virt(p-&gt;physical);&nbsp;<br>p-&gt;length&nbsp;=&nbsp;mi-&gt;bank.size;&nbsp;<br>p-&gt;domain&nbsp;=&nbsp;DOMAIN_KERNEL;&nbsp;<br>p-&gt;prot_read&nbsp;=&nbsp;0;&nbsp;<br>p-&gt;prot_write&nbsp;=&nbsp;1;&nbsp;<br>p-&gt;cacheable&nbsp;=&nbsp;1;&nbsp;//可以CACHE&nbsp;<br>p-&gt;bufferable&nbsp;=&nbsp;1;&nbsp;//使用write&nbsp;buffer&nbsp;<br>p&nbsp;++;&nbsp;//下一个区段&nbsp;<br>}&nbsp;<br><br><br>如果系统有flash,&nbsp;<br>#ifdef&nbsp;FLUSH_BASE&nbsp;<br>p-&gt;physical&nbsp;=&nbsp;FLUSH_BASE_PHYS;&nbsp;<br>p-&gt;virtual&nbsp;=&nbsp;FLUSH_BASE;&nbsp;<br>p-&gt;length&nbsp;=&nbsp;PGDIR_SIZE;&nbsp;<br>p-&gt;domain&nbsp;=&nbsp;DOMAIN_KERNEL;&nbsp;<br>p-&gt;prot_read&nbsp;=&nbsp;1;&nbsp;<br>p-&gt;prot_write&nbsp;=&nbsp;0;&nbsp;<br>p-&gt;cacheable&nbsp;=&nbsp;1;&nbsp;<br>p-&gt;bufferable&nbsp;=&nbsp;1;&nbsp;<br><br><br>p&nbsp;++;&nbsp;<br>#endif&nbsp;<br><br><br>其中的prot_read和prot_write是用来设置页表的domain的，&nbsp;<br><br><br>下面就是逐个区段建立页表：&nbsp;<br><br><br>q&nbsp;=&nbsp;init_maps;&nbsp;<br>do&nbsp;{&nbsp;<br>if&nbsp;(address&nbsp;&lt;&nbsp;q-&gt;virtual&nbsp;||&nbsp;q&nbsp;==&nbsp;p)&nbsp;{&nbsp;<br>clear_mapping(address);&nbsp;<br>address&nbsp;+=&nbsp;PGDIR_SIZE;&nbsp;<br>}&nbsp;else&nbsp;{&nbsp;<br>create_mapping(q);&nbsp;<br><br><br>address&nbsp;=&nbsp;q-&gt;virtual&nbsp;+&nbsp;q-&gt;length;&nbsp;<br>address&nbsp;=&nbsp;(address&nbsp;+&nbsp;PGDIR_SIZE&nbsp;-&nbsp;1)&nbsp;&amp;&nbsp;PGDIR_MASK;&nbsp;<br><br><br>q&nbsp;++;&nbsp;<br>}&nbsp;<br>}&nbsp;while&nbsp;(address&nbsp;!=&nbsp;0);&nbsp;<br><br><br><br>上次说到memtable_init中初始化页表的循环，&nbsp;<br>这个过程比较重要，我们看仔细些：&nbsp;<br><br><br><br>q&nbsp;=&nbsp;init_maps;&nbsp;<br>do&nbsp;{&nbsp;<br>if&nbsp;(address&nbsp;&lt;&nbsp;q-&gt;virtual&nbsp;||&nbsp;q&nbsp;==&nbsp;p)&nbsp;{&nbsp;<br>//由于内核空间是从c000&nbsp;0000开始，所以c000&nbsp;0000&nbsp;<br>//以前的页表项全部清空&nbsp;<br><br><br>clear_mapping(address);&nbsp;<br>address&nbsp;+=&nbsp;PGDIR_SIZE;&nbsp;<br>//每个表项增加1m，这里感到了section的好处&nbsp;<br>}&nbsp;<br><br><br>其中clear_mapping()是个宏，根据处理器的&nbsp;<br>不同，在920下被展开为&nbsp;<br><br><br>cpu_arm920_set_pmd(((pmd_t&nbsp;*)(((&amp;init_mm&nbsp;)-&gt;pgd+&nbsp;<br>((&nbsp;virt)&nbsp;&gt;&gt;&nbsp;20&nbsp;)))),((pmd_t){(&nbsp;0&nbsp;)}));&nbsp;<br><br><br>其中init_mm为内核的mm_struct,pgd指向&nbsp;<br>swapper_pg_dir，在arch/arm/kernel/init_task.c中定义&nbsp;<br><br><br>ENTRY(cpu_arm920_set_pmd)&nbsp;<br>#ifdef&nbsp;CONFIG_CPU_ARM920_WRITETHROUGH&nbsp;<br>eor&nbsp;r2,&nbsp;r1,&nbsp;#0x0a&nbsp;<br>tst&nbsp;r2,&nbsp;#0x0b&nbsp;<br>biceq&nbsp;r1,&nbsp;r1,&nbsp;#4&nbsp;<br>#endif&nbsp;<br>str&nbsp;r1,&nbsp;[r0]&nbsp;<br><br><br>把pmd_t填写到页表项中，由于pmd_t=0，&nbsp;<br>实际等于清除了这一项,由于d&nbsp;cache打开，&nbsp;<br>这一条指令实际并没有写回内存，而是写到cache中&nbsp;<br><br><br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c7,&nbsp;c10,&nbsp;1&nbsp;<br><br><br>把cache中&nbsp;地址r0对应的内容写回内存中，&nbsp;<br>这一条语句实际是写到了write&nbsp;buffer中,&nbsp;<br>还没有真正写回内存。&nbsp;<br><br><br>mcr&nbsp;p15,&nbsp;0,&nbsp;r0,&nbsp;c7,&nbsp;c10,&nbsp;4&nbsp;<br><br><br>等待把write&nbsp;buffer中的内容写回内存。在这之前core等待&nbsp;<br><br><br>mov&nbsp;pc,&nbsp;lr&nbsp;<br><br><br>在这里我们看到，由于页表的内容十分关键，为了确保写回内存，&nbsp;<br>采用了直接操作cache的方法。由于在arm&nbsp;core中，打开了d&nbsp;cache&nbsp;<br>则必定要用write&nbsp;buffer.所以还有wb的回写问题。&nbsp;<br>由于考虑到效率，我们使用了cache和buffer,&nbsp;<br>所以在某些地方要用指令保证数据被及时写回。&nbsp;<br><br><br><br>下面映射c000&nbsp;0000后面的页表&nbsp;<br><br><br>else&nbsp;{&nbsp;<br>create_mapping(q);&nbsp;<br><br><br>address&nbsp;=&nbsp;q-&gt;virtual&nbsp;+&nbsp;q-&gt;length;&nbsp;<br>address&nbsp;=&nbsp;(address&nbsp;+&nbsp;PGDIR_SIZE&nbsp;-&nbsp;1)&nbsp;&amp;&nbsp;PGDIR_MASK;&nbsp;<br><br><br>q&nbsp;++;&nbsp;<br>}&nbsp;<br>}&nbsp;while&nbsp;(address&nbsp;!=&nbsp;0);&nbsp;<br><br><br><br>create_mapping也在mm-armv.c中定义；&nbsp;<br><br><br>static&nbsp;void&nbsp;__init&nbsp;create_mapping(struct&nbsp;map_desc&nbsp;*md)&nbsp;<br>{&nbsp;<br>unsigned&nbsp;long&nbsp;virt,&nbsp;length;&nbsp;<br>int&nbsp;prot_sect,&nbsp;prot_pte;&nbsp;<br>long&nbsp;off;&nbsp;<br><br><br>prot_pte&nbsp;=&nbsp;L_PTE_PRESENT&nbsp;|&nbsp;L_PTE_YOUNG&nbsp;|&nbsp;L_PTE_DIRTY&nbsp;|&nbsp;<br>(md-&gt;prot_read&nbsp;?&nbsp;L_PTE_USER&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;prot_write&nbsp;?&nbsp;L_PTE_WRITE&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;cacheable&nbsp;?&nbsp;L_PTE_CACHEABLE&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;bufferable&nbsp;?&nbsp;L_PTE_BUFFERABLE&nbsp;:&nbsp;0);&nbsp;<br><br><br>prot_sect&nbsp;=&nbsp;PMD_TYPE_SECT&nbsp;|&nbsp;PMD_DOMAIN(md-&gt;domain)&nbsp;|&nbsp;<br>(md-&gt;prot_read&nbsp;?&nbsp;PMD_SECT_AP_READ&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;prot_write&nbsp;?&nbsp;PMD_SECT_AP_WRITE&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;cacheable&nbsp;?&nbsp;PMD_SECT_CACHEABLE&nbsp;:&nbsp;0)&nbsp;|&nbsp;<br>(md-&gt;bufferable&nbsp;?&nbsp;PMD_SECT_BUFFERABLE&nbsp;:&nbsp;0);&nbsp;<br><br><br>由于arm中section表项的权限位和page表项的位置不同，&nbsp;<br>所以根据struct&nbsp;map_desc&nbsp;中的保护标志，分别计算页表项&nbsp;<br>中的AP,domain,CB标志位。&nbsp;<br><br><br>有一段时间没有写了,道歉先,前一段时间在做arm&nbsp;linux的xip,终于找到了&nbsp;<br>在flash中运行kernel的方法，同时对系统的存储管理&nbsp;<br>的理解更深了一层，我们继续从上回的create_mapping往下看：&nbsp;<br><br><br>while&nbsp;((virt&nbsp;&amp;&nbsp;0xfffff&nbsp;||&nbsp;(virt&nbsp;+&nbsp;off)&nbsp;&amp;&nbsp;0xfffff)&nbsp;&amp;&amp;&nbsp;length&nbsp;&gt;=&nbsp;PAGE_SIZE)&nbsp;{&nbsp;<br>alloc_init_page(virt,&nbsp;virt&nbsp;+&nbsp;off,&nbsp;md-&gt;domain,&nbsp;prot_pte);&nbsp;<br><br><br>virt&nbsp;+=&nbsp;PAGE_SIZE;&nbsp;<br>length&nbsp;-=&nbsp;PAGE_SIZE;&nbsp;<br>}&nbsp;<br><br><br>while&nbsp;(length&nbsp;&gt;=&nbsp;PGDIR_SIZE)&nbsp;{&nbsp;<br>alloc_init_section(virt,&nbsp;virt&nbsp;+&nbsp;off,&nbsp;prot_sect);&nbsp;<br><br><br>virt&nbsp;+=&nbsp;PGDIR_SIZE;&nbsp;<br>length&nbsp;-=&nbsp;PGDIR_SIZE;&nbsp;<br>}&nbsp;<br><br><br>while&nbsp;(length&nbsp;&gt;=&nbsp;PAGE_SIZE)&nbsp;{&nbsp;<br>alloc_init_page(virt,&nbsp;virt&nbsp;+&nbsp;off,&nbsp;md-&gt;domain,&nbsp;prot_pte);&nbsp;<br><br><br>virt&nbsp;+=&nbsp;PAGE_SIZE;&nbsp;<br>length&nbsp;-=&nbsp;PAGE_SIZE;&nbsp;<br>}&nbsp;<br>这3个循环的设计还是很巧妙的，create_mapping的作用是设置虚地址virt&nbsp;<br>到物理地址virt&nbsp;+&nbsp;off的映射页目录和页表。arm提供了4种尺寸的页表：&nbsp;<br>1M,4K,16K,64K,armlinux只用到了1M和4K两种。&nbsp;<br><br><br>这3个while的作用分别是&#8220;掐头&#8220;，&#8220;去尾&#8220;，&#8220;砍中间&#8220;。&nbsp;<br>第一个while是判断要映射的地址长度是否大于1m,且是不是1m对齐，&nbsp;<br>如果不是,则需要创建页表，例如，如果要映射的长度为1m零4k,则先要将&#8220;零头&#8220;&nbsp;<br>去掉，4k的一段需要中间页表，通过第一个while创建中间页表，&nbsp;<br>而剩下的1M则交给第二个while循环。最后剩下的交给第三个while循环。&nbsp;<br><br><br><br>alloc_init_page分配并填充中间页表项&nbsp;<br>static&nbsp;inline&nbsp;void&nbsp;<br>alloc_init_page(unsigned&nbsp;long&nbsp;virt,&nbsp;unsigned&nbsp;long&nbsp;phys,&nbsp;int&nbsp;domain,&nbsp;int&nbsp;prot)&nbsp;<br>{&nbsp;<br>pmd_t&nbsp;*pmdp;&nbsp;<br>pte_t&nbsp;*ptep;&nbsp;<br><br><br>pmdp&nbsp;=&nbsp;pmd_offset(pgd_offset_k(virt),&nbsp;virt);//返回页目录中virt对应的表项&nbsp;<br><br><br>if&nbsp;(pmd_none(*pmdp))&nbsp;{//如果表项是空的，则分配一个中间页表&nbsp;<br>pte_t&nbsp;*ptep&nbsp;=&nbsp;alloc_bootmem_low_pages(2&nbsp;*&nbsp;PTRS_PER_PTE&nbsp;*&nbsp;<br>sizeof(pte_t));&nbsp;<br><br><br>ptep&nbsp;+=&nbsp;PTRS_PER_PTE;&nbsp;<br>//设置页目录表项&nbsp;<br>set_pmd(pmdp,&nbsp;__mk_pmd(ptep,&nbsp;PMD_TYPE_TABLE&nbsp;|&nbsp;PMD_DOMAIN(domain)));&nbsp;<br>}&nbsp;<br>ptep&nbsp;=&nbsp;pte_offset(pmdp,&nbsp;virt);&nbsp;<br>//如果表项不是空的，则表项已经存在，只需要设置中间页表表项&nbsp;<br>set_pte(ptep,&nbsp;mk_pte_phys(phys,&nbsp;__pgprot(prot)));&nbsp;<br>}&nbsp;<br><br><br>alloc_init_section只需要填充页目录项&nbsp;<br><br><br>alloc_init_section(unsigned&nbsp;long&nbsp;virt,&nbsp;unsigned&nbsp;long&nbsp;phys,&nbsp;int&nbsp;prot)&nbsp;<br>{&nbsp;<br>pmd_t&nbsp;pmd;&nbsp;<br><br><br>pmd_val(pmd)&nbsp;=&nbsp;phys&nbsp;|&nbsp;prot;//将物理地址和保护标志合成页目录项&nbsp;<br><br><br>set_pmd(pmd_offset(pgd_offset_k(virt),&nbsp;virt),&nbsp;pmd);&nbsp;<br>}&nbsp;<br><br><br>通过create_mapping可为内核建立所有的地址映射，最后是映射中断向量表&nbsp;<br>所在的区域：&nbsp;<br><br><br>init_maps-&gt;physical&nbsp;=&nbsp;virt_to_phys(init_maps);&nbsp;<br>init_maps-&gt;virtual&nbsp;=&nbsp;vectors_base();&nbsp;<br>init_maps-&gt;length&nbsp;=&nbsp;PAGE_SIZE;&nbsp;<br>init_maps-&gt;domain&nbsp;=&nbsp;DOMAIN_USER;&nbsp;<br>init_maps-&gt;prot_read&nbsp;=&nbsp;0;&nbsp;<br>init_maps-&gt;prot_write&nbsp;=&nbsp;0;&nbsp;<br>init_maps-&gt;cacheable&nbsp;=&nbsp;1;&nbsp;<br>init_maps-&gt;bufferable&nbsp;=&nbsp;0;&nbsp;<br><br><br>create_mapping(init_maps);&nbsp;<br><br><br>中断向量表的虚地址init_maps，是用alloc_bootmem_low_pages分配的，&nbsp;<br>通常是在c000&nbsp;8000前面的某一页，&nbsp;vectors_base()是个宏，arm规定中断&nbsp;<br>向量表的地址只能是0或ffff0000,在cp15中设置。所以上述代码映射一页到&nbsp;<br>0或ffff0000，下面我们还会看到，中断处理程序中的汇编部分也被拷贝到&nbsp;<br>这一页中。<br><br>
<img src ="http://www.cppblog.com/icecream/aggbug/79363.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/icecream/" target="_blank">icecream</a> 2009-04-09 19:54 <a href="http://www.cppblog.com/icecream/articles/79363.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>