嵌入式+Linux+应用开发完全手册.pdf

  • 文件大小: 757.22KB
  • 文件类型: pdf
  • 上传日期: 2025-08-25
  • 下载次数: 0

概要信息:

内 容 提 要 
本书全面介绍了嵌入式 Linux系统开发过程中,从底层系统支持到上层 GUI应用的方方面面,内容涵
盖 Linux 操作系统的安装及相关工具的使用、配置,嵌入式编程所需要的基础知识(交叉编译工具的选项
设置、Makefile语法、ARM汇编指令等),硬件部件的使用及编程(囊括了常见硬件,比如 UART、I2C、
LCD等),U-Boot、Linux内核的分析、配置和移植,根文件系统的构造(包括移植 busybox、glibc、制作
映象文件等),内核调试技术(比如添加 kgdb补丁、栈回溯等),驱动程序编写及移植(LED、按键、扩展
串口、网卡、硬盘、SD卡、LCD和 USB等),GUI系统的移植(包含两个 GUI系统:基于 Qtopia和基于
X),应用程序调试技术。 
本书从最简单的点亮一个 LED开始,由浅入深地讲解,使读者最终可以配置、移植、裁剪内核,编写
驱动程序,移植 GUI系统,掌握整个嵌入式 Linux系统的开发方法。 
本书由浅入深,循序渐进,适合刚接触嵌入式 Linux 的初学者学习,也可作为大、中专院校嵌入式相
关专业本科生、研究生的教材。 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
 
 
 
 
 
 前    言 
 
 
 
背景知识 
嵌入式 Linux 在嵌入式领域发展迅速、需求旺盛,但是嵌入式 Linux 的入门很难。初学
者多是自己琢磨,效率不高。学习过程中碰到的问题千奇百怪,解决后却往往发现是极其低
级的错误,以作者为例,初学时在论坛疯狂发帖求教,现在回头一看不免感叹:怎么会提出
这么弱智的问题?但是,当时就是被这类问题折磨得寝食难安。 
相对于嵌入式 Linux 常识的匮乏,更大的困难是缺乏完善的知识结构:只了解硬件,或
是只了解软件。对于有志于从事底层系统开发(比如改造 Bootloader、钻研内核、为新硬件
编写驱动程序)的人,对于想从上层软件开发转到底层软件开发的人,应该看得懂电路原理
图,看得懂芯片数据手册,清楚地知道软件是怎样和硬件发生作用的。 
同样,对于想从硬件岗位转到软件岗位的人,对于想从传统单片机(比如 51单片机)编
程进一步学习“有操作系统的”嵌入式编程的人,需要找到一个学习的切入点:先掌握各个硬
件部件的简单编程,再将它们组合起来构成一个相对复杂的软件系统——比如 Bootloader,进
而编写基于操作系统的驱动程序,最后深入钻研操作系统内核。 
对于尚未参加工作的在校生来说,缺乏实际的操作经验可能是就业的最大障碍。很多人
买了开发板想进一步练习,却发现不知从何入手。 
鉴于上述种种困难及需求,作者结合自己的学习经历、工作心得写成此书,期望能帮助
读者加快嵌入式 Linux的入门速度,并体会到深入学习嵌入式 Linux的乐趣。 
关于本书 
本书以 S3C2410、S3C2440开发板为例,从分析硬件上电执行的第一条指令开始,到构
造出一个类似 PDA、基于 Linux的桌面 GUI系统,带领读者学习、掌握从最底层到最高层的
软件编写方法。 
本书主要涉及以下主题: 
•  开发环境的搭建(包括安装 Linux系统及日常使用的工具); 
•  开发板上各硬件部件的使用方法及实际的编程操作; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║2    前言第 1 章 
 
•  嵌入式 Linux系统的构造(包括 Bootloader、内核、文件系统等); 
•  嵌入式 Linux驱动程序的编写方法及大量实例; 
•  GUI系统的移植(两个 GUI系统:基于 Qtopia和基于 X); 
•  调试技术(包括内核调试技术和应用程序调试技术)。 
本书所有章节都以理论结合代码的方式进行讲解,并可按照书中说明进行实际操作,力
求让读者“知其然,也知其所以然”。 
本书内容及组织方式 
本书按照嵌入式 Linux初学者的学习过程,从简单到复杂,从底层软件到上层软件进行
讲解,全书分 5篇,共 27章。 
第 1篇(第 1章至第 4章)为嵌入式 Linux开发环境构建篇,主要讲解以下内容。 
•  第 1章介绍基于 ARM的嵌入式 Linux系统的基本概念。 
•  第 2章讲解嵌入式开发环境的建立,包括在 PC上安装、配置 Linux操作系统,安装
随书光盘。 
•  第 3章介绍交叉编译工具的选项、Makefile的语法以及本书用到的 ARM汇编指令及
相关知识,这章可以当作阅读后续章节时的参考手册。 
•  第 4章介绍了一些日常工作要用到工具,比如源码阅读、编辑工具等。 
第 2篇(第 5章至第 14章)为 ARM9嵌入式系统基础实例篇,具体内容如下。 
本篇首先根据 S3C2410、S3C2440的数据手册介绍各硬件部件的使用方法,然后介绍怎样
编写程序来操作它们。文中穿插介绍了连接器的很多使用技巧,读者可以由此接触到“程序的
内部结构”,这是单纯的上层开发人员所缺乏的。通过读写各个硬件部件的寄存器来操作硬件,
读者还可以深刻体会到“软件”和“硬件”是怎样发生作用的,是第 3篇、第 4篇的基础。 
第 3篇(第 15章至第 18章)为嵌入式 Linux系统移植篇,主要讲解以下内容。 
•  第 15章深入分析 U-Boot(它负责引导内核)的代码结构,并详细介绍了将它移植到
开发板上的方法。 
•  第 16章首先分析了内核的代码结构,然后深入分析它的启动过程,最后将它移植到
开发板上。 
•  第 17章先从整体上介绍了 Linux文件系统的目录结构─FHS标准。然后构造文件
系统:移植常用工具的集合 Busybox,移植 glibc库,建立各个目录,建立配置文件。
最后修改、编译一些工具,使用它们来制作 yaffs、jffs2文件系统映象文件。 
•  第 18章介绍了 3种内核调试技术:printk、kgdb补丁、使用 Oops信息进行栈回溯。 
第 4篇(第 19章至第 24章)为嵌入式 Linux设备驱动开发篇,具体内容如下。 
在第 19章中总体介绍了驱动程序的编写、移植方法,在第 20章介绍了内核的异常处理
体系结构──就是怎样使用中断。 
其他章节都是一些例子:先总体介绍相关硬件的驱动程序架构,然后根据开发板的特性
进行修改。 
第 5篇(第 25章至第 27章)为嵌入式 Linux系统应用开发篇,主要讲解以下内容。 
•  第 25章移植了一个基于 Qtopia的 GUI系统,并且以简单的“Hello, world”程序为例
编写、调试 GUI程序。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 1 章  前言    3║ 
 
•  第 26章移植了一个基于 X的 GUI系统,里面涉及众多软件,读者可以体会到上层应
用的开发过程,并且获得移植大型软件的经验。这章还介绍了一个名为 Scratchbox的
交叉编译工具包,它虚拟出一个可以直接编译软件的目标机器,使得“交叉编译”变
为“本地编译”,大幅减少了为非 x86平台移植软件所需的工作量。 
•  第 27 章介绍了几种简便的应用程序调试技术,包括使用 strace 工具跟踪系统调用和
信号,使用memwatch检查程序的内存漏洞,使用库函数 backtrace和 backtrace_symbols
来定位段错误。 
本书特色 
•  由浅入深,从最简单的点亮 LED讲起直至移植 GUI系统。 
•  实例丰富,每个实例都详尽地介绍原理及分析代码。 
•  结构合理,先总体介绍概念、架构,然后进行具体操作。 
•  包括初学者所碰到的常见问题。 
参与本书编写的人员 
本书由韦东山负责编写并统编全部书稿,陈汉仪、于明俭对本书的写作提供了大力支持,
在此表示感谢。 
感谢我的父母和女友,在本书写作过程中给了我强大的精神支持,鼓励、支持我,使我
能够坚持写完本书。 
同时参与编写的还有柴作朋、单辉、丁鹏、冯发勇、付贤会、葛仕明、何国宝、何圆明、
何化成、黄永华、李志宏、廖娟、林清妹、陆江萍、祁晓璐、谭爱华、魏明辉、张帮芹、周
霜、朱旭琪等,在此一并表示感谢。 
我们为本书开通了专用的网站,网址是 http://www.100ask.net,读者可以直接同我们交流,
共同学习和提高。 
由于水平有限,书中难免遗漏和不足之处,恳请广大读者提出宝贵意见。本书责任编辑
的联系方式是 huangyan@ptpress.com.cn,欢迎来信交流。 
 
 
编  者   
2008年 6月 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
 
 
 
 
 
 目  录 
  
 
 
第 1篇  嵌入式 Linux 开发环境构建篇 
第 1章  嵌入式 Linux开发概述 .................................................................................................... 2 
1.1  嵌入式系统介绍 ............................................................................................................... 2 
1.1.1  嵌入式系统的定义和特点 .................................................................................... 2 
1.1.2  嵌入式技术的发展历史 ........................................................................................ 3 
1.2  基于 ARM处理器的嵌入式 Linux系统......................................................................... 5 
1.2.1  ARM处理器介绍 .................................................................................................. 5 
1.2.2  在嵌入式系统中选择嵌入式 Linux的理由......................................................... 8 
第 2章  嵌入式 Linux开发环境构建........................................................................................... 10 
2.1  硬件环境构建 ................................................................................................................. 10 
2.1.1  主机与目标板结合的交叉开发模式 .................................................................. 10 
2.1.2  硬件要求 .............................................................................................................. 11 
2.2  软件环境构建 ................................................................................................................. 12 
2.2.1  主机 Linux操作系统的安装 .............................................................................. 12 
2.2.2  主机 Linux操作系统上网络服务的配置与启动............................................... 18 
2.2.3  在主机 Linux操作系统中安装基本的开发环境............................................... 23 
2.2.4  光盘的内容结构及安装 ...................................................................................... 23 
2.2.5  安装交叉编译工具链 .......................................................................................... 25 
2.2.6  书中写作风格的约定 .......................................................................................... 28 
第 3章  嵌入式编程基础知识 ...................................................................................................... 29 
3.1  交叉编译工具选项说明 ................................................................................................. 29 
3.1.1  arm-linux-gcc选项 .............................................................................................. 29 
3.1.2  arm-linux-ld选项................................................................................................. 38 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║2    目录第 1 章 
 
3.1.3  arm-linux-objcopy选项 ....................................................................................... 41 
3.1.4  arm-linux-objdump选项...................................................................................... 43 
3.1.5  汇编代码、机器码和存储器的关系以及数据的表示 ...................................... 44 
3.2  Makefile介绍.................................................................................................................. 45 
3.2.1  Makefile规则 ...................................................................................................... 45 
3.2.2  Makefile文件里的赋值方法............................................................................... 46 
3.2.3  Makefile常用函数............................................................................................... 46 
3.3  常用 ARM汇编指令及 ATPCS规则 ............................................................................ 52 
3.3.1  本书使用的所有汇编指令 .................................................................................. 52 
3.3.2  ARM-THUMB子程序调用规则 ATPCS ........................................................... 55 
第 4章  Windows、Linux环境下相关工具、命令的使用......................................................... 58 
4.1  Windows环境下的工具介绍 ......................................................................................... 58 
4.1.1  代码阅读、编辑工具 Source Insight.................................................................. 58 
4.1.2  文件传输工具 Cuteftp ......................................................................................... 63 
4.1.3  远程登录工具 SecureCRT................................................................................... 63 
4.1.4  TFTP服务器软件 Tftpd32.................................................................................. 64 
4.2  Linux环境下的工具、命令介绍................................................................................... 65 
4.2.1  代码阅读、编辑工具 KScope ............................................................................ 65 
4.2.2  远程登录工具 C-kermit....................................................................................... 69 
4.2.3  编辑命令 vi.......................................................................................................... 69 
4.2.4  查找命令 grep、find命令 .................................................................................. 71 
4.2.5  在线手册查看命令 man ...................................................................................... 72 
4.2.6  其他命令:tar、diff、patch ............................................................................... 73 
第 2篇  ARM9 嵌入式系统基础实例篇 
第 5章  GPIO接口........................................................................................................................ 76 
5.1  GPIO硬件介绍............................................................................................................... 76 
5.1.1  通过寄存器来操作 GPIO引脚........................................................................... 76 
5.1.2  怎样使用软件来访问硬件 .................................................................................. 77 
5.2  GPIO操作实例:LED和按键 ...................................................................................... 80 
5.2.1  硬件设计 .............................................................................................................. 80 
5.2.2  程序设计及代码详解 .......................................................................................... 80 
5.2.3  实例测试 .............................................................................................................. 86 
第 6章  存储器控制 ...................................................................................................................... 87 
6.1  使用存储控制器访问外设的原理 ................................................................................. 87 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 1 章  目录    3║ 
 
6.1.1  S3C2410/S3C2440的地址空间 .......................................................................... 87 
6.1.2  存储控制器与外设的关系 .................................................................................. 89 
6.1.3  存储控制器的寄存器使用方法 .......................................................................... 91 
6.2  存储控制器操作实例:使用 SDRAM.......................................................................... 94 
6.2.1  代码详解及程序的复制、跳转过程 .................................................................. 94 
6.2.2  实例测试 .............................................................................................................. 97 
第 7章  内存管理单元MMU....................................................................................................... 98 
7.1  内存管理单元MMU介绍 ............................................................................................. 98 
7.1.1  S3C2410/S3C2440 MMU特性 ........................................................................... 98 
7.1.2  S3C2410/S3C2440 MMU地址变换过程 ........................................................... 99 
7.1.3  内存的访问权限检查 ........................................................................................ 107 
7.1.4  TLB的作用 ....................................................................................................... 109 
7.1.5  Cache的作用 ..................................................................................................... 110 
7.1.6  S3C2410/S3C2440 MMU、TLB、Cache的控制指令 .................................... 113 
7.2  MMU使用实例:地址映射 ........................................................................................ 113 
7.2.1  程序设计 ............................................................................................................ 113 
7.2.2  代码详解 ............................................................................................................ 114 
7.2.3  实例测试 ............................................................................................................ 124 
第 8章  NAND Flash控制器...................................................................................................... 125 
8.1  NAND Flash介绍和 NAND Flash控制器使用.......................................................... 125 
8.1.1  Flash介绍 .......................................................................................................... 125 
8.1.2  NAND Flash的物理结构.................................................................................. 127 
8.1.3  NAND Flash访问方法...................................................................................... 128 
8.1.4  S3C2410/S3C2440 NAND Flash控制器介绍 .................................................. 134 
8.2  NAND Flash控制器操作实例:读 Flash ................................................................... 135 
8.2.1  读 NAND Flash的步骤..................................................................................... 135 
8.2.2  代码详解 ............................................................................................................ 137 
第 9章  中断体系结构 ................................................................................................................ 143 
9.1  S3C2410/S3C2440中断体系结构 ............................................................................... 143 
9.1.1  ARM体系 CPU的 7种工作模式 .................................................................... 143 
9.1.2  S3C2410/S3C2440中断控制器 ........................................................................ 146 
9.1.3  中断控制器寄存器 ............................................................................................ 149 
9.2  中断控制器操作实例:外部中断 ............................................................................... 151 
9.2.1  按键中断代码详解 ............................................................................................ 151 
9.2.2  实例测试 ............................................................................................................ 158 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww 10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║4    目录第 1 章 
 
第 10章  系统时钟和定时器 ...................................................................................................... 159 
10.1  时钟体系及各类时钟部件 ......................................................................................... 159 
10.1.1  S3C2410/S3C2440时钟体系 .......................................................................... 159 
10.1.2  PWM定时器 ................................................................................................... 161 
10.1.3  WATCHDOG定时器....................................................................................... 164 
10.2  MPLL和定时器操作实例 ......................................................................................... 166 
10.2.1  程序设计 .......................................................................................................... 166 
10.2.2  代码详解 ........................................................................................................  166 
10.2.3  实例测试 .......................................................................................................... 170 
第 11章  通用异步收发器 UART............................................................................................... 171 
11.1  UART原理及 UART部件使用方法 ......................................................................... 171 
11.1.1  UART原理说明 .............................................................................................. 171 
11.1.2  S3C2410/S3C2440 UART的特性................................................................... 172 
11.1.3  S3C2410/S3C2440 UART的使用................................................................... 173 
11.2  UART操作实例 ......................................................................................................... 177 
11.2.1  代码详解 .......................................................................................................... 177 
11.2.2  实例测试 .......................................................................................................... 180 
第 12章  I2C接口........................................................................................................................ 181 
12.1  I2C总线协议及硬件介绍........................................................................................... 181 
12.1.1  I2C总线协议.................................................................................................... 181 
12.1.2  S3C2410/S3C2440 I2C总线控制器 ................................................................ 184 
12.2  I2C总线操作实例....................................................................................................... 187 
12.2.1  I2C接口 RTC芯片M41t11的操作方法........................................................ 187 
12.2.2  程序设计 .......................................................................................................... 188 
12.2.3  设置/读取M41t11的源码详解 ...................................................................... 188 
12.2.4  I2C实例的连接脚本........................................................................................ 195 
12.2.5  实例测试 .......................................................................................................... 196 
第 13章  LCD控制器 ................................................................................................................. 197 
13.1  LCD和 LCD控制器 .................................................................................................. 197 
13.1.1  LCD显示器 ..................................................................................................... 197 
13.1.2  S3C2410/S3C2440 LCD控制器介绍 ............................................................. 199 
13.2  TFT LCD显示实例 .................................................................................................... 210 
13.2.1  程序设计 .......................................................................................................... 210 
13.2.2  代码详解 .......................................................................................................... 210 
13.2.3  实例测试 .......................................................................................................... 221 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 1 章  目录    5║ 
 
第 14章  ADC和触摸屏接口..................................................................................................... 222 
14.1  ADC和触摸屏硬件介绍及使用................................................................................ 222 
14.1.1  S3C2410/S3C2440 ADC和触摸屏接口概述 ................................................. 222 
14.1.2  S3C3410/S3C2440 ADC接口的使用方法 ..................................................... 224 
14.1.3  触摸屏原理及接口 .......................................................................................... 226 
14.2  ADC和触摸屏操作实例............................................................................................ 230 
14.2.1  硬件设计 .......................................................................................................... 230 
14.2.2  程序设计 .......................................................................................................... 230 
14.2.3  测试 ADC的代码详解.................................................................................... 230 
14.2.4  测试触摸屏的代码详解 .................................................................................. 232 
14.2.5  实例测试 .......................................................................................................... 237 
第 3篇  嵌入式 Linux 系统移植篇 
第 15章  移植 U-Boot ................................................................................................................. 240 
15.1  Bootloader简介 .......................................................................................................... 240 
15.1.1  Bootloader的概念 ........................................................................................... 240 
15.1.2  Bootloader的结构和启动过程 ....................................................................... 241 
15.1.3  常用 Bootloader介绍 ...................................................................................... 246 
15.2  U-Boot分析与移植 .................................................................................................... 246 
15.2.1  U-Boot工程简介 ............................................................................................. 246 
15.2.2  U-Boot源码结构 ............................................................................................. 247 
15.2.3  U-Boot的配置、编译、连接过程 ................................................................. 249 
15.2.4  U-Boot的启动过程源码分析 ......................................................................... 257 
15.2.5  U-Boot的移植 ................................................................................................. 264 
15.2.6  U-Boot的常用命令 ......................................................................................... 288 
15.2.7  使用 U-Boot来执行程序 ................................................................................ 292 
第 16章  移植 Linux内核 .......................................................................................................... 293 
16.1  Linux版本及特点 ...................................................................................................... 293 
16.2  Linux移植准备 .......................................................................................................... 294 
16.2.1  获取内核源码 .................................................................................................. 294 
16.2.2  内核源码结构及Makefile分析...................................................................... 295 
16.2.3  内核的 Kconfig分析 ....................................................................................... 304 
16.2.4  Linux内核配置选项 ....................................................................................... 309 
16.3  Linux内核移植 .......................................................................................................... 313 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║6    目录第 1 章 
 
16.3.1  Linux内核启动过程概述 ............................................................................... 313 
16.3.2  修改内核以支持 S3C2410/S3C2440开发板 ................................................. 314 
16.3.3  修改MTD分区 ............................................................................................... 327 
16.3.4  移植 YAFFS文件系统 .................................................................................... 330 
16.3.5  编译、烧写、启动内核 .................................................................................. 333 
第 17章  构建 Linux根文件系统............................................................................................... 335 
17.1  Linux文件系统概述 .................................................................................................. 335 
17.1.1  Linux文件系统的特点 ................................................................................... 335 
17.1.2  Linux根文件系统目录结构............................................................................ 336 
17.1.3  Linux文件属性介绍 ....................................................................................... 340 
17.2  移植 Busybox.............................................................................................................. 341 
17.2.1  Busybox概述................................................................................................... 341 
17.2.2  init进程介绍及用户程序启动过程................................................................ 342 
17.2.3  编译/安装 Busybox.......................................................................................... 346 
17.3  使用 glibc库 ............................................................................................................... 350 
17.3.1  glibc库的组成 ................................................................................................. 350 
17.3.2  安装 glibc库 .................................................................................................... 351 
17.4  构建根文件系统 ......................................................................................................... 352 
17.4.1  构建 etc目录 ................................................................................................... 352 
17.4.2  构建 dev目录 .................................................................................................. 354 
17.4.3  构建其他目录 .................................................................................................. 356 
17.4.4  制作/使用 yaffs文件系统映象文件 ............................................................... 356 
17.4.5  制作/使用 jffs2文件系统映象文件................................................................ 360 
第 18章  Linux内核调试技术 ................................................................................................... 362 
18.1  内核打印函数 printk .................................................................................................. 362 
18.1.1  printk的使用 ................................................................................................... 362 
18.1.2  串口控制台 ...................................................................................................... 364 
18.2  内核源码级别的调试方法 ......................................................................................... 366 
18.2.1  内核调试工具 KGDB的作用与原理 ............................................................. 366 
18.2.2  给内核添加 KGDB功能支持 S3C2410/S3C2440 ......................................... 367 
18.2.3  结合可视化图形前端 DDD和 gdb来调试内核............................................ 372 
18.3  Oops信息及栈回溯.................................................................................................... 375 
18.3.1  Oops信息来源及格式..................................................................................... 375 
18.3.2  配置内核使 Oops信息的栈回溯信息更直观................................................ 376 
18.3.3  使用 Oops信息调试内核的实例.................................................................... 376 
18.3.4  使用 Oops的栈信息手工进行栈回溯............................................................ 380 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 1 章  目录    7║ 
 
第 4篇  嵌入式 Linux 设备驱动开发篇 
第 19章  字符设备驱动程序 ...................................................................................................... 384 
19.1  Linux驱动程序开发概述........................................................................................... 384 
19.1.1  应用程序、库、内核、驱动程序的关系 ...................................................... 384 
19.1.2  Linux驱动程序的分类和开发步骤................................................................ 385 
19.1.3  驱动程序的加载和卸载 .................................................................................. 387 
19.2  字符设备驱动程序开发 ............................................................................................. 387 
19.2.1  字符设备驱动程序中重要的数据结构和函数 .............................................. 387 
19.2.2  LED驱动程序源码分析 ................................................................................. 389 
第 20章  Linux异常处理体系结构............................................................................................ 396 
20.1  Linux异常处理体系结构概述................................................................................... 396 
20.1.1  Linux异常处理的层次结构............................................................................ 396 
20.1.2  常见的异常 ...................................................................................................... 400 
20.2  Linux中断处理体系结构........................................................................................... 401 
20.2.1  中断处理体系结构的初始化 .......................................................................... 401 
20.2.2  用户注册中断处理函数的过程 ...................................................................... 404 
20.2.3  中断的处理过程 .............................................................................................. 406 
20.2.4  卸载中断处理函数 .......................................................................................... 409 
20.3  使用中断的驱动程序示例 ......................................................................................... 410 
20.3.1  按键驱动程序源码分析 .................................................................................. 410 
20.3.2  测试程序情景分析 .......................................................................................... 415 
第 21章  扩展串口驱动程序移植 .............................................................................................. 419 
21.1  串口驱动程序框架概述 ............................................................................................. 419 
21.1.1  串口驱动程序术语介绍 .................................................................................. 419 
21.1.2  串口驱动程序的 4层结构 .............................................................................. 420 
21.2  扩展串口驱动程序移植 ............................................................................................. 423 
21.2.1  串口驱动程序低层代码分析 .......................................................................... 423 
21.2.2  修改代码以支持扩展串口 .............................................................................. 425 
21.2.3  测试扩展串口 .................................................................................................. 429 
第 22章  网卡驱动程序移植 ...................................................................................................... 431 
22.1  CS8900A网卡驱动程序移植 .................................................................................... 431 
22.1.1  CS8900A网卡特性 ......................................................................................... 431 
22.1.2  CS8900A网卡驱动程序修改 ......................................................................... 432 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║8    目录第 1 章 
 
22.2  DM9000网卡驱动程序移植...................................................................................... 441 
22.2.1  DM9000网卡特性........................................................................................... 441 
22.2.2  DM9000网卡驱动程序修改........................................................................... 442 
第 23章  IDE接口和 SD卡驱动程序移植 ............................................................................... 450 
23.1  IDE接口驱动程序移植 ............................................................................................. 450 
23.1.1  IDE接口相关概念介绍 .................................................................................. 450 
23.1.2  IDE接口驱动程序移植 .................................................................................. 452 
23.1.3  IDE接口驱动程序测试 .................................................................................. 461 
23.2  SD卡驱动程序移植 ................................................................................................... 464 
23.2.1  SD卡相关概念介绍 ........................................................................................ 464 
23.2.2  SD卡驱动程序移植 ........................................................................................ 465 
23.2.3  SD卡驱动程序测试 ........................................................................................ 472 
23.2.4  磁盘分区表 ...................................................................................................... 473 
第 24章  LCD和 USB驱动程序移植 ....................................................................................... 475 
24.1  LCD驱动程序移植 .................................................................................................... 475 
24.1.1  LCD和 USB键盘驱动程序框架 ................................................................... 475 
24.1.2  S3C2410/S3C2440 LCD控制器驱动程序移植 ............................................. 479 
24.2  USB驱动程序移植 .................................................................................................... 489 
24.2.1  USB驱动程序概述 ......................................................................................... 489 
24.2.2  配置内核支持 USB键盘、USB鼠标和 USB硬盘 ....................................  491 
24.2.3  USB设备的使用 ............................................................................................. 492 
第 5篇  嵌入式 Linux 系统应用开发篇 
第 25章  基于 Qtopia的 GUI开发 ............................................................................................ 496 
25.1  嵌入式 GUI介绍 ........................................................................................................ 496 
25.1.1  Linux桌面 GUI系统的发展 .......................................................................... 496 
25.1.2  嵌入式 Linux中的几种 GUI .......................................................................... 499 
25.2  Qtopia移植 ................................................................................................................. 501 
25.2.1  主机开发环境的搭建 ...................................................................................... 501 
25.2.2  交叉编译、安装 Qtopia 2.2.0 ......................................................................... 502 
25.2.3  开发自己的 Qt GUI程序 ................................................................................ 514 
25.2.4  在主机上使用模拟软件开发、调试嵌入式 Qt GUI程序 ............................ 518 
第 26章  基于 X的 GUI开发 .................................................................................................... 524 
26.1  X Window概述........................................................................................................... 524 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 1 章  目录    9║ 
 
26.1.1  X协议介绍 ...................................................................................................... 524 
26.1.2  窗口管理器(Window manager).................................................................. 526 
26.1.3  桌面环境(Desktop environment)................................................................ 526 
26.2  交叉编译工具包 Scratchbox ...................................................................................... 526 
26.2.1  Scratchbox介绍 ............................................................................................... 527 
26.2.2  安装 Scratchbox及编译工具 .......................................................................... 528 
26.2.3  在 Scratchbox里安装交叉编译工具链 .......................................................... 529 
26.2.4  安装其他开发工具 .......................................................................................... 535 
26.3  移植 X......................................................................................................................... 536 
26.3.1  编译软件的基本知识 ...................................................................................... 536 
26.3.2  编译 X的依赖软件 ......................................................................................... 539 
26.3.3  编译 Xorg......................................................................................................... 542 
26.4  移植Matchbox............................................................................................................ 547 
26.4.1  下载源代码 ...................................................................................................... 548 
26.4.2  编译Matchbox................................................................................................. 548 
26.4.3  运行、试验Matchbox..................................................................................... 550 
26.5  移植 GTK+ ................................................................................................................. 553 
26.5.1  GTK+介绍 ....................................................................................................... 553 
26.5.2  GTK+移植 ....................................................................................................... 553 
26.6  移植基于 GTK+/X的 GUI程序................................................................................ 555 
26.6.1  xterm移植........................................................................................................ 556 
26.6.2  gtkboard移植................................................................................................... 557 
26.6.3  裁剪文件系统 .................................................................................................. 560 
第 27章  Linux应用程序调试技术............................................................................................ 564 
27.1  使用 strace工具跟踪系统调用和信号 ...................................................................... 564 
27.1.1  strace介绍及移植............................................................................................ 564 
27.1.2  使用 strace来调试程序................................................................................... 565 
27.2  内存调试工具 ............................................................................................................. 568 
27.2.1  使用 memwatch进行内存调试....................................................................... 568 
27.2.2  其他内存工具介绍:mtrace、dmalloc、yamd.............................................. 571 
27.3  段错误的调试方法 ..................................................................................................... 573 
27.3.1  使用库函数 backtrace和 backtrace_symbols定位段错误 ............................ 573 
27.3.2  段错误调试实例 .............................................................................................. 574 
参考文献........................................................................................................................................ 578 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
 
 
 
 
 
 第 15 章  移植 U-Boot 
 
本章目标   
 
了解 Bootloader的作用及工作流程   
了解 U-Boot的代码结构、编译过程   
移植 U-Boot   
掌握常用的 U-Boot命令   
15.1  Bootloader简介 
15.1.1  Bootloader的概念 
1.Bootloader的引入 
从前面的硬件实例可以知道,系统上电之后,需要一段程序来进行初始化:关闭
WATCHDOG、改变系统时钟、初始化存储控制器、将更多的代码复制到内存中等。如果它
能将操作系统内核复制到内存中运行,无论从本地(比如 Flash)还是从远端(比如通过网络),
就称这段程序为 Bootloader。 
简单地说,Bootloader 就是这么一小段程序,它在系统上电时开始执行,初始化硬件设
备、准备好软件环境,最后调用操作系统内核。 
可以增强 Bootloader 的功能,比如增加网络功能、从 PC 上通过串口或网络下载文件、
烧写文件、将 Flash上压缩的文件解压后再运行等,这就是一个功能更为强大的 Bootloader,
也称为Monitor。实际上,在最终产品中用户并不需要这些功能,它们只是为了方便开发。 
Bootloader 的实现非常依赖于具体硬件,在嵌入式系统中硬件配置千差万别,即使是相
同的 CPU,它的外设(比如 Flash)也可能不同,所以不可能有一个 Bootloader 支持所有的
CPU、所有的电路板。即使是支持 CPU 架构比较多的 U-Boot,也不是一拿来就可以使用的
(除非里面的配置刚好与你的板子相同),需要进行一些移植。 
2.Bootloader的启动方式 
CPU上电后,会从某个地址开始执行。比如MIPS结构的 CPU会从 0xBFC00000取第一
条指令,而 ARM结构的 CPU则从地址 0x0000000开始。嵌入式开发板中,需要把存储器件
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.1  Bootloader简介    241║ 
 
ROM或 Flash等映射到这个地址,Bootloader就存放在这个地址开始处,这样一上电就可以
执行。 
在开发时,通常需要使用各种命令操作 Bootloader,一般通过串口来连接 PC和开发板,
可以在串口上输入各种命令、观察运行结果等。这也只是对开发人员才有意义,用户使用产
品时是不用接串口来控制 Bootloader 的。从这个观点来看,Bootloader 可以分为以下两种操
作模式(Operation Mode)。 
(1)启动加载(Boot loading)模式。 
上电后,Bootloader从板子上的某个固态存储设备上将操作系统加载到 RAM中运行,整
个过程并没有用户的介入。产品发布时,Bootloader工作在这种模式下。 
(2)下载(Downloading)模式。 
在这种模式下,开发人员可以使用各种命令,通过串口连接或网络连接等通信手段从主
机(Host)下载文件(比如内核映象、文件系统映象),将它们直接放在内存运行或是烧入
Flash类固态存储设备中。 
板子与主机间传输文件时,可以使用串口的 xmodem/ymodem/zmodem 协议,它们使用
简单,只是速度比较慢;还可以使用网络通过 tftp、nfs协议来传输,这时,主机上要开启 tftp、
nfs服务;还有其他方法,比如 USB等。 
像 Blob或 U-Boot等这样功能强大的 Bootloader通常同时支持这两种工作模式,而且允
许用户在这两种工作模式之间进行切换。比如,U-Boot在启动时处于正常的启动加载模式,
但是它会延时若干秒(这可以设置),等待终端用户按下任意键,而将 U-Boot切换到下载模
式。如果在指定时间内没有用户按键,则 U-Boot继续启动 Linux内核。 
15.1.2  Bootloader的结构和启动过程 
1.概述 
在移植之前先了解 Bootloader的一些通用概念,对理解它的代码会有所帮助。 
嵌入式 Linux系统从软件的角度通常可以分为以下 4个层次。 
(1)引导加载程序,包括固化在固件(firmware)中的 boot 代码(可选)和 Bootloader
两大部分。 
有些 CPU 在运行 Bootloader 之前先运行一段固化的程序(固件,firmware),比如 x86
结构的 CPU就是先运行BIOS中的固件,然后才运行硬盘第一个分区(MBR)中的Bootloader。 
在大多嵌入式系统中并没有固件,Bootloader是上电后执行的第一个程序。 
(2)Linux内核。 
特定于嵌入式板子的定制内核以及内核的启动参数。内核的启动参数可以是内核默认
的,或是由 Bootloader传递给它的。 
(3)文件系统。 
包括根文件系统和建立于 Flash 内存设备之上的文件系统。里面包含了 Linux 系统能够
运行所必需的应用程序、库等,比如可以给用户提供操作 Linux的控制界面的 shell程序、动
态连接的程序运行时需要的 glibc或 uClibc库等。 
(4)用户应用程序。 
特定于用户的应用程序,它们也存储在文件系统中。有时在用户应用程序和内核层之间
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║242    第 15 章  移植 U-Boot 
 
可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI有:Qtopia和MiniGUI等。 
显然,在嵌入系统的固态存储设备上有相应的分区来存储它们,如图 15.1所示为一个典
型的分区结构。 
 
图 15.1  嵌入式 Linux系统中的典型分区结构 
“Boot parameters”分区中存放一些可设置的参数,比如 IP地址、串口波特率、要传递给
内核的命令行参数等。正常启动过程中,Bootloader 首先运行,然后它将内核复制到内存中
(也有些内核可以在固态存储设备上直接运行),并且在内存某个固定的地址设置好要传递给
内核的参数,最后运行内核。内核启动之后,它会挂接(mount)根文件系统(“Root 
filesystem”),启动文件系统中的应用程序。 
2.Bootloader的两个阶段 
Bootloader的启动过程可以分为单阶段(Single Stage)、多阶段(Multi-Stage)两种。通
常多阶段的 Bootloader能提供更为复杂的功能以及更好的可移植性。从固态存储设备上启动
的 Bootloader 大多都是两阶段的启动过程。第一阶段使用汇编来实现,它完成一些依赖于 
CPU 体系结构的初始化,并调用第二阶段的代码;第二阶段则通常使用 C 语言来实现,这
样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。 
一般而言,这两个阶段完成的功能可以如下分类。 
(1)Bootloader第一阶段的功能。 
•  硬件设备初始化。 
•  为加载 Bootloader的第二阶段代码准备 RAM空间。 
•  复制 Bootloader的第二阶段代码到 RAM空间中。 
•  设置好栈。 
•  跳转到第二阶段代码的 C入口点。 
在第一阶段进行的硬件初始化一般包括:关闭WATCHDOG、关中断、设置 CPU的速度
和时钟频率、RAM 初始化等。这些并不都是必需的,比如 S3C2410/S3C2440 的开发板所使
用的 U-Boot中,就将 CPU的速度和时钟频率的设置放在第二阶段。 
甚至,将第二阶段的代码复制到 RAM空间中也不是必需的,对于 NOR Flash等存储设
备,完全可以在上面直接执行代码,只不过相比在 RAM中执行效率大为降低。 
(2)Bootloader第二阶段的功能。 
•  初始化本阶段要使用到的硬件设备。 
•  检测系统内存映射(memory map)。 
•  将内核映象和根文件系统映象从 Flash上读到 RAM空间中。 
•  为内核设置启动参数。 
•  调用内核。 
为了方便开发,至少要初始化一个串口以便程序员与 Bootloader进行交互。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.1  Bootloader简介    243║ 
 
所谓检测内存映射,就是确定板上使用了多少内存、它们的地址空间是什么。由于嵌入
式开发中 Bootloader多是针对某类板子进行编写,所以可以根据板子的情况直接设置,不需
要考虑可以适用于各类情况的复杂算法。 
Flash上的内核映象有可能是经过压缩的,在读到 RAM之后,还需要进行解压。当然,
对于有自解压功能的内核,不需要 Bootloader来解压。 
将根文件系统映象复制到 RAM中,这不是必需的。这取决于是什么类型的根文件系统,
以及内核访问它的方法。 
将内核存放在适当的位置后,直接跳到它的入口点即可调用内核。调用内核之前,下列
条件要满足。 
(1)CPU寄存器的设置。 
•  R0=0。 
•  R1=机器类型 ID;对于 ARM 结构的 CPU,其机器类型 ID 可以参见 linux/arch/arm/ 
tools/mach-types。 
•  R2=启动参数标记列表在 RAM中起始基地址。 
(2)CPU工作模式。 
•  必须禁止中断(IRQs和 FIQs)。 
•  CPU必须为 SVC模式。 
(3)Cache和MMU的设置。 
•  MMU必须关闭。 
•  指令 Cache可以打开也可以关闭。 
•  数据 Cache必须关闭。 
如果用 C语言,可以像下列示例代码一样来调用内核: 
 
void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, 
u32))KERNEL_RAM_BASE; 
… 
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start); 
3.Bootloader与内核的交互 
Bootloader与内核的交互是单向的,Bootloader将各类参数传给内核。由于它们不能同时
运行,传递办法只有一个:Bootloader 将参数放在某个约定的地方之后,再启动内核,内核
启动后从这个地方获得参数。 
除了约定好参数存放的地址外,还要规定参数的结构。Linux 2.4.x以后的内核都期望以
标记列表(tagged list)的形式来传递启动参数。标记,就是一种数据结构;标记列表,就是
挨着存放的多个标记。标记列表以标记 ATAG_CORE开始,以标记 ATAG_NONE结束。 
标记的数据结构为 tag,它由一个 tag_header结构和一个联合(union)组成。tag_header
结构表示标记的类型及长度,比如是表示内存还是表示命令行参数等。对于不同类型的标记
使用不同的联合(union),比如表示内存时使用 tag_mem32,表示命令行时使用 tag_cmdline。
数据结构 tag和 tag_header定义在 Linux内核源码的 include/asm/setup.h头文件中,如下所示: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║244    第 15 章  移植 U-Boot 
 
struct tag_header { 
          u32 size; 
          u32 tag; 
}; 
 
struct tag { 
          struct tag_header hdr; 
          union { 
                    struct tag_core  core; 
                    struct tag_mem32 mem; 
                    struct tag_videotext videotext; 
                    struct tag_ramdisk ramdisk; 
                    struct tag_initrd initrd; 
                    struct tag_serialnr serialnr; 
                    struct tag_revision revision; 
                    struct tag_videolfb videolfb; 
                    struct tag_cmdline cmdline; 
 
                    /* 
                     * Acorn specific 
                     */ 
                    struct tag_acorn acorn; 
 
                    /* 
                     * DC21285 specific 
                     */ 
                    struct tag_memclk memclk; 
          } u; 
}; 
 
下面以设置内存标记、命令行标记为例说明参数的传递。 
(1)设置标记 ATAG_CORE。 
标记列表以标记 ATAG_CORE 开始,假设 Bootloader 与内核约定的参数存放地址为
0x30000100,则可以以如下代码设置标记 ATAG_CORE: 
 
          params = (struct tag *) 0x30000100; 
 
          params->hdr.tag = ATAG_CORE; 
          params->hdr.size = tag_size (tag_core); 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.1  Bootloader简介    245║ 
 
          params->u.core.flags = 0; 
          params->u.core.pagesize = 0; 
          params->u.core.rootdev = 0; 
 
          params = tag_next (params); 
 
其中,tag_next定义如下,它指向当前标记的末尾: 
 
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) 
 
(2)设置内存标记。 
假设开发板使用的内存起始地址为 0x30000000,大小为 0x4000000,则内存标记可以如
下设置: 
 
          params->hdr.tag = ATAG_MEM; 
          params->hdr.size = tag_size (tag_mem32); 
 
          params->u.mem.start = 0x30000000; 
          params->u.mem.size  = 0x4000000; 
 
          params = tag_next (params); 
 
(3)设置命令行标记。 
命令行就是一个字符串,它被用来控制内核的一些行为。比如"root=/dev/mtdblock 2 
init=/linuxrc console=ttySAC0"表示根文件系统在MTD2分区上,系统启动后执行的第一个程
序为/linuxrc,控制台为 ttySAC0(即第一个串口)。 
命令行可以在 Bootloader中通过命令设置好,然后按如下构造标记传给内核。 
 
          char *p = "root=/dev/mtdblock 2 init=/linuxrc console=ttySAC0";  
          params->hdr.tag = ATAG_CMDLINE; 
          params->hdr.size = (sizeof (struct tag_header) + strlen (p) + 1 + 4) 
>> 2; 
 
          strcpy (params->u.cmdline.cmdline, p); 
 
          params = tag_next (params); 
 
(4)设置标记 ATAG_NONE。 
标记列表以标记 ATAG_NONE结束,如下设置: 
 
          params->hdr.tag = ATAG_NONE; 
          params->hdr.size = 0; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
 本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║246    第 15 章  移植 U-Boot 
 
15.1.3  常用 Bootloader介绍 
现在 Bootloader种类繁多,比如 x86上有 LILO、GRUB等。对于 ARM架构的 CPU,有
U-Boot、Vivi等。它们各有特点,下面列出 Linux的开放源代码的 Bootloader及其支持的体
系架构,如表 15.1所示。 
表 15.1 开放源码的 Linux引导程序 
Bootloader Monitor 描    述 X86 ARM PowerPC 
LILO 否 Linux磁盘引导程序 是 否 否 
GRUB 否 GNU的 LILO替代程序 是 否 否 
Loadlin 否 从 DOS引导 Linux 是 否 否 
ROLO 否 从 ROM引导 Linux而不需要 BIOS 是 否 否 
Etherboot 否 通过以太网卡启动 Linux系统的固件 是 否 否 
LinuxBIOS 否 完全替代 BUIS的 Linux引导程序 是 否 否 
BLOB 是 LART等硬件平台的引导程序 否 是 否 
U-Boot 是 通用引导程序 是 是 是 
RedBoot 是 基于 eCos的引导程序 是 是 是 
Vivi 是 Mizi公司针对 SAMSUNG的 ARM CPU
设计的引导程序 否 是 否 
 
对于本书使用的 S3C2410/S3C2440开发板,U-Boot和 Vivi是两个好选择。 
Vivi是Mizi公司针对 SAMSUNG的 ARM架构 CPU专门设计的,基本上可以直接使用,
命令简单方便。不过其初始版本只支持串口下载,速度较慢。在网上出现了各种改进版本:
支持网络功能、USB功能、烧写 YAFFS文件系统映象等。 
U-Boot则支持大多 CPU,可以烧写 EXT2、JFFS2文件系统映象,支持串口下载、网络下
载,并提供了大量的命令。相对于 Vivi,它的使用更复杂,但是可以用来更方便地调试程序。 
15.2  U-Boot分析与移植 
15.2.1  U-Boot工程简介 
U-Boot,全称为 Universal Boot Loader,即通用 Bootloader,是遵循 GPL条款的开放源
代码项目。其前身是由德国 DENX软件工程中心的Wolfgang Denk基于 8xxROM的源码创
建的 PPCBOOT工程。后来整理代码结构使得非常容易增加其他类型的开发板、其他架构的
CPU(原来只支持 PowerPC);增加更多的功能,比如启动 Linux、下载 S-Record格式的文件、
通过网络启动、通过 PCMCIA/CompactFLash/ATA disk/SCSI等方式启动。增加 ARM 架构
CPU及其他更多 CPU的支持后,改名为 U-Boot。 
它的名字“通用”有两层含义:可以引导多种操作系统、支持多种架构的 CPU。它支持
如下操作系统:Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、LynxOS 等,支持如
下架构的 CPU:PowerPC、MIPS、x86、ARM、NIOS、XScale等。 
U-Boot有如下特性。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    247║ 
 
•  开放源码。 
•  支持多种嵌入式操作系统内核,如 Linux、NetBSD、VxWorks、QNX、RTEMS、ARTOS、
LynxOS。 
•  支持多个处理器系列,如 PowerPC、ARM、x86、MIPS、XScale。 
•  较高的可靠性和稳定性。 
•  高度灵活的功能设置,适合 U-Boot调试、操作系统不同引导要求、产品发布等。 
•  丰富的设备驱动源码,如串口、以太网、SDRAM、Flash、LCD、NVRAM、EEPROM、
RTC、键盘等。 
•  较为丰富的开发调试文档与强大的网络技术支持。 
•  支持 NFS挂载、RAMDISK(压缩或非压缩)形式的根文件系统。 
•  支持 NFS挂载、从 Flash中引导压缩或非压缩系统内核。 
•  可灵活设置、传递多个关键参数给操作系统,适合系统在不同开发阶段的调试要求与
产品发布,尤对 Linux支持最为强劲。 
•  支持目标板环境变量多种存储方式,如 Flash、NVRAM、EEPROM。 
•  CRC32校验,可校验 Flash中内核、RAMDISK镜像文件是否完好。 
•  上电自检功能:SDRAM、Flash大小自动检测,SDRAM故障检测,CPU型号。 
•  特殊功能:XIP内核引导。 
可以从 http://sourceforge.net/projects/U-Boot获得 U-Boot的最新版本,如果使用过程中碰
到问题或是发现 Bug,可以通过邮件列表网站 http://lists.sourcef orge.net/ lists/ listinfo/U-Boot- 
users/获得帮助。 
15.2.2  U-Boot源码结构 
本书在U-Boot-1.1.6的基础上进行分析和移植,从 sourceforge网站下载U-Boot-1.1.6.tar.bz2
后解压即得到全部源码,U-Boot源码目录结构比较简单。 
U-Boot-1.1.6根目录下共有 26个子目录,可以分为 4类。 
(1)平台相关的或开发板相关的。 
(2)通用的函数。 
(3)通用的设备驱动程序。 
(4)U-Boot工具、示例程序、文档。 
这 26个子目录的功能与作用如表 15.2所示。 
表 15.2 U-Boot顶层目录说明 
目    录 特    性 解 释 说 明 
board 开发板相关 对应不同配置的电路板(即使 CPU相同),比如 smdk2410、sbc2410x
cpu 对应不同的 CPU,比如 arm920t、arm925t、i386等;在它们的子目录
下仍可以进一步细分,比如 arm920t下就有 at91rm9200、s3c24x0 
lib_i386类似 
平台相关 
某一架构下通用的文件 
续表 
目    录 特    性 解 释 说 明 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║248    第 15 章  移植 U-Boot 
 
include 
头文件和开发板配置文件,开发板的配置文件都放在 include/configs
目录下,U-Boot没有make menuconfig类似的莱单来进行可视化配置,
需要手动地修改配置文件中的宏定义 
lib_generic 通用的库函数,比如 printf等 
common 
通用的函数 
通用的函数,多是对下一层驱动程序的进一步封装 
disk 硬盘接口程序 
drivers 各类具体设备的驱动程序,基本上可以通用,它们通过宏从外面引入
平台/开发板相关的函数 
dtt 数字温度测量器或者传感器的驱动 
fs 文件系统 
nand_spl U-Boot一般从 ROM、NOR Flash等设备启动,现在开始支持从 NAND 
Flash启动,但是支持的 CPU种类还不多 
net 各种网络协议 
post 上电自检程序 
rtc 
通用的设备驱
动程序 
实时时钟的驱动 
doc 文档 开发、使用文档 
examples 示例程序 一些测试程序,可以使用 U-Boot下载后运行 
tools 工具 制作 S-Record、U-Boot格式映象的工具,比如 mkimage 
 
U-Boot中各目录间也是有层次结构的,虽然这种分法不是绝对的,但是在移植过程中可
以提供一些指导意义,如图 15.2所示。 
 
图 15.2  U-Boot顶层目录的层次结构 
比如 common/cmd_nand.c文件提供了操作 NAND Flash的各种命令,这些命令通过调用
drivers/nand/nand_base.c中的擦除、读写函数来实现。这些函数针对 NAND Flash的共性作了
一些封装,将平台/开发板相关的代码用宏或外部函数来代替。而这些宏与外部函数,如果与
平台相关,就要在下一层次的 cpu/xxx(xxx表示某型号的 CPU)中实现;如果与开发板相关,
就要在下一层次的 board/xxx 目录(xxx 表示某款开发板)中实现。本书移植的 U-Boot,就
是在 cpu/arm920t/s3c24x0目录下增加了一个 nand_flash.c文件来实现这些函数。 
以增加烧写 yaffs文件系统映象的功能为例,即在 common目录下的 cmd_nand.c中增加
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    249║ 
 
命令。比如 nand write.yaffs,这个命令要调用 drivers/nand/nand_util.c中的相应函数,针对 yaffs
文件系统的特点依次调用擦除、烧写函数。而这些函数依赖于 drivers/nand/nand_base.c、
cpu/arm920t/s3c24x0/nand_flash.c文件中的相关函数。 
目前 U-Boot-1.1.6支持 10种架构,根目录下有 10个类似 lib_i386的目录;31个型号(类
型)的 CPU,cpu目录下有 31个子目录;214种开发板,board目录下有 214个子目录,很
容易从中找到与自己的板子相似的配置,在上面稍作修改即可使用。 
15.2.3  U-Boot的配置、编译、连接过程 
1.U-Boot初体验 
U-Boot-1.1.6 中有几千个文件,要想了解对于某款开发板,使用哪些文件、哪个文件首
先执行、可执行文件占用内存的情况,最好的方法就是阅读它的Makefile。 
根据顶层 Readme 文件的说明,可以知道如果要使用开发板 board/,就先
执行“make _config”命令进行配置,然后执行“make all”,就可以生成如下 3
个文件。 
•  U-Boot.bin:二进制可执行文件,它就是可以直接烧入 ROM、NOR Flash的文件。 
•  U-Boot:ELF格式的可执行文件。 
•  U-Boot.srec:Motorola S-Record格式的可执行文件。 
对于 S3C2410的开发板,执行“make smdk2410_config”、“make all”后生成的 U-Boot.bin
可以烧入 NOR Flash中运行。启动后可以看到串口输出一些信息后进入控制界面,等待用户
的输入。 
对于 S3C2440的开发板,烧入上面生成的 U-Boot.bin,串口无输出,需要修改代码。 
在修改代码之前,先看看上面两个命令“make smdk2410_config”、“make all”做了什么
事情,以了解程序的流程,知道要修改哪些文件。 
另外,编译 U-Boot成功后,还会在它的 tools子目录下生成一些工具,比如 mkimage等。
将它们复制到/usr/local/bin 目录下,以后就可以直接使用它们了,比如编译内核时,会使用
mkimage来生成 U-Boot格式的内核映象文件 uImage。 
2.U-Boot的配置过程 
在顶层Makefile中可以看到如下代码: 
 
SRCTREE  := $(CURDIR) 
⋯⋯ 
MKCONFIG := $(SRCTREE)/mkconfig 
⋯⋯ 
smdk2410_config : unconfig  
          @$(MKCONFIG) $(@:_config=) arm arm920t smdk2410 NULL s3c24x0 
 
假定在 U-Boot-1.1.6 的根目录下编译,则其中的 MKCONFIG 就是根目录下的 mkconfig
文件。$(@:_config=)的结果就是将“smdk2410_config”中的“_config”去掉,结果为“smdk2410”。
所以“make smdk2410_config”实际上就是执行如下命令: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║250    第 15 章  移植 U-Boot 
 
./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0 
 
再来看看 mkconfig的作用,在 mkconfig文件开头第 6行给出了它的用法: 
 
06 # Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC] 
 
这里解释一下概念,对于 S3C2410、S3C2440,它们被称为 SoC(System on Chip),上面
除 CPU外,还集成了包括 UART、USB控制器、NAND Flash控制器等设备(称为片内外设)。
S3C2410/S3C2440中的 CPU为 ARM920T。 
下面分步骤分析 mkconfig的作用。 
(1)确定开发板名称 BOARD_NAME,相关代码如下: 
 
11 APPEND=no # Default: Create new config file 
12 BOARD_NAME="" # Name to print in make output 
13  
14 while [ $# -gt 0 ] ; do 
15  case "$1" in 
16  --) shift ; break ;; 
17  -a) shift ; APPEND=yes ;; 
18  -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;; 
19  *)  break ;; 
20  esac 
21 done 
22 
23 [ "${BOARD_NAME}" ] || BOARD_NAME="$1" 
 
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,其中没有“--”、
“-a”、“-n”等符号,所以第 14~22行没做任何事情。第 11、12行两个变量仍维持原来的值。 
执行完第 23行后,BOARD_NAME的值等于第 1个参数,即“smdk2410”。 
(2)创建到平台/开发板相关的头文件的链接。 
略过 mkconfig文件中的一些没有起作用的行,如下所示: 
 
30 # 
31 # Create link to architecture specific headers 
32 # 
33 if [ "$SRCTREE" != "$OBJTREE" ] ; then 
⋯⋯ 
45 else 
46   cd ./include 
47   rm -f asm 
48   ln -s asm-$2 asm 
49 fi 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    251║ 
 
50 
 
第 33行判断源代码目录和目标文件目录是否一样,可以选择在其他目录下编译 U-Boot,
这可以令源代码目录保持干净,可以同时使用不同的配置进行编译。不过本书是直接在源代
码目录下编译的,第 33行的条件不满足,将执行 else分支的代码。 
第 46~48 行进入 include 目录,删除 asm 文件(这是上一次配置时建立的链接文件),
然后再次建立 asm文件,并令它链接向 asm-$2目录,即 asm-arm。 
继续往下看代码: 
 
51 rm -f asm-$2/arch 
52  
53 if [ -z "$6" -o "$6" = "NULL" ] ; then 
54   ln -s ${LNPREFIX}arch-$3 asm-$2/arch 
55 else 
56   ln -s ${LNPREFIX}arch-$6 asm-$2/arch 
57 fi 
58  
59 if [ "$2" = "arm" ] ; then 
60   rm -f asm-$2/proc 
61   ln -s ${LNPREFIX}proc-armv asm-$2/proc 
62 fi 
63 
 
第 51行删除 asm-$2/arch目录,即 asm-arm/arch。 
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,$6为“s3c24x0”,
不为空,也不是“NULL”,所以第 53行的条件不满足,将执行 else分支。 
第 56行中,LNPREFIX为空,所以这个命令实际上就是“ln -s arch-$6 asm-$2/arch”,即
“ln -s arch-s3c24x0 asm-arm/arch”。 
第 60、61行重新建立 asm-arm/proc文件,并让它链接向 proc-armv目录。 
(3)创建顶层Makefile包含的文件 include/config.mk,如下所示: 
 
64 # 
65 # Create include file for Make 
66 # 
67 echo "ARCH   = $2" >  config.mk 
68 echo "CPU    = $3" >> config.mk 
69 echo "BOARD  = $4" >> config.mk 
70  
71 [ “$5” ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk 
72  
73 [ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║252    第 15 章  移植 U-Boot 
 
74 
 
对于“./mkconfig smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令,上面几行代码
创建的 config.mk文件内容如下: 
 
ARCH   = arm 
CPU    = arm920t 
BOARD  = smdk2410 
SOC    = s3c24x0 
 
(4)创建开发板相关的头文件 include/config.h,如下所示: 
 
75 # 
76 # Create board specific header file 
77 # 
78 if [ "$APPEND" = "yes" ]  # Append to existing config file 
79 then 
80   echo >> config.h 
81 else 
82   > config.h     # Create new config file 
83 fi 
84 echo "/* Automatically generated - do not edit */" >>config.h 
85 echo "#include " >>config.h 
86 
APPEND维持原值“no”,所以 config.h被重新建立,它的内容如下: 
/* Automatically generated - do not edit */ 
#include  
 
现在总结一下,配置命令“make smdk2410_config”,实际的作用就是执行“./mkconfig 
smdk2410 arm arm920t smdk2410 NULL s3c24x0”命令。假设执行“./mkconfig $1 $2 $3 $4 $5 
$6”命令,则将产生如下结果。 
(1)开发板名称 BOARD_NAME等于$1。 
(2)创建到平台/开发板相关的头文件的链接,如下所示: 
 
       ln -s asm-$2 asm  
       ln -s arch-$6 asm-$2/arch 
       ln -s proc-armv asm-$2/proc  # 如果$2不是 arm的话,此行没有 
 
(3)创建顶层Makefile包含的文件 include/config.mk,如下所示: 
 
ARCH   = $2 
CPU    = $3 
BOARD  = $4 
VENDOR = $5  # $5为空,或者是 NULL的话,此行没有 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    253║ 
 
SOC    = $6  # $6为空,或者是 NULL的话,此行没有 
 
(4)创建开发板相关的头文件 include/config.h,如下所示: 
 
/* Automatically generated - do not edit */ 
#include " 
 
从这 4个结果可以知道,如果要在 board目录下新建一个开发板的目录,
则在 include/config 目录下也要建立一个文件.h,里面存放的就是开发板
的配置信息。 
U-Boot还没有类似 Linux一样的可视化配置界面(比如使用 make menuconfig来配置),
要手动修改配置文件 include/config/.h来裁减、设置 U-Boot。 
配置文件中有以下两类宏。 
(1)一类是选项(Options),前缀为“CONFIG_”,它们用于选择 CPU、SOC、开发板
类型,设置系统时钟、选择设备驱动等。比如: 
 
#define CONFIG_ARM920T   1   /* This is an ARM920T Core */ 
#define CONFIG_S3C2410   1   /* in a SAMSUNG S3C2410 SoC     */ 
#define CONFIG_SMDK2410   1   /* on a SAMSUNG SMDK2410 Board  */ 
#define CONFIG_SYS_CLK_FREQ         12000000 /* the SMDK2410 has 12MHz input clock */ 
#define CONFIG_DRIVER_CS8900 1   /* we have a CS8900 on-board */ 
 
(2)另一类是参数(Setting),前缀为“CFG_”,它们用于设置 malloc 缓冲池的大小、
U-Boot的提示符、U-Boot下载文件时的默认加载地址、Flash的起始地址等。比如: 
 
#define CFG_MALLOC_LEN  (CFG_ENV_SIZE + 128*1024) 
#define CFG_PROMPT     "100ASK>" /* Monitor Command Prompt */ 
#define CFG_LOAD_ADDR  0x33000000 /* default load address */ 
#define PHYS_FLASH_1    0x00000000  /* Flash Bank #1 */ 
 
从下面的编译、连接过程可知,U-Boot中几乎每个文件都被编译和连接,但是这些文件
是否包含有效的代码,则由宏开关来设置。比如对于网卡驱动 drivers/cs8900.c,它的格式为: 
 
#include    /* 将包含配置文件 include/config/.h */ 
⋯ 
#ifdef CONFIG_DRIVER_CS8900 
/* 实际的代码 */ 
⋯ 
#endif /* CONFIG_DRIVER_CS8900 */ 
 
如果定义了宏 CONFIG_DRIVER_CS8900,则文件中包含有效的代码;否则,文件被注
释为空。 
可以这样认为,“CONFIG_”除了设置一些参数外,主要用来设置 U-Boot的功能、选择
使用文件中的哪一部分;而“CFG_”用来设置更细节的参数。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║254    第 15 章  移植 U-Boot 
 
3.U-Boot的编译、连接过程 
配置完后,执行“make all”即可编译,从Makefile中可以了解 U-Boot使用了哪些文件、
哪个文件首先执行、可执行文件占用内存的情况。 
先确定用到哪些文件,下面所示为Makefile中与 ARM相关的部分。 
 
117 include $(OBJTREE)/include/config.mk 
118 export  ARCH CPU BOARD VENDOR SOC 
119  
⋯ 
127 ifeq ($(ARCH),arm) 
128 CROSS_COMPILE = arm-linux- 
129 endif 
⋯ 
163 # load other configuration 
164 include $(TOPDIR)/config.mk 
165  
 
第 117、164行用于包含其他的 config.mk文件,第 117行所要包含文件的就是在上面的
配置过程中制作出来的 include/config.mk文件,其中定义了 ARCH、CPU、BOARD、SOC等
4个变量的值为 arm、arm920t、smdk2410、s3c24x0。 
第 164 行包含顶层目录的 config.mk 文件,它根据上面 4 个变量的值确定了编译器、编
译选项等。其中对我们理解编译过程有帮助的是 BOARDDIR、LDFLAGS的值,如下所示: 
 
88 BOARDDIR = $(BOARD) 
⋯ 
91 sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk # include board specific 
rules 
⋯ 
143 LDSCRIPT := $(TOPDIR)/board/$(BOARDDIR)/U-Boot.lds 
⋯ 
189 LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE) $(PLATFORM_LDFLAGS) 
 
在 board/smdk2410/config.mk中,定义了“TEXT_BASE = 0x33F80000”。所以,最终结
果如下:BOARDDIR 为 smdk2410;LDFLAGS 中有“-T board/smdk2410/U-Boot.lds -Ttext 
0x33F80000”字样。 
继续往下看Makefile: 
 
166 ######################################################################### 
167 # U-Boot objects....order is important (i.e. start must be first) 
168  
169 OBJS  = cpu/$(CPU)/start.o 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    255║ 
 
⋯ 
193 LIBS  = lib_generic/libgeneric.a 
194 LIBS += board/$(BOARDDIR)/lib$(BOARD).a 
195 LIBS += cpu/$(CPU)/lib$(CPU).a 
⋯ 
199 LIBS += lib_$(ARCH)/lib$(ARCH).a 
200 LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/ 
libjffs2.a \ 
201  fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a 
202 LIBS += net/libnet.a 
⋯ 
212 LIBS += $(BOARDLIBS) 
213 
⋯⋯ 
 
从第 169行得知,OBJS的第一个值为“cpu/$(CPU)/start.o”,即“cpu/arm920t/ start.o”。 
第193~213行指定了LIBS变量就是平台/开发板相关的各个目录、通用目录下相应的库,
比如:lib_generic/libgeneric.a、board/smdk2410/libsmdk2410.a、cpu/arm920t/ libarm920t.a、
lib_arm/libarm.a、fs/cramfs/libcramfs.a fs/fat/libfat.a等。 
OBJS、LIBS所代表的.o、.a文件就是 U-Boot的构成,它们通过如下命令由相应的源文
件(或相应子目录下的文件)编译得到。 
 
268 $(OBJS): 
269    $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@)) 
270  
271 $(LIBS): 
272    $(MAKE) -C $(dir $(subst $(obj),,$@)) 
273  
274 $(SUBDIRS): 
275    $(MAKE) -C $@ all 
276 
 
第 268、269两行的规则表示,对于 OBJS中的每个成员,都将进入 cpu/$(CPU)目录(即
cpu/arm920t)编译它们。现在 OBJS为 cpu/arm920t/start.o,它将由 cpu/arm920t/start.S编译
得到。 
第 271、272 两行的规则表示,对于 LIBS 中的每个成员,都将进入相应的子目录执行
“make”命令。这些子目录中的Makefile,结构相似,它们将Makefle中指定的文件编译、连
接成一个库文件。 
当所有的OBJS、LIBS所表示的.o和.a文件都生成后,就剩最后的连接了,这对应Makefile
中如下几行: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║256    第 15 章  移植 U-Boot 
 
246 $(obj)U-Boot.srec:  $(obj)U-Boot 
247     $(OBJCOPY) ${OBJCFLAGS} -O srec $< $@ 
248  
249 $(obj)U-Boot.bin: $(obj)U-Boot 
250     $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@ 
251 
⋯⋯ 
262 $(obj)U-Boot:  depend version $(SUBDIRS) $(OBJS) $(LIBS) $(LDSCRIPT) 
263      UNDEF_SYM=`$(OBJDUMP) -x $(LIBS) |sed  -n -e 's/.*\(__u 
_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\ 
264      cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \ 
265     --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \ 
266     -Map U-Boot.map -o U-Boot 
267  
 
先使用第 262~266 的规则连接得到 ELF 格式的 U-Boot,最后转换为二进制格式
U-Boot.bin、S-Record格式 U-Boot.srec。LDFLAGS确定了连接方式,其中的“-T board/smdk 
2410/U-Boot.lds -Ttext 0x33F80000”字样指定了程序的布局、地址。board/smdk2410/U-Boot.lds
文件如下: 
 
28 SECTIONS 
29 { 
30   . = 0x00000000; 
31  
32   . = ALIGN(4); 
33   .text      : 
34   { 
35     cpu/arm920t/start.o (.text) 
36     *(.text) 
37   } 
38  
39   . = ALIGN(4); 
40   .rodata : { *(.rodata) } 
41  
42   . = ALIGN(4); 
43   .data : { *(.data) } 
44  
45   . = ALIGN(4); 
46   .got : { *(.got) } 
47  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    257║ 
 
48   . = .; 
49   __u_boot_cmd_start = .; 
50   .u_boot_cmd : { *(.u_boot_cmd) } 
51   __u_boot_cmd_end = .; 
52  
53   . = ALIGN(4); 
54   __bss_start = .; 
55   .bss : { *(.bss) } 
56   _end = .; 
57 } 
 
从第 35 行可知,cpu/arm920t/start.o 被放在程序的最前面,所以 U-Boot 的入口点在
cpu/arm920t/start.S中。 
现在来总结一下 U-Boot的编译流程。 
(1)首先编译 cpu/$(CPU)/start.S,对于不同的 CPU,还可能编译 cpu/$(CPU)下的其他
文件。 
(2)然后,对于平台/开发板相关的每个目录、每个通用目录都使用它们各自的Makefile
生成相应的库。 
(3)将 1、2步骤生成的.o、.a文件按照 board/$(BOARDDIR)/config.mk文件中指定的代
码段起始地址、board/$(BOARDDIR)/U-Boot.lds连接脚本进行连接。 
(4)第 3 步得到的是 ELF 格式的 U-Boot,后面 Makefile 还会将它转换为二进制格式、
S-Record格式。 
15.2.4  U-Boot的启动过程源码分析 
本书使用的 U-Boot从 NOR Flash启动,下面以开发板 smdk2410的 U-Boot为例。 
U-Boot 属于两阶段的 Bootloader,第一阶段的文件为 cpu/arm920t/start.S 和 board/smdk 
2410/lowlevel_init.S,前者是平台相关的,后者是开发板相关的。 
1.U-Boot第一阶段代码分析 
(1)硬件设备初始化。 
依次完成如下设置:将 CPU的工作模式设为管理模式(svc),关闭WATCHDOG,设置
FCLK、HCLK、PCLK的比例(即设置 CLKDIVN寄存器),关闭MMU、CACHE。 
代码都在 cpu/arm920t/start.S中,注释也比较完善,读者有不明白的地方可以参考前面硬
件实验的相关章节。 
(2)为加载 Bootloader的第二阶段代码准备 RAM空间。 
所谓准备 RAM 空间,就是初始化内存芯片,使它可用。对于 S3C2410/S3C2440,通过
在 start.S 中调用 lowlevel_init 函数来设置存储控制器,使得外接的 SDRAM 可用。代码在
board/smdk2410/lowlevel_init.S中。 
 注意 
lowlevel_init.S 文件是开发板相关的,这表示如果外接的设备不一样,可以修改 lowlevel_init.S
文件中的相关宏。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║258    第 15 章  移植 U-Boot 
 
 
lowlevel_init 函数并不复杂,只是要注意这时的代码、数据都只保存在 NOR Flash 上,
内存中还没有,所以读取数据时要变换地址。代码如下: 
 
129 _TEXT_BASE: 
130  .word TEXT_BASE 
131  
132 .globl lowlevel_init 
133 lowlevel_init: 
134  /* memory control configuration */ 
135  /* make r0 relative the current location so that it */ 
136  /* reads SMRDATA out of FLASH rather than memory ! */ 
137  ldr     r0, =SMRDATA 
138  ldr r1, _TEXT_BASE 
139  sub r0, r0, r1 
140  ldr r1, =BWSCON /* Bus Width Status Controller */ 
141  add     r2, r0, #13*4 
142 0: 
143  ldr     r3, [r0], #4 
144  str     r3, [r1], #4 
145  cmp     r2, r0 
146  bne     0b 
147  
148  /* everything is fine now */ 
149  mov pc, lr 
150  
151  .ltorg 
152 /* the literal pools origin */ 
153  
154 SMRDATA:  /* 13个寄存器的值 */ 
155  .word ⋯⋯ 
156  .word ⋯⋯ 
 
第 137~139 行进行地址变换,因为这时候内存中还没有数据,不能使用连接程序时确
定的地址来读取数据。 
第 137 行中 SMRDATA 表示这 13 个寄存器的值存放的开始地址(连接地址),值为
0x33F8xxxx,处于内存中。 
第 138 行获得代码段的起始地址,它就是第 130 行中的“TEXT_BASE”,其值在
board/smdk2410/config.mk中定义为“TEXT_BASE = 0x33F80000”。 
第 139行将 0x33F8xxxx与 0x33F80000相减,这就是 13个寄存器值在 NOR Flash上存
放的开始地址。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    259║ 
 
(3)复制 Bootloader的第二阶段代码到 RAM 空间中。 
这里将整个 U-Boot 的代码(包括第一、第二阶段)都复制到 SDRAM 中,这在
cpu/arm920t/start.S中实现,如下所示: 
 
164 relocate:      /* 将 U-Boot复制到 RAM中 */ 
165  adr  r0, _start   /* r0:当前代码的开始地址 */ 
166  ldr  r1, _TEXT_BASE  /* r1:代码段的连接地址 */ 
167  cmp  r0, r1    /* 测试现在是在 Flash中还是在 RAM中 */ 
168   beq  stack_setup   /* 如果已经在RAM中(这通常是调试时直接下载到RAM中), 
             * 则不需要复制 
             */ 
169  
170   ldr  r2, _armboot_start /* _armboot_start在前面定义,是第一条指令的运行地址 */ 
171   ldr  r3, _bss_start  /* 在连接脚本U-Boot.lds中定义,是代码段的结束地址 */ 
172  sub  r2, r3, r2   /* r2 = 代码段长度 */ 
173  add  r2, r0, r2   /* r2 = NOR Flash上代码段的结束地址 */ 
174  
175 copy_loop: 
176  ldmia r0!, {r3-r10}  /* 从地址[r0]处获得数据 */ 
177  stmia r1!, {r3-r10}  /* 复制到地址[r1]处 */ 
178  cmp  r0, r2    /* 判断是否复制完毕 */ 
179  ble  copy_loop   /* 没复制完,则继续 */ 
 
(4)设置好栈。 
栈的设置灵活性很大,只要让 sp寄存器指向一段没有使用的内存即可。 
 
182  /* Set up the stack                         */ 
183 stack_setup: 
184  ldr r0, _TEXT_BASE                   /* _TEXT_BASE为代码段的开始地址,值为0x33F80000 */ 
185  sub r0, r0, #CFG_MALLOC_LEN       /* 代码段下面,留出一段内存以实现 malloc */ 
186  sub r0, r0, #CFG_GBL_DATA_SIZE  /* 再留出一段内存,存一些全局参数 */ 
187 #ifdef CONFIG_USE_IRQ 
188  sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)        /* 
IRQ、FIQ模式的栈 */ 
189 #endif 
190  sub sp, r0, #12                 /* 最后,留出 12字节的内存给 abort异常, 
                                             * 往下的内存就都是栈了  
                                             */ 
191 
到了这一步,读者可以知道内存的使用情况了,如图 15.3所示(图中与上面的划分稍有
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║260    第 15 章  移植 U-Boot 
 
不同,这是因为在 cpu/arm920t/cpu.c中的 cpu_init函数中才真正为 IRQ、FIQ模式划分了栈)。 
 
图 15.3  U-Boot内存使用情况 
(5)跳转到第二阶段代码的 C入口点。 
在跳转之前,还要清除 BSS段(初始值为 0、无初始值的全局变量、静态变量放在 BSS
段),代码如下: 
 
192 clear_bss: 
193  ldr  r0, _bss_start  /* BSS 段的开始地址,它的值在连接脚本
U-Boot.lds中确定 */ 
194  ldr  r1, _bss_end  /* BSS 段的结束地址,它的值在连接脚本
U-Boot.lds中确定 */ 
195  mov  r2, #0x00000000  
196  
197 clbss_l:str r2, [r0]  /* 往 BSS段中写入 0值 */ 
198  add  r0, r0, #4 
199  cmp  r0, r1 
200  ble  clbss_l 
201 
 
现在,C 函数的运行环境已经完全准备好,通过如下命令直接跳转(这之后,程序才在
内存中执行),它将调用 lib_arm/board.c中的 start_armboot函数,这是第二阶段的入口点。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    261║ 
 
 
223  ldr  pc, _start_armboot 
224  
225 _start_armboot: .word start_armboot 
226 
2.U-Boot第二阶段代码分析 
它与 15.1.2节中描述的Bootloader第二阶段所完成的功能基本上一致,只是顺序有点小差别。
另外,U-Boot在启动内核之前可以让用户决定是否进入下载模式,即进入U-Boot的控制界面。 
第二阶段从 lib_arm/board.c中的 start_armboot函数开始,程序流程如图 15.4所示。 
 
图 15.4  U-Boot第二阶段流程图 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║262    第 15 章  移植 U-Boot 
 
移植 U-Boot 的主要工作在于对硬件的初始化、驱动,所以下面讲解时将重点放在硬件
的操作上。 
(1)初始化本阶段要使用到的硬件设备。 
最主要的是设置系统时钟、初始化串口,只要这两个设置好了,就可以从串口看到打印
信息。 
board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在 board/smdk2410/ 
smdk2410.c 中实现。值得注意的是,board_init 函数中还保存了机器类型 ID,这将在调用内
核时传给内核,代码如下: 
 
          /* arch number of SMDK2410-Board */ 
          gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;    /* 值为 193 */ 
 
串口的初始化函数主要是 serial_init,它设置 UART 控制器,是 CPU 相关的函数,在
cpu/arm920t/s3c24x0/serial.c中实现。 
(2)检测系统内存映射(memory map)。 
对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/ 
smdk2410.c 中的 dram_init 函数指定了本开发板的内存起始地址为 0x30000000,大小为
0x4000000。代码如下: 
 
int dram_init (void) 
{ 
          gd->bd->bi_dram[0].start = PHYS_SDRAM_1;  /* 即 0x300000000 */ 
          gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; /* 即 0x4000000 */ 
 
          return 0; 
} 
 
这些设置的参数,将在后面向内核传递参数时用到。 
(3)U-Boot命令的格式。 
从图 15.3可以知道,即使是内核的启动,也是通过 U-Boot命令来实现的。U-Boot中每
个命令都通过 U_BOOT_CMD宏来定义,格式如下: 
 
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help") 
 
各项参数的意义如下。 
① name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)。 
② maxargs:最大的参数个数。 
③ repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次
运行。 
④ command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[])。 
⑤ usage:简短的使用说明,这是个字符串。 
⑥ help:较详细的使用说明,这是个字符串。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    263║ 
 
宏 U_BOOT_CMD在 include/command.h中定义,如下所示: 
 
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \ 
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, 
help} 
 
Struct_Section也是在 include/command.h中定义,如下所示: 
 
#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd"))) 
 
比如对于 bootm命令,它如下定义: 
 
U_BOOT_CMD( 
          bootm, CFG_MAXARGS, 1, do_bootm, 
           "string1", 
            "string2" 
); 
 
宏 U_BOOT_CMD扩展开后如下所示: 
 
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd"))) 
= {"bootm", CFG_MAXARGS, 1, do_bootm, "string1", "string2"}; 
 
对于每个使用 U_BOOT_CMD宏来定义的命令,其实都是在".u_boot_cmd"段中定义一个
cmd_tbl_t结构。连接脚本 U-Boot.lds中有如下代码: 
 
            __u_boot_cmd_start = .; 
            .u_boot_cmd : { *(.u_boot_cmd) } 
            __u_boot_cmd_end = .; 
 
程序中就是根据命令的名字在内存段_ _u_boot_cmd_start~_ _u_boot_cmd_end找到它的
cmd_tbl_t结构,然后调用它的函数(请参考 common/command.c中的 find_cmd函数)。 
内核的复制和启动,可以通过如下命令来完成:bootm从内存、ROM、NOR Flash中启
动内核,bootp则通过网络来启动,而 nboot从 NAND Flash启动内核。它们都是先将内核映
象从各种媒介中读出,存放在指定的位置;然后设置标记列表以给内核传递参数;最后跳到
内核的入口点去执行。具体实现的细节不再描述,有兴趣的读者可以阅读 common/ 
cmd_boot.c、common/cmd_net.c、common/cmd_nand.c来了解它们的实现。 
(4)为内核设置启动参数。 
U-Boot也是通过标记列表向内核传递参数。并且,在 15.1.2小节中内存标记、命令行标
记的示例代码就是取自 U-Boot中的 setup_memory_tags、setup_commandline_tag函数,它们
都是在 lib_arm/armlinux.c 中定义。一般而言,设置这两个标记就可以了,在配置文件
include/configs/smdk2410.h中增加如下两个配置项即可: 
 
#define CONFIG_SETUP_MEMORY_TAGS    1 
#define CONFIG_CMDLINE_TAG          1 
 
对于 ARM架构的 CPU,都是通过 lib_arm/armlinux.c中的 do_bootm_linux函数来启动内
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║264    第 15 章  移植 U-Boot 
 
核。这个函数中,设置标记列表,最后通过“theKernel (0, bd→bi_arch_number, bd→bi_boot_ 
params)”调用内核。其中,theKernel指向内核存放的地址(对于 ARM架构的 CPU,通常是
0x30008000),bd→bi_arch_number 就是前面 board_init 函数设置的机器类型 ID,而 bd→
bi_boot_params就是标记列表的开始地址。 
15.2.5  U-Boot的移植 
开发板 smdk2410的配置适用于大多数 S3C2410开发板,或是只需要极少的修改即可使
用。但是目前 U-Boot中没有对 S3C2440的支持,需要我们自己移植。 
本书基于的 S3C2410、S3C2440两款开发板,它们的外接硬件相同。 
•  BANK0外接容量为 1MB,位宽为 8的 NOR Flash芯片 AM29LV800。 
•  BANK3外接 10M网卡芯片 CS8900,位宽为 16。 
•  BANK6外接两片容量为 32MB、位宽为 16的 SDRAM芯片 K4S561632,组成容量为
64MB、位宽为 32的内存。 
•  通过 NAND Flash控制器外接容量为 64MB,位宽为 8的 NAND Flash芯片 K9S1208。 
对于 NOR Flash和 NAND Flash,如图 15.5所示划分它们的使用区域。由于 NAND Flash
的“位反转”现象比较常见,为保证数据的正确,在读写数据时需要使用 ECC校验。另外,
NAND Flash在使用过程中、运输过程中还有可能出现坏块。所以本书选择在 NOR Flash中
保存 U-Boot,在 NAND Flash中保存内核和文件系统,并在使用 U-Boot烧写内核、文件系
统时进行坏块检查、ECC校验。这样,即使 NAND Flash出现坏块导致内核或文件系统不能
使用,也可以通过 NOR Flash中的 U-Boot来重新烧写。 
 
图 15.5  开发板固态存储器分区划分 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    265║ 
 
smdk2410开发板已经支持 NOR Flash芯片 AM29LV800,U-Boot本身也已经支持 JFFS2
文件系统映象的烧写。下面一步一步移植 U-Boot(所有的修改都在补丁文件 U-boot-1.1.6_100 
ask_24 X 0.Patch里,读者可以直接打补丁),增加如下新功能。 
•  同时支持本书使用的 S3C2410和 S3C2440开发板。 
•  支持串口 xmodem协议。 
•  支持网卡芯片 CS8900。 
•  支持 NAND Flash读写。 
•  支持烧写 yaffs文件系统映象。 
1.同时支持 S3C2410和 S3C2440 
我们将在开发板 smdk2410的基础上进行移植。 
(1)新建一个开发板的相应目录和文件。 
为了不破坏原来的代码,在 board 目录下将 smdk2410 复制为 100ask24x0 目录,并将
board/100ask24x0/smdk2410.c改名为 100ask24x0.c。 
根据前面描述的配置过程可知,还要在 include/configs 目录下建立一个配置文件
100ask24x0.h,可以将 include/configs/smdk2410.h直接复制为 100ask24x0.h。 
还要修改两个Makefile,首先在顶层Makefile中增加如下两行: 
 
100ask24x0_config :     unconfig 
           @$(MKCONFIG) $(@:_config=) arm arm920t 100ask24x0 NULL s3c24x0 
 
然后在 board/100ask24x0/Makefile中进行如下修改(因为前面将 smdk2410.c文件改名为
100ask24x0.c了): 
 
COBJS := smdk2410.o flash.o 
改为: 
COBJS := 100ask24x0.o flash.o 
 
(2)修改 SDRAM的配置。 
SDRAM 的初始化在 U-Boot 的第一阶段完成,就是在 board/100ask24x0/lowlevel_init.S
文件中设置存储控制器。 
检查一下 BANK6的设置:位宽为 32,宏 B6_BWSCON刚好为 DW32(表示 32位),无
需改变;另外还要根据 HCLK设置 SDRAM的刷新参数,主要是 REFCNT寄存器。 
本书所用开发板的 HCLK都设为 100MHz,需要根据 SDRAM芯片的具体参数重新计算
REFCNT寄存器的值(请参考第 6章)。代码修改如下: 
 
126 #define REFCNT  1113 /* period=15.6µs, HCLK=60Mhz, (2048+1-15.6*60) */ 
改为 
126 #define REFCNT  0x4f4 /* period=7.8125µs, HCLK=100Mhz, 
(2048+1-7.8125*100) */ 
 
对于其他 BANK,比如网卡芯片 CS8900所在的 BANK2,原来的设置刚好匹配,无需更
改;而对于 BANK1、BANK2、BANK4、BANK5、BANK7,在 U-Boot中并没有使用到它们
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║266    第 15 章  移植 U-Boot 
 
外接的设备,也不需要理会。 
(3)增加对 S3C2440的支持。 
S3C2440是 S3C2410的改进版,它们的操作基本相似。不过在系统时钟的设置、NAND 
Flash 控制器的操作等方面有一些小差别。它们的 MPLL、UPLL 计算公式不一样,FCLK、
HCLK和 PCLK的分频化设置也不一样,这在下面的代码中可以看到。NAND Flash 控制器
的差别在增加对 NAND Flash的支持时讲述。 
本章的目标是令同一个U-Boot二进制代码既可以在 S3C2410上运行,也可以在 S3C2440
上运行。首先需要在代码中自动识别是 S3C2410还是 S3C2440,这可以通过读取 GSTATUS1
寄存器的值来分辨:0x32410000 表示 S3C2410,0x32410002 表示 S3C2410A,0x32440000
表示 S3C2440,0x32440001表示 S3C2440A。S3C2410和 S3C2410A、S3C2440和 S3C2440A,
对本书来说没有区别。 
对于 S3C2410开发板,将 FCLK设为 200MHz,分频比为 FCLK:HCLK:PCLK=1:2:4;对
于 S3C2440开发板,将 FCLK设为 400MHz,分频比为 FCLK:HCLK:PCLK=1:4:8。还将 UPLL
设为 48MHz,即 UCLK为 48MHz,以在内核中支持 USB控制器。 
首先修改 board/100ask24x0/100ask24x0.c中的 board_init函数,下面是修改后的代码: 
 
33 /* S3C2440: MPLL = (2*m * Fin) / (p * 2^s), UPLL = (m * Fin) / (p * 2^s) 
34  * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2 
35  */ 
36 #define S3C2440_MPLL_400MHZ  ((0x5c<<12)|(0x01<<4)|(0x01)) 
37 #define S3C2440_UPLL_48MHZ  ((0x38<<12)|(0x02<<4)|(0x02)) 
38 #define S3C2440_CLKDIV    0x05    /* FCLK:HCLK:PCLK = 1:4:8, UCLK = UPLL */ 
39  
40 /* S3C2410: Mpll,Upll = (m * Fin) / (p * 2^s) 
41  * m = M (the value for divider M)+ 8, p = P (the value for divider P) + 2 
42  */ 
43 #define S3C2410_MPLL_200MHZ  ((0x5c<<12)|(0x04<<4)|(0x00)) 
44 #define S3C2410_UPLL_48MHZ  ((0x28<<12)|(0x01<<4)|(0x02)) 
45 #define S3C2410_CLKDIV   0x03    /* FCLK:HCLK:PCLK = 1:2:4 */ 
46 
 
上面代码针对 S3C2410、S3C2440 分别定义了 MPLL、UPLL 寄存器的值。开发板输入
时钟为 12MHz(这在 include/configs/100ask24x0.h中的宏 CONFIG_SYS_CLK_FREQ中定义),
读者可以根据代码中的计算公式针对自己的开发板修改系统时钟。 
下面是针对 S3C2410、S3C2440分别使用不同的宏设置系统时钟。 
 
58 int board_init (void) 
59 { 
60      S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); 
61      S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO(); 
62  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
 次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    267║ 
 
63      /* 设置 GPIO */ 
64      gpio->GPACON = 0x007FFFFF; 
65      gpio->GPBCON = 0x00044555; 
66      gpio->GPBUP = 0x000007FF; 
67      gpio->GPCCON = 0xAAAAAAAA; 
68      gpio->GPCUP = 0x0000FFFF; 
69      gpio->GPDCON = 0xAAAAAAAA; 
70      gpio->GPDUP = 0x0000FFFF; 
71      gpio->GPECON = 0xAAAAAAAA; 
72      gpio->GPEUP = 0x0000FFFF; 
73      gpio->GPFCON = 0x000055AA; 
74      gpio->GPFUP = 0x000000FF; 
75      gpio->GPGCON = 0xFF95FFBA; 
76      gpio->GPGUP = 0x0000FFFF; 
77      gpio->GPHCON = 0x002AFAAA; 
78      gpio->GPHUP = 0x000007FF; 
79  
80      /* 同时支持 S3C2410和 S3C2440 */ 
81      if ((gpio->GSTATUS1 == 0x32410000) || (gpio->GSTATUS1 == 0x32410002)) 
82      { 
83          /* FCLK:HCLK:PCLK = 1:2:4 */ 
84          clk_power->CLKDIVN = S3C2410_CLKDIV; 
85  
86          /* 修改为异步总线模式 */ 
87          __asm__(    "mrc    p15, 0, r1, c1, c0, 0\n"    /* read ctrl register   */ 
88                         "orr    r1, r1, #0xc0000000\n"      /* Asynchronous         */ 
89                          "mcr     p15, 0, r1, c1, c0, 0\n"    /* write ctrl register  */ 
90                      :::"r1" 
91                      ); 
92          
93          /* 设置 PLL锁定时间 */ 
94          clk_power->LOCKTIME = 0xFFFFFF; 
95  
96          /* 配置 MPLL */ 
97          clk_power->MPLLCON = S3C2410_MPLL_200MHZ; 
98  
99          /* 配置 MPLL后,要延时一段时间再配置 UPLL */ 
100         delay (4000); 
101  
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║268    第 15 章  移植 U-Boot 
 
102         /* 配置 UPLL */ 
103         clk_power->UPLLCON = S3C2410_UPLL_48MHZ; 
104  
105         /* 再延时一会 */ 
106         delay (8000); 
107          
108         /* 机器类型 ID,这在调用 Linux内核时用到 */ 
109         gd->bd->bi_arch_number = MACH_TYPE_SMDK2410; 
110     } 
111     else 
112     { 
113         /* FCLK:HCLK:PCLK = 1:4:8 */ 
114         clk_power->CLKDIVN = S3C2440_CLKDIV; 
115  
116         /* 修改为异步总线模式 */ 
117         __asm__(    "mrc    p15, 0, r1, c1, c0, 0\n"    /* read ctrl register   */ 
118                        "orr    r1, r1, #0xc0000000\n"      /* Asynchronous         */ 
119                         "mcr    p15, 0, r1, c1, c0, 0\n"    /* write ctrl register  */ 
120                     :::"r1" 
121                     ); 
122  
123         /* 设置 PLL锁定时间 */ 
124         clk_power->LOCKTIME = 0xFFFFFF; 
125  
126         /* 配置 MPLL */ 
127         clk_power->MPLLCON = S3C2440_MPLL_400MHZ; 
128  
129         /* 配置 MPLL后,要延时一段时间再配置 UPLL */ 
130         delay (4000); 
131  
132         /* 配置 UPLL */ 
133         clk_power->UPLLCON = S3C2440_UPLL_48MHZ; 
134  
135         /* 再延时一会 */ 
136         delay (8000); 
137          
138         /* 机器类型 ID,这在调用 Linux内核时用到,这个值要与内核相对应 */ 
139         gd->bd->bi_arch_number = MACH_TYPE_S3C2440; 
140     } 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
 
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    269║ 
 
141  
142     /* 启动内核时,参数存放位置。这个值在构造标记列表时用到 */ 
143     gd->bd->bi_boot_params = 0x30000100; 
144  
145     icache_enable(); 
146     dcache_enable(); 
147  
148     return 0; 
149 } 
150 
 
最后一步:获取系统时钟的函数需要针对 S3C2410、S3C2440的不同进行修改。 
在后面设置串口波特率时需要获得系统时钟,就是在 U-Boot的第二阶段,lib_arm/board.c
中 start_armboot 函数调用 serial_init 函数初始化串口时,会调用 get_PCLK 函数。它在
cpu/arm920t/s3c24x0/speed.c中定义,与它相关的还有 get_HCLK、get_PLLCLK等函数。 
前面的 board_init 函数在识别出 S3C2410 或 S3C2440 后,设置了机器类型 ID:gd→bd→
bi_arch_number,后面的函数可以通过它来分辨是 S3C2410 还是 S3C2440。首先要在程序的
开头增加如下一行,这样才可以使用 gd变量。 
 
DECLARE_GLOBAL_DATA_PTR; 
 
S3C2410和 S3C2440的 MPLL、UPLL计算公式不一样,所以 get_PLLCLK函数也需要
修改,如下所示: 
 
56 static ulong get_PLLCLK(int pllreg) 
57 { 
58     S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); 
59     ulong r, m, p, s; 
60  
61     if (pllreg == MPLL) 
62          r = clk_power->MPLLCON; 
63     else if (pllreg == UPLL) 
64          r = clk_power->UPLLCON; 
65     else 
66          hang(); 
67  
68     m = ((r & 0xFF000) >> 12) + 8; 
69     p = ((r & 0x003F0) >> 4) + 2; 
70     s = r & 0x3; 
71  
72     /* 同时支持 S3C2410和 S3C2440 */ 
73     if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║270    第 15 章  移植 U-Boot 
 
74          return((CONFIG_SYS_CLK_FREQ * m) / (p << s)); 
75     else 
76          return((CONFIG_SYS_CLK_FREQ * m * 2) / (p << s));   /* S3C2440 */ 
77 } 
78 
 
由于分频系数的设置方法也不一样,get_HCLK、get_PCLK也需要修改。对于 S3C2410,
沿用原来的计算方法,else分支中是 S3C2440的代码,如下所示: 
 
85 /* for s3c2440 */ 
86 #define S3C2440_CLKDIVN_PDIVN  (1<<0) 
87 #define S3C2440_CLKDIVN_HDIVN_MASK (3<<1) 
88 #define S3C2440_CLKDIVN_HDIVN_1  (0<<1) 
89 #define S3C2440_CLKDIVN_HDIVN_2  (1<<1) 
90 #define S3C2440_CLKDIVN_HDIVN_4_8 (2<<1) 
91 #define S3C2440_CLKDIVN_HDIVN_3_6 (3<<1) 
92 #define S3C2440_CLKDIVN_UCLK   (1<<3) 
93  
94 #define S3C2440_CAMDIVN_CAMCLK_MASK (0xf<<0) 
95 #define S3C2440_CAMDIVN_CAMCLK_SEL (1<<4) 
96 #define S3C2440_CAMDIVN_HCLK3_HALF (1<<8) 
97 #define S3C2440_CAMDIVN_HCLK4_HALF (1<<9) 
98 #define S3C2440_CAMDIVN_DVSEN  (1<<12) 
99  
100 /* return HCLK frequency */ 
101 ulong get_HCLK(void) 
102 { 
103     S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); 
104     unsigned long clkdiv; 
105     unsigned long camdiv; 
106     int hdiv = 1; 
107  
108     /* 同时支持 S3C2410和 S3C2440 */ 
109     if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) 
110          return((clk_power->CLKDIVN & 0x2) ? get_FCLK()/2 : get_FCLK()); 
111     else 
112     { 
113          clkdiv = clk_power->CLKDIVN; 
114          camdiv = clk_power->CAMDIVN; 
115  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    271║ 
 
116          /* 计算分频比 */ 
117  
118          switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) { 
119          case S3C2440_CLKDIVN_HDIVN_1: 
120              hdiv = 1; 
121              break; 
122  
123          case S3C2440_CLKDIVN_HDIVN_2: 
124              hdiv = 2; 
125              break; 
126  
127          case S3C2440_CLKDIVN_HDIVN_4_8: 
128              hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4; 
129              break; 
130  
131          case S3C2440_CLKDIVN_HDIVN_3_6: 
132              hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3; 
133              break; 
134         } 
135  
136          return get_FCLK() / hdiv; 
137     } 
138 } 
139  
140 /* return PCLK frequency */ 
141 ulong get_PCLK(void) 
142 { 
143     S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER(); 
144     unsigned long clkdiv; 
145     unsigned long camdiv; 
146     int hdiv = 1; 
147  
148     /* 同时支持 S3C2410和 S3C2440 */ 
149     if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) 
150          return((clk_power->CLKDIVN & 0x1) ? get_HCLK()/2 : get_HCLK()); 
151     else 
152     {    
153          clkdiv = clk_power->CLKDIVN; 
154          camdiv = clk_power->CAMDIVN; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║272    第 15 章  移植 U-Boot 
 
155  
156          /* 计算分频比 */ 
157  
158          switch (clkdiv & S3C2440_CLKDIVN_HDIVN_MASK) { 
159          case S3C2440_CLKDIVN_HDIVN_1: 
160              hdiv = 1; 
161              break; 
162  
163          case S3C2440_CLKDIVN_HDIVN_2: 
164              hdiv = 2; 
165              break; 
166  
167          case S3C2440_CLKDIVN_HDIVN_4_8: 
168              hdiv = (camdiv & S3C2440_CAMDIVN_HCLK4_HALF) ? 8 : 4; 
169              break; 
170  
171          case S3C2440_CLKDIVN_HDIVN_3_6: 
172              hdiv = (camdiv & S3C2440_CAMDIVN_HCLK3_HALF) ? 6 : 3; 
173              break; 
174         } 
175  
176          return get_FCLK() / hdiv / ((clkdiv & S3C2440_CLKDIVN_PDIVN)? 2:1); 
177     }         
178 } 
179 
 
现在重新执行“make 100ask24x0_config”和“make all”生成的 U-Boot.bin文件既可以
运行于 S3C2410开发板,也可以运行于 S3C2440开发板。将它烧入 NOR Flash后启动,就可
以在串口工具(设置为 115200,8N1)中看到提示信息,可以输入各种命令操作 U-Boot了。 
(4)选择 NOR Flash的型号。 
但是,现在还无法通过 U-Boot命令烧写 NOR Flash。本书所用开发板中的 NOR Flash型
号为 AM29LV800,而配置文件 include/configs/100ask24x0.h 中的默认型号为 AM29LV400。
修改如下: 
 
#define CONFIG_AMD_LV400 1 /* uncomment this if you have a LV400 flash */ 
#if 0 
#define CONFIG_AMD_LV800 1 /* uncomment this if you have a LV800 flash */ 
#endif 
改为: 
#if 0 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    273║ 
 
#define CONFIG_AMD_LV400 1 /* uncomment this if you have a LV400 flash */ 
#endif 
#define CONFIG_AMD_LV800 1 /* uncomment this if you have a LV800 flash */ 
 
本例中 NOR Flash的操作函数在 board/100ask24x0/flash.c中实现,它支持 AM29LV400y
和 AM29LV800。对于其他型号的 NOR Flash,如果符合 CFI 接口标准,则可以在使用
drivers/cfi_flash.c中的接口函数;否则,只好自己编写了。如果要使用 cfi_flash.c,如下修改
两个文件。 
在 include/configs/100ask24x0.h中增加以下一行: 
 
#define CFG_FLASH_CFI_DRIVER    1 
 
在 board/100ask24x0/Makefile中去掉 flash.o: 
 
COBJS := 100ask24x0.o flash.o 
改为: 
COBJS := 100ask24x0.o 
 
修改好对 NOR Flash的支持后,重新编译 U-Boot:make clean、make all。运行后可以在
串口中看到如下字样: 
 
Flash:  1 MB 
 
现在可以使用 loadb、loady等命令通过串口下载文件,然后使用 erase、cp命令分别擦除、
烧写 NOR Flash了,它们的效率比 JTAG高好几倍。 
2.支持串口 xmodem协议 
上面的 loadb命令需要配合 Linux下的 kermit工具来使用,loady命令通过串口 ymodem
协议来传输文件。Windows下的超级终端虽然支持 ymodem,但是它的使用界面实在不友好。
而本书推荐使用的Windows工具 SecureCRT只支持 xmodem和 zmodem。为了方便在Windows
下开发,现在修改代码增加对 xmodem的支持,即增加一个命令 loadx。 
依照 loady的实现来编写代码,首先使用 U_BOOT_CMD宏来增加 loadx命令: 
 
/* 支持 xmodem, www.100ask.net */ 
U_BOOT_CMD(  
          loadx, 3, 0, do_load_serial_bin, 
          "loadx   - load binary file over serial line (xmodem mode)\n", 
          "[ off ] [ baud ]\n" 
          "    - load binary file over serial line" 
          " with offset 'off' and baudrate 'baud'\n" 
); 
 
其次,在 do_load_serial_bin函数中增加对 loadx命令的处理分支。也是依照 loady来实现: 
 
481     /* 支持 xmodem */ 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║274    第 15 章  移植 U-Boot 
 
482     if (strcmp(argv[0],"loadx")==0) { 
483          printf ("## Ready for binary (xmodem) download " 
484               "to 0x%08lX at %d bps...\n", 
485               offset, 
486               load_baudrate); 
487  
488          addr = load_serial_xmodem (offset); 
489  
490     } else if (strcmp(argv[0],"loady")==0) { 
491          printf ("## Ready for binary (ymodem) download " 
492               "to 0x%08lX at %d bps...\n", 
⋯⋯ 
 
第 481~490行就是为 loadx命令增加的代码。 
在第 288行调用 load_serial_xmodem函数,它是依照 load_serial_ymodem实现的一个新
函数: 
 
36 #if (CONFIG_COMMANDS & CFG_CMD_LOADB) 
37 /* 支持 xmodem */ 
38 static ulong load_serial_xmodem (ulong offset); 
39 static ulong load_serial_ymodem (ulong offset); 
40 #endif 
⋯⋯ 
995 /* 支持 xmodem */ 
996 static ulong load_serial_xmodem (ulong offset) 
997 { 
⋯⋯ 
1003         char xmodemBuf[1024];   /* 原来是ymodemBuf,这只是为了与函数名称一致 */ 
⋯⋯ 
1008        info.mode = xyzModem_xmodem; /* 原来是xyzModem_ymodem,对应ymodem */ 
⋯⋯ 
 
首先在文件开头增加 load_serial_xmodem函数的声明,然后复制 load_serial_ymodem函
数为 load_serial_xmodem,稍作修改。 
① 将局部数组 ymodemBuf改名为 xmodemBuf,并在后面使用到的地方统一修改。这只
是为了与函数名称一致。 
② info.mode的值从 xyzModem_ymodem改为 xyzModem_xmodem。 
重新编译、烧写 U-Boot.bin后,就可以使用 loadx命令下载文件了。 
3.支持网卡芯片 CS8900 
使用串口来传输文件的速率太低,现在增加对网卡芯片 CS8900的支持。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    275║ 
 
本书使用开发板的网卡芯片 CS8900 的连接方式与 smdk2410 完全一样,所以现在的
U-Boot中已经支持 CS8900了,它的驱动程序为 drivers/cs8900.c。只要在 U-Boot控制界面中
稍加配置就可以使用网络功能。使用网络之前,先设置开发板 IP地址、MAC地址,服务器
IP地址,比如可以在 U-Boot中执行以下命令: 
 
setenv ipaddr 192.168.1.17 
setenv ethaddr 08:00:3e:26:0a:5b 
setenv serverip 192.168.1.11 
saveenv 
 
然后就可以使用 tftp或 nfs命令下载文件了,注意:服务器上要开启 tftp或 nfs服务。比
如可以使用如下命令将 U-Boot.bin文件下载到内存 0x30000000中: 
 
tftp 0x30000000 U-Boot.bin 
或 
nfs 0x30000000 192.168.1.57:/work/nfs_root/U-Boot.bin 
 
可以修改配置文件,让网卡的各个默认值就是上面设置的值。在此之前,先了解网卡的
相关文件,这有助于移植代码以支持其他连接方式的 CS8900。 
首先,CS8900接在 S3C2410、S3C2440的 BANK3,位宽为 16,使用WAIT、nBE信号。
在设置存储控制器时要设置好 BANK3。代码在 board/100ask24x0/lowlevel_init.S中,如下所示: 
 
#define B3_BWSCON    (DW16 + WAIT + UBLB) 
⋯⋯ 
/* 时序参数 */ 
#define B3_Tacs     0x0 /*  0clk */ 
#define B3_Tcos     0x3 /*  4clk */ 
#define B3_Tacc     0x7 /* 14clk */ 
#define B3_Tcoh     0x1 /*  1clk */ 
#define B3_Tah     0x0 /*  0clk */ 
#define B3_Tacp     0x3  /*  6clk */ 
#define B3_PMC     0x0 /* normal */ 
 
接下来,还要确定 CS8900的基地址。这在配置文件 include/configs/100ask24x0.h中定义,
如下所示: 
 
#define CONFIG_DRIVER_CS8900 1   /* 使用 CS8900 */ 
#define CS8900_BASE    0x19000300 /* 基地址 */ 
#define CS8900_BUS16   1    /* 位宽为 16 */ 
 
网卡 CS8900的访问基址为 0x19000000,之所以再偏移 0x300是由它的特性决定的。 
最后,还是在配置文件 include/configs/100ask24x0.h中定义 CS8900的各个默认地址,如
下所示: 
 
#define CONFIG_ETHADDR  08:00:3e:26:0a:5b 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║276    第 15 章  移植 U-Boot 
 
#define CONFIG_NETMASK  255.255.255.0 
#define CONFIG_IPADDR  192.168.1.17 
#define CONFIG_SERVERIP  192.168.1.11 
 
如果要增加 ping 命令,还可以在配置文件 include/configs/100ask24x0.h 的宏 CONFIG_ 
COMMANDS中增加 CFG_CMD_PING,如下所示: 
 
#define CONFIG_COMMANDS \  
                          (CONFIG_CMD_DFL  | \ 
                          CFG_CMD_CACHE  | \ 
                          CFG_CMD_PING   | \ 
⋯⋯ 
4.支持 NAND Flash 
U-Boot 1.1.6中对 NAND Flash的支持有新旧两套代码,新代码在 drivers/nand目录下,
旧代码在 drivers/nand_legacy目录下。文档 doc/README.nand对这两套代码有所说明:使用
旧代码需要定义更多的宏,而新代码移植自 Linux 内核 2.6.12,它更加智能,可以自动识别
更多型号的 NAND Flash。目前之所以还保留旧的代码,是因为两个目标板 NETTA、
NETTA_ISDN 使用 JFFS 文件系统,它们还依赖于旧代码。当相关功能移植到新代码之后,
旧的代码将从 U-Boot中去除。 
要让 U-Boot 支持 NAND Flash,首先在配置文件 include/configs/100ask24x0.h 的宏
CONFIG_COMMANDS中增加 CFG_CMD_NAND,如下所示: 
 
#define CONFIG_COMMANDS \ 
                          (CONFIG_CMD_DFL  | \ 
                          CFG_CMD_CACHE  | \ 
                          CFG_CMD_PING   | \ 
                          CFG_CMD_NAND | \ 
⋯ 
 
然后选择使用哪套代码:在配置文件中定义宏 CFG_NAND_LEGACY则使用旧代码,否
则使用新代码。 
使用旧代码时,需要实现 drivers/nand_legacy/nand_legacy.c中使用到的各种宏,比如: 
 
#define NAND_WAIT_READY(nand) /* 等待 Nand Flash的状态为“就绪”,代码依赖于具体
的开发板 */ 
#define WRITE_NAND_COMMAND(d, adr) /* 写 NAND Flash命令,代码依赖于具体的开发板 */ 
 
本书使用新代码,下面讲述移植过程。 
代码的移植没有现成的文档,可以在配置文件 include/configs/100ask24x0.h 的宏
CONFIG_COMMANDS中增加 CFG_CMD_NAND后就编译代码,然后一个一个地解决出现
的错误。编译结果中出现的错误和警告如下: 
 
nand.h:412: error: 'NAND_MAX_CHIPS' undeclared here (not in a function) 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    277║ 
 
nand.c:35: error: 'CFG_MAX_NAND_DEVICE' undeclared here (not in a function) 
nand.c:38: error: 'CFG_NAND_BASE' undeclared here (not in a function) 
nand.c:35: error: storage size of 'nand_info' isn't known 
nand.c:37: error: storage size of 'nand_chip' isn't known 
nand.c:38: error: storage size of 'base_address' isn't known 
nand.c:37: warning: 'nand_chip' defined but not used 
nand.c:38: warning: 'base_address' defined but not used 
 
在配置文件 include/configs/100ask24x0.h中增加如下 3个宏就可以解决上述错误。在Flash
的驱动程序中,设备是逻辑上的概念,表示一组相同结构、访问函数相同的 Flash 芯片。在
本书所用开发板中,只有一个 NAND Flash芯片,所以设备数为 1,芯片数也为 1。 
 
#define CFG_NAND_BASE           0 /* 无实际意义:基地址,这在 board_nand_init
中重新指定 */ 
#define CFG_MAX_NAND_DEVICE    1 /* NAND Flash“设备”的数目为 1 */ 
#define NAND_MAX_CHIPS          1 /* 每个 NAND Flash“设备”由 1个 NAND Flash
“芯片”组成 */ 
 
修改配置文件后再次编译,现在只有一个错误了,“board_nand_init 函数未定义”,如下
所示: 
 
nand.c:50: undefined reference to `board_nand_init' 
 
调用 board_nand_init函数的过程为:NAND Flash的初始化入口函数是 nand_init,它在
lib_arm/board.c的 start_armboot函数中被调用;nand_init函数在 drivers/ nand/nand.c中实现,
它调用相同文件中的 nand_init_chip函数;nand_init_chip函数首先调用 board_nand_init函数
来初始化 NAND Flash设备,最后才是统一的识别过程。 
从 board_nand_init函数的名称就可以知道它是平台/开发板相关的函数,需要自己编写。
本书在 cpu/arm920t/s3c24x0目录下新建一个文件 nand_flash.c,在里面针对 S3C2410、S3C2440
实现了统一的 board_nand_init函数。 
在编写 board_nand_init函数的之前,需要针对 S3C2410、S3C2440 NAND Flash控制器
的不同来定义一些数据结构和函数: 
(1)在 include/s3c24x0.h文件中增加 S3C2440_NAND数据结构。 
 
typedef struct { 
     S3C24X0_REG32   NFCONF; 
     S3C24X0_REG32   NFCONT; 
     S3C24X0_REG32   NFCMD; 
     S3C24X0_REG32   NFADDR; 
     S3C24X0_REG32   NFDATA; 
     S3C24X0_REG32   NFMECCD0; 
     S3C24X0_REG32   NFMECCD1; 
     S3C24X0_REG32   NFSECCD; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║278    第 15 章  移植 U-Boot 
 
     S3C24X0_REG32   NFSTAT; 
     S3C24X0_REG32   NFESTAT0; 
     S3C24X0_REG32   NFESTAT1; 
     S3C24X0_REG32   NFMECC0; 
     S3C24X0_REG32   NFMECC1; 
     S3C24X0_REG32   NFSECC; 
     S3C24X0_REG32   NFSBLK; 
     S3C24X0_REG32   NFEBLK; 
} /*__attribute__((__packed__))*/ S3C2440_NAND; 
 
(2)在 include/s3c2410.h 文件中仿照 S3C2410_GetBase_NAND 函数定义 S3C2440_ 
GetBase_NAND函数。 
 
/* for s3c2440 */ 
static inline S3C2440_NAND * const S3C2440_GetBase_NAND(void) 
{ 
     return (S3C2440_NAND * const)S3C2410_NAND_BASE; 
} 
 
既然新的 NAND Flash 代码是从 Linux 内核 2.6.12 中移植来的,那么 cpu/arm920t/ 
s3c24x0/nand_flash.c文件也可以仿照内核中对 S3C2410、S3C2440的 NAND Flash进行初始
化的 drivers/mtd/nand/s3c2410.c文件来编写。为了方便阅读,先把 cpu/arm920t/s3c24x0/nand_ 
flash.c文件的代码全部列出来,如下所示: 
 
01 /* 
02  * s3c2410/s3c2440的 NAND Flash控制器接口 
03  * 修改自 Linux内核 2.6.13文件 drivers/mtd/nand/s3c2410.c  
04  */ 
05  
06 #include  
07  
08 #if (CONFIG_COMMANDS & CFG_CMD_NAND) && !defined(CFG_NAND_LEGACY) 
09 #include  
10 #include  
11  
12 DECLARE_GLOBAL_DATA_PTR; 
13  
14 #define S3C2410_NFSTAT_READY    (1<<0) 
15 #define S3C2410_NFCONF_nFCE     (1<<11) 
16  
17 #define S3C2440_NFSTAT_READY    (1<<0) 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    279║ 
 
18 #define S3C2440_NFCONT_nFCE     (1<<1) 
19  
20  
21 /* S3C2410:NAND Flash的片选函数 */ 
22 static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip) 
23 { 
24      S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND(); 
25  
26      if (chip == -1) { 
27           s3c2410nand->NFCONF |= S3C2410_NFCONF_nFCE; /* 禁止片选信号 */ 
28     } else { 
29           s3c2410nand->NFCONF &= ~S3C2410_NFCONF_nFCE; /* 使能片选信号 */ 
30     } 
31 } 
32  
33 /* S3C2410:命令和控制函数 
34  * 
35  * 注意,这个函数仅仅根据各种命令来修改“写地址”IO_ADDR_W 的值(这称为 tglx方法), 
36  * 这种方法使得平台/开发板相关的代码很简单。 
37  * 真正发出命令是在上一层 NAND Flash的统一的驱动中实现, 
38  * 它首先调用这个函数修改“写地址”,然后才分别发出控制、地址、数据序列。 
39 */ 
40 static void s3c2410_nand_hwcontrol(struct mtd_info *mtd, int cmd) 
41 { 
42      S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND(); 
43      struct nand_chip *chip = mtd->priv; 
44  
45      switch (cmd) { 
46      case NAND_CTL_SETNCE: 
47      case NAND_CTL_CLRNCE: 
48           printf("%s: called for NCE\n", __FUNCTION__); 
49           break; 
50  
51      case NAND_CTL_SETCLE: 
52           chip->IO_ADDR_W = (void *)&s3c2410nand->NFCMD; 
53           break; 
54  
55      case NAND_CTL_SETALE: 
56           chip->IO_ADDR_W = (void *)&s3c2410nand->NFADDR; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║280    第 15 章  移植 U-Boot 
 
57           break; 
58  
59           /* NAND_CTL_CLRCLE: */ 
60           /* NAND_CTL_CLRALE: */ 
61      default: 
62           chip->IO_ADDR_W = (void *)&s3c2410nand->NFDATA; 
63           break; 
64     } 
65 } 
66  
67 /* S3C2410:查询 NAND Flash状态 
68  * 
69  * 返回值:0表示忙,1表示就绪 
70  */ 
71 static int s3c2410_nand_devready(struct mtd_info *mtd) 
72 { 
73      S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND(); 
74  
75      return (s3c2410nand->NFSTAT & S3C2410_NFSTAT_READY); 
76 } 
77  
78  
79 /* S3C2440:NAND Flash的片选函数 */ 
80 static void s3c2440_nand_select_chip(struct mtd_info *mtd, int chip) 
81 { 
82      S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND(); 
83  
84      if (chip == -1) { 
85           s3c2440nand->NFCONT |= S3C2440_NFCONT_nFCE; /* 禁止片选信号 */ 
86      } else { 
87           s3c2440nand->NFCONT &= ~S3C2440_NFCONT_nFCE; /* 使能片选信号 */ 
88     } 
89 } 
90  
91 /* S3C2440:命令和控制函数,与 s3c2410_nand_hwcontrol函数类似 */ 
92 static void s3c2440_nand_hwcontrol(struct mtd_info *mtd, int cmd) 
93 { 
94      S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND(); 
95      struct nand_chip *chip = mtd->priv; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    281║ 
 
96  
97      switch (cmd) { 
98      case NAND_CTL_SETNCE: 
99      case NAND_CTL_CLRNCE: 
100           printf("%s: called for NCE\n", __FUNCTION__); 
101           break; 
102  
103      case NAND_CTL_SETCLE: 
104           chip->IO_ADDR_W = (void *)&s3c2440nand->NFCMD; 
105           break; 
106  
107      case NAND_CTL_SETALE: 
108            chip->IO_ADDR_W = (void *)&s3c2440nand->NFADDR; 
109            break; 
110  
111            /* NAND_CTL_CLRCLE: */ 
112            /* NAND_CTL_CLRALE: */ 
113      default: 
114           chip->IO_ADDR_W = (void *)&s3c2440nand->NFDATA; 
115           break; 
116     } 
117 } 
118  
119 /* S3C2440:查询 NAND Flash状态 
120  * 
121  * 返回值:0表示忙,1表示就绪 
122  */ 
123 static int s3c2440_nand_devready(struct mtd_info *mtd) 
124 { 
125      S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND(); 
126  
127      return (s3c2440nand->NFSTAT & S3C2440_NFSTAT_READY); 
128 } 
129  
130 /* 
131  * Nand flash硬件初始化: 
132  * 设置 NAND Flash的时序, 使能 NAND Flash控制器 
133  */ 
134 static void s3c24x0_nand_inithw(void) 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║282    第 15 章  移植 U-Boot 
 
135 { 
136      S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND(); 
137      S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND(); 
138  
139 #define TACLS   0 
140 #define TWRPH0  4 
141 #define TWRPH1  2 
142  
143      if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) 
144      { 
145           /* 使能 NAND Flash控制器,初始化 ECC,使能片选信号,设置时序 */ 
146          s3c2410nand->NFCONF = (1<<15)|(1<<12)|(1<<11)|(TACLS<<8)| (TWRPH0<<4)| 
(TWRPH1<<0); 
147     } 
148     else 
149     { 
150          /* 设置时序 */ 
151          s3c2440nand->NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); 
152          /* 初始化 ECC,使能 NAND Flash控制器,使能片选信号 */ 
153          s3c2440nand->NFCONT = (1<<4)|(0<<1)|(1<<0); 
154     } 
155 } 
156  
157 /* 
158  * 被 drivers/nand/nand.c调用, 初始化 NAND Flash硬件,初始化访问接口函数 
159  */ 
160 void board_nand_init(struct nand_chip *chip) 
161 { 
162      S3C2410_NAND * const s3c2410nand = S3C2410_GetBase_NAND(); 
163      S3C2440_NAND * const s3c2440nand = S3C2440_GetBase_NAND(); 
164  
165      s3c24x0_nand_inithw(); /* Nand flash硬件初始化 */ 
166  
167      if (gd->bd->bi_arch_number == MACH_TYPE_SMDK2410) { 
168           chip->IO_ADDR_R    = (void *)&s3c2410nand->NFDATA; 
169           chip->IO_ADDR_W    = (void *)&s3c2410nand->NFDATA; 
170           chip->hwcontrol    = s3c2410_nand_hwcontrol; 
171           chip->dev_ready    = s3c2410_nand_devready; 
172           chip->select_chip  = s3c2410_nand_select_chip; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    283║ 
 
173           chip->options      = 0; /* 设置位宽等,位宽为 8 */ 
174     } else { 
175           chip->IO_ADDR_R    = (void *)&s3c2440nand->NFDATA; 
176           chip->IO_ADDR_W    = (void *)&s3c2440nand->NFDATA; 
177           chip->hwcontrol    = s3c2440_nand_hwcontrol; 
178           chip->dev_ready    = s3c2440_nand_devready; 
179           chip->select_chip  = s3c2440_nand_select_chip; 
180           chip->options      = 0; /* 设置位宽等,位宽为 8 */ 
181     } 
182  
183     chip->eccmode       = NAND_ECC_SOFT; /* ECC校验方式:软件 ECC */ 
184 } 
185  
186 #endif 
 
文件中分别针对 S3C2410、S3C2440实现了 NAND Flash最底层访问函数,并进行了一
些硬件的设置(比如时序、使能 NAND Flash控制器等)。新的代码对 NAND Flash的封装做
得很好,只要向上提供底层初始化函数 board_nand_init来设置好平台/开发板相关的初始化、
提供底层接口即可。 
最后,只要将新建的 nand_flash.c文件编入 U-Boot中就可以擦除、读写 NAND Flash了。
如下修改 cpu/arm920t/s3c24x0/Makefile文件即可。 
修改前: 
 
COBJS   = i2c.o interrupts.o serial.o speed.o \ 
           usb_ohci.o 
 
修改后: 
 
COBJS   = i2c.o interrupts.o serial.o speed.o \ 
           usb_ohci.o nand_flash.o 
 
现在,可以使用新编译的 U-Boot.bin烧写内核映象到 NAND Flash去了。 
5.支持烧写 yaffs文件系统映象 
在实际生产中,可以通过烧片器等手段将内核、文件系统映象烧入固态存储设备中,
Bootloader 不需要具备烧写功能。但为了方便开发,通常在 Bootloader 中增加烧写内核、文
件系统映象文件的功能。 
增加了 NAND Flash 功能的 U-Boot 1.1.6 已经可以通过“nand write⋯”、“nand 
write.jffs2⋯”等命令来烧写内核,烧写 cramfs、jffs2文件系统映象文件。但是在 NAND Flash
上,yaffs文件系统的性能更佳,下面增加“nand write.yaffs⋯”命令以烧写 yaffs文件系统映
象文件。 
“nand write.yaffs⋯”字样的命令中,“nand”是具体命令,“write.yaffs⋯”是参数。nand
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║284    第 15 章  移植 U-Boot 
 
命令在 common/cmd_nand.c中实现如下: 
U_BOOT_CMD(nand, 5, 1, do_nand, 
     "nand    - NAND sub-system\n", 
     "info     - show available NAND devices\n" 
     "nand device [dev]  - show or set current device\n" 
     "nand read[.jffs2]  - addr off|partition size\n" 
     "nand write[.jffs2] - addr off|partiton size - read/write 'size' bytes 
starting\n" 
     "    at offset 'off' to/from memory address 'addr'\n" 
⋯ 
 
先在其中增加“nand write.yaffs⋯”的使用说明,如下所示: 
 
U_BOOT_CMD(nand, 5, 1, do_nand, 
     "nand    - NAND sub-system\n", 
     "info     - show available NAND devices\n" 
     "nand device [dev]  - show or set current device\n" 
     "nand read[.jffs2]  - addr off|partition size\n" 
     "nand write[.jffs2] - addr off|partiton size - read/write 'size' bytes 
starting\n" 
     "    at offset 'off' to/from memory address 'addr'\n" 
     "nand read.yaffs addr off size - read the 'size' byte yaffs image 
starting\n" 
     "    at offset 'off' to memory address 'addr'\n" 
     "nand write.yaffs addr off size - write the 'size' byte yaffs image 
starting\n" 
     "    at offset 'off' from memory address 'addr'\n" 
⋯ 
 
然后,在 nand命令的处理函数 do_nand中增加对“write.yaffs⋯”的支持。do_nand函数
仍在 common/cmd_nand.c中实现,代码修改如下: 
 
331             (!strcmp(s, ".jffs2") || !strcmp(s, ".e") || !strcmp(s, ".i"))) { 
⋯ 
354         }else if (  s != NULL && !strcmp(s, ".yaffs")){ 
355              if (read) { 
356                   /* read */ 
357                   nand_read_options_t opts; 
358                   memset(&opts, 0, sizeof(opts)); 
359                   opts.buffer = (u_char*) addr; 
360                   opts.length = size; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    285║ 
 
361                   opts.offset = off; 
362                   opts.readoob = 1; 
363                   opts.quiet      = quiet; 
364                   ret = nand_read_opts(nand, &opts); 
365              } else { 
366                   /* write */ 
367                   nand_write_options_t opts; 
368                   memset(&opts, 0, sizeof(opts)); 
369                   opts.buffer = (u_char*) addr; /* yaffs文件系统映象存放的地址 */ 
370                   opts.length = size;   /* 长度 */ 
371                   opts.offset = off;   /* 要烧写到的NAND Flash的偏移地址 */ 
372                   /* opts.forceyaffs = 1; */  /* 计算ECC码的方法,没有使用 */ 
373                   opts.noecc = 1;         /* 不需要计算 ECC,yaffs映
象中有 OOB数据 */ 
374                   opts.writeoob = 1;   /* 写 OOB区 */ 
375                   opts.blockalign = 1;  /* 每个“逻辑上的块”大小为 1个“物
理块” */ 
376                   opts.quiet      = quiet;  /* 是否打印提示信息 */ 
377                   opts.skipfirstblk = 1;   /* 跳过第一个可用块 */ 
378                   ret = nand_write_opts(nand, &opts); 
379              } 
380         } else { 
⋯ 
385         } 
386  
第 354~379行就是针对命令“nand read.yaffs⋯”、“nand write.yaffs⋯”增加的代码。有
兴趣的读者可以自己分析“if (read)”分支的代码,下面只讲解“else”分支,即“nand 
write.yaffs⋯”命令的实现。 
NAND Flash每一页大小为(512+16)字节(还有其他格式的 NAND Flash,比如每页大
小为(256+8)、(2048+64)等),其中的 512 字节就是一般存储数据的区域,16 字节称为 OOB
(Out Of Band)区。通常在 OOB区存放坏块标记、前面 512字节的 ECC校验码等。 
cramfs、jffs2 文件系统映象文件中并没有 OOB 区的内容,如果将它们烧入 NOR Flash
中,则是简单的“平铺”关系;如果将它们烧入 NAND Flash中,则 NAND Flash的驱动程
序首先根据 OOB的标记略过坏块,然后将一页数据(512字节)写入后,还会计算这 512字
节的 ECC校验码,最后将它写入 OOB区,如此循环。cramfs、jffs2文件系统映象文件的大
小通常是 512的整数倍。 
而 yaffs文件系统映象文件的格式则跟它们不同,文件本身就包含了 OOB区的数据(里
面有坏块标记、ECC校验码、其他 yaffs相关的信息)。所以烧写时,不需要再计算 ECC值,
首先检查是否坏块(是则跳过),然后写入 512字节的数据,最后写入 16字节的 OOB数据,
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║286    第 15 章  移植 U-Boot 
 
如此循环。yaffs文件系统映象文件的大小是(512+16)的整数倍。 
 注意 烧写 yaffs文件系统映象时,分区上第一个可用的(不是坏块)块也要跳过。 
 
下面分析上面的代码。 
第 369~371行设置源地址、目的地址、长度。烧写 yaffs文件系统映象前,一般通过网
络将它下载到内存某个地址处(比如 0x30000000),然后通过类似“nand write.yaffs 0x30000000 
0x00A00000 $(filesize)”的命令烧到 NAND Flash的偏移地址 0x00A00000处。对于这个命令,
第 369行中 opts.buffer等于 0x30000000,第 370行中 opts.length等于$(filesize)的值,就是前
面下载的文件的大小,第 371行中的 opts.offset等于 0x00A00000。 
这里列出不使用的第 372行,是因为 opts.forceyaffs这个名字很有欺骗性,它其实是指计
算 ECC校验码的一种方法。烧写 yaffs文件系统映象时,不需要计算 ECC校验码。 
第 373、374行指定烧写数据时不计算 ECC校验码、而是烧入文件中的 OOB数据。 
第 375行指定“逻辑块”的大小,“逻辑块”可以由多个“物理块”组成,在 yaffs文件
系统映象中,它们是 1:1的关系。 
第 377行的 opts.skipfirstblk是新加的项,nand_write_options_t结构中没有 skipfirstblk成
员。它表示烧写时跳过第一个可用的逻辑块,这是由 yaffs文件系统的特性决定的。 
既然 skipfirstblk 是在 nand_write_options_t 结构中新加的项,那么就要重新定义
nand_write_options_t结构,并在下面调用的 nand_write_opts函数中对它进行处理。 
首先在 include/nand.h中进行如下修改,增加 skipfirstblk成员。 
 
struct nand_write_options {  
          u_char *buffer; /* memory block containing image to write */ 
          ulong length;  /* number of bytes to write */ 
          ulong offset;  /* start address in NAND */ 
          int quiet;   /* don't display progress messages */ 
          int autoplace;  /* if true use auto oob layout */ 
          int forcejffs2; /* force jffs2 oob layout */ 
          int forceyaffs; /* force yaffs oob layout */ 
          int noecc;   /* write without ecc */ 
          int writeoob;  /* image contains oob data */ 
          int pad;   /* pad to page size */ 
          int blockalign;  /* 1|2|4 set multiple of eraseblocks to align to */ 
          int skipfirstblk; /* 新加,烧写时跳过第一个可用的逻辑块 */  
}; 
typedef struct nand_write_options nand_write_options_t; 
 
然后,修改 nand_write_opts函数,增加对 skipfirstblk成员的支持。它在 drivers/nand/nand_ 
util.c文件中,下面的第 301、第 421~424行的新加的。 
 
285 int nand_write_opts(nand_info_t *meminfo, const nand_write_options_t 
*opts) 
286 { 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    287║ 
 
⋯ 
300     int result; 
301     int skipfirstblk = opts->skipfirstblk; 
⋯ 
397          while (blockstart != (mtdoffset & (~erasesize_blockalign+1))) { 
⋯ 
419                   } 
420  
421                   if (baderaseblock) { 
422                        mtdoffset = blockstart 
423                             + erasesize_blockalign; 
424                   } 
⋯ 
 
进行了上面的移植后,U-Boot 已经可以烧写 yaffs 文件系统映象了。由于前面设置
“opts.noecc = 1”不使用 ECC校验码,在烧写过程中会出现很多的提示信息,如下所示: 
 
Writing data without ECC to NAND-FLASH is not recommended 
 
可以修改 drivers/nand/nand_base.c文件的 nand_write_page函数,将它去掉。 
修改前: 
 
917 case NAND_ECC_NONE: 
918   printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not 
recommended\n"); 
 
修改后: 
 
917 case NAND_ECC_NONE: 
918   //printk (KERN_WARNING "Writing data without ECC to NAND-FLASH is not 
recommended\n"); 
 
6.修改默认配置参数以方便使用 
前面移植网卡芯片 CS8900 时,已经设置过默认 IP 地址等。为了使用 U-Boot 时减少一
些设置,现在修改配置文件 include/configs/100ask24x0.h,增加默认配置参数,其中一些在移
植过程中已经增加的选项这里也再次说明。 
(1)Linux启动参数。 
增加如下 3个宏: 
 
#define CONFIG_SETUP_MEMORY_TAGS     1  /* 向内核传递内存分布信息 */ 
#define CONFIG_CMDLINE_TAG           1  /* 向内核传递命令行参数 */ 
/* 默认命令行参数 */ 
#define CONFIG_BOOTARGS         "noinitrd root=/dev/mtdblock 2 init=/linuxrc 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║288    第 15 章  移植 U-Boot 
 
console=ttySAC0" 
(2)自动启动命令。 
增加如下 2个宏: 
 
/* 自动启动前延时 3s */ 
#define CONFIG_BOOTDELAY  3 
/* 自动启动的命令 */ 
#define CONFIG_BOOTCOMMAND   "nboot 0x32000000 0 0; bootm 0x32000000" 
 
自动启动时(开机 3s内无输入),首先执行“nboot 0x32000000 0 0”命令将第 0个 NAND 
Flash偏移地址 0上的映象文件复制到内存 0x32000000中;然后执行“bootm 0x32000000”
命令启动内存中的映象。 
(3)默认网络设置。 
根据具体网络环境增加、修改下面 4个宏: 
 
#define CONFIG_ETHADDR  08:00:3e:26:0a:5b 
#define CONFIG_NETMASK  255.255.255.0 
#define CONFIG_IPADDR  192.168.1.17 
#define CONFIG_SERVERIP  192.168.1.11 
15.2.6  U-Boot的常用命令 
1.U-Boot的常用命令的用法 
进入 U-Boot控制界面后,可以运行各种命令,比如下载文件到内存,擦除、读写 Flash,
运行内存、NOR Flash、NAND Flash中的程序,查看、修改、比较内存中的数据等。 
使用各种命令时,可以使用其开头的若干个字母代替它。比如 tftpboot命令,可以使用 t、
tf、tft、tftp等字母代替,只要其他命令不以这些字母开头即可。 
当运行一个命令之后,如果它是可重复执行的(代码中使用 U_BOOT_CMD定义这个命
令时,第 3个参数是 1),若想再次运行可以直接输入回车。 
U-Boot接收的数据都是十六进制,输入时可以省略前缀 0x、0X。 
下面介绍常用的命令。 
(1)帮助命令 help。 
运行 help 命令可以看到 U-Boot 中所有命令的作用,如果要查看某个命令的使用方法,
运行“help 命令名”,比如“help bootm”。 
可以使用“?”来代替“help”,比如直接输入“?”、“? bootm”。 
(2)下载命令。 
U-Boot支持串口下载、网络下载,相关命令有:loadb、loads、loadx、loady和 tftpboot、nfs。 
前几个串口下载命令使用方法相似,以 loadx 命令为例,它的用法为“loadx [ off ] 
[ baud ]”。“[]”表示里面的参数可以省略,off表示文件下载后存放的内存地址,baud表示使
用的波特率。如果 baud参数省略,则使用当前的波特率;如果 off参数省略,存放的地址为
配置文件中定义的宏 CFG_LOAD_ADDR。 
tftpboot命令使用 TFTP协议从服务器下载文件,服务器的 IP地址为环境变量 serverip。
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    289║ 
 
用法为“tftpboot [loadAddress] [bootfilename]”,loadAddress表示文件下载后存放的内存地址,
bootfilename表示要下载的文件的名称。如果 loadAddress省略,存放的地址为配置文件中定
义的宏 CFG_LOAD_ADDR;如果 bootfilename省略,则使用开发板的 IP地址构造一个文件
名,比如开发板 IP为 192.168.1.17,则默认的文件名为 C0A80711.img。 
nfs命令使用 NFS协议下载文件,用法为“nfs [loadAddress] [host ip addr:bootfilename]”。
“loadAddress、bootfilename”的意义与 tftpboot命令一样,“host ip addr”表示服务器的 IP地
址,默认为环境变量 serverip。 
下载文件成功后,U-Boot会自动创建或更新环境变量 filesize,它表示下载的文件的长度,
可以在后续命令中使用“$(filesize)”来引用它。 
(3)内存操作命令。 
常用的命令有:查看内存命令 md、修改内存命令 md、填充内存命令 mw、复制命令 cp。
这些命令都可以带上后缀“.b”、“.w”或“.l”,表示以字节、字(2 个字节)、双字(4 个字
节)为单位进行操作。比如“cp.l 30000000 31000000 2”将从开始地址 0x30000000处,复制
2个双字到开始地址为 0x31000000的地方。 
md命令用法为“md[.b, .w, .l] address [count]”,表示以字节、字或双字(默认为双字)
为单位,显示从地址 address开始的内存数据,显示的数据个数为 count。 
mm命令用法为“mm[.b, .w, .l] address”,表示以字节、字或双字(默认为双字)为单位,
从地址 address开始修改内存数据。执行 mm命令后,输入新数据后回车,地址会自动增加,
按“Ctrl+C”键退出。 
mw命令用法为“mw[.b, .w, .l] address value [count]”,表示以字节、字或双字(默认为双
字)为单位,往开始地址为 address的内存中填充 count个数据,数据值为 value。 
cp命令用法为“cp[.b, .w, .l] source target count”,表示以字节、字或双字(默认为双字)
为单位,从源地址 source的内存复制 count个数据到目的地址的内存。 
(4)NOR Flash操作命令。 
常用的命令有查看 Flash信息的 flinfo命令、加/解写保护命令 protect、擦除命令 erase。
由于 NOR Flash的接口与一般内存相似,所以一些内存命令可以在 NOR Flash上使用,比如
读 NOR Flash时可以使用 md、cp命令,写 NOR Flash时可以使用 cp命令(cp根据地址分辨
出是 NOR Flash,从而调用 NOR Flash驱动完成写操作)。 
直接运行“flinfo”即可看到 NOR Flash的信息,有 NOR Flash的型号、容量、各扇区的
开始地址、是否只读等信息。比如对于本书基于的开发板,flinfo命令的结果如下: 
 
Bank # 1: AMD: 1x Amd29LV800BB (8Mbit) 
  Size: 1 MB in 19 Sectors 
  Sector Start Addresses: 
    00000000 (RO) 00004000 (RO) 00006000 (RO) 00008000 (RO) 00010000 (RO) 
    00020000 (RO) 00030000       00040000       00050000        00060000      
    00070000       00080000       00090000       000A0000        000B0000      
    000C0000       000D0000       000E0000       000F0000 (RO) 
 
其中的 RO表示该扇区处于写保护状态,只读。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║290    第 15 章  移植 U-Boot 
 
对于只读的扇区,在擦除、烧写它之前,要先解除写保护。最简单的命令为“protect off 
all”,解除所有 NOR Flash的写保护。 
erase命令常用的格式为“erase start end”,擦除的地址范围为 start~end;“erase start +len”,
擦除的地址范围为 start~(star+tlen−1),“erase all”,表示擦除所有 NOR Flash。 
 
 注意 
其中的地址范围,刚好是一个扇区的开始地址到另一个(或同一个)扇区的结束地址。比如要
擦除 Amd29LV800BB的前 5个扇区,执行的命令为“erase 0 0x2ffff”,而非“erase 0 0x30000”。
 
(5)NAND Flash操作命令。 
NAND Flash 操作命令只有一个:nand,它根据不同的参数进行不同操作,比如擦除、
读取、烧写等。 
“nand info”查看 NAND Flash信息。 
“nand erase [clean] [off size]”擦除 NAND Flash。加上“clean”时,表示在每个块的第一
个扇区的 OOB 区加写入清除标记;off、size 表示要擦除的开始偏移地址的长度,如果省略
off和 size,表示要擦除整个 NAND Flash。 
“nand read[.jffs2] addr off size”从 NAND Flash偏移地址 off处读出 size个字节的数据
存放到开始地址为 addr的内存中。是否加后缀“.jffs”的差别只是读操作时的 ECC校验方
法不同。 
“nand write[.jffs2] addr off size”把开始地址为 addr的内存中的 size个字节数据写到 NAND 
Flash的偏移地址 off处。是否加后缀“.jffs”的差别只是写操作时的 ECC校验方法不同。 
“nand read.yaffs addr off size”从 NAND Flash偏移地址 off处读出 size个字节的数据(包
括 OOB区域),存放到开始地址为 addr的内存中。 
“nand write.yaffs addr off size”把开始地址为 addr的内存中的 size个字节数据(其中有
要写入 OOB区域的数据)写到 NAND Flash的偏移地址 off处。 
“nand dump off”将NAND Flash偏移地址 off的一个扇区的数据打印出来,包括OOB数据。 
(6)环境变量命令。 
“printenv”命令打印全部环境变量,“printenv name1 name2⋯”打印名字为 name1、
name2、⋯的环境变量。 
“setenv name value”设置名字为 name的环境变量的值为 value。 
“setenv name”删除名字为 name的环境变量。 
上面的设置、删除操作只是在内存中进行,“saveenv”将更改后的所有环境变量写入 NOR 
Flash中。 
(7)启动命令。 
不带参数的“boot”、“bootm”命令都是执行环境变量 bootcmd所指定的命令。 
“bootm [addr [arg…]]”命令启动存放在地址 addr 处的 U-Boot 格式的映象文件(使用
U-Boot 目录 tools 下的 mkimage 工具制作得到),[arg…]表示参数。如果 addr参数省略,映
象文件所在地址为配置文件中定义的宏 CFG_LOAD_ADDR。 
“go addr [arg…]”与 bootm命令类似,启动存放在地址 addr处的二进制文件,[arg...]表
示参数。 
“nboot [[[loadAddr] dev] offset]”命令将 NAND Flash设备 dev上偏移地址 off处的映象文
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 15 章  15.2  U-Boot分析与移植    291║ 
 
件复制到内存 loadAddr处,然后,如果环境变量 autostart的值为“yes”,就启动这个映象。
如果 loadAddr参数省略,存放地址为配置文件中定义的宏 CFG_LOAD_ADDR;如果 dev参
数省略,则它的取值为环境变量 bootdevice的值;如果 offset参数省略,则默认为 0。 
2.U-Boot命令使用实例 
下面通过一个例子来演示如何使用各种命令烧写内核映象文件、yaffs映象文件,并启动
系统。 
(1)制作内核映象文件。 
对于本书使用的 Linux 2.6.22.6版本,编译内核时可以直接生成 U-Boot格式的映象文件
uImage。 
对于不能直接生成 uImage的内核,制作方法在 U-Boot根目录下的 README文件中有
说明,假设已经编译好的内核文件为 vmlinux,它是 ELF格式的。mkimage是 U-Boot目录 tools
下的工具,它在编译U-Boot时自动生成。执行以下 3个命令将内核文件 vmlinux制作为U-Boot
格式的映象文件 uImage,它们首先将 vmlinux转换为二进制格式,然后压缩,最后构造头部
信息(里面包含有文件名称、大小、类型、CRC校验码等),如下所示。 
 
① arm-linux-objcopy -O binary -R .note -R .comment -S vmlinux linux.bin 
② gzip -9 linux.bin 
③ mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n 
"Linux Kernel Image" -d linux.bin.gz uImage 
 
(2)烧写内核映象文件 uImage。 
首先将 uImage放在主机上的 tftp或 nfs目录下,确保已经开启 tftp或 nfs服务。 
然后运行如下命令下载文件,擦除、烧写 NAND Flash,如下所示。 
 
① tftp 0x30000000 uImage 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/uImage 
② nand erase 0x0 0x00200000 
③ nand write.jffs2 0x30000000 0x0 $(filesize) 
 
第 3条命令之所以使用“nand write.jffs2”而不是“nand write”,是因为前者不要求文件
的长度是页对齐的(512字节对齐)。也可以使用“nand write”,但是需要将命令中的长度参
数改为$(filesize)向上进行 512 取整(比如,513 向上进行 512 取整,结果为 512 × 2=1024)
后的值。比如 uImage的大小为 1540883,向上进行 512取整后为 1541120(即 0x178400),
可以使用命令“nand write 0x30000000 0x0 0x178400”进行烧写。 
(3)烧写 yaffs文件系统映象。 
假设 yaffs文件系统映象的文件名为 yaffs.img,首先将它放在主机上的 tftp或 nfs目录下,
确保已经开启 tftp或 nfs服务;然后执行如下命令下载、擦除、烧写,如下所示。 
 
① tftp 0x30000000 yaffs.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/ 
yaffs.img 
② nand erase 0xA00000 0x3600000 
③ nand write.yaffs 0x30000000 0xA00000 $(filesize) 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
 
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║292    第 15 章  移植 U-Boot 
 
这时,重启系统,在 U-Boot倒数 3s之后,就会自动启动 Linux系统。 
(4)烧写 jffs2文件系统映象。 
假设 jffs2文件系统映象的文件名为 jffs2.img,首先将它放在主机上的 tftp或 nfs目录下,
确保已经开启 tftp或 nfs服务;然后执行如下命令下载、擦除、烧写,如下所示。 
 
① tftp 0x30000000 jffs2.img 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/ 
jffsz.img 
② nand erase 0x200000 0x800000 
③ nand write.jffs2 0x30000000 0x200000 $(filesize) 
 
系统启动后,就可以使用“mount -t jffs2 /dev/mtdblock1 /mnt”挂接 jffs2文件系统。 
15.2.7  使用 U-Boot来执行程序 
在前面的实例中使用 JTAG 烧写程序到 NAND Flash,烧写过程十分缓慢。如果使用
U-Boot来烧写 NAND Flash,效率会高很多。烧写二进制文件到 NAND Flash中所使用的命
令与上面烧写内核映象文件 uImage 的过程类似,只是不需要将二进制文件制作成 U-Boot
格式。 
另外,可以将程序下载到内存中,然后使用 go 命令执行它。假设有一个程序的二进制
可执行文件 test.bin,连接地址为 0x30000000。首先将它放在主机上的 tftp或 nfs目录下,确
保已经开启 tftp或 nfs服务;然后将它下载到内存 0x30000000处,最后使用 go命令执行它,
如下所示。 
 
① tftp 0x30000000 test.bin 或 nfs 0x30000000 192.168.1.57:/work/nfs_root/ 
test.bin 
② go 0x30000000 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
 
 
 
 
 
 第 18 章  Linux 内核调试技术 
 
本章目标   
 
掌握几种调试内核的方法:printk、kgdb、分析 Oops、栈回溯   
使用调试工具:gdb、ddd   
18.1  内核打印函数 printk 
18.1.1  printk的使用 
1.printk函数的记录级别 
调试内核、驱动的最简单方法,是使用 printk 函数打印信息。printk 函数与用户空间的
printf函数格式完全相同,它所打印的字符串头部可以加入“”样式的字符,其中 n为 0~
7,表示这条信息的记录级别。 
在内核代码 include/linux/kernel.h中,下面几个宏控制了 printk函数所能输出的信息的记
录级别。 
 
#define console_loglevel (console_printk[0]) 
#define default_message_loglevel (console_printk[1]) 
#define minimum_console_loglevel (console_printk[2]) 
#define default_console_loglevel (console_printk[3]) 
 
举例说明这几个宏的含义。 
① 对于 printk(“…”),只有 n小于 console_loglevel时,这个信息才会被打印。 
② 假设 default_message_loglevel 的值等于 4,如果 printk 的参数开头没有“”样式
的字符,则在 printk函数中进一步处理前会自动加上“<4>”。 
③ minimum_console_loglevel 是一个预设值,平时不起作用。通过其他工具来设置
console_loglevel的值时,这个值不能小于 minimum_console_loglevel。 
④ default_console_loglevel也是一个预设值,平时不起作用。它表示设置 console_loglevel
时的默认值,通过其他工具来设置 console_loglevel的值时,会用到这个值。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.1  内核打印函数 printk    363║ 
 
minimum_console_logleve 和 default_console_loglevel 这两个值的作用,可以参考内核源
文件 kernel/printk.c的 do_syslog函数。 
上面代码中,console_printk是一个数组,它在 kernel/printk.c中定义: 
 
/* printk's without a loglevel use this.. */ 
#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */ 
 
/* We show everything that is MORE important than this.. */ 
#define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */ 
#define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */ 
⋯⋯ 
int console_printk[4] = { 
     DEFAULT_CONSOLE_LOGLEVEL,   /* console_loglevel */ 
     DEFAULT_MESSAGE_LOGLEVEL,   /* default_message_loglevel */ 
     MINIMUM_CONSOLE_LOGLEVEL,   /* minimum_console_loglevel */ 
     DEFAULT_CONSOLE_LOGLEVEL,   /* default_console_loglevel */ 
}; 
2.在用户空间修改 printk函数的记录级别 
挂接 proc文件系统后,读取/proc/sys/kernel/printk文件可以得知 console_loglevel、default_ 
message_loglevel、minimum_console_loglevel和 default_console_loglevel这 4个值。 
比如执行以下命令,它的结果“7 4 1 7”表示这 4个值。 
 
# cat /proc/sys/kernel/printk 
7       4       1       7 
 
也可以直接修改/proc/sys/kernel/printk文件来改变这 4个值,比如: 
 
# echo "1 4 1 7" > /proc/sys/kernel/printk 
 
这使得 console_loglevel被改为 1,于是所有的 printk信息都不会被打印。 
3.printk函数记录级别的名称及使用 
在内核代码 include/linux/kernel.h中有如下代码,它们表示 0~7这 8个记录级别的名称。 
 
#define KERN_EMERG  "<0>" /* system is unusable   */ 
#define KERN_ALERT  "<1>" /* action must be taken immediately */ 
#define KERN_CRIT  "<2>" /* critical conditions   */ 
#define KERN_ERR  "<3>" /* error conditions   */ 
#define KERN_WARNING "<4>" /* warning conditions   */ 
#define KERN_NOTICE  "<5>" /* normal but significant condition */ 
#define KERN_INFO  "<6>" /* informational   */ 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║364    第 18 章  Linux内核调试技术 
 
#define KERN_DEBUG  "<7>" /* debug-level messages   */ 
 
在使用 printk函数时,可以这样使用记录级别; 
 
printk(KERN_WARNING"there is a warning here!\n") 
18.1.2  串口控制台 
1.串口与 printk函数的关系 
在嵌入式 Linux 开发中,printk 信息常常从串口输出,这时串口被称为串口控制台。从
内核 kernel/printk.c的 printk函数开始,往下查看它的调用关系,可以知道 printk函数是如何
与具体设备的输出函数挂钩的。 
printk函数调用的子函数的主要脉落如下: 
 
printk -> 
     vprintk -> 
          emit_log_char  // 把要打印的数据写入一个全局缓冲区(名为 log_buf)中 
          release_console_sem -> 
               call_console_drivers -> 
                    _call_console_drivers -> 
                        __call_console_drivers -> 
                            con->write  // con是 console_drivers链表的表项,调用具
体的输出函数 
 
对于可以作为控制台的设备,在初始化时会通过 register_console 函数向 console_drivers
链表注册一个 console结构,里面有 write函数指针。 
以 drivers/serial/s3c2410.c文件中的串口初始化函数 s3c24xx_serial_initconsole为例,它的
部分代码如下: 
 
1892 static int s3c24xx_serial_initconsole(void) 
1893 { 
⋯ 
1927      register_console(&s3c24xx_serial_console); 
1928      return 0; 
1928 } 
 
第 1927行的 s3c24xx_serial_console就是 console结构,它在相同的文件中定义,部分内
容如下: 
 
1882 static struct console s3c24xx_serial_console = 
1883 { 
1884     .name       = S3C24XX_SERIAL_NAME,       // 这个宏被定义为“SAC” 
1885        .device          = uart_console_device,       // init进行、用户程序打开/dev/console时用到 
1886     .flags       = CON_PRINTBUFFER,           // 打印先前在 log_buf中保存的信息 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.1  内核打印函数 printk    365║ 
 
1887      .index       = -1,                            // 表示使用哪个串口由命令行参数决定 
1888     .write     = s3c24xx_serial_console_write,  // 串口控制台的输出函数 
1889     .setup     = s3c24xx_serial_console_setup   // 串口控制台的设置函数 
1890 }; 
 
第 1886行的 CON_PRINTBUFFER表示注册这个结构后,要把 log_buf缓冲区中的所有
信息打印出来。这表明,在实际的硬件被初始化之前,就可以使用 printk 函数,只不过这时
的打印信息是保存在 log_buf缓冲区中,还没有真正输出。 
第 1888 行的 s3c24xx_serial_console_write 是串口输出函数,它会调用 s3c24xx_serial_ 
console_putchar函数将要打印的字符一个个地从串口输出。 
s3c24xx_serial_console_putchar是最底层的函数,代码如下: 
 
static void 
s3c24xx_serial_console_putchar(struct uart_port *port, int ch) 
{ 
     unsigned int ufcon = rd_regl(cons_uart, S3C2410_UFCON); 
     while (!s3c24xx_serial_console_txrdy(port, ufcon)) 
          barrier(); 
     wr_regb(cons_uart, S3C2410_UTXH, ch); 
} 
 
从上面的代码可以知道,从串口中输出 printk打印信息时,是一个字符一个字符地发送、
等待发送完成、发送、接着等待,⋯⋯,效率很低。调试完华后,通常要将 printk信息去掉。 
2.设置内核命令行参数使用串口控制台 
第 15 章中使用 U-Boot 时,设置了命令行参数“console=ttySAC0”,它使得 printk 的信
息从串口 0中输出。 
内核是怎样根据这些命令行参数确定 printk的输出设备呢?在 kernel/printk.c中有如下
代码: 
 
__setup("console=", console_setup); 
 
内核开始执行时,发现形如“console=⋯”的命令行参数时,就会调用 console_setup 函
数进行解析。对于命令行参数“console=ttySAC0”,它会解析出:设备名(name)为 ttySAC,
索引(index)为 0,这些信息被保存在类型为 console_cmdline、名称为 console_cmdline的全
局数组中(数据光类型、数组名相同,请勿混淆)。 
在后面使用“register_console (&s3c24xx_serial_console)”注册控制台(参考前面的代码
drivers/serial/s3c2410.c中第 1927行)时,会将 s3c24xx_serial_console结构与 console_cmdline
数组中的设备进行比较,发现名字、索引相同。 
① s3c24xx_serial_console 结构中名字(name)为 S3C24XX_SERIAL_NAME,即“tty 
SAC”,而根据“console=ttySAC0”解析出来的名字也是“ttySAC”。 
② s3c24xx_serial_console 结构中索引(index)为−1,表示使用命令行中解析出来的索
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║366    第 18 章  Linux内核调试技术 
 
引 0,表示串口 0。 
综上所述,命令行参数“console=ttySAC0”决定 printk信息将通过 s3c24xx_serial_console
结构中的相关函数,从串口 0输出。 
最后,既然 printk输出的信息是先保存在缓冲区 log_buf中的,那么也可以读取 log_buf
以获得这些信息:系统启动后,想查看 printk信息时,直接运行 dmesg命令即可。通过其他
非串口的手段(比如 ssh、telnet)登录系统时,也可以使用 dmesg命令查看 printk信息。 
18.2  内核源码级别的调试方法 
18.2.1  内核调试工具 KGDB的作用与原理 
1.KGDB介绍 
KGDB 是一个源码级别的 Linux 内核调试器。使用 KGDB 调试内核时,需要结合 GDB
一起使用。它们使得调试内核就像调试应用程序一样,可以在内核代码中设置断点、一步一
步地执行指令、观察变量的值。 
使用 KGDB时,需要两台机器,即主机和目标机,两者通过串口线相连。要调试的内核
需要增加 KGDB功能,它在目标机上运行,GDB在主机上运行。串口线被 GDB用来与内核
通信。 
KGDB是一个内核补丁,目前支持 i386、x86_64、ppc、s390、ARM等架构。将内核打
上 KGDB补丁后才能够使用 GDB来调试。 
2.KGDB的原理 
安装 KGDB调试环境需要为 Linux内核加上 kgdb补丁,补丁实现 GDB远程调试所需要
的功能,包括命令处理、陷阱处理及串口通信 3个主要的部分。KGDB补丁的主要作用是在
Linux 内核中添加了一个调试 Stub。调试 Stub 是 Linux 内核中的一小段代码,是运行 GDB
的开发机和所调试内核之间的一个媒介。GDB和调试 stub之间通过 GDB串行协议进行通信。
GDB串行协议是一种基于消息的 ASCII码协议,包含了各种调试命令。当设置断点时,KGDB
将断点的指令替换为一条 trap指令,当执行到断点时控制权就转移到调试 stub中去。此时,
调试 stub的任务就是使用远程串行通信协议将当前环境传送给 GDB,然后从 GDB处接收命
令。GDB 命令告诉 stub 下一步该做什么,当 stub 收到继续执行的命令时,将恢复程序的运
行环境,把对 CPU的控制权重新交还给内核。  
KGDB补丁给内核添加以下 3个部件。 
(1)GDB stub。 
GDB stub被称为调试插桩(简称为 stub),是 KGDB调试器的核心。它是 Linux内核中
的一小段代码,用来处理主机上 GDB 发来的各种请求;并且在内核处于被调试状态时,控
制目标机板上的处理器。 
(2)修改异常处理函数。 
当这个异常发生时,内核将控制权交给 KGDB 调试器,程序进入 KGDB 提供的异常处
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.2  内核源码级别的调试方法    367║ 
 
理函数中。在里面,可以分析程序的各种情况。 
(3)串口通信。 
GDB和 stub之间通过 GDB串行协议进行通信。它是一种基于消息的 ASCII码协议,包
含了各种调试命令。 
除串口外,也可以使用网卡进行通信。 
以设置内核断点为例说明 KGDB与 GDB之间的工作过程。设置断点时,KGDB修改内
核代码,将断点位置的指令替换成一条异常指令(在 ARM中这是一条未定义的指令)。当执
行到断点时发生异常,控制权转移到 stub 的异常处理函数中。此时,stub 的任务就是使用
GDB 串行通信协议将当前环境传送给 GDB,然后从 GDB 处接收命令,GDB 命令告诉 stub
下一步该做什么。当 stub收到继续执行的命令时,将恢复原来替换的指令、恢复程序的运行
环境,把对 CPU的控制权重新交还给内核。 
18.2.2  给内核添加 KGDB功能支持 S3C2410/S3C2440 
如果读者使用了前面第 16 章提到的内核补丁文件 linux2.6.22.6_100ask24x0.patch,则本
节中对代码的修改可以忽略,只需要关注对内核的配置(补丁文件生成的 config_ok 文件对
KGDB也已经配置好了)。 
如果/work/system/linux-2.6.22.6 曾经应用了补丁文件 linux2.6.22.6_100ask24x0.patch,那
么它(基于随书光盘容量的考虑,Xorg_git_20071119.tar.bz2中删除了 doc目录,这无关紧要)。 
对于本书使用的 Linux 2.6.22内核,有对应的 KGDB补丁。但是对于 S3C2410、S3C2440,
还需要自己编写串口初始化函数、发送、接收字符函数,以供 stub调用。 
1.给内核添加 KGDB补丁 
要使用的 KGDB补丁的分支版本为 linux2_6_22_uprev,有 3种获取方法。 
① 从 web网页下载,地址如下。 
 
http://kgdb.cvs.sourceforge.net/kgdb/kgdb-2/?pathrev=linux2_6_22_uprev 
 
② 使用 cvs工具下载,执行以下命令即可。 
 
$ cd /work/debug 
$ cvs -z3 -d:pserver:anonymous@kgdb.cvs.sourceforge.net:/cvsroot/kgdb co -P 
-r linux2_6_22_uprev kgdb-2 
 
③ 也可以使用已经下载好了的,即/work/debug/kgdb-2_linux2_6_22_uprev.tar.bz2。 
在下载或解压后得到 kgdb-2目录里,除了各种补丁文件外,还有一个名为 series的文件,
它表示这些补丁文件使用的顺序。可以参考 series文件一个个地打补丁,也可以使用“quilt push 
-a”命令一次全部打上:先把 kgdb-2目录复制到内核目录下,并改名为 patches;然后在内核
目录下执行“quilt push -a”命令,命令如下: 
$ cd /work/system/linux-2.6.22.6 
$ cp -rf /work/debug/kgdb-2 patches 
$ quilt push -a 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║368    第 18 章  Linux内核调试技术 
 
2.修改补丁本身带入的错误 
修改 include/asm-arm/system.h第 380行,这是一个笔误(“−”号表示原来的代码,“+”
号表示新代码): 
 
-                pref = *p; 
+                prev = *p; 
3.编写 S3C2410/S3C2440的 KGDB串口函数 
目前的 KGDB 补丁不支持 S3C2410/S3C2440 的串口,需要自己编写相关函数。可以参
考 arch/arm/mach-pxa/kgdb-serial.c,在 arch/arm/mach-s3c2410/目录下也建立一个 kgdb-serial.c
文件。 
KGDB只需要 3个串口函数:初始化函数、发送单字符函数、接收单字符函数。然后将
它们填入同一文件中,一个名为 kgdb_io_ops的 struct kgdb_io结构中。 
下面分段介绍这 3 个函数及文件中其他内容,完整的代码请参考 linux-2.6.22.6_ok.tar. 
bz2。 
① 串口初始化函数。 
 
53 static int kgdb_serial_init(void) 
54 { 
55      struct clk *clock_p; 
56      u32 pclk; 
57      u32 ubrdiv; 
58      u32 val; 
59      u32 index = CONFIG_KGDB_PORT_NUM; 
60  
61      clock_p = clk_get(NULL, "pclk"); 
62      pclk = clk_get_rate(clock_p); 
63  
64      ubrdiv = (pclk / (UART_BAUDRATE * 16)) - 1; 
65  
66      /* 设置 GPIO用作串口, 并且禁止内部上拉 
67       * GPH2、GPH3用作 TXD0、RXD0 
68       * GPH4、GPH5用作 TXD1、RXD1 
69       * GPH6、GPH7用作 TXD2、RXD2 
70       */ 
71      if (index < MAX_PORT) 
72      { 
73          index = 2 + index * 2; 
74          
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.2  内核源码级别的调试方法    369║ 
 
75          val = inl(S3C2410_GPHUP) | (0x3 << index); 
76          outl(val, S3C2410_GPHUP); 
77  
78          index *= 2; 
79          val = (inl(S3C2410_GPHCON) & ~(~(0xF << index))) | \ 
80                 (0xA << index); 
81          outl(val, S3C2410_GPHCON); 
82      } 
83      else 
84      { 
85          return -1; 
86      } 
87      
88      // 8N1(8个数据位,无校验位,1个停止位) 
89      wr_regl(CONFIG_KGDB_PORT_NUM, S3C2410_ULCON, 0x03); 
90  
91      // 中断/查询方式,UART时钟源为 PCLK 
92      wr_regl(CONFIG_KGDB_PORT_NUM, S3C2410_UCON, 0x3c5); 
93  
94      // 使用 FIFO 
95      wr_regl(CONFIG_KGDB_PORT_NUM, S3C2410_UFCON, 0x51); 
96  
97      // 不使用流控 
98      wr_regl(CONFIG_KGDB_PORT_NUM, S3C2410_UMCON, 0x00); 
99  
100      // 设置波特率 
101      wr_regl(CONFIG_KGDB_PORT_NUM, S3C2410_UBRDIV, ubrdiv);     
102      
103      return 0; 
104 } 
105   
要使用串口,需要选择相关的 GPIO引脚用作串口,并且设置串口的数据格式、时钟源、
波特率等。 
② 发送单字符函数。  
106 static void kgdb_serial_putchar(u8 c) 
107 { 
108     /* 等待,直到发送缓冲区中的数据已经全部发送出去 */    
109        while (!(rd_regb(CONFIG_KGDB_PORT_NUM, S3C2410_UTRSTAT) & S3C2410_UTRSTAT_TXE));  
110  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║370    第 18 章  Linux内核调试技术 
 
111     /* 向 UTXH寄存器中写入数据,UART即自动将它发送出去 */     
112     wr_regb(CONFIG_KGDB_PORT_NUM, S3C2410_UTXH, c); 
113 } 
114  
 
③ 接收单字符函数。 
 
115 static int kgdb_serial_getchar(void) 
116 { 
117      /* 等待,直到接收缓冲区中有数据 */     
118     while (!(rd_regb(CONFIG_KGDB_PORT_NUM, S3C2410_UTRSTAT) & S3C2410_ 
UTRSTAT_RXDR));         
119  
120      /* 直接读取 URXH寄存器,即可获得接收到的数据 */     
121      return rd_regb(CONFIG_KGDB_PORT_NUM, S3C2410_URXH); 
122 } 
123  
 
④ 使用这些函数构建 kgdb_io_ops结构。 
 
124 struct kgdb_io kgdb_io_ops = { 
125      .init = kgdb_serial_init, 
126      .read_char = kgdb_serial_getchar, 
127      .write_char = kgdb_serial_putchar, 
128 }; 
 
kgdb_io_ops结构将在 kernel/kgdb.c中被用到,这个结构封装了开发板相关的串口操作
函数。其他的 KGDB代码都是具体开发板无关的。 
4.修改内核配置文件、Makefile 
① 修改 arch/arm/mach-s3c2410/Makefile,将新增的 kgdb-serial.c文件编译进内核。 
 
+ obj-$(CONFIG_KGDB_S3C24XX_SERIAL) += kgdb-serial.o 
 
② 上面的 CONFIG_KGDB_S3C24XX_SERIAL 是新加的配置项,要修改配置文件 lib/ 
Kconfig.kgdb来支持它。 
修改了 4个地方,下面的修改内容仿照补丁文件的格式,首字母为“-”的行表示是老文
件中的代码,首字母为“+”的行表示是新文件中的代码。 
•  在“Method for KGDB communication”下增加一个选择项。 
 
choice 
         prompt "Method for KGDB communication" 
         depends on KGDB 
+        default KGDB_S3C24XX_SERIAL if ARCH_S3C2410 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.2  内核源码级别的调试方法    371║ 
 
•  用来配置 KGDB_S3C24XX_SERIAL选项。 
 
+ config KGDB_S3C24XX_SERIAL 
+           bool "KGDB: On the S3C24xx serial port" 
+           depends on ARCH_S3C2410 
+           help 
+             Enables the KGDB serial driver for S3C24xx 
 
•  配置 KGDB_S3C24XX_SERIAL后,也可以设置 KGDB所用串口的波特率。 
 
config KGDB_BAUDRATE 
          int "Debug serial port baud rate" 
          depends on (KGDB_8250 && KGDB_SIMPLE_SERIAL) || \ 
                    KGDB_MPSC || KGDB_CPM_UART || \ 
-                   KGDB_TXX9 || KGDB_PXA_SERIAL || KGDB_AMBA_PL011 
+                   KGDB_TXX9 || KGDB_PXA_SERIAL || KGDB_AMBA_PL011 || 
KGDB_S3C24XX_SERIAL 
 
•  配置 KGDB_S3C24XX_SERIAL 后,也可以设置 KGDB 使用哪个串口,默认使用第
1个。 
 
config KGDB_PORT_NUM 
      int "Serial port number for KGDB" 
      range 0 1 if KGDB_MPSC 
      range 0 3 
-     depends on (KGDB_8250 && KGDB_SIMPLE_SERIAL) || KGDB_MPSC || KGDB_TXX9 
-     default "1" 
+     depends on (KGDB_8250 && KGDB_SIMPLE_SERIAL) || KGDB_MPSC || KGDB_TXX9 || 
KGDB_S3C24XX_SERIAL 
+     default "0" 
5.配置内核,使能 KGDB功能 
执行“make menuconfig”来配置内核,如下配置以使能 KGDB功能。 
 
Kernel hacking  ---> 
     [*] KGDB: kernel debugging with remote gdb  // 表示使能 KGDB功能 
     [*] KGDB: Console messages through gdb   // 表示控制台信息(printk)
会发送到 GDB 
          Method for KGDB communication (KGDB: On the S3C24xx serial port)  ---> 
//S3C24xx串口 
     < > KGDB: On ethernet (NEW) 
     (115200) Debug serial port baud rate (NEW)  // 波特率为 115200 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║372    第 18 章  Linux内核调试技术 
 
     (0) Serial port number for KGDB (NEW)   // 使用第 1个 S3C24xx串口 
 
然后执行“make uImage”即可生成内核 vmlinux、arch/arm/boot/uImage。 
18.2.3  结合可视化图形前端 DDD和 GDB来调试内核 
1.DDD介绍及安装 
DDD是“Data Display Debugger”的简称,是命令行调试程序,是 GDB、DBX、WDB、
Ladebug、JDB、XDB、Perl Debugger或 Python Debugger等的可视化图形前端,这意味着可
以不用记忆、输入各种调试命令,可以使用各种按钮进行调试。它特有的图形数据显示功能
(Graphical Data Display)可以把数据结构按照图形的方式显示出来。 
DDD的功能非常强大,可以调试用 C/C++、Ada、Fortran、Pascal、Modula-2和Modula-3
编写的程序;可以以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;
具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力。图形数据显示功能
(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,
并将数据结构以图形化形式显示;具有 GDB/DBX/XDB的命令行界面,包括完全的文本编辑、
历史纪录、搜寻引擎。 
通过 DDD调用 GDB来调试内核,可以在图形界面上完成调试工作。 
可以在 Ubuntu 7.10中通过网络安装 DDD,命令如下: 
 
$ sudo apt-get install ddd 
2.GDB介绍及安装 
通过 GDB 这类调试器,程序员可以知道一个程序执行时内部动作过程,可以知道一个
程序崩溃时发生了什么事。 
GDB可以完成以下 4个主要功能,这可以帮助程序员捕捉到程序的错误。 
① 启动程序,并指定各类能够影响程序运行的参数。 
② 使程序在指定条件下停止运行。 
③ 当程序停止时,观察各种状态,检查发生了什么事情。 
④ 修改程序的执行参数,比如修改某个变量,这使得在查错时可以试验各种参数。 
GDB支持多种编程语言,可以调试用 C/C++、Modula-2和 Fortran等语言编写的程序。
GDB是基于命令行的,GDB启动后,在它的控制界面使用各种命令进行操作。 
Ubuntu 7.10自带的 GDB工具是基于 x86系列的,需要自己下载源码为 ARM平台编译
一个 GDB工具,为便于区分,将它命名为 arm-linux-gdb。 
从网站 http://www.gnu.org/software/gdb/下载 gdb-6.7.tar.bz2,或者使用/work/debug/ gdb-6. 
7.tar.bz2。执行以下命令编译、安装 arm-linux-gdb。 
 
$ tar xjf gdb-6.7.tar.bz2 
$ cd gdb-6.7/ 
$ ./configure --target=arm-linux 
$ make 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.2  内核源码级别的调试方法    373║ 
 
$ sudo make install 
3.使用 arm-linux-gdb调试内核(命令行方式) 
先启动支持 KGDB的内核,然后在主机上启动 arm-linux-gdb。 
(1)启动内核。 
要使用 KGDB功能,需要增加两个命令参数:console=kgdb和 kgdbwait。前者表示内核
打印信息会被发送给 GDB,即通过上面增加的 kgdb-serial.c中的相关函数进行发送;后者表
示内核启动时先停住,等待 GDB的连接。 
假设将上面编译好的内核 uImage放在/work/nfs_root目录下,则可以在 U-Boot上使用以
下命令设置命令行参数、启动内核。 
 
100ask> set bootargs noinitrd root=/dev/mtdblock 2 console=kgdb kgdbwait 
100ask> nfs 0x31000000 192.168.1.57:/work/nfs_root/uImage 
100ask> bootm 0x31000000 
 
这时可以看到以下启动信息: 
 
Starting kernel ... 
 
Uncompressing 
Linux..............................................................................  
.......................................... done, booting the kernel. 
 
内核在等待主机 arm-linux-gdb的连接。 
(2)启动 arm-linux-gdb。 
 
 注意 
由于 arm-linux-gdb要用到串口,所以如果是在 vmware上运行 Linux,还要设置 vmware的特性,
增加串口(物理串口)。 
 
启动 arm-linux-gdb之前,先退出刚才操作 U-Boot所用的串口工具,因为 arm-linux-gdb
也要使用这个串口。 
然后在主机上进入内核目录,启动 arm-linux-gdb,可以执行以下命令: 
 
$ cd /work/system/linux-2.6.22.6 
$ sudo arm-linux-gdb ./vmlinux 
 
这时会看到 arm-linux-gdb的启动信息,进入控制界面: 
 
GNU gdb 6.7 
Copyright (C) 2007 Free Software Foundation, Inc. 
License GPLv3+: GNU GPL version 3 or later  
This is free software: you are free to change and redistribute it. 
There is NO WARRANTY, to the extent permitted by law.  Type "show copying" 
and "show warranty" for details. 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║374    第 18 章  Linux内核调试技术 
 
This GDB was configured as "--host=i686-pc-linux-gnu --target=arm- 
linux"... 
(gdb)  
 
最后,执行两个命令设置口、连接目标板。 
 
(gdb) set remotebaud 115200 
(gdb) target remote /dev/ttyS0 
 
这时可以看到如下信息,表明已经连接上了目标板,目标板在 kernel/kgdb.c的 1775行暂
停运行。 
 
Remote debugging using /dev/ttyS0 
0xc0067a28 in breakpoint () at kernel/kgdb.c:1775 
1775              atomic_set(&kgdb_setting_breakpoint, 1); 
(gdb) 
 
现在就可以使用如种 GDB的命令控制内核的执行、进行调试了,读者可以自行参考 GDB
的手册。比如输入 n命令执行下一条指令,输入 c命令全速运行,输出 q命令退出。GDB命
令的使用方法请参考 GDB手册。 
为了避免每次启动 arm-linux-gdb时手工设置串口、连接目标板,可以在内核目录下建立
一个名为“.gdbinit”文件,内容如下: 
 
set remotebaud 115200 
target remote /dev/ttyS0 
4.通过 DDD调用 arm-linux-gdb来调试内核(图形界面) 
arm-linux-gdb是通过 DDD来启动的,DDD封装了对 arm-linux-gdb的操作,提供一个图
形化的操作界面,操作步骤如下。 
(1)启动内核。 
(2)启动 DDD。 
DDD要在桌面系统中启动,在远程登录工具 ssh等的命令行中无法启动 DDD。 
首先,退出操作 U-Boot的串口工具。 
然后,确保内核目录下有.gdbinit文件。 
最后,在桌面系统的控制台里,进入内核目录,启动 DDD。执行以下命令即可。 
 
$ cd /work/system/linux-2.6.22.6 
$ sudo ddd --debugger arm-linux-gdb ./vmlinux 
 
这时,可以看到如图 18.1所示的启动界面,在里面可以很方便地使用各类按钮进行设置
断点、单步执行等操作。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.3  Oops信息及栈回溯    375║ 
 
 
图 18.1  DDD调用 arm-linux-gdb来调试内核的启动界面 
18.3  Oops信息及栈回溯 
18.3.1  Oops信息来源及格式 
Oops 这个单词含义为“惊讶”,当内核出错时(比如访问非法地址)打印出来的信息被
称为 Oops信息。 
Oops信息包含以下几部分内容。 
① 一段文本描述信息。 
比如类似“Unable to handle kernel NULL pointer dereference at virtual address 00000000”
的信息,它说明了发生的是哪类错误。 
② Oops信息的序号。 
比如是第 1次、第 2次等。这些信息与下面类似,中括号内的数据表示序号。 
 
Internal error: Oops: 805 [#1] 
 
③ 内核中加载的模块名称,也可能没有,以下面字样开头。 
 
Modules linked in: 
 
④ 发生错误的 CPU的序号,对于单处理器的系统,序号为 0,比如: 
 
CPU: 0    Not tainted  (2.6.22.6 #36) 
 
⑤ 发生错误时 CPU的各个寄存器值。 
⑥ 当前进程的名字及进程 ID,比如: 
Process swapper (pid: 1, stack limit = 0xc0480258) 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║376    第 18 章  Linux内核调试技术 
 
 
这并不是说发生错误的是这个进程,而是表示发生错误时,当前进程是它。错误可能发
生在内核代码、驱动程序,也可能就是这个进程的错误。 
⑦ 栈信息。 
⑧ 栈回溯信息,可以从中看出函数调用关系,形式如下: 
 
Backtrace:  
[] (s3c2410fb_probe+0x0/0x560) from [] (platform_drv_ 
probe+0x20/0x24) 
⋯ 
 
⑨ 出错指令附近的指令的机器码,比如(出错指令在小括号里): 
 
Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000) 
18.3.2  配置内核使 Oops信息的栈回溯信息更直观 
Linux 2.6.22自身具备的调试功能,可以使得打印出的 Oops信息更直观。通过 Oops信
息中 PC 寄存器的值可以知道出错指令的地址,通过栈回溯信息可以知道出错时的函数调用
关系,根据这两点可以很快定位错误。 
要让内核出错时能够打印栈回溯信息,编译内核时要增加“-fno-omit-frame-pointer”选
项,这可以通过配置CONFIG_FRAME_POINTER来实现。查看内核目录下的配置文件.config,
确保 CONFIG_FRAME_POINTER已经被定义,如果没有,执行“make menuconfig”命令重
新配置内核。CONFIG_FRAME_POINTER有可能被其他配置项自动选上。 
18.3.3  使用 Oops信息调试内核的实例 
1.获得 Oops信息 
本小节故意修改 LCD驱动程序 drivers/video/s3c2410fb.c,加入错误代码:在 s3c2410fb_ 
probe函数的开头增加下面两条代码: 
 
    int *ptest = NULL; 
    *ptest = 0x1234; 
 
重新编译内核,启动后会出错并打印出如下 Oops信息: 
 
Unable to handle kernel NULL pointer dereference at virtual address 00000000 
pgd = c0004000 
[00000000] *pgd=00000000 
Internal error: Oops: 805 [#1] 
Modules linked in: 
CPU: 0    Not tainted  (2.6.22.6 #36) 
PC is at s3c2410fb_probe+0x18/0x560 
LR is at platform_drv_probe+0x20/0x24 
pc : []    lr : []    psr: a0000013 
sp : c0481e64  ip : c0481ea0  fp : c0481e9c 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.3  Oops信息及栈回溯    377║ 
 
r10: 00000000  r9 : c0024864  r8 : c03c420c 
r7 : 00000000  r6 : c0389a3c  r5 : 00000000  r4 : c036256c 
r3 : 00001234  r2 : 00000001  r1 : c04c0fc4  r0 : c0362564 
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  Segment kernel 
Control: c000717f  Table: 30004000  DAC: 00000017 
Process swapper (pid: 1, stack limit = 0xc0480258) 
Stack: (0xc0481e64 to 0xc0482000) 
1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c 
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704 
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c 
1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14 
1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c 
1f00: c0389a44 c038d9dc c0481f24 c0481f18 c01bd808 c01bc568 c0481f4c c0481f28 
1f20: c01bcd78 c01bd7f8 c0389a3c 00000000 00000000 c0480000 c0023ac8 00000000 
1f40: c0481f60 c0481f50 c01bdc84 c01bcd0c 00000000 c0481f70 c0481f64 c01bf5fc 
1f60: c01bdc14 c0481f80 c0481f74 c019479c c01bf5a0 c0481ff4 c0481f84 c0008c14 
1f80: c0194798 e3c338ff e0222423 00000000 00000001 e2844004 00000000 00000000 
1fa0: 00000000 c0481fb0 c002bf24 c0041328 00000000 00000000 c0008b40 c00476ec 
1fc0: 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 
1fe0: 00000000 00000000 00000000 c0481ff8 c00476ec c0008b50 c03cdf50 c0344178 
Backtrace:  
[] (s3c2410fb_probe+0x0/0x560) from [] (platform_drv_ 
probe+0x20/0x24) 
[] (platform_drv_probe+0x0/0x24) from [] (driver_probe_ 
device+0xe8/0x18c) 
[] (driver_probe_device+0x0/0x18c) from [] (__driver_ 
attach+0x80/0xe0) 
 r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644 
[] (_ _driver_attach+0x0/0xe0) from [] (bus_for_each_ 
dev+0x50/0x84) 
 r5:c0481eec r4:00000000 
[] (bus_for_each_dev+0x0/0x84) from [] (driver_attach+ 
0x20/0x28) 
 r7:c038d9dc r6:c0389a44 r5:c0389a3c r4:00000000 
[] (driver_attach+0x0/0x28) from [] (bus_add_driver+ 
0x7c/0x1b4) 
[] (bus_add_driver+0x0/0x1b4) from [] (driver_register+ 
0x80/0x88) 
[] (driver_register+0x0/0x88) from [] (platform_driver_ 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║378    第 18 章  Linux内核调试技术 
 
register+0x6c/0x88) 
 r4:00000000 
[] (platform_driver_register+0x0/0x88) from [] (s3c2410fb_ 
init+0x14/0x1c) 
[] (s3c2410fb_init+0x0/0x1c) from [] (kernel_init+0xd4/ 
0x28c) 
[] (kernel_init+0x0/0x28c) from [] (do_exit+0x0/0x760) 
Code: e24cb004 e24dd010 e59f34e0 e3a07000 (e5873000)  
Kernel panic - not syncing: Attempted to kill init! 
2.分析 Oops信息 
(1)明确出错原因。 
由出错信息“Unable to handle kernel NULL pointer dereference at virtual address 00000000”
可知内核是因为非法地址访问出错,使用了空指针。 
(2)根据栈回溯信息找出函数调用关系。 
内核崩溃时,可以从 pc寄存器得知崩溃发生时的函数、出错指令。但是很多情况下,错
误有可能是它的调用者引入的,所以找出函数的调用关系也很重要。 
部分栈回溯信息如下: 
 
 [] (s3c2410fb_probe+0x0/0x560) from [] (platform_drv_ 
probe+0x20/0x24) 
 
这行信息分为两部分,表示后面的 platform_drv_probe函数调用了前面的 s3c2410fb_probe
函数。 
前半部含义为:“c001a6f4”是 s3c2410fb_probe 函数首地址偏移 0 的地址,这个函数大
小为 0x560。 
后半部含义为:“c01bf4e8”是 platform_drv_probe函数首地址偏移 0x20的地址,这个函
数大小为 0x24。 
另外,后半部的“[]”表示 s3c2410fb_probe执行后的返回地址。 
对于类似下面的栈回溯信息,其中是 r8~r4表示 driver_probe_device函数刚被调用时这
些寄存器的值。 
 
 [] (driver_probe_device+0x0/0x18c) from [] (__driver_ 
attach+0x80/0xe0) 
 r8:00000000 r7:c0389a3c r6:c01bd708 r5:c036256c r4:c0362644 
 
从上面的栈回溯信息可以知道内核出错时的函数调用关系如下,最后在 s3c2410fb_probe
函数内部崩溃。 
do_exit ->  
     kernel_init ->  
          s3c2410fb_init ->  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.3  Oops信息及栈回溯    379║ 
 
               platform_driver_register ->  
                    driver_register ->  
                        bus_add_driver ->  
                             driver_attach ->  
                                  bus_for_each_dev ->  
                                      __driver_attach ->  
                                        driver_probe_device ->  
                                             platform_drv_probe ->  
                                                  s3c2410fb_probe 
 
(3)根据 pc寄存器的值确定出错位置。 
上述 Oops信息中出错时的寄存器值如下: 
 
PC is at s3c2410fb_probe+0x18/0x560 
LR is at platform_drv_probe+0x20/0x24 
pc : []    lr : []    psr: a0000013 
⋯ 
 
“PC is at s3c2410fb_probe+0x18/0x560”表示出错指令为 s3c2410fb_probe函数中偏移为
0x18的指令。 
 
“pc : []”表示出错指令的地址为 c001a70c(十六进制)。 
 
(4)结合内核源代码和反汇编代码定位问题。 
先生成内核的反汇编代码 vmlinux.dis,执行以下命令: 
 
$ cd /work/system/linux-2.6.22.6 
$ arm-linux-objdump -D vmlinux > vmlinux.dis 
 
出错地址 c001a70c附近的部分汇编代码如下: 
 
c001a6f4 : 
c001a6f4: e1a0c00d  mov ip, sp 
c001a6f8: e92ddff0  stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc} 
c001a6fc: e24cb004  sub fp, ip, #4 ; 0x4 
c001a700: e24dd010  sub sp, sp, #16 ; 0x10 
c001a704: e59f34e0  ldr r3, [pc, #1248] ; c001abec <.init+0x1284c> 
c001a708: e3a07000  mov r7, #0 ; 0x0 
c001a70c: e5873000  str r3, [r7]      <===========出错指令 
c001a710: e59030fc  ldr r3, [r0, #252] 
 
出错指令为“str r3, [r7]”,它把 r3寄存器的值放到内存中,内存地址为 r7寄存器的值。
根据 Oops信息中的寄存器值可知:r3为 0x00001234,r7为 0。0地址不可访问,所以出错。 
s3c2410fb_probe函数的部分 C代码如下: 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║380    第 18 章  Linux内核调试技术 
 
static int __init s3c2410fb_probe(struct platform_device *pdev) 
{ 
     struct s3c2410fb_info *info; 
     struct fb_info     *fbinfo; 
     struct s3c2410fb_hw *mregs; 
     int ret; 
     int irq; 
     int i; 
     u32 lcdcon1; 
 
     int *ptest = NULL; 
     *ptest = 0x1234; 
 
     mach_info = pdev->dev.platform_data; 
 
结合反汇编代码,很容易知道是“*ptest = 0x1234;”导致错误,其中的 ptest为空。 
对于大多数情况,从反汇编代码定位到 C代码并不会如此容易,这需要较强的阅读汇编
程序的能力。通过栈回溯信息知道函数的调用关系,这已经可以帮助定位很多问题了。 
18.3.4  使用 Oops的栈信息手工进行栈回溯 
前面说过,从 Oops信息的 pc寄存器值可知得知崩溃发生时的函数、出错指令。但是错
误有可能是它的调用者引入的,所以还要找出函数的调用关系。 
由于内核配置了CONFIG_FRAME_POINTER,当出现Oops信息时,会打印栈回溯信息。如
果内核没有配置CONFIG_FRAME_POINTER,这时可以自己分析栈信息,找到函数的调用关系。 
1.栈的作用 
一个程序包含代码段、数据段、BSS 段、堆、栈;其中数据段用来中存储初始值不为 0
的全局数据,BSS段用来存储初始值为 0的全局数据,堆用于动态内存分配,栈用于实现函
数调用、存储局部变量。 
被调用函数在执行之前,它会将一些寄存器的值保存在栈中,其中包括返回地址寄存器
lr。如果知道了所保存的 lr 寄存的值,那么就可以知道它的调用者是谁。在栈信息中,一个
函数一个函数地往上找出所有保存的 lr值,就可以知道各个调用函数,这就是栈回溯的原理。 
2.栈回溯实例分析 
仍以前面的 LCD驱动程序为例,使用上面的 Oops信息的栈信息进行分析,栈信息如下: 
 
Stack: (0xc0481e64 to 0xc0482000) 
1e60:c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c 
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704 
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8 c0362644 c036256c c01bd708 c0389a3c 
1ec0: 00000000 c0481ee8 c0481ed4 c01bd788 c01bd4d0 00000000 c0481eec c0481f14 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 18 章  18.3  Oops信息及栈回溯    381║ 
 
1ee0: c0481eec c01bc5a8 c01bd718 c038dac8 c038dac8 c03625b4 00000000 c0389a3c 
⋯ 
 
① 根据 pc寄存器值找到第一个函数,确定它的栈大小,确定调用函数。 
从 Oops信息可知 pc值为 c001a70c,使用它在内核反汇编程序 vmlinux.dis中可以知道它
位于 s3c2410fb_probe函数内。 
根据这个函数开始部分的汇编代码可以知道栈的大小、lr 返回值在栈中保存的位置,代
码如下: 
 
c001a6f4 : 
c001a6f4: e1a0c00d  mov ip, sp 
c001a6f8: e92ddff0  stmdb sp!, {r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc} 
c001a6fc: e24cb004  sub fp, ip, #4 ; 0x4 
c001a700: e24dd010  sub sp, sp, #16 ; 0x10 
⋯ 
c001a70c: e5873000  str r3, [r7]      // pc值 c001a70c对应的指令 
⋯ 
 
{r4, r5, r6, r7, r8, r9, sl, fp, ip, lr, pc}这 11个寄存器都保存在栈中,指令“sub sp, sp, #16”
又使得栈向下扩展了 16字节,所以本函数的栈大小为(11 × 4+16)字节,即 15个双字。 
栈信息开始部分的 15个数据就是本函数的栈内容,下面列出了它们所保存的寄存器。 
 
1e60:          c02b1f70 00000020 c03625d4 c036256c c036256c 00000000 c0389a3c 
                                                          r4        r5         r6 
1e80: c0389a3c c03c420c c0024864 00000000 c0481eac c0481ea0 c01bf4e8 c001a704 
       r7        r8        r9        sl        fp        ip        lr       pc 
 
其中 lr 值为 c01bf4e8,表示函数 s3c2410fb_probe 执行完后的返回地址,它是调用函数
中的地址。下面使用 lr值再次重复本步骤的回溯过程。 
② 根据 lr寄存器值找到调用函数,确定它的栈大小,确定上一级调用函数。 
根据上步得到的 lr 值(c01bf4e8)在内核反汇编程序 vmlinux.dis 中可以知道它位于
platform_drv_probe函数内。 
根据这个函数开始部分的反汇编代码可以知道栈的大小、lr 返回值在栈中保存的位置。
代码如下: 
 
c01bf4c8 : 
c01bf4c8: e1a0c00d  mov ip, sp 
c01bf4cc: e92dd800  stmdb sp!, {fp, ip, lr, pc} 
⋯ 
c01bf4e8: e89da800  ldmia sp, {fp, sp, pc}    // lr值(c01bf4e8)对应的指令 
{fp, ip, lr, pc}这 4寄存器都保存在栈中,本函数的栈大小为 4个双字。Oops栈信息中,
前一个函数 s3c2410fb_probe的栈下面的 4个数据就是函数 platform_drv_probe的栈内容,如
下所示: 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║382    第 18 章  Linux内核调试技术 
 
 
1ea0: c0481ed0 c0481eb0 c01bd5a8 c01bf4d8  
       fp         ip        lr         pc 
 
其中 lr值为 c01bd5a8,表示函数 platform_drv_probe执行完后的返回地址,它是上一级
调用函数中的地址。使用 lr值,重复本步骤的查找过程,直到栈信息分析完毕或者再也无法
分析,这样就可以找出所有的函数调用关系。 
有些函数很简单,没有使用栈(sp 值在这个函数中没有变化),或者没有在栈中保存 lr
值。这些情况需要读者灵活处理,较强的汇编程序阅读能力是关键。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
 
 
 
 
 
 第 20 章  Linux 异常处理体系结构 
 
本章目标   
 
了解 Linux异常处理体系结构   
掌握 Linux中断处理体系结构,了解几种重要的数据结构   
学习中断处理函数的注册、处理、卸载流程   
掌握在驱动程序中使用中断的方法   
20.1  Linux异常处理体系结构概述 
20.1.1  Linux异常处理的层次结构 
内核的中断处理结构有很好的扩充性,并适当屏蔽了一些实现细节。但是开发人员应该
深入“黑盒子”了解其中的实现原理。 
1.异常的作用 
异常,就是可以打断 CPU正常运行流程的一些事情,比如外部中断、未定义的指令、试
图修改只读的数据、执行 swi指令(Software Interrupt Instruction,软件中断指令)等。当这
些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操
作系统中经常通过异常来完成一些特定的功能,除第 9章介绍的“中断”外,还有下面举的
例子(但不限于这些例子)。 
•  当 CPU执行未定义的机器指令时将触发“未定义指令异常”,操作系统可以利用这个
特点使用一些自定义的机器指令,它们在异常处理函数中实现。 
•  可以将一块数据设为只读的,然后提供给多个进程共用,这样可以节省内存。当某个
进程试图修改其中的数据时,将触发“数据访问中止异常”,在异常处理函数中将这
块数据复制出一份可写的副本,提供给这个进程使用。 
•  当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个“数据访问中
止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存(内
存不足时还可以将不使用的数据、指令换出内存),然后重新执行被中断的程序。这
样可以节省内存,还使得操作系统可以运行这类程序:它们使用的内存远大于实际的
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.1  Linux异常处理体系结构概述    397║ 
 
物理内存。 
•  当程序使用不对齐的地址访问内存时,也会触发“数据访问中止异常”,在异常处理
程序中先使用多个对齐的地址读出数据;对于读操作,从中选取数据组合好后返回给
被中断的程序;对于写操作,修改其中的部分数据后再写入内存。这使得程序(特别
是应用程序)不用考虑地址对齐的问题。 
•  用户程序可以通过“swi”指令触发“swi异常”,操作系统在 swi异常处理函数中实
现各种系统调用。 
2.Linux内核对异常的设置 
内核在 start_kernel函数(源码在 init/main.c中)中调用 trap_init、init_IRQ两个函数来设
置异常的处理函数。 
(1)trap_init函数分析。 
trap_init函数(代码在 arch/arm/kernel/traps.c中)被用来设置各种异常的处理向量,包
括中断向量。所谓“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU会自
动执行这些固定位置上的指令。ARM架构 CPU的异常向量基址可以是 0x00000000,也可
以是 0xffff0000,Linux内核使用后者。trap_init函数将异常向量复制到 0xffff0000处,部分
代码如下: 
 
708 void __init trap_init(void) 
709 { 
⋯ 
721     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); 
722     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); 
⋯ 
734 } 
 
第 721行中,vectors等于 0xffff0000。地址__vectors_start~__vectors_end之间的代码就
是异常向量,在 arch/arm/kernel/entry-armv.S中定义,它们被复制到地址 0xffff0000处。 
异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU 自动执行这些指
令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢
复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址__stubs_start~
__stubs_end之间,它们在 arch/arm/kernel/entry-armv.S中定义。第 722行将它们复制到地址
0xffff0000+0x200处。 
异常向量、异常向量跳去执行的代码都是使用汇编写的,为给读者一个形象概念,下面
讲解部分代码,它们在 arch/arm/kernel/entry-armv.S中。 
异常向量的代码如下,其中的“stubs_offset”用来重新定位跳转的位置(向量被复制到
地址 0xffff0000处,跳转的目的代码被复制到地址 0xffff0000+0x200处)。 
 
1059     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start 
1060  
1061     .globl  __vectors_start 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║398    第 20 章  Linux异常处理体系结构 
 
1062 __vectors_start: 
1063     swi SYS_ERROR0                         /* 复位时,CPU将执行这条指令 */ 
1064     b   vector_und + stubs_offset          /* 未定义异常时,CPU将执行这条指令 */ 
1065     ldr pc, .LCvswi + stubs_offset         /* swi异常 */ 
1066     b   vector_pabt + stubs_offset         /* 指令预取中止 */ 
1067     b   vector_dabt + stubs_offset         /* 数据访问中止 */ 
1068     b   vector_addrexcptn + stubs_offset   /* 没有用到 */ 
1069     b   vector_irq + stubs_offset          /* irq异常 */ 
1070     b   vector_fiq + stubs_offset          /* fiq异常 */ 
1071  
1072     .globl  __vectors_end 
1073 __vectors_end: 
 
其中的 vector_und、vector_pabt 等表示要跳转去执行的代码。以 vector_und 为例,它仍
在 arch/arm/kernel/entry-armv.S中,通过 vector_stub宏来定义,代码如下: 
 
1002     vector_stub und, UND_MODE 
1003  
1004     .long   __und_usr               @  0 (USR_26 / USR_32),在用户模式执行了未
定义的指令 
1005     .long   __und_invalid           @  1 (FIQ_26 / FIQ_32),在 FIQ模式执行了
未定义的指令 
1006     .long   __und_invalid           @  2 (IRQ_26 / IRQ_32),在 IRQ模式执行了
未定义的指令 
1007     .long   __und_svc               @  3 (SVC_26 / SVC_32),在管理模式执行了未
定义的指令 
1008     .long   __und_invalid           @  4 
1009     .long   __und_invalid           @  5 
1010     .long   __und_invalid           @  6 
1011     .long   __und_invalid           @  7 
1012     .long   __und_invalid           @  8 
1013     .long   __und_invalid           @  9 
1014     .long   __und_invalid           @  a 
1015     .long   __und_invalid           @  b 
1016     .long   __und_invalid           @  c 
1017     .long   __und_invalid           @  d 
1018     .long   __und_invalid           @  e 
1019     .long   __und_invalid           @  f 
 
第 1002行的 vector_stub是一个宏,它根据后面的参数“und, UND_MODE”定义了以
“vector_und”为标号的一段代码。vector_stub宏的功能为:计算处理完异常后的返回地址、
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.1  Linux异常处理体系结构概述    399║ 
 
保存一些寄存器(比如 r0、lr、spsr),然后进入管理模式,最后根据被中断的工作模式调
用第 1004~1019 行中的某个跳转分支。当发生异常时,CPU 会根据异常的类型进入某个
工作模式,但是很快 vector_stub 宏又会强制 CPU 进入管理模式,在管理模式下进行后续
处理,这种方法简化了程序设计,使得异常发生前的工作模式要么是用户模式,要么是管
理模式。 
第 1004~1019 行中的代码表示在各个工作模式下执行未定义指令时,发生的异常的处
理分支。比如 1004行的__und_usr表示在用户模式下执行未定义指令时,所发生的未定义异
常将由它来处理;第 1007行的__und_svc表示在管理模式下执行未定义指令时,所发生的未
定义异常将由它来处理。在其他工作模式下不可能发生未定义指令异常,否则使用 
“__und_invalid”来处理错误。ARM架构 CPU中使用 4位数据来表示工作模式(目前只有 7
种工作模式),所以共有 16个跳转分支。 
不同的跳转分支(比如__und_usr、__und_svc)只是在它们的入口处(比如保存被中断
程序的寄存器)稍有差别,后续的处理大体相同,都是调用相应的 C函数。比如未定义指令
异常发生时,最终会调用 C函数 do_undefinstr来进行处理。各种的异常的 C处理函数可以分
为 5类,它们分布在不同的文件中。 
① 在 arch/arm/kernel/traps.c中。 
未定义指令异常的 C处理函数在这个文件中定义,总入口函数为 do_undefinstr。 
② 在 arch/arm/mm/fault.c中。 
与内存访问相关的异常的 C处理函数在这个文件中定义,比如数据访问中止异常、指令
预取中止异常。总入口函数为 do_DataAbort、do_PrefetchAbort。 
③ 在 arch/arm/mm/irq.c中。 
中断处理函数的在这个文件中定义,总入口函数为 asm_do_IRQ,它调用其他文件注册
的中断处理函数。 
④ 在 arch/arm/kernel/calls.S中。 
在这个文件中,swi 异常的处理函数指针被组织成一个表格;swi 指令机器码的位[23:0]
被用来作为索引。这样,通过不同的“swi index”指令就可以调用不同的 swi异常处理函数,
它们被称为系统调用,比如 sys_open、sys_read、sys_write等。 
⑤ 没有使用的异常。 
在 Linux 2.6.22.6中没有使用 FIQ异常。 
trap_init 函数搭建了各类异常的处理框架。当发生异常时,各种 C 处理函数会被调用。
这些 C函数还要进一步细分异常发生的情况,分别调用更具体的处理函数。比如未定义指令
异常的 C处理函数总入口为 do_undefinstr,这个函数里还要根据具体的未定义指令调用它的
模拟函数。 
除了中断外,内核已经为各类异常准备了细致而完备的处理函数,比如 swi 异常处理函
数为每一种系统调用都准备了一个“sys_”开头的函数,数据访问中止异常的处理函数为对
齐错误、页权限错误、段翻译错误等具体异常都准备了相应的处理函数。这些异常的处理函
数与开发板的配置无关,基本不用修改。 
(2)init_IRQ函数分析。 
中断也是一种异常,之所以把它单独提出来,是因为中断的处理与具体开发板密切相
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║400    第 20 章  Linux异常处理体系结构 
 
关,除一些必须、共用的中断(比如系统时钟中断、片内外设 UART 中断)外,必须由驱
动开发者提供处理函数。内核提炼出中断处理的共性,搭建了一个非常容易扩充的中断处
理体系。 
init_IRQ 函数(代码在 arch/arm/kernel/irq.c 中)被用来初始化中断的处理框架,设置各
种中断的默认处理函数。当发生中断时,中断总入口函数 asm_do_IRQ 就可以调用这些函数
作进一步处理。 
这一节从总体上介绍了异常处理体系结构,并没有太多地深入具体的代码,读者可以根
据本节提供的线索自行深入了解。如图 20.1所示为异常处理体系结构。 
  
图 20.1  ARM架构 Linux内核的异常处理体系结构 
20.1.2  常见的异常 
ARM架构 Linux内核中,只用到了 5种异常,在它们的处理函数中进一步细分发生这些
异常的原因。表 20.1列出了常见的异常。 
表 20.1  ARM架构 Linux中常见的异常 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.2  Linux中断处理体系结构    401║ 
 
异 常 总 类 异 常 细 分 
ARM指令 break 
Thumb指令 break 未定义指令异常 
ARM指令 mrc 
指令预取中止异常 取指时地址翻译错误(translation fault),系统中还没有为这个指令的地址建立
映射关系 
访问数据时段地址翻译错误(section translation fault) 
访问数据时页地址翻译错误(page translation fault) 
地址对齐错误 
段权限错误(section permission fault) 
页权限错误(page permission fault) 
数据访问中止异常 
⋯ 
中断异常 GPIO引脚中断、WDT中断、定时器中断、USB中断、UART中断等 
swi异常 各类系统调用,sys_open、sys_read、sys_write等 
20.2  Linux中断处理体系结构 
20.2.1  中断处理体系结构的初始化 
1.中断处理体系结构 
Linux 内核将所有的中断统一编号,使用一个 irq_desc 结构数组来描述这些中断:每个
数组项对应一个中断(也有可能是一组中断,它们共用相同的中断号),里面记录了中断的名
称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问
函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册
的中断处理函数。 
通过 irq_desc 结构数组就可以了解中断处理体系结构,irq_desc 结构的数据类型在
include/linux/irq.h中定义,如下所示: 
 
151 struct irq_desc { 
152     irq_flow_handler_t  handle_irq; /* 当前中断的处理函数入口 */ 
153     struct irq_chip     *chip;      /* 低层的硬件访问 */ 
⋯⋯ 
157     struct irqaction    *action;    /* 用户提供的中断处理函数链表 */ 
158     unsigned int        status;     /* IRQ状态 */ 
⋯⋯ 
175     const char      *name;          /* 中断名称 */ 
176 } ____cacheline_internodealigned_in_smp; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║402    第 20 章  Linux异常处理体系结构 
 
 
第 152 行的 handle_irq 是这个或这组中断的处理函数入口。发生中断时,总入口函数
asm_do_IRQ将根据中断号调用相应 irq_desc数组项中的 handle_irq。handle_irq使用 chip结
构中的函数来清除、屏蔽或者重新使能中断,还一一调用用户在 action链表中注册的中断处
理函数。 
第 153行的 irq_chip结构类型也是在 include/linux/irq.h中定义,其中的成员大多用于操作
底层硬件,比如设置寄存器以屏蔽中断、使能中断、清除中断等。这个结构的部分成员如下: 
 
98  struct irq_chip { 
99   const char  *name; 
100  unsigned int   (*startup)(unsigned int irq); /* 启动中断,如果不设置,缺省为
"enable" */ 
101  void    (*shutdown)(unsigned int irq);    /* 关闭中断,如果不设置,缺省为
"disable" */  
102  void    (*enable)(unsigned int irq);      /* 使能中断,如果不设置,缺省为
"unmask" */ 
103  void    (*disable)(unsigned int irq);    /* 禁止中断,如果不设置,缺省为"mask" */ 
104  
105  void    (*ack)(unsigned int irq);  /* 响应中断,通常是清除当前中断使得可以接收
下一个中断*/ 
106  void    (*mask)(unsigned int irq);       /* 屏蔽中断源 */ 
107  void    (*mask_ack)(unsigned int irq);   /* 屏蔽和响应中断 */ 
108  void    (*unmask)(unsigned int irq);     /* 开启中断源 */ 
⋯ 
126 } 
 
irq_desc结构中第 157行的 irqaction结构类型在 include/linux/interrupt.h中定义。用户注
册的每个中断处理函数用一个 irqaction结构来表示,一个中断(比如共享中断)可以有多个
处理函数,它们的 irqactio结构链接成一个链表,以 action为表头。irq_desc结构定义如下:  
84 struct irqaction { 
85     irq_handler_t handler;    /* 用户注册的中断处理函数 */ 
86     unsigned long flags;      /* 中断标志,比如是否共享中断、电平触发还是边沿触发等 */ 
87     cpumask_t mask;           /* 用于 SMP(对称多处理器系统) */ 
88     const char *name;         /* 用户注册的中断名字,"cat /proc/interrupts"时
可以看到 */ 
89     void *dev_id;             /* 用户传给上面的 handler 的参数,还可以用来区分共
享中断 */ 
90     struct irqaction *next; 
91     int irq;                 /* 中断号 */ 
92     struct proc_dir_entry *dir; 
93 }; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.2  Linux中断处理体系结构    403║ 
 
 
irq_desc结构数组、它的成员“struct irq_chip *chip”、“struct irqaction *action”,这 3种
数据结构构成了中断处理体系的框架。这 3者的关系如图 20.2所示。 
 
图 20.2  Linux中断处理体系结构 
中断的处理流程如下。 
(1)发生中断时,CPU执行异常向量 vector_irq的代码。 
(2)在 vector_irq里面,最终会调用中断处理的总入口函数 asm_do_IRQ。 
(3)asm_do_IRQ根据中断号调用 irq_desc数组项中的 handle_irq。 
(4)handle_irq 会使用 chip 成员中的函数来设置硬件,比如清除中断、禁止中断、重新
使能中断等。 
(5)handle_irq逐个调用用户在 action链表中注册的处理函数。 
可见,中断体系结构的初始化就是构造这些数据结构,比如 irq_desc 数组项中的
handle_irq、chip等成员;用户注册中断时就是构造 action链表;用户卸载中断时就是从 action
链表中去除不需要的项。 
2.中断处理体系结构的初始化 
init_IRQ函数被用来初始化中断处理体系结构,代码在 arch/arm/kernel/irq.c中。 
156 void __init init_IRQ(void) 
157 { 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║404    第 20 章  Linux异常处理体系结构 
 
158     int irq; 
159  
160     for (irq = 0; irq < NR_IRQS; irq++) 
161         irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; 
⋯ 
167     init_arch_irq(); 
168 } 
 
第 160~161行初始化 irq_desc结构数组中每一项的中断状态。 
第 167行调用架构相关的中断初始化函数。对于本书所用的 S3C2410、S3C2440开发板,
这个函数就是 s3c24xx_init_irq,移植 Linux 内核时讲述的 machine_desc 结构中的 init_irq 成
员就指向这个函数。 
s3c24xx_init_irq 函数在 arch/arm/plat-s3c24xx/irq.c 中定义,它为所有的中断设置了芯片
相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq]. handle_irq)。以外
部中断 EINT4~EINT23为例,用来设置它们的代码如下: 
 
760     for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) { 
761         irqdbf("registering irq %d (extended s3c irq)\n", irqno); 
762         set_irq_chip(irqno, &s3c_irqext_chip); 
763         set_irq_handler(irqno, handle_edge_irq); 
764         set_irq_flags(irqno, IRQF_VALID); 
765     } 
 
第 762行 set_irq_chip函数的作用就是“irq_desc[irqno].chip = &s3c_irqext_chip”。以后就
可以通过 irq_desc[irqno].chip 结构中的函数指针设置这些外部中断的触发方式(电平触发、
边沿触发等)、使能中断、禁止中断等。 
第 763行设置这些中断的处理函数入口为 handle_edge_irq,即“irq_desc[irqno]. handle_irq = 
handle_edge_irq”。发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数。 
第 764行设置中断标志为“IRQF_VALID”,表示可以使用它们。 
对于本书使用的 S3C2410、S3C2440 开发板,init_IRQ 函数执行完后,图 20.2 中各个
irq_desc数组项的 chip、handle_irq成员都被设置好了。 
20.2.2  用户注册中断处理函数的过程 
用户(即驱动程序)通过 request_irq 函数向内核注册中断处理函数,request_irq 函数根
据中断号找到 irq_desc数组项,然后在它的 action链表中添加一个表项。 
request_irq函数在 kernel/irq/manage.c中定义,函数原型如下: 
 
int request_irq(unsigned int irq, irq_handler_t handler, 
        unsigned long irqflags, const char *devname, void *dev_id) 
request_irq函数首先使用这 4个参数构造一个 irqaction结构,然后调用 setup_irq函数将
它链入链表中,代码如下: 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.2  Linux中断处理体系结构    405║ 
 
 
527     action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC); 
⋯ 
531     action->handler = handler; 
532     action->flags = irqflags; 
533     cpus_clear(action->mask); 
534     action->name = devname; 
535     action->next = NULL; 
536     action->dev_id = dev_id; 
⋯ 
559     retval = setup_irq(irq, action); 
 
setup_irq函数也是在 kernel/irq/manage.c中定义,它完成如下 3个功能(本书忽略了其他
不感兴趣的功能)。 
(1)将新建的 irqaction结构链入 irq_desc[irq]结构的 action链表中,这有两种可能。 
① 如果 action链表为空,则直接链入。 
② 否则先判断新建的 irqaction 结构和链表中的 irqaction 结构所表示的中断类型是否一
致;即是否都声明为“可共享的”(IRQF_SHARED)、是否都使用相同的触发方式(电平、
边沿、极性),如果一致,则将新建的 irqaction结构链入。 
(2)设置 irq_desc[irq]结构中 chip成员的还没设置的指针,让它们指向一些默认函数。 
 
 注意 
chip成员在 init_IRQ函数初始化中断体系结构的时候已经被设置,这里只是设置其中还没设置
的指针。 
 
这通过 irq_chip_set_defaults函数来完成,它在 kernel/irq/chip.c中定义。 
 
251 void irq_chip_set_defaults(struct irq_chip *chip) 
252 { 
253     if (!chip->enable) 
254         chip->enable = default_enable;      /* 它调用 chip->unmask */ 
255     if (!chip->disable) 
256         chip->disable = default_disable;    /* 此函数为空 */     
257     if (!chip->startup) 
258         chip->startup = default_startup;    /* 它调用 chip->enable */ 
259     if (!chip->shutdown) 
260         chip->shutdown = chip->disable; 
261     if (!chip->name) 
262         chip->name = chip->typename; 
263     if (!chip->end) 
264         chip->end = dummy_irq_chip.end; 
265 } 
(3)设置中断的触发方式。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║406    第 20 章  Linux异常处理体系结构 
 
如果 requet_irq函数中传入的 irqflags参数表示中断的触发方式为高电平触发、低电平触
发、上升沿触发或下降沿触发,则调用 irq_desc[irq]结构中的 chip->set_type 成员函数来进行
设置:设置引脚功能为外部中断,设置中断触发方式。 
 
 注意 如果原来的 action链表非空,表示以前已经设置过这个中断的触发方式,就不用再次设置了。
 
(4)启动中断。 
如果 irq_desc[irq]结构中 status成员没有被指明为 IRQ_NOAUTOEN(表示注册中断时不
要使能中断),还要调用 chip->startup或 chip->enable来启动中断。所谓启动中断通常就是使
能中断。 
一般来说,只有那些“可以自动使能的”中断对应的 irq_desc[irq].status 才会被指明为
IRQ_NOAUTOEN。所以,无论哪种情况,执行 reqeust_irq 注册中断之后,这个中断就已经
被使能了,在编写驱动程序时要注意这点。 
总结一下使用 reqeust_irq函数注册中断后的“成果”。 
(1)irq_desc[irq]结构中的 action链表中已经链入了用户注册的中断处理函数。 
(2)中断的触发方式已经被设好。 
(3)中断已经被使能。 
总之,执行 reqeust_irq函数之后,中断就可以发生并能够被处理了。 
20.2.3  中断的处理过程 
asm_do_IRQ是中断的 C语言总入口函数,它在 arch/arm/kernel/irq.c中定义,部分代码如下:  
111 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) 
112 { 
113     struct pt_regs *old_regs = set_irq_regs(regs); 
114     struct irq_desc *desc = irq_desc + irq; 
⋯ 
125     desc_handle_irq(irq, desc); 
⋯   
132 } 
 
第 125 行的 desc_handle_irq 函数直接调用 desc 结构中的 handle_irq 成员函数,它就是
irq_desc[irq].handle_irq。 
需要说明的是,asm_do_IRQ函数中参数 irq的取值范围为 IRQ_EINT0~(IRQ_EINT0 + 
31),只有 32 个取值。它可能是一个实际中断的中断号,也可能是一组中断的中断号。这是
由 S3C2410、S3C2440 的芯片特性决定的:发生中断时 INTPND 寄存器的某一位被置 1,
INTOFFSET 寄存器中记录了是哪一位(0~31),中断向量调用 asm_do_IRQ 之前根据
INTOFFSET寄存器的值确定 irq参数。每一个实际的中断在 irq_desc数组中都有一项与它对
应,它们的数目不止 32。当 asm_do_IRQ 函数中参数 irq 表示的是“一组”中断时,
irq_desc[irq].handle_irq 成员函数还需要先分辨出是哪一个中断(假设中断号为 irqno),然后
调用 irq_desc[irqno].handle_irq来进一步处理。 
以外部中断 EINT8~EINT23为例,它们通常是边沿触发。 
(1)它们被触发时,INTOFFSET寄存器中的值都是 5,asm_do_IRQ函数中参数 irq的值
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.2  Linux中断处理体系结构    407║ 
 
为( IRQ_EINT0+5),即 IRQ_EINT8t23。上面代码中第 125 行将调用 irq_desc[IRQ_ 
EINT8t23].handle_irq来进行处理。 
(2)irq_desc[IRQ_EINT8t23].handle_irq在前面 init_IRQ函数初始化中断体系结构的时候
被设为 s3c_irq_demux_extint8。 
(3)s3c_irq_demux_extint8 函数的代码在 arch/arm/plat-s3c24xx/irq.c 中,它首先读取
EINTPEND、EINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用
irq_desc数组项中的 handle_irq成员函数。 
代码如下: 
 
558 static void 
559 s3c_irq_demux_extint8(unsigned int irq, 
560               struct irq_desc *desc) 
561 { 
562     unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); /* EINT8~EINT23
发生时,相应位被置 1 */ 
563     unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);  /* 屏蔽寄存器 */ 
564  
565     eintpnd &= ~eintmsk;  /* 清除被屏蔽的位 */ 
566     eintpnd &= ~0xff;     /* 清除低 8位(EINT8对应位 8,⋯) */ 
567  
568     /* 循环处理所有的子中断 */ 
569  
570     while (eintpnd) { 
571         irq = __ffs(eintpnd);   /* 确定 eintpnd中为 1的最高位 */ 
572         eintpnd &= ~(1<chip->ack(irq); 
⋯ 
497     action_ret = handle_IRQ_event(irq, action); 
⋯ 
507 } 
 
第 466行用来统计中断发生的次数。 
第 469行响应中断,通常是清除当前中断使得可以接收下一个中断。对于 IRQ_EINT8~
IRQ_EINT23这几个中断,desc->chip在前面 init_IRQ函数初始化中断体系结构的时候被设为
s3c_irqext_chip。desc->chip->ack就是 s3c_irqext_ack函数(arch/arm/plat-s3c24xx/ irq.c),它
用来清除中断。 
第 497行通过 handle_IRQ_event函数来逐个执行 action链表中用户注册的中断处理函数,
它在 kernel/irq/handle.c中定义,关键代码如下: 
 
139     do { 
140         ret = action->handler(irq, action->dev_id); /* 执行用户注册的中断处
理函数 */ 
141         if (ret == IRQ_HANDLED) 
142             status |= action->flags; 
143         retval |= ret; 
144         action = action->next;                      /* 下一个 */ 
145     } while (action); 
 
从第 140行可以知道,用户注册的中断处理函数的参数为中断号 irq、action->dev_id。后
一个参数是通过 request_irq函数注册中断时传入的 dev_id参数。它由用户自己指定、自己使
用,可以为空,当这个中断是“共享中断”时除外(这在下面卸载中断时说明)。 
对于电平触发的中断,它们的 irq_desc[irq].handle_irq通常是 handle_level_irq函数。它也
是在 kernel/irq/chip.c中定义,其功能与上述 handle_edge_irq函数相似,关键代码如下: 
 
335 void fastcall 
336 handle_level_irq(unsigned int irq, struct irq_desc *desc) 
337 { 
⋯ 
343     mask_ack_irq(desc, irq); 
⋯ 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.2  Linux中断处理体系结构    409║ 
 
348     kstat_cpu(cpu).irqs[irq]++; 
⋯ 
364     action_ret = handle_IRQ_event(irq, action); 
⋯ 
371         desc->chip->unmask(irq); 
⋯ 
374 } 
 
第 343行用来屏蔽和响应中断,响应中断通常就是清除中断,使得可以接收下一个中断。 
 
 注意 这时即使触发了下一个中断,也只是记录寄存器中而已,只有在中断被再次使能后才能被处理。
 
第 348行用来统计中断发生的次数。 
第 364行通过 handle_IRQ_event函数来逐个执行 action链表中用户注册的中断处理函数。 
第 371行开启中断,与前面第 343行屏蔽中断对应。 
在 handle_edge_irq、handle_level_irq 函数的开头都清除了中断。所以一般来说,在用户
注册的中断处理函数中就不用再次清除中断了。但是对于电平触发的中断也有例外:虽然
handle_level_irq函数已经清除了中断,但是它只限于清除 SoC内部的信号;如果外设输入到
SoC的中断信号仍然有效,这就会导致当前中断处理完毕后,会误认为再次发生了中断。对
于这种情况,需要在用户注册的中断处理函数中清除中断:先清除外设的中断,然后再清除
SoC内部的中断信号。 
忽略上述的中断号重新计算过程(这不影响对整体流程的理解),中断的处理流程可以
总结如下。 
(1)中断向量调用总入口函数 asm_do_IRQ,传入根据中断号 irq。 
(2)asm_do_IRQ函数根据中断号 irq调用 irq_desc[irq].handle_irq,它是这个中断的处理
函数入口。对于电平触发的中断,这个入口通常为 handle_level_irq;对于边沿触发的中断,
这个入口通常为 handle_edge_irq。 
(3)入口函数首先清除中断,入口函数是 handle_level_irq时还要屏蔽中断。 
(4)逐个调用用户在 irq_desc[irq].action链表中注册的中断处理函数。 
(5)入口函数是 handle_level_irq时还要重新开启中断。 
20.2.4  卸载中断处理函数 
中断是一种很稀缺的资源,当不再使用一个设备时,应该释放它占据的中断。这通过 free_irq
函数来实现,它与 request_irq一样,也是在 kernel/irq/manage.c中定义。它的函数原型如下。 
 
void free_irq(unsigned int irq, void *dev_id) 
 
它需要用到两个参数:irq和 dev_id,它们与通过 reqeust_irq注册中断函数时使用的参数
一样。使用中断号 irq定位 action链表,再使用 dev_id在 action链表中找到要卸载的表项。
所以,同一个中断的不同中断处理函数必须使用不同的 dev_id来区分,这就要求在注册共享
中断时参数 dev_id必须惟一。 
free_irq函数的处理过程与 reqeust_irq函数相反。 
(1)根据中断号 irq、dev_id从 action链表中找到表项,将它移除。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║410    第 20 章  Linux异常处理体系结构 
 
(2)如果它是惟一的表项,还要调用 IRQ_DESC[IRQ].CHIP->SHUTDOWN 或 IRQ_ 
DESC[IRQ]. CHIP->DISABLE来关闭中断。 
20.3  使用中断的驱动程序示例 
20.3.1  按键驱动程序源码分析 
开发板上有 4个按键,它们的连线如图 20.3所示。 
 
图 20.3  按键连线图 
按键驱动程序就是光盘上的 drivers_and_test/buttons/s3c24xx_buttons.c文件,下面按照函
数调用的顺序进行讲解。 
1.模块的初始化函数和卸载函数 
代码如下: 
 
130 /* 
131  * 执行"insmod s3c24xx_buttons.ko"命令时就会调用这个函数 
132  */ 
133 static int __init s3c24xx_buttons_init(void) 
134 { 
135     int ret; 
136  
137     /* 注册字符设备驱动程序 
138      * 参数为主设备号、设备名字、file_operations结构; 
139      * 这样,主设备号就和具体的 file_operations结构联系起来了, 
140      * 操作主设备为 BUTTON_MAJOR的设备文件时,就会调用 s3c24xx_buttons_fops中的
相关成员函数 
141      * BUTTON_MAJOR可以设为 0,表示由内核自动分配主设备号 
142      */ 
143     ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops); 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.3  使用中断的驱动程序示例    411║ 
 
144     if (ret < 0) { 
145       printk(DEVICE_NAME " can't register major number\n"); 
146       return ret; 
147     } 
148      
149     printk(DEVICE_NAME " initialized\n"); 
150     return 0; 
151 } 
152  
153 /* 
154  * 执行"rmmod s3c24xx_buttons.ko"命令时就会调用这个函数  
155  */ 
156 static void __exit s3c24xx_buttons_exit(void) 
157 { 
158     /* 卸载驱动程序 */ 
159     unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME); 
160 } 
161  
162 /* 这两行指定驱动程序的初始化函数和卸载函数 */ 
163 module_init(s3c24xx_buttons_init); 
164 module_exit(s3c24xx_buttons_exit); 
165 
 
与 LED驱动相似,执行“insmod s3c24xx_buttons.ko”命令加载驱动时就会调用这个驱
动初始化函数 s3c24xx_buttons_init;执行“rmmod s3c24xx_buttons.ko”命令卸载驱动时就会
调用卸载函数 s3c24xx_buttons_exit。前者调用 register_chrdev 函数向内核注册驱动程序,后
者调用 s3c24xx_buttons_exit函数卸载这个驱动程序。 
驱动程序的核心是 s3c24xx_buttons_fops结构,定义如下: 
 
119 /* 这个结构是字符设备驱动程序的核心 
120  * 当应用程序操作设备文件时所调用的 open、read、write等函数, 
121  * 最终会调用这个结构中的对应函数 
122  */ 
123 static struct file_operations s3c24xx_buttons_fops = { 
124     .owner   =   THIS_MODULE,    /* 这是一个宏,指向编译模块时自动创建的__ 
this_module变量 */ 
125     .open    =   s3c24xx_buttons_open, 
126     .release =   s3c24xx_buttons_close,  
127     .read    =   s3c24xx_buttons_read, 
128 }; 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║412    第 20 章  Linux异常处理体系结构 
 
129 
 
第 123~125行的 3个函数在下面依次讲述。 
2.s3c24xx_buttons_open函数 
在应用程序执行 open("/dev/buttons",⋯)系统调用时,s3c24xx_buttons_open 函数将被调
用。它用来注册 4个按键的中断处理程序,代码如下: 
 
54 /* 应用程序对设备文件/dev/buttons执行 open(⋯)时, 
55  * 就会调用 s3c24xx_buttons_open函数 
56  */ 
57 static int s3c24xx_buttons_open(struct inode *inode, struct file *file) 
58 { 
59     int i; 
60     int err; 
61      
62     for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { 
63         // 注册中断处理函数 
64         err = request_irq(button_irqs[i].irq, buttons_interrupt, button_ 
irqs[i].flags,  
65                           button_irqs[i].name, (void *)&press_cnt[i]); 
66         if (err) 
67             break; 
68     } 
69  
70     if (err) { 
71         // 如果出错,释放已经注册的中断 
72         i--; 
73         for (; i >= 0; i--) 
74             free_irq(button_irqs[i].irq, (void *)&press_cnt[i]); 
75         return -EBUSY; 
76     } 
77      
78     return 0; 
79 } 
80 
第 64行向内核注册中断处理函数,request_irq函数的作用在前面已经讲解过。注册成功
后,这 4个按键所用 GPIO引脚的功能被设为外部中断,触发方式为下降沿触发,中断处理
函数为 buttons_interrupt。最后一个参数“(void *)&press_cnt[i]”将在 buttons_interrupt函数中
用到,它用来存储按键被按下的次数。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.3  使用中断的驱动程序示例    413║ 
 
第 70~76 行是出错处理的代码,如果前面有某个中断没有注册成功,这几行代码用来
卸载已经注册的中断。 
第 64 行中用到的参数 button_irqs 定义如下,表示了这 4 个按键的中断号、中断触发方
式、中断名称(名称只是供执行“cat /proc/interrupts”时显示用)。 
 
15 struct button_irq_desc { 
16     int irq;               /* 中断号 */ 
17     unsigned long flags;   /* 中断标志,用来定义中断的触发方式 */ 
18     char *name;            /* 中断名称 */ 
19 }; 
20  
21 /* 用来指定按键所用的外部中断引脚及中断触发方式、名字 */ 
22 static struct button_irq_desc button_irqs [] = { 
23     {IRQ_EINT19, IRQF_TRIGGER_FALLING, "KEY1"}, /* K1 */ 
24     {IRQ_EINT11, IRQF_TRIGGER_FALLING, "KEY2"}, /* K2 */ 
25     {IRQ_EINT2,  IRQF_TRIGGER_FALLING, "KEY3"}, /* K3 */ 
26     {IRQ_EINT0,  IRQF_TRIGGER_FALLING, "KEY4"}, /* K4 */ 
27 }; 
28 
3.s3c24xx_buttons_close函数 
s3c24xx_buttons_close函数的作用与 s3c24xx_buttons_open函数相反,它用来卸载 4个按
键的中断处理函数,代码如下: 
 
82 /* 应用程序对设备文件/dev/buttons执行 close(⋯)时, 
83  * 就会调用 s3c24xx_buttons_close函数 
84  */ 
85 static int s3c24xx_buttons_close(struct inode *inode, struct file *file) 
86 { 
87     int i; 
88      
89     for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) { 
90         // 释放已经注册的中断 
91         free_irq(button_irqs[i].irq, (void *)&press_cnt[i]); 
92     } 
93  
94     return 0; 
95 } 
96 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║414    第 20 章  Linux异常处理体系结构 
 
4.s3c24xx_buttons_read函数 
中断处理函数会在 press_cnt 数组中记录按键被按下的次数。s3c24xx_buttons_read 函数
首先判断是否有按键被再次按下,如果没有则休眠等待;否则读取 press_cnt数组的数据,代
码如下:  
32 /* 等待队列:  
33  * 当没有按键被按下时,如果有进程调用 s3c24xx_buttons_read函数, 
34  * 它将休眠 
35  */ 
36 static DECLARE_WAIT_QUEUE_HEAD(button_waitq); 
37  
38 /* 中断事件标志, 中断服务程序将它置 1,s3c24xx_buttons_read将它清 0 */ 
39 static volatile int ev_press = 0; 
⋯ 
98 /* 应用程序对设备文件/dev/buttons执行 read(⋯)时, 
99  * 就会调用 s3c24xx_buttons_read函数 
100  */ 
101 static int s3c24xx_buttons_read(struct file *filp, char __user *buff,  
102                                          size_t count, loff_t *offp) 
103 { 
104     unsigned long err; 
105      
106     /* 如果 ev_press等于 0,休眠 */ 
107     wait_event_interruptible(button_waitq, ev_press); 
108  
109     /* 执行到这里时 ev_press肯定等于 1,将它清 0 */ 
110     ev_press = 0; 
111  
112     /* 将按键状态复制给用户,并清 0 */ 
113     err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), 
count)); 
114     memset((void *)press_cnt, 0, sizeof(press_cnt)); 
115  
116     return err ? -EFAULT : 0; 
117 } 
118 
第 107行的 wait_event_interruptible首先会判断 ev_press是否为 0,如果为 0才会令当前
进程进入休眠;否则向下继续执行。它的第一个参数 button_waitq 是一个等待队列,在前面
第 36行中定义;第二个参数 ev_press用来表示中断是否已经发生,中断服务程序将它置 1,
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.3  使用中断的驱动程序示例    415║ 
 
s3c24xx_buttons_read 将它清 0。如果 ev_press 为 0,则当前进程会进入休眠,中断发生时中
断处理函数 buttons_interrupt会把它唤醒。 
第 110行将 ev_press清 0。 
第 113行将 press_cnt数组的内容复制到用户空间。buff参数表示的缓冲区位于用户空间,
使用 copy_to_user向它赋值。 
第 114行将 press_cnt数组清 0。 
5.中断处理函数 buttons_interrupt 
这 4个按键的中断处理函数都是 buttons_interrupt,代码如下: 
 
42 static irqreturn_t buttons_interrupt(int irq, void *dev_id) 
43 { 
44     volatile int *press_cnt = (volatile int *)dev_id; 
45      
46     *press_cnt = *press_cnt + 1;            /* 按键计数加 1 */ 
47     ev_press = 1;                           /* 表示中断发生了 */ 
48     wake_up_interruptible(&button_waitq);   /* 唤醒休眠的进程 */ 
49      
50     return IRQ_RETVAL(IRQ_HANDLED); 
51 } 
52 
 
buttons_interrupt 函数被调用时,第一个参数 irq 表示发生的中断号,第二个参数 dev_id
就是前面使用 request_irq注册中断时传入的“&press_cnt[i]”(请参考前面第 65行代码)。 
第 46行将按键计数加 1。 
第 47~48行将 ev_press设为 1,唤醒休眠的进程。 
将 s3c24xx_buttons.c放到内核源码目录 drivers/char下,在 drivers/char/Makefile中增加如
下一行: 
 
obj-m    += s3c24xx_buttons.o 
 
在内核根目录下执行“make modules”命令即可在 drivers/char 目录下生成可加载模块
s3c24xx_buttons.ko,把它放到开发板根文件系统的/lib/modules/2.6.22.6/目录下,就可以使用
“insmod s3c24xx_buttons”、“rmmod s3c24xx_buttons”命令进行加载、卸载了。 
20.3.2  测试程序情景分析 
按键测试程序源码为 drivers_and_test/buttons/button_test.c,在这个目录下执行“make”
命令即可生成可执行程序 button_test,然后把它放到开发板根文件系统/usr/bin/目录下。 
在开发板根文件系统中建立设备文件: 
 
# mknod /dev/buttons c 232 0 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║416    第 20 章  Linux异常处理体系结构 
 
然后使用“insmod s3c24xx_buttons”命令加载模块。 
现在直接运行 button_test 即可进行测试:按下 K1~K4,就可以在控制台上观察到类似
如下的打印输出: 
 
K1 has been pressed 2 times! 
 
要终止 button_test,如果它在前台运行,可以输入“Ctrl + C”,如果它在后台运行,可以
输入“killall button_test”。 
测试程序 button_test.c代码很简单,下面按照使用过程进行分析。 
1.加载模块 
执行“insmod s3c24xx_buttons”即可加载模块,这时在控制台执行“cat /proc/devices”
命令可以看到内核中已经有了 buttons设备,可以看到如下字样: 
 
Character devices: 
⋯ 
232 buttons 
 
这表明按键设备属于字符设备,主设备号为 232。 
2.测试程序打开设备 
运行测试程序 button_test后,/dev/buttons设备就被打开了,可以使用“cat /proc/interrupts”
命令看到注册了 4 个中断。为了便于输入其他命令,在后台运行测试程序 button_test,在命
令的最后加上“&”就可以了,如下所示: 
 
# button_test & 
 
这时候执行“cat /proc/interrupts”命令可以看到中断的注册、使用情况。第一列数据表
示中断号;第二列数据表示这个中断发生的次数;第三列的文字表示这个中断的硬件访问结
构“struct irq_chip *chip”的名字,它在初始化中断体系结构时指定;第四列文字表示中断的
名称,它在 reqeust_irq中指定。对于这 4个按键,可以看到如下字样: 
 
# cat /proc/interrupts  
           CPU0 
 16:          1    s3c-ext0  KEY4 
 18:          0    s3c-ext0  KEY3 
⋯ 
 55:          0     s3c-ext  KEY2 
 63:         22     s3c-ext  KEY1 
⋯ 
添试程序中打开设备的代码如下: 
 
01 #include  
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
第 20 章  20.3  使用中断的驱动程序示例    417║ 
 
02 #include  
03 #include  
04 #include  
05  
06 int main(int argc, char **argv) 
07 { 
08     int i; 
09     int ret; 
10     int fd; 
11     int press_cnt[4]; 
12      
13     fd = open("/dev/buttons", 0);  // 打开设备 
14     if (fd < 0) { 
15         printf("Can't open /dev/buttons\n"); 
16         return -1; 
17     } 
18 
 
3.测试程序读取数据 
读取数据的代码如下: 
 
19     // 这是个无限循环,进程有可能在 read函数中休眠,当有按键被按下时,它才返回 
20     while (1) { 
21         // 读出按键被按下的次数 
22         ret = read(fd, press_cnt, sizeof(press_cnt)); 
23         if (ret < 0) { 
24             printf("read err!\n"); 
25             continue; 
26         }  
27  
28         for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) { 
29             // 如果被按下的次数不为 0,打印出来 
30             if (press_cnt[i]) 
31                   printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]); 
32         } 
33     } 
34      
第 22 行用来读取数据,如果以前(或自从上次读取之后)没有按键被按下,则进程进
入休眠,可以使用“ps”命令观察到这点,如下所示: 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页
 
║418    第 20 章  Linux异常处理体系结构 
 
 
# ps 
  PID  Uid        VSZ Stat Command 
 ⋯⋯ 
  752 0           120 S   button_test 
 
上面的“S”表示 button_test进程处于休眠状态。 
 
 
 
 
 
 
 
 
 
 
 
 
 
样
章
 
丛
  
书
  
名
:
 嵌
入
式
Linu
x应
用
开
发
完
全
手
册
 
作
  
  
  
者
:
 韦
东
山
  
编
  
  
  
辑
:
 黄
  
焱
 
网
  
  
  
站
:
 百
问
网
htt
p:/
/w
ww.10
0a
sk.
ne
t 
书
  
  
  
号
:
 9
78
-7-
11
5-1
82
62
-3 
 
出
  
版
  
社
:
 人
民
邮
电
出
版
社
  
出
 版
 日
 期
:
20
08
-08
  
版
  
  
  
次
:
 第
1版
第
1次
  
开
  
  
  
本
:
 1
6开
 
页
  
  
  
数
:
 5
79
页

缩略图:

  • 缩略图1
  • 缩略图2
  • 缩略图3
  • 缩略图4
  • 缩略图5
当前页面二维码

广告: