基于S3C2410嵌入式Linux开发实验与实践


高校嵌入式通用教材 · Embest EduKit-IV 系列丛书 基于 S3C2410 嵌入式 Linux 开发实验与实践 Embest · 教学业务线 深圳市英蓓特信息技术有限公司 www.embedinfo.com support@edukit.com.cn - 1 - 前 言 本书是深圳市英蓓特信息技术有限公司《嵌入式教学平台 EduKit-IV 实验丛书》之一,主要基 于英蓓特公司目前最新嵌入式教学平台 EduKit-IV,搭配核心 SAMSUNG S3C2410 板和其他相关模块, 主要介绍嵌入式操作系统 Windows CE6.0 及其 BSP 包的开发,应用的开发等。 经历过近几年的嵌入式 ARM 的推广,一般老师和学生对于嵌入式 ARM 已经有一定的了解,市 面上也存在大量低端的 ARM7 和 ARM9 学习板以供课余学习。 本教程主要介绍 Linux-2.6.14 在 SAMSUNG S3C2410 上的移植,通过从浅入深的介绍,引领学 生步入嵌入式 Linux 开发的大门,整个教程从 Linux 的理论介绍,到 Linux 内核的开发、驱动应用的 设计,贯穿嵌入式 Linux 学习整个过程。完整的学习本书,学生可以全面的掌握高端的嵌入式平台 的整个开发流程和经验。 本书各章节内容主要安排如下: 第一章:介绍 EMBEST EDUKIT-IV 嵌入式实验平台的软硬件资源,重点根据硬件接口定义,讲 述了实验平台主板及 CPU 子板的原理、功能及结构。 第二章:讲述嵌入式操作系统 Linux 的特点、系统原理。 第三章:讲述 Linux 的安装与使用,linux 常用命令的使用等。 第四章:主要介绍嵌入式 Linux 环境的构建,嵌入式编译器,嵌入式开发基础。 第五章:主要介绍嵌入式 Linux 驱动开发基础。 第六章:主要主要介绍嵌入式 Linux 系统移植,包括 Bootloader 的开发、内核的移植裁剪编译、 文件系统的制作等。 第七章:本章节主要介绍嵌入式 Linux 应用程序开发实验。 第八章:本章节包含一些基础驱动开发实验,包括 LED、INT、ADC、RTC 等接口操作实验。 第九章:本章节包含一些高级驱动开发实验,包括 LCD、TSP、SD、USB 等接口操作实验。 第十章:本章节包含一些模块驱动开发实验,包括电机、蓝牙、GPRS、GPS 等接口操作实验。 第十一章:本章节包含一些高级应用开发实验,包括 BOA 服务器、MADPALY 等操作实验。 第十二章:本章节包含一些模块驱动开发实验,包括电机、蓝牙、GPRS、GPS 等接口操作实验。 本书可作为电子,通信,自动化,计算机等电类专业嵌入式系统课程实验教学的教材,也可以 供嵌入式系统相关工程技术人员参考。 由于时间短暂,编写本书难免存在一些错误和不足,欢迎大家来信(support@edukit.com.cn) 指正。 深圳市英蓓特信息技术有限公司 2008 年 4 月 - 2 - 目 录 第一章 EduKit-IV 嵌入式教学系统平台 .................................................................... - 1 - 1. 1 教学系统平台简介 ............................................................................................ - 1 - 1.1.1 基本组成.............................................................................................. - 1 - 1.1.2 功能特点.............................................................................................. - 1 - 1. 2 教学系统主板硬件介绍...................................................................................... - 4 - 1.2.1 主板硬件设计原理说明 .......................................................................... - 4 - 1.2.2 主板硬件结构 ..................................................................................... - 21 - 1.2.3 主板硬件资源分配............................................................................... - 21 - 1.2.4 扩展接口及功能模块介绍..................................................................... - 22 - 1. 3 Mini2410-IV 核心子板硬件介绍 ....................................................................... - 28 - 1.3.1 嵌入式系统与最小系统介绍.................................................................. - 28 - 1.3.2 Mini2410-IV 核心板原理说明 ............................................................... - 29 - 第二章 嵌入式操作系统 Linux 概述......................................................................... - 41 - 2. 1 Linux 的诞生与发展........................................................................................ - 41 - 2. 1. 1 Linux 的诞生与版本历史...................................................................... - 41 - 2. 1. 2 Linux 在嵌入式领域的延伸 .................................................................. - 48 - 2. 2 Linux 内核结构 .............................................................................................. - 52 - 2. 2. 1 Linux 内核概述................................................................................... - 53 - 2. 2. 2 存储与进程管理 .................................................................................. - 60 - 2. 2. 3 内核源代码目录结构 ........................................................................... - 61 - 2. 3 Linux 存储管理 .............................................................................................. - 64 - 2. 3. 1 进程虚存空间的管理 ........................................................................... - 64 - 2. 3. 2 虚存空间的映射和虚存区域的建立........................................................ - 65 - 2. 3. 3 Linux 的分页式存储管理...................................................................... - 66 - 2. 3. 4 物理内存空间的管理 ........................................................................... - 66 - 2. 3. 5 内存的分配与释放............................................................................... - 68 - 2. 4 Linux 进程管理 .............................................................................................. - 71 - 2. 4. 1 Linux 进程管理介绍 ............................................................................ - 71 - 2. 4. 2 进程及作业......................................................................................... - 72 - 2. 4. 3 启动进程............................................................................................ - 72 - 2. 4. 4 进程管理............................................................................................ - 75 - 2. 5 Linux 内核启动和初始化进程........................................................................... - 78 - 2. 5. 1 引导程序 Bootloader........................................................................... - 78 - 2. 5. 2 Kernel 引导入口 ................................................................................. - 78 - 2. 5. 3 核心数据结构初始化---内核引导第一部分 ............................................ - 78 - - 3 - 2. 5. 4 外设初始化---内核引导第二部分 ......................................................... - 80 - 2. 5. 5 init 进程和 inittab 引导脚本 ............................................................. - 81 - 2. 5. 6 rc 启动脚本........................................................................................ - 81 - 2. 5. 7 getty 和 login.................................................................................... - 82 - 2. 5. 8 bash................................................................................................... - 83 - 第三章 嵌入式 Linux 开发基础 ................................................................................ - 84 - 3. 1 搭建嵌入式 Linux 开发环境 ............................................................................. - 84 - 3. 1. 1 常用的 Linux 发行版 ........................................................................... - 84 - 3. 1. 2 Ubuntu 的安装与运行........................................................................... - 89 - 3. 1. 3 嵌入式环境的配置与源码的安装........................................................... - 94 - 3. 2 Linux 准备知识 .............................................................................................- 102 - 3. 2. 1 常用 Linux 命令与使用 .......................................................................- 102 - 3. 2. 2 Linux 下的编辑器 vi ..........................................................................- 107 - 3. 2. 3 Linux 下的 Shell ...............................................................................- 113 - 3. 2. 4 Linux 下的编译器 GCC.........................................................................- 117 - 3. 2. 5 认识 Makefile....................................................................................- 126 - 3. 3 嵌入式 Linux 开发 .........................................................................................- 134 - 3. 3. 1 嵌入式 Linux 开发模型 .......................................................................- 134 - 3. 3. 2 Bootloader、内核、文件系统 .............................................................- 137 - 3. 3. 3 Linux 驱动开发..................................................................................- 143 - 第四章 嵌入式 Linux 系统开发 .............................................................................. - 149 - 4. 1 交叉编译工具................................................................................................- 149 - 4. 1. 1 宿主机与交叉编译..............................................................................- 150 - 4. 1. 2 制作 ARM 交叉编译器 ..........................................................................- 155 - 4. 2 BootLoader ...................................................................................................- 156 - 4. 2. 1 常用 Bootloader 介绍.........................................................................- 158 - 4. 2. 2 vivi 详解 ..........................................................................................- 161 - 4. 2. 3 vivi 命令操作....................................................................................- 161 - 4. 3 Linux 内核移植 .............................................................................................- 164 - 4. 3. 1 内核移植基础 ....................................................................................- 164 - 4. 3. 2 内核配置与裁剪 .................................................................................- 165 - 4. 3. 3 Kconfig 与 Makefile ..........................................................................- 172 - 4. 4 文件系统 ......................................................................................................- 174 - 4. 4. 1 Linux 的文件系统...............................................................................- 174 - 4. 4. 2 嵌入式 Linux 文件系统内容.................................................................- 179 - 4. 5 Linux 内核调试 .............................................................................................- 185 - 4. 5. 1 Printk、GDB、KDB..............................................................................- 186 - 4. 5. 2 NFS 服务............................................................................................- 201 - - 4 - 4. 6 Linux 映像固化与运行....................................................................................- 205 - 4. 6. 1 Linux 基本映像的固化........................................................................- 205 - 4. 6. 2 根文件系统的更新..............................................................................- 212 - 4. 6. 3 Linux 映像的运行...............................................................................- 217 - 第五章 嵌入式 Linux 设备驱动开发....................................................................... - 224 - 5. 1 设备驱动基础................................................................................................- 224 - 5. 1. 1 用户态与内核态 .................................................................................- 225 - 5. 1. 2 Linux 驱动程序结构 ...........................................................................- 225 - 5. 1. 3 设备文件与设备文件系统....................................................................- 226 - 5. 1. 4 Linux 模块 ........................................................................................- 235 - 5. 1. 5 file_operations 结构........................................................................- 238 - 5. 1. 6 inode 结构 ........................................................................................- 242 - 5. 1. 7 file 结构 ..........................................................................................- 251 - 5. 2 字符设备驱动................................................................................................- 254 - 5. 2. 1 scull 的设计和内存使用.....................................................................- 255 - 5. 2. 2 字符设备注册 ....................................................................................- 258 - 5. 2. 3 open 和 release .................................................................................- 260 - 5. 2. 4 读写操作...........................................................................................- 262 - 5. 2. 5 ioctl 接口 ........................................................................................- 269 - 5. 2. 6 Hello World 模块...............................................................................- 278 - 第六章 基于 Mini2410 的 Linux 内核开发 ............................................................. - 280 - 6. 1 vivi 的编译与运行实验..................................................................................- 280 - 6. 1. 1 实验目的...........................................................................................- 280 - 6. 1. 2 实验设备...........................................................................................- 280 - 6. 1. 3 实验内容...........................................................................................- 280 - 6. 1. 4 实验原理...........................................................................................- 281 - 6. 1. 5 实验步骤...........................................................................................- 281 - 6. 2 内核编译与运行实验......................................................................................- 290 - 6. 2. 1 实验目的...........................................................................................- 290 - 6. 2. 2 实验设备...........................................................................................- 290 - 6. 2. 3 实验内容...........................................................................................- 290 - 6. 2. 4 实验原理...........................................................................................- 290 - 6. 2. 5 实验步骤...........................................................................................- 291 - 6. 3 busybox 编译实验 ..........................................................................................- 294 - 6. 3. 1 实验目的...........................................................................................- 294 - 6. 3. 2 实验设备...........................................................................................- 294 - 6. 3. 3 实验内容...........................................................................................- 294 - 6. 3. 4 实验原理...........................................................................................- 295 - - 5 - 6. 3. 5 实验步骤...........................................................................................- 295 - 6. 4 ramdisk 根文件系统的制作.............................................................................- 299 - 6. 4. 1 实验目的...........................................................................................- 299 - 6. 4. 2 实验设备...........................................................................................- 299 - 6. 4. 3 实验内容...........................................................................................- 299 - 6. 4. 4 实验原理...........................................................................................- 299 - 6. 4. 5 实验步骤...........................................................................................- 300 - 6. 5 NFS 文件系统实验 ..........................................................................................- 306 - 6. 5. 1 实验目的...........................................................................................- 306 - 6. 5. 2 实验设备...........................................................................................- 306 - 6. 5. 3 实验内容...........................................................................................- 307 - 6. 5. 4 实验原理...........................................................................................- 307 - 6. 5. 5 实验步骤...........................................................................................- 307 - 第七章 基于 Mini2410 的应用程序开发 ................................................................. - 321 - 7. 1 HelloWorld 运行实验 .....................................................................................- 321 - 7. 1. 1 实验目的...........................................................................................- 321 - 7. 1. 2 实验设备...........................................................................................- 321 - 7. 1. 3 实验内容...........................................................................................- 321 - 7. 1. 4 实验步骤...........................................................................................- 321 - 7. 1. 5 参考程序...........................................................................................- 323 - 7. 1. 6 练习题 ..............................................................................................- 324 - 7. 2 文件操作实验................................................................................................- 324 - 7. 2. 1 实验目的...........................................................................................- 324 - 7. 2. 2 实验设备...........................................................................................- 324 - 7. 2. 3 实验内容...........................................................................................- 325 - 7. 2. 4 实验原理...........................................................................................- 325 - 7. 2. 5 实验步骤...........................................................................................- 327 - 7. 2. 6 参考程序...........................................................................................- 328 - 7. 2. 7 练习题 ..............................................................................................- 329 - 7. 3 进程控制实验................................................................................................- 329 - 7. 3. 1 实验目的...........................................................................................- 329 - 7. 3. 2 实验设备...........................................................................................- 329 - 7. 3. 3 实验内容...........................................................................................- 329 - 7. 3. 4 实验原理...........................................................................................- 329 - 7. 3. 5 实验步骤...........................................................................................- 340 - 7. 3. 6 参考程序...........................................................................................- 341 - 7. 3. 7 练习题 ..............................................................................................- 342 - 7. 4 线程控制实验................................................................................................- 342 - 7. 4. 1 实验目的...........................................................................................- 342 - - 6 - 7. 4. 2 实验设备...........................................................................................- 343 - 7. 4. 3 实验内容...........................................................................................- 343 - 7. 4. 4 实验原理...........................................................................................- 343 - 7. 4. 5 实验步骤...........................................................................................- 347 - 7. 4. 6 参考程序...........................................................................................- 349 - 7. 4. 7 练习题 ..............................................................................................- 350 - 7. 5 计时器实验 ...................................................................................................- 351 - 7. 5. 1 实验目的...........................................................................................- 351 - 7. 5. 2 实验设备...........................................................................................- 351 - 7. 5. 3 实验内容...........................................................................................- 351 - 7. 5. 4 实验原理...........................................................................................- 351 - 7. 5. 5 实验步骤...........................................................................................- 353 - 7. 5. 6 参考程序...........................................................................................- 355 - 7. 5. 7 练习题 ..............................................................................................- 356 - 7. 6 Tcp 实验 .......................................................................................................- 356 - 7. 6. 1 实验目的...........................................................................................- 356 - 7. 6. 2 实验设备...........................................................................................- 356 - 7. 6. 3 实验内容...........................................................................................- 356 - 7. 6. 4 实验原理...........................................................................................- 357 - 7. 6. 5 实验步骤...........................................................................................- 364 - 7. 6. 6 参考程序...........................................................................................- 366 - 7. 6. 7 练习题 ..............................................................................................- 370 - 7. 7 Webserver 实验 .............................................................................................- 371 - 7. 7. 1 实验目的...........................................................................................- 371 - 7. 7. 2 实验设备...........................................................................................- 371 - 7. 7. 3 实验内容...........................................................................................- 371 - 7. 7. 4 实验原理...........................................................................................- 371 - 7. 7. 5 实验步骤...........................................................................................- 371 - 7. 7. 6 参考程序...........................................................................................- 372 - 7. 7. 7 练习题 ..............................................................................................- 376 - 7. 8 Udp 实验 .......................................................................................................- 376 - 7. 8. 1 实验目的...........................................................................................- 376 - 7. 8. 2 实验设备...........................................................................................- 376 - 7. 8. 3 实验内容...........................................................................................- 376 - 7. 8. 4 实验原理...........................................................................................- 376 - 7. 8. 5 实验步骤...........................................................................................- 378 - 7. 8. 6 参考程序...........................................................................................- 379 - 7. 8. 7 练习题 ..............................................................................................- 382 - 第八章 基于 Mini2410 的基础驱动开发 ................................................................. - 384 - - 7 - 8. 1 LED 控制实验.................................................................................................- 384 - 8. 1. 1 实验目的...........................................................................................- 384 - 8. 1. 2 实验设备...........................................................................................- 384 - 8. 1. 3 实验内容...........................................................................................- 384 - 8. 1. 4 实验原理...........................................................................................- 385 - 8. 1. 5 实验步骤...........................................................................................- 387 - 8. 1. 6 参考程序...........................................................................................- 391 - 8. 1. 7 练习题 ..............................................................................................- 395 - 8. 2 中断控制实验................................................................................................- 396 - 8. 2. 1 实验目的...........................................................................................- 396 - 8. 2. 2 实验设备...........................................................................................- 396 - 8. 2. 3 实验内容...........................................................................................- 396 - 8. 2. 4 实验原理...........................................................................................- 396 - 8. 2. 5 驱动分析...........................................................................................- 400 - 8. 2. 6 实验步骤...........................................................................................- 405 - 8. 2. 7 参考程序...........................................................................................- 407 - 8. 2. 8 练习题 ..............................................................................................- 412 - 8. 3 ADC 操作实验.................................................................................................- 412 - 8. 3. 1 实验目的...........................................................................................- 412 - 8. 3. 2 实验设备...........................................................................................- 412 - 8. 3. 3 实验内容...........................................................................................- 413 - 8. 3. 4 实验原理...........................................................................................- 413 - 8. 3. 5 实验步骤...........................................................................................- 416 - 8. 3. 6 参考程序...........................................................................................- 418 - 8. 3. 7 练习题 ..............................................................................................- 419 - 8. 4 RTC 实时时钟实验 ..........................................................................................- 419 - 8. 4. 1 实验目的...........................................................................................- 419 - 8. 4. 2 实验设备...........................................................................................- 419 - 8. 4. 3 实验内容...........................................................................................- 419 - 8. 4. 4 实验原理...........................................................................................- 420 - 8. 4. 5 实验步骤...........................................................................................- 422 - 8. 4. 6 参考程序...........................................................................................- 423 - 8. 4. 7 练习题 ..............................................................................................- 427 - 8. 5 串口通信实验................................................................................................- 427 - 8. 5. 1 实验目的...........................................................................................- 427 - 8. 5. 2 实验设备...........................................................................................- 427 - 8. 5. 3 实验内容...........................................................................................- 427 - 8. 5. 4 实验原理...........................................................................................- 428 - 8. 5. 5 实验步骤...........................................................................................- 430 - - 8 - 8. 5. 6 参考程序...........................................................................................- 432 - 8. 5. 7 练习题 ..............................................................................................- 435 - 8. 6 eeprom 读写实验............................................................................................- 435 - 8. 6. 1 实验目的...........................................................................................- 435 - 8. 6. 2 实验设备...........................................................................................- 435 - 8. 6. 3 实验内容...........................................................................................- 435 - 8. 6. 4 实验原理...........................................................................................- 435 - 8. 6. 5 实验步骤...........................................................................................- 447 - 8. 6. 6 参考程序...........................................................................................- 449 - 8. 6. 7 练习题 ..............................................................................................- 450 - 第九章 基于 Mini2410 的高级驱动开发 ................................................................. - 451 - 9. 1 LCD 显示驱动开发实验....................................................................................- 451 - 9. 1. 1 实验目的...........................................................................................- 451 - 9. 1. 2 实验设备...........................................................................................- 451 - 9. 1. 3 实验内容...........................................................................................- 451 - 9. 1. 4 实验原理...........................................................................................- 452 - 9. 1. 5 实验步骤...........................................................................................- 459 - 9. 1. 6 练习题 ..............................................................................................- 460 - 9. 2 TSP 触摸屏实验 .............................................................................................- 460 - 9. 2. 1 实验目的...........................................................................................- 460 - 9. 2. 2 实验设备...........................................................................................- 460 - 9. 2. 3 实验内容...........................................................................................- 461 - 9. 2. 4 实验原理...........................................................................................- 461 - 9. 2. 5 实验步骤...........................................................................................- 476 - 9. 2. 6 参考程序...........................................................................................- 477 - 9. 2. 7 练习题 ..............................................................................................- 479 - 9. 3 SD 卡驱动实验 ...............................................................................................- 479 - 9. 3. 1 实验目的...........................................................................................- 479 - 9. 3. 2 实验设备...........................................................................................- 479 - 9. 3. 3 实验内容...........................................................................................- 480 - 9. 3. 4 实验原理...........................................................................................- 480 - 9. 3. 5 实验步骤...........................................................................................- 483 - 9. 3. 6 参考程序...........................................................................................- 484 - 9. 3. 7 练习题 ..............................................................................................- 487 - 9. 4 IIS 音频驱动实验 ..........................................................................................- 487 - 9. 4. 1 实验目的...........................................................................................- 487 - 9. 4. 2 实验设备...........................................................................................- 487 - 9. 4. 3 实验内容...........................................................................................- 487 - 9. 4. 4 实验原理...........................................................................................- 487 - - 9 - 9. 4. 5 实验步骤...........................................................................................- 491 - 9. 4. 6 练习题 ..............................................................................................- 492 - 9. 5 USB 通信实验.................................................................................................- 492 - 9. 5. 1 实验目的...........................................................................................- 492 - 9. 5. 2 实验设备...........................................................................................- 492 - 9. 5. 3 实验内容...........................................................................................- 492 - 9. 5. 4 实验原理...........................................................................................- 492 - 9. 5. 5 实验步骤...........................................................................................- 497 - 9. 5. 6 练习题 ..............................................................................................- 499 - 9. 6 CAN 总线通信实验 ..........................................................................................- 499 - 9. 6. 1 实验目的...........................................................................................- 499 - 9. 6. 2 实验设备...........................................................................................- 499 - 9. 6. 3 实验内容...........................................................................................- 499 - 9. 6. 4 实验原理...........................................................................................- 499 - 9. 6. 5 实验步骤...........................................................................................- 505 - 9. 6. 6 参考程序...........................................................................................- 507 - 9. 6. 7 练习题 ..............................................................................................- 518 - 第十章 基于 Mini2410 的模块驱动开发 ................................................................. - 519 - 10. 1 DAC 操作实验 ...............................................................................................- 519 - 10. 1. 1 实验目的 .........................................................................................- 519 - 10. 1. 2 实验设备 .........................................................................................- 519 - 10. 1. 3 实验内容 .........................................................................................- 519 - 10. 1. 4 实验原理 .........................................................................................- 520 - 10. 1. 5 实验步骤 .........................................................................................- 524 - 10. 1. 6 参考程序 .........................................................................................- 525 - 10. 1. 7 练习题.............................................................................................- 533 - 10. 2 直流电机控制实验........................................................................................- 533 - 10. 2. 1 实验目的 .........................................................................................- 533 - 10. 2. 2 实验设备 .........................................................................................- 533 - 10. 2. 3 实验内容 .........................................................................................- 534 - 10. 2. 4 实验原理 .........................................................................................- 534 - 10. 2. 5 实验步骤 .........................................................................................- 539 - 10. 2. 6 参考程序 .........................................................................................- 541 - 10. 2. 7 练习题.............................................................................................- 548 - 10. 3 步进电机操作实验........................................................................................- 548 - 10. 3. 1 实验目的 .........................................................................................- 548 - 10. 3. 2 实验设备 .........................................................................................- 548 - 10. 3. 3 实验内容 .........................................................................................- 548 - 10. 3. 4 实验原理 .........................................................................................- 549 - - 10 - 10. 3. 5 实验步骤 .........................................................................................- 553 - 10. 3. 6 参考程序 .........................................................................................- 555 - 10. 3. 7 练习题.............................................................................................- 562 - 10. 4 数码管显示实验 .........................................................................................- 562 - 10. 4. 1 实验目的 .........................................................................................- 562 - 10. 4. 2 实验设备 .........................................................................................- 562 - 10. 4. 3 实验内容 .........................................................................................- 562 - 10. 4. 4 实验原理 .........................................................................................- 563 - 10. 4. 5 实验步骤 .........................................................................................- 569 - 10. 4. 6 参考程序 .........................................................................................- 570 - 10. 4. 7 练习题.............................................................................................- 579 - 10. 5 键盘扫描操作实验........................................................................................- 579 - 10. 5. 1 实验目的 .........................................................................................- 579 - 10. 5. 2 实验设备 .........................................................................................- 579 - 10. 5. 3 实验内容 .........................................................................................- 579 - 10. 5. 4 实验原理 .........................................................................................- 580 - 10. 5. 5 实验步骤 .........................................................................................- 582 - 10. 5. 6 参考例程 .........................................................................................- 583 - 10. 5. 7 练习题.............................................................................................- 592 - 10. 6 摄像头驱动实验...........................................................................................- 592 - 10. 6. 1 实验目的 .........................................................................................- 592 - 10. 6. 2 实验设备 .........................................................................................- 592 - 10. 6. 3 实验内容 .........................................................................................- 593 - 10. 6. 4 实验原理 .........................................................................................- 593 - 10. 6. 5 实验步骤 .........................................................................................- 593 - 10. 6. 6 参考程序 .........................................................................................- 595 - 10. 6. 7 练习题.............................................................................................- 606 - 10. 7 蓝牙通信实验 ..............................................................................................- 606 - 10. 7. 1 实验目的 .........................................................................................- 606 - 10. 7. 2 实验设备 .........................................................................................- 606 - 10. 7. 3 实验内容 .........................................................................................- 607 - 10. 7. 4 实验原理 .........................................................................................- 607 - 10. 7. 5 实验步骤 .........................................................................................- 608 - 10. 7. 6 练习题.............................................................................................- 615 - 10. 8 GPS 通信实验 ...............................................................................................- 615 - 10. 8. 1 实验目的 .........................................................................................- 615 - 10. 8. 2 实验设备 .........................................................................................- 615 - 10. 8. 3 实验内容 .........................................................................................- 616 - 10. 8. 4 实验原理 .........................................................................................- 616 - - 11 - 10. 8. 5 实验步骤 .........................................................................................- 620 - 10. 8. 6 参考程序 .........................................................................................- 622 - 10. 8. 7 练习题.............................................................................................- 630 - 10. 9 GPRS 通信实验 .............................................................................................- 630 - 10. 9. 1 实验目的 .........................................................................................- 630 - 10. 9. 2 实验设备 .........................................................................................- 630 - 10. 9. 3 实验内容 .........................................................................................- 630 - 10. 9. 4 实验原理 .........................................................................................- 630 - 10. 9. 5 实验步骤 .........................................................................................- 632 - 10. 9. 6 参考程序 .........................................................................................- 634 - 10. 9. 7 练习题.............................................................................................- 648 - 第十一章 嵌入式 Linux 高级应用的开发 ............................................................... - 649 - 11. 1 网络服务器 Boa 实验 ....................................................................................- 649 - 11. 1. 1 实验目的 .........................................................................................- 649 - 11. 1. 2 实验设备 .........................................................................................- 649 - 11. 1. 3 实验内容 .........................................................................................- 649 - 11. 1. 4 实验原理 .........................................................................................- 649 - 11. 1. 5 实验步骤 .........................................................................................- 652 - 11. 1. 6 练习题.............................................................................................- 654 - 11. 2 Madplay 的移植............................................................................................- 654 - 11. 2. 1 实验目的 .........................................................................................- 654 - 11. 2. 2 实验设备 .........................................................................................- 654 - 11. 2. 3 实验内容 .........................................................................................- 655 - 11. 2. 4 实验步骤 .........................................................................................- 655 - 11. 2. 5 练习题.............................................................................................- 657 - 第十二章 嵌入式 GUI 设计实验 .............................................................................. - 658 - 12. 1 嵌入式 GUI 简介...........................................................................................- 658 - 12. 1. 1 QT/Embedded ....................................................................................- 658 - 12. 1. 2 MiniGUI...........................................................................................- 659 - 12. 1. 3 Microwindows、TinyX .......................................................................- 662 - 12. 2 QT/Embedded 开发 ........................................................................................- 665 - 12. 2. 1 实验目的 .........................................................................................- 665 - 12. 2. 2 实验设备 .........................................................................................- 665 - 12. 2. 3 实验内容 .........................................................................................- 665 - 12. 2. 4 实验原理 .........................................................................................- 665 - 12. 3 在主机上编译 Qtopia....................................................................................- 675 - 12. 3. 1 实验目的 .........................................................................................- 675 - 12. 3. 2 实验设备 .........................................................................................- 675 - - 12 - 12. 3. 3 实验内容 .........................................................................................- 676 - 12. 3. 4 实验原理 .........................................................................................- 676 - 12. 3. 5 实验步骤 .........................................................................................- 676 - 12. 3. 6 练习题.............................................................................................- 680 - 12. 4 移植 Qtopia 到 Mini2410 平台 .......................................................................- 681 - 12. 4. 1 实验目的 .........................................................................................- 681 - 12. 4. 2 实验设备 .........................................................................................- 681 - 12. 4. 3 实验内容 .........................................................................................- 681 - 12. 4. 4 实验原理 .........................................................................................- 681 - 12. 4. 5 实验步骤 .........................................................................................- 681 - 12. 4. 6 练习题.............................................................................................- 695 - 12. 5 QtDesigner 开发 QT 下的应用程序 .................................................................- 695 - 12. 5. 1 实验目的 .........................................................................................- 695 - 12. 5. 2 实验设备 .........................................................................................- 696 - 12. 5. 3 实验内容 .........................................................................................- 696 - 12. 5. 4 实验原理 .........................................................................................- 696 - 12. 5. 5 实验步骤 .........................................................................................- 699 - 12. 5. 6 练习题.............................................................................................- 711 - 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 1 - 上册:基础篇 本教材分为基础篇和实践篇上下两册,上册主要是介绍 linux 的基本知识、linux 环境的构建, 基础内核的开发等内容,下册主要是介绍基于 EduKit-IV 平台的 2410 Linux2.6 的驱动应用实验内容。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 1 - 第一章 EduKit-IV嵌入式教学系统平台 本章节主要介绍 EduKit-IV 嵌入式 ARM 教学实验平台的软硬件资源,讲述了本实验平台的特点, 重点结合电路原理图描述了本实验平台的硬件结构,并且根据硬件平台各个信号的接口定义,分别 描述硬件主板以及 CPU 子板的硬件原理、资源分配等。 1. 1 教学系统平台简介 Embest EduKit-IV 教学实验平台是一款功能强大的 32 位嵌入式 ARM 实验开发平台,是深圳市 英蓓特信息技术有限公司推出的更具创新意识的 EduKit 系列第四代嵌入式教学实验平台。如图 1-1-1 所示: 图 1-1-1 EduKit-IV 教学实验平台实验箱外观图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 1 - 1.1.1 基本组成 Embest EduKit-IV 教学实验系统硬件由核心板、主板、功能模块板以及相应的适配器、连接线 组成;软件资源包含了当前主流嵌入式操作系统 Linux、Windows CE 6.0 下的 BSP 包以及应用程序。 Embest EduKit-IV 支持多核多操作系统,用户可以根据学习、研究、开发的需要选择软硬件资源。 本教程主要讲述基于 SAMSUNG S3C2410 处理器的 Linux 2.6 嵌入式开发方法,要求用户配备 的软硬件资源主要包括 Embest EduKit-IV 主板、Mini2410-IV 核心子板(基于 SAMSUNG S3C2410)、 各种功能模块板(如 GPRS 模块、蓝牙模块等)以及 Linux 2.6 下的 Bootloader、kernel、rootfs 等 实验源码包。 1.1.2 功能特点 Embest EduKit-IV 嵌入式 ARM 教学实验系统采用了模块化的设计思路,整个硬件平台的接口信 号定义完整,在设计的时候充分考虑到了 CPU 的最大性能与最全功能,包含了各种功能模块的接口, 如 GPRS、PWM 电机、蓝牙、DAC 数模转换等模块,用户可以方便地在平台上进行各种实验与实践。 此外,除了具有丰富的接口资源,本教学系统还具备了良好的扩展性,比如,EduKit-IV 预留了 32 位扩展总线接口,从而大大减轻了用户扩展新功能的负担,方便用户开发自已的目标系统。 与本教程相关的主要硬件资源如下: 表 1-1-1 EduKit-IV 教学实验平台主要硬件配置 EduKit-IV Mainboard 硬件配置 详细参数 CPU Board Interface 2 * 140 Pin (可扩展 XScale270、S3C2410、Cortex M3 ...) Extent A Interface 4 * 17 Pin (4 * (4 + 5 + 8)) Extent B Interface 4 * 20 Pin (4 * (8 + 12)) LED 4 个红色发光二极管(可操作 LED) Button 2 个中断按钮,1 个复位按钮 Beep 1 个可控蜂鸣器 Power 提供稳定的 5V、1.8V、3.3V 电压 RS232 UART 3 个 RS232 协议的串口,一个全功能串口,一个蓝牙串口,一个 ICP 串口 RS485 UART 1 个 RS485 UART ADC 4 路 ADC,1 个滑动电阻器 CAN 1 路 CAN,采用 SJA1000 控制器 Ethernet 1 个板载 DM9000 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 2 - USB Host 2 个 USB Host 接口 USB Device 1 个 USB Device 接口 Audio 音频输入输出口,采用 IIS 总线,UDA1341 芯片 Dotled 16 * 16 点阵屏 CPLD 1 个 CPLD,采用 EPM7128 芯片 TFT LCD 8 寸 TFT 真彩液晶,分辨率 640*480,16bit TSP 8 寸电阻式触摸屏 EEROM AT24C02,2K EEROM Speaker 2 个喇叭 SD Card 1 个 SD Card 接口,可支持 2G SD 卡 CF Card 1 个 CF Card 接口 Keypad Interface 1 个扫描矩阵键盘接口 Camera Interface 1 个板载摄像头接口 Ethernet Interface 1 个 CPU 板网卡扩展接口 LCD/TSP/Speaker... Interface 1 个 40Pin 接口,提供主板与液晶上盖通信,扩充了 LCD、TSP、Speaker 等 IDE Interface 1 个标准 40Pin IDE 硬盘接口 ARM JTAG Interface 1 个标准 20 Pin ARM JTAG 接口 CPLD JTAG Interface 1 个标准 10 Pin CPLD JTAG 接口 保护面板 1 个保护面板 PCMCIA Interface 1 个 20 Pin PCMCIA 接口,需要外扩电路 PCI Interface 1 个 PCI 接口,需要 CPU 核心板支持 ISA Interface 1 个 ISA 接口,需要 CPU 核心板支持 Mini2410 - IV 硬件配置 详细参数 CPU Samsung S3C2410 ARM9 处理器 主频可达 266M SDRAM 2 片 HY57V561620,64M NVRAM 8Bit、256K Nand Flash K9F1208,64M Nor Flash AM29LV160DB,2M Power 提供稳定的 5V、1.8V、3.3V 电压 Mini JTAG 8Pin Mini JTAG 口 RTC 提供实时时钟 Ethernet 100M 网卡,DM9000 CPU Board Interface 2 * 140 Pin,用于连接主板 若干模块板 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 3 - 硬件配置 详细参数 直流电机模块 DC_Motor (带红外测速功能) 步进电机模块 Step_Motor 薄膜键盘模块 5*4 扫描薄膜键盘 DAC 模块 数模转换模块 8LED&KEY 模块 8 段数码管及扩展键盘 GPRS 模块 GPRS GPS 模块 GPS 模块+延长天线 蓝牙 Bluetooth,USB 接口 摄像头 USB 接口摄像头 本教学系统软件配置如下: 表 1-1-2 EduKit-IV 教学实验平台主要软件配置 * 主要特性: - 提供 64M 大容量 K9F1208 Nand Flash 用于 Linux 映像的固化,2 片 32M HY57V561620 SDRAM。 - 提供带 USB 驱动的 vivi 映像,能够支持通过 USB 下载内核及文件系统映像。 - 内核支持 yaffs 或 ramdisk 做根文件系统,同时支持 cramfs、jffs2、FAT 文件系统格式。 - 提供隐藏 ramdisk 根文件系统,用于 yaffs 文件系统崩溃后迅速的恢复。 - 最新版本的 QTOPIA-2.2 版本 - 支持自动加载卸载 U 盘和 SD 卡,支持热插拔。 - vivi 支持 Nor Flash 和 Nand Flash 驱动,支持 Nand Flash 坏块处理,提高产品的成品率及使用寿命。 - vivi 带 USB 驱动,可通过 USB 下载内核和文件系统映像,支持 YAFFS 根文件系统直接下载。 - 可使用简易仿真器 EasyICE 通过 Embest Flash Programmer 工具来固化 vivi,也可使用专业的仿真器下载。 - 提供稳定的 linux-2.6.14 内核源码包,完全支持 SAMSUNG S3C2410 处理器。 - 提供 Ubuntu 开发环境套件,真正的 linux 环境开发平台。 - 提供 arm-linux-gcc-2.95.3、arm-linux-gcc-3.4.5 交叉编译工具。 - 支持 NFS 文件系统,更方便的调试驱动及应用。 - 支持 USB Host/USB Gadget 驱动,支持 U 盘,USB 鼠标,模拟 U 盘等。 - 提供 Nand Flash 驱动 - 支持 TFT LCD 驱动,支持 TSP 触摸屏驱动,支持 IIS 音频驱动。 - 支持双百兆网卡工作。 - 支持串口、RS485、CAN、IIC 等串行总线驱动。 - 提供 GPRS、GPS 应用源码。 - 提供 Madplayer 播放器,支持 MP3 格式音频播放。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 4 - - 提供 BOA 软件包,支持网络服务器。 - 提供蓝牙协议包,支持蓝牙。 - 提供摄像头驱动及应用源码,采集摄像头数据并实时显示。 - 提供 USB 无线 wifi 驱动应用包,支持无线上网。 - 提供 QTOPIA 环境包,详细移植 QTOPIA 到嵌入式平台的指导。 - 多达 30 个驱动应用实验,厚达 400+页的教材文档。 - 丰富的模块应用,提供驱动源码及应用源码。 1. 2 教学系统主板硬件介绍 Embest EduKit-IV 主板遵循了模块化的设计思路,提供了丰富的接口,并且具有良好的扩展性。 在 EduKit-IV 上设计了 2 组独有专利技术规范的 CPU 接口槽,用于连接 EduKit-IV 主板与核心子板, 将核心板上的信号引至主板上。这种设计使得 EduKit-IV 主板可以支持不同的 CPU 核心子板,用户 按照主板接口 CPU 接口槽的信号定义,可以开发自已的目标核心板。主板的结构以及硬件资源将会 在下面的章节中讲述。 1.2.1 主板硬件设计原理说明 EduKit-IV 主板方框图如下: 图 1-2-1 EduKit-IV 主板系统方框图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 5 - 电源电路 EduKit-IV 主板使用 5V 直流电源输入,电源 IC 采用 AP1507,该芯片的输入电压范围为 4V-22V, 输出电压可调范围为 1.23-18V,输出电流最大为 3A,持续电流推荐在 1.5A 以下。如图 1-2-2 所示, 经过 D/C 转换器得到的 3.3V 直流电压供主板 I/O 端口使用,而 1.8V 电压采用电源芯片 MIC5207BM5 来取得。主板上的 1.8 V 电压主要用在 BUFFER 处,可用电阻来选择 BUFFER 的电压为 1.8V 或者 3.3V。 图 1-2-2 EduKit-IV 电源电路 复位电路 EduKit-IV 的复位电路位于主板,复位电路使用两级反相器产生复位时序。电路如图 1-2-3 所示, 通过按键 K1 产生低电平信号 RST,RST 通过两级反相器产生复位信号 RESETIN。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 6 - 图 1-2-3 EduKit-IV 复位电路 JTAG 接口电路 JTAG 调试用到了 TCK、TMS、TDI、TDO 和 NTRST 这几个脚。其中 NTRST 是用来对 TAP controller 进行复位的,NRESET 是对 CPU 的复位输出信号。 图 1-2-4 EduKit-IV JTAG 接口电路 UART 串口 EduKit-IV 设计了 4 个串行接口,包括一个 9 线串口、一个 5 线串口和 2 个标准串口。在 EduKit-IV 主板,如果位于 Area11 区域的跳线组 COM_JUMP 全部闭合,主板 9 线串口和 5 线串口有效,如果 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 7 - 跳线组 COM_JUMP 断开,9 线串口和 5 线串口可供扩展接口使用。EduKit-IV 主板串口电路如图 1-2-5 所示。 图 1-2-5 EduKit-IV UART 串口电路(9 针与 5 针串口) 485 接口 EduKit-IV 设计了 1 个 485 接口,电路如图 1-2-6 所示,当位于 Area10 区的 RS485_JP 闭合时 有效。 图 1-2-6 EduKit-IV 485 接口电路 IIC 接口 EduKit-IV 设计了 IIC 总线接口,在 EduKit-IV 主板 IIC 总线接口用于各 IIC 设备,也通过扩展接 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 8 - 口提供给用户定制模块。EduKit-IV 主板在 IIC 总线上扩展了 2KB IIC 总线串行 EEPROM AT24C08。 EduKit-IV IIC 接口电路如图 1-2-7 所示。 图 1-2-7 EduKit-IV IIC 接口电路 CAN 总线接口 EduKit-IV 采用芯片 SJA1000 扩展 CAN 总线接口模块,电路如图 1-2-8 所示: 图 1-2-8 EduKit-IV CAN 总线接口电路 IIS 音频接口 EduKit-IV 采用 IIS 扩展了音频接口,电路如图 1-2-9 所示。IIS 是 SONY、PHILIPS 等电子巨头 共同推出的音频接口标准。EduKit-IV 将 IIS 接口与 PHILIPS 的 UDA1341TS 音频数字信号编译码器 相连接,从而进行音频输入和音频输出。UDA1341TS 可把立体声模拟信号转化为数字信号,同样也 能把数字信号转换成模拟信号,并可用 PGA(可编程增益控制),AGC(自动增益控制)对模拟信号 进行处理。UDA1341TS 的 L3 总线,用于配置该芯片的参数,包括 L3DATA、L3MODE、L3CLOCK 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 9 - 三根信号线,分别为数据线、模式线,时钟线。EduKit-IV 通过扩展 I/O 模拟 L3 接口,对 UDA1341TS 进行配置。 图 1-2-9 EduKit-IV IIS 接口电路 LCD 连接扩展接口 EduKit-IV 主板上的 LCD 接口电路如图 1-2-10 所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 10 - 图 1-2-10 EduKit-IV 主板 LCD 连接扩展接口 键盘接口 EduKit-IV 设计了 1 个支持 6×6 键盘的接口,通过扩展 I/O 接口,可以判断键盘状态。EduKit-IV 键盘接口电路如图 1-2-11 所示。 图 1-2-11 EduKit-IV 主板键盘扩展电路 AD 转换接口 EduKit-IV 设计了 1 个 ADC 接口,通过改变滑动变阻器位置,可以改变模拟输入量 AIN0,输入 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 11 - 到核心子板上(处理器)的 ADC 接口。EduKit-IV ADC 接口电路如图 1-2-12 所示。 图 1-2-12 EduKit-IV ADC 接口电路 蜂鸣器接口电路 EduKit-IV 主板上设计了 1 个蜂鸣器接口,通过扩展 I/O 接口,可以控制蜂鸣器的状态。EduKit-IV 蜂鸣器接口电路如图 1-2-13 所示。 图 1-2-13 EduKit-IV 蜂鸣器接口电路 主板网口接口电路 EduKit-IV 主板上设计了 100M 以太网接口,使用了 DM9000AE 控制芯片,接口电路如图 1-2-14。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 12 - 图 1-2-14 EduKit-IV 主板网口接口电路 LED 接口电路 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 13 - EduKit-IV 设计了 5 个 LED(D1~D5)用于指示和控制系统的状态,其中 D2 指示电源的状态, 其他 4 个的状态是用户可编程的,在 EduKit-IV 中,这 4 个 LED 的状态通过扩展 I/O 接口进行控制。 EduKit-IV LED 接口电路如图 1-2-15 所示。 图 1-2-15 EduKit-IV LED 接口电路 按键接口电路 EduKit-IV 设计了 2 个按键 KEY1、KEY2,按键可以通过 CPLD 产生中断。EduKit-IV 按键接口电 路如图 1-2-16 所示。 图 1-2-16 EduKit-IV 按键接口电路 CPLD 扩展电路 EduKit-IV 为了在设计上追求稳定,在电路上增加了 CPLD 逻辑(采用 EPM7128 芯片),通过 CPLD 扩展了 CPU 的中断及片选信号,同时增加了 CAN、IDE、CF Card 单元,如图 1-2-17 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 14 - 图 1-2-17 EduKit-IV CPLD 扩展电路 USB 接口 EduKit-IV 设计了一个 USB DEVICE 接口和两路 USB HOST 接口,电路如图 1-2-18 与 1-2-19。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 15 - 图 1-2-18 EduKit-IV USB Device 接口电路 图 1-2-19 EduKit-IV USB Host 接口电路 点阵屏接口电路 EduKit-IV 实验平台设计了一个 16*16 的点阵屏,如下图所示: 图 1-2-20 点阵屏的行扫描信号 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 16 - 图 1-2-21 点阵屏的列扫描信号 图 1-2-22 点阵屏及其接口 图 1-2-23 点阵屏的行驱动信号 SDCARD 接口电路 EduKit-IV sdcard 接口电路如图 1-2-24 所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 17 - 图 1-2-24 EduKit-IV SDCARD 接口电路 CFCARD 接口电路 EduKit-IV CFCARD 接口电路如图 1-2-25 所示。 图 1-2-25 EduKit-IV CFCARD 连接器接口电路 PCMCIA 接口电路 EduKit-IV PCMCIA 接口电路下图所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 18 - 图 1-2-26 EduKit-IV PCMCIA 接口电路 图 1-2-27 EduKit-IV PCMCIA 连接器接口电路 IDE 接口电路 EduKit-IV IDE 接口电路下图所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 19 - 图 1-2-28 EduKit-IV IDE 接口电路 ISA 接口电路 图 1-2-29 EduKit-IV ISA 接口电路 PCI 接口电路 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 20 - 图 1-2-30 EduKit-IV PCI 总线接口电路 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 21 - 1.2.2 主板硬件结构 图 1-2-31 主板结构图 1.2.3 主板硬件资源分配 PXA270 处理器共有 6 个地址空间,在 Edukit-IV 实验平台上,NCS0 接了一块 Norflash,地址 空间为 0x0~0x1FFFFF,共 2M,用来存放启动代码等数据;SDCS0 用来扩展 SDRAM,Mini270-IV 核心板上采用了 2 片 32M 存储空间的 HY57561620-H SDRAM 芯片作为内存。2 片芯片并接实现 32 位总线宽度,64M 存储空间,支持字节访问。SDRAM 的地址空间为:0xA0000000~0xA3FFFFFF。 此外,通过 NCS1、NCS2、NCS5 外接了一片 CPLD 芯片,用于扩展主板器件所用到的一些地址。 表 1-2-1 CPLD 扩展资源分配 地址范围 作用 W/R 备注 0x04800000 CAN 总线 ALE 信号 W / 0x04a00000 CAN0 读写 R/W / 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 22 - 0x05080000 输入口,键盘列数据寄存器 R 列数据,只读 0x05140000 输出口,键盘行数据寄存器 W 行数据,只写 0x06000000 中断状态寄存器 0 R ISA 中断状态 0x06200000 中断状态寄存器 1 R ISA、网卡、CAN 和键盘中断状态 0x06400000 中断控制寄存器 0 R/W ISA 中断控制 0x06600000 中断控制寄存器 1 R/W ISA、网卡、CAN 和键盘控制 0x04100000 主板网卡 DATA / / 0x04000000 主板网卡 CMD R/W / 1.2.4 扩展接口及功能模块介绍 EduKit-IV 主板提供了专门的扩展 A、B 接口插槽,并规范定义了一系列引脚供用户扩展通用型 的模块(注:专用的接口不在此列,如 CF 卡接口、PCMCIA 接口、CAMERA 接口等,主板上已经预 留专用接口插槽)。 在 EduKit-IV 主板电路图中,将扩展接口分为 A、B 区。A 区包含 6 个扩展接口,引脚分别为: A1(2*4)、A2(2*5)、A3(2*8)、A4(2*4)、A5(2*5)、A6(2*8);B 区包含 4 个扩展接口,引 脚分别为:B1(2*8)、B2(2*12)、B3(2*8)、B4(2*12)。信号定义如下图所示: 图 1-2-32 标准扩展接口原理图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 23 - A 区扩展接口(A1-A3) A 区(A1-A3)扩展接口共有 34pin,包括 COM1(对 Mini270-IV 来说是 BT 串口,已经用于 Windows CE 系统的调试串口)、USB3、IIC、SSP2、IIS(AC97)、EXIO,逻辑图如图 1-2-33 所示: 图 1-2-33 A 区(A1-A3)扩展接口逻辑图 A 区(A1-A3)的扩展接口信号定义如表 1-2-2 所示: 表 1-2-2 A 区(A1-A3)扩展接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 1 VDD33 VDD33 2 VDD33 VDD33 3 RXD1 BTRXD 4 CTS1 BTCTS 5 TXD1 BTTXD 6 RTS1 BTRTS 7 GND GND 8 GND GND 5 线串口: 连接核心子板串口 1, 此处扩展可作为 GPRS 接口用 根据跳线帽,引脚功能有变化 9 VDD50 VDD50 11 U2- UHDN1 13 U2+ UHDP1 15 GND GND USB 接口: 连接核心子板接口 USB3 同时连接板载 USB HOST 接口插座 USB_HOST; 接口扩展了 USB 蓝牙 10 VDD33 VDD33 12 SCK SCL 14 SDA SDA 16 GND GND IIC 接口: 连接核心子板唯一 IIC 接口 EduKit-IV 主板使用 IIC 扩展了 EEROM; 该接口可扩展 FM 收音 17 IO EXIO1 18 IO EXIO2 扩展 IO 接口 19 VDD33 VDD33 21 MISO2 SSP2RXD 23 MFRM2 SSP2FR 25 MOSI2 SSP2TXD 27 MCS2 SSP2CS 29 MCLK2 SSP2SCK SSP(SPI)接口: 可做为 SPI 接口使用, 连接核心子板 SSP2 接 口 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 24 - 31 GND GND 20 VDD33 VDD33 22 IISSDO IISSDO 24 IISSDI IISSDI 26 IISLRCLK IISSYNC 28 IISCLK IISBITCLK 30 CDCLK IISSYSCLK 32 GND GND IIS(AC97)接口: 可作为 AC97 接口使 用,连接核心子板 AC97 接口 根据跳线帽,引脚功能有变化 33 IO EXIO3 34 IO EXIO4 扩展 IO 接口 A 区扩展接口(A4-A6) A 区(A4-A6)扩展接口共有 34pin,包 括 ADC0/1、COM2、USB1、PWM1、RESET、SSP1、SD、 EXIO,逻辑图如图 1-2-34 所示: 图 1-2-34 A 区(A4-A6)扩展接口逻辑图 A 区(A4-A6)的扩展接口信号定义如表 1-2-3 所示: 表 1-2-3 A 区(A4-A6)扩展接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 1 VDD33 VDD33 3 RXD2 COM2RXD 5 TXD2 COM2TXD 7 GND GND 标准串口 连接核心子板接口的串口 2 2 VDD33 VDD33 4 AIN0 AIN0 6 AIN1 AIN1 8 GND GND ADC接口 连接核心子板接口的 AIN0、AIN1, 其中 AIN0 是主板 ADC 模块(使用滑动变阻器) 的输入接口 9 VDD50 VDD50 11 U1- UHDN 13 U1+ UHDP 15 GND GND USB接口 连接核心子板接口的 USB 接口 1 10 VDD33 VDD33 电源 12 PWM1 PWM1 PWM接口 连接核心子板接口的 PWM1 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 25 - 14 NRESET NRESET 复位 16 GND GND 接地 17 IO EXIO5 18 IO EXIO6 扩展IO接口 供扩展接口模块使用 19 VDD33 VDD33 21 MISO1 SSP1RXD 23 MFRM1 SSP1FR 25 MOSI1 SSP1TXD 27 MCS1 SSP1CS 29 MCLK1 SSP1CLK 31 GND GND 同步串行接口 连接核心子板接口的 SSP 接口 1 20 VDD33 VDD33 22 SDD1 MMDAT1 24 SDD0 MMDAT0 26 SDCLK MMCLK 28 SDCMD MMCCMD 30 SDD2 MMDATA2 34 SDD3 MMDATA3 32 GND GND SD卡接口 连接核心子板接口的 SD 接口,与主板 SD 接口 J16 相连 33 IO EXIO7 扩展IO接口 B 区扩展接口(B1-B2) B 区( B1-B2)扩展接口共有 40pin,包 括 PWM2/3/4、ADC2/3、CAN1/2、COM3、IRCOM、SSP1、 RESET,逻辑图如图 1-2-35 所示: 图 1-2-35 B 区(B1-B2)扩展接口逻辑图 B 区(B1-B2)的扩展接口信号定义如表 1-2-4 所示: 表 1-2-4 B 区(B1-B2)扩展接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 1 VDD33 VDD33 3 PWM3 PWM3 5 PWM4 PWM4 7 GND GND PWM接口 连接核心子板接口的 PWM3、PWM4 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 26 - 2 VDD33 VDD33 4 AIN2 AIN2 6 AIN3 AIN3 8 GND GND ADC接口 连接核心子板接口的 AIN2、AIN3 9 VDD50 VDD50 11 CAN1RX CAN1RX 13 CAN1TX CAN1TX 15 GND GND CAN接口1 连接核心子板接口的 CAN 接口 1 10 VDD50 VDD50 12 CAN2RX CAN2RX 14 CAN2TX CAN2TX 16 GND GND CAN接口2 连接核心子板接口的 CAN 接口 2 17 VDD33 VDD33 19 DTR3 COM1DTR 21 TXD3 COM1TXD 23 RTS3 COM1RTS 25 RI3 COM1RI 27 CTS3 COM1CTS 29 RXD3 COM1RXD 31 DSR3 COM1DSR 33 DCD3 COM1DCD 35 GND GND 全功能串口 连接核心子板接口的串口 3 18 VDD33 VDD33 20 RXD4 ICPRXD 22 TXD4 ICPTXD 24 GND GND 标准串口 连接核心子板接口的串口 2 26 MCLK1 SSP1CLK 28 MISO1 SSP1RXD 30 MFRM1 SSP1FR 32 MOSI1 SSP1TXD 34 MCS1 SSP1CS 36 GND GND 同步串行接口 连接核心子板接口的 SSP 接口 1 37 NRESET NRESET 复位 38 PWM2 PWM2 PWM接口 连接核心子板接口的 PWM1 39 VDD50 VDD50 电源 40 GND GND 接地 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 27 - B 区扩展接口(B3-B4) B 区(B3-B4)扩展接口共有 40pin,包 括 IIC、CS0/1、INT0/1、NOE/NWE、Data Bus、Address Bus、RESET,逻辑图如图 1-2-36 所示: 图 1-2-36 B 区(B1-B2)扩展接口逻辑图 B 区(B3-B4)扩展接口信号定义如表 1-2-5 所示: 表 1-2-5 B 区(B1-B2)扩展接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 1 VDD33 VDD33 电源 3 EXTCS1 CS2 扩展模块片选 5 EXTINT1 EXTINT1 扩展模块中断 7 GND GND 接地 扩展接口可直接使用 9 VDD50 VDD50 电源 11 EXTCS0 CS1 扩展模块片选 13 EXTINT0 EXTINT0 扩展模块中断 15 GND GND 接地 扩展接口可直接使用 2 VDD33 VDD33 4 SCK SCL 6 SDA SDA 8 GND GND IIC接口 连接核心子板接口的 IIC 接口 10 VDD50 VDD50 12 NOE OE 14 NWE WE 16 GND GND 控制信号 连接缓冲后的相应的控制信号 17 VDD33 VDD33 19 DB0 D0 21 DB1 D1 23 DB2 D2 25 DB3 D3 27 DB4 D4 29 DB5 D5 31 DB6 D6 数据总线 连接缓冲后的数据总线 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 28 - 33 DB7 D7 35 GND GND 18 VDD33 VDD33 20 AB0 A0 22 AB1 A1 24 AB2 A2 26 AB3 A3 28 AB4 A4 30 AB5 A5 32 AB6 A6 33 AB7 A7 35 GND GND 地址总线 连接缓冲后的地址总线 37 NRESET NRESET 复位 38 CDCLK1 39 VDD50 VDD50 电源 40 GND GND 接地 1. 3 Mini2410-IV 核心子板硬件介绍 Embest EduKit-IV 实验系统采用了主板与核心板分离的模块化设计架构。主板与核心板之间遵 循了一定的接口信号定义,按照这种定义,核心板把 CPU 的控制信号输出到主板上。在这里将讲述 Mini2410-IV 核心子板与主板的接口信号以及其相关的设计原理。Mini2410-IV 核心子板采用了功能 强大的 ARM 处理器 SAMSUNG S3C2410。 1.3.1 嵌入式系统与最小系统介绍 嵌入式系统诞生于微型机时代,经历了漫长的独立发展的单片机道路,而随着电子技术、计算 机技术、网络技术的不断发展,嵌入式技术走向了一个更加专业化的发展方向。由于嵌入式系统具 有体积小、性能强、功耗低、可靠性高以及面向行业具体应用等突出特征,嵌入系统广泛地应用于 军事国防、消费电子、信息家电、网络通信、工业控制等各个领域,就我们周围的日常生活用品而 言,各种电子手表、电话、手机、PDA、洗衣机、电视机、电饭锅、微波炉、空调器都有嵌入式系 统的存在,如果说我们生活在一个充满嵌入式的世界,是毫不夸张的。传统的嵌入式系统是基于单 片机的,对于简单设备的控制,大多数功能都能实现,但是随着嵌入式应用规模的增大,应用深度 的不断提高,嵌入式技术不断融合新的科学技术而快速发展。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 29 - 那么到底什么是嵌入式系统呢?嵌入式系统是以应用为中心,以计算机技术为基础,软件硬件 可裁剪,适应应用系统对功能、可靠性、成本、体积、功耗严格需求的专用计算机系统。嵌入式系 统包括硬件与软件,嵌入式系统发展有过很长一段单片机的独立发展道路,大多是基于 8 位单片机, 实现最底层的嵌入式统应用,硬件资源不是特别丰富、软件的规模也比较小。而随着嵌入式技术的 深入发展、为了能够处理更复杂的事件以及管理更丰富的资源,现在广泛地使用了 32 位嵌入式处理 器,并且操作系统也被应用到了嵌入式系统中。从当前应用的角度来看,嵌入式系统一般由嵌入式 微处理器、外围硬件设备、嵌入式操作系统以及用户应用程序等部分组成,用于实现对其它设备的 控制、监视或管理功能。 在嵌入式系统的概念上又提出了嵌入式最小系统,嵌入式最小系统要保证在最精简的资源条件 下,系统能够工作。通常,一个最小系统是以处理器为控制和管理中心,具有相配接的电源电路、 时钟电路、复位信号电路、系统存储单元。而作为嵌入式应用,为方便开发与调试,最小系统还应 该包括系统总线扩展、调试电路(通常是 JTAG 电路)。 在本实验系统中,核心子板采用了处理器 SAMSUNG S3C2410,扩展了存储系统,系统总线以 及调试 JTAG 电路,在此基础,利用处理器的硬件资源,根据需求扩展外部设备。 1.3.2 Mini2410-IV 核心板原理说明 1. 核心板硬件资源 处理器 SAMSUNG S3C2410 提供了各种应用接口,如 LCD 控制接口、SD 卡控制接口、USB 主/ 从控制接口、UART 控制接口等。 Mini2410-IV 核心子板利用处理器 SAMSUNG S3C2410 提供的各 种控制接口扩展了功能模块,这些接口信号最终通过核心板输送到主板上。比如,在核心板上通过 CPU 引出 LCD 控制信号,然后再输送到主板上的 LCD 连接器上。所以,在理解电路原理图时,读者 应该结合主板,从整体上来把握。下面讲述核心板上的一些功能扩展电路。 (1)SDRAM 扩展电路 SAMSUNG S3C2410 内部集成 SDRAM 控制器,Mini2410-IV 采用了 2 片 32M 存储空间的 HY57561620-H SDRAM 芯片作为内存。2 片芯片并接实现 32 位总线宽度,64M 存储空间,支持字 节访问。Mini270-IV 的 SDRAM 地址空间为:0x30000000---0x33FFFFFF。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 30 - 图 1-3-1 SDRAM 扩展电路 (2)NOR Flash 扩展电路 Mini2410-IV 上设计了一片 2M 容量的 AM29LV1600B NOR Flash 芯片,NOR Flash 中用来存放 MDK 映像。 图 1-3-2 Norflash 扩展电路 (3)NAND Flash 扩展电路 Mini2410-IV 上设计了一片 64M 容量的 K9F1208 NAND Flash 芯片,NAND falsh 是块读写设备, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 31 - 在本实验平台中,用来存放 Linux 操作系统映像文件。 图 1-3-3 Nand flash 扩展电路 (4)核心板以太网接口 Mini2410-IV 核心板上提供了一路以太网接口(注:主板上也有一路独立的以太网接口),网络 模块选项用了 DM9000 网络控制芯片。 图 1-3-4 核心板以太网接口电路 2. 核心板与主板的接口电路 EduKit-IV 设计了 2 个符合 PC104 规范的接口 Main_Con_A 与 Main_Con_B,用于连接 EduKit-IV 的主板和核心子板,将核心板的控制信号引至主板。EduKit-IV 核心子板与主板的接口原理图如图 1-3-6 所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 32 - 图 1-3-6 核心子板接口原理图 核心子板接口 U7 信号定义如表 3-1-1 所示: 表 3-1-1 U7 核心子板接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 2 GND GND 4 IRQ0 IRQ0 5 IRQ1 IRQ1 中断 中断引脚 8 GPIO3 GPIO3 9 GPIO6 GPIO6 10 GPIO4 GPIO4 12 GPIO5 GPIO5 14 GND GND 121 GPIO7 GPIO7 122 GND GND 123 GPIO8 GPIO8 124 GPIO9 GPIO9 GPIO 接口 当主板 DOTLED_JP1 跳线帽全部闭合 时,GPIO3~9 用于控制主板 LED 点 阵,当主板 DOTLED_JP1 全部断开时, GPIO3~9 可用于用户定制功能模块。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 33 - 11 TDO TDO 13 TDI TDI 15 TCK TCK 17 NTRST NTRST 19 TMS TMS JTAG 接口 仿真调试接口 1 PCIREQ0 PIRQ0 3 PCIGNT0 GNT0 5 PCIGNT1 GNT1 7 VDD33 VDD33 16 PCICLK0 PCLK 18 PCIREQ1 PIRQ1 20 PCIAD0 AD0 21 PCIAD2 AD2 22 PCIAD1 AD1 23 PCIAD4 AD4 24 PCIAD3 AD3 25 PCIAD5 AD5 26 GND GND 27 PCIAD7 AD7 28 PCIAD6 AD6 29 PCIAD8 AD8 30 PCICBE0 CBE0 31 PCIINTA INTA 32 PCIAD9 AD9 33 CPIINTB INTB 34 PCIAD10 AD10 35 PCIAD11 AD11 36 PCIAD12 AD12 37 PCIAD13 AD13 38 GND GND 39 PCIAD14 AD14 40 PCIAD15 AD15 41 PCICBE1 CBE1 42 PCIPAR PFAR 43 VDD33 VDD33 44 PCIPERR PERR 45 PCISERR PSERR 46 PCISTOP PSTOP PCI 接口 扩展到主板 PC104_PCI 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 34 - 47 PCIDEVSEL PIDEVSEL 48 PCITRDY PTRDY 49 PCIIRDY PIRDY 50 GND GND 51 PCIFRAME PFRAME 52 PCICBE2 CBE2 53 PCIAD16 AD16 54 PCIAD17 AD17 55 PCIAD18 AD18 56 PCIAD19 AD19 57 PCIAD21 AD21 58 PCIAD20 AD20 59 PCIAD23 AD23 60 PCIAD22 AD22 61 PCICBE3 CBE3 62 GND GND 63 PCIAD25 AD25 64 PCIAD24 AD24 65 PCIAD27 AD27 66 PCIAD26 AD26 67 VDD33 VDD33 68 PCIAD28 AD28 69 PCIAD30 AD30 70 PCIAD29 AD29 72 PCIAD31 AD31 71 TSMX X- 73 TSMY Y- 75 TSPX X+ 77 TSPY Y+ 触摸屏接口 触摸屏接口 74 GND GND 76 SP1 CIFDD0 78 SP2 CIFDD1 79 SP10 CIFDD9 80 SP3 CIFDD2 81 SP11 CIFFV 82 SP4 CIFDD3 83 SP12 CIFLV 84 SP5 CIFDD4 CIF 接口 CIF 接口 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 35 - 85 SP13 CIFMCLK 86 GND GND 87 SP14 CIFPCLK 88 SP6 CIFDD5 90 SP7 CIFDD6 92 SP8 CIFDD7 94 SP9 CIFDD8 89 LCDSTN0 STN0 91 LCDSTN1 STN1 93 LCDSTN2 STN2 95 LCDB1 LDD0 96 LCDSP LCDLP 97 LCDB2 LDD1 98 GND GND 99 LCDB4 LDD3 100 LCDB3 LDD2 101 LCDG0 LDD5 102 LCDB5 LDD4 103 LCDG3 LDD8 104 LCDG1 LDD6 105 LCDG5 LDD10 106 LCDG2 LDD7 107 LCDR1 LDD11 108 LCDG4 LDD9 109 LCDFRM LCDFRAM 110 GND GND 111 LCDR2 LDD12 112 LCDSCK LCDSCK 113 LCDR5 LDD15 114 LCDDEM LCDDEM 116 LCDR3 LDD13 118 LCDR4 LDD14 LCD 接口 LCD 接口 115 PWM2 PWM1 117 PWM3 PWM2 119 PWM4 PWM3 120 PWM1 PWM0 PWM 接口 连接到扩展接口 126 SCK SCL 128 SDA SDA IIC 接口 连接到扩展接口;在主板连接串行 EEPROM 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 36 - 125 CAN1TX CAN1TX 127 CAN1RX CAN1RX CAN 接口 1 连接到扩展接口 129 AC97RST IISRSTCLK 130 AC97SDOUT IISSDO 131 AC97SDIN IISSDI 132 AC97BITCLK IISBITCLK 134 GND GND 136 AC97SYNC IISSYNC AC97 接口 连接到扩展接口;主板音频接口 133 USB1OVC 135 VDD33 VDD33 138 USB1P UHDP 140 USB1N UHDN USB 接口 1 连接到扩展接口;主板 USB HOST 接 口 137 USB2P UDDP 139 USB2N UDDN USB 接口 2 主板 USB DEVICE 接口 核心子板接口 U8 信号定义如表 3-1-2 所示: 表 3-1-2 U8 核心子板接口信号定义 Pin Interface Name Singal Line Name Functional Description Remarks 1 ETH1TDP TX1+ 2 ETH1RDN RX1- 3 ETH1TDN TX1- 4 EYH1RPN RX1+ 5 ETH1LINK100 LED11 6 ETH1LINK10 NVDD25 8 GND GND 10 ETH1ACK LED12 网络接口 核心子板网络接口,主板通过 EXNET 可使用核心子板网络接口 9 DACK1 DACK1 11 DREQ1 DREQ1 13 DACK0 DACK0 15 DREQ0 DREQ0 DMA 12 CAN2TX 14 GND GND 16 CAN2RX CAN 接口 2 连接到扩展接口 18 COM1CTS BTCTS 20 COM1RTS BTRTS 22 COM1RX BTRXD 24 COM1TX BTTXD 串口 1 5 线串口(蓝牙串口),连接到扩展 接口,在主板通过跳线组COM_JUMP 与主板串行接口 COM2 相连 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 37 - 26 GND GND 23 COM2RX COM2RXD 25 COM2TX COM2TXD 串口 2 标准串口,连接到扩展接口 27 IRRX ICPRXD 29 IRTX ICPTXD 串口 4 标准串口,连接到扩展接口 28 COM3RX COM1RXD 30 COM3TX COM1TXD 32 COM3DCD COM1DCD 34 COM3DTR COM1DTR 36 COM3DSR COM1DSR 38 GND GND 40 COM3CTS COM1CTS 42 COM3RTS COM1RTS 44 COM3RI COM1RI 串口 3 5 线串口(蓝牙串口),连接到扩展 接口,在主板通过跳线组COM_JUMP 与主板串行接口 COM2 相连,该接口 支持 MODEM 控制功能 31 SDWP SDWP 33 SDCD SDCD 35 SDD0 MMDAT0 37 SDD1 MMDAT1 39 SDD2 MMDAT2 41 SDD3 MMDAT3 43 SDCMD MMCCMD 45 SDCLK MMCLK SD 接口 SD 接口 46 AIN0 AIN0 48 AIN1 AIN1 50 GND GND 52 AIN2 53 AIN3 54 GND GND ADC 接口 AIN0、AIN1 连接到扩展接口 AIN2、AIN3 连接到扩展接口 47 USB3OVC 49 USB3P 51 USB3N USB 接口 3 连接到扩展接口;主板 USB HOST 接口 55 RESETOUT RESETOUT 57 RESETIN RESETIN 复位 56 SSP1CS SSP1CS 58 SSP1FRM SSP1FRM 59 SSP1DIN SSP1RXD 60 SSP1DOUT SSP1TXD 61 SSP1CLK SSP1SCK SSP 接口 1 同步串行接口,连接到扩展接口 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 38 - 62 GND GND 63 A1 A1 64 A0 A0 65 A3 A3 66 A2 A2 67 VDD33 VDD33 68 A4 A4 69 A5 A5 70 A6 A6 71 A7 A7 72 A8 A8 73 A9 A9 74 GND GND 75 A11 A11 76 A10 A10 77 A13 A13 78 A12 A12 79 A15 A15 80 A14 A14 81 A17 A17 82 A16 A16 83 A19 A19 84 A18 A18 85 A21 A21 86 GND GND 87 A23 A23 88 A20 A20 89 A25 A25 90 A22 A22 92 A24 A24 地址总线 91 CS0 CS0 93 CS1 CS1 片选信号 连接到主板 CPLD 94 D0 D0 95 D1 D1 96 D2 D2 97 D3 D3 98 GND GND 99 D5 D5 数据总线 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 39 - 100 D4 D4 101 D7 D7 102 D6 D6 103 VDD33 VDD33 104 D8 D8 105 D9 D9 106 D10 D10 107 D11 D11 108 D12 D12 109 D13 D13 110 GND GND 111 D15 D15 112 D14 D14 113 IORDY IORDY 116 RD NOE 118 WR NWE 控制信号 114 PCMIOCS16 NIOIS16 115 PCMCDB PCMCDB 117 PCMMEMRC NPOE 119 PCMIORE NPIOIR 120 PCMMEMWD NPWE 121 PCMIOWF NPIOIW 122 GND GND 123 PCMWAIT NPWAIT 124 PCMCE1 NPCE1 125 PCDRST PCMRST 126 PCMCDA PCMCDA 127 PCMINTRDYB PCMINTRDYB 128 PCMINTRDY1 PCMINTRDY1 129 PCMREG NPREG 130 PCMWE 131 PCMCE2 NPCE1 132 PCMSKTSEL PSKTSEL PCMICA 接口 PCMICA 接口 133 SSP2DOUT SSP2TXD 134 GND GND 135 SSP2CS SSP2CS 136 SSP2IN SSP2RXD 137 SSP2CLK SSP2CLK SSP 接口 2 同步串行接口,连接到扩展接口 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 40 - 138 SSP2FRM SSP2FRM 139 VDD33 VDD33 140 VCC5 VCC5 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 41 - 第二章 嵌入式操作系统 Linux 概述 嵌入式 Linux 是以 Linux 为基础的操作系统,只有对 Linux 系统有了较为熟练的使用之后,才能 在嵌入式 Linux 开发领域得心应手。本章节主要介绍 Linux 的诞生与发展、Linux 内核结构、存储管 理和进程管理,以及 Linux 内核启动和初始化进程等,通过本章节的学习,可以对 Linux 系统有很好 的掌握。 2. 1 Linux 的诞生与发展 Linux 操作系统是 UNIX 操作系统的一种克隆系统。它诞生于 1991 年的 10 月 5 日(这是第一 次正式向外公布的时间)。以后借助于 Internet 网络,并在全世界各地计算机爱好者的共同努力下, 已经成为今天世界上使用最多的一种 UNIX 类操作系统,并且使用人数还在迅猛增长。Linux 操作系 统的诞生、发展和成长过程始终依赖着以下五个重要支柱:UNIX 操作系统、MINIX 操作系统、GNU 计 划、POSIX 标准和 Internet 网络。目前,Linux 主要应用在服务器、桌面系统和嵌入式应用 3 大领 域。其中在嵌入式领域的应用最为突出,嵌入式应用对操作系统的要求主要是:功能高效,节约内 存资源,启动速度快,技术支持好。Linux 的性能特点使得它天生就是一个适合于嵌入式开发和应用 的操作系统,它能方便地应用于机顶盒、IA 设备、PDA、掌上电脑、WAP 手机、寻呼机、车载盒以 及工业控制等智能信息产品中,因此有理由相信,它能成为 Internet 时代嵌入式操作系统中的最强 音。 2. 1. 1 Linux 的诞生与版本历史 1.Linux 的诞生 Linux 操作系统的诞生、发展和成长过程始终依赖着以下五个重要支柱:UNIX 操作系统、 MINIX 操作系统、GNU 计划、POSIX 标准和 Internet 网络。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 42 - UNIX 操作系统是美国贝尔实验室的 Ken.Thompson 和 Dennis Ritchie 于 1969 年夏在 DEC PDP-7 小型计算机上开发的一个分时操作系统。当时 Ken Thompson 在一个月内开发出了 Unix 操作系统的原型,使用的是低级语言编写,后经 Dennis Ritchie 于 1972 年用移植性很强的 C 语 言进行了改写,使得 UNIX 系统在大专院校得到了推广。此后 Unix 系统走上了以美国电话电报公司 AT&T 和加州 Berkeley 分校为主的发展道路,经过几年的发展,人们迫切需要给 Unix 系统制订一个 统一的标准,最后由 Unix International 和 Open Software Foundation 两大组织定义了 Unix 系统: Unix 是能够提供一个 Unix 的标准界面,包括程序级的和用户级的,不管它内部如何实现,更不管它 运行于什么硬件平台,都是一个遵守开放系统标准的 Unix 操作系统。开始,Unix 是一个自由软件, 当 AT&T 在 20 世纪 70 年代末期认识到软件的价值,对 Unix 的使用和发布强制实施版权控制后,对 Unix 的支持和发展做出贡献的人们受到了很大的打击。 MINIX 系统是由 Andrew S. Tanenbaum(AST)1987 年开发的,主要用于学生学习操作系统原 理。到 91 年时版本是 1.5,目前主要有两个版本在使用:1.5 版和 2.0 版,当时该操作系统在大学 使用是免费的,因而在全世界的大学中刮起了学习 Minix 系统的旋风。对于 Linux 系统,他表示对其 开发者 Linus 的称赞。但他认为 Linux 的发展有很大原因是因为他没有接纳全世界许多人对 Minix 的 扩展要求,而是保持 Minix 的小型化,这样能让学生在一个学期内就能学完。因此这也激发了 Linus 编 写 Linux 系统的动力,Linus 正好抓住了这个好时机,从 1991 年开始开发 Linux 系统。作为一个操 作系统,MINIX 并不是优秀者,但它同时提供了用 C 语言和汇编语言写的系统源代码。这是第一次 使得有抱负的程序员或 hacker 能够阅读操作系统的源代码。 GNU 计划和自由软件基金会(the Free Software Foundation - FSF)是由美国麻省理工学院 (MIT)的 Richard M. Stallman 于 1984 年一手创办的。旨在开发一个类似 Unix、并且是自由软件 的完整操作系统:GNU 系统。各种使用 linux 作为核心的 GNU 操作系统正在被广泛的使用。虽然这 些系统通常被称作“Linux”,但是严格地说,它们应该被称为 GNU/Linux 系统。20 世纪 90 年代初, GNU 项目已经开发出许多高质量的免费软件,其中包括有名的 emacs 编辑系统、bash shell 程序、 gcc 系列编译程序、gdb 调试程序等。这些软件为 Linux 操作系统的开发创造了一个合适的环境,是 Linux 能够诞生的基础之一。以至于目前许多人都将 Linux 操作系统称为 GNU/Linux 操作系统。 POSIX(Portable Operating System Interface for Computing Systems)可移植操作系统接口标 准是由 IEEE 开发的,并由 ISO/IEC 标准化的一簇标准。该标准是基于现有的 UNIX 实践和经验,描 述了操作系统的调用服务接口,用于保证编制的应用程序可以在源代码一级上在多种操作系统上移 植运行。1985 年,IEEE 操作系统技术委员会标准小组委员会(TCOS-SS)开始在 ANSI 的支持下组 成 IEEE 标准委员会制定有关程序源代码可移植性操作系统服务接口正式标准。到了 1986 年 4 月, IEEE 就制定出了试用标准。第一个正式标准是在 1988 年 9 月份批准的(IEEE 1003.1-1988),也 即 以后经常提到的 POSIX.1 标准。 1989 年 POSIX 的工作被转移至 ISO/IEC 社团,并由 15 工作组继续将其制定成 ISO 标准。到 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 43 - 1990 年,POSIX.1 与已经通过的 C 语言标准联合,正式批准为 IEEE 1003.1-1990(也是 ANSI 标准) 和 ISO/IEC 9945-1:1990 标准。 在 90 年代初,POSIX 标准的制定正处在最后投票敲定的时候,那是 1991-1993 年间。此时正 是 Linux 刚刚起步的时候,这个 UNIX 标准为 Linux 提供了极为重要的信息,Linux 就以该标准为指 导进行开发,做到与绝大多数 Unix 系统兼容。POSIX 现在已经发展成为一个非常庞大的标准簇。 如果没有 Internet 网,没有遍布全世界的无数计算机黑客通过网络的无私奉献,那么 Linux 绝 对不可能发展到现在的水平。 追溯到 1990 年,也就是 Linux 诞生的阶段,当时计算机技术的两大阵营——MS-DOS 操作系统 和 Unix 操作系统,因其天价而无人能够轻易靠近。正在此时,出现了 MINIX 操作系统,并有一本 详细的书描述它的设计实现原理,这本书写的非常详细,并且叙述有条有理,几乎全世界的计算机 爱好者都在看这本书以理解操作系统的工作原理。其中也包括 Linux 系统的创始者 Linus Benedict Torvalds。当时(1991 年),Linus Benedict Torvalds 是赫尔辛基大学计算机科学系 的二年级学生,也是一个自学 hacker。这个 21 岁的芬兰年轻人喜欢鼓捣计算机,测试计算机的能 力和限制。但当时缺乏的是一个专业级的操作系统。MINIX 虽然很好,但只是一个用于教学目的简 单操作系统,而不是一个强有力的实用操作系统。 2.Linux 的版本发展历史 Linux 的开发都超越了国界经由互联网进行。通常,按照一定规律,每周发布一个 Linux 开发版 本,供全世界开发者参照。Linux 内核版本有两种:稳定版和开发版。稳定的内核具有工业级的强度, 可以广泛地应用和部署。新的稳定内核相对于较旧的只是修正一些 bug 或加入一些新的驱动程序。 而开发版内核由于要试验各种解决方案,所以变化很快。这两种版本是相互关联,相互循环的。Linux 内核的命名机制:num.num.num。其中第一个数字是主版本号,第二个数字是次版本号,第三个数 字是修订版本号,如图 2-1-1 所示。主版本号和次版本号标志着重要的功能变动;修正号表示较小 的功能变动。以 2.6.12 版本为例,2 代表主版本号,6 代表次版本号,12 代表修正号。其中次版本 号还有特定的意义:如果次版本号是偶数,那么该内核就是稳定版的;若是奇数,则是开发版的。 例 如:1.2.0 是发布版,而 1.3.0 则是开发版。头两个数字合在一齐可以描述内核系列。这两个版本是 关联的,是一前一后完成的。这两个版本不断的扩充增长,稳定代码会添加到发布版,而测试阶段 的代码则添加到开发版。当 Linus 本人确定开发版本具有足够的新功能并且性能稳定时,就称为代 码冻结(Code Freeze)。开发版和发布版一同升级为 x.y.0 和 x.y+1.0,然后继续修复错误,添加功 能。例如:1.2.0 和 1.3.0 是相同的,1.2.1 是对 1.2 版代码的第一次错误修复,而 1.3.1 是往 1.3 版 中第一次添加新功能。最后到 1.2.9 中的错误在 1.2 中得到修复,1.3.9 最终为 1.3。最后,随着新 功能的不断增加,当有足够的新功能时,代码冻结,版本一同升级为 1.4.0 和 1.5.0。然后 1.4.0 继 续修复错误,1.5.0 添加功能。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 44 - 图 2-1-1 Linux 版本号 到 1991 年,GNU 计划已经开发出了许多工具软件。最受期盼的 Gnu C 编译器已经出现,但还 没有开发出免费的 GNU 操作系统。即使是 MINIX 也开始有了版权,需要购买才能得到源代码。而 GNU 的操作系统 HURD 一直在开发之中,但并不能在几年内完成。对于 Linus 来说,已经不能等待 了。从 1991 年 4 月份起,他开始酝酿并着手编制自己的操作系统。刚开始,他的目的很简单,只 是为了学习 Intel 386 体系结构保护模式运行方式下的编程技术。但后来 Linux 的发展却完全改变了 初衷。 经过几个月的不懈努力,到了 1991 年的 10 月 5 日,Linus 在 comp.os.minix 新闻组上发布 消息,正式向外宣布 Linux 内核系统的诞生(Free minix-like kernel sources for 386-AT)。这段消息 可以称为 Linux 的诞生宣言,并且一直广为流传。因此 10 月 5 日对 Linux 社区来说是一个特殊的日 子,许多后来 Linux 的新版本发布时都选择了这个日子。Linux 操作系统刚开始时并没有被称作 Linux,Linus 给他的操作系统取名为 FREAX,其英文含义是怪诞的、怪物、异想天开等意思。在他 将新的操作系统上载到 ftp.funet.fi 服务器上时,管理员 Ari Lemke 很不喜欢这个名称。他认为既然 是 Linus 的操作系统就取其谐音 Linux 作为该操作系统的目录吧,于是 Linux 这个名称就开始流传 下来。 1994 年 3 月,Linux1.0 发布,代码量 17 万行,当时是按照完全自由免费的协议发布,随后正 式采用 GPL 协议。至此,Linux 的代码开发进入良性循环。很多系统管理员开始在自己的操作系统 环境中尝试 Linux,并将修改的代码通过网络提交给核心小组。由于拥有了丰富的操作系统平台,因 而 Linux 的代码中也充实了对不同硬件系统的支持,大大地提高了跨平台移植性。 1998 年是 Linux 迅猛发展的一年。1 月,小红帽高级研发实验室成立,同年 RedHat 5.0 获得了 InfoWorld 的操作系统奖项。4 月,Mozilla 代码发布,成为 Linux 图形界面上的王牌浏览器。RedHat 宣布商业支持计划,网络了多名优秀技术人员开始商业运作。王牌搜索引擎 Google 现身,采用的也 是 Linux 服务器。值得一提的是,Oracle 和 Informix 两家数据库厂商明确表示不支持 Linux,这个决 定给予 Mysql 数据库充分的发展机会。同年 10 月,Intel 和 Netscape 宣布小额投资 Red Hat 软件, 2.5.1 主版本号为 2 从版本号为 5(这是一个开发版内核) 修订版本号为 14 2.6.2 主版本号为 2 从版本号为 6(这是一个稳定版内核) 修订版本号为 26 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 45 - 这被业界视作 Linux 获得商业认同的信号。同月,微软在法国发布了反 Linux 公开信,这表明微软公 司开始将 Linux 视作了一个对手。12 月,IBM 发布了适用于 Linux 的文件系统 AFS3.5、Jikes Java 编辑器、Secure Mailer 及 DB2 测试版。IBM 的此番行为,可以看作是与 Linux 的第一次接触。迫于 Windows 和 Linux 的压力,Sun 逐渐开放了 Java 协议,并且在 UltraSparc 上支持 Linux 操作系统。 1998 年可以说是 Linux 与商业接触的一年。 2001 年 1 月,Linux2.4 版内核发布,它进一步提升了 SMP(Symmetric Multi Processing)系统 的扩展性,同时也集成了很多用于支持桌面系统的特性:USB、PC 卡(PCMCIA)的支持,内置的 即插即用等功能。 2003 年 12 月,Linux2.6 版内核发布,在对系统的支持上 2.6 版内核相对于 2.4 版内核有很大 的变化,这些变化包括: ¾ 更好地支持大型多处理器服务器,特别是采用 NUMA 设计的服务器; ¾ 更好地支持嵌入式设备,如手机、网络路由器以及视频录像机等; ¾ 对鼠标和键盘指令等用户行为反应更加迅速; ¾ 块设备驱动程序做了彻底更新,如与硬盘和 CD 光驱通信的软件模块。 从 Linux 诞生开始,Linux 内核就从来没有停止过升级,从 Linus 第一次发布的 0.02 版本到 1999 年具有里程碑意义的 2.2 版本,一直到我们现在看到的 2.6 版本,都凝聚了 Linux 内核开发人员大量 辛苦的劳动。目前 Linux 在各种工作平台上,包括企业服务器和个人电脑上的广泛应用,使得 Linux 成为了 Windows 的强劲对手,如图 2-2-2 所示。 图 2-2-2 Linux 的主要发行版本 Linux 快速从一个个人项目进化成为一个全球数千人参与的开发项目。对于 Linux 来说,最为 重要的决策之一是采用 GPL(GNU General Public License)。在 GPL 保护之下,Linux 内核可以防 止商业使用,并且它还从 GNU 项目(Richard Stallman 开发,其源代码要比 Linux 内核大得多) 的用户空间中受益。这允许使用一些非常有用的应用程序,例如 GCC(GNU Compiler Collection) 和各种 shell 支持。 当今 Linux 的全部开发活动分布在各个国家,在互联网上由近 100 位高手日夜研究,总协调人 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 46 - 是 Linus Torvalds 本人,带有序列号的 Linux 发布权掌握在他手中。在法律上,标称这组代码集的 Linux 注册商标的版权归 Linus 本人所有。 Linux 体系发行版是由特定序列号的 Linux(内核)及属于 GNU 体系源码开放的功能性支撑模块 和一些运行于 Linux 上的商用软件所集成。发行版整体集成版权归相应的发行商所有。Linux 发行版 的发行商(称为 Linux 发行商)一般并不拥有其发行版中各软件模块的版权,发行商关注的只是发 行版的品牌价值,以含于其中的集成版的质量和相关特色服务进行市场竞争。严格地讲,Linux 发行 商并非一定是独立于软件开发商,它本质上属于一种新兴的 IT 行业。 3.Linux 的应用领域 Linux 的应用主要有服务器、桌面系统和嵌入式应用 3 大领域。 在服务器方面,过去的几年中,尽管 Linux 在服务器端的市场远远好于桌面端,但是市场份额 从未超过 10%,应用水平大多仅限于前端或简单的应用,如网络服务器、文件和打印服务器、邮件 服务器和 DNS 服务器。但是随着 Linux 系统的进步、Linux 厂商的投入、硬件厂商和软件厂商的支 持,用户接受程度也随之提高,在政府部门和研究机构 Linux 正逐渐取代 Windows、Unix,成为这 些用户最喜欢使用的操作系统。这些机构需要处理大量的数据,却不希望花费大量的支出在 Windows 或者 Unix 这种将他们限定在单一技术中的软件上。用户喜欢开放式标准和广泛拥有的技术,不喜欢 专有的技术是推动 Linux 市场的源动力。同时,服务器厂商的持续投入是推动 Linux 市场发展的决定 力量。这让人有理由相信, Linux 产业链在向前不停运转,促使 Linux 服务器的应用走向广泛和深 入,Linux 服务器成为更多人的选择。 随着 Linux 技术的日新月异,以前 Linux 的弱项——桌面应用也逐渐表现出了它独到的优势。 ¾ Linux 系统对 windows,DOS 文件格式的完全支持、完全兼容,而反之则否。这意味着, 你以前的文件都没有浪费; ¾ Linux 系统对 windows 网络的完全支持,而且反向也能被 Windows NT 网络识别。这意味 着,如果有位带着 windows 操作系统的朋友来到你的公司(家庭),你没有必要担心他不 能通过网络访问(当然也不会是越权访问)隔层的一台 Linux 主机,如果你是老板,你当 然可以用它来代替 windows NT 作为局域网的服务器; ¾ 支持 windows 下的共享打印机打印,反之亦然; ¾ 支持运行 windows 下的程序,而反之则否。用户希望看到的 Linux 桌面系统应该有大量的 应用程序,可以使用 Linux 及其应用程序来解决工作中遇到的各种问题。值得欣喜的是, Linux 下的软件越来越丰富,几乎 Windows 下的所有程序在 Linux 下都能找到相应功能的 软件,并且这些软件全部免费,Linux 正在突破 Windows 在桌面应用领域对它形成的遏制; ¾ Linux 下有优秀的办公字处理、表格及数据线图套件,它兼容 MSoffice,而且有优秀而免费 的图像处理软件,兼容几乎所有的图形文件格式,家庭娱乐用的媒体播放机,也十分成熟, 可以说具备了一切 windows 下的通用功能; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 47 - ¾ Linux 是个安全、高效、超能的操作系统,这也与 windows 下天天担心被病毒洗盘、担心 运行越来越慢,担心新的 CPU 一上市,自己的计算机就好像一下子成为古董等等局面形成 鲜明对照。 目前,对嵌入式 Linux 系统的开发正在蓬勃兴起,并已形成了很大的市场,Linux 现在的表现越 来越引人注意。Linux 向来被认为是与 Internet 相关联的移动电话、手持设备、信息家电以及工业自 动化等的应用平台。这些智能设备可以完成 PC 桌面系统的大部分 Internet 功能,发展前景毋庸置 疑。 Linux 系统是层次结构且内核完全开放。Linux 是由很多体积小且性能高的微内核系统组成。在 内核代码完全开放的前提下,不同领域和不同层次的用户可以根据自己的应用需要方便地对内核进 行改造,低成本地设计和开发出满足自己需要的嵌入式系统。 嵌入式应用对操作系统的要求主要是:功能高效,节约内存资源,启动速度快,技术支持好。 Linux 的特点使得它天生就是一个适合于嵌入式开发和应用的操作系统,与其它嵌入式操作系统相比 (详见表 2-1-1),Linux 的特点如下。 表 2-1-1 专用嵌入式实时操作系统与嵌入式 Linux 的比较 专用嵌入式实时操作系统 嵌入式 Linux 操作系统 版权费 每生产一件产品需交纳一份版权费 免费 购买费用 数十万元(RMB) 免费 技术支持 由开发商独家提供有限的技术支持 全世界的自由软件开发者提供支持 网络特性 另加数十万元(RMB)购买 免费且性能优异 软件移植 难(因为是封闭系统) 易,代码开放(有许多应用软件支持) 产品开发周期 长,因为可参考的代码有限 短,新产品上市迅速 实时性能 好 须改进,可用 RT_Linux 等模块弥补 稳定性 较好 较好,但在高性能系统中须改进 ¾ 强大的网络支持功能。Linux 诞生于因特网时代并具有 Unix 的特性,保证了它支持所有标 准因特网协议,并且可以利用 Linux 的网络协议栈将其开发成为嵌入式的 TCP/IP 网络协议 栈。 此外,Linux 还支持 ext2、fat16、fat32、romfs 等文件系统,为开发嵌入式系统应用 打下了很好的基础; ¾ Linux 具备一整套工具链,容易自行建立嵌入式系统的开发环境和交叉运行环境,可以跨越 嵌入式系统开发中仿真工具的障碍。Linux 也符合 IEEE POSIX.1 标准,使应用程序具有较 好的可移植性; ¾ Linux 具有广泛的硬件支持特性。无论是 RISC 还是 CISC、32 位还是 64 位等各种处理器, Linux 都能运行。Linux 支持各种主流硬件设备和最新硬件技术,甚至可以在没有存储管理 单元(MMU)的处理器上运行。这意味着嵌入式 Linux 将具有更广泛的应用前景; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 48 - ¾ Linux 的内核精简而高效。与微软的 Windows 操作系统或者普通的 Unix 系统不同,根据实 际的需要,大小功能都可定制。经过对不需要的功能进行裁剪,Linux 内核完全可以小到 100KB 以下; ¾ Linux 的可裁剪性强。由于 Linux 的模块性比较强,并且 Linux 是一个代码完全公开的操作 系统,任何人都可以对其代码进行修改,开发人员可以方便地根据实际系统进行裁剪和修 改。 2. 1. 2 Linux 在嵌入式领域的延伸 由上节的知识可知,Linux 操作系统正凭借着它优良的性能发展壮大成为全球第二大操作系统。 目前,在嵌入式行业,它越来越受到各种商家的青睐。在所有的操作系统中,Linux 是一个发展最快, 应用最为广泛的操作系统,Linux 本身的种种特性也使其成为嵌入式开发的首选。下面本节将会从嵌 入式 Linux 的开发平台、开发模式、面临的挑战和发展前景这四个方面进行具体阐述。 1. Linux 嵌入式系统开发平台 1) 系统软件操作平台 Linux 作为嵌入式操作系统是完全可行的,因为 Linux 提供了完成嵌入功能的基本内核和所需要 的所有用户界面,能处理嵌入式任务和用户界面。将 Linux 看作是连续的统一体,从一个具有内存 管理、任务切换和时间服务及其它分拆的微内核到完整的服务器,支持所有的文件系统和网络服务。 Linux 作为嵌入式系统,是一个带有很多优势的新成员。它对许多 CPU 和硬件平台都是易移植、稳 定、功能强大、易于开发的。 嵌入式 Linux 系统需要下面三个基本元素:系统引导工具(用于机器加电后的系统定位引导)、 Linux 微内核(内存管理、 程序管理)、初始化进程。但如果要它成为完整的操作系统并且继续保持 小型化,还必须加上硬件驱动程序、硬件接口程序和应用程序组。 Linux 是基于 GNU 的 C 编译器,作为 GNU 工具链的一部分,与 gdb 源调试器一起工作的。它 提供了开发嵌入式 Linux 系统的所有软件工具。 2) 系统硬件平台 在选择硬件时,常由于缺乏完整或精确的信息而使硬件选择成为复杂且困难的工作。硬件开发 成本常是我们很关心的。当考虑硬件成本时,须要考虑产品的整个成本而不仅是 CPU 的成本。因为 合适的 CPU,一旦加上总线逻辑和延时电路使之与外设一起工作,硬件系统就可能变得非常昂贵。 如果要寻找嵌入式软件系统,那么应首先确定硬件平台,即确定微处理器 CPU 的型号。选定硬件平 台前,首先要确定系统的应用功能和所需要的速度,并制定好外接设备和接口标准。这样才能准确 地定位所需要的硬件方案,得到性价比最高的系统。 2. 嵌入式 Linux 系统开发模式 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 49 - 嵌入式系统通常为一个资源受限的系统。直接在嵌入式系统的硬件平台上编写软件比较困难, 有时甚至是不可能的。目前,一般采用的办法是,先在通用计算机上编写程序,然后,通过交叉编 译,生成目标平台上可运行的二进制代码格式,最后下载到目标平台上的特定位置上运行,具体步 骤如图 2-1-3 所示。 ???????? ( ????) ????????? ( ????) ????????? ????? ???? ???? Y N 图 2-1-3 嵌入式 Linux 开发模式一般流程 1)建立嵌入式 Linux 交叉开发环境。 目前,常用的交叉开发环境主要有开放和商业两种类型。开放的交叉开发环境的典型代表是 GNU 工具链。商业的交叉开发环境主要有 Metrowerks CodeWarrior、ARM Software Development Toolkit、 SDS Cross compiler、WindRiver Tornado、Microsoft Embedded Visual C++等。交叉开发环境是指 编译、链接和调试嵌入式应用软件的环境。它与运行嵌入式应用软件的环境有所不同,通常采用宿 主机/目标机模式,如图 2-2-4 所示。 图 2-2-4 宿主机/目标机调试模式 2)交叉编译和链接。 在完成嵌入式软件的编码之后,就是进行编译和链接,以生成可执行代码。由于开发过程大多 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 50 - 是在Intel 公司x86系列CPU 的通用计算机上进行的,而目标环境的处理器芯片却大多为ARM、MIPS、 PowerPC、DragonBall 等系列的微处理器,这就要求在建立好的交叉开发环境中进行交叉编译和链 接。 3)交叉调试。 ① 硬件调试。如果不采用在线仿真器,可以让 CPU 直接在其内部实现调试功能,并通过在开 发板上引出的调试端口,发送调试命令和接收调试信息,完成调试过程。目前,Motorola 公司提供 的开发板上使用的是 DBM 调试端口,而 ARM 公司提供的开发板上使用的则是 JTAG 调试端口。使 用合适的软件工具与这些调试端口进行连接,可以获得与 ICE 类似的调试效果。 ② 软件调试。在嵌入式 Linux 系统中,Linux 系统内核调试,可以先在 Linux 内核中设置一个 调试桩(debug stub),用作调试过程中和宿主机之间的通信服务器。然后,可以在宿主机中通过调 试器的串口与调试桩进行通信,并通过调试器控制目标机上 Linux 内核的运行。 ③ 嵌入式上层应用软件的调试可以使用本地调试和远程调试两种方法。如果采用的是本地调 试,首先要将所需的调试器移植到目标系统中,然后就可以直接在目标机上运行调试器来调试应用 程序了;如果采用的是远程调试,则需要移植一个调试服务器到目标系统中,并通过它与宿主机上 的调试器共同完成应用程序的调试。在嵌入式 Linux 系统的开发中,远程调试时目标机上使用的调 试服务器通常是 gdbserver,而宿主机上使用的调试器则是 gdb。两者相互配合共同完成调试过程。 4)系统测试。 整个软件系统编译过程,嵌入式系统的硬件一般采用专门的测试仪器进行测试,而软件则需要 有相关的测试技术和测试工具的支持,并要采用特定的测试策略。测试技术指的是软件测试的专门 途径,以及能够更加有效地运用这些途径的特定方法。在嵌入式软件测试中,常常要在基于目标机 的测试和基于宿主机的测试之间做出折衷。基于目标机的测试需要消耗较多的时间和经费,而基于 宿主机的测试虽然代价较小,但毕竟是在仿真环境中进行的,因此难以完全反映软件运行时的实际 情况。这两种环境下的测试可以发现不同的软件缺陷,关键是要对目标机环境和宿主机环境下的测 试内容进行合理取舍。嵌入式软件测试中经常用到的测试工具主要有:内存分析工具、性能分析工 具、覆盖分析工具、缺陷跟踪工具等,在这里不加详述。嵌入式 Linux 系统的典型构成见图 2-2-5。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 51 - 图 2-2-5 嵌入式 Linux 系统典型构成 3.嵌入式 Linux 面临的挑战 目前,对嵌入式 Linux 系统的开发正在蓬勃兴起,并已形成了很大的市场。除了一些传统的 Linux 公司,像 Red Hat、VA Linux 等,正在从事嵌入式 Linux 的研究之外,一批新公司(如 Lineo、TimeSys 等)和一些传统的大公司(如 IBM、SGI、Motorola、Intel 等)以及一些开发专用嵌入式操作系统的 公司(如 Lynx)也都在进行嵌入式 Linux 的研究和开发。但就目前的技术而言,嵌入式 Linux 的研 究成果与市场的真正需求还有一些距离,因此,嵌入式 Linux 走向成熟还需要在以下几个方面有所 发展。 ¾ Linux 的实时性扩充 实时性是嵌入式操作系统的基本要求。由于 Linux 还不是一个真正的实时操作系统,内核不支 持事件优先级和抢占实时特性,所以在开发嵌入式 Linux 的过程中,首要问题是扩展 Linux 的实时性 能。对 Linux 实时性的扩展可以从两方面进行:向外扩展和向上扩展。向外扩展即从范围上扩展, 让实时系统支持的范围更广,支持的设备更多。向上扩展是扩充 Linux 内核,从功能上扩充 Linux 的实时处理和控制系统。 ¾ 改变 Linux 内核的体系结构 Linux 的内核体系采用的是 Monolithic。在这种体系结构中,内核的所有部分都集中在一起,而 且所有的部件在一起编译连接。这样虽然能使系统的各部分直接沟通,有效地缩短任务之间的切换 时间,提高系统的响应速度和 CPU 的利用率,且实时性好;但在系统比较大时体积也比较大,与嵌 入式系统容量小、资源有限的特点不符。而另外一种内核体系结构 Microkernel,在内核中只包括了 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 52 - 一些基本的内核功能,如创建和删除任务、任务调度、内存管理和中断处理等部分,而文件系统、 网络协议栈等部分都是在用户内存空间运行。这种结构虽然执行效率不如 Monolithic 内核,但大大 减小了内核的体积,同时也方便了整个系统的升级、维护和移植,更能满足嵌入式系统的特点需要。 为此,要使嵌入式 Linux 的应用更加广泛,若将 Linux 目前的 Monolithic 内核结构中的部分结构改造 成 Microkernel 体系结构,可使得到的 Linux 既具有很好的实时性,又能满足嵌入式系统体积小的要 求。 另外,Linux 是一个需要占用存储器的操作系统。虽然这可以通过减少一些不必要的功能来弥补, 但可能会浪费很多时间,而且容易带来很大的麻烦。许多 Linux 的应用程序都要用到虚拟内存,这 在许多嵌入式系统中是没有价值的。所以,并不是一个没有磁盘的 Linux 嵌入式系统就可以运行任 何 Linux 应用程序。 ¾ 完善 Linux 的集成开发环境 提供完整的集成开发环境是每一个嵌入式系统开发人员所期待的。一个完整的嵌入式系统的集 成开发环境一般需要提供的工具是:编译/连接器、内核调试/跟踪器和集成图形界面开发平台。其 中的集成图形界面开发平台包括编辑器、调试器、软件仿真器和监视器等。在 Linux 系统中,具有 功能强大的 gcc 编译器工具链,使用了基于 GNU 的调试器 gdb 的远程调试功能,一般由一台客户机 运行调试程序调试宿主机运行的操作系统内核;在使用远程开发时还可以使用交叉平台的方式,如 在 Windows 平台下的调试跟踪器对 Linux 的宿主系统做调试。但是,Linux 在基于图形界面的特定 系统定制平台的研究上,与 Windows 操作系统相比还存在差距。因此,要使嵌入式 Linux 在嵌入式 操作系统领域中的优势更加明显,整体集成开发环境还有待提高和完善。 4.嵌入式 Linux 的发展及应用前景 综上,由于 Linux 具有对各种设备的广泛支持性,因此,能方便地应用于机顶盒、IA 设备、PDA、 掌上电脑、WAP 手机、寻呼机、车载盒以及工业控制等智能信息产品中。与 PC 相比,手持设备、 IA 设备以及信息家电的市场容量要高得多,而 Linux 嵌入式系统的强大的生命力和利用价值,使越 来越多的企业和高校表现出对它极大的研发热情。蓝点软件公司、博利思公司、共创软件联盟、中 科红旗等公司都已将嵌入式系统的开发作为自己的主要发展方向之一。 在嵌入式系统的应用中,Linux 嵌入式操作系统所具有的技术优势和独特的开发模式给业界以新 异,有理由相信,它能成为 Internet 时代嵌入式操作系统中的最强音。 2. 2 Linux 内核结构 Linux 内核是一个庞大而复杂的操作系统的核心,不过尽管庞大,但是却采用子系统和分层的概 念很好地进行了组织。Linux 是个人计算机和工作站上的 Unix 类操作系统。但是,它绝不是简化的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 53 - Unix。相反,Linux 是强有力和具有创新意义的 Unix 类操作系统。它不仅继承了 Unix 的特征,而且 在许多方面超过了 Unix。作为 Unix 类操作系统,Linux 内核具有下列基本特征: ¾ Linux 内核的组织形式为整体式结构; ¾ Linux 的进程调度方式简单而有效; ¾ Linux 支持内核线程(或称守护进程); ¾ Linux 支持多种平台的虚拟内存管理; ¾ Linux 内核另一个独具特色的部分是虚拟文件系统(VFS); ¾ Linux 的模块机制使得内核保持独立而又易于扩充; ¾ 增加系统调用以满足你特殊的需求; ¾ 网络部分面向对象的设计思想使得 Linux 内核支持多种协议、多种网卡驱动程序变得容易。 在本节中,您将探索 Linux 内核的总体结构,并学习一些主要的子系统和核心接口。Linux 有 两种不同的含义,从严格的技术角度讲,Linux 指的是开放源代码的 Unix 类操作系统的内核。然而, 大多数人用它来表示以 Linux 内核为基础的整个操作系统。从这种意义讲,Linux 指的是开放源代码 的,包含内核、系统工具、完整的开发环境和应用软件的 Unix 类操作系统。 2. 2. 1 Linux 内核概述 从 Unix 起,内核一般采用 C 语言编写,使得内核具有良好的扩展性。单一内核(Monolithichernel) 是当时操作系统的主流,操作系统中所有功能都被封装在内核中,它们与外部程序处在不同的内存 地址空间,并通过各种方式防止外部程序直接访问内核中的数据结构,程序只有通过系统调用来访 问内核。近些年来,微内核(Microkernel)技术逐渐引入,并被多数现代操作系统所采用,成为操 作系统的主要潮流。1987 年,Andrew Tanenbaum 创建了一个微内核版本的 UNIX,名为 MINIX (代表 minimal UNIX),它可以在小型的个人计算机上运行。这个开源操作系统在 20 世纪 90 年 代激发了 Linus Torvalds 开发 Linux 的灵感。不同的是,Linux 系统并没有采用微内核结构,使用 了单一内核结构,这是由于 Linux 是注重效率的操作系统。Linus Tovarlds 以代码执行效率作为操作 系统的第一要务,并没有进行系统的结构设计工作。随后,Linux 在短短的十几年中发生了日新月异 的变化。 1.Linux 内核特点 1)Linux 内核的重要特点: ¾ 可移植性(Portability),支持硬件平台广泛,在大多数体系结构上都可以运行; ¾ 可量测性(Scalability),即可以运行在超级计算机上,也可以运行在很小的设备上(4MB RAM 就能满足); ¾ 标准化和互用性(Interoperability),遵守标准化和互用性规范; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 54 - ¾ 完善的网络支持; ¾ 安全性,开放源码使缺陷暴露无疑,它的代码也接受了许多专家的审查; ¾ 稳定性(Stability)和可靠性(Reliability); ¾ 模块化(Modularity),运行时可以根据系统的需要加载程序; ¾ 编程容易,可以学习现有的代码,还可以从网络上找到很多有用的资源。 2)Linux 内核支持的处理器体系结构 Linux 内核能够支持的处理器的最小要求:32 位处理器,带或者不带 MMU。需要说明的是,不 带 MMU 的处理器过去是 uClinux 支持的。Linux2.6 内核采纳了 m68k 等不带 MMU 的部分平台,Linux 支持的绝大多数处理器还是带 MMU 的。 Linux 内核既能支持 32 位体系结构,又能支持 64 位体系结构。 每一种体系结构在内核源码树的 arch/目录下有子目录。各种体系结构的详细内容可以查看源码 Documentation//目录下的文档。 3)Linux 内核遵守的软件许可 Linux 内核全部源代码是遵守 GPL 软件许可的免费软件,这就要求在发布 Linux 软件的时候免费 开放源码。 对于 Linux 等自由软件,必须对最终用户开放源代码,但是没有义务向其他任何人开放。在商 业 Linux 公司中,通常会要求客户签署最终用户的使用许可。 私有的模块是允许使用。只要不被认定为源自 GPL 的代码,就可以按照私有许可使用。但是, 私有的驱动程序不能静态链接到内核中去,可以作为动态加载的模块使用。 4)开放源码驱动程序的优点 基于庞大的 Linux 社区和内核源码工程,有各种各样的驱动程序和应用程序可以利用,而没有 必要从头写程序。 开发者可以免费得到社区的贡献、支持、检查代码和测试。驱动程序可以免费发布给其他人, 可以静态编译进内核。 对 Linux 公司来说,用户和社区的正面形象可以使他们更容易聘请到有才能的开发者。 以源码形式发布驱动程序,可以不必为每一个内核版本和补丁版本都提供二进制的程序。另外 通过分析源代码,可以保证它没有安全隐患。 2.Linux2.6 内核新特性 Linux2.6 内核吸收了一些新技术,在性能、可测量性、支持和可用性方面不断提高。这些改进 多数是添加支持更多的体系结构、处理器、总线、接口和设备;也有一些是标准化内部接口,简化 扩展添加新设备和子系统的支持。 与 Linux2.4 版本相比,Linux2.6 版本具有许多新特性,内核也有很大修改。其中一些修改只跟 内核或者驱动开发者有关,另外一些修改则会影响到系统启动、系统管理和应用程序开发。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 55 - Linux2.6 内核重要的新特性如下,如下图 2-2-2 所示。 图 2-2-2 Linux2.6 新特性 1)新的调度器 Linux2.6 版本的 Linux 内核使用了新的调度器算法,它是由 Ingo Molnar 开发的 O(1)调度器 算法。它在高负载的情况下极其出色,并且对处理器调度有很好的扩展。 Linux2.4 版本的标准调度器中,使用时间片重算的算法。这种算法要求在所有的进程都用尽时 间片以后,重新计算下一次运行的时间片。这样每次任务调度的花销不确定,可能因为计算比较复 杂,产生较大调度延迟。特别是多处理器系统,可能由于调度的延迟,导致大部分处理器处于空闲 状态,影响系统性能。 新的调度器采用 O(1)的调度算法,通过优先级数组的数据结构来实现。优先级数组可以使每 个优先级都有相应的任务队列,还有一个优先级位图。每个优先级对应位图中一位,通过位图可快 速执行最高优先级任务。因优先级个数是固定的,所以查找的时间也固定,不受运行任务数的影响。 新的调度器为每个处理器维护 2 个优先级数组:有效数组和过期数组。有效数组内任务队列的 进程都还有可以运行的时间片;过期数组内任务队列的进程都没有时间片可以执行。当一个进程的 时间片用光时,就把它从有效数组移到过期数组,并且时间片也已经重新计算好了。当需要重新调 度这些任务的时候,只要在有效数组和过期数组之间切换就好了。这种交换是 O(1)算法的核心。 它根本不需要从头到尾重新计算所有任务的时间片,调度器的效率更高。 O(1)调度器具有以下优点: ¾ SMP 效率高。如果有工作需要完成,那么所有处理器都会工作; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 56 - ¾ 没有进程需要长时间地等待处理器;也没有进程会无端地占用大量的 CPU 时间; ¾ SMP 进程只映射到一个 CPU 而且不会在 CPU 之间跳跃; ¾ 不重要的任务可以设置低优先级,重要的任务可以设置高优先级; ¾ 负载平衡功能。调度器会降低那些超出处理器负载能力的进程的优先级; ¾ 交互性能提高。即使在高负载的情况下,也不会再发生长时间不响应鼠标点击或者键盘输 入的情况。 2)内核抢占 Linux2.6 采纳了内核抢占的补丁,大大减小了用户交互、多媒体等应用程序的调度延迟。这一 特性对实时系统和嵌入式系统来说特别有用。这项工作是由 Robert Love 完成的。 在 Linux2.4 以前的内核版本中,内核空间运行的任务(包括通过系统调用进入内核空间的用户 任务)不允许被抢占。一个内核任务可以被抢占,为的是让重要的用户应用程序可以继续运行。这 样做可以极大增强系统的用户交互性,用户将觉得鼠标点击和击键的事件得到了更快速的响应。 当然,不是所有的内核代码段都可以被抢占。可以锁定内核代码的关键部分,不允许抢占。这 样可以确保每个 CPU 的数据结构和状态始终受到保护。 3)新的线程模型 Linux2.6 内核重写了线程框架。它也是由 Ingo Molnar 完成的。它基于一个 1:1 的线程模型, 能够支持 NPTL(Native Posix Threading Library)线程库。NPTL 是一个改进的 Linux 线程库,它是 由 Molnar 和 Ulrich Drepper 合作开发的。 对于 2.4 内核的 Linux 线程库,存在一些不足。例如:总是需要一个线程管理,来负责创建和 删除子线程,负责接受和分布信号等。如果系统中使用大量的线程,这种 Linux 线程库就存在严重 的效率问题。 NPTL 线程库解决了传统的 Linux 线程库存在的问题,对系统有很大性能提升。实际上,RedHat 已经将它向后移植到了 Linux2.4 内核,从 RedHat9.0 版本就开始包含对它的支持。新的线程框架的 改进包含 Linux 线程空间中的许多新的概念,包括线程组、线程各自的本地存储区、POSIX 风格的 信号以及其他改进。 4)文件系统 相对于 Linux2.4,Linux2.6 对文件系统的支持在很多方面都有大的改进。关键的变化包括对扩 展属性(extended attributes)以及 POSIX 标准的访问控制(access controls)的支持。 EXT2/EXT3 文件系统作为多数 Linux 系统缺省安装的文件系统,是在 2.6 中改进最大的一个。 最主要的变化是对扩展属性的支持,也即给指定的文件在文件系统中嵌入一些元数据(metadata)。 新的扩展属性子系统的第一个用途就是实现 POSIX 访问控制链表。POSIX 访问控制是标准 UNIX 权 限控制的超集,支持更细粒度的访问控制。EXT3 还有其他一些细微变化。 Linux 对文件系统层还进行了大量的改进以兼容其他操作系统。Linux2.6 对 NTFS 文件系统的支 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 57 - 持也进行了重写;同时也支持 IBM 的 JFS(journaling file system)和 SGI 的 XFS。 此外,Linux 文件系统中还有很多零散的变化。 5)声音 Linux2.6 内核还添加了新的声音系统:ALSA(Advanced Linux Sound Architecture)。老的声音 系统 OSS(Open Sound System)存在一些系统结构的缺陷。新的声音体系结构支持 USB 音频和 MIDI 设备,全双工重放等。 6)总线 Linux2.6 的 IDE/ATA、SCSI 等存储总线也都被更新。最主要的是重写了 IDE 子系统,解决了许 多可扩展性问题以及其他限制。其次是可以像微软的 Windows 操作系统那样检测介质的变动,以更 好地兼容那些并不完全遵照标准规范的设备。 Linux2.6 还大大提升了对 PCI 总线的支持,增强或者扩展了 USB、蓝牙(Bluetooth)、红外(IrDA) 等外围设备总线。所有的总线设备类型(硬件、无线和存储)都集成到了 Linux 新的设备模型子系 统中。 7)电源管理 Linux2.6 支持高级电源配置管理界面(ACPI,Advanced Configuration and Power Interface), 最早 Linux2.4 中有些支持。ACPI 不同于 APM(高级电源管理),拥有这种接口的系统在改变电源状 态是需要分别通知每一个兼容的设备。新的内核系统允许子系统跟踪需要进行电源状态转换的设备。 8)网络 Linux 是一种网络性能优越的操作系统,已经可以支持世界上大多数主流网络协议,包括 TCP/IP (IPv4/IPv6)、AppleTalk、IPX 等。 在网络硬件驱动方面,利用了 Linux 的设备模型底层的改进和许多设备驱动程序的升级。例如, Linux2.6 提供一个独立的 MII(媒体独立接口,或是 IEEE802.3u)子系统,它被许多网络设备驱动 程序使用。新的子系统替换了原先系统中各自运行的多个实例,消除了原先系统中多个驱动程序使 用重复代码、采用类似的方法处理设备的 MII 支持的情况。 在网络安全方面,Linux2.6 的一个重要改进是提供了对 IPsec 协议的支持。IPsec 是在网络协议 层为 IPv4 和 IPv6 提供加密支持的一组协议。由于安全是在协议层提供的,对应用层是透明的。它 与 SSL 协议及其他 tunneling/security 协议很相似,但是位于一个低很多的层面。当前内核支持的加 密算法包括 SHA(“安全散列算法”)、DES(“数据加密标准”)等。 在协议方面,Linux2.6 还加强了对多播网络的支持。网络多播使得有一点发出的数据包可以被 多台计算机接收(传统的点对点网络每次只能有两方通信)。 还有其他一些改进。例如:IPv6 已经成熟;VLAN 的支持也已经成熟等。 9)用户界面层 Linux2.6 中一个主要的内部改动是人机接口层的大量重写。人机接口层是一个 Linux 系统中用 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 58 - 户体验的中心,包括视频输出、鼠标、键盘等。内核的新版本中,这一层的重写以及模块化工作超 出了以前的任何一个版本。 Linux2.6 对显示器输出处理的支持也有不少改进,但大部分只能在配置使用内核内部的帧缓冲 控制台子系统时才有用。 人机界面层还加入了对近乎所有可接入设备的支持,从触摸屏到盲人用的设备,到各种各样的 鼠标。 10)统一的设备模型 Linux2.6 内核最值得关注的变化是创建了一个统一的设备模型。这个设备模型通过维持大量的 数据结构囊括了几乎所有的设备结构和系统。这样做的好处是,可以改进设备的电源管理和简化设 备相关的任务管理。 ¾ 这种设备模型可以跟踪获取以下信息; ¾ 系统中存在的设备,及其所连接的总线; ¾ 特定情形下设备的电源状态; ¾ 系统清楚设备的驱动程序,并清楚那些设备受其控制; ¾ 系统的总线结构,哪个设备连接在哪个总线上,以及哪些总线互联(例如:USB 和 PCI 总 线的互联); ¾ 设备在系统中的类别描述(类别包括磁盘,分区等)。 Linux2.6 内核引入了 sysfs 文件系统,提供了系统的设备模型的用户空间描述。通常 sysfs 文件 系统挂接在/sys 目录下。 3.Linux 内核的组成 现在使用图 2-2-3 Linux 内核结构的体系透视图中的分类说明 Linux 内核的主要组件。Linux 内核主要由 5 个子系统组成:进程调度、内存管理、虚拟文件系统、网络接口、进程间通信。 图 2-2-3 Linux 内核结构的体系透视图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 59 - 这 5 部分之间是相互依赖的关系,如图 2-2-4 所示,其中箭头表示依赖关系。 进程调度程序(SCHED)负责控制进程访问 CPU。保证进程能够公平地访问 CPU,同时保证内 核可以准时执行一些必需的硬件操作。 ¾ 内核管理程序(MM)使多个进程可以安全地共享机器的主存系统,并支持虚拟内存; ¾ 虚拟文件系统(VFS)。通过提供一个所有设备的公共文件接口,VFS 抽象了不同硬件设备 的细节。此外,VFS 支持与其他操作系统兼容的不同的文件系统格式; ¾ 网络接口(NET)提供对许多建网标准和网络硬件的访问; ¾ 进程间通信(IPC)子系统为进程与进程之间的通信提供了一些机制。 这些子系统虽然实现的功能相对独立,但存在着较强的依赖性(调用依赖模块中相应的函数), 所以说 linux 内核是单块结构(monolithic)的,而 windows 体系结构是微内核(microkernel)的。 同时,这些子系统之间是相辅相成的依赖关系。 进程调度与内存管理之间的关系:这两个子系统互相依赖。在多道程序环境下,程序要运行必 须为之创建进程,而创建进程的第一件事情,就是将程序和数据装入内存。 进程间通信与内存管理的关系:进程间通信子系统要依赖内存管理支持共享内存通信机制,这 种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。 虚拟文件系统与网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统(NFS), 也利用内存管理支持 RAMDISK 设备。 内存管理与虚拟文件系统之间的关系:内存管理利用虚拟文件系统支持交换,交换进程(swapd) 定期由调度程序调度,这也是内存管理依赖于进程调度的唯一原因。当一个进程存取的内存映射被 换出时,内存管理向文件系统发出请求,同时,挂起当前正在运行的进程。 图 2-2-4 Linux 内核的组成 虚拟文件系统 硬件驱动程序 逻辑文件系统 内存管理 硬件无关 硬件相关 进程调度 进程间通信 网络接口 网络协议 硬件驱动程序 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 60 - 2. 2. 2 存储与进程管理 1.存储管理 存储管理是 Linux 中负责管理内存的模块。内存管理的任务是屏蔽各种硬件的内存结构并向上 层返回统一的访问界面。Linux 支持各种各样的硬件体系结构,对每种硬件结构,其内存的组织形式 不尽相同。并解决了多进程状态下内存不足的问题,做到按需调页。 Linux 采用页式存储管理机制,每个页面的大小随处理机芯片而异。例如,Intel 386 处理机页 面大小可为 4KB 和 2MB,而 Alpha 处理机页面大小可为 8KB、16KB、32KB 和 64KB。Linux 的内存 管理支持虚拟内存,即在计算机中运行的程序,其代码、数据和堆栈的总量可以超过实际内存的大 小,操作系统只是把当前使用的程序块保留在内存中,其余的程序块则保留在磁盘中,必要时操作 系统负责在磁盘和内存间交换程序块。内存管理从逻辑上分为硬件无关部分和硬件相关部分,硬件 无关部分提供了进程的映射和逻辑内存的对换,硬件相关部分为内存管理硬件提供了虚拟接口。 在 Linux 中,为了建立虚拟空间和物理空间之间的映射,每个进程保留一张页表,用于将本进 程空间中的虚拟地址变换成物理地址。页表还对物理页的访问权限做出规定,定义了哪能些页可读/ 写,哪些页只读。在进行虚实变换时,Linux 将根据页表中规定的访问权限来判定进程对物理地址的 访问是否合法,从而达到存储保护的目的。 尽管 Linux 对物理存储器资源的使用十分谨慎,但还是经常出现物理存储器资源短缺的情况。 Linux 中 kswapd 进程专门负责页面的换出,当系统中的空闲页面小于一定的数目时,kswapd 将按 照一定的淘汰算法选出某些页面,或者直接丢弃(页面未作修改),或者将其写回硬盘(页面已被修 改)。 2.进程调度 进程调度控制进程对 CPU 的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行 的进程。可运行进程实际上是仅等待 CPU 资源的进程,如果某个进程在等待其他资源,则该进程是 不可运行进程。Linux 使用了比较简单的、基于优先级的进程调度算法选择可运行进程。 进程调度器处于 Linux 内核的中心位置,所有其他的子系统都依赖它,因为每个子系统都需要 挂起或恢复进程。一般情况下,当一个进程等待硬件操作完成时,它被挂起;当操作真正完成时, 进程被恢复执行。例如,当一个进程通过网络发送一条消息时,网络接口需要挂起发送进程,直到 硬件成功地完成消息的发送,当消息被成功地发送出去以后,网络接口给进程返回一个代码,表示 操作的成功或失败。其他子系统以相似的理由依赖于进程调度。 调度程序运行时,要在所有可运行状态的进程中选择最值得运行的进程投入运行。选择进程的 依据在每个进程的 task_struct 结构中有以下 4 项:policy、priority、rt_priority 和 counter,其中: policy 是进程的调度策略。系统中存在两类 Linux 进程:普通进程与实时进程。实时进程的优先 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 61 - 级高于其他进程,如果一个实时进程处于可执行状态,它将先得到执行。实时进程有两种策略:时 间片轮转(RP)和先进先出(FIFO)。在时间片轮转策略中,每个可执行实时进程轮流执行一个时 间片;而在先进先出的策略中,每个可执行进程按各自的运行队列听顺序报告并且顺序不能变化。 priority 是调度管理器分配给进程(包括实时和普通)的优先级,同时也是进程允许运行的时间 (jiffies)。系统调用 renice 可以改变进程的优先级。 rt_priority 用于实时进程间的选择,是实时进程特有的。Linux 支持实时进程,且它们的优先级 是高于非实时进程。调度器使用这个域给每个实时进程一个相对优先级,同样可以通过系统调用来 改变实时进程的优先级。 counter 是进程剩余的时间片,保存在 jiffies 中。它的起始值是 priority 的值,随时间变化递减。 由于 counter 在后面计算一个处于可运行状态的进程要运行的程序 goodness 时起重要作用,因此, counter 也可以看作是进程的动态优先级。 Linux 用函数 goodness( )来衡量一个处于可运行状态的进程值要运行的程序。该函数综合了以 上提到的 4 项,还结合了一些其他因素,给每个处于可运行状态的进程赋予一个权值(Weight),调 度程序以该权值作为选择进程的惟一依据。 首先,Linux 根据 policy 从整体上区分实时进程和普通进程,因为实时进程和普通进程度调度不 同,实时进程应该先于普通进程而运行。然后,对于同一类型的不同进程,采用不同的标准来选择 进程:对于普通进程,Linux 采用运态优先调度,选择进程的依据是进程 counter 的大小。 核心在以下时机调用调度管理器:例如当前进程被放入等待队列时、系统调用结束时以及从系 统模式返回用户模式时。 2. 2. 3 内核源代码目录结构 在阅读源码之前,还应知道 Linux 内核源码的整体分布情况。一般 Linux 内核源代码位于系统的 /usr/src/linux 目录下。Linux 内核非常庞大,结构复杂,据统计,Linux 内核接近 1 万个文件,4 百 万行代码。因此对代码的结构进行分析可以帮助读者更好地阅读和理解内核代码。现代的操作系统 一般由进程管理、内存管理、文件系统、驱动程序和网络等组成。Linux 内核源码的各个目录大致与 此相对应,其组成如图 2-2-5 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 62 - 图 2-2-5 Linux 内核代码分布框图 1.arch 目录 Linux 系统的内核中将源程序代码分为:与体系结构相关部分和体系结构无关部分,这样方便支 持多平台的硬件。arch 目录包含了体系结构相关部分的内核代码,它下面的每一个子目录都代表一 种 Linux 支持的体系结构,例如 i386 就是 Intel CPU 及与之相兼容体系结构的子目录。PC 机一般都 基于此目录。arch 目录在系统移植过程中是需要重点修改的部分。对于任何平台,都包含下列目录: ¾ boot:包含内核启动时所用到的和特定硬件平台有关的代码; ¾ kernel:包含和体系结构特有的特征相关的内核代码; ¾ lib:存放和体系结构相关的库文件代码,如 strlen 和 memcpy; ¾ mm:存放体系结构特有的内存管理程序代码; ¾ math-emu:模拟 FPU 的代码,对于 ARM 处理器,此目录由 math-xxx 代替。 2.include 目录 include 目录包括编译核心所需要的大部分头文件,例如与平台无关的头文件在 include/linux 子 目录下。不同的平台需要的头文件是不同的,因此该目录和 arch 目录一样,按平台划分了多个子目 录,例如,次目录中的 asm 子目录是对应某种处理器的符号连接,例如 include/asm-i386、 include/asm-arm 目录等。 3.init 目录 init 目录包含核心的初始化代码(不是系统的引导代码),有 main.c 和 Version.c 两个文件。这 是研究核心如何工作的好起点。 4.mm 目录 mm 此目录包含了所有的内存管理代码。与具体硬件体系结构相关的内存管理代码位于 arch/ 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 63 - mm 目录下,如对应于 X86 的就是 arch/i386/mm/fault.c。 5.drivers 目录 drivers 目录中是系统中所有的设备驱动程序。它又进一步划分成几类设备驱动,每一种有对应 的子目录,如声卡的驱动对应于 drivers/sound。该目录占用了整个内核发行版本代码的一半以上, 其中有些驱动程序是与硬件平台相关的,有些是与硬件平台无关,例如字符设备、块设备、串口、 USB 以及 LCD 显示驱动等。 6.ipc 目录 ipc 此目录包含了 Linux 操作系统核心的进程间通讯代码。 7.kernel 目录 Kernel 内核管理的核心代码放在这里。这部分内容包括进程调度(kernel/sched.c)及创建和撤 销进程的代码(kernel/fork.c 和 kernel/exit.c)。同时与处理器结构相关代码都放在 arch/ kernel 目 录下,其中*为特定的处理器体系结构名称。 8.fs 目录 该目录下列出了 Linux 支持的所有文件系统。目前 Linux 已经支持包括 JFFS2、YAFFS、ext3 和 NFS 在内的多种文件系统,其中 JFFS2 常用于嵌入式系统 NOR Flash 中的文件系统,YAFFS 常用于 NAND Flash 中的文件系统,ext3 常用于台式 PC 机的 Linux 操作系统中的文件系统。还有一些伪文 件系统,例如 proc 文件系统,可以以伪文件的形式提供其他信息(例如,在 proc 的情况下是提供 内核的内部变量和数据结构)。虽然在底层并没有实际的存储设备与这些文件系统相对应,但是进程 可以像有实际存储设备一样处理。 9.net 目录 这个目录是核心的网络部分代码,其每个子目录对应于网络的一个方面,也包括 Linux 应用的 网络协议代码,例如 TCP/IP、IPX 等。 10.lib 目录 lib 目录包含了核心的库代码,不过与处理器结构相关的库代码被放在 arch/ lib/目录下。 Lib/inflate.c 中的函数能够在系统启动时展开经过压缩的内核。Lib 目录下剩余的其他文件实现一个 标准 C 库的有用子集,主要集中在字符串和内存操作的函数(strlen、memcpy 和其他类似的函数) 及有关 sprintf 和 atoi 的系列函数上。 11.scripts 目录 scripts 目录下没有代码,包含用于配置核心的脚本文件。当运行 make menuconfig 或 make xconfig 之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。 12.Documentation 目录 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 64 - documentation 目录下是一些非常有用的文档,是对每个目录作用的具体说明,起参考作用, 非常详细。 一般在每个目录下都有一个.depend 文件和一个 Makefile 文件。这两个文件都是编译时使用的 辅助文件。仔细阅读这两个文件对弄清各个文件之间的联系和依托关系很有帮助。另外有的目录下 还有 Readme 文件,它是对该目录下文件的一些说明,同样有利于对内核源码的理解。 2. 3 Linux 存储管理 2. 3. 1 进程虚存空间的管理 Linux 操作系统采用了请求式分页存储管理方法。 系统为每个进程提供了 4GB 的虚拟内存空间。 各个进程的虚拟内存彼此独立。 进程运行时能访问的存储空间只是它的虚拟内存空间。对当前该进程而言只有属于它的虚拟内 存是可见的。在进程的虚拟内存包含着进程本身的程序代码和数据。进程在运行中还必须得到操作 系统的支持。进程的虚拟内存中还包含着操作系统内核。Linux 把进程的虚拟内存分成两部分,内核 区和用户区。操作系统内核的代码和数据等被映射到内核区。进程的可执行映像(代码和数据)映 射到虚拟内存的用户区。进程虚拟内存的内核区的访问权限设置为 0 级,用户区为 3 级。内核访问 虚存的权限为 0 级,而进程的访问权限为 3 级。 Linux 的存储管理主要是管理进程虚拟内存的用户区。进程虚拟内存的用户区分成代码段,数据 段,堆栈以及进程运行的环境变量,参数传递区域等。每一个进程,用一个 mm-struct 结构体来定 义它的虚存用户区。mm_struct 结构体首地址在任务结构体 task-struct 成员项 mm 中:struct mm-struct *mm。 mm_struct 结构定义在/include/linux/schedul.h 中。 struct mm_struct { int count; pgd_t * pgd; unsigned long context; unsigned long start_code, end_code, start_data, end_data; unsigned long start_brk, brk, start_stack, start_mmap; unsigned long arg_start, arg_end, env_start, env_end; unsigned long rss, total_vm, locked_vm; unsigned long def_flags; struct vm_area_struct * mmap; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 65 - struct vm_area_struct * mmap_avl; struct semaphore mmap_sem; }; 一个虚存区域是虚存空间中一个连续的区域,在这个区域中的信息具有相同的操作和访问特性。 每个虚拟区域用一个 vm-area-struct 结构体进行描述。它定义在/include/linux/mm.h 中。 struct vm_area_struct { struct mm_struct * vm_mm; unsigned long vm_start; unsigned long vm_end; pgprot_t vm_page_prot; unsigned short vm_flags; short vm_avl_height; struct vm_area_struct * vm_avl_left; struct vm_area_struct * vm_avl_right; struct vm_area_struct * vm_next; struct vm_area_struct * vm_next_share; struct vm_area_struct * vm_prev_share; struct vm_operations_struct * vm_ops; unsigned long vm_offset; struct inode * vm_inode; unsigned long vm_pte; }; 2. 3. 2 虚存空间的映射和虚存区域的建立 1. 虚拟空间的地址映射 在多进程操作系统中,同时运行多个用户的程序,系统分配给用户的物理地址空间放不下代码 和数据等。为了解决这个矛盾而出现了虚拟存储技术。在虚拟存储技术中,用户的代码和数据(可 执行映像)等并不是完整地装入物理内存,而是全部映射到虚拟内存空间。在进程需要访问内存时, 在虚拟内存中“找到”要访问的程序代码和数据等。 系统再把虚拟空间的地址转换成物理内存的物理地址。 2. 虚存区域的建立 Linux 使用 do_mmap()函数完成可执行映像向虚存区域的映射,由它建立有关的虚存区域。 do_mmap()函数定义在/mm/mmap.c 文件中: unsigned long do_mmap(struct file * file, unsigned long addr, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 66 - unsigned long len,unsigned long prot, unsigned long flags, unsigned long off) 2. 3. 3 Linux 的分页式存储管理 1. Linux 的三级分页结构 页表是从线性地址向物理地址转换中不可缺少的数据结构,而且它使用的频率较高。页表必须 存放在物理存储器中。虚存空间有 4GB,按 4KB 页面划分页表可以有 1M 页。若采用一级页表机制, 页表有 1M 个表项,每个表项 4 字节,这个页面就要占用 4MB 的内存空间。由于系统中每个进程都 有自己的页表,如果每个页表占用 4MB,对于多个进程而言就要占去大量的物理内存,这是不现实 的。在目前用户的进程不可能需要使用 4GB 这么庞大的虚存空间,若使用 1M 个表项的一级页表, 势必造成物理内存极大的浪费。为此,Linux 采用了三级页表结构,以利于节省物理内存。三级分页 管理把虚拟地址分成四个位段: 页目录、页中间目录、页表、页内编址。 系统设置三级页表系列: ¾ 页目录 PGD(PaGe Directory); ¾ 页中间目录 PMD(Page Middle Directory); ¾ 页表 PTE(Page TablE)。 三级分页结构是 Linux 提供的与硬件无关的分页管理方式。当 Linux 运行在某种机器上时,需要 利用该种机器硬件的存储管理机制来实现分页存储。Linux 内核中对不同的机器配备了不同的分页结 构的转换方法。对 x86,提供了把三级分页管理转换成两级分页机制的方法。其中一个重要的方面 就是把 PGD 与 PMD 合二为一,使所有关于 PMD 的操作变为对 PGD 的操作。 在/include/asm-i386/pgtable.h 中有如下定义: #define PTRS_PER_PTE 1024 #define PTRS_PER_PMD 1 #define PTRS_PER_PGD 1024 2. 地址映射 地址映射就是在几个存储空间(逻辑地址空间、线形地址空间、物理地址空间)或存储设备之 间进行的地址转换。 2. 3. 4 物理内存空间的管理 1. 物理内存的页面管理 Linux 对物理内存空间按照分页方式进行管理,把物理内存划分成大小相同的物理页面。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 67 - Linux设置了一个mem_map[]数组管理内存页面。mem_map[]在系统初始化时由free_area_init ()函数创建,它存放在物理内存的底部(低地址部分)。mem_map[]数组的元素是一个个的 page 结构体,每一个 page 结构体它对应一个物理页面。page 结构进一步被定义为 mem_map_t 类型, 其定义在/include/linux/mm.h 中。 typedef struct page { struct page *next; struct page *prev; struct inode *inode; unsigned long offset; struct page *next_hash; atomic_t count; unsigned flags; unsigned dirty:16,age:8; struct wait_queue *wait; struct buffer_head * buffers; unsigned long swap_unlock_entry; unsigned long map_nr; } mem_map_t; 2. 空闲页面的管理—Buddy 算法 Linux 对内存空闲空间的管理采用 Buddy 算法,Buddy 是“伙伴”、“搭档”的意思。Buddy 算 法是把内存中的所有页面按照 2n 划分,其中 n=0~5,对一个内存空间按 1 个页面、2 个页面、4 个页面、8 个页面、16 个页面、32 个页面进行六次划分。划分后形成了大小不等的存储块,称为页 面块,简称页块。包含 1 个页面的页块称为 1 页块,包含 2 个页面的称为 2 页块,依此类推。Linux 把物理内存划分成了 1、2、4、8、16、32 六种页块。对于每种页面块按前后顺序两两结合成一对 Buddy“伙伴”。按照 1 页面划分后,0 和 1 页、2 和 3 页…是 1 页块 Buddy。按照 2 页面划分,0-1 和 2-3、4-5 和 6-7…是 2 页块 Buddy,依次类推。 Linux 把空闲的页面按照页块大小分组进行管理,数组 free_area[]来管理各个空闲页块组。 在/mm/page_alloc.c 中定义如下: #define NR_MEM_LISTS 6 static struct free_area_struct free_area[NR_MEM_LISTS]; struct free_area_struct { struct page *next; struct page *prev; unsigned int * map; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 68 - }; 2. 3. 5 内存的分配与释放 Linux 中用于内存分配和释放的函数主要是 kmalloc()和 kfree(),它们用于分配和释放连续 的内存空间。 1. 内存分配与释放的数据结构 ¾ blocksize 表。 kmalloc()和 kfree()分配和释放内存是以块(block)为单位进行的。可以分配的空闲块的 大小记录在 blocksize 表中,它是一个静态数组,定义在/mm/kmalloc.c 中: #if PAGE_SIZE == 4096 static const unsigned int blocksize[] = { 32,64,128,252,508,1020,2040, 4096-16,8192-16,16384-16, 32768-16,65536-16,131072-16, 0 }; 在使用 kmalloc()分配空闲块时仍以 Buddy 算法为基础,即以 free_area[]管理的空闲页面块 做为分配对象。重新制定了分配的单位,blocksize[]数组中的块长度。它可以分配比 1 个页面更小 的内存空间。blocksize[]中的前 7 个是在 1 个空闲页面内进行分配,其后的 6 种分别对应 free_area[] 的 1 至 32 空闲页面块。当申请分配的空间小于或等于 1 个页面时,从 free_area[] 管理的 1 页面块 中查找空闲页面进行分配。若申请的空间大于一个页面时,按照 blocksize[]后六个块单位进行申请, 从 free_area[]中与该块长度对应的空闲页块组中查找空闲页面块。 ¾ page_descriptor。 对 kmalloc()分配的内存页面块中加上一个信息头,它处于该页面块的前部。页面块中信息头 后的空间是可以分配的内存空间。加在页面块前部的信息头称为页描述符,定义在/mm/kmalloc.c 中: struct page_descriptor { struct page_descriptor *next; // 指向下一个页面块的指针 struct block_header *firstfree; // 本页中空闲块链表的头 int order; // 本页中块长度的级别 int nfree; // 本页中空闲的数目 }; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 69 - 具有相同块单位和使用特性的页面块组成若干个链表。 ¾ sizes 表。 Linux 设置了 sizes[]数组,对页面块进行描述。数组元素是 size_descriptor 结构体,定义在 /mm/kmalloc.c 中: struct size_descriptor { struct page_descriptor *firstfree; // 一般页块链表的头指针 struct page_descriptor *dmafree; // DMA页块链表的头指针 int nblocks; // 页块中划分的块数目 int nmallocs; // 链表中各页块中已分配的块总数 int nfrees; // 链表中各页块中尚空闲的块总数 int nbytesmalloced; // 链表中各页块中已分配的字节总数 int npages; // 链表中页块数目 unsigned long gfporder; // 页块的页面数目 }; static struct size_descriptor sizes[] = { {NULL, NULL, 127, 0, 0, 0, 0, 0}, {NULL, NULL, 63, 0, 0, 0, 0, 0}, {NULL, NULL, 31, 0, 0, 0, 0, 0}, {NULL, NULL, 16, 0, 0, 0, 0, 0}, {NULL, NULL, 8, 0, 0, 0, 0, 0}, {NULL, NULL, 4, 0, 0, 0, 0, 0}, {NULL, NULL, 2, 0, 0, 0, 0, 0}, {NULL, NULL, 1, 0, 0, 0, 0, 0}, {NULL, NULL, 1, 0, 0, 0, 0, 1}, {NULL, NULL, 1, 0, 0, 0, 0, 2}, {NULL, NULL, 1, 0, 0, 0, 0, 3}, {NULL, NULL, 1, 0, 0, 0, 0, 4}, {NULL, NULL, 1, 0, 0, 0, 0, 5}, {NULL, NULL, 0, 0, 0, 0, 0, 0} }; blocksize[]与 sizes[]元素数目相同,它们一一对应。由 kmalloc()分配的每种块长度的页面块 链接成两个链表,一个是 DMA 可以访问的页面块链表,dmafree 指向这个链表。一个是一般的链表, firstfree 指向这个链表。成员项 gfporder 是 0~5,它做为 2 的幂数表示所含的页面数。 ¾ block_header。 由 sizes[]管理的各个页面块中每个块(空闲块和占用块)的头部还有一个对该块进行描述的块 头 block_header: struct block_header { unsigned long bh_flags; // 块的分配标志 union { unsigned long ubh_length; // 块长度 struct block_header *fbh_next; //指向下一空闲块的指针 } vp; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 70 - }; bh_flages 是块的标志,有三种: ¾ MF_FREE 指明该块是空闲块; ¾ MF_USED 表示该块已占用; ¾ MF_DMA 表示该块是 DMA 可访问。 ubh_length 和 fbh_next 是联合体成员项,用法如下: ¾ 当块占用时使用 ubh_length 表示该块的长度; ¾ 当块空闲时使用 fbh_next 链接下一个空闲块。 在一个页块中的空闲块组成一个链表,表头由页块的 page_descriptor 中 firstfree 指出。 2. 内存分配函数 kmalloc() void *kmalloc(size_t size, int priority)。 参数 size 是申请分配内存的大小,priority 是申请优先级。 priority 常用的值为 GFP_KERNEL 和 GFP_ATOMIC,内存不够时: ¾ GFP_KERNEL:当前申请进程暂时被挂起而等待换页; ¾ GPF_ATOMIC:该函数不允许推迟,而立即返回 0 值。 priority 还取值 GPF_DMA,表示申请的内存用于 DMA 传送。 kfree()用于释放由 kmalloc()分配的内存空间:void kfree(void *__ptr)。 ptr 是 kmalloc()分配的内存空间的首地址。 当 kmalloc 管理的一个页面块中的占用块全部被释放后,它就成为一个空闲页面块。系统把这 个空闲页面块从 sizes[]管理的相应链表中删除,把它交给 free_area[]数组按照 buddy 算法管理。 kmalloc()和 kfree()还共同维护一个 kmalloc 缓冲区,由 kmalloc_cache 的数组进行管理, 定义如下: #define MAX_CACHE_ORDER 3 struct page_descriptor * kmalloc_cache[MAX_CACHE_ORDER]; kmalloc_cache[]3 个元素,分别指向一个空闲的 1、2、4 页面块。由 sizes[]管理的内存中有 1 页块、2 页块或 4 页块被释放时,它们不立即交还 free_area[]管理,先交给 kmalloc_cache[]管理。 sizes[]中有新的 1 页块、2 页块或 4 页块被释放时,把 kmalloc_cache[]当前指向的空闲页块交 给 free_area[]管理,然后指向新释放的空闲页块。 3. 虚拟内存的申请和释放 在申请和释放较小且连续的内存空间时,使用 kmalloc()和 kfree()在物理内存中进行分配。 申请较大的内存空间时,使用 vmalloc()。由 vmalloc()申请的内存空间在虚拟内存中是连续的, 它们映射到在物理内存时,可以使用不连续的物理页面,而且仅把当前访问的部分放在物理页面中。 由 vmalloc()分配的虚存空间称为虚拟内存块(虚存块)。由 vmalloc()分配的虚存块用一个链表 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 71 - 来管理,系统定义的指针变量 vmlist 指向链表的表头,在 mm/vmalloc.c 中定义如下: static struct vm_struct * vmlist = NULL; // 结构vm_struct描述由vmalloc()分配的虚存块: struct vm_struct { unsigned long flags; // 虚存块的标志 void * addr; // 虚存块起始地址 unsigned long size; // 虚存块大小 struct vm_struct * next; // 指向下一个虚存块的指针 }; vmalloc()和 vfree()定义在 mm/vmalloc.c 中: void * vmalloc(unsigned long size) void vfree(void * addr) 可以看到 vmalloc()参数 size 指出申请的内存的大小。分配成功后返回值为在虚存空间分配 的虚存块首地址,失败返回值为 0。vfree()用来释放由 vmalloc()分配的虚存块,参数 addr 是 要释放的虚存块首址。 2. 4 Linux 进程管理 2. 4. 1 Linux 进程管理介绍 Linux 是一个多用户多任务的操作系统。多用户是指多个用户可以在同一时间使用计算机系统; 多任务是指 Linux 可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一项任务。 操作系统管理多个用户的请求和多个任务。大多数系统都只有一个 CPU 和一个主存,但一个系 统可能有多个二级存储磁盘和多个输入/输出设备。操作系统管理这些资源并在多个用户间共享资 源,当您提出一个请求时,给您造成一种假象,好象系统只被您独自占用。而实际上操作系统监控 着一个等待执行的任务队列,这些任务包括用户作业、操作系统任务、邮件和打印作业等。操作系 统根据每个任务的优先级为每个任务分配合适的时间片,每个时间片大约都有零点几秒,虽然看起 来很短,但实际上已经足够计算机完成成千上万的指令集。每个任务都会被系统运行一段时间,然 后挂起,系统转而处理其他任务;过一段时间以后再回来处理这个任务,直到某个任务完成,从任 务队列中去除。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 72 - 2. 4. 2 进程及作业 Linux 系统上所有运行的东西都可以称之为一个进程。每个用户任务、每个系统管理守护进程, 都可以称之为进程。Linux 用分时管理方法使所有的任务共同分享系统资源。我们所关心的是如何去 控制这些进程,让它们能够很好地为用户服务。 进程的一个比较正式的定义是:在自身的虚拟地址空间运行的一个单独的程序。进程与程序是 有区别的,进程不是程序,虽然它由程序产生。程序只是一个静态的指令集合,不占系统的运行资 源;而进程是一个随时都可能发生变化的、动态的、使用系统运行资源的程序。而且一个程序 可以 启动多个进程。 进程和作业的概念也有区别。一个正在执行的进程称为一个作业,而且作业可以包含一个或多 个进程,尤其是当使用了管道和重定向命令。 作业控制指的是控制正在运行的进程的行为。比如,用户可以挂起一个进程,等一会儿再继续 执行该进程。shell 将记录所有启动的进程情况,在每个进程过程中,用户可以任意地挂起进程或重 新启动进程。作业控制是许多 shell(包括 bash 和 tcsh)的一个特性,使用户能在多个独立作业间 进行切换。 例如,当用户编辑一个文本文件,并需要中止编辑做其他事情时,利用作业控制,用户可以让 编辑器暂时挂起,返回 shell 提示符开始做其他的事情。其他事情做完以后,用户可以重新启动挂起 的编辑器,返回到刚才中止的地方,就象用户从来没有离开编辑器一样。这只是一个例子,作业控 制还有许多其他实际的用途。 2. 4. 3 启动进程 在 Shell 环境下键入需要运行的程序或在图形界面下执行一个程序,其实也就是启动了一个进 程。在 Linux 系统中每个进程都具有一个进程号,用于系统识别和调度。启动一个进程有两个主要 途径。手工启动和调度启动,后者是事先进行设置,根据用户要求自行启动。 1. 手工启动 由用户输入命令,直接启动一个进程便是手工启动。但手工启动进程又可以分为前台启动和后 台启动。 前台启动是手工启动一个进程的最常用的方式。一般用户键入一个命令“ls –l”,这就已经启 动了一个进程,而且是一个前台的进程。这时候系统其实已经处于一个多进程状态。或许有些用户 会疑惑:我只启动了一个进程而已。但实际上有许多运行在后台的、系统启动时就已经自动启动的 进程正在悄悄运行着。还有的用户在键入“ls –l”命令以后赶紧使用“ps –x”查看,却没有看到 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 73 - ls 进程,也觉得很奇怪。其实这是因为 ls 这个进程结束太快,使用 ps 查看时该进程已经执行结束了。 直接从后台手工启动一个进程用得比较少一些,除非是该进程甚为耗时,且用户也不急着需要 结果的时候。假设用户要启动一个需要长时间运行的格式化文字文件的进程。为了不使整个 Shell 在格式化过程中都处于“瘫痪”状态,从后台启动这个进程是明智的选择。 2. 调度启动 有时候需要对系统进行一些比较费时而且占用资源的维护工作,这些工作适合在深夜进行,这 时候用户就可以事先进行调度安排,指定任务运行的时间或者场合,到时候系统会自动完成这些工 作。要使用自动启动进程的功能,就需要掌握以下几个启动命令。 1) at 命令。 用户使用 at 命令在指定时刻执行指定的命令序列。也就是说,该命令至少需要指定一个命令、 一个执行时间才可以正常运行。at 命令可以只指定时间,也可以时间和日期一起指定。需要注意的 是,指定时间有个系统判别问题。比如说,用户现在指定了一个执行时间——凌晨 3:20,而发出 at 命令的时间是头天晚上的 20:00,那么究竟是在哪一天执行该命令呢?如果用户在 3:20 以前仍然在 工作,那么该命令将在这个时候完成;如果用户 3:20 以前就退出了工作状态,那么该命令将在第二 天凌晨才得到执行。下面是 at 命令的语法格式: at [-V] [-q 队列] [-f 文件名] [-mldbv] 时间 at -c 作业 [作业...] at 允许使用一套相当复杂的指定时间的方法,它可以接受在当天的 hh:mm(小时:分钟)式的 时间指定。如果该时间已经过去,那么就放在第二天执行。当然也可以使用 midnight(深夜)、noon (中午)、teatime(饮茶时间,一般是下午 4 点)等比较模糊的词语来指定时间。用户还可以采用 12 小时计时制,即在时间后面加上 AM (上午)或者 PM (下午)来说明是上午还是下午。也可以 指定命令执行的具体日期,指定格式为 month day(月 日)、mm/dd/yy(月/日/年)或者 dd.mm.yy (日.月.年)。指定的日期必须跟在指定时间的后面。 上面介绍的都是绝对计时法,其实还可以使用相对计时法,这对于安排不久就要执行的命令是 很有好处的。指定格式为:now + count time-units ,now 就是当前时间,time-units 是时间单位, 这里可以是 minutes(分钟)、hours(小时)、days(天)、weeks(星期)。count 是时间的数量,究 竟是几天,还是几小时。 还有一种计时方法就是直接使用 today(今天)、tomorrow(明天)来指定完成命令的时间。下 面通过一些例子来说明具体用法。 比如,指定在今天下午 5:30 执行某命令。假设现在时间是 2001 年 2 月 24 日中午 12:30,其 命令格式如下: at 5:30pm at 17:30 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 74 - at 17:30 today at now + 5 hours at now + 300 minutes at 17:30 24.2.99 at 17:30 2/24/99 at 17:30 Feb 24 以上这些命令表达的意义是完全一样的,所以在安排时间的时候完全可以根据个人喜好和具体 情况自由选择。一般采用绝对时间的 24 小时计时法可以避免由于用户自己的疏忽造成计时错误的情 况发生,例如上例可以写成: at 17:30 2/24/99 这样非常清楚,而且别人也看得懂。 对于 at 命令来说,需要定时执行的命令是从标准输入或者使用-f 选项指定的文件中读取并执行 的。如果 at 命令是从一个使用 su 命令切换到用户 Shell 中执行的,那么当前用户被认为是执行用户, 所有的错误和输出结果都会送给这个用户。但是如果有邮件送出的话,收到邮件的将是原来的用户, 也就是登录时 Shell 的所有者。 在任何情况下,超级用户都可以使用这个命令。对于其他用户来说,是否可以使用取决于两个 文件:/etc/at.allow 和/etc/at.deny 。 2) cron 命令。 前面介绍的两条命令都会在一定时间内完成一定任务,但是要注意它们都只能执行一次。也就 是说,当指定了运行命令后,系统在指定时间完成任务,一切就结束了。但是在很多时候需要不断 重复一些命令,比如:某公司每周一自动向员工报告头一周公司的活动情况,这时候就需要使用 cron 命令来完成任务了。实际上,cron 命令是不应该手工启动的。cron 命令在系统启动时就由一个 shell 脚本自动启动,进入后台(所以不需要使用&符号)。一般的用户没有运行该命令的权限,虽然超级 用户可以手工启动 cron,不过还是建议将其放到 shell 脚本中由系统自行启动。 首先 cron 命令会搜索/var/spool/cron 目录,寻找以/etc/passwd 文件中的用户名命名的 crontab 文件,被找到的这种文件将载入内存。例如一个用户名为 foxy 的用户,它所对应的 crontab 文件就 应该是/var/spool/cron/foxy。也就是说,以该用户命名的 crontab 文件存放在/var/spool/cron 目录 下面。cron 命令还将搜索/etc/crontab 文件,这个文件是用不同的格式写成的。cron 启动以后,它 将首先检查是否有用户设置了 crontab 文件,如果没有就转入“休眠”状态,释放系统资源。所以 该后台进程占用资源极少。它每分钟“醒”过来一次,查看当前是否有需要运行的命令。命令执行 结束后,任何输出都将作为邮件发送给 crontab 的所有者,或者是/etc/crontab 文件中 MAILTO 环境 变量中指定的用户。上面简单介绍了一些 cron 的工作原理,但是 cron 命令的执行不需要用户干涉; 需要用户修改的是 crontab 中要执行的命令序列,所以下面介绍 crontab 命令。 3) crontab 命令。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 75 - crontab 命令用于安装、删除或者列出用于驱动 cron 后台进程的表格。也就是说,用户把需要 执行的命令序列放到 crontab 文件中以获得执行。每个用户都可以有自己的 crontab 文件。下面就来 看看如何创建一个 crontab 文件。在/var/spool/cron 下的 crontab 文件不可以直接创建或者直接修改。 crontab 文件是通过 crontab 命令得到的。现在假设有个用户名为 foxy,需要创建自己的一个 crontab 文件。首先可以使用任何文本编辑器建立一个新文件,然后向其中写入需要运行的命令和要定期执 行的时间。然后存盘退出。假设该文件为/tmp/test.cron。再后就是使用 crontab 命令来安装这个文 件,使之成为该用户的 crontab 文件。键入:crontab test.cron。 这样一个 crontab 文件就建立好了。可以转到/var/spool/cron 目录下面查看,发现多了一个 foxy 文件。这个文件就是所需的 crontab 文件。 在 crontab 文件中如何输入需要执行的命令和时间。该文件中每行都包括六个域,其中前五个 域是指定命令被执行的时间,最后一个域是要被执行的命令。每个域之间使用空格或者制表符分隔。 格式如下: minute hour day-of-month month-of-year day-of-week commands 第一项是分钟,第二项是小时,第三项是一个月的第几天,第四项是一年的第几个月,第五项 是一周的星期几,第六项是要执行的命令。这些项都不能为空,必须填入。如果用户不需要指定其 中的几项,那么可以使用*代替。因为*是统配符,可以代替任何字符,所以就可以认为是任何时间, 也就是该项被忽略了。 3. 进程的挂起及恢复 作业控制允许将进程挂起并可以在需要时恢复进程的运行,被挂起的作业恢复后将从中止处开 始继续运行。只要在键盘上按【Ctrl+Z】,即可挂起当前的前台作业。在键盘上按【Ctrl+Z】后,将 挂起当前执行的命令 cat。使用 jobs 命令可以显示 Shell 的作业清单,包括具体的作业、作业号以 及作业当前所处的状态。恢复进程执行时,有两种选择:用 fg 命令将挂起的作业放回到前台执行; 用 bg 命令将挂起的作业放到后台执行。灵活使用上述命令,将给自己带来很大的方便。 2. 4. 4 进程管理 由于 Linux 是个多用户系统,有时候也要了解其他用户现在在干什么;同时 Linux 是一个多进程 系统,经常需要对这些进程进行一些调配和管理;而要进行管理,首先就要知道现在的进程情况: 究竟有哪些进程?进程情况如何?所以需要查看进程。 1) who 命令。 该命令主要用于查看当前线上的用户情况。这个命令非常有用。如果用户想和其他用户创建即 时通信,比如使用 talk 命令,那么首先要确定的就是该用户确实上线,不然 talk 进程就无法创建起 来。又如,系统管理员希望监视每个登录的用户此时此刻的行为,也要使用 who 命令。who 命令应 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 76 - 用起来非常简单,可以比较准确地掌握用户的情况,所以使用非常广泛。 2) w 命令。 该命令也用于显示登录到系统的用户情况,但是与 who 不同的是,w 命令功能更加强大,它不 但可以显示有谁登录到系统,还可以显示出这些用户当前正在进行的工作,并且统计资料相对 who 命令来说更加详细和科学。可以认为 w 命令就是 who 命令的一个增强版。w 命令的显示项目按以下 顺序排列:当前时间;系统启动到现在的时间;登录用户的数目;系统在最近 1 秒、5 秒和 15 秒的 平均负载。然后是每个用户的各项资料,项目显示顺序如下:登录账号、终端名称、远程主机名、 登录时间、空闲时间、JCPU、PCPU、当前正在运行进程的命令行。其中 JCPU 时间指的是和该终端 (tty)连接的所有进程占用的时间。这个时间里并不包括过去的后台作业时间,但却包括当前正在 运行的后台作业所占用的时间。而 PCPU 时间则是指当前进程(即在 WHAT 项中显示的进程)所占 用的时间。 3) ps 命令。 ps 命令就是最基本的同时也是非常强大的进程查看命令。使用该命令可以确定有哪些进程正在 运行以及运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大 部分信息都可以通过执行该命令得到。 ps 命令最常用的还是用于监控后台进程的工作情况,因为后台进程是不和屏幕键盘这些标准输 入/输出设备进行通信的,所以如果需要检测其情况,便可以使用 ps 命令了。 ps [选项] 下面对命令选项进行说明: ¾ -e 显示所有进程; ¾ -f 全格式; ¾ -h 不显示标题; ¾ -l 长格式; ¾ -w 宽输出; ¾ a 显示终端上的所有进程,包括其他用户的进程; ¾ r 只显示正在运行的进程; ¾ x 显示没有控制终端的进程。 O[+|-] k1 [, [+|-] k2 [, …]] 根据 SHORT KEYS, k1, k2 中快捷键指定的多级排序顺序显示进程 列表。对于 ps 的不同格式都存在着默认的顺序指定。这些默认顺序可以被用户的指定所覆盖。其中 + 字符是可选的,- 字符是倒转指定键的方向。 最常用的 3 个参数是 u, a, x。 4) top 命令。 top 命令和 ps 命令的基本作用是相同的,用于显示系统当前的进程和其他状况。但是 top 是一 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 77 - 个动态显示过程,即可以通过用户按键来不断刷新当前状态。如果在前台执行该命令,它将独占前 台,直到用户终止该程序为止。准确地说,top 命令提供了即时的对系统处理器的状态监视。它将显 示系统中 CPU 最“敏感”的任务列表。该命令可以按 CPU 使用。内存使用和执行时间对任务进行排 序;而且该命令的很多特性都可以通过互动式命令或者在个人定制文件中进行设置。 下面是该命令的语法格式: top [-] [d delay] [q] [c] [s] [S] [i] 其中: ¾ d 指定每两次屏幕信息刷新之间的时间间隔。当然用户可以使用 s 交互命令来改变它; ¾ q 该选项将使 top 没有任何延迟地进行刷新。如果调用程序有超级用户许可权,那么 top 将以尽可能高的优先顺序运行; ¾ c 显示整个命令行而不只是显示命令名; ¾ s 使 top 命令在安全模式中运行。这将去除交互命令所带来的潜在危险; ¾ S 指定累计模式; ¾ i 使 top 不显示任何闲置或者僵死进程。 top 命令显示的项目很多,默认值是每 5 秒刷新一次,当然这是可以设置的。 5) Kill 命令。 当需要中断一个前台进程的时候,通常是使用组合键【Ctrl+C】;但是对于一个后台进程恐怕就 不是一个组合键所能解决的了,这时就必须求助于 kill 命令,该命令可以终止后台进程。终止后台进 程的原因很多,或许是该进程占用的 CPU 时间过多,或许是该进程已经挂死。总之这种情况是经常 发生的。 kill 命令是通过向进程发送指定的信号来结束进程的。如果没有指定发送信号,那么默认值为 TERM 信号,它将终止所有不能捕获该信号的进程。至于那些可以捕获该信号的进程可能就需要使 用 kill(9)信号了,该信号是不能被捕捉的。 kill 命令的语法格式很简单,大致有以下两种方式: kill [-s 信号 | -p ] [ -a ] 进程号 ... kill -l [信号] 其中: ¾ -s 指定需要送出的信号,既可以是信号名也可以是对应数位; ¾ -p 指定 kill 命令只是显示进程的 pid,并不真正送出结束信号。 在本实验平台的主板上设计了两路外部按键,当键被按下时,会产生按键中断信号。按键产生 的中断信号经过 CPLD 逻辑处理后连接到 CPU 的中断引脚。 6) nohup 命令。 理论上,我们一般退出 Linux 系统时会把所有的程序全部结束,包括那些后台程序。有些时候, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 78 - 例如你正在编辑一个很长的程序,但是你下班或是有事需要先退出系统,你又不希望系统把你编辑 那么久的程序结束,希望退出系统时,程序还能继续执行。这时,我们就可以使用 nohup 命令使进 程在用户退出后仍继续执行。 一般我们都是让这些进程在后台执行,结果会写到用户自己目录下的 nohup.out 文件里(也可 以使用输出重定向,让它输出到一个特定的文件)。 例如:# nohup sort sales.dat & 这条命令告诉 sort 命令忽略用户已退出系统,它应该一直运行,直到进程完成。利用这种方法, 可以启动一个要运行几天甚至几周的进程,而且在它运行时,用户不需要去登录。 nohup 命令把一条命令的所有输出和错误信息送到 nohup.out 文件中。若将输出重定向,则只 有错误信息放在 nohup.out 文件中。 2. 5 Linux 内核启动和初始化进程 2. 5. 1 引导程序 Bootloader Linux 的内核本身是不能自举的,系统上电后通过 BIOS 或者引导程序 Bootloader 加载系统内核。 在嵌入式设备中,Bootloader 是在操作系统运行之前运行的一段小程序。通过这段小程序,初始化 最基本的硬件设备并建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便 为最终调用操作系统内核准备好正确的环境。然后,将 Kernel 映像和根文件系统映像从 Flash 上读 到 RAM 空间,并为内核设置启动参数,跳转到内核映像文件在 RAM 中的地址,开始启动内核。 2. 5. 2 Kernel 引导入口 当 Bootloader 跳转到内核映像文件在 RAM 中的地址时,会运行 start_kernel 函数。Start-kernel ()是 init/main.c 中的 asmlinkage 函数,start_kernel 函数开始启动过程,这是与体系结构无关的 通用 C 代码。 2. 5. 3 核心数据结构初始化---内核引导第一部分 start_kernel()中调用了一系列初始化函数,以完成 kernel 本身的设置。 这些动作有的是 公共的,有的则是需要配置的才会执行的。 start_kernel()函数中用于初始化工作的各函数的功能如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 79 - ¾ 输出 Linux 版本信息(printk(linux_banner)); ¾ 设置与体系结构相关的环境(setup_arch()); ¾ 页表结构初始化(paging_init()); ¾ 使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口(trap_init()); ¾ 使用 alpha_mv 结构和 entry.S 入口初始化系统 IRQ(init_IRQ()); ¾ 核心进程调度器初始化(包括初始化几个缺省的 Bottom-half,sched_init()); ¾ 时间、定时器初始化(包括读取 CMOS 时钟、估测主频、初始化定时器中断等,time_init ()提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理, parse_options()); ¾ 控制台初始化(为输出信息而先于 PCI 初始化,console_init()); ¾ 剖析器数据结构初始化(prof_buffer 和 prof_len 变量); ¾ 核心 Cache 初始化(描述 Cache 信息的 Cache,kmem_cache_init()); ¾ 延迟校准(获得时钟 jiffies 与 CPU 主频 ticks 的延迟,calibrate_delay()); ¾ 内存初始化(设置内存上下界和页表项初始值,mem_init()); ¾ 创建和设置内部及通用 cache("slab_cache",kmem_cache_sizes_init()); ¾ 创建 uid taskcount SLAB cache("uid_cache",uidcache_init()); ¾ 创建文件 cache("files_cache",filescache_init()); ¾ 创建目录 cache("dentry_cache",dcache_init()); ¾ 创建与虚存相关的 cache("vm_area_struct","mm_struct",vma_init()); ¾ 块设备读写缓冲区初始化(同时创建"buffer_head"cache 用户加速访问,buffer_init()); ¾ 创建页 cache(内存页 hash 表初始化,page_cache_init()); ¾ 创建信号队列 cache("signal_queue",signals_init()); ¾ 初始化内存 inode 表(inode_init()); ¾ 创建内存文件描述符表("filp_cache",file_table_init()); ¾ 检查体系结构漏洞(对于 alpha,此函数为空,check_bugs()); ¾ SMP 机器其余 CPU(除当前引导 CPU)初始化(对于没有配置 SMP 的内核,此函数为空, smp_init()); ¾ 启动 init 过程(创建第一个核心线程,调用 init()函数,原执行序列调用 cpu_idle()等 待调度,init())。 至此 start_kernel()结束,基本的核心环境已经建立起来了。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 80 - 2. 5. 4 外设初始化---内核引导第二部分 init()函数作为核心线程,首先锁定内核(仅对 SMP 机器有效),然后调用 do_basic_setup ()完成外设及其驱动程序的加载和初始化。过程如下: ¾ 总线初始化(比如 pci_init()); ¾ 网络初始化(初始化网络数据结构,包括 sk_init()、skb_init()和 proto_init()三部分, 在 proto_init()中,将调用 protocols 结构中包含的所有协议的初始化过程,sock_init()); ¾ 创建 bdflush 核心线程(bdflush()过程常驻核心空间,由核心唤醒来清理被写过的内存 缓冲区,当 bdflush()由 kernel_thread()启动后,它将自己命名为 kflushd); ¾ 创建 kupdate 核心线程(kupdate()过程常驻核心空间,由核心按时调度执行,将内存缓 冲区中的信息更新到磁盘中,更新的内容包括超级块和 inode 表); ¾ 设置并启动核心调页线程 kswapd(为了防止 kswapd 启动时将版本信息输出到其他信息中 间,核心线程调用 kswapd_setup()设置 kswapd 运行所要求的环境,然后再创建 kswapd 核心线程); ¾ 创建事件管理核心线程(start_context_thread()函数启动 context_thread()过程,并 重命名为 keventd); ¾ 设备初始化(包括并口 parport_init()、字符设备 chr_dev_init()、块设备 blk_dev_ini ()、SCSI 设备 scsi_dev_init()、网 络 设 备 net_dev_init()、磁盘初始化及分区检查等等, device_setup()); ¾ 执行文件格式设置(binfmt_setup()); ¾ 启动任何使用__initcall 标识的函数(方便核心开发者添加启动函数,do_initcalls()); ¾ 文件系统初始化(filesystem_setup()); ¾ 安装 root 文件系统(mount_root())。 至此 do_basic_setup()函数返回 init(),在释放启动内存段(free_initmem())并给内核解 锁以后,init()打开/dev/console 设备,重定向 stdin、stdout 和 stderr 到控制台,最后,搜索文 件系统中的 init 程序(或者由 init=命令行参数指定的程序),并使用 execve()系统调用加载执行 init 程序。 init()函数到此结束,内核的引导部分也到此结束了,这个由 start_kernel()创建的第一个 线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体: ¾ start_kernel()本身所在的执行体,这其实是一个“手工”创建的线程,它在创建了 init ()线程以后就进入 cpu_idle()循环了,它不会在进程(线程)列表中出现; ¾ init 线程,由 start_kernel()创建,当前处于用户态,加载了 init 程序; ¾ kflushd 核心线程,由 init 线程创建,在核心态运行 bdflush()函数; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 81 - ¾ kupdate 核心线程,由 init 线程创建,在核心态运行 kupdate()函数; ¾ kswapd 核心线程,由 init 线程创建,在核心态运行 kswapd()函数; ¾ keventd 核心线程,由 init 线程创建,在核心态运行 context_thread()函数。 2. 5. 5 init 进程和 inittab 引导脚本 init 进程是系统所有进程的起点,内核在完成核内引导以后,即在本线程(进程)空间内加载 init 程序,它的进程号是 1。 init 程序需要读取/etc/inittab 文件作为其行为指针,inittab 是以行为单位的描述性(非执行性) 文本,每一个指令行都具有以下格式:id:runlevel:action:process。 其中 id 为入口标识符,runlevel 为运行级别,action 为动作代号,process 为具体的执行程序。 id 一般要求 4 个字符以内,对于 getty 或其他 login 程序项,要求 id 与 tty 的编号相同,否则 getty 程序将不能正常工作。 runlevel 是 init 所处于的运行级别的标识,一般使用 0-6 以及 S 或 s。0、1、6 运行级别被系 统保留,0 作为 shutdown 动作,1 作为重启至单用户模式,6 为重启;S 和 s 意义相同,表示单用 户模式,且无需 inittab 文件,因此也不在 inittab 中出现。实际上,进入单用户模式时,init 直接在 控制台(/dev/console)上运行/sbin/sulogin。 在一般的系统实现中,都使用了 2、3、4、5 几个级别,在 Redhat 系统中,2 表示无 NFS 支持 的多用户模式,3 表示完全多用户模式(也是最常用的级别),4 保留给用户自定义,5 表示 XDM 图 形登录方式。7-9 级别也是可以使用的,传统的 Unix 系统没有定义这几个级别。runlevel 可以是并 列的多个值,以匹配多个运行级别,对大多数 action 来说,仅当 runlevel 与当前运行级别匹配成功 才会执行。 initdefault 是一个特殊的 action 值,用于标识缺省的启动级别;当 init 由核心激活以后,它将读 取 inittab 中的 initdefault 项,取得其中的 runlevel,并作为当前的运行级别。如果没有 inittab 文件, 或者其中没有 initdefault 项,init 将在控制台上请求输入 runlevel。 sysinit、boot、bootwait 等 action 将在系统启动时无条件运行,而忽略其中的 runlevel,其余的 action(不含 initdefault)都与某个 runlevel 相关。各个 action 的定义在 inittab 的 man 手册中有详 细的描述。 2. 5. 6 rc 启动脚本 上一节已经提到 init 进程将启动运行 rc 脚本,这一节将介绍 rc 脚本具体的工作。一般情况下, rc 启动脚本都位于/etc/rc.d 目录下,rc.sysinit 中最常见的动作就是激活交换分区,检查磁盘,加载 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 82 - 硬件模块,这些动作无论哪个运行级别都是需要优先执行的。仅当 rc.sysinit 执行完以后 init 才会执 行其他的 boot 或 bootwait 动作。 如果没有其他 boot、bootwait 动作,在运行级别 3 下,/etc/rc.d/rc 将会得到执行,命令行参数 为 3,即执行/etc/rc.d/rc3.d/目录下的所有文件。rc3.d 下的文件都是指向/etc/rc.d/init.d/目录下各 个 Shell 脚本的符号连接,而这些脚本一般能接受 start、stop、restart、status 等参数。rc 脚本以 start 参数启动所有以 S 开头的脚本,在此之前,如果相应的脚本也存在 K 打头的链接,而且已经处 于运行态了(以/var/lock/subsys/下的文件作为标志),则将首先启动 K 开头的脚本,以 stop 作为参 数停止这些已经启动了的服务,然后再重新运行。显然,这样做的直接目的就是当 init 改变运行级 别时,所有相关的服务都将重启,即使是同一个级别。 rc 程序执行完毕后,系统环境已经设置好了,下面就该用户登录系统了。 2. 5. 7 getty 和 login rc 执行完毕后,返回 init,这时基本系统环境已经配置好。各种守护进程也已经启动。接下来 init 会打开 6 个终端,以便用户登录系统。通过按 ALT+Fn(n 对应 1~6),也可以在 6 个终端中切换。 在 initt 中,以下 6 行定义 6 个终端: ¾ 2345:respawn:/sbin/getty-n 115200,tty1; ¾ 2345:respawn:/sbin/getty-n 115200,tty2; ¾ 2345:respawn:/sbin/getty-n 115200,tty3; ¾ 2345:respawn:/sbin/getty-n 115200,tty4; ¾ 2345:respawn:/sbin/getty-n 115200,tty5; ¾ 2345:respawn:/sbin/getty-n 115200,/dev/tts/0。 从上面可以看出,在 2、3、4、5 的运行级别中都将以 respawn 方式运行 mingetty 程序,mingetty 程序能打开终端,设置模式。同时它会显示一个文本登录界面,这个界面就是经常看到的登录界面, 在这个登录界面中会提示用户名,而用户输入的用户名将作为参数传给 login 程序来验证用户的身 份。缺省的登录提示纪录在/etc/issue 文件中,但每次启动,一般都会由 rc.local 脚本根据系统环境 重新生成,用于远程登录的提示信息位于 etc/issue.net。 login 程序在 getty 的同一个进程空间中运行,接受 getty 传来的用户名参数作为登录的用户名。 如果用户名不是 root,且存在 etc/nologin 文件,则 login 将输出 nologin 文件的内容,然后退出, 这通常用来系统维护时防止非 root 用户登录。只有在/etc/securetty 中登记了的终端才允许 root 登 录,如果不存在这个文件,则 root 可以在任何终端上登录。/etc/usertty 文件用于对用户做出附加访 问限制,如果不存在这个文件,则没有其他限制。 当用户登录通过了这些检查后,login 将搜索/etc/passwd 文件(必要时搜索 /etc/shadow 文件) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 83 - 用于匹配密码、设置主目录和加载 shell。如果没有指定主目录,将默认为根目录;如果没有指定 shell, 将默认为/bin/sh。在将控制转交给 shell 以前,getty 将输出/var/log/lastlog 中记录的上次登录系统 的信息,然后检查用户是否有新邮件(/usr/spool/mail/{username})。在设置好 shell 的 uid、gid, 以及 TERM,PATH 等环境变量以后,进程加载 shell,login 的任务也就完成了。 2. 5. 8 bash 运行级别 3 下的用户 login 以后,将启动一个用户指定的 shell,以下以/bin/bash 为例继续我们 的启动过程。 bash 是 Bourne Shell 的 GNU 扩展,除了继承了 sh 的所有特点以外,还增加了很多特性和功能。 由 login 启动的 bash 是作为一个登录 shell 启动的,它继承了 getty 设置的 TERM、PATH 等环境变 量,其中 PATH 对于普通用户为"/bin:/usr/bin:/usr/local/bin" ,对于 root 为 "/sbin:/bin:/usr/sbin:/usr/bin"。作为登录 shell,它将首先寻找/etc/profile 脚本文件,并执行它;然 后如果存在~/.bash_profile,则执行它,否则执行~/.bash_login,如果该文件也不存在,则执行 ~/.profile 文件。然后 bash 将作为一个交互式 shell 执行~/.bashrc 文件(如果存在的话),很多系统 中,~/.bashrc 都将启动/etc/bashrc 作为系统范围内的配置文件。 当显示出命令行提示符的时候,整个启动过程就结束了。此时的系统,运行着内核,运行着几 个核心线程,运行着 init 进程,运行着一批由 rc 启动脚本激活的守护进程(如 inetd 等),运行着一 个 bash 作为用户的命令解释器。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 84 - 第三章 嵌入式 Linux 开发基础 本章节主要介绍嵌入式 Linux 下开发的基础,包括搭建嵌入式 Linux 开发环境、常用 Linux 命令 与使用、Linux 下的 Shell、Linux 下的编辑器 vi、Linux 下的编译器 GCC、Bootloader、内核、文件 系统、Linux 驱动开发等一些基础知识。 3. 1 搭建嵌入式 Linux 开发环境 本节主要分为 3 个部分,包括常用的 Linux 发行版的介绍、Ubuntu 的安装运行和 Ubuntu 的优 化与配置的应用开发。首先介绍了当前比较流行的 Linux 的发行版本,如 Ubuntu,RedHat 等,并 对各个版本进行了比较,方便读者的理解;然后以现在最常用的发行版 Ubuntu 为例,说明了 Ubuntu 的安装与运行过程;最后,本文系统地讲解了嵌入式 Ubuntu 的优化配置,包括一些基本的软件的 配置,如通行工具 Skype,星际译王等。本节为 Linux 的基础部分,为 Linux 初学者能快速入门提供 了保证。同时,本文提供了大量的截图,使读者对嵌入式系统的发行版、安装与配置有一个全面的 了解。 3. 1. 1 常用的 Linux 发行版 也许很多人会不屑的说,Linux 不就是个操作系统么。错!Linux 不是一个操作系统,严格来讲, Linux 只是一个操作系统中的内核。内核是什么?内核建立了计算机软件与硬件之间通讯的平台,内 核提供系统服务,比如文件管理、虚拟内存、设备 I/O 等。 既然 Linux 只是一个内核。然而,一个完整的操作系统不仅仅是内核而已。所以,许多个人、 组织和企业,开发了基于 GNU/Linux 的 Linux 发行版。 linux 发行版本非常多,但是常用的 linux 发行版本就不是很多了,下文中介绍了在国内外常见 的几种 linux 发行版本。事实上对 linux 高手而言,选用那种发行版本差别并不大,但对我们新手差 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 85 - 别就比较大了,对于新手而言要求系统易安装、易使用、配置简单、上手容易且硬件兼容性要好。 如果你是新手,可以先选择适于新手使用的流行发行版本,比如 Ubuntu、Fedora、openSUSE 等, 推荐使用 Ubuntu。如果你对 linux 一窍不通,也不想费很多时间配置系统,想先使用,后面在慢慢 学,可以使用 Magic linux、Hiweed linux 等已经配置好中文环境的 linux 系统,当然也可以使用上手 容易的类 windows 的红旗、新华等的 linux 操作系统,不过不推荐你用这些类 windows 的系统,虽 然对 windows 用户而言比较容易上手。至于 Gentoo、Arch 等 linux 系统运行速度虽然快,不过入门 较难,等你对 linux 比较熟练后,可尝试使用这些操作系统。你也可以使用 Live cd 直接在电脑上运 行 linux 而不安装,或者把 linux 装在 U 盘或移动硬盘中(这一点是 windows 做不到的)。 1.Ubuntu Ubuntu 就是一个拥有 Debian 所有的优点,以及自己所加强的优点的近乎完美的 Linux 操作系 统。Ubuntu 是一个相对较新的发行版,它的出现可能改变了许多潜在用户对 Linux 的看法。也许, 从前人们会认为 Linux 难以安装、难以使用,但是,Ubuntu 出现后,这些都成为了历史。Ubuntu 基于 Debian Sid,所以这也就是上文所说的,Ubuntu 拥有 Debian 的所有优点,包括 apt-get。然而, 不仅如此而已,Ubuntu 默认采用的 GNOME 桌面系统也将 Ubuntu 的界面装饰的简易而不失华丽。 当然,如果你是一个 KDE 的拥护者的话,Kubuntu 同样适合你! Ubuntu 的安装非常的人性化,只要按照提示一步一步进行,安装和 Windows 同样简便!并且, Ubuntu 被誉为对硬件支持最好最全面的 Linux 发行版本之一,许多在其他发行版本上无法使用,或 者默认配置时无法使用的硬件,在 Ubuntu 上轻松搞定。并且,Ubuntu 采用自行加强的内核(kernel), 安全性方面更上一层楼。并且,Ubuntu 默认不能直接 root 登陆,必须从第一个创建的用户通过 su 或 sudo 来获取 root 权限 (这也许不太方便,但无疑增加了安全性,避免用户由于粗心而损坏系统)。 Ubuntu 的版本周期为六个月,弥补了 Debian 更新缓慢的不足。而且具有人气颇高的论坛提供优秀 的资源和技术支持,固定的版本更新周期和技术支持,适于新手使用,是当前最流行的发行版之一。 Ubuntu 的版本周期为六个月,弥补了 Debian 更新缓慢的不足,推荐使用。 ¾ 优点:人气颇高的论坛提供优秀的资源和技术支持,固定的版本更新周期和技术支持,可 从 Debian Woody 直接升级; ¾ 缺点:还未建立成熟的商业模式; ¾ 软件包管理系统:APT(DEB); ¾ 免费下载:是; ¾ 官方主页:http://www.ubuntulinux.org/。 2.Suse SUSE 是德国最著名的 Linux 发行版,在全世界范围中也享有较高的声誉。SUSE 自主开发的软 件包管理系统 YaST 也大受好评。SUSE 于 2003 年年末被 Novell 收购。SUSE 是一个非常专业、优 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 86 - 秀的发行版,一向以华丽的用户界面著称。使用方便,也是当前最流行的版本之一,适于新手使用。 ¾ 优点:专业,易用的 YaST 软件包管理系统; ¾ 缺点:FTP 发布通常要比零售版晚 1~3 个月; ¾ 软件包管理系统:YaST(RPM), 第三方 APT(RPM)软件库(repository); ¾ 免费下载:取决于版本; ¾ 官方主页:http://www.suse.com/。 3.Redhat/Fedora 国内,乃至是全世界的 Linux 用户所最熟悉、最耳闻能详的发行版想必就是 Red Hat 了。Red Hat 最早由 Bob Young 和 Marc Ewing 在 1995 年创建。而公司在最近才开始真正步入盈利时代,归功于 收费的 Red Hat Enterprise Linux(RHEL,Red Hat 的企业版)。而正统的 Red Hat 版本早已停止技 术支持,最后一版是 Red Hat 9.0。于是,目前 Red Hat 分为两个系列:由 Red Hat 公司提供收费技 术支持和更新的 Red Hat Enterprise Linux,以及由社区开发的免费的 Fedora Core。Fedora Core 1 发布于 2003 年年末,而 FC 的定位便是桌面用户。 适用于服务器的版本是 Red Hat Enterprise Linux,而由于这是个收费的操作系统。于是,国内 外许多企业或空间商选择 CentOS。CentOS 可以算是 RHEL 的克隆版,但它最大的好处是免费! ¾ 优点:拥有数量庞大的用户,优秀的社区技术支持,许多创新; ¾ 缺点:免费版(Fedora Core)版本生命周期太短,多媒体支持不佳; ¾ 软件包管理系统:up2date(RPM),YUM(RPM); ¾ 免费下载:Fedora 免费、RHEL 商业; ¾ 官方主页:http://www.redhat.com/。 4.Debian Debian 最早由 Ian Murdock 于 1993 年创建。Debian 是迄今为止,最遵循 GNU 规范的 Linux 系统。Debian 系统分为三个版本分支(branch): stable,testing 和 unstable。截 至 2005 年 5 月, 这三个版本分支分别对应的具体版本为:Woody,Sarge 和 Sid。其中,unstable 为最新的测试版 本,其中包括最新的软件包,但是也有相对较多的 bug,适合桌面用户。testing 的版本都经过 unstable 中的测试,相对较为稳定,也支持了不少新技术(比如 SMP 等)。而 Woody 一般只用于服务器,上 面的软件包大部分都比较过时,但是稳定 和安全性都非常的高。dpkg 是 Debian 系列特有的软件包 管理工具,它被誉为所有 Linux 软件包管理工具(比如 RPM)最强大的!配合 apt-get,在 Debian 上安装、升级、删除和管理软件变得异常容易。Debian 具有优秀的网络和社区资源,强大的 apt- get, 许多发行版都是基于 Debian 的,最有影响力的发行版之一。不过 Debian 安装相对不易,stable 分 支的软件极度过时,不适于新手使用。 ¾ 优点:遵循 GNU 规范,100%免费,优秀的网络和社区资源,强大的 apt-get; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 87 - ¾ 缺点:安装相对不易,stable 分支的软件极度过时; ¾ 软件包管理系统:APT(DEB); ¾ 免费下载:是; ¾ 官方主页:http://www.debian.org/。 5.Mandrake Mandriva 原名 Mandrake,发布于 1998 年 7 月。最早 Mandrake 的开发者是基于 Redhat 进行 开发的。Redhat 默认采用 GNOME 桌面系统,而 Mandrake 将之改为 KDE。而由于当时的 Linux 普 遍比较难安装,不适合第一次接触 Linux 的新手,所以 Mandrake 还简化了安装系统。同时 Mandrake 在易用性方面下了不少功夫,包括默认情况下的硬件检测等。并且具有友好的操作界面,图形配置 工具,庞大的社区技术支持,是国际上比较有影响力的版本之一。 ¾ 优点:友好的操作界面,图形配置工具,庞大的社区技术支持,NTFS 分区大小变更; ¾ 缺点:部分版本 bug 较多,最新版本只先发布给 Mandrake 俱乐部的成员; ¾ 软件包管理系统:urpmi(RPM); ¾ 免费下载:FTP 即时发布下载,ISO 在版本发布后数星期内提供; ¾ 官方主页:http://www.mandrivalinux.com/。 6.PCLinuxOS PCLinuxOS 是一份纯英文的自启动运行光盘,它最初基于 Mandrake Linux。PCLinuxOS 秉承“简 单易用,安全无忧”的理念,完全从一张可启动光盘运行。光盘上的数据实时地解压缩,从而使得 这一张光盘上集成的应用程序多达 2G,这包括一份完整的 X 服务器,KDE 桌面,OpenOffice.org 以及更多即刻可用的程序。除了作为自启动运行光盘来使用,你也可以通过一套易于使用的光盘安 装程序将 PCLinuxOS 安装到硬盘。额外的应用程序可以通过一份友好的 apt-get 前端 Synaptic 来添 加或删除。 ¾ 优点:无需安装可直接运行于 CD 上,集成的程序丰富; ¾ 缺点:LiveCD 由于光盘的数据读取速度限制导致性能大幅下降; ¾ 软件包管理系统:APT(DEB); ¾ 免费下载:是; ¾ 官方主页:http://www.pclinuxos.com/。 7.MEPIS MEPIS 由 Warren Woodford 在 2003 年建立。MEPIS 虽然刚建立不久,但是迅速的传播在 Linux 用户间。简单来说,MEPIS 是一个集合了 Debian Sid 和 Knoppix 的产物。用户即能将之当作 Live CD 使用,也能使用常规的图形界面进行安装。 MEPIS 默认集成安装了 Java Runtime Environment、Flash 插件、nVidia 加速驱动等许多常用的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 88 - 程序。用户可以非常轻松的安装完系统后就直接开始使用,而不用到处寻找资料如何下载、如何安 装、如何配置这些软件。这不仅给 Linux 新手带来了便捷,也给老手们节约了相当多的时间。 ¾ 优点:LiveCD 与常规安装两用,优秀的硬件检测能力,预装了许多实用的软件; ¾ 缺点:建立时间不长,默认的界面有些寒酸; ¾ 软件包管理系统:APT(DEB); ¾ 免费下载:是; ¾ 官方主页:http://www.mepis.org/。 8.Knoppix 由德国的 Klaus Knopper 开发的 Knoppix,是一个基于 Debian 的发行版本。严格算起来,Knoppix 是一款 LiveCD Linux,所谓的 Live CD 就是整个操作系统都在一张光盘上,只要开机从光盘启动, 就能拥有一个完整的 Linux 系统!无需安装!当然,Knoppix 也能够非常轻松的安装到硬盘上。其强 大的硬件检测能力、系统修复能力、即时压缩传输技术,都令人大加称赞。由于无需安装可直接运 行于 CD 上,加上其优秀的硬件检测能力,使它可作为系统急救盘使用,可以说,在 Live CD 界, Knoppix 是无人能及的! ¾ 优点:无需安装可直接运行于 CD 上,优秀的硬件检测能力,可作为系统急救盘使用; ¾ 缺点:LiveCD 由于光盘的数据读取速度限制导致性能大幅下降; ¾ 软件包管理系统:APT(DEB); ¾ 免费下载:是; ¾ 官方主页:http://www.knoppix.com/。 9.Slackware Slackware 由 Patrick Volkerding 创建于 1992 年,是一个历史悠久的 Linux 发行版本。其他主 流发行版强调易用性的时候,他依然固执的追求最原始的效率——所有的配置均还是要通过配置文 件来进行。Slackware 稳定、安全,所以有大批的忠实用户。由于尽量采用原版的软件包而不进行任 何修改,所以制造新 bug 的几率比较低。Slackware 的版本更新周期较长。系统非常稳定、安全, 并且高度坚持 UNIX 的规范;不过由于所有的配置均通过编辑文件来进行,只适用于对 linux 非常熟 悉的用户,而且自动硬件检测能力较差,不适于新手使用。 ¾ 优点:非常稳定、安全,高度坚持 UNIX 的规范; ¾ 缺点:所有的配置均通过编辑文件来进行,自动硬件检测能力较差; ¾ 软件包管理系统:Slackware Package Management(TGZ); ¾ 免费下载:是; ¾ 官方主页:http://www.slackware.com/。 10.Gentoo 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 89 - Gentoo 最初由 Daniel Robbins(前 Stampede Linux 和 FreeBSD 的开发者之一)创建。Gentoo 具有高度的自定制性:因为它是一个基于源代码的(source-based)发行版本。尽管安装时可以选 择预先编译好的软件包,但是大部分使用 Gentoo 的用户都选择自己手动编译。所以 Gentoo 适合比 较有 Linux 使用经验的老手使用。但是注意的是,由于编译软件需要消耗大量的时间,所以如果你 所有的软件都自己编译,并安装桌面系统则需要较长的时间。GENTOO 是一个极有特色的发行版本, 优点是具有高度的可定制性,完整的使用手册,媲美 Ports 的 Portage 系统,运行速度极快,适合 “臭美”的高手使用。也是现在比较流行的发行版之一。不过缺点也明显:编译时耗时很多,安装 缓慢,只适用于熟悉 Linux 的用户,不适于新手使用。 ¾ 优点:高度的可定制性,完整的使用手册,媲美 Ports 的 Portage 系统,适合“臭美”的高 手使用; ¾ 缺点:编译耗时多,安装缓慢; ¾ 软件包管理系统:Portage(SRC); ¾ 免费下载:是; ¾ 官方主页:http://www.gentoo.org/。 3. 1. 2 Ubuntu 的安装与运行 在 Windows XP 下面安装 Ubuntu-8.04 有很多种方法,如用虚拟机安装、从硬盘安装,还有用 Wubi 安装等,其中用 Wubi 安装是最安全,也是最简单的一种方法。 Wubi 是 Windows based Ubuntu Installer 缩写,是一个专门针对 Windows 用户的 Ubuntu 安 装工具。如果你想尝试一下但是却害怕复杂的操作或者影响你娇贵的 Windows 的话,Wubi 很适合 你。如同 Live CD 一样,Wubi 不会给你的 Xp 带来任何改变,但是不同的是它提供完整的硬件接入, 你还可以如同 Ubuntu 中下载、安装和使用应用程序。Wubi 会把大部分文件储存在 Windows 下的 一个文件夹内,你可以随时卸载它们。 下面将具体介绍使用 Wubi 安装 Ubuntu-8.04 的过程: 1.安装步骤: 1)将出厂光盘 DISK8_Ubuntu 内的文件 ubuntu-8.04.1-desktop-i386.iso 拷贝到本地磁盘,使 用虚拟光驱加载该文件,在虚拟的盘符中双击文件 umenu.exe,弹出文件安装界面,选择第二个选 项卡(在 Windows 中安装),使用 wubi 直接在 Windows 下安装 Ubuntu。 附:用户也可以自行从网上下载,可以根据所在地区选择速度最快的服务器下载,推荐下载地 址:http://tw.releases.ubuntu.com/8.04/ubuntu-8.04.1-desktop-i386.iso。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 90 - 图 3-1-1 Ubuntu 安装界面 2)接下来出现如下图 3-1-2 所示的界面,在安装驱动器栏选择 Ubuntu 需要安装的分区(建议 大于 17G),安装程序会自己计算相应所需的空间(修改安装大小,建议为 15G),同时在语言栏选 择语言为“Chinese(Simplify)”。并且填入自己的基本信息,包括用户名和用户密码等。 图 3-1-2 用户选择信息 3)当填写完图 3-1-2 中的信息以后,点击“安装”进行安装,出现如下图 3-1-3 所示的安装界 面,检查安装文件,并拷贝主要文件备份到硬盘。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 91 - 图 3-1-3 Ubuntu 在 Windows 下的安装进度条 4)大约 5 分钟左右,出现如下图 3-1-4 所示的界面,要求进行重启进行下一步安装,点击“完 成”重启电脑。 图 3-1-4 完成安装向导 5)电脑在再次启动后,出现启动选择菜单,此处我们选择“Ubuntu”,利用键盘的上下键可以 进行选择,回车后进入到 Ubuntu 系统。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 92 - 图 3-1-5 开机选项 6)接下来将会进入 Ubuntu 的开机进度条,如图 3-1-6 所示: 图 3-1-6 Ubuntu 开机进度条 8)大约 2-5 分钟左右系统启动,并真正进入到系统的安装界面,如图 3-1-7 所示的 Ubuntu 的 安装进度条。这个过程大约会花费 20-40 分钟左右的时间。 附:在安装过程中,当进度条显示装到 82%左右的时候可能会假死。此时是安装程序正在网上 下载相应的安装包资源,由于资源很多,这会消耗大量的时间。然后造成了安装程序“不动”的假 象。用户可以在此时断开网线,手动停止下载。这些需要的安装包可以在系统装完了之后再进行下 载。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 93 - 图 3-1-7 Ubuntu 的安装界面 8)安装结束之后,系统会自动重启。再次开机在启动菜单中选择 ubuntu 进入,在登陆界面出 现后输入你预设的用户名和密码后,将出现 Ubuntu 的启动界面,如图 3-1-8 所示。 图 3-1-8 Ubuntu 的界面 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 94 - 3. 1. 3 嵌入式环境的配置与源码的安装 刚装好的 Ubuntu8.04 还不适合进行嵌入式开发,必须要安装必备的软件资源包,本节将介绍如 何建立自己的 Ubuntu 嵌入式环境。 1.嵌入式环境的配置 一般来说 Ubuntu 是离不开网络的,它提供了比较便利的升级模式,从 debian 继承来的更新源 系统,让 Ubuntu 下安装及升级软件包变得很容易。用户只要设定好了网络,并选择合适的更新源 将可以很好的更新自己的系统或者安装自己所需要的软件。 一般对于国内的用户,以下更新源速度比较快: # Mirror.lupaworld.com更新服务器(浙江省杭州市双线服务器): deb http://mirror.lupaworld.com/ubuntu hardy main restricted universe multiverse deb http://mirror.lupaworld.com/ubuntu hardy -security main restricted universe multiverse deb http://mirror.lupaworld.com/ubuntu hardy -updates main restricted universe multiverse deb http://mirror.lupaworld.com/ubuntu hardy -backports main restricted universe multiverse deb http://mirror.lupaworld.com/ubuntu hardy -proposed main restricted universe multiverse deb-src http://mirror.lupaworld.com/ubuntu hardy main restricted universe multiverse deb-src http://mirror.lupaworld.com/ubuntu hardy-security main restricted universe multiverse deb-src http://mirror.lupaworld.com/ubuntu hardy-updates main restricted universe multiverse deb-src http://mirror.lupaworld.com/ubuntu hardy-backports main restricted universe multiverse deb-src http://mirror.lupaworld.com/ubuntu hardy-proposed main restricted universe multiverse # 厦门大学更新服务器(教育网服务器): deb ftp://ubuntu.realss.cn/ubuntu/ hardy main restricted universe multiverse deb ftp://ubuntu.realss.cn/ubuntu/ hardy-backports restricted universe multiverse deb ftp://ubuntu.realss.cn/ubuntu/ hardy-proposed main restricted universe multiverse deb ftp://ubuntu.realss.cn/ubuntu/ hardy-security main restricted universe multiverse deb ftp://ubuntu.realss.cn/ubuntu/ hardy-updates main restricted universe multiverse deb-src ftp://ubuntu.realss.cn/ubuntu/ hardy main restricted universe multiverse deb-src ftp://ubuntu.realss.cn/ubuntu/ hardy-backports main restricted universe multiverse deb-src ftp://ubuntu.realss.cn/ubuntu/ hardy-proposed main restricted universe multiverse deb-src ftp://ubuntu.realss.cn/ubuntu/ hardy-security main restricted universe multiverse deb-src ftp://ubuntu.realss.cn/ubuntu/ hardy-updates main restricted universe multiverse # 成都市 电子科技大学更新服务器(教育网,推荐校园网和网通用户使用): deb http://ubuntu.uestc.edu.cn/ubuntu/ hardy main multiverse restricted universe deb http://ubuntu.uestc.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb http://ubuntu.uestc.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 95 - deb http://ubuntu.uestc.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb http://ubuntu.uestc.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe deb-src http://ubuntu.uestc.edu.cn/ubuntu/ hardy main multiverse restricted universe deb-src http://ubuntu.uestc.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb-src http://ubuntu.uestc.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe deb-src http://ubuntu.uestc.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb-src http://ubuntu.uestc.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe # 上海市上海交通大学更新服务器(教育网,推荐校园网和网通用户使用): deb http://ftp.sjtu.edu.cn/ubuntu/ hardy main multiverse restricted universe deb http://ftp.sjtu.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb http://ftp.sjtu.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe deb http://ftp.sjtu.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb http://ftp.sjtu.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe deb-src http://ftp.sjtu.edu.cn/ubuntu/ hardy main multiverse restricted universe deb-src http://ftp.sjtu.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb-src http://ftp.sjtu.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe deb-src http://ftp.sjtu.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb-src http://ftp.sjtu.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe # 中国科学技术大学更新服务器(教育网,推荐校园网和网通用户使用): deb http://debian.ustc.edu.cn/ubuntu/ hardy main multiverse restricted universe deb http://debian.ustc.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb http://debian.ustc.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe deb http://debian.ustc.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb http://debian.ustc.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe deb-src http://debian.ustc.edu.cn/ubuntu/ hardy main multiverse restricted universe deb-src http://debian.ustc.edu.cn/ubuntu/ hardy-backports main multiverse restricted universe deb-src http://debian.ustc.edu.cn/ubuntu/ hardy-proposed main multiverse restricted universe deb-src http://debian.ustc.edu.cn/ubuntu/ hardy-security main multiverse restricted universe deb-src http://debian.ustc.edu.cn/ubuntu/ hardy-updates main multiverse restricted universe 用户可以修改/etc/apt/sources.list 文件,替换为速度最快的源。 考虑到教学需要,我们提供了针对 EduKit-IV 嵌入式实验平台的嵌入式环境离线升级包,在出厂 光盘 DISK9_Ubuntu-VAL 内,实验室用户可以考虑采用离线升级。 以下步骤将开始配置嵌入式环境: 1)打开 root 用户登陆窗口权限,在 Ubuntu 的标题栏选择“System -> 系统管理 -> 登陆窗 口”,弹出输入密码提示框,输入完密码后,弹出“Login Window Preferences”窗体,选择“Securtity” 选项卡,在 Security 选项选中“Allow local system administrator login”,如图 3-1-9 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 96 - 图 3-1-9 允许 root 用户登陆 2)设置 root 用户密码,在 Ubuntu 的标题栏选择“Applications -> 附件 -> 终端”运行终端, 在终端的命令行输入以下命令设置 root 密码: $ sudo passwd root 附:本教材默认 Ubuntu 终端提示符为“$“,minicom 串口终端提示符为“#”。 输入命令后,提示输入当前用户的密码,输入完成后,将提示输入 root 的新密码,并要求输入 两次,设置完成后将提示设置成功。 3)准备嵌入式环境离线升级包,在终端输入命令建立目录 eduk4-pack 用于存放资源包: $ mkdir -p ~/eduk4-pack 拷贝出厂光盘 DISK9_Ubuntu-VAL 里面的内容到~/eduk4-pack 文件夹下。 4)准备好嵌入式环境离线升级包后,在终端输入命令开始建立本地的更新源并初步更新系统: $ cd ~/eduk4-pack/update $ su $ sudo sh E-pack-install-1.sh 附:su 为切换当前用户为 root 用户,执行命令后会提示输入密码。 其中 E-pack-install-1.sh 脚本主要是设置本地更新源,并开始更新系统,具体如下: #!/bin/bash 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 97 - # # E-pack-install.sh - create Ubuntu Embedded development environment, Please use root user. # # Copyright (C) 2002-2007 # Created. lusi # 更新服务器列表 sudo mv /etc/apt/sources.list /etc/apt/sources.list_backup sudo echo "deb file:///usr/local/update hardy main" >/etc/apt/sources.list # 离线升级 sudo tar xvf update.tar -C /usr/local/ sudo apt-get -y --force-yes update sudo apt-get -y --force-yes dist-upgrade echo "Install the E-pack susscess! ^_^ " 安装完成后,终端会提示安装成功的提示,重启系统。 5)再次启动系统后,开始安装中文语言包支持,在 Ubuntu 的标题栏选择“System -> 系统管 理 -> Language Support”,弹出“Language Support”窗口,选中“Supported Languages”中的 “汉语”,然后点击“OK”按钮: 图 3-1-10 选择安装汉语语言包 接下来弹出窗口提示确认输入用户密码,输入完成后,再接下来弹出的窗口提示是否安装这些 文件包,选择“Apply”,系统将开始安装中文语言包: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 98 - 图 3-1-11 提示应用改变 图 3-1-12 下载语言包 图 3-1-13 安装语言包 安装结束将提示汉语语言包安装成功,关闭后,提示要求重启系统,关闭窗口,并重新启动 Ubuntu。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 99 - 图 3-1-14 安装成功 图 3-1-15 提示重启系统 6)再次启动系统后,开始安装嵌入式环境必备软件资源包,在 Ubuntu 的标题栏选择“应用程 序 -> 附件 -> 终端”运行终端,在终端的命令行输入以下命令开始安装嵌入式环境资源包: $ cd ~/eduk4-pack/update $ su $ sudo sh E-pack-install-2.sh 其中 E-pack-install-2.sh 脚本主要是用于安装嵌入式环境必备资源包,并安装了一些常用软件如 ksnapshot、kscope、minicom、tftp、rar、stardict 等。安装完成后,终端会提示安装成功的提示, 重启系统。 2.安装其他应用软件 在在出厂光盘 DISK9_Ubuntu-VAL 内还提供了其他的一些软件包,用户可以自行根据自己的需 要安装。 3.常用软件的配置 z Linux 终端的配置: 1)配置 linux 的终端,在 Ubuntu 的标题栏选择“应用程序 -> 附件 -> 终端”运行终端,在 终端的菜单栏选择“编辑 -> 当前配置文件”,弹出配置对话框: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 100 - 图 3-1-16 配置终端 2)在常规选项卡中去掉“使用系统的等宽字体”的勾,根据自己的喜好选择合适的字体,比如: 字体为 Bitstream Vera Sans Mono、大小为 10。 图 3-1-17 配置终端字体 确定后退出,用户还可以根据自己的喜好设置终端的其他属性。 z 串口终端 Minicom 的设置: 在 Ubuntu 的标题栏选择“应用程序 -> 附件 -> 终端”运行终端,输入命令建立 Minicom 配 置文件 minirc.dfl。 $ sudo gedit /etc/minicom/minirc.dfl 在弹出的 minirc.dfl 文件中编辑如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 101 - # Machine-generated file - use "minicom -s" to change parameters. pr port /dev/ttyS0 pu baudrate 115200 pu bits 8 pu parity N pu stopbits 1 pu minit pu mreset pu rtscts No 其中 pu port 的选项根据用户电脑的实际情况设置,比如笔记本使用的 USB 转串口的适配器, 则为/dev/ttyUSB0。 4.源码的安装 基于 EduKit-IV 嵌入式系统平台上 Mini2410-IV 的 Linux 实验源码将在出厂光盘 DISK3_S3C2410 中提供(DISK3_S3C2410\03-Codes\02-Linux\E-bsp.tar.bz2),下面的步骤将指导安装该实验源码资 源包到 Ubuntu 中的相应位置。 1)建立源码资源包存放目录,在 Ubuntu 的标题栏选择“应用程序 -> 附件 -> 终端”运行终 端,在终端的命令行输入以下命令建立目录: $ mkdir -p ~/eduk4-pack/E-pack 将出厂光盘 DISK3_S3C2410 中提供的文件 E-bsp.tar.bz2 和 E-bsp_install.sh (DISK3_S3C2410\03-Codes\02-Linux)拷贝到 E-pack 目录下,在终端中执行以下命令来安装实验 源码包: $ cd ~/eduk4-pack/E-pack $ sudo sh E-bsp_install.sh 安装完成将提示“End.”。 以下是实验源码的默认路径: WORKDIR="/usr/local/src/EduKit-IV" // EduKit-IV平台的工作路径 EXPDIR="/home/example" // 实验生成映像存放路径 TFTPDIR="$EXPDIR/tftp" // tftp服务共享目录 NFSDIR="$EXPDIR/nfs" // nfs服务共享目录 KERNELDIR="$WORKDIR/Mini2410/bsp/linux-2.6.14" // 实验内核包路径 VIVIDIR="$WORKDIR/Mini2410/bsp/vivi-0.1.4" // vivi实验源码路径 ROOTBASEDIR="$WORKDIR/Mini2410/bsp/rootfs-eduk4-base" // 基础根文件系统路径 ROOTTSPDIR="$WORKDIR/Mini2410/bsp/rootfs-eduk4-tsp" // 带TSP的QT文件系统路径 ROOTMOUSEDIR="$WORKDIR/Mini2410/bsp/rootfs-eduk4-mouse" // 带mouse的QT文件系统路径 SIMPLEDIR="$WORKDIR/Mini2410/simple" // 实验例程路径 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 102 - 3. 2 Linux 准备知识 Linux 的系统管理主要在控制终端下进行,通过使用命令行的方式进行管理。Linux 的文件命令 可以完成各种复杂的工作,例如对目录进行复制、移动和链接,搜索和查找文件和目录,阅读、显 示或打印文件内容等操作。Linux 操作系统提供的命令很多,但用户日常使用的命令却很有限。本节 将介绍一些在日常工作中最常用的 Linux 命令。 3. 2. 1 常用 Linux 命令与使用 z 查询当前目录文件列表:ls ls 命令默认状态下将按首字母升序列出你当前文件夹下面的所有内容,但这样直接运行所得到 的信息也是比较少的,通常它可以结合以下这些参数运行以查询更多的信息: ls / 显示/.下的所有文件和目录 ; ls -l 给出文件或者文件夹的详细信息; ls -a 显示所有文件,包括隐藏文件; ls -h 以 KB/MB/GB 方式给出文件大小,而不仅仅是字节。 z 查询当前所在目录:pwd 在 Linux 层次目录结构中,用户可以在被授权的任意目录下利用 mkdir 命令创建新目录,也可 以利用 cd 命令从一个目录转换到另一个目录。然而,没有提示符来告知用户目前处于哪一个目录中。 要想知道当前所处的目录,可以使用 pwd 命令,该命令显示整个路径名。 语法:pwd; 说明:此命令显示出当前工作目录的绝对路径。 z 进入其他目录:cd 功能:改变工作目录; 语法:cd [directory]; 说明:该命令将当前目录改变至 directory 所指定的目录。若没有指定 directory,则回到用户的 主目录。为了改变到指定目录,用户必须拥有对指定目录的执行和读权限。 例如: $ cd /root/ $ pwd /root z 在屏幕上输出字符:echo 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 103 - 例如: $ echo “hello” Hello z 显示文件内容:cat 例如: $ cat temp text test temp z 复制文件:cp 该命令的功能是将给出的文件或目录拷贝到另一文件或目录中,同 MSDOS 下的 copy 命令一样, 功能十分强大。 语法:cp [选项] 源文件或目录目标文件或目录; 说明:该命令把指定的源文件复制到目标文件或把多个源文件复制到目标目录中。 例如: $ ls Desktop Examples file2.txt new_file.txt $ cp file2.txt file2_copy.txt $ ls Desktop Examples file2_copy.txt file2.txt new_file.txt z 移动文件:mv 用户可以使用 mv 命令来为文件或目录改名或将文件由一个目录移入另一个目录中。该命令如 同 MSDOS 下的 ren 和 move 的组合。 语法:mv [选项] 源文件或目录目标文件或目录。 例如: $ ls Desktop Examples file1.txt file2.txt $ mv file1.txt new_file.txt $ ls Desktop Examples file2.txt new_file.txt z 建立一个空文本文件:touch 例如: $ ls Desktop Examples $ touch file1.txt $ ls 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 104 - Desktop Examples file1.txt z 建立一个目录:mkdir 功能:创建一个目录(类似 MSDOS 下的 md 命令); 语法:mkdir [选项] dir-name; 说明:该命令创建由 dir-name 命名的目录。要求创建目录的用户在当前目录中(dir-name 的父 目录中)具有写权限,并且 dirname 不能是当前目录中已有的目录或文件名称。 例如: $ ls Desktop Examples file2_copy.txt file2_new.txt new_file.txt $ mkdir test_dir $ ls Desktop Examples file2_copy.txt file2_new.txt new_file.txt test_dir z 删除文件/目录:rm 用户可以用 rm 命令删除不需要的文件。该命令的功能为删除一个目录中的一个或多个文件或 目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是断开了链接, 原文件保持不变。 语法:rm [选项] 文件; 说明:如果没有使用-r 选项,则 rm 不会删除目录。 例如: $ ls Desktop Examples file2_copy.txt file2_new.txt new_file.txt test_dir $ rm -r test_dir $ ls Desktop Examples file2_copy.txt file2_new.txt new_file.txt $ rm new_file.txt $ ls Desktop Examples file2_copy.txt file2_new.txt z 访问权限:chmod 确定了一个文件的访问权限后,用户可以利用 Linux 系统提供的 chmod 命令来重新设定不同的 访问权限。也可以利用 chown 命令来更改某个文件或目录的所有者。利用 chgrp 命令来更改某个文 件或目录的用户组。 chmod u+s file 为 file 的属主加上特殊权限 ; chmod g+r file 为 file 的属组加上读权限 ; chmod o+w file 为 file 的其它用户加上写权限 ; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 105 - chmod a-x file 为 file 的所有用户减去执行权限 ; chmod 765 file 为 file 的属主设为完全权限,属组设成读写权,其它用户具有读和执行权限。 z 改变所属的组:chown 命令 功能:更改某个文件或目录的属主和属组。这个命令也很常用。例如 root 用户把自己的一个文 件拷贝给用户 xu,为了让用户 xu 能够存取这个文件,root 用户应该把这个文件的属主设为 xu,否 则,用户 xu 无法存取这个文件。 语法:chown [选项] 用户或组文件。 z 修改密码:passwd 命令 出于系统安全考虑,Linux 系统中的每一个用户除了有其用户名外,还有其对应的用户口令。因 此使用 useradd 命令增加时,还需使用 passwd 命令为每一位新增加的用户设置口令;用户以后还 可以随时用 passwd 命令改变自己的口令。 该命令的使用方法如下: $ sudo passwd root New UNIX password: Retype new UNIX password: passwd: all authentication tokens updated successfully z write 命令 write 命令的功能是向系统中某一个用户发送信息。该命令的一般格式为: write 用户帐号 [终端名称]。 例如: $ write Guest hello 此时系统进入发送信息状态,用户可以输入要发送的信息,输入完毕,希望退出发送状态时, 按组合键< Ctrl+c>即可。 z mesg 指令 mesg 命令设定是否允许其他用户用 write 命令给自己发送信息。如果允许别人给自己发送信息, 输入命令:# mesg y 否则,输入:# mesg n 对于超级用户,系统的默认值为 n;而对于一般用户系统的默认值为 y。 如果 mesg 后不带任 何参数,则显示当前的状态是 y 还是 n。 z shutdown 命令 shutdown 命令可以安全地关闭或重启 Linux 系统,它在系统关闭之前给系统上的所有登录用户 提示一条警告信息。该命令还允许用户指定一个时间参数,可以是一个精确的时间,也可以是从现 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 106 - 在开始的一个时间段。精确时间的格式是 hh:mm,表示小时和分钟;时间段由“+”和分钟数表示。 系统执行该命令后,会自动进行数据同步的工作。 该命令的一般格式为:shutdown [选项] [时间] [警告信息] 命令中各选项的含义为: - k 并不真正关机,而只是发出警告信息给所有用户; - r 关机后立即重新启动; - h 关机后不重新启动; - f 快速关机,重启动时跳过 fsck; - n 快速关机,不经过 init 程序; - c 取消一个已经运行的 shutdown。 需要特别说明的是,该命令只能由超级用户使用。 z cal 命令 cal 命令的功能是显示某年某月的日历。 该命令的一般格式为:cal [选项] [月 [年]] 命令中各选项的含义为: - j 显示出给定月中的每一天是一年中的第几天(从 1 月 1 日算起); - y 显示出整年的日历。 z clear 命令 clear 命令的功能是清除屏幕上的信息,它类似于 DOS 中的 cls 命令。清屏后,提示符移动到屏 幕左上角。 z 压缩解压:tar 命令 # tar -c 创建包 –x 释放包 -v 显示命令过程 –z 代表压缩包 例如: tar –cvf benet.tar /home/benet 把/home/benet 目录打包; tar –zcvf benet.tar.gz /mnt 把目录打包并压缩; tar –tf benet.tar 看非压缩包的文件列表; tar –tf benet.tar.gz 看压缩包的文件列表; tar –xf benet.tar 非压缩包的文件恢复; tar –zxvf benet.tar.gz 压缩包的文件解压恢复。 z 查看系统命令 查看内核:uname –a 查看 Ubuntu 版本:cat /etc/issue 查看内核加载的模块:lsmod 查看 PCI 设备:lspci 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 107 - 查看 USB 设备:lsusb 查看网卡状态:sudo ethtool eth0 查看 CPU 信息:cat /proc/cpuinfo 显示当前硬件信息:lshw 查看当前的内存使用情况:free –l 查看硬盘的分区:sudo fdisk –l 查看硬盘剩余空间:df -hdf –H 查看目录占用空间:du -hs 目录名 3. 2. 2 Linux 下的编辑器 vi 1.vi 的工作模式 Vi 在初始启动后首先进入编辑模式,这时用户可以利用一些预先定义的按键来移动光标、删除 文字、复制或粘贴文字等。这些按键均是普通的字符,例如 l 是向右移动光标,相当于向右箭头键, k 是向下移动光标,相当于向下箭头键。在编辑模式下,用户还可以利用一些特殊按键选定文字, 然后再进行删除、或复制等操作。当用户在编辑模式下键入 i,a,o 等命令之后,可进入插入模式; 键入:可进入命名模式。在插入模式下,用户随后输入的,除 Esc 之外的任何字符均将被看成是插 入到编辑缓冲区中的字符。按 Esc 之后,从插入模式切换到编辑模式。在命令模式,Vi 将把光标 挪到屏幕的最下方,并在第一个字符的位置显示一个:(冒号)。这时,用户就可以键入一些命令。 这些命令可用来保存文件、读取文件内容、执行 Shell 命令、设置 Vi 参数、以正则表达式的方式查 找字符串或替换字符串等。 2.编辑模式 z 移动光标。 要对正文内容进行修改,首先必须把光标移动到指定位置。移动光标的最简单的方式是按键盘 的上、下、左、右箭头键。除了这种最原始的方法之外,用户还可以利用 vi 提供的众多字符组合 键,在正文中移动光标,迅速到达指定的行或列,实现定位。例如: k、j、h、l 功能分别等同于上、下、左、右箭头键; Ctrl+b 在文件中向上移动一页(相当于 PageUp 键); Ctrl+f 在文件中向下移动一页(相当于 PageDown 键); H 将光标移到屏幕的最上行(Highest); nH 将光标移到屏幕的第 n 行; 2H 将光标移到屏幕的第 2 行; M 将光标移到屏幕的中间(Middle); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 108 - L 将光标移到屏幕的最下行(Lowest); nL 将光标移到屏幕的倒数第 n 行; 3L 将光标移到屏幕的倒数第 3 行; W 在指定行内右移光标,到下一个字的开头; e 在指定行内右移光标,到一个字的末尾; b 在指定行内左移光标,到前一个字的开头; 0 数字 0,左移光标,到本行的开头; $ 右移光标,到本行的末尾; ^ 移动光标,到本行的第一个非空字符; z 替换和删除。 将光标定位于文件内指定位置后,可以用其他字符来替换光标所指向的字符,或从当前光标位 置删除一个或多个字符。例如: rc 用 c 替换光标所指向的当前字符; nrc 用 c 替换光标所指向的前 n 个字符; 5rc 用 c 替换光标所指向的前 5 个字符; x 删除光标所指向的当前字符; nx 删除光标所指向的前 n 个字符; 3x 删除光标所指向的前 3 个字符; dw 删除光标右侧的字; ndw 删除光标右侧的 n 个字; 3dw 删除光标右侧的 3 个字; db 删除光标左侧的字; ndb 删除光标左侧的 n 个字; 5db 删除光标左侧的 5 个字; dd 删除光标所在行,并去除空隙; ndd 删除 n 行内容,并去除空隙; 3dd 删除 3 行内容,并去除空隙; z 粘贴和复制。 从正文中删除的内容(如字符、字或行)并没有真正丢失,而是被剪切并复制到了一个内存缓 冲区中。用户可将其粘贴到正文中的指定位置。完成这一操作的命令是: 小写字母 p 将缓冲区的内容粘贴到光标的后面; 大写字母 P 将缓冲区的内容粘贴到光标的前面; 如果缓冲区的内容是字符或字,直接粘贴在光标的前面或后面;如果缓冲区的内容为整行正文, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 109 - 则粘贴在当前光标所在行的上一行或下一行。 注意上述两个命令中字母的大小写。vi 编辑器经常以一对大、小写字母(如 p 和 P)来提供 一对相似的功能。通常,小写命令在光标的后面进行操作,大写命令在光标的前面进行操作。有时 需要复制一段正文到新位置,同时保留原有位置的内容。这种情况下,首先应当把指定内容复制(而 不是剪切)到内存缓冲区。完成这一操作的命令是: yy 复制当前行到内存缓冲区; nyy 复制 n 行内容到内存缓冲区; 5yy 复制 5 行内容到内存缓冲区; z 搜索字符串。 和许多先进的编辑器一样,vi 提供了强大的字符串搜索功能。要查找文件中指定字或短语出现 的位置,可以用 vi 直接进行搜索,而不必以手工方式进行。搜索方法是:键入字符/,后面跟以要 搜索的字符串,然后按回车键。编辑程序执行正向搜索(即朝文件末尾方向),并在找到指定字符串 后,将光标停到该字符串的开头;键入 n 命令可以继续执行搜索,找出这一字符串下次出现的位置。 用字符 ? 取代 / ,可以实现反向搜索(朝文件开头方向)。例如: /str1 正向搜索字符串 str1; n 继续搜索,找出 str1 字符串下次出现的位置; ?str2 反向搜索字符串 str2; 无论搜索方向如何,当到达文件末尾或开头时,搜索工作会循环到文件的另一端并继续执行。 z 撤销和重复。 在编辑文档的过程中,为消除某个错误的编辑命令造成的后果,可以用撤消命令。另外,如果 用户希望在新的光标位置重复前面执行过的编辑命令,可用重复命令。 u 撤消前一条命令的结果; . 重复最后一条修改正文的命令; z 文本选中。 vi 可进入到一种成为 Visual 的模式,在该模式下,用户可以用光标移动命令可视地选择文本, 然后再执行其他编辑操作,例如删除、复制等。 v 字符选中命令,V 行选中命令。 3.插入模式 z 进入插入模式。 在编辑模式下正确定位光标之后,可用以下命令切换到插入模式: i 在光标左侧输入正文; a 在光标右侧输入正文; o 在光标所在行的下一行增添新行; O 在光标所在行的上一行增添新行; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 110 - I 在光标所在行的开头输入正文; A 在光标所在行的末尾输入正文; 上面介绍了几种切换到插入模式的简单方法。另外还有一些命令,它们允许在进入插入模式之 前首先删去一段正文,从而实现正文的替换。这些命令包括: s 用输入的正文替换光标所指向的字符; ns 用输入的正文替换光标右侧 n 个字符; cw 用输入的正文替换光标右侧的字; ncw 用输入的正文替换光标右侧的 n 个字; cb 用输入的正文替换光标左侧的字; ncb 用输入的正文替换光标左侧的 n 个字; cd 用输入的正文替换光标的所在行; ncd 用输入的正文替换光标下面的 n 行; c$ 用输入的正文替换从光标开始到本行末尾的所有字符; c0 用输入的正文替换从本行开头到光标的所有字符; z 退出插入模式。 退出插入模式的方法是,按 ESC 键。 4.命令模式 在 vi 的命令模式下,可以使用复杂的命令。在编辑模式下键入“:”,光标就跳到屏幕最后一行, 并在那里显示冒号,此时已进入命令模式。命令模式又称“末行模式”,用户输入的内容均显示在屏 幕的最后一行,按回车键,vi 执行命令。 5.退出命令 在编辑模式下可以用 ZZ 命令退出 vi 编辑程序,该命令保存对正文所作的修改,覆盖原始文 件。如果只需要退出编辑程序,而不打算保存编辑的内容,可用下面的命令: : q 在未作修改的情况下退出; : q! 放弃所有修改,退出编辑程序; 6.行号与文件 编辑中的每一行正文都有自己的行号,用下列命令可以移动光标到指定行: : n 将光标移到第 n 行; 命令模式下,可以规定命令操作的行号范围。数值用来指定绝对行号;字符“.”表示光标所在 行的行号; 字符“$”表示正文最后一行的行号;简单的表达式,例如“.+5”表示当前行往下的第 5行。 例如: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 111 - :345 将光标移到第 345 行; :345w file 将第 345 行写入 file 文件; :3,5w file 将第 3 行至第 5 行写入 file 文件; :1,.w file 将第 1 行至当前行写入 file 文件; :.,$w file 将当前行至最后一行写入 file 文件; :.,.+5w file 从当前行开始将 6 行内容写入 file 文件; :1,$w file 将所有内容写入 file 文件,相当于 :w file 命令; 在命令模式下,允许从文件中读取正文,或将正文写入文件。例如: :w 将编辑的内容写入原始文件,用来保存编辑的中间结果; :wq 将编辑的内容写入原始文件并退出编辑程序(相当于 ZZ 命令); :w file 将编辑的内容写入 file 文件,保持原有文件的内容不变; :a,bw file 将第 a 行至第 b 行的内容写入 file 文件; :r file 读取 file 文件的内容,插入当前光标所在行的后面; :e file 编辑新文件 file 代替原有内容; :f file 将当前文件重命名为 file; :f 打印当前文件名称和状态,如文件的行数、光标所在的行号等; 7.字符串搜索 给出一个字符串,可以通过搜索该字符串到达指定行。如果希望进行正向搜索,将待搜索的字 符串置于两个“/”之间;如果希望反向搜索,则将字符串放在两个“?”之间。例如: :/str/ 正向搜索,将光标移到下一个包含字符串 str 的行; :?str? 反向搜索,将光标移到上一个包含字符串 str 的行; :/str/w file 正向搜索,并将第一个包含字符串 str 的行写入 file 文件; :/str1/,/str2/w file 正向搜索,并将包含字符串 str1 的行至包含字符串 str2 的行写 入 file 文件; 8.正文替换 利用 :s 命令可以实现字符串的替换。具体的用法包括: :s/str1/str2/ 用字符串 str2 替换行中首次出现的字符串 str1; :s/str1/str2/g 用字符串 str2 替换行中所有出现的字符串 str1; :.,$ s/str1/str2/g 用字符串 str2 替换正文当前行到末尾所有出现的字符串 str1; :1,$ s/str1/str2/g 用字符串 str2 替换正文中所有出现的字符串 str1; 从上述替换命令可以看到:g 放在命令末尾,表示对搜索字符串的每次出现进行替换;不加 g, 表示只对搜索字符串的首次出现进行替换。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 112 - 9.删除正文 在命令模式下,同样可以删除正文中的内容。例如: :d 删除光标所在行; :3d 删除 3 行; :.,$d 删除当前行至正文的末尾; :/str1/,/str2/d 删除从字符串 str1 到 str2 的所有行; 10.恢复文件 vi 在编辑某个文件时,会另外生成一个临时文件,这个文件的名称通常以 . 开头,并以 .swp 结 尾。vi 在正常退出时,该文件被删除,若意外退出,而没有保存文件的最新修改内容,则可以使用 恢复命令: :recover 恢复文件; 也可以在启动 vi 时利用 -r 选项。 11.选项设置 为控制不同的编辑功能,vi 提供了很多内部选项。利用 :set 命令可以设置选项。基本语法为: :set option 设置选项 option; 常见的功能选项包括: autoindent 设置该选项,则正文自动缩进; ignorecase 设置该选项,则忽略规则表达式中大小写字母的区别; number 设置该选项,则显示正文行号; ruler 设置该选项,则在屏幕底部显示光标所在行、列的位置; tabstop 设置按 Tab 键跳过的空格数。例如 :set tabstop=n,n 默认值为 8; mk 将选项保存在当前目录的 .exrc 文件中; 12.shell 切换 在编辑正文时,利用 vi 命令模式下提供的 shell 切换命令,无须退出 vi 即可执行 Linux 命令, 十分方便。语法格式为: :! command 执行完 shell 命令 command 后回到 vi; 另外,在编辑模式下,键入 K ,可命令 vi 查找光标所在单词的手册页,相当于运行 man 命 令。 13.vim 和 gvim 的高级特色 Vim 代表 Vi Improved,如同其名称所暗示的那样,Vim 作为标准 UNIX 系统 vi 编辑器的提 高版而存在。Vim 除提供和 vi 编辑器一样强大的功能外,还提供有多级恢复、命令行历史以及命 令及文件名补全等功能。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 113 - gvim 是 vi 的 X Window 版本,该版本支持鼠标选中,一些高级光标移动功能,并且带有菜 单和工具按钮。 3. 2. 3 Linux 下的 Shell 1. Shell 简介 Shell 是一种具备特殊功能的程序,它是介于使用者和 UNIX/Linux 操作系统之核心程序 (kernel)间的一个接口。为什么我们说 shell 是一种介于系统核心程序与使用者间的中介者呢?读 过操作系统概论的读者们都知道操作系统是一个系统资源的管理者与分配者,当您有需求时,您得 向系统提出;从操作系统的角度来看,它也必须防止使用者因为错误的操作而造成系统的伤害?众 所周知,对计算机下命令得透过命令(command)或是程序(program);程序有编译器(compiler) 将程序转为二进制代码,可是命令呢?其实 shell 也是一支程序,它由输入设备读取命令,再将其 转为计算机可以了解的机械码,然后执行它。 各种操作系统都有它自己的 Shell,以 DOS 为例,它的 shell 就是 command.com。如 同 DOS 下有 NDOS,4DOS,DRDOS 等不同的命令解译程序可以取代标准的 command.com ,UNIX 下除 了 Bourne shell(/bin/sh)外还有 C shell(/bin/csh)、Korn shell(/bin/ksh)、Bourne again shell (/bin/bash)、Tenex C shell(tcsh)… 等其它的 shell。UNIX/Linux 将 shell 独立于核心程序之 外,使得它就如同一般的应用程序,可以在不影响操作系统本身的情况下进行修改、更新版本或是 添加新的功能。 在系统起动的时候,核心程序会被加载内存,负责管理系统的工作,直到系统关闭为止。它建 立并控制着处理程序,管理内存、档案系统、通讯等等。而其它的程序,包括 shell 程序,都存放 在磁盘中。核心程序将它们加载内存,执行它们,并且在它们中止后清理系统。 当您刚开始学 UNIX/Linux 系统时,您大部分的时间会花在在提示符号(prompt)下执行命令。 如果您经常输入一组相同形式的命令,您可能会想要自动执行那些工作。如此,您可以将一些 命令放入一个档案(称为命令档,script),然后执行该档。一个 shell 命令档很像是 DOS 下的批次 档(如 Autoexec.bat):它把一连串的 UNIX 命令存入一个档案,然后执行该档。较成熟的命令档 还支持若干现代程序语言的控制结构,譬如说能做条件判断、循环、档案测试、传送参数等。要学 着写命令档,不仅要学习程序设计的结构和技巧,而且对 UNIX/Linux 公用程序及如何运作需有深 入的了解。有些公用程序的功能非常强大(例如 grep、sed 和 awk),它们常被用于命令档来操控 命令输出和档案。在您对那些工具和程序设计结构变得熟悉之后,您就可以开始写命令档。当由命 令档执行命令时,此刻,您就已经把 shell 当做程序语言使用了。 2. Shell 的发展历史 第一个有重要意义的,标准的 UNIX shell 是 V7(AT&T 的第七版)UNIX,在 1979 年底被提 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 114 - 出,且以它的创造者 Stephen Bourne 来命名。Bourne shell 是以 Algol 这种语言为基础来设计, 主要被用来做自动化系统管理工作。虽然 Bourne shell 以简单和速度而受欢迎,但它缺少许多交谈 性使用的特色,例如历程、别名和工作控制。 C shell 是在加州大学柏克来分校于 70 年代末期发展而成,而以 2BSD UNIX 的部分发行。这个 shell 主要是由 Bill Joy 写成,提供了一些在标准 Bourne shell 所看不到的额外特色。C shell 是以 C 程序语言作为基础,且它被用来当程序语言时,能共享类似的语法。它也提供在交谈式运用上的 改进,例如命令列历程、别名和工作控制。因为 C shell 是在大型机器上设计出来,且增加了一些 额外功能,所以 C shell 有在小型机器上跑得较慢,即使在大型机器上跟 Bourne shell 比起来也显 得缓慢。 有了 Bourne shell 和 C shell 之后,UNIX 使用者就有了选择,且争论那一个 shell 较好。AT&T 的 David Korn 在 80 年代中期发明了 Korn shell,在 1986 年发行且在 1988 年成为正式的部分 SVR4 UNIX。Korn shell 实际上是 Bourne shell 的超集,且不只可在 UNIX 系统上执行,同时也可 在 OS/2、VMS、和 DOS 上执行。它提供了和 Bourne shell 向上兼容的能力,且增加了许多在 C shell 上受欢迎的特色,更增加了速度和效率。 Korn shell 已历经许多修正版,要找寻您使用的是那一个 版本可在 ksh 提示符号下按 Ctrl-v 键。 三种主要的 Shell 与其分支: 在大部份的 UNIX 系统,三种著名且广被支持的 shell 是 Bourne shell(AT&T shell,在 Linux 下 是 BASH)、C shell(Berkeley shell,在 Linux 下是 TCSH)和 Korn shell(Bourne shell 的超集)。 这三种 shell 在交谈(interactive)模式下的表现相当类似,但作为命令文件语言时,在语法和执行 效率上就有些不同了。 Bourne shell 是标准的 UNIX shell,以前常被用来做为管理系统之用。大部份的系统管理命令 文件,例如 rc start、stop 与 shutdown 都是 Bourne shell 的命令档,且在单一使用者模式(single user mode)下以 root 签入时它常被系统管理者使用。Bourne shell 是由 AT&T 发展的,以简洁、 快速著名。 Bourne shell 提示符号的默认值是$。 C shell 是柏克莱大学(Berkeley)所开发的,且加入了一些新特性,如命令列历程(history)、 别名(alias)、内建算术、档名完成(filename completion)、和工作控制(job control)。对于常在 交谈模式下执行 shell 的使用者而言,他们较喜爱使用 C shell;但对于系统管理者而言,则较偏好 以 Bourne shell 来做命令档,因为 Bourne shell 命令檔比 C shell 命令档来的简单及快速。C shell 提示符号的默认值是%。 Korn shell 是 Bourne shell 的超集(superset),由 AT&T 的 David Korn 所开发。它增加了一 些特色,比 C shell 更为先进。Korn shell 的特色包括了可编辑的历程、别名、函式、正规表达式 万用字符(regular expression wildcard)、内建算术、工作控制(job control)、共作处理 (coprocessing)、和特殊的除错功能。Bourne shell 几乎和 Korn shell 完全向上兼容(upward 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 115 - compatible),所以在 Bourne shell 下开发的程序仍能在 Korn shell 上执行。Korn shell 提示符号 的默认值也是$。在 Linux 系统使用的 Korn shell 叫做 pdksh,它是指 Public Domain Korn Shell。 除了执行效率稍差外,Korn shell 在许多方面都比 Bourne shell 好;但是,若将 Korn shell 与 C shell 相比就很困难,因为二者在许多方面都各有所长,就效率和容易使用上看,Korn shell 是优 于 C shell,相信许多使用者对于 C Shell 的执行效率都有负面的印象。 在 shell 的语法方面,Korn shell 是比较接近一般程序语言,而且它具有子程序的功能及提供较 多的资料型态。至于 Bourne shell,它所拥有的资料型态是三种 shell 中最少的,仅提供字符串变量 和布尔型态。在整体考量下 Korn shell 是三者中表现最佳者,其次为 C shell,最后才是 Bourne shell,但是在实际使用中仍有其它应列入考虑的因素,如速度是最重要的选择时,很可能应该采用 Bourne shell,因它是最基本的 shell,执行的速度最快。 tcsh 是近几年崛起的一个免费软件(Linux 下的 C shell 其实就是使用 tcsh 执行),它虽然不 是 UNIX 的标准配备,但是从许多地方您都可以下载到它。如果您是 C shell 的拥护者,笔者建议不 妨试试 tcsh,因为您至少可以将它当作是 C shell 来使用。如果您愿意花点时间学习,您还可以享 受许多它新增的优越功能,例如: 1)tcsh 提供了一个命令行(command line)编辑程序。 2)提供了命令行补全功能。 3)提供了拼写更正功能。它能够自动检测并且更正在命令行拼错的命令或是单字。 4)危险命令侦测并提醒的功能,避免您一个不小心执行了 rm* 这种杀伤力极大的命令。 5)提供常用命令的快捷方式(shortcut)。 bash 对 Bourne shell 是向下兼容(backward compatible),并融入许多 C shell 与 Korn shell 的 功能。这些功能其实 C shell(当然也包括了 tcsh)都有,只是过去 Bourne shell 都未支持。以下 将介绍 bash 六点重要的改进: 1)工作控制(job contorl)。bash 支持了关于工作的讯号与指令,本章稍后会提及。 2)别名功能(aliases)。alias 命令是用来为一个命令建立另一个名称,它的运作就像一个宏, 展开成为它所代表的命令。别名并不会替代掉命令的名称,它只是赋予那个命令另一个名字。 3)命令历程(command history)。 BASH shell 加入了 C shell 所提供的命令历程功能,它以 history 工具程序记录了最近您执行 过的命令。命令是由 1 开始编号,默认最大值为 500。history 工具程序是一种短期记忆,记录您 最近所执行的命令。要看看这些命令,您可以在命令行键入 history,如此将会显示最近执行过的命 令的清单,并在前方加上编号。 这些命令在技术上每个都称为一个事件。事件描述的是一个已经采取的行动(已经被执行的命 令)。事件是依照执行的顺序而编号,越近的事件其编号码越大,这些事件都是以它的编号或命令的 开头字符来辨认的。history 工具程序让您参照一个先前发生过的事件,将它放在命令行上并允许您 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 116 - 执行它。最简单的方法是用上下键一次放一个历程事件在您的命令列上;您并不需要先用 history 显 示清单。按一次向上键会将最后一个历程事件放在您的命令列上,再按一次会放入上一个历史事件。 按向下键则会将后一个事件放在命令行上。 4)命令行编辑程序。 BASH shell 命令行编辑能力是内建的,让您在执行之前轻松地修改您输入的命令。若是您在输 入命令时拼错了字,您不需重新输入整个命令,只需在执行命令之前使用编辑功能纠正错误即可。 这尤其适合于使用冗长的路径名称当作参数的命令时。命令行编辑作业是 Emacs 编辑命令的一部 分,您可以用 Ctrl-F 或向右键往后移一个字符,Ctrl-b 或向左键往回移一个字符。Ctrl-d 或 DEL 键会 删除光标目前所在处的字符。要增加文字的话,您只需要将光标移到您要插入文字的地方并键入新 字符即可。无论何时,您都可以按 ENTER 键执行命令。 5)允许使用者自订按键。 6)提供更丰富的变量型态、命令与控制结构至 shell 中。 bash 与 tcsh 一样可以从许多网站上免费下载,它们的性质也十分类似,都是整合其前一代的 产品然后增添新的功能,这些新增的功能主要着重在强化 shell 的程序设计能力以及让使用者能够自 行定义自己偏好的作业环境。除了上述的五种 shell 之外,zsh 也是一个广为 UNIX 程序设计人员与 进阶使用者所采用的 shell,zsh 基本上也是 Bourne shell 功能的扩充。 3. Shell 的使用 不论是哪一种 Shell,它最主要的功用都是解译使用者在命令行提示符号下输入的指令。Shell 语 法分析命令行,把它分解成以空白区分开的符号(token),在此空白包括了 Tab 键、空白和换行(New Line)。如果这些字包含了 metacharacter,shell 将会评估(evaluate)它们的正确用法。另外,shell 还管理档案输入输出及幕后处理(background processing)。在处理命令行之后,shell 会寻找命令 并开始执行它们。 Shell 的另一个重要功用是提供个人化的使用者环境,这通常在 shell 的初始化档案中完成 (.profile、.login、.cshrc、.tcshrc 等等)。这些档案包括了设定终端机键盘和定义窗口的特征,设 定变量,定义搜寻路径、权限、提示符号和终端机类型,以及设定特殊应用程序所需要的变量,例 如窗口、文字处理程序及程序语言的链接库。Korn shell 和 C shell 加强了个别化的能力:增加例 程、别名、和内建变量集以避免使用者误杀档案、不慎签出、并在当工作完成时通知使用者。 Shell 也能当解译性的程序语言(interpreted programing language)。Shell 程序,通常叫做命 令文件,它由列在档案内的命令所构成。此程序在编辑器中编辑(虽然也可以直接在命令行下写程 序,online scripting),由 UNIX 命令和基本的程序结构,例如变量的指定、测试条件和循环所构成。 您不需要编译 shell 命令档。Shell 本身会解译命令档中的每一行,就如同由键盘输入一样。shell 负 责解译命令,而使用者则必须了解这些命令能做什么。这本书的索引列出了一些有用的命令和它们 的使用方法。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 117 - 4. Shell 的功能 为了确保任何提示符号下输入的命令都能够适当地执行。shell 担任的工作包括有: ¾ 读取输入和语法分析命令列; ¾ 对特殊字符求值; ¾ 设立管线、转向、和幕后处理; ¾ 处理讯号; ¾ 设立程序来执行。 3. 2. 4 Linux 下的编译器 GCC 在为 Linux 开发应用程序时,绝大多数情况下使用的都是 C 语言,因此几乎每一位 Linux 程序 员面临的首要问题都是如何灵活运用 C 编译器。目前 Linux 下最常用的 C 语言编译器是 GCC(GNU Compiler Collection),它是 GNU 项目中符合 ANSI C 标准的编译系统,能够编译用 C、C++和 Object C 等语言编写的程序。GCC 不仅功能非常强大,结构也异常灵活。最值得称道的一点就是它可以通 过不同的前端模块来支持各种语言,如 Java、 Fortran、Pascal、Modula-3 和 Ada 等。 开放、自由和灵活是 Linux 的魅力所在,而这一点在 GCC 上的体现就是程序员通过它能够更好 地控制整个编译过程。在使用 GCC 编译程序时,编译过程可以被细分为四个阶段: ¾ 预处理(Pre-Processing); ¾ 编译(Compiling); ¾ 汇编(Assembling); ¾ 链接(Linking)。 Linux 程序员可以根据自己的需要让 GCC 在编译的任何阶段结束,以便检查或使用编译器在该 阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代 码来为今后的调试做好准备。和其它常用的编译器一样,GCC 也提供了灵活而强大的代码优化功能, 利用它可以生成执行效率更高的代码。 GCC 提供了 30 多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。 此外,GCC 还对标准的 C 和 C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行 代码优化,能够减轻编程的工作量。 1. GCC 起步 在学习使用 GCC 之前,下面的这个例子能够帮助用户迅速理解 GCC 的工作原理,并将其立即 运用到实际的项目开发中去。首先用熟悉的编辑器输入清单 1 所示的代码: 清单 1:hello.c #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 118 - int main(void) { printf ("Hello world, Linux programming!\n"); return 0; } 然后执行下面的命令编译和运行这段程序: $ gcc hello.c -o hello $ ./hello Hello world, Linux programming! 从程序员的角度看,只需简单地执行一条 GCC 命令就可以了,但从编译器的角度来看,却需要 完成一系列非常繁杂的工作。首先,GCC 需要调用预处理程序 cpp,由它负责展开在源文件中定义 的宏,并向其中插入“#include”语句所包含的内容;接着,GCC 会调用 ccl 和 as 将处理后的源代 码编译成目标代码;最后,GCC 会调用链接程序 ld,把生成的目标代码链接成一个可执行程序。 为了更好地理解 GCC 的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的 运行结果。第一步是进行预编译,使用-E 参数可以让 GCC 在预处理结束后停止编译过程: $ gcc -E hello.c -o hello.i 此时若查看 hello.i 文件中的内容,会发现 stdio.h 的内容确实都插到文件里去了,而其它应当被 预处理的宏定义也都做了相应的处理。下一步是将 hello.i 编译为目标代码,这可以通过使用-c 参数 来完成: $ gcc -c hello.i -o hello.o GCC 默认将.i 文件看成是预处理后的 C 语言源代码,因此上述命令将自动跳过预处理步骤而开 始执行编译过程,也可以使用-x 参数让 GCC 从指定的步骤开始编译。最后一步是将生成的目标文件 链接成可执行文件: $ gcc hello.o -o hello 在采用模块化的设计思想进行软件开发时,通常整个程序是由多个源文件组成的, 相应地也就 形成了多个编译单元,使用 GCC 能够很好地管理这些编译单元。假设有一个由 foo1.c 和 foo2.c 两 个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序 foo,可以使用下面这条命令: $ gcc foo1.c foo2.c -o foo 如果同时处理的文件不止一个,GCC 仍然会按照预处理、编译和链接的过程依次进行。如果深 究起来,上面这条命令大致相当于依次执行如下三条命令: $ gcc -c foo1.c -o foo1.o $ gcc -c foo2.c -o foo2.o $ gcc foo1.o foo2.o -o foo 在编译一个包含许多源文件的工程时,若只用一条 GCC 命令来完成编译是非常浪费时间的。假 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 119 - 设项目中有 100 个源文件需要编译,并且每个源文件中都包含 10000 行代码,如果像上面那样仅用 一条 GCC 命令来完成编译工作,那么 GCC 需要将每个源文件都重新编译一遍,然后再全部连接起 来。很显然,这样浪费的时间相当多,尤其是当用户只是修改了其中某一个文件的时候,完全没有 必要将每个文件都重新编译一遍,因为很多已经生成的目标文件是不会改变的。要解决这个问题, 关键是要灵活运用 GCC,同时还要借助像 Make 这样的工具。 2. 警告提示功能 GCC 包含完整的出错检查和警告提示功能,它们可以帮助 Linux 程序员写出更加专业和优美的 代码。先来读读清单 2 所示的程序,这段代码写得很糟糕,仔细检查一下不难挑出很多毛病: ¾ main 函数的返回值被声明为 void,但实际上应该是 int; ¾ 使用了 GNU 语法扩展,即使用 long long 来声明 64 位整数,不符合 ANSI/ISO C 语言标准; ¾ main 函数在终止前没有调用 return 语句。 清单 2:illcode.c #include void main(void) { long long int var = 1; printf("It is not standard C code!\n"); } 下面来看看 GCC 是如何帮助程序员来发现这些错误的。当 GCC 在编译不符合 ANSI/ISO C 语言 标准的源代码时,如果加上了-pedantic 选项,那么使用了扩展语法的地方将产生相应的警告信息: $ gcc -pedantic illcode.c -o illcode illcode.c: In function `main': illcode.c:9: ISO C89 does not support `long long' illcode.c:8: return type of `main' is not `int' 需要注意的是,-pedantic 编译选项并不能保证被编译程序与 ANSI/ISO C 标准的完全兼容,它 仅仅只能用来帮助 Linux 程序员离这个目标越来越近。或者换句话说,-pedantic 选项能够帮助程序 员发现一些不符合 ANSI/ISO C 标准的代码,但不是全部,事实上只有 ANSI/ISO C 语言标准中要求 进行编译器诊断的那些情况,才有可能被 GCC 发现并提出警告。 除了-pedantic 之外,GCC 还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以 -W 开头,其中最有价值的当数-Wall 了,使用它能够使 GCC 产生尽可能多的警告信息: $ gcc -Wall illcode.c -o illcode illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var' GCC 给出的警告信息虽然从严格意义上说不能算作是错误,但却很可能成为错误的栖身之所。 一个优秀的 Linux 程序员应该尽量避免产生警告信息,使自己的代码始终保持简洁、优美和健壮的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 120 - 特性。 在处理警告方面,另一个常用的编译选项是-Werror,它要求 GCC 将所有的警告当成错误进行 处理,这在使用自动编译工具(如 Make 等)时非常有用。如果编译时带上-Werror 选项,那么 GCC 会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改。只有当相应的警告信息消 除时,才可能将编译过程继续朝前推进。执行情况如下: $ gcc -Wall -Werror illcode.c -o illcode cc1: warnings being treated as errors illcode.c:8: warning: return type of `main' is not `int' illcode.c: In function `main': illcode.c:9: warning: unused variable `var' 对 Linux 程序员来讲,GCC 给出的警告信息是很有价值的,它们不仅可以帮助程序员写出更加 健壮的程序,而且还是跟踪和调试程序的有力工具。建议在用 GCC 编译源代码时始终带上-Wall 选 项,并把它逐渐培养成为一种习惯,这对找出常见的隐式编程错误很有帮助。 3. 库依赖 在 Linux 下开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助 一个或多个函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文 件(.h)和库文件(.so 或者.a)的集合。虽然 Linux 下的大多数函数都默认将头文件放到 /usr/include/ 目录下,而库文件则放到/usr/lib/目录下,但并不是所有的情况都是这样。正因如此,GCC 在编译时 必须有自己的办法来查找所需要的头文件和库文件。 GCC 采用搜索目录的办法来查找所需要的文件,-I 选项可以向 GCC 的头文件搜索路径中添加新 的目录。例如,如果在/home/embest/include/目录下有编译时所需要的头文件,为了让 GCC 能够 顺利地找到它们,就可以使用-I 选项: $ gcc foo.c -I /home/embest/include -o foo 同样,如果使用了不在标准位置的库文件,那么可以通过-L 选项向 GCC 的库文件搜索路径中添 加新的目录。例如,如果在/home/embest/lib/目录下有链接时所需要的库文件 libfoo.so,为了让 GCC 能够顺利地找到它,可以使用下面的命令: $ gcc foo.c -L /home/embest/lib –l foo -o foo 值得好好解释一下的是-l 选项,它指示 GCC 去连接库文件 libfoo.so。Linux 下的库文件在命名 时有一个约定,那就是应该以 lib 三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用 -l 选项指定链接的库文件名时可以省去 lib 三个字母,也就是说 GCC 在对-foo 进行处理时, 会自动 去链接名为 libfoo.so 的文件。 Linux 下的库文件分为两大类分别是动态链接库(通常以.so 结尾)和静态链接库(通常以.a 结 尾),两者的差别仅在程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。默 认情况下,GCC 在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库, 如果需要的话可以在编译时加上-static 选项,强制使用静态链接库。例如,如果在/home/embest/lib/ 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 121 - 目录下有链接时所需要的库文件 libfoo.so 和 libfoo.a,为了让 GCC 在链接时只用到静态链接库,可 以使用下面的命令: $ gcc foo.c -L /home/embest/lib -static –l foo -o foo 4. 代码优化 代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组 合,目的是改善程序的执行性能。GCC 提供的代码优化功能非常强大,它通过编译选项-On 来控制 优化代码的生成,其中 n 是一个代表优化级别的整数。对于不同版本的 GCC 来讲,n 的取值范围及 其对应的优化效果可能并不完全相同,比较典型的范围是从 0 变化到 2 或 3。 编译时使用选项-O 可以告诉 GCC 同时减小代码的长度和执行时间,其效果等价于-O1。在这一 级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)和 延迟退栈(Deferred Stack Pops)两种优化。选项-O2 告诉 GCC 除了完成所有-O1 级别的优化之外, 同时还要进行一些额外的调整工作,如处理器指令调度等。选项-O3 则除了完成所有-O2 级别的优 化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越大优化的等 级越高,同时也就意味着程序的运行速度越快。许多 Linux 程序员都喜欢使用-O2 选项,因为它在 优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。 下面通过具体实例来感受一下 GCC 的代码优化功能,所用程序如清单 3 所示。 清单 3:optimize.c #include int main(void) { double counter; double result; double temp; for (counter = 0; counter < 2000.0 * 2000.0 * 2000.0 / 20.0 + 2020; counter += (5-1) / 4) { temp = counter / 1979; result = counter; } printf("Result is %lf\n", result); return 0; } 首先不加任何优化选项进行编译: $ gcc -Wall optimize.c -o optimize 借助 Linux 提供的 time 命令,可以大致统计出该程序在运行时所需要的时间: $ time ./optimize Result is 400002019.000000 real 0m14.942s user 0m14.940s sys 0m0.000s 接下去使用优化选项来对代码进行优化处理: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 122 - $ gcc -Wall -O optimize.c -o optimize 在同样的条件下再次测试一下运行时间: $ time ./optimize Result is 400002019.000000 real 0m3.256s user 0m3.240s sys 0m0.000s 对比两次执行的输出结果不难看出,程序的性能的确得到了很大幅度的改善,由原来的 14 秒缩 短到了 3 秒。这个例子是专门针对 GCC 的优化功能而设计的,因此优化前后程序的执行速度发生了 很大的改变。尽管 GCC 的代码优化功能非常强大,但作为一名优秀的 Linux 程序员,首先还是要力 求能够手工编写出高质量的代码。如果编写的代码简短,并且逻辑性强,编译器就不会做更多的工 作,甚至根本用不着优化。 优化虽然能够给程序带来更好的执行性能,但在如下一些场合中应该避免优化代码: ¾ 程序开发的时候优化等级越高,消耗在编译上的时间就越长,因此在开发的时候最好不要 使用优化选项,只有到软件发行或开发结束的时候,才考虑对最终生成的代码进行优化; ¾ 资源受限的时候一些优化选项会增加可执行代码的体积,如果程序在运行时能够申请到的 内存资源非常紧张(如一些实时嵌入式设备),那就不要对代码进行优化,因为由这带来的 负面影响可能会产生非常严重的后果; ¾ 跟踪调试的时候在对代码进行优化的时候,某些代码可能会被删除或改写,或者为了取得 更佳的性能而进行重组,从而使跟踪和调试变得异常困难。 5. 调试 一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到 解决问题的方法。对于 Linux 程序员来讲,GDB(GNU Debugger)通过与 GCC 的配合使用,为基 于 Linux 的软件开发提供了一个完善的调试环境。 默认情况下,GCC 在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执 行文件的大小。如果需要在编译时生成调试符号信息,可以使用 GCC 的-g 或者-ggdb 选项。GCC 在 产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g 选项后附加数字 1、2 或 3 来指 定在代码中加入调试信息的多少。默认的级别是 2(-g2),此时产生的调试信息包括扩展的符号表、 行号、局部或外部变量信息。级别 3(-g3)包含级别 2 中的所有调试信息,以及源代码中定义的宏。 级别 1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。 回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式 保存程序执行环境的方法,两者都是经常用到的调试手段。 GCC 产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是 GDB, 那么还可以通过-ggdb 选项在生成的二进制代码中包含 GDB 专用的调试信息。这种做法的优点是可 以方便 GDB 的调试工作,但缺点是可能导致其它调试器(如 DBX)无法进行正常的调试。选项-ggdb 能够接受的调试级别和-g 是完全一样的,它们对输出的调试符号有着相同的影响。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 123 - 需要注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增 加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码 大小的影响从下面的对比过程中可以看出来: $ gcc optimize.c -o optimize $ ls optimize -l -rwxrwxr-x 1 embest embest 11649 Nov 20 08:53 optimize (未加调试选项) $ gcc -g optimize.c -o optimize $ ls optimize -l -rwxrwxr-x 1 embest embest 15889 Nov 20 08:54 optimize (加入调试选项) 虽然调试选项会增加文件的大小,但事实上 Linux 中的许多软件在测试版本甚至最终发行版本 中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,是 Linux 的一个显著特色。 下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序见清单 4 所示。 清单 4:crash.c #include int main(void) { int input =0; printf("Input an integer:"); scanf("%d", input); printf("The integer you input is %d\n", input); return 0; } 编译并运行上述代码,会产生一个严重的段错误(Segmentation fault)如下: $ gcc -g crash.c -o crash $ ./crash Input an integer:10 Segmentation fault 为了更快速地发现错误所在,可以使用 GDB 进行跟踪调试,方法如下: $ gdb crash GNU gdb …… (gdb) 当 GDB 提示符出现的时候,表明 GDB 已经做好准备进行调试了,现在可以通过 run 命令让程 序开始在 GDB 的监控下运行: (gdb) run Starting program: /home/embest/thesis/gcc/code/crash Input an integer:10 Program received signal SIGSEGV, Segmentation fault. 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 124 - 仔细分析一下 GDB 给出的输出结果不难看出,程序是由于段错误而导致异常中止的,说明内存 操作出了问题,具体发生问题的地方是在调用 _IO_vfscanf_internal ( )的时候。为了得到更加有价 值的信息,可以使用 GDB 提供的回溯跟踪命令 backtrace,执行结果如下: (gdb) backtrace #0 0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6 #1 0xbffff0c0 in ?? () #2 0x4008e0ba in scanf () from /lib/libc.so.6 #3 0x08048393 in main () at crash.c:11 #4 0x40042917 in __libc_start_main () from /lib/libc.so.6 跳过输出结果中的前面三行,从输出结果的第四行中不难看出,GDB 已经将错误定位到 crash.c 中的第 11 行了。现在仔细检查一下: (gdb) frame 3 #3 0x08048393 in main () at crash.c:11 11 scanf("%d", input); 使用 GDB 提供的 frame 命令可以定位到发生错误的代码段,该命令后面跟着的数值可以在 backtrace 命令输出结果中的行首找到。现在已经发现错误所在了,应该将 scanf("%d", input); 改 为 scanf("%d", &input); 完成后就可以退出 GDB 了,命令如下: (gdb) quit GDB 的功能远远不止如此,它还可以单步跟踪程序、检查内存变量和设置断点等。 调试时可能会需要用到编译器产生的中间结果,这时可以使用-save-temps 选项,让 GCC 将预 处理代码、汇编代码和目标代码都作为文件保存起来。如果想检查生成的代码是否能够通过手工调 整的办法来提高执行性能,在编译过程中生成的中间文件将会很有帮助,具体情况如下: $ gcc -save-temps foo.c -o foo $ ls foo* foo foo.c foo.i foo.s GCC 支持的其它调试选项还包括-p 和-pg,它们会将剖析(Profiling)信息加入到最终生成的二 进制代码中。剖析信息对于找出程序的性能瓶颈很有帮助,是协助 Linux 程序员开发出高性能程序 的有力工具。在编译时加入-p 选项会在生成的代码中加入通用剖析工具(Prof)能够识别的统计信 息,而- pg 选项则生成只有 GNU 剖析工具(Gprof)才能识别的统计信息。 最后提醒一点,虽然 GCC 允许在优化的同时加入调试符号信息,但优化后的代码对于调试本身 而言将是一个很大的挑战。代码在经过优化之后,在源程序中声明和使用的变量很可能不再使用, 控制流也可能会突然跳转到意外的地方,循环语句有可能因为循环展开而变得到处都有,所有这些 对调试来讲都将是一场噩梦。 建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行 的时候才考虑对其进行优化。 6. 加速 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 125 - 在将源代码变成可执行文件的过程中,需要经过许多中间步骤,包含预处理、编译、汇编和连 接。这些过程实际上是由不同的程序负责完成的。大多数情况下 GCC 可以为 Linux 程序员完成所有 的后台工作,自动调用相应程序进行处理。 这样做有一个很明显的缺点,就是 GCC 在处理每一个源文件时,最终都需要生成好几个临时文 件才能完成相应的工作,从而无形中导致处理速度变慢。例如,GCC 在处理一个源文件时,可能需 要一个临时文件来保存预处理的输出、一个临时文件来保存编译器的输出、一个临时文件来保存汇 编器的输出,而读写这些临时文件显然需要耗费一定的时间。当软件项目变得非常庞大的时候,花 费在这上面的代价可能会变得很沉重。 解决的办法是,使用 Linux 提供的一种更加高效的通信方式——管道。它可以用来同时连接两 个程序,其中一个程序的输出将被直接作为另一个程序的输入,这样就可以避免使用临时文件,但 编译时却需要消耗更多的内存。 在编译过程中使用管道是由 GCC 的-pipe 选项决定的。下面的这条命令就是借助 GCC 的管道功 能来提高编译速度的: $ gcc -pipe foo.c -o foo 在编译小型工程时使用管道,编译时间上的差异可能还不是很明显,但在源代码非常多的大型 工程中,差异将变得非常明显。 7. 文件扩展名 在使用 GCC 的过程中,用户对一些常用的扩展名一定要熟悉,并知道其含义。为了方便大家学 习使用 GCC,在此将这些扩展名罗列如下: .c C 原始程序; .C C++原始程序; .cc C++原始程序; .cxx C++原始程序; .m Objective-C 原始程序; .i 已经过预处理的 C 原始程序; .ii 已经过预处理的 C++原始程序; .s 组合语言原始程序; .S 组合语言原始程序; .h 预处理文件(标头文件); .o 目标文件; .a 存档文件。 8. GCC 常用选项 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 126 - GCC 作为 Linux 下 C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译 方便,在此将常用的选项及说明罗列出来如下: -c 通知 GCC 取消链接步骤,即编译源码并在最后生成目标文件; -Dmacro 定义指定的宏,使它能够通过源码中的#ifdef 进行检验; -E 不经过编译预处理程序的输出而输送至标准输出; -g3 获得有关调试程序的详细信息,它不能与-o 选项联合使用; -Idirectory 在包含文件搜索路径的起点处添加指定目录; -llibrary 提示链接程序在创建最终可执行文件时包含指定的库; -O、-O2、-O3 将优化状态打开,该选项不能与-g 选项联合使用; -S 要求编译程序生成来自源代码的汇编程序输出; -v 启动所有警报; -Wall 在发生警报时取消编译操作,尽可能找出多的警告信息; -Werror 在发生警报时取消编译操作,即把报警当作是错误; -w 禁止所有的报警。 9. 小结 GCC 是在 Linux 下开发程序时必须掌握的工具之一。本文对 GCC 做了一个简要的介绍,主要讲 述了如何使用 GCC 编译程序、产生警告信息、调试程序和加快 GCC 的编译速度。对所有希望早日 跨入 Linux 开发者行列的人来说,GCC 就是成为一名优秀的 Linux 程序员的起跑线。 3. 2. 5 认识 Makefile make 是 Linux 下的一款程序自动维护工具,配合 Makefile 的使用,就能够根据程序中模块的修 改情况,自动判断应该对哪些模块重新编译,从而保证软件是由最新的模块构成。本文分为上下两 部分,我们将紧紧围绕 make 在软件开发中的应用展开详细的介绍。 1. 都是源文件太多惹的祸 当我们在开发的程序中涉及众多源文件时,常常会引起一些问题。首先,如果程序只有两三个 源文件,那么修改代码后直接重新编译全部源文件就行了,但是如果程序的源文件较多,这种简单 的处理方式就有问题了。 设想一下,如果我们只修改了一个源文件,却要重新编译所有源文件,那么这显然是在浪费时 间。其次,要是只重新编译那些受影响的文件的话,我们又该如何确定这些文件呢?比如我们使用 了多个头文件,那么它们会被包含在各个源文件中,修改了某些头文件后,哪些源文件受影响,哪 些与此无关呢?如果采取拉网式大检查的话,可就费劲了。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 127 - 由此可以看出,源文件多了可真是件让人头疼的事。幸运的是,实用程序 make 可以帮我们解 决这两个问题——当程序的源文件改变后,它能保证所有受影响的文件都将重新编译,而不受影响 的文件则不予编译,这真是太好了。 2. Make 程序的命令行选项和参数 我们知道,make 程序能够根据程序中各模块的修改情况,自动判断应对哪些模块重新编译,保 证软件是由最新的模块构建的。至于检查哪些模块,以及如何构建软件由 Makefile 文件来决定。 虽然 make 可以在 Makefile 中进行配置,除此之外我们还可以利用 make 程序的命令行选项对 它进行即时配置。make 命令参数的典型序列如下所示: make [-f makefile 文件名][选项][宏定义][目标] 这里用[]括起来的表示是可选的。命令行选项由破折号“–”指明,后面跟选项,如: make –e 如果需要多个选项,可以只使用一个破折号,如: make –kr 也可以每个选项使用一个破折号,如: make –k –r 甚至混合使用也行,如: make –e –kr make 命令本身的命令行选项较多,这里只介绍在开发程序时最为常用的三个,它们是: 1.–k 如果使用该选项,即使 make 程序遇到错误也会继续向下运行;如果没有该选项,在遇到第一 个错误时 make 程序马上就会停止,那么后面的错误情况就不得而知了。我们可以利用这个选项来 查出所有有编译问题的源文件。 2.–n 该选项使 make 程序进入非执行模式,也就是说将原来应该执行的命令输出,而不是执行。 3.–f 指定作为 Makefile 的文件的名称。 如果不用该选项,那么 make 程序首先在当前目录查找名为 Makefile 的文件,如果没有找到,它就会转而查找名为 Makefile 的文件。如果您在 Linux 下使用 GNU Make 的话,它会首先查找 GNUmakefile,之后再搜索 makefile 和 Makefile。按照惯例,许多 Linux 程序员使用 Makefile,因为这样能使 Makefile 出现在目录中所有以小写字母命名的文件的前面。所 以,最好不要使用 GNUmakefile 这一名称,因为它只适用于 make 程序的 GNU 版本。 当我们想构建指定目标的时候,比如要生成某个可执行文件,那么就可以在 make 命令行中给 出该目标的名称;如果命令行中没有给出目标的话,make 命令会设法构建 makefile 中的第一个目 标。我们可以利用这一特点,将 all 作为 makefile 中的第一个目标,然后将让目标作为 all 所依赖的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 128 - 目标,这样,当命令行中没有给出目标时,也能确保它会被构建。 3. Makefile 概述 上面提到,make 命令对于构建具有多个源文件的程序有很大的帮助。事实上,只有 make 命令 还是不够的,前面说过还必须用 makefile 告诉它要做什么以及怎么做才行,对于程序开发而言,就 是告诉 make 命令应用程序的组织情况。 我们现在对 makefile 的位置和数量简单说一下。一般情况下,makefile 会和项目的源文件放在 同一个目录中。另外,系统中可以有多个 makefile,一般说来一个项目使用一个 makefile 就可以了; 如果项目很大的话,我们就可以考虑将它分成较小的部分,然后用不同的 makefile 来管理项目的不 同部分。 make 命令和 Makefile 配合使用,能给我们的项目管理带来极大的便利,除了用于管理源代码 的编译之外,还用于建立手册页,同时还能将应用程序安装到指定的目录。 因为 Makefile 用于描述系统中模块之间的相互依赖关系,以及产生目标文件所要执行的命令, 所以,一个 makefile 由依赖关系和规则两部分内容组成,下面分别加以解释。 依赖关系由一个目标和一组该目标所依赖的源文件组成。这里所说的目标就是将要创建或更新 的文件,最常见的是可执行文件。规则用来说明怎样使用所依赖的文件来建立目标文件。 当 make 命令运行时,会读取 makefile 来确定要建立的目标文件或其他文件,然后对源文件的 日期和时间进行比较,从而决定使用那些规则来创建目标文件。一般情况下,在建立起最终的目标 文件之前,肯定免不了要建立一些中间性质的目标文件。这时,Make 命令也是使用 makefile 来确 定这些目标文件的创建顺序,以及用于它们的规则序列。 4. makefile 中的依赖关系 make 程序自动生成和维护通常是可执行模块或应用程序的目标,目标的状态取决于它所依赖的 那些模块的状态。Make 的思想是为每一块模块都设置一个时间标记,然后根据时间标记和依赖关系 来决定哪一些文件需要更新。一旦依赖模块的状态改变了,make 就会根据时间标记的新旧执行预先 定义的一组命令来生成新的目标。 依赖关系规定了最终得到的应用程序跟生成它的各个源文件之间的关系。如下面的图 3-2-1 描 述了可执行文件 main 对所有的源程序文件及其编译产生的目标文件之间的依赖关系,见下图所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 129 - 图 3-2-1 模块间的依赖关系 就图 3-2-1 而言,我们可以说可执行程序 main 依赖于 main.o、f1.o 和 ff1.o。与此同时,main.o 依赖于 main.c 和 def1.h;f1.o 依赖于 f1.c、def1.h 和 def2.h;而 ff1.o 则依赖于 ff1.c、def2.h 和 def3. h。在 makefile 中,我们可以用目标名称,加冒号,后跟空格键或 tab 键,再加上由空格键或 tab 键分隔的一组用于生产目标模块的文件来描述模块之间的依赖关系。对于上例来说,可以作以下描 述: main: main.o f1.o f2.o main.o: main.c def1.h f1.o: f1.c def1.h def2.h f2.o: f2.c def2.h def3.h 不难发现,上面的各个源文件跟各模块之间的关系具有一个明显的层次结构,如果 def2.h 发生 了变化,那么就需要更新 f1.o 和 f2.o,而 f1.o 和 f2.o 发生了变化的话,那么 main 也需要随之重新 构建。 默认时,make 程序只更新 makefile 中的第一个目标,如果希望更新多个目标文件的话,可以 使用一个特殊的目标 all,假如我们想在一个 makefile 中更新 main 和 hello 这两个程序文件的话, 可以加入下列语句达到这个目的: all: main hello 5. makefile 中的规则 除了指明目标和模块之间的依赖关系之外,makefile 还要规定相应的规则来描述如何生成目标, 或者说使用哪些命令来根据依赖模块产生目标。就上例而言,当 make 程序发现需要重新构建 f1.o 的时候,该使用哪些命令来完成呢?很遗憾,到目前为止,虽然 make 知道哪些文件需要更新,但 是却不知道如何进行更新,因为我们还没有告诉它相应的命令。 当然,我们可以使用命令 gcc -c f1.c 来完成,不过如果我们需要规定一个 include 目录,或者为 将来的调试准备符号信息的话,该怎么办呢?所有这些,都需要在 makefile 中用相应规则显式地指 出。 def1.h def2.h def3.h main.c f1.c f2.c main.o f1.o f2.o main 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 130 - 实际上,makefile 是以相关行为为基本单位的,相关行用来描述目标、模块及规则(即命令行) 三者之间的关系。一个相关行格式通常为:冒号左边是目标(模块)名;冒号右边是目标所依赖的 模块名;紧跟着的规则(即命令行)是由依赖模块产生目标所使用的命令。相关行的格式为: 目标:[依赖模块][;命令] 习惯上写成多行形式,如下所示: 目标:[依赖模块] 命令 命令 需要注意的是,如果相关行写成一行,“命令”之前用分号“;”隔开,如果分成多行书写的话, 后续的行务必以 tab 字符为先导。对于 makefile 而言,空格字符和 tab 字符是不同的。所有规则所 在的行必须以 tab 键开头,而不是空格键。初学者一定对此保持警惕,因为这是新手最容易疏忽的 地方,因为几个空格键跟一个 tab 键在肉眼是看不出区别的,但 make 命令却能明察秋毫。 此外,如果在 makefile 文件中的行尾加上空格键的话,也会导致 make 命令运行失败。所以, 大家一定要小心了,免得耽误许多时间。 6. Makefile 文件举例 根据图 3-2-1 的依赖关系,这里给出了一个完整的 makefile 文件,这个例子很简单,由四个相 关行组成,我们将其命名为 mymakefile1。文件内容如下所示: main: main.o f1.o f2.o gcc -o main main.o f1.o f2.o main.o: main.c def1.h gcc -c main.c f1.o: f1.c def1.h def2.h gcc -c f1.c f2.o: f2.c def2.h def3.h gcc -c f2.c 注意,由于我们这里没有使用缺省名 makefile 或者 Makefile ,所以一定要在 make 命令行中 加上-f 选项。如果在没有任何源码的目录下执行命令“make -f Mymakefile1”的话,将收到下面的 消息: make: *** No rule to make target ‘main.c’, needed by ‘main.o’. Stop. Make 命令将 makefile 中的第一个目标即 main 作为要构建的文件,所以它会寻找构建该文件所 需要的其他模块,并判断出必须使用一个称为 main.c 的文件。因为迄今尚未建立该文件,而 makefile 又不知道如何建立它,所以只好报告错误。好了,现在建立这个源文件,为简单起见,我们让头文 件为空,创建头文件的具体命令如下: $ touch def1.h 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 131 - $ touch def2.h $ touch def3.h 我们将 main 函数放在 main.c 文件中,让它调用 function2 和 function3,但将这两个函数的定 义放在另外两个源文件中。由于这些源文件含有#include 命令,所以它们肯定依赖于所包含的头文 件。如下所示: /* main.c */ #include #include “def1.h” extern void function2(); extern void function3(); int main() { function2(); function3(); exit (EXIT_SUCCESS); } /* f1.c */ #include “def1.h” #include “def2.h” void function2() { } /* f2.c */ #include “def2.h” #include “def3.h” void function3(){ } 建好源代码后,再次运行 make 程序,看看情况如何: $ make -f Mymakefile1 gcc -c main.c gcc -c f1.c gcc -c f2.c gcc -o main main.o f1.o f2.o 好了,这次顺利通过了。这说明 Make 命令已经正确处理了 makefile 描述的依赖关系,并确定 出了需要建立哪些文件,以及它们的建立顺序。虽然我们在makefile 中首先列出的是如何建立main, 但是 make 还是能够正确的判断出这些文件的处理顺序,并按相应的顺序调用规则部分规定的相应 命令来创建这些文件。当这些命令执行时,make 程序会按照执行情况来显示这些命令。 如今,我们对 def2.h 加以变动,来看看 makefile 能否对此作出相应的回应: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 132 - $ touch def2.h $ make -f Mymakefile1 gcc -c f1.c gcc -c f2.c gcc -o main main.o f1.o f2.o 这说明,当 Make 命令读取 makefile 后,只对受 def2.h 的变化的影响的模块进行了必要的更新, 注意它的更新顺序,它先编译了 C 程序,最后连接生成了可执行文件。现在,让我们来看看删除目 标文件后会发生什么情况,先执行删除,命令如下: $ rm f1.o 然后运行 make 命令,如下所示: $ make -f Mymakefile1 gcc -c f1.c gcc -o main main.o f1.o f2.o 很好,make 的行为让我们非常满意。 7. makefile 中的宏 在 makefile 中可以使用诸如 XLIB、UIL 等类似于 Shell 变量的标识符,这些标识符在 makefile 中称为“宏”,它可以代表一些文件名或选项。宏的作用类似于 C 语言中的 define,利用它们来代表 某些多处使用而又可能发生变化的内容,可以节省重复修改的工作,还可以避免遗漏。 Make 的宏分为两类,一类是用户自己定义的宏,一类是系统内部定义的宏。用户定义的宏必须 在 makefile 或命令行中明确定义,系统定义的宏不由用户定义。我们首先介绍第一种宏。 这里是一个包含宏的 makefile 文件,我们将其命名为 mymakefile2,如下所示: all: main # 使用的编译器 CC = gcc #包含文件所在目录 INCLUDE = . # 在开发过程中使用的选项 CFLAGS = -g -Wall –ansi # 在发行时使用的选项 # CFLAGS = -O -Wall –ansi main: main.o f1.o f2.o $(CC) -o main main.o f1.o f2.o main.o: main.c def1.h $(CC) -I$(INCLUDE) $(CFLAGS) -c main.c f1.o: f1.c def1.h def2.h $(CC) -I$(INCLUDE) $(CFLAGS) -c f1.c 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 133 - f2.o: f2.c def2.h def3.h $(CC) -I$(INCLUDE) $(CFLAGS) -c f2.c 我们看到,在这里有一些注释。在 makefile 中,注释以#为开头,至行尾结束。注释不仅可以 帮助别人理解我们的 makefile,如果时间久了,有些东西我们自己也会忘掉,它们对 makefile 的编 写者来说也是很有必要的。 现在言归正传,先看一下宏的定义。我们既可以在 make 命令行中定义宏,也可以在 makefile 中定义宏。在 makefile 中定义宏的基本语法是: 宏标识符=值列表 其中,宏标识符即宏的名称通常全部大写,但它实际上可以由大、小写字母、阿拉伯数字和下 划线构成。等号左右的空白符没有严格要求,因为它们最终将被 make 删除。至于值列表,既可以 是零项,也可以是一项或者多项。如: LIST_VALUE = one two three 当一个宏定义之后,我们就可以通过$(宏标识符)或者${宏标识符}来访问这个标识符所代表的 值了。 在 makefile 中,宏经常用作编译器的选项。很多时候,处于开发阶段的应用程序在编译时是不 用优化的,但是却需要调试信息;而正式版本的应用程序却正好相反,没有调试信息的代码不仅所 占内存较小,进过优化的代码运行起来也更快。 对于 Mymakefile1 来说,它假定所用的编译器是 gcc,不过在其他的 UNIX 系统上,更常用的编 译器是 cc 或者 c89,而非 gcc。如果你想让自己的 makefile 适用于不同的 UNIX 操作系统,或者在 一个系统上使用其他种类的编译器,这时就不得不对这个 makefile 中的多处进行修改。 但对于 mymakefile2 来说则不存在这个问题,我们只需修改一处,即宏定义的值就行了。除了 在 makefile 中定义宏的值之外,我们还可以在 make 命令行中加以定义,如: $ make CC=c89 当命令行中的宏定义跟 makefile 中的定义有冲突时,以命令行中的定义为准。当在 makefile 文 件之外使用时,宏定义必须作为单个参数进行传递,所以要避免使用空格,但是更妥当的方法是使 用引号,如: $ make “CC = c89” 这样就不必担心空格所引起的问题了。现在让我们将前面的编译结果删掉,来测试一下 mymakefile2 的工作情况。命令如下所示: $ rm *.o main $ make -f Mymakefile2 gcc -I. -g -Wall -ansi -c main.c gcc -I. -g -Wall -ansi -c f1.c gcc -I. -g -Wall -ansi -c f2.c 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 134 - gcc -o main main.o f1.o f2.o 就像我们看到的那样,Make 程序会用相应的定义来替换宏引用$(CC )、$(CFLAGS )和 $(INCLUDE),这跟 C 语言中的宏的用法比较相似。 上面介绍了用户定义的宏,现在介绍 make 的内部宏。常用的内部宏有: $? :比目标的修改时间更晚的那些依赖模块表。 $@ :当前目标的全路径名。可用于用户定义的目标名的相关行中。 $< :比给定的目标文件时间标记更新的依赖文件名。 $* :去掉后缀的当前目标名。例如,若当前目标是pro.o,则$*表示pro。 8. 小结 我们在本章节中分别介绍了 make 程序的使用方法,makefile 中的依赖关系及规则等基础知识, 同时还介绍了一些常用的宏。 3. 3 嵌入式 Linux 开发 目前 Linux 已被广泛应用于信息家电、数据网络、工业控制、医疗卫生、航空航天等众多领域。 在嵌入式领域,由于价格低廉、结构小巧的各种微处理器的出现为外设连接提供了稳定可靠的硬件 架构,限制嵌入式系统发展的瓶颈就突出表现在软件方面。 尽管从 20 世纪 80 年代末开始,陆续出现了一些嵌入式操作系统,比较著名的有 VxWorks、pSOS、 Neculeus 和 Windows CE。但这些专用操作系统都是商业化产品,其高昂的价格使许多低端产品的 小公司望而却步,并且其源代码的封闭性也大大限制了开发者的积极性。 结合中国实情,当前国家对自主操作系统的大力支持,为开放源码的 Linux 的推广提供了广阔 的发展前景。对上层应用开发者而言,嵌入式系统需要的是一套高度简练、界面友善、质量可靠、 应用广泛、易开发、多任务,并且价格低廉的操作系统。Linux 对厂商不偏不倚而且成本极低,因此 很快成为用于各种设备的操作系统。如今,业界已经达成共识:即嵌入式 Linux 是大势所趋,其巨 大的市场潜力与酝酿的无限商机必然会吸引众多的厂商进入这一领域。 3. 3. 1 嵌入式 Linux 开发模型 1. 嵌入式 Linux 简介 Linux 正在嵌入式开发领域稳步发展。因为 Linux 使用 GPL,所以任何对将 Linux 定制于自 己特定开发板或 PDA、掌上机、可佩带设备感兴趣的人都可以从因特网免费下载其内核和应用程序, 并开始移植或开发。许多 Linux 改良品种迎合了嵌入式市场,它们包括 RTLinux(实时 Linux)、μ 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 135 - CLinux(用于非 MMU 设备的 Linux)、Montavista Linux(用于 ARM、MIPS、PPC 的 Linux 分发版)、 ARM-Linux(ARM 上的 Linux)和其他 Linux 系统。 数年来,“Linux 标准库”组织一直在从事对在服务器上运行的 Linux 进行标准化的工作。现在, 嵌入式计算领域也开始了这一工作。嵌入式 Linux 标准吸引了“Linux 标准库”以及 UNIX 组织中有 益的元素。 虽然大多数 Linux 系统运行在 PC 平台上,但 Linux 也可以作为嵌入式系统的操作系统。Linux 的安装和管理比 UNIX 更加简单灵活,这对于那些 UNIX 专家们来说又是一个优点,因为 Linux 中 有许多命令和编程接口同传统的 UNIX 一样。但是对于习惯于 Windows 操作系统的人来说,需要记 忆大量的命令行参数却是一个缺点。随着 Linux 社团的不断努力,Linux 的人机界面开发环境正在不 断完善。 典型的 Linux 系统经过打包,在拥有硬盘和大容量内存的 PC 机上运行,而嵌入式系统不需要 这么高的配置。一个功能完备的 Linux 内核要求大约 1 MB 内存。而 Linux 微内核只占用其中很 小一部分内存,包括虚拟内存和所有核心的操作系统功能在内,只需占用系统约 100KB 内存。只要 有 500KB 的内存,一个有网络栈和基本实用程序的完全的 Linux 系统就可以在一台 8 位总线(SX) 的 Intel 386 微处理器上运行得很好了。由于内存要求常常是由应用的需要所决定的,例如 Web 服 务器或者 SNMP 代理,Linux 系统甚至可以仅使用 256 KB ROM 和 512 KB RAM 进行工作。因此 它是一个瞄准嵌入式市场的轻量级操作系统。 与传统的实时操作系统(RTOS)相比,采用像嵌入式 Linux 这样的开放源码的操作系统的另 外一个好处是 Linux 开发团体看来会比 RTOS 的供应商更快地支持新的 IP 协议和其他协议。例 如,用于 Linux 的设备驱动程序要比用于商业操作系统的设备驱动程序多,如网络接口卡(NIC) 驱动程序以及并口、串口驱动程序。 Linux 操作系统本身的微内核体系结构相当简单。网络和文件系统以模块形式置于微内核的上 层。驱动程序和其他部件可在运行时作为可加载模块编译到或者是添加到内核。这为构造定制的可 嵌入式系统提供了高度模块化的构件方法。而在典型情况下该系统需结合定制的驱动程序和应用程 序以提供附加功能。 嵌入式系统也常常要求通用的功能,为了避免重复劳动,这些功能的实现运用了许多现成的程 序和驱动程序,它们可以用于公共外设和应用。Linux 可以在外设范围广泛的多数微处理器上运行, 并早已经具备了现成的应用库。 Linux 用于嵌入式的因特网设备也是很合适的,原因是它支持多处理器系统,该特性使 Linux 具 有了伸缩性。因而设计人员可以选择在双处理器系统上运行实时应用,提高整体的处理能力。例如, 可以在一个处理器运行 GUI,同时在另一个处理器上运行 Linux 系统。 在嵌入式系统上运行 Linux 的一个缺点是 Linux 体系提供实时性能需要添加实时软件模块。而 这些模块运行的内核空间正是操作系统实现调度策略、硬件中断异常和执行程序的部分。由于这些 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 136 - 实时软件模块是在内核空间运行的,因此代码错误可能会破坏操作系统从而影响整个系统的可靠性, 这对于实时应用将是一个非常严重的弱点。尽管如此,已经有许多嵌入式 Linux 系统的示例,可以 有把握地说,某种形式的 Linux 能在几乎任一台执行代码的计算机上运行。 2. 嵌入式 Linux 开发流程 在一个嵌入式系统中使用 Linux 开发,根据应用需求的不同有不同的配置开发方法,但是一般 都要经过如下的过程: 1.建立开发环境 操作系统这里我们使用 ubuntu8.04,选择定制安装或全部安装,通过网络下载相应的 GCC 交叉 编译器进行安装(我们这里使用 arm-Linux-gcc),或者安装产品厂家提供的交叉编译器。 2.配置开发主机 配置 minicom,一般的参数为波特率为 115200bps,数据位为 8 位,停止位为 1,无奇偶校验, 软件硬件流控设为无。在 Windows 下的超级终端的配置也是这样的。minicom 软件的作用是作为调 试嵌入式开发板的信息输出的监视器和键盘输入的工具。配置网络,主要是配置 NFS 网络文件系统, 需要关闭防火墙,简化嵌入式网络调试环境设置过程。 3.建立引导装载程序 BOOTLOADER 从网络上下载一些公开源代码的 BOOTLOADER,如 U-BOOT、BLOB、VIVI、LILO、ARM-BOOT、 RED-BOOT 等,根据自己具体的芯片进行移植修改。有些芯片没有内置引导装载程序,例如三星的 ARM7、ARM9 系列芯片,这样就需要编写开发板上 Flash 的烧写程序,网络上有免费下载的 Windows 下通过 JTAG 并口简易仿真器烧写 ARM 外围 Flash 芯片的烧写程序,也有 Linux 下的公开源代码的 J-Flash 程序。如果不能烧写自己的开发板,就需要根据自己的具体电路进行源代码修改。这是系统 正常运行的第一步。如果购买了厂家的仿真器当然比较容易烧写 Flash,这对于需要迅速开发自己产 品的人来说可以极大地提高开发速度,但是其中的核心技术是无法了解的。 4.下载别人已经移植好的 Linux 操作系统 如μCLinux、ARM-Linux、PPC-Linux 等,如果有专门针对所使用的 CPU 移植好的 Linux 操作系 统那是再好不过的,下载后再添加自己的特定硬件的驱动程序,进行调试修改,对于带 MMU 的 CPU 可以使用模块方式调试驱动,对于μCLinux 这样的系统则需编译进内核进行调试。 5.建立根文件系统 从 www.busybox.net 下载使用 BUSYBOX 软件进行功能裁减,产生一个最基本的根文件系统, 再根据自己的应用需要添加其他程序。默认的启动脚本一般都不会符合应用的需要,所以就要修改 根文件系统中的启动脚本,它的存放位置位于/etc 目录下,包括:/etc/init.d/rc.S、/etc/profile、 /etc/.profile 等,自动挂装文件系统的配置文件/etc/fstab,具体情况会随系统不同而不同。根文件 系统在嵌入式系统中一般设为只读,需要使用 mkcramfs、genromfs 等工具产生烧写映像文件。 6.建立应用程序的 Flash 磁盘分区 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 137 - 一般使用 JFFS2 或 YAFFS 文件系统,这需要在内核中提供这些文件系统的驱动,有的系统使用 一个线性 Flash(NOR 型)512KB~32MB,有的系统使用非线性 Flash(NAND 型)8~512MB,有 的两个同时使用,需要根据应用规划 Flash 的分区方案。 7.开发应用程序 应用程序可以放入根文件系统中,也可以放入 YAFFS、JFFS2 文件系统中,有的应用不使用根 文件系统,直接将应用程序和内核设计在一起,这有点类似于μCOS-II 的方式。 8.烧写内核、根文件系统、应用程序 9.发布产品 3. 3. 2 Bootloader、内核、文件系统 1. Linux 操作系统的引导程序 z Bootloader 的概念 Bootloader 就是在引导操作系统内核或用户应用程序之前运行的一段程序,其主要功能是完成 处理器和周边电路正常运行所需要的初始化工作,建立内存空间的映射(包括设置系统堆栈和系统 启动参数区等),将系统的软、硬件环境带到一个合适的状态,并可以从 ROM,FLASH 等非易失存 储器上,甚至网络上加载操作系统,以便最终引导操作系统或用户应用程序。嵌入式 Linux 的 Flash 分区如图 3-3-1 所示。 图 3-3-1 嵌入式 Linux 的 Flash 分区 对于常见的 PC(Personal Computer)来说,操作系统一般存放在磁盘介质上,Bootloader 的 主要任务是根据操作系统的位置和大小等信息从磁盘读入操作系统内核并加以引导,而像一些基本 的硬件初始化等功能则由 BIOS 本身来完成。由于 BIOS 的存在,大大简化了 PC 上的 Bootloader 对 硬件的检测和初始化过程。因此,PC 上的 Bootloader 和硬件的关系就比较松散了,Bootloader 本身 也相应地具有很强的通用性。 然而对于嵌入式系统来说,由于其系统的多样性,而且通常都缺少一个类似于 BIOS 的通用底 层软件支持,因此嵌入式系统上的 Bootloader 的功能要求更加多样化,不仅包括从存储介质中读取 操作系统并加以执行,还必须担负其最初的硬件初始化和一些硬件的检测功能(即 PC 机上 BIOS 所 完成的部分功能)。 有些 Bootloader 不仅具有类似 PC 机上 BIOS 功能,而且还具有一定的调试功能,用户可以在 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 138 - Monitor 界面下与目标系统进行交互,了解目标系统的硬件状态、更新 Bootloader 版本及操作系统 版本等等功能。 z Bootloader 的启动过程 为了满足 Bootloader 的专有性和灵活性需要,大多数 Bootloader 的代码都由 stage1 和 stage2 两部分组成。其中,依赖于 CPU 体系结构的代码,例如 CPU 及存储管理部件初始化代码等,通常都 放在 stage1 中,并用汇编语言来实现,以达到短小精悍的目的;stage2 则通常用 c 语言等高级语言 来实现,这样可以实现比较复杂的功能,并且使程序具有更好的可读性和可移植性。 Bootloader 的 stage1 通常包括以下关键步骤: 硬件设备初始化,包括配置处理器中的关键寄存器和一些必要的硬件。为加载 Bootloader 的 stage2代码准备RAM空间拷贝,Bootloader的stage2的代码到RAM空间中设置好堆栈跳转到stage2 的 C 程序入口点。 Bootloader 的 stage2 通常包括以下步骤: 初始化本阶段要使用到的硬件设备检测系统内存映射(memory map)。将操作系统映像文件从 Flash 等非易失性存储器或者网络文件系统加载到 RAM 空间中。为操作系统设置启动参数跳转到操 作系统入口执行。 z Bootloader 的操作模式 一般来讲,Bootloader 有两种操作模式(Operation Mode),即启动加载模式(BootingLoading Mode)和下载模式(Downloading Mode)。这种操作模式的划分仅仅对于开发人员有意义,从最终 用户的角度看,Bootloader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载 工作模式的区别。 ¾ 启动加载模式:这种模式也称为自主模式(AutonomousMode),即 Bootloader 从目标机上 的非易失存储设备上将操作系统加载到 RAM 中运行,整个过程并不需要用户的介入。这种 模式是 Bootloader 的正常工作模式,也是正式发布的嵌入式产品中 Bootloader 的默认操作 模式。 ¾ 下载模式:在这种模式下,目标机上的 Bootloader 将通过串口连接或网络连接等通信方式 从开发主机(Host)上下载文件(包括内核映像、根文件系统映像等)并进行操作系统的 引导。从主机下载的文件通常首先被 Bootloader 保存到目标机的 RAM 中,然后再写到目 标机上的 FLASH 等非易失存储设备中。Bootloader 的这种模式通常在第一次安装内核与根 文件系统时被使用。此外,以后的系统更新(SystemUPdate)也会使用这种工作模式。工 作于这种模式下的 Bootloader 通常都需要向它的终端用户提供一个简单的命令行接口。 像 Blob 或 U-Boot 等这样功能强大的 Bootloader 通常同时支持以上两种工作模式,而且允许用 户在这两种工作模式之间进行切换。比如,U-Boot 在启动时处于正常的启动加载模式。但是,U-Boot 也会提供 3 秒(该值在头文件中由宏 CONFIGBOOTDELAY 所定义)时间,等待终端用户的干预(按 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 139 - 下任意键),从而将 U-Boot 切换到下载模式。如果在 3 秒内没有用户按键,则 U-Boot 继续启动,加 载并引导操作系统。 2. Linux 内核 内核是 Linux 的心脏,它是在引导时装入的程序,用来提供用户层程序和硬件之间的接口,执 行发生在多任务系统中的实际任务转换,处理读写磁盘的需求,处理网络接口,以及管理内存等。 z Linux 内核的任务 Linux 将系统的一些关键性程序分离出来构成所谓操作系统内核。Linux 内核必须完成下面的一 些任务:对文件系统的读写进行管理,把文件系统的操作映射成对磁盘或其它块设备的操作;管理 程序的运行。为程序分配资源,并且处理程序之间的通讯:管理存储器,为程序分配内存,并且管 理虚拟内存;管理输入输出,将设备映射成设备文件;管理网络。 Linux 以统一的方式支持多任务,而这种方式对用户进程是透明的,每一个进程运行起来就好像 只有它一个进程在计算机上运行一样,独占内存和其它的硬件资源。实际上内核在并发地运行几个 程序,并且能够让几个进程公平合理地使用硬件资源,也能使各进程之间互不干扰安全地运行。 z Linux 内核的组成 Linux 内核主要由 5 个子系统组成:进程调度(SCHED),内存管理(MM),虚拟文件系统(VFs), 网络接口(NET),进程间通信(IPC)。 进程调度(SCHED),负责控制进程对 CPU 的访问,当需要选择下一个进程运行时,由调度程 序选择最值得运行的进程。可运行进程是仅等待 CPU 资源的进程,等待其它资源的进程是不可运行 进程。Linux 使用了比较简单的基于优先级的进程调度算法选择新的进程。 内存管理(MM),MM 的作用是允许多个进程安全的共享主内存区域。Linux 的内存管理支持虚 拟内存,即在计算机中运行程序的代码、数据、堆栈的总量可以超过实际内存的大小,操作系统只 是把当前使用的程序块保留在内存中,其余的程序块则保留在磁盘中。必要时,操作系统负责在磁 盘和内存间交换程序块。内存管理从逻辑上分为硬件无关部分和硬件有关部分。硬件无关部分提供 了进程的映射和逻辑内存的对换;硬件相关的部分为内存管理硬件提供了虚拟接口。 虚拟文件系统(VFs),VFS 隐藏了各种硬件的具体细节,为所有的设备提供了统一的接口。VFS 提供了多达数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文 件系统指 Linux 所支持的文件系统,如 ext2,fat 等,设备驱动程序指为每一种硬件控制器所编写的 设备驱动程序模块。 网络接口(NET),NET 提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为 网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议;网络设备驱动程序 负责与硬件设备通信,每一种可能的硬件设备都有相应的设备驱动程序。 进程间通信(IPC),IPC 支持进程间各种通信机制,每个子系统都需要挂起或恢复进程,所以 其它的子系统都要依赖它。一般情况下,当一个进程等待硬件操作完成时,它就被挂起,操作真正 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 140 - 完成后才被恢复执行。 z 各个子系统之间的依赖关系 进程调度与内存管理之间的关系:这两个子系统互相依赖。在多道程序环境下,程序要运行必 须为之创建进程,而创建进程的第一件事情,就是将程序和数据装入内存。 进程间通信与内存管理的关系:进程问通信子系统要依赖内存管理支持共享内存通信机制,这 种机制允许两个进程除了拥有自己的私有空间,还可以存取共同的内存区域。 虚拟文件系统与网络接口之间的关系:虚拟文件系统利用网络接口支持网络文件系统,也利用 内存管理支持 RAMDISK 设备。 内存管理与虚拟文件系统之间的关系:内存管理利用虚拟文件系统支持交换,交换进程定期由 调度程序调度,这也是内存管理依赖于进程调度的唯一原因。当一个进程存取的内存映射被换出时, 内存管理向文件系统发出请求,同时挂起当前正在运行的进程。 除了这些依赖关系外,内核中的所有子系统还要依赖于一些共同的资源。这些资源包括所有子 系统都用到的过程。例如,分配和释放内存空间的过程,打印警告或错误信息的过程,还有系统的 调试例程等等。 3. Linux 内核各部分的工作机制 z 进程调度 进程调度是 Linux 操作系统最基本的抽象概念之一。一个进程就是处于执行期的程序(目标码放 在某种存储介质上)。但进程并不仅仅局限于一段可执行程序代码,通常进程还要包括其它资源,象 用来存放全局变量的数据段、打开的文件、挂起的信号等,还包括地址空间及一个或几个执行线程。 执行线程简称线程,是进程中活动的对象,每个线程都拥有一个独立的程序计数器、进程栈和一组 进程寄存器。内核调度的对象是线程不是进程,但是 Linux 中对线程和进程并不特别区分。 z 内存管理 内存管理子系统是操作系统的重要部分。从计算机发展早期开始,就存在对于大于系统中物理 能力的内存需要。为了克服这种限制,开发了许多种策略,其中最成功的就是虚拟内存。虚拟内存 通过在竞争进程之间共享内存的方式使系统显得拥有比实际更多的内存。 z 虚拟文件系统 VFS 是 Linux 内核中的一个软件层,用于给用户空间的程序提供文件系统接口。它也提供了内 核中的一个抽象功能,允许不同的文件系统共存。这样,文件系统的代码就分成了两部分:上层用 于处理系统内核的各种表格和数据结构;而下层用来实现文件系统本身的函数,并通过 VFS 来调用。 VFS 接口则由一系列相对高级的操作组成,这些操作由和文件系统无关的代码调用,并且由不 同的文件系统执行。其中最主要的结构有 inode_operations 和 file_operations。file_system_type 是 系统内核中指向真正文件系统的结构。每挂接一次文件系统,都将使用 file_systemjype 组成的数组。 相关文件系统的 read_super 函数负责填充 super_block 结构。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 141 - 4. Linux 内核中常用的数据结构 ¾ task_struct,Linux 内核利用一个数据结构(task_struct)代表一个进程,代表进程的数据 结构指针形成了一个 task 数组(Linux 中,任务和进程是相同的术语),这种指针数组有时 也称为指针向量。这个数组的大小由 NR_TASKS 决定(默认为 512),表明 Linux 系统中最 多能同时运行的进程数目。当建立新进程的时候,Linux 为新进程分配一个 task_struct 结 构,然后将指针保存在 task 数组中。调度程序则一直维护着指向当前正在运行进程的 current 指针。 ¾ mm_struct,每个进程的虚拟内存由一个 mm_struct 结构来代表,该结构实际上包含了当 前执行映像的有关信息,并且包含了一组指向 vm_area_struct 以结构的指针, vm_area_struct 结构描述了虚拟内存的一个区域。 ¾ inode,虚拟文件系统(VFS)中的文件、目录等均由对应的索引节点(inode)代表。每个 VFS 索引节点中的内容由文件系统专属的例程提供。VFS 索引节点只存在于内核内存中, 实际保存于 VFs 的索引节点高速缓存中。如果两个进程用相同的进程打开,则可以共享 inode 的数据结构,这种共享是通过两个进程中数据块指向相同的 inode 完成。 5. Linux 的文件系统 Linux 内核源代码位于/usr/sre/linux 目录下: ¾ /Documentation,这个目录下面没有内核代码,只有内核文档。有一部分内核文档,例如 文件系统,在该目录下有完整的文档;而另外一部分内核,例如进程调度,则没有文档。 ¾ /arch,这个目录下的所有子目录中都是体系结构相关的代码。每个体系结构特有的子目录 下都又至少包含三个子目录。/kernel,存放支持体系结构特有的诸如信号量处理和 SMP 之 类特征的实现;/Iib,存放高速的体系结构特有的诸如 strlen 和 memcpy 之类的通用函数 的实现;/mm,存放体系结构特有的内存管理程序的实现。 除了这三个子目录以外,大多数体系结构在必要的情况下还都有一个/boot 子目录,该目录中包 含有在这种平台上启动内核所使用的部分或全部平台特有代码。这些启动代码中的部分或全部也可 以在平台特有的内核目录下找到。 下面几个是 arch 目录下的子目录: z arch/alpha,Linux 内核到基于 DEC Alphs CPU 工作站的移植。 z arch/arm,Linux 内核到 ARM 系列 CPU 的移植。 z areh/i386,Linux 内核到 Intel 的 80386、80486、Pentium 等等结构 CPU 的移植。它 还包括了对 AMD,Cyrix 和 IDT 等公司的一些兼容产品的支持。 z arch/m68k,Linux 内核到 Motorola 的 680x0CPU 系列的移植。该版本可以提供对基于 从 68020(只要它同内存管理单元(MMU)68851 一起使用)到 68060 的一切机器的 支持。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 142 - z arch/mips,Linux 内核到 MIPS 的 CPU 系列的移植。虽然有其它几个厂商也使用 MIPS 开发了一些系统,但是基于这种 CPU 的最出名的机器是 Silicon Graphics(SGI)工作 站。 z arch/ppc,Linux 内核到 Motorola/IBM 的 PowerPC 系列 CPU 的移植。包括对基于 PowerPC 的 Macintosh 和 Amiga 以及 BeBox、IBM 的 RS/6000 等其它一些机器的支持。 z arch/sparc,Linux 内核到 32 位 SPARC CPU 的移植。包括对从 Sun SPARC1 到 SPARC20 的全部支持。 z arch/spare64,Linux 内核到基于 64 位 SPARC CPU(UltraSPARC 系列)系统的移植。 所能够支持的机器包括 Sun 的 Ultra 1,Ultra 2 和更高配置的机器,直到 Sun 的最新 产品 Enterprise 10000。 ¾ /drivers,这个目录在 Linux 内核中所占比例最大的,包含的代码占整个内核发行版本代码 的一半以上。它包括显卡、网卡、SCSI 适配器、软盘驱动器,PCI 设备和其它任何你可以 说出的 Linux 支持的外围设备的软件驱动程序。 ¾ /fs,Linux 支持的所有文件系统在这个目录下面都有一个对应的子目录。一个文件系统 (filesystem)是存储设备和需要访问存储设备的进程之间的媒介。 ¾ /include,这个目录包含了 Linux 源程序树中大部分的包含.h 文件。 ¾ /init,这个目录下面只有两个文件 version.c 和 main.c,比较重要的一个是 main.c,它包含 了大部分协调内核初始化的代码。 ¾ /ipc,这个目录下的文件实现了进程间通一讯。 ¾ /kernel,这个目录中包含了 Linux 中最重要的部分,实现平台独立的基本功能。这部分内 容包括进程调度(kemel/sched.c )以及创建和撤销进程的代码(kernel/fork.c 和 kernel/exit.c)。 ¾ /lib,这个目录包含两部分的内容。lib/inflate.c 中的函数能够在系统启动时展开经过压缩的 内核。/lib 目录下剩余的其它文件实现一个标准 C 库的有用子集。这些实现的焦点集中在 字符串和内存操作的函数(strlen,mmcpy 和其它类似的函数)以及有关 sprintf 和 atoi 的 系列函数上。这些文件都是使用 C 语言编写的,因此在新的内核移植版本中可以立即使用 这些文件。 ¾ /mm,这个目录包含了体系结构无关的内存管理代码。 ¾ /net,这个目录包含了 Linux 应用的网络协议代码,例如 ApPleTalk,TCP/IP,IPX 等。 ¾ /scripts,这个目录下没有内核代码,它包含了用来配置内核的脚本。当运行 make menuconfig 或者 make xconfig 之类的命令配置内核时,用户就是和位于这个目录下的脚 本进行交互的。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 143 - 3. 3. 3 Linux 驱动开发 1. Linux 的设备驱动程序概述 z 设备驱动程序概述 设备驱动可以理解为操作系统的一部分,对于一个特定的硬件设备来说,其对应的设备驱动程 序是不同的。比如网卡、声卡、键盘、鼠标、显卡等。对于操作系统来说,挂接的设备越多,所需 要的设备驱动程序也越多。操作系统在没有设备驱动程序支持下是无法正常支配硬件行为的,这就 需要独立开发一套适合自己产品的设备驱动。正是操作系统留下了扩展设备驱动的接口,才有了现 在支持各种应用场合的硬件设备的蓬勃发展。对于嵌入式开发,更没有通用的驱动程序可以使用。 因此,驱动程序开发是整个嵌入式系统设计过程中必不可少的一部分。 z Linux 下的驱动程序 Linux 通过设备驱动程序为应用程序提供了统一抽象的接口,从而隐藏了大量不同设备之间的区 别和细节。在 Linux 中所有对硬件设备的操作和通常的文件一样,利用标准的系统调用可在设备上 进行打开、关闭、读取或写入操作。系统中的每个设备由“设备特殊文件”来代表。例如,/dev/sdal 则代表系统中 SCSI 硬盘上的第一个分区。 z Linux 下驱动程序完成的功能 在 Linux 操作系统中,驱动程序是操作系统内核与硬件设备的直接接口,它屏蔽了硬件的细节, 是内核的一部分,完成以下功能:对设备初始化和释放;对设备进行管理,包括实时参数设置以及 提供对设备的操作接口;负责应用程序和设备文件间的数据传输;检测处理设备出现的错误。 z Linux 下驱动程序的特点 Linux 核心中虽然存在许多不同的设备驱动程序,但是它们具有一些相同的特点: z 核心代码 设备驱动程序是核心的一部分,象核心中其它代码一样,出错将导致系统的严重损伤。一个编 写不当的设备驱动程序甚至能够使系统崩溃并导致文件系统的破坏和数据的丢失。 z 核心接口 设备驱动程序必须为 Linux 核心或者其从属子系统提供一个标准接口。例如终端驱动可以为 Linux 核心提供一个文件 I/O 接口而 SCSI 设备驱动则为 SCSI 子系统提供一个 SCSI 设备接口,同时 此子系统为核心提供了文件 UO 和 buffer cache 接口。 z 核心机制与服务 设备驱动程序可以使用标准的核心服务,如内存分配、中断发送和等待队列等。 z 动态加载 多数的 Linux 设备驱动程序可以在核心模块发出加载请求时进行加载,同时在不再使用设备时 进行卸载。这样核心能有效地利用系统的资源。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 144 - z 可配置 Linux 设备驱动程序可以连接到核心中。当核心被编译时,哪些驱动程序被连入核心是用户自己 可以进行配置的。 z 动态性 当系统启动及设备驱动程序初始化的时候查找它所控制的硬件设备。如果某个设备的驱动程序 为一个空过程并不会有什么问题。此时此设备驱动程序仅仅是一个冗余的程序,它除了会占用少量 的系统内存外对系统并没有其它的危害。 2. 设备驱动的结构 z 设备文件和设备号 每个设备文件都有其文件属性(c/b),表示是字符设备还是块设备。另外每个文件都有两个设 备号,第一个是主设备号,标识驱动程序,取值空间是 0 到 255,其中 USB 设备是 180。第二个是 从设备号,标识使用同一个设备驱动程序的、不同的硬件设备。设备文件的主设备号必须与设备驱 动程序在登记时申请的主设备号一致,否则用户进程将无法访问驱动程序。 设备驱动程序是操作系统内核和机器硬件之间的接口,分为驱动程序与操作系统内核的接口、 驱动程序与系统引导的接口、驱动程序与设备的接口。设备驱动程序为应用程序屏蔽了硬件的细节, 这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以像操作普通文件一样对硬件设备 进行操作。 z 设备驱动程序的组成 自动配置和初始化子程序,负责检测所要驱动的硬件设备是否存在和是否能正常工作。如果该 设备正常,则对这个设备及其相关的设备驱动程序需要的软件状态进行初始化。这部分驱动程序仅 在初始化的时候被调用一次。 服务与 I/O 的子程序,又称驱动程序的上半部分。调用这部分是系统调用的结果。在执行这部 分程序的时候,系统仍认为和进行调用的进程属于同一个进程,只是由用户态变成了核心态,并具 有进行此系统调用的用户程序的运行环境,所以可以在其中调用 sleep()等与进程运行环境有关的函 数。 中断服务程序,又称驱动程序的下半部。在 Linux 系统中并不是直接从中断向量表调用设备驱 动程序的中断服务子程序,而是由 Linux 系统来接收硬件中断,再由系统调用中断子程序。中断可 以在任何一个进程运行时产生,因而在中断服务程序被调用时,不能依赖于任何进程的状态,也就 不能调用任何与进程运行有关的函数。 z 驱动程序关键数据结构 z file_operations 结构 在系统内部,I/O 设备的存取通过一组固定的入口点来进行,这组入口点是由每个设备的驱动 程序提供的。具体到 Linux 系统,设备驱动程序提供的这组入口点由一个文件操作结构来向系统进 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 145 - 行说明。file_operations 结构定义于 linux/fs.h 文件中。 struct file_oPerations { int(*lseek)( struct inode*inode,struct file*filp,off_t off,int pos); int(*read)(struct inode*inode,struct file*filP,char*buf,int count); int(*write)(struct inode*inode,struct file*filp,const char*buf,int count); int(*readdir)(struct inode*inode,struct file*fllP,struct dirent*dirent,int count); int(*select)(srrucit inode*inode,struct file*filP,int sel_tyPe,seleet_table*wait); int(*ioctl)(struct inode*inode,struct file*filP,unsigned int cmd,unsigned int arg); int(*mmaP)(void); int(*oPen)(struct inode*inode,struet file*filP); int(*release)(struet inode*inode,structfile*filP); int(*fasyne)(struct inode*inode,struetfile*filP); }; pen 入口点,用来打开设备准备 I/O 操作。对字符特别设备文件进行打开操作,都会调用设备 的 open 入口点。open 子程序必须对将要进行的 I/O 操作做好必要的准备工作,如清除缓冲区等。 如果设备是独占的,即同一时刻只能有一个程序访问此设备,则 open 子程序必须设置一些标志以表 示设备处于忙状态。 close 入口点,用来关闭一个设备。当最后一次使用设备终结后,调用 close 子程序。独占设备 必须标记设备可再次使用。 read 入口点,用来从设备上读数据。对于有缓冲区的 I/O 操作,一般是从缓冲区里读数据。对 字符特别设备文件进行读操作将调用 read 子程序。 write 入口点,用来往设备上写数据。对于有缓冲区的 I/O 操作,一般是把数据写入缓冲区里。 对字符特别设备文件进行写操作将调用 write 子程序。 iocti 入口点,用来执行读、写之外的操作。 select 入口点,用来检查设备,看数据是否可读或设备是否可用于写数据。select 系统调用在检 查与设备特别文件相关的文件描述符时使用 select 入口点。如果设备驱动程序没有提供上述入口点 中的某一个,系统会用缺省的子程序来代替。对于不同的系统,也还有一些其它的入口点。 在用户自己的驱动程序中,首先要根据驱动程序的功能,完成 file_operations 结构中函数实现。 不需要的函数接口可以直接在 file_operations 结构中初始化为 NULL。File_operations 变量会在驱动 程序初始化时,注册到系统内部。当操作系统对设备进行操作时,会调用驱动程序注册的 file_perations 结构中的函数指针。 z inode 和 file 数据结构 inode 数据结构中存放磁盘上的一个文件或目录信息; file 数据结构主要被与文件系统对应的设备驱动程序使用,它提供有关被打开的文件的信息。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 146 - 3. Linux 设备驱动程序的分类 Linux 支持三类硬件设备,字符设备(character deviees)、块设备(bloke devices)以及网络接 口(network interfaces)。Linux 内核必须能够用标准的方式和设备驱动程序交互,每一类的设备驱 动程序都提供了通用的接口供内核在需要请求它们的服务的时候使用。这些通用的接口意味着内核 可以完全相同地看待通常是非常不同的设备和它们的设备驱动程序。例如,SCSI 和 IDE 磁盘的行为 非常不同,但是 Linux 内核对它们使用相同的接口。 Linux 非常地动态,每一次 Linux 内核启动,它都可能遇到不同的物理设备从而需要不同的设备 驱动程序。Linux 允许在建立内核时通过配置脚本,将设备驱动程序包含在内核中。在系统启动时, 这些设备驱动程序初始化,此时它们可能没有发现自己可以控制的任何硬件。其它驱动程序可以在 需要的时候作为内核模块加载。为了处理设备驱动程序的这种动态特性,设备驱动程序要在它们初 始化时向内核登记。Linux 维护己经登记的设备驱动程序列表,作为和它们接口的一部分。这些列表 包括了例程指针和支持这一类设备的接口信息。 z 字符设备的驱动程序 字符设备以字节为单位进行数据处理,一般不使用缓存技术。大多数字符设备仅仅是数据通道, 只能按顺序读/写,不支持随机读写,也不允许查找。典型的字符设备有鼠标、键盘、触摸屏、手写 板、串口、USB 等。字符设备驱动的源码一般在目录/drivers/char 中。 字符设备和普通文件之间的主要区别是,普通文件可以来回读/写,而大多数字符设备仅仅是数 据通道,只能顺序读/写。但是不能完全排除字符设备模拟普通文件读/写过程的可能性。字符设备 是 Linux 最简单的设备,可以象文件一样访问。应用程序使用标准系统调用打开、读、写和关闭, 完全好像这个设备是一个普通文件一样。初始化字符设备时,它的设备驱动程序向 Linux 登记,并 在字符设备向量表中增加一个 device_struct 以数据结构条目,这个设备的主设备标识符用做这个向 量表的索引。一个设备的主设备标识符是固定的。一个 device_struct 数据结构包含两个指针,即一 个登记的设备驱动程序的名称的指针和一个指向一组文件操作的指针。 z 块设备的驱动程序 块设备数据可以按可寻址的块为单位进行处理,块的大小通常为 512B 到 32KB 不等。大多数块 设备允许随机访问,而且常常采用缓存技术。典型的块设备有硬盘、光盘驱动器等。 块设备也支持像文件一样被访问。为打开块特殊文件提供了一组正确的文件操作集,这种机制 与字符设备的十分相似。Linux 用 blkdevs 向量表维护已经登记了的块设备文件。与 chrdevs 向量表 一样,使用块设备的主设备号作为该向量表的索引。它的条目也是 device_struct 数据结构。和字符 设备不同,块设备进行了分类。SCSI 设备是其中的一类,而 IDE 设备是另一类。 每一个块设备驱动程序都必须提供普通的文件操作接口,同时提供对 buffer cache 的接口。每 一个块设备驱动程序必须填充它在 blk_dev 向量表中的 blk_dev_struct 数据结构。同样,这个向量 表的索引还是设备的主设备号。这个 blk_dev_struct 数据结构包括一个请求例程地址和一个指针, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 147 - 该指针指向一个 request 数据结构列表,每一个 request 数据结构都表示一个来自 buffer cache 的、 要求设备驱动程序读写一块数据的请求。 每当 buffer cache 希望从一个登记的设备读一块数据,或希望向一个登记的设备写一块数据时, 它就在它的 blk_dev_Struc 中增加一个 request 数据结构。一旦设备驱动程序完成了一个请求,该请 求的每一个 buffer_head 结构都必须从 request 结构中删除,并被标记为最新的,然后解锁。对于 buffer_head 的解锁会唤醒任何正在等待这个阻塞操作完成的进程。这样的例子包括文件解析的时 候,EXT2 文件系统必须从包括这个文件系统的块设备上,读取包括下一个 EXTZ 目录条目的数据块, 这个进程会在 buffer_head(将要包括下一个 EXT2 目录条目)上睡眠,直到设备驱动程序唤醒它。 这个 request 数据结构会被标记为空闲,从而可以被另一个块请求使用。 z 网络设备接口驱动程序 网络接口用于网络通信,可能针对某个硬件,如网卡或是纯软件。网络接口只是面向数据包而 不是面向数据流,所以内核的处理也不同,没有映射成任何设备文件而是按照 Unix 标准给它们分配 一个唯一的名字。 为了屏蔽网络环境中物理网络设备的多样性,Linux 对所有的物理设备进行抽象并定义了一个统 一的概念,称之为接口(Interface)。所有对网络硬件的访问都是通过接口进行的,接口提供了一个 对所有类型的硬件一致化的操作集合来处理基本数据的发送和接收。一个网络接口被看作是一个发 送和接收数据包(packets)的实体。对于每个网络接口,都用一个 device 的数据结构表示,有关该 数据结构的具体内容,将在本文的后面详细介绍。通常,网络设备是一个物理设备,如以太网卡, 但软件也可以作为网络设备,如回送设备(loopback)。在内核启动时,通过网络设备驱动程序,将 登记存在的网络设备。设备用标准的支持网络的机制来转递收到的数据到相应的网络层。所有被发 送和接收的包都用数据结构 Sk_buff 表示。这是一个具有很好的灵活性的数据结构,可以很容易增 加或删除网络协议数据包的首部。 网络设备作为 Linux 的三类设备之一,它有其非常特殊的地方,与字符设备及块设备都有很大 的不同:网络接口不存在于 Linux 的文件系统中,而是用一个 device 数据结构表示的;而每一个字 符设备或块设备则在文件系统中都存在一个相应的特殊设备文件来表示该设备,如/dev/hdal、 /dev/sdal、/dev/tty1 等。网络设备在做数据包发送和接收时,直接通过接口访问,不需要进行文件 的操作;而对字符设备和块设备的访问都需通过文件操作界面。 网络接口是在系统初始化时实时生成的,对于核心支持的但不存在的物理网络设备,将不可能 有与之相对应的 device 结构。而对于字符设备和块设备,即使该物理设备不存在,在/dev 下也必定 有相应的特殊文件与之相对应。且在系统初始化时,核心将会对所有内核支持的字符设备和块设备 进行登记,初始化该设备的文件操作界面(struct_file_operations),而不管该设备在物理上是否存 在。 以上两点是网络设备与其它设备之间存在的最主要的不同。然而,它们之间又有一些共同之处, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 148 - 如在系统中一个网络设备的角色和一个安装的块设备相似。一个块设备在 blk_dev 数组及核心其它 的数据结构中登一记自己,然后根据请求,通过自己的 request_function 函数“发送”和“接收” 数据块。相似地,为了能与外面世界进行数据交流,一个网络接口也必须在一个特殊的数据结构中 登记自己。 在系统内核中,存在字符设备管理表 chrdevs 和块设备管理表 blkdevs,这两张保存着指向 file_operations 结构的指针的设备管理表,分别用来描述各种字符驱动程序和块设备驱动程序。类 似地,在内核中也存在着一张网络接口管理表 dev_base,但与前两张表不同,dev_base 是指向 device 结构的指针,因为网络设备是通过 device 数据结构来表示的。dev_base 实际上是一条 device 结构 链表的表头,在系统初始化完成以后,系统检测到的网络设备将自动地保存在这张链表中,其中每 一个链表单元表示一个存在的物理网络设备。当要发送数据时,网络子系统将根据系统路由表选择 相应的网络接口进行数据传输,而当接收到数据包时,通过驱动程序登记的中断服务程序进行数据 的接收处理。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 149 - 第四章 嵌入式 Linux 系统开发 本章节主要介绍嵌入式 Linux 下开发知识的详解,包括安装交叉编译工具、Bootloader、Linux 内核移植、文件系统、Linux 内核调试、Linux 映像固化与运行等一些基础知识。 4. 1 交叉编译工具 交叉编译是嵌入式开发过程中的一项重要技术,简单地说,就是在一个平台上生成另一个平台 上的可执行代码。交叉编译这个概念的出现和流行是和嵌入式系统的广泛发展同步的。我们常用的 计算机软件,都需要通过编译的方式,把使用高级计算机语言编写的代码(比如 C 代码)编译 (compile)成计算机可以识别和执行的二进制代码。交叉编译的主要特征是某机器中执行的程序代 码不是由本机编译生成,而是由另一台机器编译生成,一般把前者称为目标机,后者称为主机,如 图 4-1-1 所示。这是因为目标平台上不允许或不能够安装开发所需要的编译器,而又需要该编译器 的某些特征;有时是因为目标平台上的资源贫乏,无法运行开发所需要的编译器;有时是因为目标 平台还没有建立,连操作系统都没有,根本谈不上运行编译器。 图 4-1-1 交叉开发模型 交叉编译的引入主要是由于不同架构的 CPU 的指令集不相同,比如在 X86 架构的处理器上编译 运行的程序,不能直接在 XScale 构架处理器上运行,不同的 CPU 有不同的编译器;另一方面,编译 HOST 内核映像 根文件目录 TARGET 内核映像 各种 连接 下载内核映像 挂接 NFS 文件系统 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 150 - 器本身也是程序,也要在某一个 CPU 平台上运行,而嵌入式目标系统不能提供足够的资源,不能运 行编译器。 这里所谓的平台,实际上包含两个概念:体系结构(Architecture)和操作系统(Operating System)。同一个体系结构可以运行不同的操作系统,同样,同一操作系统也可以在不同的体系结构 上运行。例如,常说的 X86 Linux 平台实际上是 Intel X86 体系结构和 Linux for X86 操作系统的统称, 而 X86 WinNT 平台实际上是 Intel X86 体系结构和 Windows NT for X86 操作系统的统称。 要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(cross compilation tool chain),然后用这个交叉编译工具链编译我们的源代码,最终生成可在目标平台上运行的代码。 运行于宿主机上的交叉开发环境至少必须包含编译调试模块,其编译器为交叉编译器。宿主机 一般为基于 x86 体系的台式计算机,而编译出的代码必须在 ARM 体系结构的目标机上运行,这就是 所谓的交叉编译。在宿主机上编译好目标代码后,通过宿主机到目标机的调试通道将代码下载到目 标机,然后由运行于宿主机的调试软件控制代码在目标机上运行调试。为了方便调试开发,交叉开 发环境一般为一个整合编辑、编译汇编链接、调试、工程管理及函数库等功能模块的集成开发环境 (Integrated Development Environment,IDE)。 4. 1. 1 宿主机与交叉编译 1.交叉编译器及交叉编译环境的组成 当提到交叉编译器时,不仅仅是指将一种编程语言的代码转换成对象代码的软件,还指其他必 要的开发工具: ¾ 汇编器:编译器工具链后端的一部分; ¾ 连接器:编译器工具链后端的另一部分; ¾ 用于处理可执行程序和库的一些基本工具,例如 Binutils 等。 一般采用装有 Linux 操作系统的 PC 机作为开发系统,PC 机通常为 X86 体系结构,目标板为 ARM 系列的体系结构。PC 机的 CPU 处理速度及系统资源一般要比 ARM 的处理速度快得多,所以把 PC 机作为开发系统,利用交叉编译,生成可在 ARM 开发板上运行的二进制程序,然后通过网络或其他 方式下载到开发板后运行。 Linux 下的交叉编译环境如图 4-1-2 所示,主要包括以下几部分: ¾ 针对目标系统的编译器 GCC; ¾ 针对目标系统的二进制工具包 Binutils; ¾ 目标系统的标准 C 库 Glibc; ¾ 目标系统的 Linux 内核头文件。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 151 - 图 4-1-2 Linux 的交叉编译环境 交叉编译环境的建立最重要的是要有一个交叉编译器。交叉编译器的生成依赖于相应的函数库, 而这些函数库又依靠编译器来编译,这里有个“蛋和鸡”的关系,所以最初第一个版本的编译器肯 定得用机器码去生成,现在的编译器就不必了。这里主要用到的编译器是 ARM-Linux-GCC,它是 GCC 的 ARM 改版。GCC 是功能强大的 C 语言编译工具,其历史比 Linux 还长。无论编译器的功能有多么 强大,实质都是一样的,都是把某种以数字和符号为内容的高级编程语言转换成机器语言指令的集 合。编译工具的基本结构如图 4-1-3 所示。 图 4-1-3 编译工具的基本结构 编译过程中,仅有一个编译器是不行的,还必须和其他的一些辅助工具联合,才编译器通常用 机器语言或汇编语言编写而成,也可以用其他高级语言编写。编译过程中,编译器把源程序的各类 信息和编译各阶段的中间信息保存在不同的符号表中。表格管理程序负责构造、查找和更新这些表 格。错误处理程序的主要功能是处理各个阶段中出现的错误。 能工作。这些辅助编译工具主要有: 符 号 表 及 其 管 理 错 误 处 理 语法分析 语义分析 生成中间代码 优化代码 生成目标代码 源程序 目标程序 构建交叉编译环境 Binutils 工具包(链 接器和汇编器等) GCC 编译器(C 与 C++ 的编译器和预处理器) Glibc 库(提供系统调用 和基本函数 C 库等) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 152 - ¾ 解释程序:它本身与编译器类似,也是一种语言翻译工具。它直接执行源程序,尤其是一 些脚本语言程序。其优点是简单、好移植,但执行速度与编译好的目标代码相比要慢许多; ¾ 汇编器:用于特定计算机上的汇编语言翻译程序; ¾ 链接器:其作用是把在不同的目标文件中编译或汇编的代码收集到一个可直接执行的文件 中。同时它也把目标程序和标准库函数的代码链接; ¾ 装载器:编译器、汇编器及链接器所生成的代码经常不能直接执行,它们的主要存储器访 问可以在存储器的任何位置,在逻辑上相互之间存在固定的关系,最终位置的确定和某个 起始位置相关。通常这样的代码是可复位的。转载器可处理所有与指定的基地址或起始地 址相关的可复位的地址。这样使得代码的编译更加灵活; ¾ 预处理器:它在编译开始时由编译器调用,专门负责删除注释,包含其他文件以及执行宏 替换; ¾ 调试器:用于对目标代码的调试,从而达到排除代码中存在的错误。 目前交叉编译技术有 2 种典型的实现模式:Java 模式和 GNU GCC 模式。其中,JAVA 模式即 Java 的字节码编译技术;GCC 模式即通常所说的 Cross GCC 技术。 Java 模式最大的特点就是引入了一个自定义的虚拟机(JVM),所有 Java 源程序都会被编译成 在这个虚拟机上才能执行的“目标代码”——字节码(Bytecode)。在实时运行时,可以有两种运行 方式:一种是编译所获得的字节码由 JVM 在实际计算机系统上执行;另一种是通过 Java 实时编译 器(Just-In-Time Compiler)将字节码首先转换成本地机可以直接执行的目标代码,而后交给实际的 计算机系统运行。这实际上是两次编译过程,一次是非实时的;另一次是实时的。第一次非实时编 译时,Java 编译器生成的是基于 JVM 的“目标代码”,所以其实也就是一次交叉编译过程。 GCC 模式与 Java 模式不同,它通过 Cross GCC 直接生成目标平台的目标代码,从而能够直接在 目标平台上运行。其关键在于对 Cross GCC 的选择,需要选择针对具体目标平台的 Cross GCC。相 对来说,GCC 模式代码比 Java 模式更为优化,效率更高。目前 Linux 操作系统也主要是以 GCC 模 式进行移植的。 GCC 在进行代码编译时,为了保证编译过程与具体计算机硬件平台的无关性,它使用 RTL (Register Transfer Language)寄存器传递语言对目标平台的指令进行描述。GCC 编译过程比较复 杂,其基本流程如图 4-1-4 所示。 从 GCC 输出的是汇编语言源程序,如果要进一步编译成想要的机器代码,则还需要汇编器等的 协助,这就是前面提到的工具链。工具链中通常包含 GNU Binutils、GNU GCC 和 GNU Glibc。Binutils 中主要包含链接器 LD 和汇编器 AS。GNU Glibc 提供了一个 C 库,使得系统能完成基本的系统调用 及其他的一些函数调用。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 153 - 图 4-1-4 用 GCC 编译程序流程 2.交叉编译环境的搭建 交叉编译的概念在上面已经详细讲述过,搭建交叉编译环境是嵌入式开发的第一步,也是必备 的一步。搭建交叉编译环境的方法很多,不同的体系结构、不同的操作内容甚至是不同版本的内核, 都会用到不同的交叉编译器,而且,有些交叉编译器经常会有部分的 BUG,这都会导致最后的代码 无法正常地运行。因此,选择合适的交叉编译器对于嵌入式开发是非常重要的。 交叉编译器完整的安装一般涉及到多个软件的安装,包括 binutils、gcc、glibc 等软件。其中, binutils 主要用于生成一些辅助工具,如 objdump、as、ld 等;gcc 是用来生成交叉编译器,主要 生成 arm-linux-gcc 交叉编译工具(应该说,生成此工具后已经搭建起了交叉编译环境,可以编译 Linux 内核了,但由于没有提供标准用户函数库,用户程序还无法编译);glibc 主要是提供用户程序 所使用的一些基本的函数库。这样,交叉编译环境就完全搭建起来了。 建立交叉编译环境主要常规 6 个步骤,比较繁琐,本书仅作简单介绍: 注:用户可以根据 4.1.2 使用 crosstool 工具来编译,该方法比较简单。 1)下载源代码 到相关网站(ftp://ftp.gnu.org)下载包括 Binutils、GCC、Glibc 及 Linux 内核的源代码(需要 注意的是,Glibc 和内核源代码的版本必须与目标机上实际使用的版本保持一致),并设定 shell 变量 PREFIX 指定可执行程序的安装路径。 2)编译 Binutils 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 154 - 首先运行 configure 文件,并使用--prefix=$PREFIX 参数指定安装路径,使用--target=arm-linux 参数指定目标机类型,然后执行 make install。 3)配置 Linux 内核头文件 首先执行 make mrproper 进行清理工作,然后执行 make config ARCH=arm(或 make menuconfig/xconfig ARCH=arm)进行配置(注意,一定要在命令行中使用 ARCH=arm 指定 CPU 架 构,因为缺省架构为主机的 CPU 架构),这一步需要根据目标机的实际情况进行详细配置。 配置完成之后,需要将内核头文件拷贝到安装目录,代码如下: cp –dR include/asm-arm $PREFIX/arm-linux/include/asm cp –dR include/linux $PREFIX/arm-linux/include/linux 4)第一次编译 GCC 首先运行 configure 文件,使用--prefix=$PREFIX 参数指定安装路径,使用--target=arm-linux 参数指定目标机类型,并使用--disable-threads、--disable-shared、--enable-languages=c 参数,然 后执行 make install。这一步将生成一个最简单的 GCC。由于编译整个 GCC 需要目标机的 Glibc 库, 该库现在还不存在,因此需要首先生成一个最简单的 GCC,它只需要具备编译目标机 Glibc 库的能 力即可。 5)交叉编译 Glibc 这一步骤生成的代码是针对目标机 CPU 的,因此它属于一个交叉编译过程。该过程要用到 Linux 内核头文件,默认路径为$PREFIX/arm-linux/sys-linux,因而需要在$PREFIX/arm-linux 中建立一个 名为 sys-linux 的软链接,使其指向内核头文件所在的 include 目录,或者,也可以在接下来要执行 的 configure 命令中,使用--with-headers 参数指定 Linux 内核头文件的实际路径。 Configure 的运行参数设置如下(因为是交叉编译,所以要将编译器变量 CC 设为 arm-linux-gcc): CC=arm-linux-gcc ./configure --prefix=$PREFIX/arm-linux --host=arm-linux --enable-add-ons 最后,按以上配置执行 configure 和 make install,Glibc 的交叉编译过程就算完成了,需要指出 的是,Glibc 的安装路径设置为$PREFIXARCH=arm/arm-linux,如果此处设置不当,第二次编译 GCC 时可能找不到 Glibc 的头文件和库。 6)第二次编译 GCC Glibc 是供用户程序使用的一些基本的函数库,由于在前面编译 GCC 时避开了与 Glibc 有关的部 分,所以在 Glibc 编译完成后必须重新编译 GCC,以便得到完整的 ARM-Linux-GCC 的交叉编译器。 运行 configure,参数设置为: --prefix=$PREFIX --target=arm-linux --enable-languages=c,c++ 运行 make install。到此为止,就可得到需要的交叉编译工具链。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 155 - 4. 1. 2 制作 ARM 交叉编译器 本文档是用于介绍 Linux 下怎样建立 arm-linux 下 gcc 交叉编译工具,采用自动配置 crosstool 工具。 文档中所用到的源码如下:linux-2.6.12.tar.bz2,crosstool-0.42.tar.gz,binutils-2.16.tar.bz2, linux-libc-headers-2.6.12.0.tar.bz2,gcc-3.4.5.tar.bz2,gcc-4.0.0.tar.bz2,glibc-2.3.6.tar.bz2, flex-2.5.4a.tar.gz,glibc-linuxthreads-2.3.6.tar.bz2,perl-5.8.8.tar.bz2。 以上源码均可在互联网上下载。如:http://ftp.gnu.org/gnu/,http://kegel.com/crosstool/ , http://ftp.gnu.org/non-gnu/,http://ftp.funet.fi/pub/CPAN/src/。 本文档采用的是使用 crosstool 工具,编写脚本实现 arm-linux 下 gcc 交叉编译工具。不同的源 码版本在实际中编译中可能会遇到一些问题,比如会存在兼容性问题,这里不作太多说明,可以查 看网页:http://kegel.com/crosstool/。 通过本文档,大家可以尝试在不同环境下编译针对特定环境的交叉编译工具。相信大家会有不 同的收获。 1.建立开发环境 为了便于介绍,现约定工作目录: WORKDIR = $PWD -> 工作目录 SOURCEDIR = $WORKDIR/downloads -> 源码包 BUILDIR = $WORKDIR/build -> 编译目录 TMPDIR = $WORKDIR/tmp -> 缓存目录 RESULTDIR = /usr/crosstool -> 生成的交叉编译工具目录 运行终端,设置环境变量,并按以下方法创建目录: $ mkdir -p $SOURCEDIR $BUILDIR $TMPDIR $RESULTDIR 从网上下载编译所需要所有源码包,见文档说明。将所有源码都放在$SOURCEDIR 目录。 2.编写 crosstool 脚本文件 针对我们所下载的源码包,我们需要定制我们的 crosstool 脚本文件,这里我们需要建立的文件 有:arm.dat,demo-arm.sh,gcc-3.4.5-glibc-2.3.6.dat。我们可以参照 crosstool-0.42 里面的相应 文件进行部分修改,修改如下: arm.dat: KERNELCONFIG=`pwd`/arm.config TARGET=arm-linux TARGET_CFLAGS="-O" demo-arm.sh: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 156 - #!/bin/sh set -ex TARBALLS_DIR=$SOURCEDIR RESULT_TOP=$RESULTDIR export TARBALLS_DIR RESULT_TOP GCC_LANGUAGES="c,c++" export GCC_LANGUAGES # Really, you should do the mkdir before running this, # and chown /opt/crosstool to yourself so you don't need to run as root. mkdir -p $RESULT_TOP # Build the toolchain. Takes a couple hours and a couple gigabytes. eval `cat arm.dat gcc-3.4.5-glibc-2.3.6.dat` sh all.sh --notest echo Done. gcc-3.4.5-glibc-2.3.6.dat: BINUTILS_DIR=binutils-2.16 GCC_DIR=gcc-3.4.5 GLIBC_DIR=glibc-2.3.6 LINUX_DIR=linux-2.6.12 LINUX_SANITIZED_HEADER_DIR=linux-libc-headers-2.6.12.0 GLIBCTHREADS_FILENAME=glibc-linuxthreads-2.3.6 3.编译 解压 crosstool-0.42.tar.gz,将上面编写的三个脚本文件拷贝到 crosstool-0.42 目录: $ cd $BUILDIR $ tar zxvf $SOURCEDIR/crosstool-0.42.tar.gz $ cd crosstool-0.42 $ sh demo-arm.sh 编译成功将在$RESULTDIR 目录看到编译成功的交叉工具。 4. 2 BootLoader 简单地说,BootLoader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我 们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 157 - 以便为最终调用操作系统内核准备好正确的环境。 Bootloader(引导加载程序)是系统加电后运行的第一段代码,一般运行的时间非常短,但是 对于嵌入式系统来说,这段代码非常重要。在我们的台式电脑当中,引导加载程序由 BIOS(固件程 序)和位于硬盘 MBR 中的操作系统引导加载程序(比如 NTLOADER,GRUB 和 LILO)一起组成。 在嵌入式系统当中没有像 BIOS 这样的固件程序,不过也有一些嵌入式 CPU 会在芯片内部嵌入 一小段程序,一般用来将 bootloader 装进 RAM 中,有点类似 BIOS,但是功能比 BIOS 弱很多。在 一般的典型系统中,整个系统的加载启动任务全由 bootloader 来完成。在 ARM 中,系统上电或复 位时通常从地址 0x00000000 处开始执行,而在这个位置,通常安排的就是系统的 bootloader。通 过这小段程序可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境设置到一个合 适的状态!以为最终调用操作系统内核准备好正确的环境。 嵌入式 LINUX 系统从软件的角度可看成是 4 个层次: 1)引导加载程序,包括固化在固件中(firmware)中的启动代码(可选)和 BOOTLOADER 两 大部分。 2)内核。特定于板子的定制内核以及控制内核引导系统的参数。 3)文件系统。包括根文件系统和建立与 FLASH 内存设备上的文件系统。 4)用户应用程序。特定于用户的应用程序,有时还包括一个 GUI。 bootloader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立 一个通用的 bootloader 几乎是不可能的。尽管如此,我们仍然可以对 bootloader 归纳出一些通用的 概念来,以指导用户特定的 bootloader 设计与实现。bootloader 的启动流程: 大多数分为两个阶段,第一个阶段主要是包含依赖于 CPU 的体系结构的硬件初始化代码,通常 都是用汇编语言来实现的。这个阶段的任务有: ¾ 基本的硬件设备初始化(屏蔽所有中断、关闭处理器内部指令/数据 CACHE 等); ¾ 为第二阶段准备 RAM 空间; ¾ 如果是从某个固态存储媒质中,则复制 bootloader 的第二阶段代码到 RAM; ¾ 设置堆栈; ¾ 跳转到第二阶段的 C 程序入口点。 第二阶段通常是由 C 语言实现的,这个阶段的主要任务有: ¾ 初始化本阶段所要用到的硬件设备; ¾ 检测系统的内存映射; ¾ 将内核映像和根文件系统映像从 FLASH 读到 RAM; ¾ 为内核设置启动参数; ¾ 调用内核。 BOOTLOADER 调用 LINUX 内核的方法是直接跳转到内核的第一条指令处,即跳转 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 158 - MEM_START+0x8000 地址处,在跳转的时候必须满足下面的条件: ¾ CPU 寄存器:R0 为 0,R1 为机器类型 ID,R2 为启动参数,标记列表在 RAM 中的起始基 地址; ¾ CPU 模式:必须禁止中断,CPU 设置为 SVC 模式; ¾ Cache 和 MMU 设置:MMU 必须关闭,指令 CACHE 可以打开也可以关闭,数据 CACHE 必 须关闭。 4. 2. 1 常用 Bootloader 介绍 1. Redboot Redboot 是 Redhat 公司随 eCos 发布的一个 BOOT 方案,是一个开源项目。Redhat 公司将会继 续支持该项目,其官方发布网址为:http://sources.redhat.com/redboot/。 Redboot 支持的处理器构架有 ARM,MIPS,MN10300,PowerPC, Renesas SHx,v850,x86 等,是一个完善的嵌入式系统 Boot Loader。 Redboot 是在 ECOS 的基础上剥离出来的,继承了 ECOS 的简洁、轻巧、可灵活配置、稳定可 靠等品质优点。它可以使用 X-modem 或 Y-modem 协议经由串口下载,也可以经由以太网口通过 BOOTP/DHCP 服务获得 IP 参数,使用 TFTP 方式下载程序映像文件,常用于调试支持和系统初始化 (Flash 下载更新和网络启动)。Redboot 可以通过串口和以太网口与 GDB 进行通信,调试应用程序, 甚至能中断被 GDB 运行的应用程序。Redboot 为管理 FLASH 映像,映像下载,Redboot 配置以及其 他如串口、以太网口提供了一个交互式命令行接口,自动启动后,REDBOOT 用来从 TFTP 服务器或 者从 Flash 下载映像文件加载系统的引导脚本文件保存在 Flash 上。当前支持单板机的移植版特性有: ¾ 支持 ECOS,Linux 操作系统引导; ¾ 在线读写 Flash; ¾ 支持串行口 kermit,S-record 下载代码; ¾ 监控(minitor)命令集:读写 I/O,内存,寄存器、 内存、外设测试功能等。 Redboot 是标准的嵌入式调试和引导解决方案,支持几乎所有的处理器构架以及大量的外围硬 件接口,并且还在不断地完善过程中。 2. ARMboot ARMboot 是一个 ARM 平台的开源固件项目,它特别基于 PPCBoot,一个为 PowerPC 平台上的 系统提供类似功能的姊妹项目。鉴于对 PPCBoot 的严重依赖性,已经与 PPCBoot 项目合并,新的项 目为 U-Boot。ARMboot 发布的最后版本为 ARMboot-1.1.0,2002 年 ARMboot 终止了维护。 ARMboot 支持的处理器构架有 StrongARM ,ARM720T ,PXA250 等,是为基于 ARM 或者 StrongARM CPU 的嵌入式系统所设计的。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 159 - ARMboot 的目标是成为通用的、容易使用和移植的引导程序,非常轻便地运用于新的平台上。 ARMboot 是 GPL 下的 ARM 固件项目中唯一支持 Flash 闪存,BOOTP、DHCP、TFTP 网络下载,PCMCLA 寻线机等多种类型来引导系统的。特性为: ¾ 支持多种类型的 FLASH; ¾ 允许映像文件经由 BOOTP、DHCP、TFTP 从网络传输; ¾ 支持串行口下载 S-record 或者 binary 文件; ¾ 允许内存的显示及修改; ¾ 支持 jffs2 文件系统等。 Armboot 对 S3C44B0 板的移植相对简单,在经过删减完整代码中的一部分后,仅仅需要完成初 始化、串口收发数据、启动计数器和 FLASH 操作等步骤,就可以下载引导 uClinux 内核完成板上系 统的加载。总得来说,ARMboot 介于大、小型 Boot Loader 之间,相对轻便,基本功能完备,缺点 是缺乏后续支持。 3. U-Boot U-Boot 是由开源项目 PPCBoot 发展起来的,ARMboot 并入了 PPCBoot,和其他一些 arch 的 Loader 合称 U-Boot。2002 年 12 月 17 日第一个版本 U-Boot-0.2.0 发布,同时 PPCBoot 和 ARMboot 停止维护。 U-Boot 自发布以后已更新多次,最新版本为 U-Boot-1.3.4,U-Boot 的支持是持续性的。其发布 网址为:http://sourceforge.net/projects/u-boot/。 U-Boot 支持的处理器构架包括 PowerPC (MPC5xx,MPC8xx,MPC82xx,MPC7xx,MPC74xx, 4xx), ARM (ARM7,ARM9,StrongARM,Xscale),MIPS (4Kc,5Kc),x86 等等, U-Boot(Universal Bootloader)从名字就可以看出,它是在 GPL 下资源代码最完整的一个通用 Boot Loader。 U-Boot 提供两种操作模式:启动加载(Boot loading)模式和下载(Downloading)模式,并具 有大型 Boot Loader 的全部功能。主要特性为: ¾ SCC/FEC 以太网支持; ¾ BOOTP/TFTP 引导; ¾ IP,MAC 预置功能; ¾ 在线读写 FLASH,DOC, IDE,IIC,EEROM,RTC; ¾ 支持串行口 kermit,S-record 下载代码; ¾ 识别二进制、ELF32、pImage 格式的 Image,对 Linux 引导有特别的支持; ¾ 监控(minitor)命令集:读写 I/O,内存,寄存器、内存、外设测试功能等; ¾ 脚本语言支持(类似 BASH 脚本); ¾ 支持 WatchDog,LCD logo,状态指示功能等。 U-Boot 的功能是如此之强大,涵盖了绝大部分处理器构架,提供大量外设驱动,支持多个文件 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 160 - 系统,附带调试、脚本、引导等工具,特别支持 Linux,为板级移植做了大量的工作。 4. vivi vivi 是由 mizi 公司为 ARM 处理器系列设计的一个 bootloader,因 为 vivi 目前只支持使用串口和 主机通信,所以您必须使用一条串口电缆来连接目标板和主机。主要功能为: ¾ 把内核(kernel)从 flash 复制到 RAM,然后启动它; ¾ 初始化硬件; ¾ 下载程序并写入 flash(一般通过串口或者网口先把内核下载到 RAM 中,然后写入到 flash); ¾ 检测目标板(bootloader 会有一些简单的代码用以测试目标板硬件的好坏)。 5. Blob Blob(Boot Loader Object)是 由 Jan-Derk Bakker and Erik Mouw 发布的,是专门为 StrongARM 构架下的 LART 设计的 Boot Loader。Blob 的最后版本是 blob-2.0.5。 Blob 支持 SA1100 的 LART 主板,但用户也可以自行修改移植。 Blob 也提供两种工作模式,在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端 用户按下任意键而将 Blob 切换到下载模式。如果在 10 秒内没有用户按键,则 Blob 继续启动 Linux 内核。其基本功能为: ¾ 初始化硬件(CPU 速度,存储器,中断,RS232 串口); ¾ 引导 Linux 内核并提供 ramdisk; ¾ 给 LART 下载一个内核或者 ramdisk; ¾ 给 FLASH 片更新内核或者 ramdisk; ¾ 测定存储配置并通知内核; ¾ 给内核提供一个命令行。 Blob 功能比较齐全,代码较少,比较适合做修改移植,用来引导 Liunx,目前大部分 S3C44B0 板都用 Blob 修改移植后来加载 uClinux。 6. Bios-lt Bios-lt 是专门支持三星(Samsung)公司 ARM 构架处理器 S3C4510B 的 Loader,可以设置 CPU/ROM/SDRAM/EXTIO,管理并烧写 FLASH,装载引导 uClinux 内核。 Bios-lt 的最新版本是 Bios-lt-0.74,另外还提供了 S3C4510B 的一些外围驱动。 7. Bootldr Bootldr 是康柏(Compaq)公司发布的,类似于 compaq iPAQ Pocket PC,支持 SA1100 芯片。 它被推荐用来引导 Llinux,支持串口 Y-modem 协议以及 jffs 文件系统。 Bootldr 的最后版本为 Bootldr-2.19,网址:http://www.wearablegroup.org/software/bootldr/。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 161 - 4. 2. 2 vivi 详解 vivi 是由韩国 Mizi 公司开发的一种 Bootloader,适合于 ARM9 处理器,支持 S3C2410x 处理器, 其源代码可以在 http://www.mizi.com 网站下载。和所有的 Bootloader 一样,vivi 有两种工作模式, 即启动加载模式和下载模式。当 vivi 处于下载模式时, 它为用户提供一个命令行接口,通过该接口 能使用 vivi 提供的一些命令集。 vivi 作为一种 Bootloader,其运行过程分成两个阶段。第一阶段在代码 vivi/arch/s3c2410/head.s 中定义,大小不超过 10 KB,它包括从系统上电后在 0x00000000 地址开始执行的部分。这部分代 码运行在 Flash 中,它包括对 S3C2410 的一些寄存器、时钟等的初始化并跳转到第二阶段执行。第 二阶段的代码在 vivi\init\main.c 中,主要进行一些开发板初始化、内存映射和内存管理单元初始化 等工作,最后会跳转到 boot_or_vivi()函数中,接收命令并进行处理。需要注意的是在 Flash 中执行 完内存映射后,会将 vivi 代码拷贝到 SDRAM 中执行。 大多数 Bootloader 都分为 stage1 和 stage2 两部分,stage2 的代码通常用 C 语言来实现,以 便于实现更复杂的功能并取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是, 在编译和链接 Bootloader 程序时,不能使用 glibc 库中的函数。因此,从那里跳转进 main()函数, 而把 main()函数的起始地址作为整个 stage2 执行映像的入口点也存在两个缺点:无法通过 main() 函数传递函数参数且无法处理 main()函数返回的情况。 一种较为巧妙的方法是利用“弹簧床”的概念,也就是用汇编语言写一段 trampoline 小程序, 并将这段程序作为 stage2 可执行映象的执行入口点,然后在 trampoline 汇编小程序中用 CPU 跳转 指令跳入 main()函数中去执行。当 main()函数返回时,CPU 执行路径再次回到 trampoline 程序。简 而言之,这种方法的思想就是:用这段 trampoline 小程序来作为 main()函数的外部包裹。 vivi 中的 trampoline 程序如下: @ get read to call C functions ldr sp, DW_STACK_START @ setup stack pointer mov fp, #0 @ no previous frame, so fp=0 mov a2, #0 @ set argv to NULL bl main @ call main mov pc, #FLASH_BASE @ otherwise, reboot 正常情况下,程序能够正常执行完毕,但是如果出错了,就回到最后一条语句重新启动系统。 4. 2. 3 vivi 命令操作 vivi 有两种工作模式,启动加载模式可以在一段时间后(这个时间可更改)自行启动 Linux 内 核,这是 vivi 的默认模式。在下载模式下,vivi 为用户提供一个命令行接口,通过该接口可以使用 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 162 - vivi 提供的一些命令。启动 vivi 时,在超级终端界面中键入任意键(除 enter 外)进入 vivi 命令界面, 字符提示为“vivi>”,一个较好的方法是在启动 vivi 前按住 ESC 不放,因为 vivi 启动比较快,按其 它键会有字符产生。下面对 vivi 常用命令进行说明: ------------------------------------------------------------------------------------------------------- reset 命令 reset 复位 ARM9 系统 。 ------------------------------------------------------------------------------------------------------- help 命令 help 显示开发板上 vivi 支持的所有命令。 param help 显示 param 命令的用法。param 也可换成其它命令。 ------------------------------------------------------------------------------------------------------- part 命令 part 命令用于对分区进行操作。通过 part help 可以显示系统对 part 系列命令的帮助提示。 part show 显示分区信息。 part add partname part_start_addr part_leng flag 添加分区,参数 flag 为分区类型。 part del partname 删除分区。 part save 保存 part 分区信息。 part reset 恢复为系统默认 part 分区。 ------------------------------------------------------------------------------------------------------- load 命令 load 命令下载程序到存储器中(Flash 或者 RAM 中)。通过 load help 可以显示系统对 load 系 列命令的帮助提示。 load flash partname x 使用 xmodom 协议通过串口下载文件并且烧写带 partname 分区。 例如: load flash vivi x // 注意,这里的vivi是分区名。 load flash kernel x load flash root x load ram partname or addr x 使用xmodom 协议通过串口下载文件到内存中。 ------------------------------------------------------------------------------------------------------- param 命令 param 命令用于对 bootloader 的参数进行操作。通过 param help 可以显示系统对 param 系列 命令的帮助提示。 param show 命令用于显示 bootloader 的当前参数值。 param reset 将 bootloader 参数值复位成系统默认值。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 163 - param set paramname value 设置参数值。 param set linux_cmd_line “linux bootparam”设置 linux 启动参数,参数 linux bootparam 表 示要设置的 linux kernel 命令行参数。 param save 保存参数设置。 ------------------------------------------------------------------------------------------------------- boot 命令 boot 命令用于引导 linux kernel 启动。通过 boot help 可以显示系统对 boot 命令的帮助提示。 boot 默认方式启动。 boot ram ramaddr lenth 启动 sdram 中 ramaddr 处长度为 lenth 的 linux 内核。 ------------------------------------------------------------------------------------------------------- bon 命令 bon 命令用于对 bon 分区进行操作。通过 bon help 可以显示系统对 bon 系列命令的帮助提示。 bon 分区是 nand flash 设备的一种简单的分区管理方式。 bon part info 命令用于显示系统中 bon 分区的信息。 bon part 命令用于建立系统的 bon 分区表。bon 分区表被保存到 nand flash 的最后 0x4000 个 字节中。 例如分为 3 个区:0~192k,192k~1M,1M~ vivi> bon part 0 192k 1M doing partition size = 0 size = 196608 size = 1048576 check bad block part = 0 end = 196608 ------------------------------------------------------------------------------------------------------- go 命令 go 命令用于跳转到指定地址处执行该地址处的代码。 go addr 跳转到指定地址运行该处程序。 ------------------------------------------------------------------------------------------------------- 以上是整理的一些常用的 vivi 命令,具体语法可通过相应的 help 命令查看。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 164 - 4. 3 Linux 内核移植 1. Linux 内核版本介绍 Linux 内核的版本号分为主版本号、次版本号和扩展版本号等。根据稳定版本、测试版本和开发 版本定义不同版本序列。 稳定版本的主版本号用偶数表示,例如:2.2、2.4、2.6。每隔 2~3 年启动一个 Linux 稳定主版 本号。 紧接着是次版本号,例如:2.6.13、2.6.14、2.6.15。次版本号不分奇偶数,顺序递增。每隔 1~ 2 个月发布一个稳定版本。 然后是升级版本号,例如:2.6.14.3、2.6.14.4、2.6.14.5。升级版本号不分奇偶数,顺序递增。 每周几次发布升级版本号,修正最新的稳定版本的问题。 另外一种是测试版本。在下一个稳定版本发布之前,每个月发布几个测试版本,例如:2.6.12-rc1。 通过测试,可以使内核正式发布的时候更加稳定。 还有一类是开发版本。开发版本的主版本号用奇数表示,例如:2.3、2.5。也有次版本号,例 如:2.5.32、2.5.33。开发版本是不稳定的,适合内核开发者在新的稳定的主版本发布之前使用。 2. Linux 内核特点 ¾ 可移植性(Portability),支持硬件平台广泛,在大多数体系结构上都可以运行; ¾ 可量测性(Scalability),即可以运行在超级计算机上,也可以运行在很小的设备上(4MB RAM 就能满足); ¾ 标准化和互用性(Interoperability),遵守标准化和互用性规范; ¾ 完善的网络支持; ¾ 安全性,开放源码使缺陷暴露无疑,它的代码也接受了许多专家的审查; ¾ 稳定性(Stability)和可靠性(Reliability); ¾ 模块化(Modularity),运行时可以根据系统的需要加载程序; ¾ 编程容易,可以学习现有的代码,还可以从网络上找到很多有用的资源。 4. 3. 1 内核移植基础 内核是 Linux 操作系统的核心。它管理所有的系统线程、进程、资源和资源分配。与其它操作 系统不同的是,Linux 操作系统允许用户对内核进行重新设置。用户可以对内核进行“瘦身”,增加 或消除对某些特定设备或子系统的支持。在开发嵌入式系统时,开发人员经常会减少系统对一些无 用设备的支持,将节省下来的内存分配给各种应用软件。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 165 - Linux 内核对各种硬件和端口的支持要靠各种硬件驱动程序来实现。这些驱动程序可以被直接写 入内核,也可以针对某些特定硬件在需要时自动加载。通常情况下,可以被自动加载进内核的内核 编码称为自动加载内核模块。 Linux 内核的设置是通过内核设置编辑器完成的。内核设置编辑器可对每个内核设置变量进行描 述,帮助用户决定哪些变量需要被清除,哪些需要写入内核,或者编成一个可加载内核模块在需要 时进行加载。 建立新内核的第一步是对内核进行设置。当用户对内核进行设置时,必须先对内核和其它可加 载内核模块进行编写和安装。如果用户要对原系统的内核进行构建设置,那么这一步是十分简单的。 但如果用户要将原系统内核编译应用于其它目标系统,那么这一步就会变得相对困难一些。例如, 当用户修改嵌入式系统的 Linux 内核时,很可能会先在一个桌面系统上对内核进行设置,然后再通 过一套编译工具将其移植到嵌入式系统中。此类编译工具被称为交叉编译程序。 交叉编译程序在一类系统运行的同时会生产一系列二进制编码。这些编码是专门为另一类系统 而设计的。两种系统有着完全不同的处理器或架构。在对内核或模块的编译过程中,用户必须通过 多种多样的环境变量或 Makefile 设置来确定具体的交叉编译程序。用户还可以直接使用一个诸如 TimeSys 之类的集成开发环境来实现这一目标。TimeStorm 可以帮助用户很容易地选择交叉编译程 序。同样,当用户对 Linux 的内核和模块进行交叉编译使之应用于嵌入式系统时,如果没有 TimeStorm 之类的软件,那么用户必须通过额外的设置和 Makefile 手工修改,才能确定内核和模块的安装过程 和安装位置。 4. 3. 2 内核配置与裁剪 1. Linux 内核配置 基于 Linux 2.6 内核的设置较以往已经简便多了。Linux 2.6 内核采用新的图形设置编辑器使内 核的编译和设置变量的从属关系确定变得更加简单。内核配置的方法很多,make config、make xconfig、make menuconfig、make oldconfig 等等,它们的功能都是一样的,区别应该从名字上就 能看出来,只有 make oldconfig 是指用系统当前的设置(./.config)作为缺省值。这里用的是 make menuconfig。 过去基于 2.x 的内核为用户提供了四种基本的内核设置编辑器: ¾ config 服务于内核设置的一个冗长的命令行界面; ¾ oldconfig 一个文本模式的界面,主要包含一个已有设置文件,对用户所发现的内核资源中 的设置变量进行排序; ¾ menuconfig 一个基于光标控制库的终端导向编辑器,可提供文本模式的图形用户界面; ¾ xconfig 一个图形内核设置编辑器,需要安装 X-Window 系统。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 166 - 前三种编辑器在设置 2.6 内核时仍可使用,在运行“make xconfig”后,原有的界面被两个新 的图形设置编辑器所代替。这需要具体的图形库和 X-Window 系统的支持。另外,用户还可以通过 “make defconfig”命令,利用所有内核设置变量的缺省值自动建立一个内核设置文件。 下面具体介绍 Linux 内核配置选项: ¾ 代码成熟度选项 Code maturity level options ---> [*] Prompt for development and/or incomplete code/drivers [*] Select only drivers expected to compile cleanly 在内核中包含了一些不成熟的代码和功能,如果我们想使用这些功能,想打开相关的配置选项, 就必需打开这一选项。 ¾ 通用设置选项 General setup ---> () Local version - append to kernel release [*] Automatically append version information to the version string [*] Support for paging of anonymous memory (swap) [*] System V IPC [*] POSIX Message Queues [*] BSD Process Accounting [*] BSD Process Accounting version 3 file format [*] Sysctl support [ ] Auditing support [*] Support for hot-pluggable devices [*] Kernel Userspace Events [*] Kernel .config support [*] Enable access to .config through /proc/config.gz () Initramfs source file(s) [*] Configure standard kernel features (for small systems) ---> --- Configure standard kernel features (for small systems) [ ] Load all symbols for debugging/kksymoops [ ] Do an extra kallsyms pass [ ] Enable support for prinlk [ ] BUG()support [ ] Enable full-sinzed data structures for core [*] Enable futex support [*] Enable eventpoll support [*] Optimize for size [*] Use full shmem filesystem 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 167 - (0) Function alignment (0) Label alignment (0) Loop alignment (0) Jump alignment Local version - append to kernel release:这里填入的是 64 字符以内的字符串,在这里填上的 字符串可以用 uname -a 命令看到。 Support for paging of anonymous memory(swap):这是使用交换分区或者交换文件来做为虚 拟内存的,当然要选上。 System V IPC:表示系统 5 的 Inter Process Communication,它用于处理器在程序之间同步和 交换信息,如果不选这项,很多程序运行不起来的。 POSIX Message Queues:这是 POSIX 的消息队列,它同样是一种 IPC。建议最好将它选上。 BSD Process Accounting:这是允许用户进程访问内核将账户信息写入文件中的。这通常被认为 是个好主意,建议最好将它选上。 Sysctl support:这个选项能不重新编译内核修改内核的某些参数和变量,如果你也选择了支持 /proc,将能从/proc/sys 存取可以影响内核的参数或变量。建议最好将它选上。 Auditing support:审记支持,用于和内核的某些子模块同时工作,例如 SELinux。只有选择此 项及它的子项,才能调用有关审记的系统调用。 Support for hot-pluggable devices:是否支持热插拔的选项,肯定要选上。不然 USB、PCMCIA 等这些设备都用不了。 Kernel Userspace Events:内核中分为系统区和用户区,这里是系统区和用户区进行通讯的一 种方式,选上。 Kernel .config support:将.config 配置信息保存在内核中,选上它及它的子项使得其它用户能 从/proc 中得到内核的配置。 Configure standard kernel features(for small systems):这是为了编译某些特殊的内核使用的, 通常你可以不选择这一选项,你也不用对它下面的子项操心了。 Load all symbols for debugging/kksymoops:是否装载所有的调试符号表信息,如果你不需要 对内核调试,不需要选择此项。 Enable futex support:不选这个内核不一定能正确的运行使用 glibc 的程序,当然要选上。 Enable eventpoll support:不选这个内核将不支持事件轮循的系统调用,最好选上。 Optimize for size:这个选项使 gcc 使用-Os 的参数而不是-O2 的参数来优化编译,以获得更小 尺寸的内核,建议选上。 Use full shmem filesystem:除非你在很少的内存且不使用交换内存时,才不要选择这项。 后面的这四项都是在编译时内存中的对齐方式,0 表示编译器的默认方式。使用内存对齐能提 高程序的运行速度,但是会增加程序对内存的使用量。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 168 - ¾ 可加载模块 Loadable module support ---> [*] Enable loadable module support [*] Module unloading [ ] Forced module unloading [*] Module versioning support (EXPERIMENTAL) [ ] Source checksum for all modules [*] Automatic kernel module loading Enable loadable module support:很多人喜欢将全部功能、硬件支持一股脑的编进内核,而不 是使用模块的方式。这样做非常不好。其实我也做过嵌入式的开发,在针对特定硬件的平台下尽可 能将内核编小,将始终是支持模块加载的。例如我们开发的防火墙就是做为内核的模块被加载的。 使用模块支持,你的系统能具有更好的可扩充性。还有一个原因就是自己编写的功能模块、设备驱 动模块(假设编写的质量不高)以模块方式工作引起 Kernel Panic 的机率要远远低于不支持模块全 部编进内核的方式。讲了这么多,终于可以理直气壮的选上这一功能了。 Module unloading:不选这个功能,加载的模块就不能卸载。没什么需要多解释的,建议最好 选上。 Forced module unloading:这个选项能强行卸载模块,即使内核认为这样并不安全,也就是说 你可以把正在使用中的模块卸载掉。如果你不是内核开发人员,不要选择这个选项。 Module versioning support(EXPERIMENTAL):这个功能可以让你使用其它版本的内核模块, 不过建议你不要选择这个选项。 Source checksum for all modules:这个功能是为了防止更改了内核模块的代码但忘记更改版本 号而造成版本冲突。如果你不是自己写内核模块,那就不需要这一选项了。 Automatic kernel module loading:这个选项能让内核自动的加载部份模块,建议你最好选上。 举个例子说明一下,如模块 eth1394 依赖于模块 ieee1394。如果选择了这个选项,可以直接加载模 块 eth1394;如果没有选择这个选项,必需先加载模块 ieee1394,再加载模块 eth1394,否则将出 错。 ¾ 总线支持配置 Bus support ---> PCCARD (PCMCIA/CardBus) support ---> [ ] Enable PCCARD debugging [ ] 16-bit PCMCIA support (NEW) [ ] Load CIS updates from userspace (EXPERIMENTAL)(NEW) [ ] PCMCIA control ioctl (obsolete) (NEW) --- PC-card bridges PCCard(PCMCIA/CardBus)support:你的计算机是否支持 PCMCIA 卡 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 169 - Enable PCCARD debugging:通常不需要选择调试 PCMCIA 设备,除非你是设备驱动的开发人 员。 16-bit PCMCIA support:16 位的 PCMCIA 总线支持。 ¾ 支持的可执行文件格式 Userspace binary formats -à [*] Kernel support for ELF binaries [*] Kernel support for a.out and ECOFF binaries [*] Kernel support for MISC binaries [*] RISC OS personality Kernel support for ELF binaries:ELF 是开放平台下最常用的二进制文件,它支持不同的硬件平 台。 Kernel support for a.out and ECOFF binaries:这是早期 UNIX 系统的可执行文件格式,目前已 经被 ELF 格式取代。 Kernel support for MISC binaries:此选项允许插入二进制的封装层到内核中,当使用 Java、.NET、Python、Lisp 等语言编写的程序时非常有用。 ¾ 文件系统 File systems <*> Second extended fs support [*] Ext2 extended attributes [*] Ext2 POSIX Access Control Lists [*] Ext2 Security Labels <*> Ext3 journalling file system support [*] Ext3 extended attributes [*] Ext3 POSIX Access Control Lists [*] Ext3 Security Labels [ ] JBD (ext3) debugging support <*> Reiserfs support [ ] Enable reiserfs debug mode [ ] Stats in /proc/fs/reiserfs [*] ReiserFS extended attributes [*] ReiserFS Security Labels JFS filesystem support [*] JFS POSIX Access Control Lists [ ] JFS debugging [ ] JFS statistics XFS filesystem support [*] Realtime support (EXPERIMENTAL) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 170 - [*] Quota support [*] Security Label support [*] POSIX ACL support < > Minix fs support < > ROM file system support [*] Quota support < > Old quota format support Quota format v2 support [*] Dnotify support < > Kernel automounter support < > Kernel automounter version 4 support (also supports v3) CD-ROM/DVD Filesystems ---> DOS/FAT/NT Filesystems ---> Pseudo filesystems ---> Miscellaneous filesystems ---> Network File Systems ---> Partition Types ---> Native Language Support ---> Second extended fs support:标准的 Linux 文件系统,建议将这种文件系统编译进内核。 Ext2 extended attributes:Ext2 文件系统的结点名称、属性的扩展支持。 Ext2 POSIX Access Control Lists : POSIX 系统的访问权限列表支持。也就是 Owner/Group/Others 的 Read/Write/Execute 权限。 Ext2 Security Labels:扩展的安全标签,例如 SElinux 之类的安全系统会使用到这样的扩展安全 属性。 Ext3 journalling file system support:如果你熟悉 Redhat Linux,你一定会习惯 Ext3 文件系统。 Ext3 extended attributes:Ext3 文件系统的结点名称、属性的扩展支持。 Ext3 POSIX Access Control Lists:POSIX 系统的访问权限列表支持。 Ext3 Security Labels:扩展的安全标签支持。 JBD (ext3) debugging support:Ext3 的调试。除非你是文件系统的开发者,否则不要选上这一 项。 Reiserfs support:如果你熟悉 Suse Linux,你一定会习惯 Reiserfs 文件系统。 Enable reiserfs debug mode:Reiserfs 的调试。除非你是文件系统的开发者,否则不要选上这 一项。 Stats in /proc/fs/reiserfs:在/proc/fs/reiserfs 文件中显示 Reiserfs 文件系统的状态。一般来说 不需要选择这一项。 ReiserFS extended attributes:Reiserfs,文件系统的结点名称、属性的扩展支持。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 171 - ReiserFS POSIX Access Control Lists:POSIX 系统的访问权限列表支持。 ReiserFS Security Labels:扩展的安全标签支持。 JFS filesystem support:JFS 是 IBM 公司设计用于 AIX 系统上的文件系统。后来这一文件系统 也能应用于 Linux 系统。 JFS POSIX Access Control Lists:POSIX 系统的访问权限列表支持。 JFS debugging:JFS 的调试。除非你是文件系统的开发者,否则不要选上这一项。 JFS statistics:在/proc/fs/jfs 文件中显示 Reiserfs 文件系统的状态。一般来说不需要选择这一项。 XFS filesystem support:XFS 是 SGI 公司为其图形工作站设计的一种文件系统,后来这一文件 系统也能应用于 Linux 系统。 Realtime support (EXPERIMENTAL):实时卷的支持,能大幅提高大文件的读写速度。不过并 不太安全,建议暂时不要选择这一选项。 Quota support:XFS 文件系统的配额支持。 Security Label support:扩展的安全标签支持。 POSIX ACL support:POSIX 系统的访问权限列表支持。 Minix fs support:Minix 可能是最早的 Linux 系统所使用的文件系统。后来被 Ext2 文件系统所 取代。 ROM file system support:内存文件系统的支持。除非你是嵌入式系统的开发者,明确知道你 要干什么,否则不要选这一项。 Quota support:配额支持。也就是说限制某个用户或者某组用户的磁盘占用空间。 Old quota format support:旧版本的配额支持。 Quota format v2 support:新版本(第二版)的配额支持。 Dnotify support:基于目录的文件变化的通知机制。 Kernel automounter support:内核自动加载远程文件系统的支持。 Kernel automounter version 4 support (also supports v3):新的内核自动加载远程文件系统 的支持,也支持第三版。 2. Linux 内核裁剪 嵌入式 LINUX 内核裁剪主要有以下三种方法,基于 EduKit-IV 平台的内核裁剪请见配置文件(路 径:$KERLENDIR/config-eduk4)。 (1) 使用 LINUX 自身的配置工具,编译定制内核。LINUX 内核能够很好的支持模块化,内核 有许多可以独立增加删除的功能模块可以设置为内核配置选项。嵌入式 LINUX 内核支持很多的硬件, 如果在编译的时候把这些选上,编译出来的内核会很大,编译时应根据系统平台特点和应用需求配 置内核,添加需要的功能、删除不必要的功能,这样可以显著减小内核的大小。但这种裁剪方法的 缺点是内核裁剪的粒度较大,精度较小。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 172 - (2) 修改内核源代码进行系统裁剪。通过分析系统平台和应用需求,结合对内核代码的理解, 在内核源代码的适当位置加入一些条件编译语句,使用 CML(菜单定制语言)定制内核选项。基于 内核源码的方法裁剪粒度更小,裁剪出来的内核体积更小,更适合嵌入式系统的需求。 (3) 基于系统调用关系进行内核裁剪 内核是操作系统运行的核心,内核函数在系统调用、异常产生和中断发生时被调用。 4. 3. 3 Kconfig 与 Makefile 2.6 版本内核源码树的目录下都有两个文档 Kconfig(2.4 版本是 Config.in)和 Makefile。分布 到各目录的 Kconfig 构成了一个分布式的内核配置数据库,每个 Kconfig 分别描述了所属目录源文档 相关的内核配置菜单。在内核配置 make menuconfig(或 xconfig 等)时,从 Kconfig 中读出菜单, 用户选择后保存到.config 的内核配置文档中。在内核编译时,主 Makefile 调用这个.config,就知道 了用户的选择。 上面的内容说明了,Kconfig 就是对应着内核的配置菜单。假如要想添加新的驱动到内核的源码 中,能够修改 Kconfig,这样就能够选择这个驱动,假如想使这个驱动被编译,要修改 Makefile。 因此添加新的驱动时需要修改的文档有两种: ¾ Kconfig ¾ Makefile 要想知道怎么修改这两种文档,就要知道两种文档的语法结构,下面作简介: ¾ Kconfig 每个菜单都有一个关键字标识,最常见的就是 config 语法: config symbol 是个新的标记的菜单项,options 是在这个新的菜单项下的属性和选项 其中 options 部分有: 1、类型定义: 每个 config 菜单项都要有类型定义,bool 布尔类型、 tristate 三态:内建、模块、移除 string 字符串、 hex 十六进制、 integer 整型。 例如 config HELLO_MODULE bool "hello test module" bool 类型的只能选中或不选中,tristate 类型的菜单项多了编译成内核模块的选项,假如选择编 译成内核模块,则会在.config 中生成一个 CONFIG_HELLO_MODULE=m 的配置,假如选择内建, 就是直接编译成内核映像,就会在.config 中生成一个 CONFIG_HELLO_MODULE=y 的配置。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 173 - 2、依赖型定义 depends on 或 requires 指此菜单的出现是否依赖于另一个定义。 config HELLO_MODULE bool "hello test module" depends on ARCH_PXA 这个例子表明 HELLO_MODULE 这个菜单项只对 XScale 处理器有效。 3、帮助性定义 只是增加帮助用关键字 help 或---help---。 ¾ 内核的 Makefile 在 linux2.6.x/Documentation/kbuild 目录下有周详的介绍有关 kernel makefile 的知识。内核的 Makefile 分为 5 个组成部分: Makefile 最顶层的 Makefile; .config 内核的当前配置文档,编译时成为定层 Makefile 的一部分; arch/$(ARCH)/Makefile 和体系结构相关的 Makefile; s/ Makefile.* 一些 Makefile 的通用规则; kbuild Makefile 各级目录下的大概约 500 个文档,编译时根据上层 Makefile 传下来 的宏定义和其他编译规则,将源代码编译成模块或编入内核。 顶层的 Makefile 文档读取.config 文档的内容,并总体上负责 build 内核和模块。Arch Makefile 则提供补充体系结构相关的信息。 s目录下的 Makefile 文档包含了任何用来根据 kbuild Makefile 构 建内核所需的定义和规则(其中.config 的内容是在 make menuconfig 的时候,通过 Kconfig 文档配 置的结果)。 举个例子: 假设想把自己写的一个 flash 的驱动程序加载到工程中,而且能够通过 menuconfig 配置内核时 选择该驱动该怎么办呢?能够分三步: 第一:将您写的 flashtest.c 文档添加到/driver/mtd/maps/目录下。 第二:修改/driver/mtd/maps 目录下的 kconfig 文档: config MTD_flashtest tristate “ap71 flash" 这样当 make menuconfig 时 ,将会出现 ap71 flash 选项。 第三:修改该目录下 makefile 文档。添加如下内容:obj-$(CONFIG_MTD_flashtest) += flashtest.o。 这样,当您运行 make menuconfig 时,您将发现 ap71 flash 选项,假如您选择了此项。该选择 就会保存在.config 文档中。当您编译内核时,将会读取.config 文档,当发现 ap71 flash 选项为 yes 时,系统在调用/driver/mtd/maps/下的 makefile 时,将会把 flashtest.o 加入到内核中。即可达到 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 174 - 您的目的。 4. 4 文件系统 文件系统是一个操作系统的重要组成部分,是操作系统在计算机硬盘存储和检索数据的逻辑方 法。Linux 通过 VFS(虚拟文件系统)支持多种文件格式。Linux 支持的各种常用的文件系统有以下 几种。 表 4-4-1 Linux 支持的文件系统 文件系统 类型名称 用途 Second Extended filesystem ext2 最常用的 Linux 文件系统 Three Extended filesystem ext3 ext2 的升级版,带日志功能 Minix filesystem minix Minix 文件系统,很少用 RAM filesystem ramfs 内存文件系统,速度超快 Network File System(NFS) NFS 网络文件系统,由 SUN 发明,主要用于远程文件共享 DOS-FAT filesystem msdos ms-dos 文件系统 VFAT filesystem vfat Windows95/98 采用的文件系统 NT filesystem ntfs Windows NT 采用的文件系统 HPFS filesystem hpfs OS/2 采用的文件系统 /proc filesystem proc 虚拟的进程文件系统 ISO 9660 filesystem iso9660 大部份光盘所用的文件系统 UFS filesystem ufs Sun OS 所用的文件系统 Apple Mac filesystem hfs Macintosh 机采用的文件系统 Novell filesystem ncpfs Novell 服务器所采用的文件系统 SMB filesystem smbfs Samba 的共享文件系统 XFS filesystem xfs 由 SGI 开发的先进的日志文件系统,支持超大容量文件 JFS filesystem jfs IBM 的 AIX 使用的日志文件系统 ReiserFS filesystem reiserfs 基于平衡树结构的文件系统 4. 4. 1 Linux 的文件系统 Linux 文件系统结构如下图: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 175 - 图 4-4-1 Linux 文件系统组件的体系结构 用户空间包含一些应用程序(例如,文件系统的使用者)和 GNU C 库(glibc),它们为文件系 统调用(打开、读取、写和关闭)提供用户接口。系统调用接口的作用就像交换器,它将系统调用 从用户空间发送到内核空间中的适当端点。 VFS 是底层文件系统的主要接口。这个组件导出一组接口,然后将它们抽象到各个文件系统, 各个文件系统的行为可能差异很大。有两个针对文件系统对象的缓存(inode 和 dentry),它们缓 存最近使用过的文件系统对象。 每个文件系统实现(比如 ext2、JFS 等等)导出一组通用接口,供 VFS 使用。缓冲区缓存会 缓存文件系统和相关块设备之间的请求。例如,对底层设备驱动程序的读写请求会通过缓冲区缓存 来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度。以最近使用(LRU) 列表的形式管理缓冲区缓存。注意,可以使用 sync 命令将缓冲区缓存中的请求发送到存储媒体(迫 使所有未写的数据发送到设备驱动程序,进而发送到存储设备)。 这就是 VFS 和文件系统组件的高层情况。现在,讨论实现这个子系统的主要结构。 Linux 以一组通用对象的角度看待所有文件系统。这些对象是超级块(superblock)、inode、 dentry 和文件。超级块在每个文件系统的根上,超级块描述和维护文件系统的状态。文件系统中管 理的每个对象(文件或目录)在 Linux 中表示为一个 inode。inode 包含管理文件系统中的对象所 需的所有元数据(包括可以在对象上执行的操作)。另一组结构称为 dentry,它们用来实现名称和 inode 之间的映射,有一个目录缓存用来保存最近使用的 dentry。dentry 还维护目录和文件之间的 关系,从而支持在文件系统中移动。最后,VFS 文件表示一个打开的文件(保存打开的文件的状态, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 176 - 比如写偏移量等等)。 VFS 作为文件系统接口的根层。VFS 记录当前支持的文件系统以及当前挂装的文件系统。 可以使用一组注册函数在 Linux 中动态地添加或删除文件系统。内核保存当前支持的文件系统 的列表,可以通过 /proc 文件系统在用户空间中查看这个列表。这个虚拟文件还显示当前与这些文 件系统相关联的设备。在 Linux 中添加新文件系统的方法是调用 register_filesystem。这个函数的 参数定义一个文件系统结构(file_system_type)的引用,这个结构定义文件系统的名称、一组属性 和两个超级块函数。也可以注销文件系统。 下面主要介绍常见的几种 Linux 文件系统: 1)JFFS 文件系统 JFFS,全称为:The Journalling Flash File System(日志闪存文件系统)最初由瑞典的 Axis Communications 研发,Red Hat 的 David Woodhouse 对他进行了改进。作为用于微型嵌入式设 备的原始闪存芯片的实际文件系统而出现。JFFS 文件系统是日志结构化的,这意味着他基本上是一 长列节点。每个节点包含有关文件的部分信息?可能是文件的名称、也许是一些数据。相对于 Ext2 fs,JFFS 因为有以下这些好处而在无盘嵌入式设备中越来越受欢迎: ¾ JFFS 在扇区级别上执行闪存擦除/写/读操作要比 Ext2 文件系统好。 ¾ JFFS 提供了比 Ext2 更好的崩溃/掉电安全保护。当需要更改少量数据时,Ext2 文件系 统将整个扇区复制到内存(DRAM)中,在内存中合并新数据,并写回整个扇区。这意味着 为了更改单个字,必须对整个扇区(64 KB)执行读/擦除/写例程?这样做的效率非常低。 要是运气差,当正在 DRAM 中合并数据时,发生了电源故障或其他事故,那么将丢失整个 数据集合,因为在将数据读入 DRAM 后就擦除了闪存扇区。JFFS 附加文件而不是重写整 个扇区,并且具有崩溃/掉电安全保护这一功能。 ¾ 这可能是最重要的一点:JFFS 是专门为象闪存芯片那样的嵌入式设备创建的,所以他的整 个设计提供了更好的闪存管理。 2)YAFFS/YAFFS2 文件系统 YAFFS(Yet Another Flash File System),是一种类似于 JFFS/JFFS2 的专门为 Flash 设计的嵌入 式文件系统。和 JFFS 相比,它减少了一些功能,因此速度更快、占用内存更少。此外,YAFFS 自带 NAND 芯片的驱动,并且为嵌入式系统提供了直接访问文件系统的 API,用户可以不使用 Linux 中的 MTD 与 VFS,直接对文件系统操作。YAFFS2 支持大页面的 NAND 设备,并且对大页面的 NAND 设 备做了优化。JFFS2 在 NAND 闪存上表现并不稳定,更适合于 NOR 闪存,所以相对大容量的 NAND 闪存,YAFFS 是更好的选择。 YAFFS 和 JFFS 都提供了写均衡,垃圾收集等底层操作。他们的不同之处在于: ¾ JFFS 是一种日志文件系统,通过日志机制确保文件系统的稳定性。YAFFS 仅仅借鉴了日志 系统的思想,不提供日志机能,所以稳定性不如 JAFFS,不过资源占用少。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 177 - ¾ JFFS 中使用多级链表管理需要回收的脏块,并且使用系统生成伪随机变量决定要回收的块, 通过这种方法能提供较好的写均衡,在 YAFFS 中是从头到尾对块搜索,所以在垃圾收集上 JFFS 的速度慢,不过能延长 NAND 的寿命。 ¾ JFFS 支持文件压缩,适合存储容量较小的系统;YAFFS 不支持压缩,更适合存储容量大的 系统。 YAFFS 还带有 NAND 芯片驱动,并为嵌入式系统提供了直接访问文件系统的 API,用户能不使 用 Linux 中的 MTD 和 VFS,直接对文件进行操作。NAND Flash 大多采用 MTD+YAFFS 的模式。MTD ( Memory Technology Devices,内存技术设备)是对 Flash 操作的接口,提供了一系列的标准函 数,将硬件驱动设计和系统程式设计分开。 3)Ext2/Ext3 文件系统 Linux ext2/ext3 文件系统使用索引节点来记录文件信息,作用像 windows 的文件分配表。索引 节点是一个结构,它包含了一个文件的长度、创建及修改时间、权限、所属关系、磁盘中的位置等 信息。 一个文件系统维护了一个索引节点的数组,每个文件或目录都与索引节点数组中的唯一一个元 素对应。系统给每个索引节点分配了一个号码,也就是该节点在数组中的索引号,称为索引节点号。 linux 文件系统将文件索引节点号和文件名同时保存在目录中。所以,目录只是将文件的名称和它的 索引节点号结合在一起的一张表,目录中每一对文件名称和索引节点号称为一个连接。 对于一个文 件来说有唯一的索引节点号与之对应,对于一个索引节点号,却可以有多个文件名与之对应。因此, 在磁盘上的同一个文件可以通过不同的路径去访问它。 Linux 缺省情况下使用的文件系统为 Ext2,ext2 文件系统的确高效稳定。但是,随着 Linux 系 统在关键业务中的应用,Linux 文件系统的弱点也渐渐显露出来了:其中系统缺省使用的 ext2 文件系 统是非日志文件系统。这在关键行业的应用是一个致命的弱点。本文向各位介绍 Linux 下使用 ext3 日志文件系统应用。 Ext3 文件系统是直接从 Ext2 文件系统发展而来,目前 ext3 文件系统已经非常稳定可靠。它完 全兼容 ext2 文件系统。用户可以平滑地过渡到一个日志功能健全的文件系统中来。这实际上了也是 ext3 日志文件系统初始设计的初衷。 4)Ramdisk 文件系统 Ram Disk 就是将内存中的一块区域作为物理磁盘来使用的一种技术。对于用户来说,可以把 RAM disk 与通常的硬盘分区(如/dev/hda1)同等对待来使用。RAM disk 不适合作为长期保存文件 的介质,掉电后 Ramdisk 的内容会随内存内容的消失而消失。RAM disk 的其中一个优势是它的读写 速度高,内存盘的存取速度要远快于目前的物理硬盘,可以被用作需要高速读写的文件。 内存盘对于保存加密数据来说是一个福音,因为我们如果将加密的文件解密到普通磁盘的话, 即使我们随后删除了解密文件,数据仍然会留在磁盘上。这样是非常不安全的。而对于 RamDisk 来 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 178 - 说,没有这样的问题。 假设有几个文件要频繁的使用,你如果将它们加到内存当中,程序运行速度 会大副提高,因为内存的读写速度远高于硬盘。 5)Romfs 文件系统 传统型的 Romfs 文件系统是最常使用的一种文件系统,它是一种简单的、紧凑的、只读的文件 系统,不支持动态擦写保存;它按顺序存放所有的文件数据,所以这种文件系统格式支持应用程序 以 XIP 方式运行,在系统运行时,可以获得可观的 RAM 节省空间。uClinux 系统通常采用 Romfs 文 件系统。 6)Cramfs 文件系统 Cramfs 是 Linux 的创始人Linus Torvalds 开发的一种可压缩只读文件系统在 Cramfs 文件系统中, 每一页被单独压缩,可以随机页访问,其压缩比高达 2:1,为嵌入式系统节省大量的 Flash 存储空间。 Cramfs 文件系统以压缩方式存储,在运行时解压缩,所以不支持应用程序以 XIP 方式运行,所有的 应用程序要求被拷到 RAM 里去运行,但这并不代表比 Ramfs 需求的 RAM 空间要大一点,因为 Cramfs 是采用分页压缩的方式存放档案,在读取档案时,不会一下子就耗用过多的内存空间,只针对目前 实际读取的部分分配内存,尚没有读取的部分不分配内存空间,当我们读取的档案不在内存时, Cramfs 文件系统自动计算压缩后的资料所存的位置,再即时解压缩到 RAM 中。另外,它的速度快, 效率高,其只读的特点有利于保护文件系统免受破坏,提高了系统的可靠性;但是它的只读属性同 时又是它的一大缺陷,使得用户无法对其内容对进扩充。Cramfs 映像通常是放在 Flash 中,但是也 能放在别的文件系统里,使用 loopback 设备可以把它安装别的文件系统里。使用 mkcramfs 工具可 以创建 Cramfs 映像。 7)Ramfs/Tmpfs 文件系统 Ramfs 也是 Linus Torvalds 开发的,Ramfs 文件系统把所有的文件都放在 RAM 里运行,通常是 Flash 系统用来存储一些临时性或经常要修改的数据,相对于 ramdisk 来说,Ramfs 的大小可以随着 所含文件内容大小变化,不像 ramdisk 的大小是固定的。Tmpfs 是基于内存的文件系统,因为 tmpfs 驻留在 RAM 中,所以写/读操作发生在 RAM 中。tmpfs 文件系统大小可随所含文件内容大小变化, 使得能够最理想地使用内存;tmpfs 驻留在 RAM,所以读和写几乎都是瞬时的。tmpfs 的一个缺点 是当系统重新引导时会丢失所有数据。 8)NFS 文件系统 NFS 文件系统是指网络文件系统,这种文件系统也是 Linux 的独到之处。它可以很方便地在局 域网实现文件共享,并且使多台主机共享同一主机上的文件系统。而且 NFS 文件系统访问速度快、 稳定性高,已经得到了广泛的应用,尤其在嵌入式领域,使用 NFS 文件系统可以很方便地实现文件 本地修改,而免去了一次次读写 Flash 的忧虑。对 NFS 文件系统的介绍请参见后面章节。 在具体的嵌入式系统设计中可根据不同目录存放的内容不同以及存放的文件属性,确定使用何 种文件系统。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 179 - 4. 4. 2 嵌入式 Linux 文件系统内容 1.嵌入式系统存储设备及其管理机制 构建适用于嵌入式系统的 Linux 文件系统,必然会涉及到两个关键点,一是文件系统类型的选 择,它关系到文件系统的读写性能、尺寸大小;另一个就是根文件系统内容的选择,它关系到根文 件系统所能提供的功能及尺寸大小。 嵌入式设备中使用的存储器是像 Flash 闪存芯片、小型闪存卡等专为嵌入式系统设计的存储装 置。Flash 是目前嵌入式系统中广泛采用的主流存储器,它的主要特点是按整体/扇区擦除和按字节 编程,具有低功耗、高密度、小体积等优点。目前,Flash 分为 NOR,NAND 两种类型。 NOR 型闪存可以直接读取芯片内储存的数据,因而速度比较快,但是价格较高。NOR 型芯片, 地址线与数据线分开,所以 NOR 型芯片可以像 SRAM 一样连在数据线上,对 NOR 芯片可以以“字” 为基本单位操作,因此传输效率很高,应用程序可以直接在 Flash 内运行,不必再把代码读到系统 RAM 中运行。它与 SRAM 的最大不同在于写操作需要经过擦除和写入两个过程。 NAND 型闪存芯片共用地址线与数据线,内部数据以块为单位进行存储,直接将 NAND 芯片做 启动芯片比较难。NAND 闪存是连续存储介质,适合放大文件。擦除 NOR 器件时是以 64-128KB 的 块进行的,执行一个写入/擦除操作的时间为 5s;擦除 NAND 器件是以 8-32KB 的块进行的,执行相 同的操作最多只需要 4ms。NAND Flash 的单元尺寸几乎是 NOR 器件的一半,由于生产过程更为简 单,NAND 结构可以在给定的模具尺寸内提供更高的容量,也就相应地降低了价格。NOR flash 占据 了容量为 1―16MB 闪存市场的大部分,而 NAND flash 只是用在 8―128MB 的产品当中,这也说明 NOR 主要应用在代码存储介质中,NAND 适合于数据存储。寿命(耐用性),在 NAND 闪存中每个 块的最大擦写次数是一百万次,而 NOR 的擦写次数是十万次。NAND 存储器除了具有 10 比 1 的块 擦除周期优势,典型的 NAND 块尺寸要比 NOR 器件小 8 倍,每个 NAND 存储器块在给定的时间内 的删除次数要少一些。 所有嵌入式系统的启动都至少需要使用某种形式的永久性存储设备,它们需要合适的驱动程序, 当前在嵌入式 Linux 中有三种常用的块驱动程序可以选择。 1)Blkmem 驱动层 Blkmem 驱动是为 uclinux 专门设计的,也是最早的一种块驱动程序之一,现在仍然有很多嵌入 式 Linux 操作系统选用它作为块驱动程,尤其是在 uClinux 中。它相对来说是最简单的,而且只支持 建立在 NOR 型 Flash 和 RAM 中的根文件系统。使用 Blkmem 驱动,建立 Flash 分区配置比较困难, 这种驱动程序为 Flash 提供了一些基本擦除/写操作。 2)RAMdisk 驱动层 RAMdisk 驱动层通常应用在标准 Linux 中无盘工作站的启动,对 Flash 存储器并不提供任何的直 接支持,RAM disk 就是在开机时,把一部分的内存虚拟成块设备,并且把之前所准备好的档案系统 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 180 - 映像解压缩到该 RAM disk 环境中。当在 Flash 中放置一个压缩的文件系统,可以将文件系统解压到 RAM,使用 RAM disk 驱动层支持一个保持在 RAM 中的文件系统。 3)MTD 驱动层 为了尽可能避免针对不同的技术使用不同的工具,以及为不同的技术提供共同的能力,Linux 内核纳入了 MTD 子系统(Memory Technology Device)。它提供了一致且统一的接口,让底层的 MTD 芯片驱动程序无缝地与较高层接口组合在一起。JFFS2,Cramfs,YAFFS 等文件系统都可以被安装成 MTD 块设备。MTD 驱动也可以为那些支持 CFI 接口的 NOR 型 Flash 提供支持。虽然 MTD 可以建立 在 RAM 上,但它是专为基于 Flash 的设备而设计的。MTD 包含特定 Flash 芯片的驱动程序,开发者 要选择适合自己系统的 Flash 芯片驱动。Flash 芯片驱动向上层提供读、写、擦除等基本的操作,MTD 对这些操作进行封装后向用户层提供 MTD char 和 MTD block 类型的设备。MTD char 类型的设备包 括/dev/mtd0,/dev/mtdl 等,它们提供对 Flash 原始字符的访问。MTD block 类型的设备包括 /dev/mtdblock0,/dev/mtdblock1 等,MTD block 设备是将 Flash 模拟成块设备,这样可以在这些 模拟的块设备上创建像 Cramfs,JFFS2 等格式的文件系统。 MTD 驱动层也支持在一块 Flash 上建立多个 Flash 分区,每一个分区作为了一个 MTD block 设 备,可以把系统软件和数据等分配到不同的分区上,同时可以在不同的分区采用不用的文件系统格 式。这一点非常重要,正是由于这一点才为嵌入式系统多文件系统的建立提供了灵活性。 Linux 的目录结构如图 4-4-1 所示,下面以 Ubuntu8.04 为例,详细列出 Linux 文件系统中各主 要目录的存放内容,如表 4-4-1 所示: 表 4-4-1 Linux 文件系统结构 目录 目录内容 /bin bin 是 binary 的缩写。这个目录沿袭了 UNIX 系统的结构,存放着使用者最经常使用 的命令。例如 cp、ls、cat,等等。这个目录中的文件都是可执行的、普通用户都可以 使用的命令。作为基础系统所需要的最基础的命令就是放在这里。 /boot Linux 的内核及引导系统程序所需要的文件目录,比如 vmlinuz initrd.img 文件都位 于这个目录中。在一般情况下,GRUB 或 LILO 系统引导管理器也位于这个目录; /dev dev 是 device(设备)的缩写。这个目录下是所有 Linux 的外部设备,其功能类似 DOS 下的.sys 和 Win 下的.vxd。在 Linux中设备和文件是用同种方法访问的。例如:/dev/hda 代表第一个物理 IDE 硬盘。 /etc 这个目录用来存放系统管理所需要的配置文件和子目录。 /home 用户的主目录,比如说有个用户叫 wang,那他的主目录就是/home/wang 也可以用 ~wang 表示。 /lib 这个目录里存放着系统最基本的动态链接共享库,其作用类似于 Windows 里的.dll 文件。几乎所有的应用程序都须要用到这些共享库。 /lost+found 在 ext2 或 ext3 文件系统中,当系统意外崩溃或机器意外关机,而产生一些文件碎片 放在这里。当系统启动的过程中 fsck 工具会检查这里,并修复已经损坏的文件系统。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 181 - 有时系统发生问题,有很多的文件被移到这个目录中,可能会用手工的方式来修复, 或移到文件到原来的位置上。 目录 目录内容 /mnt 这个目录是空的,一般是用于存放挂载储存设备的挂载目录的,比如有 cdrom 等目 录。有时我们可以把让系统开机自动挂载文件系统,把挂载点放在这里也是可以的。 主要看/etc/fstab 中怎么定义了;比如光驱可以挂载到/mnt/cdrom 。 /proc 这个目录是一个虚拟的目录,它是系统内存的映射,我们可以通过直接访问这个目录 来获取系统信息。也就是说,这个目录的内容不在硬盘上而是在内存里。 /root 系统管理员(也叫超级用户)的主目录。作为系统的拥有者,总要有些特权啊!比如 单独拥有一个目录。 /sbin s 就是 Super User 的意思,也就是说这里存放的是系统管理员使用的管理程序。大多 是涉及系统管理的命令的存放,是超级权限用户 root 的可执行命令存放地,普通用户 无权限执行这个目录下的命令。 /tmp tmp 临时文件目录,有时用户运行程序的时候,会产生临时文件。/tmp 就用来存放 临时文件的。/var/tmp 目录和这个目录相似。 /usr 这个是系统存放程序的目录,比如命令、帮助文件等。这个目录下有很多的文件和目 录。当我们安装一个 Linux 发行版官方提供的软件包时,大多安装在这里。如果有涉 及服务器配置文件的,会把配置文件安装在/etc 目录中。 /srv 该目录存放一些服务启动之后需要提取的数据 /sys 这是 Linux2.6 内核的一个很大的变化。该目录下安装了 2.6 内核中新出现的一个文件 系统 sysfs sysfs 文件系统集成了下面 3 种文件系统的信息:针对进程信息的 proc 文件系统、针 对设备的 devfs 文件系统以及针对伪终端的 devpts 文件系统。该文件系统是内核设备 树的一个直观反映。当一个内核对象被创建的时候,对应的文件和目录也在内核对象 子系统中被创建 /var 这个目录中存放着那些不断在扩充着的东西,为了保持/usr 的相对稳定,那些经常被 修改的目录可以放在这个目录下,实际上许多系统管理员都是这样干的。顺带说一下 系统的日志文件就在/var/log 目录中。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 182 - / /tmp srcsbinbin/usr devicessbus/sys block /srv /sbin ttysysnet/proc /var /opt /net /misc floppycdrom/media /mnt /lib /home cron.drc.d/etc /dev grub/boot /bin 图 4-4-1 Linux 目录结构 除上面介绍的主目录之外,下面介绍一些比较重要的子目录: (1)“usr”目录下比较重要的目录 ¾ /usr/X11R6 存放 X-Window 的目录。 ¾ /usr/bin 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 183 - 这个目录是可执行程序的目录,普通用户就有权限执行;当我们从系统自带的软件包安装一个 程序时,他的可执行文件大多会放在这个目录。比如安装 gaim 软件包时。相似的目录是 /usr/local/bin;有时/usr/bin 中的文件是/usr/local/bin 的链接文件; ¾ /usr/sbin 这个目录也是可执行程序的目录,但大多存放涉及系统管理的命令。只有 root 权限才能执行; 相似目录是/sbin 或/usr/local/sbin 或/usr/X11R6/sbin 等; ¾ /usr/local 这个目录一般是用来存放用户自编译安装软件的存放目录;一般是通过源码包安装的软件,如 果没有特别指定安装目录的话,一般是安装在这个目录中。这个目录下面有子目录。自己看看吧。 ¾ /usr/share 系统共用的东西存放地,比如 /usr/share/fonts 是字体目录,/usr/share/doc 和/usr/share/man 帮助文件。 ¾ /usr/src 是内核源码存放的目录,比如下面有内核源码目录,比如 linux 、linux-2.xxx.xx 目录等。有的 系统也会把源码软件包安装在这里。比如 Fedora/Redhat,当我们安装 file.src.rpm 的时候,这些软 件包会安装在 /usr/src/redhat 相应的目录中。 ¾ /usr/doc 这是 Linux 文档的大本营。 ¾ /usr/include Linux 下开发和编译应用程序需要的头文件,在这里查找。 ¾ /usr/lib 存放一些常用的动态链接共享库和静态档案库。 ¾ /usr/man man 在 Linux 中是帮助的同义词,这里就是帮助文档的存放目录。 ¾ /usr/src Linux 开放的源代码就存在这个目录。 (2)“/proc”下比较重要的目录 ¾ /proc/cpuinfo 关于处理器的信息,如类型、厂家、型号和性能等。 ¾ /proc/devices 当前运行内核所配置的所有设备清单。 ¾ /proc/dma 当前正在使用的 DMA 通道。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 184 - ¾ /proc/filesystems 当前运行内核所配置的文件系统。 ¾ /proc/interrupts 正在使用的中断,和曾经有多少个中断。 ¾ /proc/ioports 当前正在使用的 I/O 端口。 (3)一些其它重要子目录 ¾ /etc/init.d 这个目录是用来存放系统或服务器以 System V 模式启动的脚本,这在以 System V 模式启动或 初始化的系统中常见。比如 Fedora/RedHat; ¾ /etc/xinit.d 如果服务器是通过 xinetd 模式运行的,它的脚本要放在这个目录下。有些系统没有这个目录, 比如 Slackware,有些老的版本也没有。在 Rehat/Fedora 中比较新的版本中存在。 ¾ /etc/rc.d 这是 Slackware 发行版有的一个目录,是 BSD 方式启动脚本的存放地;比如定义网卡,服务器 开启脚本等。 ¾ /etc/X11 这是 X-Windows 相关的配置文件存放地。 ¾ /var/adm 比如软件包安装信息、日志、管理信息等,在 Slackware 操作系统中是有这个目录的。在 Fedora 中好象没有;自己看看吧。 ¾ /var/log 系统日志存放,分析日志要看这个目录的东西; ¾ /var/spool 打印机、邮件、代理服务器等假脱机目录; 总结来说: ¾ 用户应该将文件存在/home/user_login_name 目录下(及其子目录下)。 ¾ 本地管理员大多数情况下将额外的软件安装在/usr/local 目录下并符号连接在/usr/local/bin 下的执行程序。 ¾ 系统的所有设置在/etc 目录下。 ¾ 不要修改根目录(“/”)或/usr 目录下的任何内容,除非真的清楚要做什么。这些目录最好 和 LINUX 发布时保持一致。 ¾ 大多数工具和应用程序安装在目录:/bin,/usr/sbin,/sbin,/usr/x11/bin,/usr/local/bin。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 185 - ¾ 所有的文件在单一的目录树下。没有所谓的“驱动符”。 另外注意两点,一个是 Linux 文件系统区分大小写,system 和 System 是不同的文件,另外是 Linux 文件没有扩展名的概念,有可能你看到的 xxlinuxcom.txt 文件不是文本文件而是一个可执行文 件。 4. 5 Linux 内核调试 调试是软件开发过程中一个必不可少的环节,在 Linux 内核开发的过程中也不可避免地会面对 如何调试内核的问题。但是,Linux 系统的开发者出于保证内核代码正确性的考虑,不愿意在 Linux 内核源代码树中加入一个调试器。他们认为内核中的调试器会误导开发者,从而引入不良的修正。 所以对 Linux 内核进行调试一直是个令内核程序员感到棘手的问题,调试工作的艰苦性是内核级的 开发区别于用户级开发的一个显著特点。 对于庞大的 Linux 内核软件工程,单靠阅读代码查找问题已经非常困难,需要借助调试技术解 决 BUG。通过合适的调试手段,可以有效地查找和判断 BUG 的位置和原因。 当内核运行出现错误的时候,首先要明确定义和可靠地重视这个错误现象。如果一个 BUG 不能 重现,修正起来只有凭想象和读代码。内核、用户空间和硬件之间的交互非常,在特定配置、特定 机器、特殊负载条件下,运行某些程序可能会产生一个 BUG,其他条件下就不一定产生。这个在嵌 入式 Linux 系统上很常见,例如:在 X86 平台上运行正常的驱动程序,在 ARM 平台上就可能会出现 BUG。在跟踪 BUG 的时候,掌握的信息越多越好。 内核的 BUG 是多种多样的。可能由于不同原因出现,并且表现形式也多种多样。BUG 范围,从 完全不正确的代码(例如:没有在适当的地址存储正确的值)到同步的错误(例如:不适当地对一 个共享变量加锁)。它们的表现形式也各种各样,从系统崩溃的错误操作到系统性能差等。 通常 BUG 是一系列事件,内核代码的错误使得用户程序出现错误。例如:一些共享的结构体可 能引起条件竞争。没有合适的统计,一个进程可以释放这个结构体,但是另外一个进程仍然想要用 它。再往下,第二个进程可能会使用通过一个无效的指针访问一个不存在的结构体。这就会导致 NULL 指针废弃、读垃圾数据,如果这个数据还没有被覆盖,也可能基本正常。NULL 指针废弃会产生 oops 或者错误的行为。内核开发者必须处理这个错误,知道这个数据是在释放以后访问的,这存在一个 条件竞争。修正的方法是为这个结构体添加引用计数,并且可能需要加锁保护。 调试内核很难,实际上内核不同于其他软件工程。内核有操作系统独特的问题,例如:时间管 理和条件竞争,这可以使多个线程同时在内核中执行。 因此,调试 BUG 需要有效的调试手段。几乎没有一种调试工具或者方法能够解决全部问题。即 使在一些集成测试环境中,也要划分不同测试调试功能,例如:跟踪调试、内存泄漏测试、性能测 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 186 - 试等。掌握的调试方法越多,调试 BUG 就越方便。Linux 有很多开放源代码的工具,每一个工具的 调试功能专一,所以这些工具的实现一般也比较简单。 本文将首先介绍三种 Linux 内核的调试方法,然后重点讲解 NFS(网络文件系统)的具体原理 和安装配置过程。 4. 5. 1 Printk、GDB、KDB 正是由于内核的复杂性,无论使用什么调试手段,都需要熟悉内核源码。只有熟悉了内核各部 分的代码实现,才能够找到准确的跟踪点;只有熟悉操作系统的内核机制,才能准确地判断系统运 行状态。 对于初学者来说,阅读内核源代码将是非常枯燥的工作。最好先掌握一种搜索工具,学会从源 码树中搜索关键词。当能够对内核源代码进行情景分析的时候,你就能感到其中的乐趣了。 调试是无法逃避的任务。进行调试有很多种方法,比如将消息打印到屏幕上、使用调试器,或 只是考虑程序执行的情况并仔细分析问题所在。 1.Linux 内核调试——Printk printk() 是调试内核代码时最常用的一种技术。在内核代码中的特定位置加入 printk() 调试调 用,可以直接把所关心的信息打印到屏幕上,从而可以观察程序的执行路径和所关心的变量、指针 等信息。 内核提供的打印函数 printk(),函数原型为 int printk(const char *fmt, ...),需要包含的头文件 为#include ,和 C 库提供的 printf()函数功能几乎相同。从它实现的大部分意图来 说,这个名字很不错;printk()就是内核的格式化打印函数。 printk 函数具有极好的健壮性,不受内核运行条件的限制,在系统运行期间都可以使用。printk 日志级别如表 4-5-1 所示。 表 4-5-1 printk 日志级别 日志级别 说明 日志级别 说明 KERN_EMERG 紧急情况,系统可能会死掉 KERN_WARNING 警告信息 KERN_ALERT 需要立即相应的问题 KERN_NOTICE 普通但是可能有用的信息 KERN_CRIT 重要情况 KERN_INFO 情报信息 KERN_ERR 错误信息 KERN_DEBUG 调试信息 这些级别有助于内核控制信息的紧急程度,判断是否向串口输出等。正如 printk 函数的日志级 别,printk 的函数的实现也比较复杂。printk 函数不是直接向控制台设备或者串口直接打印信息,而 是把打印信息先写到缓冲区里面。 printk()函数首先把要打印的信息放到 buffer 里面,然后调用函数 release_console_sem()最后 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 187 - 调用到相关驱动的 write 函数,如果你设定了 CONFIG_CMDLINE=”console=ttySL0,19200”,那么 printk 信息就会调用 ttySL 这个驱动的 write 函数,也就是从串口输出数据了。_call_console_drivers 里面有一个很重要的变量 console_drivers,它决定了调用哪支 driver 输出 printk 信息。 1) printk()函数的健壮性 健壮性是 printk()函数最容易让人们接受的一个特质。任何时候,任何地方都能调用它,内核中 的 printk()比比皆是。它可以在中断上下文和进程上下文中调用,它可以在持有锁时调用;它可以在 多处理器上同时调用,而且调用者连锁都不必使用。 它是一个弹性极佳的函数,这一点相当重要, printk()之所以这么有用,就在于它随时都在那里而且随时都能用。 2) printk()的脆弱之处 printk()函数的健壮性也有漏洞。在系统启动过程中,终端还没有初始化之前,在某些地方不能 使用它。不过说实在的,如果终端没有初始化,你又能输出到什么地方去呢? 这一般不是一个什么问题,除非你要调试的是启动过程最开始的那些步骤(比如说在负责执行 硬件体系结构相关的初始化动作的 setup_arch()函数中)。着手进行这样的调试挑战性很强——没有 任何打印函数能用确实让问题更加棘手。 不过还是有一些指望的,虽然不多。核心硬件部分的黑客依靠此时能够工作的硬件设备(比如 说一个串口)与外界通信。相信我,绝大部分人对此都不会感兴趣。一些被支持的体系结构确实靠 这种方式实现了健全的解决方案,但是其它系统(包括 i386)都通过提供可用的补丁来扭转局面。 解决的办法是提供一个 printk()的变体函数,在启动过程的初期就具有在终端上打印的能力: early_printk()。它的功能与 printk()完全相同,区别仅仅在于名字和它能够更早地工作。不过,由于 该函数在一些内核支持的硬件体系结构上无法实现,所以这种这种办法缺少可移植性。可是,如果 它能够工作的话,它就是你最好的助手。 3) 记录缓冲区 内核消息都被保存在一个 LOG_BUF_LEN 大小的环形队列中。 关于 LOG_BUF_LEN 定义: #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) 注:变量 CONFIG_LOG_BUF_SHIFT 在内核编译时由配置文件定义,对于 i386 平台,其值定 义如下(在 linux26/arch/i386/defconfig 中): CONFIG_LOG_BUF_SHIFT=18 记录缓冲区操作: 4) 消息被读出到用户空间时,此消息就会从环形队列中删除; 5) 当消息缓冲区满时,如果再有 printk()调用时,新消息将覆盖队列中的老消息; 6) 在读写环形队列时,同步问题很容易得到解决。 注:这个纪录缓冲区之所以称为环形,是因为它的读写都是按照环形队列的方式进行操作的。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 188 - 7) syslogd 和 klogd 在标准的 Linux 系统上,用户空间的守护进程 klogd 从纪录缓冲区中获取内核消息,再通过 syslogd 守护进程把这些消息保存在系统日志文件中。klogd 进程既可以从/proc/kmsg 文件中,也可 以通过 syslog()系统调用读取这些消息。默认情况下,它选择读取/proc 方式实现。klogd 守护进程 在消息缓冲区有新的消息之前,一直处于阻塞状态。一旦有新的内核消息,klogd 被唤醒,读出内核 消息并进行处理。默认情况下,处理例程就是把内核消息传给 syslogd 守护进程。 syslogd 守护进程一般把接收到的消息写入/var/log/messages 文件中。不过,还是可以通过 /etc/syslog.conf 文件来进行配置,可以选择其他的输出文件。整个过程如图 4-5-1 所示。 图 4-5-1 整个过程全景 2.Linux 内核调试——GDB 应用程序的调试是开发过程中必不可少的环节之一。Linux 下的 GNU 的调试器称为 GDB(GNU Debugger),该软件最早由 Richard Stallman 编写,GDB 是一个用来调试 C 和 C++程序的调试器 (Debugger)。使用者能在程序运行时观察程序的内部结构和内存的使用情况,GDB 是一种基于命 令行工作模式下的程序,工作在字符模式,由多个不同的图形用户界面前端支持,每个前端都能以 多种方式提供调试控制功能,它的功能非常丰富,适用于修复程序代码中的问题。 8) GDB 调试器简介 GDB 是 GNU 开源组织发布的一个强大的 UNIX 下的命令行调试工具。大家知道命令行的强大就 是在于其可以形成执行序列,形成脚本。UNIX 下的软件全是命令行的,这给程序开发提供了极大的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 189 - 便利,命令行软件的优势在于,它们可以非常容易的集成在一起,使用几个简单的已有工具的命令, 就可以做出一个非常强大的功能。Linux 的大部分特色源自于 shell 的 GNU 调试器,也就是 GDB。 GDB 可以让您查看程序的内部结构、打印变量值、设置断点,以及单步调试源代码。它是功能极其 强大的工具,适用于修复程序代码中的问题。或许,各位比较喜欢那种图形界面方式的,像 VC、BCB 等 IDE 的调试,但如果你是在 UNIX 平台下做软件,你会发现 GDB 这个调试工具有比 VC、BCB 的 图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。 一般来说,GDB 主要帮忙你完成下面四个方面的功能: 9) 启动你的程序,可以按照你的自定义的要求随心所欲的运行程序; 10) 可让被调试的程序在你所指定的调试的断点处停住(断点可以是条件表达式); 11) 当程序被停住时,可以检查此时你的程序中所发生的事; 12) 动态的改变你程序的执行环境。 从上面看来,GDB 和一般的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上, 你会发现 GDB 这个调试工具的强大,大家可能比较习惯了图形化的调试工具,但有时候,命令行的 调试工具却有着图形化工具所不能完成的功能。 需要注意的是,GDB 调试的是可执行文件,而不是源程序。如果想让 GDB 调试编译后生成的可 执行文件,在使用 GDB 工具调试程序之前,必须使用带有-g 或-gdb 编译选项的 gcc 命令来编译源 程序,例如: $ gcc –g –o test test.c 只有这样才会在目标文件中产生相应的调试信息。调试信息包含源程序的每个变量的类型和在 可执行文件里的地址映射以及源代码的行号,GDB 利用这些信息使源代码和机器码相关联。 使用 gdb 命令的语法如下: $ gdb [参数] Filename 下面列举一些常用的参数: ¾ -help:列出所有参数,并作简要说明。 ¾ -symbols=file -s file:读出文件(file)的所有符号。 ¾ -core -c 这里的 core 是程序非法执行后 core dump 后产生的文件。 ¾ -directory -d 加入一个源文件的搜索路径。默认搜索路径是环境变量中 PATH 所定义的路径。 ¾ -quiet -q 使用该参数不显示 gdb 的介绍和版权信息等。 ¾ 在 GDB 中运行程序 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 190 - 在 gdb 中,运行程序使用 r 或是 run 命令。程序的运行,你有可能需要设置下面四方面。 1)程序运行参数 set args 可指定运行时参数(如:set args 10 20 30 40 50)。 show args 命令可以查看设置好的运行参数。 2)运行环境 path 可设定程序的运行路径。 show paths 查看程序的运行路径。 set environment varname [=value] 设置环境变量,如:set env USER=hchen。 show environment [varname] 查看环境变量。 3)工作目录 cd 相当于 shell 的 cd 命令。 pwd 显示当前的所在目录。 4、程序的输入输出 info terminal 显示你程序用到的终端的模式。 使用重定向控制程序输出,如:run > outfile。 tty 命令可以指写输入输出的终端设备,如:tty /dev/ttyb。 ¾ 调试已运行的程序 1)在 UNIX 下用 ps 查看正在运行的程序的 PID(进程 ID),然后用 gdb PID 格式挂接正在运行 的程序。 2)先用 gdb 关联上源代码,并进行 gdb,在 gdb 中用 attach 命令来挂接进程的 PID。并用 detach 来取消挂接的进程。 ¾ 暂停 / 恢复程序运行 调试程序中,暂停程序运行是必须的,GDB 可以方便地暂停程序的运行。你可以设置程序在哪 行停住,在什么条件下停住,在收到什么信号时停住等等。以便于你查看运行时的变量,以及运行 时的流程。当进程被 gdb 停住时,你可以使用 info program 来查看程序的是否在运行,进程号,被 暂停的原因。在 gdb 中,我们可以有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、 捕捉点(CatchPoint)、信号(Signals)、线程停止(ThreadStops)。如果要恢复程序运行,可以使 用 c 或是 continue 命令。 1)设置断点(BreakPoint) 我们用 break 命令来设置断点。下面有几点设置断点的方法: break 在进入指定函数时停住。 break 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 191 - 在指定行号停住。 break +offset break -offset 在当前行号的前面或后面的 offset 行停住,offiset 为自然数。 break filename:linenum 在源文件 filename 的 linenum 行处停住。 break filename:function 在源文件 filename 的 function 函数的入口处停住。 break *address 在程序运行的内存地址处停住。 break break 命令没有参数时,表示在下一条指令处停住。 break linenum-or-function if condition linenum-or-function 表示行数或者函数入口处;condition 表示条件,在条件成立时停住。比如 在循环体中,可以设置 break if i=100,表示当 i 为 100 时停住程序。 查看断点时,可使用 info 命令,如下所示(注:n 表示断点号): info breakpoints [n] info break [n] 2)设置观察点(WatchPoint) 观察点一般用来观察某个表达式(变量也是一种表达式)的值是否有变化,如果有变化,马上 停住程序。我们有下面的几种方法来设置观察点: watch 为表达式,(变量)expr 设置一个观察点。一旦表达式值有变化时,马上停住程序。 rwatch 当表达式(变量)expr 被读时,停住程序。 awatch 当表达式(变量)的值被读或被写时,停住程序。 info watchpoints 列出当前设置了的所有观察点。 3)设置捕捉点(CatchPoint) 你可设置捕捉点来补捉程序运行时的一些事件。如:载入共享库(动态链接库)或是 C++的异 常。设置捕捉点的格式为: catch 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 192 - 当 event 发生时,停住程序。event 可以是下面的内容: ¾ throw 一个 C++抛出的异常(throw 为关键字); ¾ catch 一个 C++捕捉到的异常(catch 为关键字); ¾ exec 调用系统调用 exec 时(exec 为关键字,目前此功能只在 HP-UX 下有用); ¾ fork 调用系统调用 fork 时(fork 为关键字,目前此功能只在 HP-UX 下有用); ¾ vfork 调用系统调用 vfork 时(vfork 为关键字,目前此功能只在 HP-UX 下有用); ¾ load 或 load 载入共享库(动态链接库)时(load 为关键字,目前此功能只在 HP-UX 下有 用); ¾ unload 或 unload 卸载共享库(动态链接库)时(unload 为关键字,目前此功能只在 HP-UX 下有用)。 tcatch 只设置一次捕捉点,当程序停住以后,该点被自动删除。 4)维护停止点 上面说了如何设置程序的停止点,GDB 中的停止点也就是上述的三类。在 GDB 中,如果你觉得 已定义好的停止点没有用了,你可以使用 delete、clear、disable、enable 这几个命令来进行维护。 clear 清除所有的已定义的停止点。 clear ,clear 清除所有设置在指定函数上的停止点。 clear ,clear 清除所有设置在指定行上的停止点。 delete [breakpoints] [range...] 删除指定的断点,breakpoints 为断点号。如果不指定断点号,则表示删除所有的断点。range 表 示断点号的范围(如:3-7),其简写命令为 d。 比删除更好的一种方法是 disable 停止点,disable 了的停止点,GDB 不会删除,当你还需要时, enable 即可,就好像回收站一样。 disable [breakpoints] [range...] disable 所指定的停止点,breakpoints 为停止点号。如果什么都不指定,表示 disable 所有的停 止点,简写命令是 dis。 enable [breakpoints] [range...] enable 所指定的停止点,breakpoints 为停止点号。 enable [breakpoints] once range... enable 所指定的停止点一次,当程序停止后,该停止点马上被 GDB 自动 disable。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 193 - enable [breakpoints] delete range... enable 所指定的停止点一次,当程序停止后,该停止点马上被 GDB 自动删除。 5)停止条件维护 前面在说到设置断点时,我们提到过可以设置一个条件,当条件成立时,程序自动停止,这是 一个非常强大的功能,这里,我想专门说说这个条件的相关维护命令。一般来说,为断点设置一个 条件,我们使用 if 关键词,后面跟其断点条件。并且,条件设置好后,我们可以用 condition 命令来 修改断点的条件(只有 break 和 watch 命令支持 if,catch 目前暂不支持 if)。 condition 修改断点号为 bnum 的停止条件为 expression。 condition 清除断点号为 bnum 的停止条件。 还有一个比较特殊的维护命令 ignore,你可以指定程序运行时,忽略停止条件几次。 ignore 表示忽略断点号为 bnum 的停止条件 count 次。 6)为停止点设定运行命令 我们可以使用 GDB 提供的 command 命令来设置停止点的运行命令。也就是说,当运行的程序 在被停止住时,我们可以让其自动运行一些别的命令,这很有利行自动化调试。对基于 GDB 的自动 化调试是一个强大的支持。 commands [bnum] ... command-list ... end 为断点号 bnum 指写一个命令列表。当程序被该断点停住时,gdb 会依次运行命令列表中的命 令。例如: break foo if x>0 commands printf "x is %d\n",x continue end 断点设置在函数 foo 中,断点条件是 x>0,如果程序被断住后,也就是,一旦 x 的值在 foo 函 数中大于 0,GDB 会自动打印出 x 的值,并继续运行程序。 如果你要清除断点上的命令序列,那么只要简单的执行一下 commands 命令,并直接在打个 end 就行了。 7)断点菜单 在 C++中,可能会重复出现同一个名字的函数若干次(函数重载),在这种情况下,break 不 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 194 - 能告诉 GDB 要停在哪个函数的入口。当然,你可以把函数的参数类型告诉 GDB,以指定一个函数。 否则的话,GDB 会给你列出一个断点菜单供你选择你所需要的断点。你只要输入你菜单列表中的编 号就可以了。如: (gdb) b String::after [0] cancel [1] all [2] file:String.cc; line number:867 [3] file:String.cc; line number:860 [4] file:String.cc; line number:875 [5] file:String.cc; line number:853 [6] file:String.cc; line number:846 [7] file:String.cc; line number:735 > 2 4 6 Breakpoint 1 at 0xb26c: file String.cc, line 867. Breakpoint 2 at 0xb344: file String.cc, line 875. Breakpoint 3 at 0xafcc: file String.cc, line 846. Multiple breakpoints were set. Use the "delete" command to delete unwanted breakpoints. (gdb) 可见,GDB 列出了所有 after 的重载函数,你可以选一下列表编号就行了。0 表示放弃设置断点, 1 表示所有函数都设置断点。 8)恢复程序运行和单步调试 当程序被停住了,你可以用 continue 命令恢复程序的运行直到程序结束,或下一个断点到来。 也可以使用 step 或 next 命令单步跟踪程序。 continue [ignore-count] c [ignore-count] fg [ignore-count] 恢复程序运行,直到程序结束,或是下一个断点到来。ignore-count 表示忽略其后的断点次数。 continue,c,fg 三个命令都是一样的意思。 step 单步跟踪,如果有函数调用,他会进入该函数。进入函数的前提是,此函数被编译有 debug 信 息。很像 VC 等工具中的 step in。后面可以加 count 也可以不加,不加表示一条条地执行,加表示 执行后面的 count 条指令,然后再停住。 next 同样单步跟踪,如果有函数调用,他不会进入该函数。很像 VC 等工具中的 step over。后面可 以加 count 也可以不加,不加表示一条条地执行,加表示执行后面的 count 条指令,然后再停住。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 195 - set step-mode, set step-mode on 打开 step-mode 模式,于是,在进行单步跟踪时,程序不会因为没有 debug 信息而不停住。这 个参数有利于查看机器码。 set step-mod off 关闭 step-mode 模式。 finish 运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。 until 或 u 当你厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。 stepi 或 si nexti 或 ni 单步跟踪一条机器指令!一条程序代码有可能由数条机器指令完成,stepi 和 nexti 可以单步执 行机器指令。与之一样有相同功能的命令是 “display/i $pc”,当运行完这个命令后,单步跟踪会在 打出程序代码的同时打出机器指令(也就是汇编代码)。 9)信号(Signals) 信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号。尤其 是 UNIX,比较重要应用程序一般都会处理信号。UNIX 定义了许多信号,比如 SIGINT 表示中断字 符信号,也就是 Ctrl+C 的信号,SIGBUS 表示硬件故障的信号;SIGCHLD 表示子进程状态改变信号; SIGKILL 表示终止程序运行的信号,等等。信号量编程是 UNIX 下非常重要的一种技术。GDB 有能 力在你调试程序的时候处理任何一种信号,你可以告诉 GDB 需要处理哪一种信号。你可以要求 GDB 收到你所指定的信号时,马上停住正在运行的程序,以供你进行调试。你可以用 GDB 的 handle 命 令来完成这一功能。 handle 在 GDB 中定义一个信号处理。信号可以以 SIG 开头或不以 SIG 开头,可以用定义一个要处理信 号的范围(如:SIGIO-SIGKILL,表示处理从 SIGIO 信号到 SIGKILL 的信号,其中包括 SIGIO,SIGIOT, SIGKILL 三个信号),也可以使用关键字 all 来标明要处理所有的信号。一旦被调试的程序接收到信 号,运行程序马上会被 GDB 停住,以供调试。其可以是以下几种关键字中的一个或多个。 nostop 当被调试的程序收到信号时,GDB 不会停住程序的运行,但会打出消息告诉你收到这种信号。 stop 当被调试的程序收到信号时,GDB 会停住你的程序。 print 当被调试的程序收到信号时,GDB 会显示出一条信息。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 196 - noprint 当被调试的程序收到信号时,GDB 不会告诉你收到信号的信息。 pass, noignore 当被调试的程序收到信号时,GDB 不处理信号。这表示,GDB 会把这个信号交给被调试程序会 处理。 nopass, ignore 当被调试的程序收到信号时,GDB 不会让被调试程序来处理这个信号。 info signals, info handle 查看有哪些信号在被 GDB 检测中。 10)线程(Thread Stops) 如果你的程序是多线程的话,你可以定义你的断点是否在所有的线程上,或是在某个特定的线 程。GDB 很容易帮你完成这一工作。 break thread break thread if ... linespec 指定了断点设置在的源程序的行号。threadno 指定了线程的 ID,注意,这个 ID 是 GDB 分配的,你可以通过“info threads”命令来查看正在运行程序中的线程信息。如果你不指定 thread 则表示你的断点设在所有线程上面。你还可以为某线程指定断点条件。如: (gdb) break frik.c:13 thread 28 if bartab > lim 当你的程序被 GDB 停住时,所有的运行线程都会被停住。这方便你你查看运行程序的总体情况。 而在你恢复程序运行时,所有的线程也会被恢复运行,哪怕是主进程在被单步调试时。 3.Linux 内核调试——KDB KDB 是一个 Linux 系统的内核调试器,它是由 SGI 公司开发的遵循 GPL 许可证的开放源码调试 工具。kdb 嵌入在 Linux 内核中,为内核程序员提供调试手段。它适合于调试内核空间的程序代码, 譬如进行设备驱动程序调试,内核模块的调试等。官方发布的 Linux 内核并不包含 kdb,kdb 是一个 内核源程序的补充。kdb 通过修改内核源程序将调试器的源代码嵌入到内核中从而提供方便的调试 手段。因此要使用 kdb 进行调试,需要重新编译内核。编译后的内核中包含 kdb 的调试器代码。 安装新内核后,使用新内核重新启动机器。现在,内核已经有 kdb 支持了,那么如何进入调试 环境呢?如果在配置内核时,没有选择 kdb-off by default,那么在内核启动后,按下“pause”键 即可进入 kdb 调试环境。如果在配置内核时选择了 kdb-off by default,那么有两种办法:一种是在 启动时加入“kdb=on”,另一种方式是在 proc 文件系统加载后,输入如下命令:#echo “1”>/proc/sys/kernel/kdb。然后就可以按“pause”键进入调试环境了。按“pause”键后,出现提 示符 kdb>,同时键盘上 Caps 和 Scroll 两指示灯不停闪烁,提示现在处于 kdb 调试环境中。 KDB 是一个功能非常强大的工具,它允许进行几个操作,比如内存和寄存器修改、应用断点和 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 197 - 堆栈跟踪。根据这些,可以把 kdb 提供丰富的命令分为运行控制、内存操纵、寄存器操纵、断点设 置、堆栈跟踪等类别,如表 4-5-2 所示,总共有 33 条命令,下面是有关每一类中最常用命令的详细 信息。 表 4-5-2 KDB 的功能分类 类别 功能 运行控制类 提供对程序执行的控制 断点设置类 设置断点、清除断点、激活断点、使断点失效 内存操作类 对内存进行显示和修改 堆栈跟踪类 实现对堆栈的跟踪 寄存器类 对寄存器内容进行显示和修改 环境变量类 对 kdb 调试器环境变量进行显示和设置 ¾ 运行控制类 包括 go、ss 和 ssb 三个命令,提供对程序执行的控制。具体用法如下: 1)go:继续程序执行 格式:go 说明:该命令使内核继续执行,直到遇到一个断点才停止。如果没有设置断点,该命令将离开 kdb 调试器,系统回到正常运行状态。Caps 和 Scroll 指示灯恢复到原来的状态。 2)ss:单步执行程序 格式:ss 说明:该命令仅仅执行下一条指令,执行完后停止。这在进行跟踪时是必不可少的。 3)ssb:执行到分支或者函数调用时停止 格式:ssb 说明:该命令与 ss 的区别是,ss 只执行一条语句,而 ssb 执行一组语句,它使指令继续执行, 在遇到一个分支语句,或者遇到一个函数调用语句时停止。 ¾ 断点类 kdb 提供强大的断点功能,包括设置断点、清除断点、激活断点、使断点失效,kdb 也可以设 置硬件断点。断点指令包括 bp、bl、bpa、bph、bpha、bc、be 和 bd。 1)bp:设置或者显示断点 格式:bp [] 说明:该命令设置一个新的断点,其中 vaddr 是要设置的断点的地址。如果不带参数,运行 bp 将显示当前设置的所有断点。 2)bl:设置或者显示断点 格式:bl [] 说明:该命令的操作与 bp 命令相同。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 198 - 3)bpa:设置或者显示全局断点 格式:bpa [] 说明:该命令设置一个全局断点,或者显示所有全局断点,用法同上。 4)bph:设置硬件断点或者显示所有断点 格式:bph [vaddr [datar|dataw|io [length]]] 说明:如果不带参数,则显示所有断点。如果带参数,那么设置断点。其中 vaddr 为要设置硬 件断点的地址,datar 表示对该内存区进行读操作,dataw 表示写操作,io 表示对该内存区进行 io 输入输出操作,length 指明读写 io 操作的数据长度。 5)bpha:设置硬件断点或者显示所有断点 说明:格式和用法同 bph。 6)bc:清除断点 格式:bc 说明:清除标号为 bpnum 的断点。如果断点号为“*”,将清除所有断点。 7)bd:使断点无效 格式:bd 说明:使标号为 bpnum 的断点无效,如果标号为“*”,表示使所有断点无效。 8)be:激活断点 格式:be 说明:激活标号为 bpnum 的断点。如果标号为“*”,将激活所有无效的断点。 ¾ 内存操作类 内存操作类命令包括对内存进行显示和修改的 md、mdr、mds、mm 四条命令。 1)md:显示内存内容 格式 1:md [vaddr [line-count [output-radix]]] 说明 1:显示地址为 vaddr 的内存的内容。line-count 为要显示的内存的行数,output-radix 指 定以 8 进制、10 进制或者 16 进制显示。如果省略 line-count 和 output-radix,那么将以设置的环境 变量 MDCOUNT 和 RADIX 方式显示。如果不带任何参数,md 命令将接着上次 md 命令的后续地址 显示内存内容 格式 2:mdWcn 说明 2:在缺省情况下,md 以当前环境变量 BYTESPERWORD 的值读取数据,在读取硬件寄存 器的时候,需要指定数据的宽度。这是可以使用 mdWcn 来进行读取,W 是读取的宽度,单位是字 节,cn 为要读取的数目。 2)mdr:显示原始内存的内容 格式:mdr 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 199 - 说明:从指定地址 vaddr 开始显示 count 长度的内存,它打印一连串的内存数据。这个命令是 留给外部的调试器使用的,一般很少使用。 3)mds:以符号的方式显示内存的内容 格式:mds [vaddr [line-count [output-radix]]] 说明:从指定地址 vaddr 开始显示内存的内容,与 md 的区别是每行仅显示一个字,并且它试 图将该地址与符号表进行匹配,如果找到,那么它将显示相应的符号名以及偏移值。如果不带参数, 它将从上次 mds 的末尾开始显示。 4)mm:修改内存内容 格式 1:mm 说明 1:将指定地址 vaddr 开始的数据修改为新的数据。修改的数据的长度为一个机器字。 格式 2:mmW 说明 2:意义同上,区别在于它改变 W 字节的内容。 ¾ 堆栈跟踪类 该类指令实现对堆栈的跟踪,包括 bt、btp 和 bta 三条命令。 1)bt:显示调用堆栈 格式:bt [] 说明:如果不指定参数,它根据当前寄存器的内容显示堆栈,提供当前活动线程的完整的堆栈 跟踪。如果指定 stack-frame addr 参数,它将从该地址开始跟踪。 2)btp:显示进程的堆栈 格式:btp 说明:显示由 pid 指定的进程的堆栈。 3)bta:显示所有进程的堆栈 格式:bta 说明:显示所有进程的堆栈。 ¾ 寄存器类 寄存器类命令包括对寄存器内容进行显示和修改的 rd 和 rm 指令,以及异常帧显示指令 ef。 1)rd:显示寄存器内容 格式:rd [c|d|u] 说明:如果不带任何参数,rd 显示所有进入 kdb 调试器时该点所设置的所有通用寄存器的值。 如果带 c 参数,它将显示控制寄存器 cr0、cr1、cr2、cr4 寄存器的内容。如果带 d 参数,它显示调 试寄存器的内容。如果带 u 参数,它显示当进入 kdb 调试器时当前任务的所有寄存器。 2)rm:修改寄存器的内容 格式:rm 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 200 - 说明:该命令修改 register-name 指定的寄存器的内容为 register-content。其中 register-name 为%eax、%ebx、%ecx、%edx、%esi、%edi、%esp、%eip 或%ebp。如果参数为%%,由 rd u 指定的寄存器将被修改。当前 rm 命令不允许修改控制寄存器,也不允许显示和修改 Pentium 和 Pentium Pro 系列的特定寄存器。 3)ef:显示异常帧 格式:ef 说明:显示 vaddr 地址处的异常帧。 ¾ 环境变量类 这类指令对 kdb 调试器环境变量进行显示和设置,包括 set 和 env 命令。 1)set:设置环境变量 格式:set 说明:将环境变量 env-var 的值设置为 value。最多有 33 个环境变量,每个环境变量最大 512 字节。 2)env:显示环境变量 格式:env 说明:显示所有环境变量的值。 ¾ 其他命令 1)id:指令反汇编 格式:id 说明:从 vaddr 开始的地址反汇编指令。 2)cpu:切换到另一个 CPU 格式:cpu 说明:这条命令仅仅在 SMP 结构下有用,它切换到由 cpunum 指定的 CPU。 3)ps:显示所有活动的进程 格式:ps 说明:显示当前的活动的进程。包括 pid、父进程 pid、CPU 号、当前状态,以及对应的线程。 4)reboot:重新启动机器 格式:reboot 说明:在某些情况下,内核无法返回到正常工作状态,这时可以利用 reboot 重新启动机器。注 意在重启机器前,它不进行任何状态保存的工作。 5)sections:列出内核中所有已知的段的信息 格式:sections 说明:列出模块和内核的所有已知的段的信息。首先是模块信息,最后是内核信息。包括模块 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 201 - 名和一个或者多个段的信息。段信息包括段名、段起始地址、段结束地址和段标识。本命令仅仅是 为外部调试器而设立的。 6)sr:激活 SysRq 代码,也就是调用 MAGIC_SYSRQ 函数 格式:sr 说明:将 sysrq key 字符作为参数传递给 SysRq 函数进行处理,就像你已经键入了 SysRq 键和 该字符一样。如果要使用这个命令,需要在配置内核时,选择 Magic SysRq Key。然后在新内核启动 后,使用如下命令激活 SysRq 功能。 #echo “1” > /proc/sys/kernel/sysrq 说明:这是一个功能强大的命令,它使得在 kdb 中可以使用操作系统提供的 SysRq 处理函数。 7)lsmod:列出内核中加载的所有模块 格式:lsmod 说明:显示所有模块的信息。包括模块名、模块大小、模块结构地址、引用计数,以及被哪个 模块所引用。 8)rmmod:卸载一个模块 格式:rmmod 说明:将由 modname 指定的模块从内核中卸载。 9)ll:对链表中的每个元素重复执行命令 格式:ll 说明:它对以地址 addr 开头的链表的头 link-offset 个元素,重复执行 cmd 命令。 10)help 和?:显示帮助信息。 格式:help 或者? 说明:显示 kdb 的命令以及简单的用法。 kdb 是一个强大的内核调试工具,gdb 需要两台机器通过串口才能进行调试,而 kdb 只需要一 台机器即可进行调试,对于普通用户来说,是非常方便的。对于编写内核程序(譬如可加载模块) 的程序员来说,kdb 提供的这些命令使得调试工作难度大大降低,使得调试效率得以提高。另外对 于内核感兴趣的人可以使用 kdb 来查看内核的数据结构和运行状态,从而加深对内核的理解。不足 之处是 kdb 无法提供源码级的调试,要求程序员有一定的汇编程序基础。但总的来说,kdb 提供了 一种强有力的内核调试手段。 4. 5. 2 NFS 服务 NFS 就是 Network File System 的缩写,最早是由 Sun 公司于 1984 年开发出来的。它最大的 功能就是可以透过网络,让不同的机器、不同的操作系统、可以彼此分享个别的档案(share file), 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 202 - 所以,也可以简单的将他看作是一个 file server 呢!这个 NFS Server 可以让你的 PC 机将网络远程 的 NFS 主机分享的目录,挂载到本地端的机器当中,所以,在本地端的机器看起来,那个远程主 机的目录就好像是自己的 partition 一般!由于 NFS 使用起来非常方便,因此很快得到了大多数的 UNIX/Linux 系统的广泛支持,而且还被 IETE(国际互联网工程组)制定为 RFC1904、RFC1813 和 RFC3010 标准。 NFS 采用客户/服务器工作模式。就如图 4-5-2 所示一般,当我们的 NFS Server 设定好了分享出 来的 /home/sharefile 这个目录后,其他的 NFS Client 端就可以将这个目录挂载到自己系统上面的 某个挂载点(挂载点可以自订),例如前面图示中的 NFS Client 1 与 NFS Client 2 挂载的目录就不相 同。只要在 NFS Client 1 系统中进入 /home/data/sharefile 内,就可以看到 NFS Server 系统内的 /home/sharefile 目录下的所有资料了。这个 /home/data/sharefile 就好像 NFS Client 1 自己机器里 面的一个 partition 了,只要具有相应的权限,那就可以使用 cp、cd、mv、rm 和 df 等命令对磁盘或 文件进行相应的操作。 图 4-5-2 NFS 主机分享目录与 Client 挂载示意图 好的,既然 NFS 是透过网络来进行资料的传输,那么经由 socket pair 的概念你会知道 NFS 应 该会使用一些 port 吧?那么 NFS 使用哪个 port 来进行传输呢?答案是不知道,因为 NFS 用来传输 的 port 是随机选择小于 1024 以下的端口来使用的。咦!用户端怎么知道服务器端使用哪个 port 啊? 此时就得要远程程序呼叫(Remote Procedure Call ,RPC)的协议来辅助!底下我们就来谈谈什么 是 RPC? 因为 NFS 支援的功能相当多,而不同的功能都会使用不同的程序来启动,每启动一个功能就会 启用一些 port 来传输资料,因此,NFS 的功能所对应的 port 才没有固定住,而是采用随机取用一些 未被使用的小于 1024 的端口来作为传输之用。但如此一来又造成用户端想要连上服务器时的困扰, 因为用户端得要知道服务器端的相关端口才能够连接吧! 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 203 - 此时我们就得需要远端程序呼叫(RPC)的服务!RPC 最主要的功能就是在指定每个 NFS 功能 所对应的 port number,并且回报给用户端,让用户端可以连接到正确的端口上去。那 RPC 又是如 何知道每个 NFS 的端口呢?这是因为当服务器在启动 NFS 时会随机取用数个端口,并主动的向 RPC 注册,因此,RPC 可以知道每个端口对应的 NFS 功能,然后 RPC 又是固定使用 port 111 来监听用 户端的需求并回服用户端正确的端口,所以当然可以让 NFS 的启动更为轻松愉快了! 同时要注意到的是,在某些状况中,不但跑 NFS 的 Server 需要激活 RPC 的服务,连带的, 要挂载 NFS partition 的 Client 机器,也需要同步激活 RPC 才行!这样 Server 端与 Client 端才 能借由 RPC 的协议来进行 program port 的对应喔!NFS 主要管理分享出来的目录,而至于资料 的传递,就直接将他丢给 RPC 的协议来运作就是了! 图 4-5-3 NFS 与 RPC 服务及档案系统操作的相关性 如图 4-5-3 所示,当用户端有 NFS 档案存取需求时,他会如何向服务器端要求资料呢? ¾ 用户端会向服务器端的 RPC(port 111)发出 NFS 档案存取功能的询问要求; ¾ 服务器端找到对应的已注册的 NFS daemon 端口后,会回报给用户端; ¾ 用户端了解正确的端口后,就可以直接与 NFS daemon 来连线。 由于 NFS 的各项功能都必须要向 RPC 来注册,如此一来 RPC 才能了解 NFS 这个服务的各项功 能的 port number,PID,NFS 在主机所监听的 IP 等等,而用户端才能够透过 RPC 的询问找到正确 对应的端口。也就是说,NFS 必须要有 RPC 存在时才能成功的提供服务,因此我们称 NFS 为 RPC Server的一种。事实上,有很多这样的服务器都是向RPC注册的,举例来说,NIS(Network Information Service)也是 RPC Server 的一种。此外,由图 4-5-2 你也会知道,不论是用户端还是服务器端,要 使用 NFS 时,两者都需要启动 RPC 才行。 使用 NFS 服务,至少需要启动以下 3 个系统守护进程。 1)rpc.nfsd 这个 daemon 主要的功能就是在管理 Client 是否能够登入主机的权限,其中还包含这个登入者 的 ID 的判别。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 204 - 2)rpc.mountd 这个 daemon 主要的功能,则是管理 NFS 的档案系统!当 Client 端顺利的通过 rpc.nfsd 而 登入主机之后,在他可以使用 NFS server 提供的档案之前,还会经过档案使用权限(就是那个 -rwxrwxrwx 与 owner,group 那几个权限)的认证程序!他会去读 NFS 的设定档 /etc/exports 来 比对 Client 的权限,当通过这一关之后, Client 就可以取得使用 NFS 档案的权限啦! 注:这个也是我们用来管理 NFS 分享之目录的使用权限与安全设定的地方。 3)portmap 就如同刚刚提的到,我们的 NFS 其实可以被视为一个 RPC server program,而要激活任何一 个 RPC server program 之前,我们都需要做好 port 的对应(mapping)的工作才行,这个工作其 实就是 portmap 这个服务所负责的!也就是说,在激活任何一个 RPC server 之前,我们都需要激 活 portmap 才行呢!那么这个 portmap 到底在干嘛呢?就如同这个服务的名称,就是作 port 的 mapping 啊!举个例子来说:当 Client 端尝试来使用 RPC server 所提供的服务时,由于 Client 需 要取得一个可以连接的 port 才能够使用 RPC server 所提供的服务,因此,Client 首先就会去跟 portmap 讲:可不可以通知一下,给我个 port number ,好让我可以跟 RPC 联络吧!这个时候 portmap 就自动的将自己管理的 port mapping 告知 Client ,好让他可以连接上来 server!所以 激活 NFS 之前,请先激活 portmap 。 以下是 NFS 最显而易见的好处: ¾ 本地工作站使用更少的磁盘空间,因为通常的数据可以存放在一台机器上而且可以通过网 络访问到; ¾ 用户不必在每个网络上机器里头都有一个 home 目录。Home 目录可以被放在 NFS 服务器 上并且在网络上处处可用; ¾ 诸如软驱,CDROM 和 Zip® 之类的存储设备可以在网络上面被别的机器使用,这可以 减少整个网络上的可移动介质设备的数量。 NFS 至少有两个主要部分:一台服务器和一台(或者更多)客户机。客户机远程访问存放在服 务器上的数据。为了正常工作,一些进程需要被配置并运行。NFS 有很多实际应用,下面是比较常 见的一些: ¾ 多个机器共享一台 CDROM 或者其他设备。这对于在多台机器中安装软件来说更加便宜跟 方便。 ¾ 在大型网络中,配置一台中心 NFS 服务器用来放置所有用户的 home 目录可能会带来便 利。这些目录能被输出到网络以便用户不管在哪台工作站上登录,总能得到相同的 home 目录。 ¾ 几台机器可以有通用的/usr/ports/distfiles目录。这样的话,当您需要在几台机器上安装port 时,您可以无需在每台设备上下载而快速访问源码。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 205 - NFS 至少包括两个主要的部分:一台服务器,以及至少一台客户机,客户机远程地访问保存在 服务器上的数据。要让这一切运转起来,需要配置并运行几个程序。 服务器必须运行以下服务: 表 4-5-3 服务器运行的服务 服务 描述 Nfsd NFS,为来自 NFS 客户端的请求服务。 Mountd NFS 挂载服务,处理 nfsd(8)递交过来的请求 rpcbind 此服务允许 NFS 客户程序查询正在被 NFS 服务使用的端口 客户端同样运行一些进程,比如 nfsiod,nfsiod 处理来自 NFS 的请求。这是可选的,而且可以 提高性能,对于普通和正确的操作来说并不是必须的。 4. 6 Linux 映像固化与运行 嵌入式 Linux 在宿主机上编译后会生成映像文件,这个映像文件一般来说需要固化到 Flash 中, 比如 NorFlash 或 NandFlash。一 般 BootLoader 具有擦除 Flash 的功能,也具有从宿主机接收映像文 件的功能,因此我们一般通过 BootLoader 来完成映像的固化和更新。 4. 6. 1 Linux 基本映像的固化 Linux 的基本映像包含三个部分 bootloader、内核、根文件系统,其中 bootloader 采用专门的 硬件仿真器进行固化,内核和根文件系统一般是通过 bootloader 来固化的。在 EduKit-IV 嵌入式实 验平台中,基于 S3C2410 的 Linux 映像都是固化到板载的 Nand Flash 中。 为了提供 Linux 的更多特性,设置 Linux 的 MTD 分区如下: static struct mtd_partition partition_info[] ={ [0] = { .name = "bootloader", .offset = 0x0, .size = 0x00030000, }, [1] = { .name = "kernel", .offset = 0x00030000, .size = 0x001d0000, }, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 206 - [2] = { .name = "ramdisk", .offset = 0x00200000, .size = 0x00200000, }, [3] = { .name = "yaffs", .offset = 0x00400000, .size = 0x03800000, }, [4] = { .name = "jffs2", .offset = 0x03c00000, .size = 0x00100000, }, [5] = { .name = "data", .offset = 0x03d00000, .size = 0x002f0000, } }; 其中 ramdisk 根文件系统是作为后面实验用,并提供更快更好的 yaffs 根文件系统的固化方式, 所以必须在实验前固化好 ramdisk 根文件系统。 下面将完整的介绍实验系统 Linux 映像的固化过程。(在实验系统出厂前已经固化好出厂 Linux 映像,下面的内容仅供参考,用户如果并不需要更新全部映像,可以单独更新单个映像) z 准备工作: 1)准备好 EduKit-IV 实验平台一套,Mini2410-IV 核心子板一套,5V/2A 电源适配器一个,EasyICE 烧写器一个,交叉串口线一个。并口线 1 个,U 盘一个(空闲空间大于 32M)。 2)在测试台摆放好 EduKit-IV 实验平台,小心打开 EduKit-IV 实验平台上盖,注意防止箱体向 后倾倒(最好箱体上盖后面靠着其他物品)。 3)检查 EduKit-IV 实验平台出厂跳线,注意 Mini2410-IV 核心子板上的跳线为闭合状态,电源 拨动开关拨向向下端的断开状态。 4)连接 5V/2A 电源适配器到 EduKit-IV 实验平台的电源接口(电源插座供电为 220V 市电)。 5)连接 EasyICE 烧写器:JTAG 线连接到 EduKit-IV 实验平台的 Area1 区的 ARM JTAG 接口, 并口端通过并口线连接 PC 机与 EasyICE 烧写器。(用于固化 Bootloader) 6)连接交叉串口线于 PC 机的串口端和 EduKit-IV 实验平台的 COM2 端。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 207 - 7)将 Mini2410-IV 核心子板插入 EduKit-IV 实验平台的 CPU PACK 接口,注意用力均匀且确保 插槽连接紧密(一般可用力先插紧上面的槽,然后在插紧下面的槽,插好后,再均匀用力固定确保 连接紧密,槽插进去的时候可以听到卡进去的声音)。 8)将 EduKit-IV 实验平台的电源的拨动开关拨向向上端的加电状态,给实验平台上电。可以看 到 POWER 区的三个红色电源指示灯会亮:1V8_LED、3V3_LED、5V0_LED。如果有任意电源指示 灯不亮,请立刻关闭电源,检查电路故障。 z 固化启动映像 Bootloader: 1)运行软件“Embest Online Flash Programmer for ARM”(需安装,软件见出厂光盘 DISK3_S3C2410\04-Tools\Flash Programmer V6.0,同时该文件夹附带安装注册说明),如图 4-6-1 所示,在菜单栏选择“Setting -> Configure…”,在弹出的对话框中按照图 4-6-2 所示设置仿真器参 数,设置完成后,点击“OK”关闭对话框,然后继续选择菜单栏“Tools -> Option…”,在弹出的对 话框中按照图 4-6-3 所示设置软件参数,设置完成后,点击“OK”关闭对话框。 图 4-6-1 运行 Flash Programmer 图 4-6-2 配置仿真器类型 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 208 - 图 4-6-3 设置软件参数 2)完成软件设置后,选择 Flash Programmer 软件的菜单栏“File -> Open…”,在弹出的对话 框中选择烧写配置文件 vivi.cfg(DISK3_S3C2410\02-Images\02-Linux\vivi.cfg),软件将自动加载 烧写参数,如图 4-6-4 所示: 图 4-6-4 加载配置文件 3)在如图 4-6-4 所示的 File 选项的 Program 处选择需要固化的 vivi 映像(出厂光盘 DISK3_S3C2410\02-Images\02-Linux\vivi),单击 Flash Programmer 软件的功能键“Program”,开 始烧写 Linux 的启动映像 vivi,烧写过程如图 4-6-5、4-6-6 所示,成功将提示“Info: program suceess.”。 附:此处用户也可以烧写自己的其他 vivi 映像。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 209 - 图 4-6-5 烧写 vivi 映像 图 4-6-6 烧写成功界面 4)拨动电源开关拨向向下端的断开状态,给 EduKit-IV 实验平台断电。拔出 EasyICE 烧写器连 接在 EduKit-IV 实验平台的 ARM JTAG 接口的 JTAG 线。 5)运行 PC 机上 Windows XP 系统自带的超级终端软件,“开始 -> 所有程序 -> 附件 -> 通 讯 -> 超级终端”,按照图 4-6-7 所示设置每秒位数 115200、数据位 8、奇偶校验无、停止位 1、数 据流控制无: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 210 - 图 4-6-7 超级终端设置 6)将 EduKit-IV 实验平台的电源的拨动开关拨向向上端的加电状态,重新给实验平台上电,在 超级终端界面将会出现 Linux 启动映像 vivi 的启动信息,在 vivi 启动的同时迅速按下 PC 机的空格键 进入 vivi 的命令行界面,如图 4-6-8 所示。(如果没有及时进入 vivi 的命令行界面请重新启动实验系 统,再进入 vivi 命令行界面) 图 4-6-8 启动 vivi,进入命令行界面 8)接下来在 vivi 的命令行输入命令“bon part 0”格式化 Flash,并检查 Flash 坏块,屏蔽坏块 分区,如图 4-6-9 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 211 - 图 4-6-9 整理 Flash 坏块 z 固化内核映像 zImage: 参照 vivi 的固化方法,仅需在步骤 2)、3)处修改为:完成软件设置后,选择 Flash Programmer 软件的菜单栏“File -> Open… ”,在弹出的对话框中选择烧写配置文件 kernel.cfg (DISK3_S3C2410\02-Images\02-Linux\kernel.cfg),软件将自动加载烧写参数,另外 File 选项的 Program 处选择需要固化的内核映像(出厂光盘 DISK3_S3C2410\02-Images\02-Linux\zImage), 单击 Flash Programmer 软件的功能键“Program”,开始烧写 Linux 的内核映像 zImage,成功将提 示“Info: program suceess.”。 z 固化 ramdisk 根文件系统映像: 参照 vivi 的固化方法,仅需在步骤 2)、3)处修改为:完成软件设置后,选择 Flash Programmer 软件的菜单栏“File -> Open… ”,在弹出的对话框中选择烧写配置文件 rootfs.cfg (DISK3_S3C2410\02-Images\02-Linux\rootfs.cfg),软件将自动加载烧写参数,另外 File 选项的 Program 处选择需要固化的内核映像(出厂光盘 DISK3_S3C2410\02-Images\02-Linux\ramdisk.gz), 单击 Flash Programmer 软件的功能键“Program”,开始烧写 Linux 的 ramdisk 根文件系统映像 ramdisk.gz,成功将提示“Info: program suceess.”。 这样一个最基本的 Linux 系统就固化成功了,可以修改 vivi 的启动参数来设置启动方式(出厂 默认是采用 yaffs 根文件系统启动方式): 启动 ramdisk 根文件系统: vivi> param ramdisk vivi> param save 启动 yaffs 根文件系统:(下节将介绍如何固化更新 yaffs 根文件系统) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 212 - vivi> param reset vivi> param save 启动 nfs 根文件系统:(根据本地局域网的不同,IP 需要自己重新设置,下面的命令仅作参考) vivi> param set linux_cmd_line "root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs ip=192.192.192.200:192.192.192.190:192.192.192.1:255.255.255.0:EDUK4:eth1:off console=ttySAC1,115200 mem=64M init=/linuxrc noinitrd" vivi> param save 根据不通的 vivi 启动参数可以引导不同的根文件系统。 4. 6. 2 根文件系统的更新 在前面的基础上,可以进行根文件系统的固化与更新。步骤如下: 1)在 EduKit-IV 实验平台中,Mini2410-IV 核心子板中运行的 Linux 采用了“双根文件系统”的 技术,MTD2 分区存放 ramdisk 根文件系统,MTD3 分区存放 yaffs 根文件系统。对于正常使用的用 户而言,可见的文件系统是 yaffs 根文件系统,只有在该文件系统崩溃的时候,才通过修改 vivi 参数 启动 ramdisk 根文件系统,来恢复更新 yaffs 根文件系统。同时在 Linux 映像固化的时候,yaffs 根文 件系统也是通过 ramdisk 文件系统更新的。下面的步骤将介绍通过 ramdisk 根文件系统来固化更新 yaffs 根文件系统。 2)运行 PC 机上 Windows XP 系统自带的超级终端软件,“开始 -> 所有程序 -> 附件 -> 通 讯 -> 超级终端”,设置每秒位数 115200、数据位 8、奇偶校验无、停止位 1、数据流控制无。(确 认已经关闭其他超级终端工具) 3)将 EduKit-IV 实验平台的电源的拨动开关拨向向上端的加电状态,重新给实验平台上电,在 超级终端的界面将会出现 Linux 启动映像 vivi 的启动信息,在 vivi 启动的同时迅速按下 PC 机的空格 键进入 vivi 的命令行界面。(如果没有及时进入 vivi 的命令行界面请重新启动实验系统,再进入 vivi 命令行界面) 4)在超级终端界面的 vivi 命令行输入命令修改传递给内核的启动参数 “param ramdisk”,输 入完成后按回车键: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 213 - 图 4-6-10 修改传递给内核的启动参数 5)参数修改成功后,在 vivi 的命令行输入“boot”,启动 Linux 内核,在超级终端中可以看到 Linux 的启动信息,同时也可以在 LCD 屏上看到 Linux 的启动画面。 图 4-6-11 Linux 启动信息 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 214 - 图 4-6-12 Linux 启动界面 6)Linux 启动完成后,输入回车即可进入 Linux 的命令行界面。拷贝支持触摸屏的 QT yaffs 文 件系统映像更新包 rootfs-eduk4-tsp-ys.tgz 到 U 盘(DISK3_S3C2410\02-Images\02-Linux)。 附:此处也可以通过其他途径传送 rootfs-eduk4-tsp-ys.tgz 到系统。 7)在超级终端的 Linux 命令行执行命令: $ flash_eraseall /dev/mtd3 擦除 MTD3 的 yaffs 分区,如图 4-6-34 所示: 图 4-6-13 擦除 MTD3 分区 8)在超级终端的 Linux 命令行执行命令: $ mount -t yaffs /dev/mtdblock/3 /mnt 将 MTD3 分区映射到/mnt 目录,如图 4-6-14 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 215 - 图 4-6-14 映射 MTD3 分区到/mnt 目录 9)将拷贝有 rootfs-eduk4-tsp-ys.tgz 文件(DISK3_S3C2410\02-Images\02-Linux)的 U 盘插 入 EduKit-IV 实验平台的 USB 接口的上面 U 口,在超级终端中可以看到提示 U 盘插入的信息,如图 4-6-15 所示: 附:出厂的 yaffs 根文件系统映像为 rootfs-eduk4-tsp-ys.tgz,其他的映像见光盘内同级目录下的 list.txt 文件说明,后面章节所介绍的 linux 实验使用的 yaffs 根文件系统映像为 rootfs-eduk4-base.tgz。 另外此处也可以不必使用 U 盘作为传送映像的媒介,也可以使用 SD 卡或者网络等传送。 图 4-6-15 插入 U 盘提示的信息 10)在超级终端的 Linux 命令行执行命令: $ mount -t vfat /dev/ub/a/part1 /media 将 U 盘映射到/media 目录: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 216 - 图 4-6-16 映射 U 盘到/media 目录 11)在超级终端的 Linux 命令行执行命令: $ tar zxvf /media/rootfs-eduk4-tsp-ys.tgz -C /mnt 解压 rootfs-eduk4-tsp-ys.tgz 文件到 MTD3 所映射的目录,如图 4-6-38 所示: 图 4-6-17 解压 root-ts.tgz 文件 12)解压完成后,输入命令: $ umount /mnt $ umount /media 卸载 U 盘及 MTD3 设备: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 217 - 图 4-6-18 卸载设备 13)拔出 U 盘,给 EduKit-IV 实验平台重新上电,在超级终端的界面将会出现最终的 Linux 启 动信息,同时 LCD 屏上也会显示 Linux 的启动画面。 附:Linux 操作系统下想更新启动映像、内核映像映像和根文件系统映像可以使用 Minicom 软 件,由于 Linux 下没有 vivi 支持的 USB 驱动,所以不能使用“load flash vivi u”命令,但是可 以使用 xmodem 方式来下载,命令为“load flash vivi x” 4. 6. 3 Linux 映像的运行 固化好了 bootloader、内核、根文件系统后,重启实验平台或者给实验平台重新加电,正确设 置好超级终端,将可以看到串口打印的信息,如下: VIVI version 0.1.4 (embest@embest-laptop) (gcc version 2.95.3 20010315 (release)) #0.1.4 2008年 11月 2TMMU table base address = 0x33DFC000 Succeed memory mapping. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Found saved vivi parameters. Press Return to start the LINUX now, any other key for vivi Copy linux kernel from 0x00030000 to 0x30008000, size = 0x001d0000 ... done zImage magic = 0x016f2818 Copy ramdisk from 0x00200000 to 0x30800000, size = 0x00200000 ... size = 2097152 done Setup linux parameters at 0x30000100 linux command line is: "noinitrd root=/dev/mtdblock/3 rootfstype=yaffs console=ttySAC1" MACH_TYPE = 193 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 218 - NOW, Booting Linux...... Uncompressing Linux.................................................................................... Linux version 2.6.14 (embest@embest-laptop) (gcc version 3.4.5) #2 Wed Nov 26 09:30:51 CST 2008 CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T) Machine: SMDK2410 ATAG_INITRD is deprecated; please update your bootloader. Memory policy: ECC disabled, Data cache writeback CPU S3C2410A (id 0x32410002) S3C2410: core 200.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz S3C2410 Clocks, (c) 2004 Simtec Electronics CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on USB Control, (c) 2006 pc104 CPU0: D VIVT write-back cache CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets Built 1 zonelists Kernel command line: noinitrd root=/dev/mtdblock/3 rootfstype=yaffs console=ttySAC1 irq: clearing subpending status 00000010 PID hash table entries: 512 (order: 9, 8192 bytes) timer tcon=00000000, tcnt a2c1, tcfg 00000200,00000000, usec 00001eb8 Console: colour dummy device 80x30 Dentry cache hash table entries: 16384 (order: 4, 65536 bytes) Inode-cache hash table entries: 8192 (order: 3, 32768 bytes) Memory: 64MB = 64MB total Memory: 60928KB available (2803K code, 688K data, 420K init) Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok softlockup thread 0 started up. NET: Registered protocol family 16 S3C2410: Initialising architecture SCSI subsystem initialized usbcore: registered new driver usbfs usbcore: registered new driver hub Bluetooth: Core ver 2.7 NET: Registered protocol family 31 Bluetooth: HCI device and connection manager initialized Bluetooth: HCI socket layer initialized S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 219 - DMA channel 0 at c4800000, irq 33 DMA channel 1 at c4800040, irq 34 DMA channel 2 at c4800080, irq 35 DMA channel 3 at c48000c0, irq 36 NetWinder Floating Point Emulator V0.97 (double precision) devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au) devfs: boot_options: 0x1 JFFS2 version 2.2. (NAND) (C) 2001-2003 Red Hat, Inc. yaffs Nov 25 2008 16:42:48 Installing. Initializing Cryptographic API Console: switching to colour frame buffer device 80x30 fb0: s3c2410fb frame buffer device S3C2410 RTC, (c) 2004 Simtec Electronics s3c2410-rtc s3c2410-rtc: rtc disabled, re-enabling s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410 s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410 s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410 io scheduler noop registered io scheduler anticipatory registered io scheduler deadline registered io scheduler cfq registered RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize loop: loaded (max 8 devices) nbd: registered device at major 43 usbcore: registered new driver ub DM9000A eth0 found DM9000A eth1 found Linux video capture interface: v1.00 ovcamchip: v2.27 for Linux 2.6 : OV camera chip I2C driver S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2410-nand: mapped registers at c4880000 s3c2410-nand: timing: Tacls 10ns, Twrph0 40ns, Twrph1 10ns NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit) Scanning device for bad blocks Creating 6 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00030000 : "bootloader" 0x00030000-0x00200000 : "kernel" 0x00200000-0x00400000 : "ramdisk" 0x00400000-0x03c00000 : "yaffs" 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 220 - 0x03c00000-0x03d00000 : "jffs2" 0x03d00000-0x03ff0000 : "data" usbmon: debugfs is not available s3c2410-ohci s3c2410-ohci: S3C24XX OHCI s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected Initializing USB Mass Storage driver... usbcore: registered new driver usb-storage USB Mass Storage support registered. usbcore: registered new driver hiddev usbcore: registered new driver usbhid drivers/usb/input/hid-core.c: v2.6:USB HID core driver usbcore: registered new driver dabusb drivers/usb/media/dabusb.c: v1.54:DAB-USB Interface Driver for Linux (c)1999 s3c2410_udc: version 28 Aug 2005 s3c2410_udc_probe got and enabled clock s3c2410_udc: got irq 41 mice: PS/2 mouse device common for all mice ts: Compaq touchscreen protocol output s3c2410 TouchScreen successfully loaded i2c /dev entries driver s3c2410-i2c s3c2410-i2c: slave address 0x10 s3c2410-i2c s3c2410-i2c: bus frequency set to 390 KHz s3c2410-i2c s3c2410-i2c: i2c-0: S3C I2C adapter Bluetooth: HCI USB driver ver 2.9 usbcore: registered new driver hci_usb s3c2410-sdi driver initialisation done. UDA1341 audio driver initialized NET: Registered protocol family 26 NET: Registered protocol family 2 IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 4096 (order: 2, 16384 bytes) TCP bind hash table entries: 4096 (order: 2, 16384 bytes) TCP: Hash tables configured (established 4096 bind 4096) TCP reno registered TCP bic registered 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 221 - NET: Registered protocol family 1 Bluetooth: L2CAP ver 2.7 Bluetooth: L2CAP socket layer initialized Bluetooth: RFCOMM ver 1.5 Bluetooth: RFCOMM socket layer initialized Bluetooth: RFCOMM TTY layer initialized Bluetooth: BNEP (Ethernet Emulation) ver 1.2 Bluetooth: BNEP filters: protocol multicast 802.1Q VLAN Support v1.8 Ben Greear All bugs added by David S. Miller Root-NFS: No NFS server available, giving up. VFS: Unable to mount root fs via NFS, trying floppy. yaffs: dev is 32505859 name is "mtdblock3" yaffs: Attempting MTD mount on 31.3, "mtdblock3" VFS: Mounted root (yaffs filesystem). Mounted devfs on /dev Freeing init memory: 420K init started: BusyBox v1.12.0 (2008-11-07 11:32:47 CST) starting pid 761, tty '': '/etc/init.d/rcS' running /etc/init.d/rcS mount tmpfs filesystem to /tmp mount ramfs filesystem to /var s3c2410_adc driver initial eduk4_can: module license 'unspecified' taints kernel. Tiny device SJA1000 driver registered as major device 252 successfully SJA100 init Begin,and go to reset mode 80 ts2 2 ts1 15 BPR0 0x43 BPR1 0x2f Driver init the SJA1000 devfs_mk_dev: could not append to parent for can Make device file error. insmod: 'eduk4-can.ko': Operation not permitted eduk4 dac driver initial dc_motor driver initial at24c 0-0050: 256 byte 24c02 I2C EEPROM (writable) key initialized keypad initialized, major number 249 led initialized 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 222 - s3c2410_leddot driver initial steping motor driver initial usbcore: registered new driver ov511 drivers/usb/media/ov511.c: v1.64 for Linux 2.5 : ov511 USB Camera Driver _____ ____ _ ____ |__ / _| _ \ / \ / ___| / / | | | | | |/ _ \ \___ \ / /| |_| | |_| / ___ \ ___) | /____\__, |____/_/ \_\____/ |___/ ZD1211B - version 2.15.0.0 usbcore: registered new driver zd1211b Please press Enter to activate this console. Create pluginlibman in libqpe Use QPEApplication's PluginLibraryManager QMemoryFile::QMemoryFile("/opt/qtopia/etc/dict/dawg") Created QMemoryfile for /opt/qtopia/etc/dict/dawg with a size of 189396 inserting Documents at -1 could not register server found obex lib inserting Applications at 0 inserting Games at 1 inserting Settings at 2 QGDict::hashKeyString: Invalid null key addAppLnk: No view for type (null). Can't add app (null)! Create pluginlibman in libqpe Use QPEApplication's PluginLibraryManager QuickLauncher running Registered QPE/QuickLauncher-864 同时可以看到 LCD 屏上出现 QT 的界面,如果固化的是带触摸屏的 QT 文件系统,则可以使用 触摸笔操作界面。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 223 - 图 4-6-19 启动的 QT 界面 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 224 - 第五章 嵌入式 Linux 设备驱动开发 本章节主要介绍设备驱动的一些基础知识、设备文件程序结构、以及字符设备驱动等,通过本 章节的学习,可以对嵌入式 Linux 设备驱动开发有个详细的了解,为以后独立进行设备驱动开发打 下牢固的基础。 5. 1 设备驱动基础 驱动程序是连接硬件设备和设备文件的纽带,是操作系统内核和硬件设备之间的接口。设备驱 动程序为应用程序屏蔽了硬件的细节,使应用程序可以像操作普通文件一样操作硬件设备。图 5-1-1 所示更加直观地表述了驱动程序的地位和功能。 用户程序 特殊文件 其他存储介质 或传输介质 设备文件 其他设备驱动磁盘驱动 磁盘文件 VFS VFS层 磁盘介质 系统调用界面 fs_operations结构界面 图 5-1-1 驱动程序在嵌入式 Linux 系统中的作用 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 225 - 5. 1. 1 用户态与内核态 在单内核模式系统中,操作系统提供服务的流程为(即用户应用程序调用系统内核功能):应用 主程序使用指定的参数执行系统调用指令(int x80),使 CPU 从用户态(User Mode)切换到核心态 (Kernel Mode),然后系统根据参数值调用特定的系统调用服务程序,而这些服务程序则根据需要 调用底层的支持函数以完成特定的功能。在完成了应用程序要求的服务后,操作系统又从核心态切 换回用户态,回到应用程序中继续执行后续指令。因此,单内核模式的内核也可粗略的分为三层: 调用服务的主程序层、执行系统调用的服务层和支持系统调用的底层函数。 简单来讲一个进程由于执行系统调用而开始执行内核代码,则称该进程处于内核态中。一个进 程执行应用程序自身代码则称该进程处于用户态。 intel x86 架构的 CPU 分为好几个运行级别,从 0--3,0 为最高级别,3 为最低级别。 针对不同的级别,有很多的限制,比如说传统的 in,out 指令,就是端口的输入输出指令,在 0 级下是可以用的,但在 3 级下就不能用,使用就产生陷阱,提示出错。当然限制还有很多,不只是 这一点。 操作系统就是利用这个特点,当操作系统自己的代码运行时,CPU 就切成 0 级,当用户的程序 运行时就只让它在 3 级运行,这样如果用户的程序想做什么破坏系统的事情的话,也没办法做到。 当然,低级别的程序是没法把自己升到高级别的,也就是说用户程序运行在 3 级,他想把自己 变成 0 级自己是做不到的,除非是操作系统帮忙,利用这个特性,操作系统就可以控制所有的程序 的运行,确保系统的安全了。平时把操作系统运行时的级别就叫内核态(因为是操作系统内核运行 时的状态),而且普通用户程序运行时的那个级别叫用户态。 当操作系统刚引导时,CPU 处于实模式,这时就相当于是 0 级,于是操作系统就自动得到最高 权限,然后切到保护模式时就是 3 级,这时操作系统就占了先机,成为了最高级别的运行者,由于 你的程序都是由操作系统来加载的,所以当它把你加载上来后,就把你的运行状态设为 3 级,即最 低级,然后才让你运行,所以没办法,你只能在最低级运行了,因为没办法把自己从低级上升到高 级,这就是操作系统在内核态可以管理用户程序,杀死用户程序的原因。 5. 1. 2 Linux 驱动程序结构 以 Linux 的方式看待设备可区分为 3 种基本设备类型。每个模块常常实现 3 种类型中的 1 种, 因此可分类成字符模块、块模块,或者网络模块。这种将模块分成不同类型或类别的方法并非是固 定不变的,程序员可以选择建立在一个大块代码中实现了不同驱动的巨大模块。但是,好的程序员, 常常创建一个不同的模块给每个它们实现的新功能,因为分解是可伸缩性和可扩张性的关键因素。 3 类驱动如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 226 - 1)字符设备 一个字符(char)设备是一种可以当作一个字节流来存取的设备(如同一个文件),一个字符驱 动负责实现这种行为。这样的驱动常常至少实现 open,close,read 和 write 系统调用。文本控制台 (/dev/console)和串口(/dev/ttyS0 及其他)是字符设备的例子,因为它们很好地展现了流的抽象。 字符设备通过文件系统结点来存取,例如/dev/tty1 和/dev/lp0。在一个字符设备和一个普通文件之 间唯一的不同就是,你经常可以在普通文件中移来移去,但是大部分字符设备仅仅是数据通道,你 只能顺序存取。然而,存在看起来像数据区的字符设备,你可以在里面移来移去。例如,framegrabber 经常这样,应用程序可以使用 mmap 或者 lseek 存取整个要求的图像。 2)块设备 如同字符设备,块设备通过位于/dev 目录的文件系统结点来存取。一个块设备(例如一个磁盘) 应该是可以驻有一个文件系统的。在大部分的 Unix 系统,一个块设备只能处理这样的 I/O 操作,传 送一个或多个长度经常是 512 字节(或一个更大的 2 的幂的数)的整块。Linux,相反,允许应用程 序读写一个块设备像一个字符设备一样--它允许一次传送任意数目的字节。结果就是,块和字符设 备的区别仅仅在于内核在内部管理数据的方式上,以及在内核/驱动的软件接口上不同。如同一个字 符设备,每个块设备都通过一个文件系统结点被存取,它们之间的区别对用户是透明的。块驱动和 字符驱动相比,与内核的接口完全不同。 3)网络接口 任何网络事务都通过一个接口来进行,就是说,这个接口是一个能够与其他主机交换数据的设 备。通常,一个接口是一个硬件设备,但是它也可能是一个纯粹的软件设备,比如环回接口。一个 网络接口负责发送和接收数据报文,在内核网络子系统的驱动下,不必知道单个事务是如何映射到 实际的被发送的报文上的。很多网络连接(特别那些使用 TCP 的)是面向流的,但是网络设备却常 常设计成处理报文的发送和接收。一个网络驱动对单个连接一无所知,它只处理报文。 5. 1. 3 设备文件与设备文件系统 设备管理是 Linux 中比较基础的东西,但是由于 Linux 智能程度越来越高,Udev 的使用越来越 广泛,使得越来越多的 Linux 新用户对/dev 目录下的东西变得不再熟悉。 Linux 中的设备有 2 种类型:字符设备(无缓冲且只能顺序存取)、块设备(有缓冲且可以随机 存取)。每个字符设备和块设备都必须有主、次设备号,主设备号相同的设备是同类设备(使用同一 个驱动程序)。这些设备中,有些设备是实际存在的物理硬件的抽象,而有些设备则是内核自身提供 的功能(不依赖于特定的物理硬件,又称为“虚拟设备”)。每个设备在/dev 目录下都有一个对应的 文件(节点)。可以通过 cat/proc/devices 命令查看当前已经加载的设备驱动程序的主设备号。内核 能够识别的所有设备都记录在原码树下的 Documentation/devices.txt 文件中。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 227 - 1)设备文件 Linux 内核所能识别的所有设备都记录在 http://www.lanana.org/docs/device-list/,而内核原码 树中的 Documentation/devices.txt 可能不是最新版本。 了解这些设备的最基本要求就是对每个设备文件的含义了如指掌,下面列出常见的设备文件以 及相应的含义(比较偏僻的就省略了)。 ---------------------------------------------------------------------- 主设备号 设备类型 次设备号=文件名 简要说明 ---------------------------------------------------------------------- 0 未命名设备(例如:挂载的非设备) 0 = 为空设备号保留 1 char 内存设备 1 = /dev/mem 直接存取物理内存 2 = /dev/kmem 存取经过内核虚拟之后的内存 3 = /dev/null 空设备。任何写入都将被直接丢弃,任何读取都将得到EOF。 4 = /dev/port 存取 I/O 端口 5 = /dev/zero 零字节源,只能读取到无限多的零字节。 7 = /dev/full 满设备。任何写入都将失败, 并把errno设为ENOSPC以表示没有剩余空间。 8 = /dev/random 随机数发生器。完全由用户的输入来产生随机数。 如果用户停止所有动作,则停止产生新的随机数。 9 = /dev/urandom 更快,但是不够安全的随机数发生器。尽可能由用户的输入来产生 随机数,如果用户停止所有动作,则把已经产生的随机数做为种子 来产生新的随机数。 10 = /dev/aio 异步 I/O 通信接口 11 = /dev/kmsg 任何对该文件的写入都将作为 printk 的输出 1 block RAM disk 0 = /dev/ram0 第 1 个 RAM disk (initrd只能使用ram0) 1 = /dev/ram1 第 2 个 RAM disk ... 200 = /dev/ram200 第 200 个 RAM disk 4 char TTY(终端)设备 0 = /dev/tty0 当前虚拟控制台 1 = /dev/tty1 第 1 个虚拟控制台 ... 63 = /dev/tty63 第 63 个虚拟控制台 4 block 如果根文件系统是以只读方式挂载的,那么就不可能创建真正的 设备节点,此时就使用该设备作为动态分配的主设备的别名 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 228 - 0 = /dev/root 5 char 其他 TTY 设备 0 = /dev/tty 当前 TTY 设备 1 = /dev/console 系统控制台(一般是/dev/tty0) 2 = /dev/ptmx 所有 PTY master 的复用器 7 char 虚拟控制台捕捉设备(这些设备既允许读也允许写) 0 = /dev/vcs 当前虚拟控制台(vc)的文本内容 1 = /dev/vcs1 tty1 的文本内容 ... 63 = /dev/vcs63 tty63 的文本内容 128 = /dev/vcsa 当前虚拟控制台(vc)的文本/属性内容 129 = /dev/vcsa1 tty1 的文本/属性内容 ... 191 = /dev/vcsa63 tty63 的文本/属性内容 7 block 回环设备(用一个普通的磁盘文件来模拟一个块设备)对回环设备 的绑定由 mount(8) 或 losetup(8) 处理 0 = /dev/loop0 第 1 个回环设备 1 = /dev/loop1 第 2 个回环设备 ... 8 block SCSI 磁盘(0-15) 0 = /dev/sda 第 1 个 SCSI 磁盘(整个磁盘) 16 = /dev/sdb 第 2 个 SCSI 磁盘(整个磁盘) 32 = /dev/sdc 第 3 个 SCSI 磁盘(整个磁盘) ... 240 = /dev/sdp 第 16 个 SCSI 磁盘(整个磁盘) 分区表示方法如下(以第 3 个 SCSI 磁盘为例) 33 = /dev/sdc1 第 1 个分区 34 = /dev/sdc2 第 2 个分区 ... 47 = /dev/sdc15 第 15 个分区 对于Linux/i386 来说,分区 1-4 是主分区,5-15 是逻辑分区。 9 block Metadisk(RAID)设备 0 = /dev/md0 第 1 组 metadisk 1 = /dev/md1 第 2 组 metadisk ... metadisk 驱动用于将同一个文件系统分割到多个物理磁盘上。 10 char 非串口鼠标,各种杂项设备和特性 1 = /dev/psaux PS/2 鼠标 131 = /dev/temperature 机器内部温度 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 229 - 134 = /dev/apm_bios APM(高级电源管理)BIOS 135 = /dev/rtc 实时时钟(Real Time Clock) 144 = /dev/nvram 非易失配置 RAM 162 = /dev/smbus 系统管理总线(System Management Bus) 164 = /dev/ipmo Intel的智能平台管理(Intelligent Platform Management)接 口 173 = /dev/ipmikcs 智能平台管理(Intelligent Platform Management)接口 175 = /dev/agpgart AGP图形地址重映射表(Graphics Address Remapping Table) 182 = /dev/perfctr 性能监视计数器 183 = /dev/hwrng 通用硬件随机数发生器 184 = /dev/cpu/microcode CPU微代码更新接口 186 = /dev/atomicps 进程状态数据的原子快照 188 = /dev/smbusbios SMBus(系统管理总线) BIOS 200 = /dev/net/tun TAP/TUN 网络设备(TAP/TUN以软件的方式实现了网络设备) TAP模拟了以太网帧(第二层),TUN模拟了IP包(第三层)。 202 = /dev/emd/ctl 增强型 Metadisk RAID (EMD) 控制器 220 = /dev/mptctl Message passing technology (MPT)control 223 = /dev/input/uinput 用户层输入设备驱动支持 227 = /dev/mcelog X86_64 Machine Check Exception driver 228 = /dev/hpet HPET driver 229 = /dev/fuse Fuse(用户空间的虚拟文件系统) 231 = /dev/snapshot 系统内存快照 232 = /dev/kvm 基于内核的虚构机(基于AMD SVM和Intel VT硬件虚拟技术) 11 block SCSI CD-ROM 设备 0 = /dev/scd0 第 1 个 SCSI CD-ROM 1 = /dev/scd1 第 2 个 SCSI CD-ROM ... 13 char 核心输入设备 32 = /dev/input/mouse0 第 1 个鼠标 33 = /dev/input/mouse1 第 2 个鼠标 ... 62 = /dev/input/mouse30 第 31 个鼠标 63 = /dev/input/mice 所有鼠标的统一 64 = /dev/input/event0 第 1 个事件队列 65 = /dev/input/event1 第 2 个事件队列 ... 95 = /dev/input/event1 第 32 个事件队列 21 char 通用 SCSI 设备(通常是SCSI光驱) 0 = /dev/sg0 第 1 个通用 SCSI 设备 1 = /dev/sg1 第 2 个通用 SCSI 设备 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 230 - ... 29 char 通用帧缓冲(frame buffer)设备 0 = /dev/fb0 第 1 个帧缓冲设备 1 = /dev/fb1 第 2 个帧缓冲设备 ... 31 = /dev/fb31 第 32 个帧缓冲设备 30 char iBCS-2 兼容设备 0 = /dev/socksys 套接字访问接口 1 = /dev/spx SVR3 本地 X 接口 32 = /dev/inet/ip 网络访问接口 33 = /dev/inet/icmp 34 = /dev/inet/ggp 35 = /dev/inet/ipip 36 = /dev/inet/tcp 37 = /dev/inet/egp 38 = /dev/inet/pup 39 = /dev/inet/udp 40 = /dev/inet/idp 41 = /dev/inet/rawip 此外,iBCS-2 还需要下面的连接必须存在 /dev/ip -> /dev/inet/ip /dev/icmp -> /dev/inet/icmp /dev/ggp -> /dev/inet/ggp /dev/ipip -> /dev/inet/ipip /dev/tcp -> /dev/inet/tcp /dev/egp -> /dev/inet/egp /dev/pup -> /dev/inet/pup /dev/udp -> /dev/inet/udp /dev/idp -> /dev/inet/idp /dev/rawip -> /dev/inet/rawip /dev/inet/arp -> /dev/inet/udp /dev/inet/rip -> /dev/inet/udp /dev/nfsd -> /dev/socksys /dev/X0R -> /dev/null 36 char Netlink 支持 0 = /dev/route 路由,设备更新,kernel to user 3 = /dev/fwmonitor Firewall packet 复制 59 char sf 防火墙模块 0 = /dev/firewall 与 sf 内核模块通信 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 231 - 65 block SCSI 磁盘(16-31) 0 = /dev/sdq 第 17 个 SCSI 磁盘(整个磁盘) 16 = /dev/sdr 第 18 个 SCSI 磁盘(整个磁盘) 32 = /dev/sds 第 19 个 SCSI 磁盘(整个磁盘) ... 240 = /dev/sdaf 第 32 个 SCSI 磁盘(整个磁盘) 66 block SCSI 磁盘(32-47) 0 = /dev/sdag 第 33 个 SCSI 磁盘(整个磁盘) 16 = /dev/sdah 第 34 个 SCSI 磁盘(整个磁盘) 32 = /dev/sdai 第 35 个 SCSI 磁盘(整个磁盘) ... 240 = /dev/sdav 第 48 个 SCSI 磁盘(整个磁盘) 89 char I2C 总线接口 0 = /dev/i2c-0 第 1 个 I2C 适配器 1 = /dev/i2c-1 第 2 个 I2C 适配器 ... 98 block 用户模式下的虚拟块设备(分区处理方式与 SCSI 磁盘相同) 0 = /dev/ubda 第 1 个用户模式块设备 16 = /dev/udbb 第 2 个用户模式块设备 ... 103 block 审计(Audit)设备 0 = /dev/audit 审计(Audit)设备 128-135 char Unix98 PTY master这些设备不应当存在设备节点,而应当通过 /dev/ptmx 接口访问。 136-143 char Unix98 PTY slave这些设备节点是自动生成的(伴有适当的权限和 模式),不能手动创建。方法是通过使用适当的 mount 选项(通 常是:mode=0620,gid=<"tty"组的gid>)将 devpts 文件系统 挂载到/dev/pts目录即可。 0 = /dev/pts/0 第 1 个 Unix98 PTY slave 1 = /dev/pts/1 第 2 个 Unix98 PTY slave ... 153 block Enhanced Metadisk RAID (EMD) 存储单元 (分区处理方式与 SCSI 磁盘相同) 0 = /dev/emd/0 第 1 个存储单元 1 = /dev/emd/0p1 第 1 个存储单元的第 1 个分区 2 = /dev/emd/0p2 第 1 个存储单元的第 2 个分区 ... 15 = /dev/emd/0p15 第 1 个存储单元的第 15 个分区 16 = /dev/emd/1 第 2 个存储单元 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 232 - 32 = /dev/emd/2 第 3 个存储单元 ... 240 = /dev/emd/15 第 16 个存储单元 180 char USB 字符设备 96 = /dev/usb/hiddev0 第 1 个USB人机界面设备 (鼠标/键盘/游戏杆/手写版等人操作计算机的设备) ... 111 = /dev/usb/hiddev15 第 16 个USB人机界面设备 180 block USB 块设备(U盘之类) 0 = /dev/uba 第 1 个USB 块设备 8 = /dev/ubb 第 2 个USB 块设备 16 = /dev/ubc 第 3 个USB 块设备 ... 192 char 内核 profiling 接口 0 = /dev/profile Profiling 控制设备 1 = /dev/profile0 CPU 0 的 Profiling 设备 2 = /dev/profile1 CPU 1 的 Profiling 设备 ... 193 char 内核事件跟踪接口 0 = /dev/trace 跟踪控制设备 1 = /dev/trace0 CPU 0 的跟踪设备 2 = /dev/trace1 CPU 1 的跟踪设备 ... 195 char Nvidia 图形设备(比如显卡) 0 = /dev/nvidia0 第 1 个 Nvidia 卡 1 = /dev/nvidia1 第 2 个 Nvidia 卡 ... 255 = /dev/nvidiactl Nvidia 卡控制设备 202 char 特定于CPU模式的寄存器(model-specific register,MSR) 0 = /dev/cpu/0/msr CPU 0 的 MSRs 1 = /dev/cpu/1/msr CPU 1 的 MSRs ... 203 char CPU CPUID 信息 0 = /dev/cpu/0/cpuid CPU 0 的 CPUID 1 = /dev/cpu/1/cpuid CPU 1 的 CPUID 2)设备文件系统 过去的 Linux 系统中提供了一个抽象化的设备目录,叫做/dev。在该目录中用户可以找到设备 节点,这些特殊的文件直接指向了系统中的硬件设备。例如,/dev/hda 指向了系统中的第一个 IDE 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 233 - 设备。利用这些提供给用户的设备文件,他们可以编写应用程序像访问普通文件一样来访问硬件, 而不需要通过特殊的 API。 (1)查看设备树 如果你查看一个设备文件,你将会看到类似下面的内容:查看设备文件信息。 Embest@Ubuntu:/dev# ls -l brw-rw--- 1 root disk 3,0 2008-10-06 18:52 hda ...... crw-rw-rw- 1 root tty 3, 209 2008-10-06 18:52 ttyc1 crw-rw-rw- 1 root tty 3, 210 2008-10-06 18:52 ttyc2 crw-rw-rw- 1 root tty 3, 211 2008-10-06 18:52 ttyc3 crw-rw-rw- 1 root tty 3, 212 2008-10-06 18:52 ttyc4 crw-rw-rw- 1 root tty 3, 213 2008-10-06 18:52 ttyc5 crw-rw-rw- 1 root tty 3, 214 2008-10-06 18:52 ttyc6 crw-rw-rw- 1 root tty 3, 215 2008-10-06 18:52 ttyc7 crw-rw-rw- 1 root tty 3, 216 2008-10-06 18:52 ttyc8 crw-rw-rw- 1 root tty 3, 217 2008-10-06 18:52 ttyc9 crw-rw-rw- 1 root tty 3, 218 2008-10-06 18:52 ttyca crw-rw-rw- 1 root tty 3, 219 2008-10-06 18:52 ttycb ...... 在上一个范例中我们看到/dev/hda 是一个块设备。但是,更重要的是,它链接了两个特殊的号 码:3,0。这对号码称为主-次对。它用于核心将该设备文件映射到真实设备上。主编号指向了该设 备,而次编号指向了子设备,这看起来冲突吗?不会的。 这里有两个范例为/dev/hda4 和/dev/tty5。第一个设备文件指向了第一个 IDE 设备的第四个分 区。它的主次对为 3,4,在这里,次编号指向了分区而主编号指向了设备。第二个范例中,主次对 为 3,4,在这里,主编号指向到终端设备,次编号指向终端编号(在这里时第五个终端)。 ¾ 相关问题 如果快速扫描一次/dev 中的文件,将会发现其中不仅仅有机器上的所有设备,或者说在你机器 上所有你可以看到的设备,而且还有许多你并没有指向该设备的设备文件。至于如何管理这大量的 设备组将会在后面提到。你可以先想象一下改变你机器上所有设备所对应的设备文件的权限,然后 将它们恢复过来。 当你添加一个新设备到你的系统中,但是这个设备之前并没有对应的设备文件时,你应该自己 创建一个,高级用户可以很熟练地通过/dev 中的./MAKEDEV 来做这件事情,但是你知道哪种设备文 件需要你创建吗? 当你有了一个通过设备文件来访问硬件的程序时,你不能将根目录以只读属性加载,没有其它 办法时必须以可读写模式加载。并且你不能将/dev 加载于任何一个单独的分区上,因为 mount 命令 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 234 - 需要/dev 来加载分区。 ¾ 实际问题 其实,内核开发者们已经遇到了前面所提到的种种问题。不管怎样,其中大多数问题能在这里 得到更详细的描述 http://www.atnf.csiro.au/people/rgooch/Linux/docs/devfs.html#faq-why。我们 现在并没有在讨论这些,但是请注意这些问题将直接关系到稳定版内核:devfs。 devfs 就是解决方案,devfs 处理了所有的问题。它只是提供给用户管理这些已有设备的功能, 当新硬件添加时自动添加新的节点,使将根目录以只读属性加载成为可能。以及解决了其它我们之 前还没有发现的问题。 举个例子,通过 devfs 你将可以逃离主次设备对管理的痛苦。它始终支持着(为了向后兼容), 但是不必要。它使 Linux 自动支持更多的设备成为可能,直到没有任何资源可以分配(数字总是有 限的)。 (2)浏览设备树 首先你要注意的事情是 devs 通过设备组目录树来组织设备文件。这种方式提高了可读性,同时 所有相关设备都将放在其标准的目录里。 举个例子,所有的 IDE 相关设备都在/dev/ide/设备目录里,而 SCSI 相关设备在/dev/scsi/。SCSI 和 IDE 磁盘在某种程度上有些相同点,这意味着他们可能在同一个子目录结构里。 IDE 和 SCSI 磁盘通过其控制卡进行控制(可以是内置与主板上的,也可以是单独的控制卡), 叫做控制器,每个控制卡可以有很多通道。一个通道总线。在每一个总线里,还能有许多编号。假 设某个编号控制着一个磁盘,该编号成为目标。许多 SCSI 设备还可以有多个逻辑单元号 (LogicalUnitNumbers),举个例子,某个设备链接到多个媒体(如高端的磁带设备)时,你通常只 有一个逻辑单元号,lun0/。 所以,当/dev/hda4 如果已经被使用过了,我们会拥有一个 /dev/ide/host0/bus0/target0/lun0/part4,这将使管理容易很多。 注释:你同样能使用类似Unix的磁盘命名方式来管理磁盘,如c0b0t0u0p2。他们能在/dev/ide/hd 或者/dev/scsi/hd 类似的路径中找到。 这里是一个目录范例,这是我的计算机的目录列表:/dev 目录列表。 adsp ptybd ptyq9 ptyv5 ram1 tty53 ttydd ttys5 ttyx1 agpgart ptybe ptyqa ptyv6 ram10 tty54 ttyde ttys6 ttyx2 apm_bios ptybf ptyqb ptyv7 ram11 tty55 ttydf ttys7 ttyx3 audio ptyc0 ptyqc ptyv8 ram12 tty56 ttye0 ttys8 ttyx4 bus ptyc1 ptyqd ptyv9 ram13 tty57 ttye1 ttys9 ttyx5 cdrom ptyc2 ptyqe ptyva ram14 tty58 ttye2 ttysa ttyx6 console ptyc3 ptyqf ptyvb ram15 tty59 ttye3 ttysb ttyx7 core ptyc4 ptyr0 ptyvc ram2 tty6 ttye4 ttysc ttyx8 disk ptyc5 ptyr1 ptyvd ram3 tty60 ttye5 ttysd ttyx9 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 235 - dmmidi1 ptyc6 ptyr2 ptyve ram4 tty61 ttye6 ttyse ttyxa dri ptyc7 ptyr3 ptyvf ram5 tty62 ttye7 ttysf ttyxb dsp ptyc8 ptyr4 ptyw0 ram6 tty63 ttye8 ttyt0 ttyxc fb0 ptyc9 ptyr5 ptyw1 ram7 tty7 ttye9 ttyt1 ttyxd ...... 通过 devfsd 实现向后兼容。 使用新的声音主题自然能够增加情趣,但是许多工具和程序使用的是旧的主题。请确认你的系 统运行正常,devfsd 正常运行。并且该守护进程创建了旧方式的名称来指向设备文件。 $ ls -l hda5 brw-rw---- 1 root disk 3, 5 2008-10-06 18:52 hda5 5. 1. 4 Linux 模块 Linux 是单内核结构,也就是说,它是一个大程序,其中任一函数都可以访问公共数据结构和其 它函数调用。单核结构在添加新模块时,一种方法是重新调整设置,所以非常费时。比如,你想在 内核中加一个 NCR 810 SCSI 的驱动程序,你必须重新设置,重建内核。这也有另外一个办法,Linux 允许动态装载和卸掉模块。Linux 模块是一段可以在机器起动后任意时间被动态连接的代码。在不需 要时,它们可以被从内核中卸掉。大多数 Linux 模块是设备驱动程序或伪设备驱动程序,如网络驱 动程序,文件系统等。 可以使用 insmod 和 rmmod 命令来装载和卸掉 Linux 模块,内核自己也可以调用内核驻留程序 (Kerneld)来按需要装载和卸掉模块。 按需动态装载模块可以使内核保持最小,并更具灵活性。例如,我很少用到 VFAT 文件系统, 所以让 Linux 内核只在装载 VFAT 分区时,才自动上载 VFAT 文件系统。当卸掉 VFAT 分区时,内核 会检测到,并自动卸掉 VFAT 文件系统。当测试新程序时,如果不想每次都重建内核,动态装载模 块是非常有用的。但是,动态模块会多消耗一些内存,并对速度有一定影响。而且模块装载程序是 一段代码,它的数据将占用一部份内存。这样还会造成不能直接访问内核资源,效率不高的问题。 一旦 Linux 模块被装载后,它就和一般内核代码一样,对其它内核代码,享受同样的访问权限。 换句话说,Linux 内核模块可以像其它内核代码,或驱动程序一样使系统崩溃。模块可以使用内核资 源,但首先它需知道怎样调用。例如,一个模块要调用 Kmalloc()(内核内存分配程序)。但在模块 建立时,它并不知道到哪儿去找 Kmalloc(),所以在它被装载时,内核必须先设定模块中所有 Kmalloc() 调用的函数指针。内核有一张所有资源调用的列表,在模块被装载时,内核重设所有资源调用的函 数指针。Linux 允许栈式模块,即一个模块调用另一个模块的函数。例如,由于 VFAT 文件系统可以 看成是 FAT 文件系统的超集,所以 VFAT 文件系统模块需要调用 FAT 文件系统提供的服务。一个模 块调用另一模块的资源与调用内核资源很相似。唯一的不同是被调用的模块需被先载入。一个模块 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 236 - 被载入后,内核将修改它的内核符号表(KERAELSYMOBOLTABLE),加入新载入模块提供的所有资 源和符号。所以另一个模块被载入时,它就可以调用所有已载入模块提供的服务。 当卸掉一模块时,内核先确定该模块不会再被调用,然后通过某种方式通知它。在该模块被内 核卸掉以前,该模块须释放所有占用的系统资源。例如,内存或中断。当模块被卸掉后,内核从内 核符号表中删除所有该模块提供的资源。 如果模块代码不严谨,它将使整个操作系统崩溃。另一个问题,如果载入的是为其它版本服务 的模块,那怎么办?例如,一个模块凋用一个内函数,但提供了错误的输入参数,这将导致运行错误。 但内核可以在模块被载入时选择性地通过严格版本检查来杜绝这种现象。 载入模块有两种方法。第一种是通过 INSTALL 命令来载入;另一种更聪明的方法是在模块被调 用时自动载入,这叫所需载入(DEMANDLOADING)。例如,当用户在装一个不在内核中的文件系统 时,内核会自动调用内核驻留程序(KERNELD)来载入对应的处理模块。 内核驻留程序是一个具有超级用户权限的普通用户程序。当它被启动时(通常在系统启动时), 它将打开一个和内核之间的进程间通信管道(IPC CHANNEL),内核将利用这条管道来通知进程驻留 程序去完成各种任务。内核驻留程序的主要任务是载入和卸掉模块,它也能完成一些其他任务,例 如按需打开和关掉一条通过串口的 DDD LINK。KERNELD 自己并不完成这些任务,它将调用如 INSMOD 这样的命令来完成,KERNELD 只是一个内核的代理,协调完成各项任务。 1)载入模块 载入模块时,INSMODE 命令必须先找到要被载入的模块。可所需载入的模块通常被放在 /LIB/MODULES/KERNEL-VERSION 下,这些模块与一般系统程序都是已连接好的目标代码,不同处 在于模块是可重定位的映像文件。也就是说,模块并不是从一个固定的地址开始执行的,模块可以 是 a.out,也可以是 ELF 格式的目标代码。INSMODE 通过一个有系统权限的调用来找到内核中可被 调用的资源。 系统(资源)符号由名和值两部份组成。内核用 MODULE_LIST 指针指向其管理的所有模块所 串成的链表。内核的输出符号表在第一个 MODULE 数据结构中,并不是内核所有的符号都能被模块 调用,可调用符号必须被加入输出符号表中,而输出符号表是与内核一起编译连接的。例如,当一 驱动程序想控制某一系统中断时,她需调用“REQUEST_IRQ”这样一个系统函数,在我机器的内核 中,它现在的值是 0x0010cd30,你可以看/PROC/KSYMS 文件或用 KSYMS 来查询。KSYMS 命令可 以显示所有内核输出符号的值,也可以显示载入模块的输出符号的值。当 INSMOD 载入模块时,它 先将模块载入虚存,根据内核输出符号,重设所有内核资源函数调用的指针,即在模块的函数调用 处写入对应符号的物理地址。 当 INSMOD 重设完内核输出符号的地址后,它将调用一个系统函数,要求内核分配足够的空间。 内存就会分配一个新的 MODULE 数据结构和足够的内存来装载这个新模块,并把这个 MODULE 数 据结构放在模块链表的最后,置成未初始化(UNINITALIZED)。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 237 - 显示的是内核载入 FAT 和 VFAT 两模块后的模块链表。链表的第一模块并没有显示出来,那是 一个伪模块,只是用来记录内核的输出符号表。你可以用 ISMOD 命令来列出所有载入模块及它们之 间的关系。ISMOD 只是格式化的输出记录内核链表的/PROC/MODULES 文件。INSMOD 可以访问内 核分配给新载入模块的内存,它先将模块写入这块内存,然后对它进行重定位处理,使模块可以从 这个地址开始执行。由于每次模块被载入时,无论在不在同一台机器上,都不大可能分配到相同的 内存地址,所以重定位(即重设它的函数指针)是必须的。 新载入模块也可以输出符号,INSMOD 会为这些符号建一个表。另外,每一个模块必须有自己 的初始和清理(即析构)函数。这两个函数不能被输出,但它的地址将在初始化时由 INSMOD 传给 内核。 当一个新模块被载入内核时,它要更新系统符号表及被它调用的模块。内核中被调用模块都需 在其符号表的最后保留一列指向调用模块的指针。显示 VFAT 文件系统依赖于 FAT 文件系统,所以, 在 FAT 模块中有一个指向 VFAT 的指针,这个指针是在 VFAT 被载入时加入的。内核将调用模块的初 始化函数,如果成功,它将继续完成安装新模块的任务。模块的清理函数的地址将被存在它的 MODULE 数据结构中。当模块被卸掉时,它将被调用。到这个时候模块的状态被置为“运行” (RUNNING)。 2)卸掉模块 用 RMMOD 命令可以卸掉一个指定模块,但按需载入模块没用时,它会被内核自动卸掉, KERNELD 每次被激活时,它会调用一个系统函数将所有没用的模块从内核中卸掉。例如,如果你装 了一个 ISO9660 的 CDROM,并且它的文件系统是一个按需载入模块,那么当你卸掉 CDROM 后不 久,ISO9660 文件系统也会被从内核中卸掉。你可以在起动 KERNELD 时,设置其被激活的时间间 隔,我的 KERNELD 每 180 秒被激活一次。 当还在被其他模块调用时,模块是不能被卸掉的。例如,当你还在用 VFAT 文件系统时,VFAT 模块不会被卸掉。当你看 ISMOD 命令的输出时,你会发现每个模块都带有一个计数器。这个计数器 记录依赖于该模块的模块数。在上面的例子中,VFAT 和 MSDOS 都依赖于 FAT 模块,所以 FAT 模块 的计数器为 2,VFAT 和 MODOS 的都为 1,表示只有文件系统依赖于它们。如果我再装入一个 VFAT 文件系统,VFAT 模块的计数器将变成 2。模块的计数器是它映像的第一个长字(LONGWORD)。 这个长字同时也记录了 AUTOCLEAN 和 VISITED 两个标志,只有按需载入模块才用到这两个标 志。AUTOCLEAN 用来使系统识别哪一个模块需被自动卸掉。VISITED 标志表示该模块是否还在被其 它模块调用。每次 KERNELD 试图卸掉已没用的按需载入模块时,系统检查所有模块。它只注意标为 AUTOCLEAN 并正在运行的模块,如果这个模块没有设置 VISTIED 标志,它将被卸掉。否则,系统 就清掉 VISTIED 标志,并继续检查下一模块。 当一个模块可以被卸掉时,系统会调用它的清理函数来释放它所占用的所有系统资源。 该模块的 MODULE 数据结构将被标为 DELEDTED,并从模块链表中去除,所有它依赖的模块会 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 238 - 修改它们的指针,表示该模块已不再依赖它们了。所有该模块占用的内存将被释放掉。 注册设备编号仅仅是驱动代码必须进行的诸多任务中的第一个。首先需要涉及一些驱动操作, 大部分的基础性的驱动操作包括 3 个重要的内核数据结构,称为 file_operations,file,和 inode, 需要对这些结构进行基本了解才能够做大量感兴趣的事情。 struct file_operations 是一个字符设备把驱动的操作和设备号联系在一起的纽带,是一系列指针 的集合,每个被打开的文件都对应于一系列的操作,这就是 file_operations,用来执行一系列的系 统调用。 struct file 代表一个打开的文件,在执行 file_operation 中的 open 操作时被创建,这里需要注意 的是与用户空间 inode 指针的区别,一个在内核,而 file 指针在用户空间,由 c 库来定义。 struct inode 被内核用来代表一个文件,注意和 struct file 的区别,struct inode 是代表文件,struct file 是代表打开的文件。 struct inode 包括很重要的二个成员: ¾ dev_t i_rdev 设备文件的设备号; ¾ struct cdev *i_cdev 代表字符设备的数据结构。 struct inode 结构是用来在内核内部表示文件的。同一个文件可以被打开好多次,所以可以对应 很多 struct file,但是只对应一个 struct inode。 5. 1. 5 file_operations 结构 结构体 file_operations 在头文件 Linux/fs.h 中定义,用来存储驱动内核模块提供的对设备进行 各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的事务的函 数的地址。 举个例子,每个字符设备需要定义一个用来读取设备数据的函数。结构体 file_operations 中存 储着内核模块中执行这项操作的函数的地址。 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 239 - int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long,loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t,void __user *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t,loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long,unsigned long, unsigned long, unsigned long); }; 驱动内核模块是不需要实现每个函数的。像视频卡的驱动就不需要从目录的结构中读取数据。 那么,相对应的 file_operations 中的项就为 NULL。 ¾ struct module *owner; 第一个 file_operations 成员根本不是一个操作,它是一个指向拥有这个结构的模块的指针。这 个成员用来在它的操作还在被使用时阻止模块被卸载。几乎所有时间中,它被简单初始化为 THIS_MODULE,一个在 中定义的宏。 ¾ loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置,并且新位置作为(正的)返回值。loff_t 参数是 一个“long offset”,并且就算在 32 位平台上也至少 64 位宽,错误由一个负返回值指示。如果这 个函数指针是 NULL,seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器。 ¾ ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用来从设备中获取数据。在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument")失败。一个非负返回值代表了成功读取的字节数( 返回值是一个“signed size”类型, 常常是目标平台本地的整数类型)。 ¾ ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t); 初始化一个异步读——可能在函数返回前不结束的读操作。如果这个方法是 NULL,所有的操 作会由 read 代替进行(同步地)。 ¾ ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 发送数据给设备。如果 NULL,-EINVAL 返回给调用 write 系统调用的程序。如果非负,返回 值代表成功写的字节数。 ¾ ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 240 - 初始化设备上的一个异步写。 ¾ int (*readdir) (struct file *, void *, filldir_t); 对于设备文件这个成员应当为 NULL,它用来读取目录,并且仅对文件系统有用。 ¾ unsigned int (*poll) (struct file *,struct poll_table_struct *); poll 方法是 3 个系统调用的后端:poll,epoll,和 select,都用作查询对一个或多个文件描述 符的读或写是否会阻塞。poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的,并且, 可能地,提供给内核信息用来使调用进程睡眠直到 I/O 变为可能。如果一个驱动的 poll 方法为 NULL,设备假定为不阻塞地可读可写。 ¾ int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道,这不是读也不是 写)。另外,几个 ioctl 命令被内核识别而不必引用 fops 表。如果设备不提供 ioctl 方法,对于任 何未事先定义的请求(-ENOTTY,“设备无这样的 ioctl”),系统调用返回一个错误。 ¾ int (*mmap) (struct file *, struct vm_area_struct *); mmap 用来请求将设备内存映射到进程的地址空间。如果这个方法是 NULL,mmap 系统调用 返回 –ENODEV。 ¾ int (*open) (struct inode *, struct file *); 尽管这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法。如果这个项是 NULL,设备打开一直成功,但是你的驱动不会得到通知。 ¾ int (*flush) (struct file *); flush 操作在进程关闭它的设备文件描述符的拷贝时调用;它应当执行(并且等待)设备的任何 未完成的操作。这个必须不要和用户查询请求的 fsync 操作混淆了。当前,flush 在很少驱动中使 用;SCSI 磁带驱动使用它,例如,为确保所有写的数据在设备关闭前写到磁带上。如果 flush 为 NULL,内核简单地忽略用户应用程序的请求。 ¾ int (*release) (struct inode *, struct file *); 在文件结构被释放时引用这个操作。如同 open,release 可以为 NULL。 ¾ int (*fsync) (struct file *, struct dentry *, int); 这个方法是 fsync 系统调用的后端,用户调用来刷新任何挂着的数据。如果这个指针是 NULL, 系统调用返回 –EINVAL。 ¾ int (*aio_fsync)(struct kiocb *, int); 这是 fsync 方法的异步版本。 ¾ int (*fasync) (int, struct file *, int); 这个操作用来通知设备它的 FASYNC 标志的改变。这个成员可以是 NULL 如果驱动不支持异步 通知。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 241 - ¾ int (*lock) (struct file *, int, struct file_lock *); lock 方法用来实现文件加锁;加锁对常规文件是必不可少的特性,但是设备驱动几乎从不实现 它。 ¾ ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ¾ ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); 这些方法实现发散/汇聚读和写操作。应用程序偶尔需要做一个包含多个内存区的单个读或写操 作;这些系统调用允许它们这样做而不必对数据进行额外拷贝。如果这些函数指针为 NULL,read 和 write 方法被调用(可能多于一次)。 ¾ ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *); 这个方法实现 sendfile 系统调用的读,使用最少的拷贝从一个文件描述符搬移数据到另一个。 例如,它被一个需要发送文件内容到一个网络连接的 web 服务器使用。设备驱动常常使 sendfile 为 NULL。 ¾ ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); sendpage 是 sendfile 的另一半;它由内核调用来发送数据,一次一页到对应的文件。设备驱 动实际上不实现 sendpage。 ¾ unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段。这个任 务通常由内存管理代码进行,这个方法存在是为了使驱动能强制执行特殊设备可能有的任何的对齐 请求。大部分驱动可以置这个方法为 NULL。 ¾ int (*check_flags)(int); 这个方法允许模块检查传递给 fnctl(F_SETFL...)调用的标志。 ¾ int (*dir_notify)(struct file *, unsigned long)。 这个方法在应用程序使用 fcntl 来请求目录改变通知时调用。只对文件系统有用,驱动不需要 实现 dir_notify。 gcc 还有一个方便使用这种结构体的扩展,你会在较现代的驱动内核模块中见到。新的使用这 种结构体的方式如下: struct file_operations fops = { read: device_read, write: device_write, open: device_open, release: device_release }; 同样也有 C99 语法的使用该结构体的方法,并且它比 GNU 扩展更受推荐。为了方便那些想移植 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 242 - 你的代码的人,你最好使用这种语法。它将提高代码的兼容性: struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release }; 这种语法很清晰,你也必须清楚的意识到没有显示声明的结构体成员都被 gcc 初始化为 NULL。 指向结构体 struct file_operations 的指针通常命名为 fops。 5. 1. 6 inode 结构 inode 在 Linux 里算是一个很大的结构,基本上跟 super_block 结构一样。我们一样可以把 inode 结构分成几部分来看:链表管理域,基本资料,用来做 inode synchronization 的资料,跟结构体管 理有关的资料,Quota 管理域,跟 file lock 有关的域,以及一组用来操作 inode 的函数。 struct inode { struct list_head i_hash; struct list_head i_list; struct list_head i_dentry; struct list_head i_dirty_buffers; unsigned longi_ino; // 每一个inode都有一个序号,经由super block结构和其序号, // 我们可以很轻易的找到这个inode。 atomic_t i_count; // 在Kernel里,很多的结构都会记录其reference count,以确保 // 如果某个结构正在使用,它不会被不小心释放掉,i_count就是 // 其reference count。 kdev_t i_dev; // inode所在的device代码 umode_t i_mode; // inode的权限 nlink_t i_nlink; // hard link的个数 uid_t i_uid; // inode拥有者的id gid_t i_gid; // inode所属的群组id kdev_t i_rdev; // 若inode代表的是device的话,那此字段将记录device的代码 off_t i_size; // inode所代表的档案大小 time_t i_atime; // inode最近一次的存取时间 time_t i_mtime; // inode最近一次的修改时间 time_t i_ctime; // inode的产生时间 unsigned long i_blksize; // inode在做IO时的区块大小 unsigned long i_blocks; // inode所使用的block数,一个block为 512 byte unsigned long i_version; // 版本号码 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 243 - unsigned short i_bytes; struct semaphore i_sem; struct rw_semaphore i_truncate_sem; struct semaphore i_zombie; struct inode_operations *i_op; struct file_operations *i_fop; // former ->i_op->default_file_ops struct super_block *i_sb; // inode所属档案系统的super block wait_queue_head_t i_wait; struct file_lock *i_flock; // 用来做file lock struct address_space *i_mapping; struct address_space i_data; struct dquot *i_dquot [MAXQUOTAS]; // 以下三个必须联合使用 struct pipe_inode_info *i_pipe; struct block_device *i_bdev; struct char_device *i_cdev; unsigned long i_dnotify_mask; // 文件夹标识事件 struct dnotify_struct *i_dnotify; // 标识文件夹 unsigned long i_state; // inode目前的状态,可以是I_DIRTY,I_LOCK // 和 I_FREEING的OR组合 unsigned int i_flags; // 记录此inode的参数 unsigned char i_sock; // 用来记录此inode是否为socket atomic_t i_write count; unsigned int i_attr_flags; // 用来记录此inode的属性参数 __u32 i_generation; union { struct minix_inode_info minix_i; struct ext2_inode_info ext2_i; struct ext3_inode_info ext3_i; struct hpfs_inode_info hpfs_i; struct ntfs_inode_info ntfs_i; struct msdos_inode_info msdos_i; struct umsdos_inode_info umsdos_i; struct iso_inode_info isofs_i; struct sysv_inode_info sysv_i; struct affs_inode_info affs_i; struct ufs_inode_info ufs_i; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 244 - struct efs_inode_info efs_i; struct romfs_inode_info romfs_i; struct shmem_inode_info shmem_i; struct coda_inode_info coda_i; struct smb_inode_info smbfs_i; struct hfs_inode_info hfs_i; struct adfs_inode_info adfs_i; struct qnx4_inode_info qnx4_i; struct reiserfs_inode_info reiserfs_i; struct bfs_inode_info bfs_i; struct udf_inode_info udf_i; struct ncp_inode_info ncpfs_i; struct proc_inode_info proc_i; struct socket socket_i; struct usbdev_inode_info usbdev_i; struct jffs2_inode_info jffs2_i; void *generic_ip; } u; }; 以下我们分别来说明这些域的意义。 1) 链表管理域 inode 结构前三个域就是用来帮助将 inode 串起来的域,分别是: struct list_head i_hash; struct list_head i_list; struct list_head i_dentry; 这跟我们在 super block 那里所看到的 s_list 是属于同样的类型,都是 struct list_head。list_head 这种结构在 Kernel 里实在用的很多,事实上,它也的确很好用。现在我们只要知道 list_head 可以 帮我们将一些结构链表串在一起就够了。在 VFS 里,有四个链表是用来管理 inode 的: ¾ inode_unused:用来将目前还没使用的 inode 串在一起,它就是使用 i_list 这个域; ¾ inode_in_use:用来将目前正在使用的 inode 串在一起,当一个 inode 被使用时,它会从 inode_unused 中被取出来,因此,此时 i_list 不会被用到,接着它会利用 i_list 域放到 inode_in_use 中; ¾ sb->s_dirty:用来将 dirty inode 链表在一起。这个链表的开头位于 super block 的 s_dirty 域,一样也是使用 i_list 串接; ¾ 所有正在使用中的 inode 都可以经由 inode_in_use 链表找到,但是因为系统的 inode 太多, 所以链表可能会很长,如果慢慢找,在速度上并不理想。因此,每个使用中的 inode 都会 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 245 - 计算出其 hash value,并且放到 hash table,但是 hash table 有时会有 collision 的情形出 现,因此每一个 entry 是由一个 list 串接起来,这个 list 就是利用 i_hash 域来串接的。至于 i_dentry 是在 dcache 中使用的,dcache 利用这个域将 inode 串接起来。 2) 基本资料 inode 的基本资料很多,这里很简略地介绍一下: unsigned long i_ino; 每一个 inode 都有一个序号,经由 super block 结构和其序号,我们可以很轻易的找到这个 inode。 unsigned int i_count; 在 Kernel 里,很多的结构都会记录其 reference count,以确保如果某个结构正在使用,它不会 被不小心释放掉,i_count 就是其 reference count。 kdev_t i_dev; // inode所在的device代码 umode_t i_mode; // inode的许可权 nlink_t i_nlink; // hard link的个数 uid_t i_uid; // inode拥有者的id gid_t i_gid; // inode所属的群组id kdev_t i_rdev; // 如果inode代表的是device的话,那此域将记录device的代码 off_t i_size; // inode所代表的文件大小 time_t i_atime; // inode最近一次的调用时间 time_t i_mtime; // inode最近一次的修改时间 time_t i_ctime; // inode的产生时间 unsigned long i_blksize; // inode在做IO时的区块大小 unsigned long i_blocks; // inode所使用的block数,一个block为 512 byte unsigned long i_version; // 版本号码 unsigned long i_nrpages; // inode所使用的page个数 struct page *i_pages; // inode使用的page会被放在链表里,这个域记录着此链表的开头 struct super_block *i_sb; // inode所属文件系统的super block unsigned long i_state; // inode目前的状态,可以是I_DIRTY,I_LOCK和 I_FREEING的OR组合 unsigned int i_flags; // 记录此inode的参数 unsigned char i_pipe; // 用来记录此inode是否为pipe unsigned char i_sock; // 用来记录此inode是否为socket unsigned int i_attr_flags; // 用来记录此inode的属性参数 struct file_lock *i_flock; // 用来做file lock 3) 结构体对映 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 246 - 在 Linux 里,我们可以利用 mmap()将文件或 device 的某个区块对应到结构体里使用。在 inode 里这两个域就是跟它有关的: struct vm_area_struct *i_mmap; int i_writecount; i_writecount 这个域的值是用来记录目前有多少个进程是可以用写入的模式来开启此文件的。为 什么需要这个值呢? 因为系统没办法支持即可以对一个文件写入,而又同时将这个文件对应为 MAP_DENYWRITE 的模式。所以,用这个域来代表目前有多个进程可对此 inode 做写入的动作或是 有多少个进程将它映像成 MAP_DENYWRITE 的模式。它的值有以下三种情形: ¾ 0: 没有进程将它开启为可写入,也没有进程对它做 MAP_DENYWRITE 的映像; ¾ < 0: 有-i_writecount 个进程对它做 MAP_DENYWRITE 的映像; ¾ > 0: 有 i_writecount 个进程将它开启为可写入模式。 至于 i_mmap 这个域就是用来做结构体映像的域。 4) inode 同步 就跟 super_block 结构一样,Kernel 里的重要结构在修改时,都必须做好同步的动作,以免产 生竞争条件,造成系统出错。因此,当我们要修改某个 inode 结构时,必须先确定没有人在使用这 个 inode 才行。这件事是使用信号量和等待队列来完成的。 struct wait_queue *i_wait; struct semaphore i_sem; 除了这两个域之外,新版的 Kernel 又多加了一个域叫 i_atomic_write,这也是一个信号量,那 它的用途又是什么呢? 相信如果你用过管道的话,一定知道当我们写资料到管道里的时候,资料长 度必须小于等于 PIPE_BUF 这个值,所以当写入的资料小于等于 PIPE_BUF 时,Kernel 要确保写入 的动作是原子的,因此加了这个域来做控制。 struct semaphore i_atomic_write; 5) Quota 相关域 在前面讲 super_block 时,我们说过里面有个域 dq_op 是用来存放 quota 函数用的。因为在 Linux 里,quota 的管理可分为两种,一是所使用的块数限制,另一种则是使用的 inode 数目的限制。所以, 将 quota 管理的资料放在 inode 是很适合的。至于将 quota 函数放在 super block 里则是因为同一个 文件系统会使用相同的 quota 管理方式,而刚好从任一个 inode 都可以经由 i_sb 取得其 super_block 结构,所以,这也就是为什么 quota 函数要放在 super block 里。 struct dquot *i_dquot[MAXQUOTAS]; 目前的 quota 管理还可以分为 user quota 管理和 group quota 管理,所以,其实 MAXQUOTAS 这个常数的值是 2。在 i_dquot 里,一个是用来管理 user quota,另一个则是管理 group quota。 6) 操作 inode 的函数 就跟 super_block 结构一样,每一个 inode 都有一个 i_op 的域用来记录一组操做 inode 的函数。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 247 - struct inode_operations *i_op; 接下来,我们就来看看 inode_operations 结构里各个函数是做什么用的: ¾ create(dir, dentry, mode) ; 当我们要产生一个新的文件时,Kernel 必须要先为这个文件产生一个 inode,当然,配置 inode 结构体这种事是属于 VFS 的工作范围,但是产生一个 inode 这件事跟文件系统本身有很大的关系。 因此,VFS 会调用文件系统里 i_op->create()来做些额外的事,那 i_op 这个域是打那儿来的呢? 因 为一个文件一定是位于某个目录底下,所以,i_op 这个域就是从文件所在目录的 inode 里取出来的。 而传给 create()的 dir 就是那个目录的 dentry 指针,dentry 则是我们要产生的文件的 dentry(此时 dentry 已经配置好,但内部的资料却还没填入),而 mode 则是产生文件时所给的模式。我们知道 Linux 里有很多种的 inode,有的是代表普通文件,有的则是代表目录,还有代表 socket,pipe 的。 不同种类的 inode 其 i_op 所提供的函数都不尽相同,像一个普通的文件,我们根本不可能去调用它 的 create()函数,因为它不是目录,它没办法在目录底下产生一个 inode。而像代表目录的 inode 就 必须要提供 create()才行,不然没办法在其底下产生子目录或文件。 ¾ lookup(dir, dentry); 这个函数也是代表目录的 inode 所应该提供的。比方说我们有一个文件叫/usr/tmp/hello.txt, 如果我们想读取这个文件的内容,第一步就是要开启这个文件,如果要开启这个文件,我们首先就 得先找到这个文件的 inode。那 Kernel 是怎么找到它的 inode 的呢? 它会调用根目录的 inode->i_op->lookup()找到/usr的dentry,然后调用/usr目录的inode-> i_op_lookup()找到/usr/tmp 的 dentry,接着再调用/usr/tmp 的 inode->i_op->lookup()找到/usr/tmp/hello.txt 的 dentry。而从 它的 dentry 我们自然可以取得它的 inode。而 lookup()的用处就是从 dir 目录底下找到名称跟 dentry 指定的相同的文件 dentry,基本上,这是属于文件系统应该做的事,VFS 只负责帮你配置好 dentry 结构,并填入要找的文件名称。 ¾ link(old_dentry, dir, dentry) ; 在 Linux 里,除了 symbolic link 之外,还有一种叫 hard link 的东西,symbolic link 有它自己的 inode,只是其内容指到别的文件的路径而已,但是 hard link 却是跟指到的文件共享一个 inode。但 是,hard link 只能跟指到的文件位于同一个文件系统而已。当被指到的文件被删除时,只是你看不 到那个文件而已,事实上,文件仍然是存在的,你可以使用之前建立的 hard link 来读取它。系统提 供一个叫 ln 的命令可以产生 hard link,有兴趣的朋友可以试试看。而就 programmer 来讲,系统也 提供了一个叫 link()的系统调用来做 hard link。link()在准备好一切之后,会调用 i_op->link()去处理 文件系统方面要做的事,i_op->link()至少应该要将 inode->i_nlink 的值加一才行。在 i_op->link() 的参数里,old_dentry 是指被指到文件的 dentry,dir 是指我们所要产生的 link 所在目录的 dentry, 至于 dentry 则是要产生的 link 的 dentry。这个函数是代表目录的 inode 所应该提供的。 ¾ unlink(dir, dentry) ; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 248 - 相信很多人都用过 unlink()这个系统调用,这是用来将 dir 指到的目录底下的 dentry 文件删除掉。 在真正删除之前,它会去检查 dentry->d_inode->i_nlink 是否归 0,只有在 nlink 的值是 0 时才会删 除。unlink()系统调用最后会调用 i_op->unlink()去做文件系统额外要做的事,它至少应该把 dentry->d_inode->i_nlink 的值减一才对。跟 i_op->link()一样,i_op->unlink()也是目录型的 inode 所应提供的。 ¾ symlink(dir, dentry, symname) ; 这个函数故名思义就是用来产生 symbolic link 用的。dir 是 symbolic link 所在的目录 dentry, symname 则是 symbolic link 的内容,通常是个路径名称,至于 dentry 则是 symbolic link 本身的 dentry。系统提供了一个 symlink()的系统调用,就是用来做 symbolic link 的,它最后也是调用 i_op->symlink()来处理。当然,每个文件系统内部要如何产生 symbolic link 的方式不尽相同。以 ext2 来讲,如果 symname 的长度小于 60 个 byte 的话,那在对 ext2 而言,这是一个 fast symbolic link, 因为路径名称就直接存在 inode 结构里,不用另外读取 disk。所以,当 i_op->symblink()被调用时, 它的工作就是将路径名称加到 inode 里。但是如果大于等于 60 个 byte,那就称为 slow symbolic link, symname 的内容会被放到 disk 上的 block 里,此时,i_op->symlink()就需要配置一个 block 存放 symname 的内容。这个函数也是目录型别的 inode 所应提供的,当然,如果你不想提供的话,也可 以直接设成 NULL。 ¾ mkdir(dir, dentry, mode) ; 这个函数就是在产生一个目录时用,系统有提供 mkdir()系统调用来产生目录,而这个系统调用 最后会调用 i_op->mkdir()来做底层的事情。dir 是指我们要产生的目录所在的目录,至于 dentry 则 是要产生的目录 dentry,mode 则是目录的许可权。之前我们曾说过,每个 inode->i_nlink 记录了 hard link 的个数,而事实上,在代表目录的 inode 里,i_nlink 的意义则跟它很像,它的意思是指目 录里有几个文件或子目录,所以,每个目录刚产生时,它的 i_nlink 的都是 2,因为,每个目录至少 有二个子目录,分别是“.”和“..”。当产生完子目录之后,dir->d_inode->i_nlink 的值也应该加 1 才对。如果 inode 是目录的话,那它应该提供这个函数才对。 ¾ rmdir(dir, dentry) ; i_op->rmdir()所做的事是跟 mkdir()是相反的。跟 mkdir()一样,i_op->rmdir()最后也会被 rmdir() 系统调用所使用。当 VFS 要调用 rmdir()之前,它会先替我们把要删除的目录名称 dentry 找到,并 把其父目录的 dentry 也找到,其中 dir 就是其父目录 dentry,dentry 就是指要删除的目录的 dentry。 当然,在调用 i_op->rmdir()去删除目录时,VFS 会先调用 permission()并检查我们是否可以删除此 目录并检查目录此时的状态,比方像目录是否现在被 mount,是否为系统根目录,以及使用者要删 除的是否为目录等等。所以,i_op->rmdir()要做的事就是纯粹检查目录是否为空的,是否目前还有 别人在使用它,并做好删除目录的事情。如果 inode 是目录的话,那它应该提供这个函数。 ¾ mknod(dir, dentry, mode, rdev) ; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 249 - 在 Linux 里,也有一个命令是叫 mknod。mknod 命令主要是用来产生的 special file,像是 character device,block device,或 fifo,socket 之类的东西。同时,系统里也有一个系统调用 mknod(), 这个 mknod()系统调用不尽可以产生特殊文件,也可以产生一般的文件,详情可见其 man page。而 事实上,mknod()系统调用最后也是调用文件系统的 mknod()函数。mknod()系统调用会先检查 user 给的参数是否对,如果你指定要产生一个目录,VFS 就会拒绝你,除此之外,VFS 还会先替你产生 一个空的 dentry 用来放要产生的文件,当然,它也会检查 user 是否有权力产生这个文件,最后, 它会把重头戏都交给 i_op->mknod()去做。而 i_op->mknod()要做什么呢? 当然,这部分是跟各个 文件系统内部有关,基本上,这个函数需要在 dir 这个目录底下产生一个 inode,其模式为 mode, 如果 mknod()要产生的文件是 device 的话,那 rdev 就这个 device 的 major number 与 minor number 组合。除此之外,最重要的一件事就是要根据 mode,在 inode->i_op 填入适当的值,比方说,如果 产生一个 character device,那 inode->i->op 应该指定一组操作 character device 的函数,如果产生 的是普通文件的话,那 inode->i_op 也应填入操作普通文件 inode 的函数。这个函数对代表 inode 的目录而言,也是应该要提供的。 ¾ rename(old_dir, old_dentry, new_dir, new_dentry) ; 这个函数是用来将位于 old_dir 里的 old_dentry 文件改名为 new_dir 里的 new_dentry 文件名称。 文件系统所提供的 rename()要做的事就是根据系统的 implementation 把改名字的事情做好,其它 像许可权的检查在上层 VFS 会帮我们做好。要注意的是,old_dir->d_inode->i_nlink 的值应该减一, 而 new_dir->d_inode->i_nlink 的值则是应该加一。这个函数在 Kernel 里只有被系统调用 rename() 调用而已。有的人可能会以为这个函数应该由代表文件的 inode 提供,但事实上,这个函数必须要 由代表目录的 inode 提供。理由就留给各位去想了。 ¾ readlink(dentry, buffer, buflen) ; 只有当 inode 是代表一个 symbolic link 时,才需要提供这个函数,其它诸如文件或目录是不用 提供这个函数的。这个函数的用处在于读取 symbolic link 的内容,也就是读取 symbolic link 指到的 文件路径。跟上面其它的函数一样,这个函数最后也是会被系统调用 readlink()所调用。至于 readlink() 要如何做是跟文件系统的 implementation 有关。像 ext2,当文件路径的长度小于 60 个时,会直接 从 inode 里读出资料,如果不是,则会读取 disk 上记录路径的 block 内容。dentry 是代表 symbolic link 的 inode,buffer 是要路径存放的位置,至于 buflen 则是 buffer 的长度。 ¾ follow_link(dentry, base, follow) ; 跟前一个函数一样,follow_link()这个函数只有 symbolic link 的 inode 需要提供。我们知道,当 我们读到一个 symbolic link 叫 a 时,如果 a 指到/usr/hello.txt 的话,那当我们读 a 时,事实上会读 到/usr/hello.txt。这部分的工作就是由 follow_link()完成的。这部分的转换就使用者的观点来看是不 会感觉到的。在 Linux 里,并没有一个系统调用会调用 follow_link()的。这个函数事实上是由 lookup_dentry()调用 do_follow_link(),再由 do_follow_link()调用 i_op-> follow_link()。在 Kernel 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 250 - 里,寻找某个文件的 inode 是由 namei(),再由它调用 lookup_dentry()完成的,lookup_dentry()会 由目录的最上层一层一层的找,如果找到的文件是 symbolic link 时,它最后会调用 symbolic link 的 follow_link(),而 follow_link()应该要读取所指到的文件路径,并且再调用 lookup_dentry()去找这个 文件,找到之后,再把它的 dentry 传回去。 ¾ readpage(file, page) ; 在 Linux 里,每一个 inode 都代表一个文件或目录,而每一个文件在系统中则是由一个 file 结构 所记录,readpage()就是将此 file 里的 page 内容读进来。基本上,VFS 已经提供了一个 readpage() 的函数叫 generic_readpage(),可以直接使用这个函数。 ¾ writepage(file, page) ; 这个函数则是跟 readpage()相反,是将 page 中的内容写回 file 里。但是,在 VFS 里并没有提 供这样的一个函数可供使用,所以,如果有需要的话,需要自己提供。 ¾ bmap(inode, block) ; bmap()主要是用在做结构体映像时用的。block 是一个数字,它代表的是 inode 所代表的文件逻 辑上的第几个 block,bmap()负责将这个 block 的序号转换成 disk 上的区块序号。 ¾ truncate(inode) ; truncate()的作用就是用来将 inode 所代表的文件长度减小或增加,当然,详细的 implementation 是要依照系统而有所不同。至于最后的长度应该是多少,则是由 VFS 在调用 i_op->truncate()之前 将想要改变的长度填在 inode->i_size 里。 ¾ permission(inode, mask) ; 故名思义,这个函数用来检查 inode 的许可权,一般来讲,i_op->permission()在 Kernel 里并不 会被直接调用,VFS 提供一个也叫 permission()函数,这个函数会去调用 i_op->permission()。一般 系统里如果要检查许可权都是直接调用 VFS 提供的 permission(),再由 VFS 的 permission()去调用 i_op->permission()。如果文件系统有提供 i_op->permission ()时,那就以 i_op->permission()的结 果为准。如果没有,就依照 VFS 的标准来做。mask 的可以是 MAY_READ,MAY_WRITE,和 MAY_EXEC 这三个值的 OR 组合。 ¾ smap(inode, sector) ; smap()的作用跟 bmap()很像,但是,sector 在这里指到是 disk 上的 sector number。而不是逻 辑上的 block number。大部分的文件系统都没有提供这个函数,除了在 umsdos 有提供之外。 ¾ updatepage(file, page, offset) ; 这个函数目前只有 NFS 文件系统有提供。有兴趣的朋友可以参考 NFS 的源代码。 ¾ revalidate(dentry) . 由于 NFS 有 cache 的问题,所以,这个函数主要也是在 NFS 中所使用的,为的是将 dentry->i_node 的内容做重置。有一个函数叫 do_revalidate()就会调用这个函数,很多系统调用像 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 251 - stat 等都会调用 do_revalidate()对 inode 做重置。 如果我们去看 inode_operations 结构的内容,就可以发现第一个域是 default_file_ops,其实这 也是一组函数。在 Linux 里,每一个文件都会有一个 file 结构来描述,而每一个 file 结构都会定义一 组函数来操作 file 结构,在 inode 里,也同时记录了用来操作 inode 所代表的文件的函数。在开始讲 操作 file 结构的函数之前,让我们来看看 file 结构的内容。 5. 1. 7 file 结构 在 Linux 里,每一个文件都有一个 file 结构和 inode 结构,inode 结构是用来让 Kernel 做管理的, 而 file 结构则是我们平常对文件读写或开启,关闭所使用的。当然,从用户的观点来看是看不出什 么的。在 Linux 里,文件的观念应用的很广泛的,甚至是写一个 driver 你也只要提供一组的 file operations 就可以完成了。我们现在来看看 file 结构的内容。 struct file { struct file *f_next,**f_pprev; struct dentry *f_dentry; struct file_operations *f_op; mode_t f_mode; loff_t f_pos; unsigned int f_count,f_flags; unsigned long f_reada,f_ramax,f_raend,f_ralen,f_rawin; struct fown_struct f_owner; unsigned long f_version; void *private_data; }; 比起 inode 结构,file 结构就显得小多了,file 结构也是用链表来管理,f_next 会指到下一个 file 结构,而 f_pprev 则是会指到上一个 file 结构地址的地址,不过,这个域的用法跟一般指到前一个 file 结构的用法不太一样,f_dentry 会记录其 inode 的 dentry 地址,f_mode 为文件调用的种类,f_pos 则是目前文件的 offset,每次读写都从 offset 记录的位置开始读写,f_count 是此 file 结构的 reference cout,f_flags 则是开启此文件的模式,f_reada,f_ramax,f_raend,f_ralen,f_rawin 则是控制 read ahead 的参数,f_owner 记录了要接收 SIGIO 和 SIGURG 的进程 ID 或进程群组 ID,private_data 则是 tty driver 所使用的域。最后,我们来看看 f_op 这个域。这个域记录了一组的函数是专门用来 使用文件的。 ¾ llseek(file, offset, where); 我们写程序会调用 llseek()系统调用设定从文件那个位置开始读写。这个函数你可以不用提供, 因为系统已经有一个写好的,但是系统提供的 llseek()没有办法让你将 where 设为 SEEK_END,因 为 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 252 - 系统不知道你的文件长度是多少,所以没办法提供这样的服务。如果你不提供 llseek()的话,那系统 会直接使用它已经有的 llseek()。llseek()必须要将 file->offset 的值更新。 ¾ read(file, buf, buflen, poffset); 当我们读取一个文件时,最终就是会调用 read()这个函数来读取文件内容。这些参数 VFS 会替 我们准备好,至于 poffset 则是 offset 的指标,这是要告诉 read()从那里开始读,读完之后必须更新 poffset 的内容。请注意,在这里 buf 是一个地址,而且是一个位于 user space 的地址。 ¾ write(file, buf, buflen, poffset); write()的动作跟 read()是相反的,参数也都一样,buf 依然是位于 user space 的地址。 ¾ readdir(file, dirent, filldir); 这是用来读取目录的下一个 direntry 的,其中 file 是 file 结构地址;dirent 则是一个 readdir_callback 结构,这个结构里包含了使用者调用 readdir()系统调用时所传过去的 dirent 结构地 址;filldir 则是一个函数指标,这个函数在 VFS 已经有提供了,这个函数其实是增加了 kernel 在读 取 dirent 方面的弹性。当文件系统的 readdir()被调用时,在它把下一个 dirent 取出来之后,应该要 调用 filldir(),让它把所需的资料写到 user space 的 dirent 结构里,也许还会多做些处理。有兴趣的 朋友可以参考的 filldir()函数。 ¾ poll(file, poll_table); 之前的 Kernel 版本本来是在 file_operations 结构里有 select()函数而不是 poll()函数的。但是, 这并不代表 Linux 不提供 select()系统调用,相反的,Linux 仍然提供 select()系统调用,只不过 select() 系统调用 implement 的方式是使用 poll()函数来做的。 ¾ ioctl(inode, file, cmd, arg); ioctl()这个函数其实有很大的用途,尤其它可以做为 user space 的程序对 Kernel 的一个沟通管 道。那 ioctl()是什么时候被调用呢? 还记得平常写程序时偶而会用到 ioctl()系统调用来直接控制文件 或 device 吗? 是的,ioctl()系统调用最后就是把命令交给文件的 f_op->ioctl()来执行。f_op->ioctl() 要做的事很简单,只要根据 cmd 的值,做出适当的行为,并传回值即可。但是,ioctl()系统调用其 实是分几个步骤的:第一,系统有几个内定的 command 它自己可以处理,在这种情形下,它不会 调用 f_op->ioctl()来处理。如果 user 指定的 command 是以下的一种,那 VFS 会自己处理。 9 FIONCLEX:清除文件的 close-on-exec 位; 9 FIOCLEX:设定文件的 close-on-exec 位; 9 FIONBIO:如果 arg 传过来的值为 0 的话,就将文件的 O_NONBLOCK 属性去掉,但 是如果不等于 0 的话,就将 O_NONBLOCK 属性设起来; 9 FIOASYNC:如果 arg 传过来的值为 0 的话,就将文件的 O_SYNC 属性去掉,但是如 果不等于 0 的话,就将 O_SYNC 属性设起来。只是在 Kernel 2.2.1 时,这个属性的功 能还没完成。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 253 - 如果 cmd 的值不是以上数种,而且如果 file 所代表的不是普通的文件的话,而是像 device 之类 的特殊文件,VFS 会直接调用 f_op->ioctl()去处理。但是如果 file 代表普通文件的话,那 VFS 会调 用 file_ioctl()做另外的处理。何谓另外的处理呢? file_ioctl()会再取一次 cmd 的值,如果是以下数种, 它会先做些处理,然后再调用 f_op->ioctl(),不管怎么样,file_ioctl()最后都会再调用 f_op->ioctl() 去处理。 9 FIBMAP:先将 arg 指到的文件 block number 取出来,并调用 f_op->bmap()计算出 其 disk 上的 block number,最后再将计算出来的 block number 放到 arg 参数里; 9 FIGETBSZ:取得文件系统 block 的大小并放入 arg 的参数里; 9 FIONREAD:将文件剩下尚未读取的长度写到 arg 里。比方说文件大小是 1000,而 f_op->offset 的值是 300,表示还有 700 个 byte 尚未读取,所以,将 700 写到 arg 参 数里。 ¾ mmap(file, vmarea); 这个函数是用来将文件的部分内容映像到结构体中,file 是指要被映像的文件,而 vmarea 则是 用来描述映像到结构体的那里。 ¾ open(inode, file); 当我们调用 open()系统调用来开启文件时,open()会把所有的事都做好,最后则会调用 f_op->open()看文件系统是否要做些什么事。一般来讲,VFS 已经把事做好了,所以很多系统事实 上根本不提供这个函数。当然,你要提供也可以,比方说,你可以在这个函数里计算这个文件系统 的文件被使用过多少次等。 ¾ flush(file); 这个函数也是新增加的,这个函数是在我们调用 close()系统调用来关闭文件时所调用的。只要 你调用 close()系统调用,那 close()就会调用 flush(),不管那个时候 f_count 的值是否为 0。事实上, 在 ext2 里也没有提供这么一个函数,也许是在关闭文件之前,VFS 允许文件系统先做些 backup 的 动作吧。 ¾ release(inode, file); 这个函数也是在 close()系统调用中使用的,当然,不尽在 close()中使用,在别的地方也是有使 用到。基本上,这个函数的定位跟 open()很像,不过,当我们对一个文件调用 close()时,只有当 f_count 的值归 0 时,VFS 才会调用这个函数做处理。一般来讲,如果你在 open()时配置了一些东西,那应 该在 release()时将配置的东西释放掉。至于 f_count 的值则是不用在 open()和 release()中控制,VFS 已经在 fget()和 fput()中增减 f_count 了。 ¾ fsync(file, dentry); fsync()这个函数主要是由 buffer cache 所使用,它是用来把 file 这个文件的资料写到 disk 上。 事实上,Linux 里有两个系统调用,fsync()和 fdatasync(),都是调用 f_op->fsync()。它们几乎是一 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 254 - 模一样的,差别在于 fsync()调用 f_op->fsync()之前会使用 semaphore 将 f_op->fsync()设成 critical section,而 fdatasync()则是直接调用 f_op->fsync()而不设 semaphore。 ¾ fasync(fd, file, on); 当我们调用 fcntl()系统调用,并使用 F_SETFL 命令来设定文件的参数时,VFS 就会调用 fasync() 这个函数,而当读写文件的动作完成时,进程会收到 SIGIO 的讯息。 ¾ check_media_change(dev); 这个函数只对可以使用可移动的 disk 的 block device 有效而已,像 MO,CDROM,floopy disk 等等。为什么对这些可以把 disk 随时抽取的需要提供这么一个函数呢? 其实,从字面上我们大概可 以知道,这是用来检查 disk 是否换过了。以 CDROM 为例,每一个光盘片都代表一个文件系统,如 果今天我们把光盘片换掉了,那表示这个文件系统不存在了,如果 user 此时去读取这个文件系统的 资料,那会发生什么事? 很有可能系统就这么崩溃了。所以,对于这种的 device,每当在 mount 时, 我们就必须检查其中的 disk 是否换过了,如何检查呢? 当然只有文件系统本身才知道,所以,文件 系统必须提供此函数。 ¾ revalidate(dev); 这个函数跟上面的 check_media_change()有着相当大的关系。当 user 执行 mount 要挂上一个 文件系统时,mount 会先调用 check_disk_change(),如果文件系统所属的 device 提供了这个函数 的话,那 check_disk_change()会先调用 f_op-> check_media_change()来检查是否其中的 disk 有换 过,如果有则调用 invalidate_inodes()和 invalidate_buffers(),将跟原本 disk 有关的 buffer 或 inode 都设为无效,如果文件系统所属的 device 还有提供 revalidate()的话,那就再调用 revalidate()将此 device 的资料记录好。 ¾ lock(file, cmd, file_lock). 这个函数也是新增加的,在 Linux 里,我们可在一个文件调用 fcntl()时对它使用 lock。如果调 用 fcntl()时,cmd 的参数我们给 F_GETLK,F_SETLK,或 F_SETLKW,那么系统会间接调用 f_op->lock 来做事。当然,如果你的文件系统不想提供 lock 的功能的话,你可以不用提供这个函数。 5. 2 字符设备驱动 字符设备驱动是嵌入式 Linux 最基本、也是最常用的驱动程序。它的功能非常强大,几乎可以 描述不涉及挂载文件系统的所有设备。图 5-3-1 所示为驱动程序在应用程序和硬件设备之间接口功 能的流程图。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 255 - 应用程序 调用标准的read()、write()、ioctl()、 open()、close()函数 File_operations 驱动程序中对应硬件设备的实际的 device_read()、device_write()、 device_ioctl()、device_open()、 device_close()函数 硬件设备 图 5-3-1 驱动程序的接口流程图 5. 2. 1 scull 的设计和内存使用 编写驱动的第一步是定义驱动将要提供给用户程序的能力(机制)。因为我们的“设备”是计 算机内存的一部分,我们可自由做我们想做的事情。它可以是一个顺序的或者随机存取的设备,一 个或多个设备,等等。 为使 scull 作为一个模板来编写真实设备的真实驱动,我们将展示给你如何在计算机内存上实现 几个设备抽象,每个有不同的特性。 scull 源码实现下面的设备,模块实现的每种设备都被引用做一种类型。 ¾ scull0 到 scull3 这 4 个设备,每个由一个全局永久的内存区组成。全局意味着如果设备被 多次打开,设备中含有的数据由所有打开它的文件描述符共享。永久意味着如果设备关闭 又重新打开,数据不会丢失。这个设备用起来有意思,因为它可以用惯常的命令来存取和 测试,例如 cp,cat,以及 I/O 重定向。 ¾ scullpipe0 到 scullpipe3 这 4 个 FIFO(先入先出)设备,它们的行为像管道。一个进程读 的内容来自另一个进程所写的。如果多个进程同时读同一个设备,它们竞争数据。scullpipe 的内部将展示阻塞读写和非阻塞读写如何实现,而不必采取中断。尽管真实的驱动使用硬 件中断来同步它们的设备,阻塞和非阻塞操作的主题是重要的并且与中断处理是分开的。 ¾ scullsingle、scullpriv、sculluid、scullwuid 这些设备与 scull0 相似,但是在什么时候允许打 开上有一些限制。第一个(snullsingle)只允许一次一个进程使用驱动,而 scullpriv 对每个 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 256 - 虚拟终端(或者 X 终端会话)是私有的,因为每个控制台/终端上的进程有不同的内存区。 sculluid 和 scullwuid 可以多次打开,但是一次只能是一个用户;前者返回一个“设备忙” 错误,如果另一个用户锁着设备,而后者实现阻塞打开。这些 scull 的变体可能看来混淆了 策略和机制,但是它们值得看看,因为一些实际设备需要这类管理。 scull 使用的内存区,也称为一个设备,长度可变。你写的越多,它增长越多;通过使用一个短 文件覆盖设备来进行修整。 scull 驱动引入 2 个核心函数来管理 Linux 内核中的内存。这些函数,定义在中: void *kmalloc(size_t size,int flags); void kfree(void *ptr); 对 kmalloc 的调用试图分配 size 字节的内存,返回值是指向那个内存的指针或者如果分配失败 为 NULL。flags 参数用来描述内存应当如何分配。对于现在,我们一直使用 GFP_KERNEL。分配的 内存应当用 kfree 来释放。你应当从不传递任何不是从 kmalloc 获得的东西给 kfree。但是,传递一 个 NULL 指针给 kfree 是合法的。 kmalloc 不是最有效的分配大内存区的方法,所以挑选给 scull 的实现不是一个特别巧妙的。一 个巧妙的源码实现可能更难阅读,而本节的目标是展示读和写,不是内存管理。这是为什么代码只 是使用 kmalloc 和 kfree 而不依靠整页的分配,尽管这个方法会更有效。 在 flip 一边,我们不想限制“设备”区的大小,是由于理论上的和实践上的理由。理论上,给 在被管理的数据项施加武断的限制总是个坏想法。实践上,scull 可用来暂时地吃光你系统中的内存, 以便运行在低内存条件下的测试。运行这样的测试可能会帮助你理解系统的内部。你可以使用命令 cp /dev/zero/dev/scull0 来用 scull 吃掉所有的真实 RAM,并且你可以使用 dd 工具来选择拷贝多少 数据给 scull 设备。 在 scull,每个设备是一个指针链表,每个都指向一个 scull_dev 结构。每个这样的结构,缺省 的时候指向最多 4 兆字节,通过一个中间指针数组来进行。发行代码使用一个 1000 个指针的数组 指向每个 4000 字节的区域。我们称每个内存区域为一个量子,数组(或者它的长度)为一个量子集。 一个 scull 设备和它的内存区如图一个 scull 设备的布局所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 257 - 图 5-3-2 一个 scull 设备的布局 选定的数字是这样,在 scull 中写一个字节消耗 8000 或 12,000KB 内存:4000 是量子,4000 或者 8000 是量子集(根据指针在目标平台上是用 32 位还是 64 位表示)。相反,如果你写入大量数 据,链表的开销不是太坏。每 4MB 数据只有一个链表元素,设备的最大尺寸受限于计算机的内存大 小。 为量子和量子集选择合适的值是一个策略问题,而不是机制,并且优化的值依赖于设备如何使 用。因此,scull 驱动不应当强制给量子和量子集使用任何特别的值。在 scull 中,用户可以掌管改变 这些值,有几个途径:编译时间通过改变 scull.h 中的宏 SCULL_QUANTUM 和 SCULL_QSET,在模 块加载时设定整数值 scull_quantum 和 scull_qset,或者使用 ioctl 在运行时改变当前值和缺省值。 使用宏定义和一个整数值来进行编译时和加载时的配置,是对于如何选择主编号的回忆。我们 在驱动中任何与策略相关或专断的值上运用这个技术。 余下的唯一问题是如果选择缺省值。在这个特殊情况下,问题是找到最好的平衡,由填充了一 半的量子和量子集导致内存浪费,如果量子和量子集小的情况下分配释放和指针连接引起开销。另 外,kmalloc 的内部设计应当考虑进去。缺省值的选择来自假设测试时可能有大量数据写进 scull, 尽管设备的正常使用最可能只传送几 KB 数据。 我们已经见过内部代表我们设备的 scull_dev 结构。结构体的 quantum 和 qset 分别代表设备的 量子和量子集大小的实际数据,但是,是一个不同的结构跟踪,我们称为 struct scull_qset: struct scull_qset { void **data; struct scull_qset *next; }; 下一个代码片段展示了实际中 struct scull_dev 和 struct scull_qset 是如何被用来持有数据的。 sucll_trim 函数负责释放整个数据区,由 scull_open 在文件为写而打开时调用。它简单地遍历列表 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 258 - 并且释放它发现的任何量子和量子集。 int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; // dev非空 int i; for (dptr = dev->data; dptr; dptr = next) { // 所有的列表项 if (dptr->data) { for (i = 0; i < qset; i++) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; } next = dptr->next; kfree(dptr); } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } scull_trim 也用在模块清理函数中,用来归还 scull 使用的内存给系统。 5. 2. 2 字符设备注册 内核在内部使用类型 struct cdev 的结构来代表字符设备。在内核调用你的设备操作前,你编 写分配并注册一个或几个这些结构。为此,你的代码应当包含,这个结构和它的关 联帮助函数定义在这里。 有 2 种方法可用来分配和初始化这些结构。如果你想在运行时获得一个独立的 cdev 结构,你可 以使用这样的代码: struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops; 但是,偶尔你会想将 cdev 结构嵌入一个你自己的设备特定的结构,scull 这样做了。在这种情 况下,你应当初始化你已经分配的结构,使用: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 259 - void cdev_init(struct cdev *cdev,struct file_operations *fops); 任一方法,有一个其他的 struct cdev 成员你需要初始化。像 file_operations 结构,struct cdev 有一个拥有者成员,应当设置为 THIS_MODULE。一旦 cdev 结构建立,最后的步骤是把它告诉内 核,调用: int cdev_add(struct cdev *dev,dev_t num,unsigned int count); 这里,dev 是 cdev 结构,num 是这个设备响应的第一个设备号,count 是应当关联到设备的 设备号的数目。常常 count 是 1,但是有多个设备号对应于一个特定的设备的情形。例如,设想 SCSI 磁带驱动,它允许用户空间来选择操作模式(例如密度),通过安排多个次编号给每一个物理设备。 在使用 cdev_add 是有几个重要事情要记住。第一是这个调用可能失败。如果它返回一个负的 错误码,说明你的设备没有增加到系统中。第二是它几乎会一直成功,但是带起了其他的点: cdev_add 一返回,你的设备就是“活的”并且内核可以调用它的操作。除非你的驱动完全准备好 处理设备上的操作,你不应当调用 cdev_add。 从系统去除一个字符设备,调用: void cdev_del(struct cdev *dev); 显然,你不应当在传递给 cdev_del 后存取 cdev 结构。 1)scull 中的设备注册 在内部,scull 使用一个 struct scull_dev 类型的结构表示每个设备。这个结构定义为: struct scull_dev { struct scull_qset *data; // 指向第一个量子 int quantum; // 当前量子大小 int qset; // 当前数组大小 unsigned long size; // 存放的数据大小 unsigned int access_key; // 供sculluid和scullpriv使用 struct semaphore sem; // 互斥信号量 struct cdev cdev; // 字符设备结构 }; 我们在遇到它们时讨论结构中的各个成员,但是现在,我们关注于 cdev,我们的设备与内核接 口的 struct cdev。这个结构必须初始化并且如上所述添加到系统中,处理这个任务的 scull 代码是: static void scull_setup_cdev(struct scull_dev *dev,int index) { int err,devno = MKDEV(scull_major,scull_minor + index); cdev_init(&dev->cdev,&scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 260 - err = cdev_add (&dev->cdev,devno,1); // 出错处理 if (err) printk(KERN_NOTICE "Error %d adding scull%d",err,index); } 因为 cdev 结构嵌在 struct scull_dev 里面,cdev_init 必须调用来进行那个结构的初始化。 2) 老方法 如果你深入浏览 2.6 内核的大量驱动代码,你可能注意到有许多字符驱动不使用我们刚刚描述 过的 cdev 接口。你见到的是还没有更新到 2.6 内核接口的老代码。因为那个代码实际上能用,这 个更新可能很长时间不会发生。为完整,我们描述老的字符设备注册接口,但是新代码不应当使用 它;这个机制在将来内核中可能会消失。 注册一个字符设备的经典方法是使用: int register_chrdev(unsigned int major,const char *name,struct file_operations *fops); 这里,major 是感兴趣的主编号,name 是驱动的名子(出现在 /proc/devices),fops 是缺省的 file_operations 结构。一个对 register_chrdev 的调用为给定的主编号注册 0 - 255 的次编号,并 且为每一个建立一个缺省的 cdev 结构。使用这个接口的驱动必须准备好处理对所有 256 个次编号 的 open 调用( 不管它们是否对应真实设备 ),它们不能使用大于 255 的主或次编号。 如果你使用 register_chrdev,从系统中去除你的设备的正确的函数是: int unregister_chrdev(unsigned int major,const char *name); major 和 name 必须和传递给 register_chrdev 的相同,否则调用会失败。 5. 2. 3 open 和 release 1)open 方法 open 方法提供给驱动来做任何的初始化来准备后续的操作。在大部分驱动中,open 应当进行 下面的工作: ¾ 检查设备特定的错误(例如设备没准备好,或者类似的硬件错误); ¾ 如果它第一次打开,初始化设备; ¾ 如果需要,更新 f_op 指针; ¾ 分配并填充要放进 filp->private_data 的任何数据结构。 但是,事情的第一步常常是确定打开哪个设备。记住 open 方法的原型是: int (*open)(struct inode *inode,struct file *filp); inode 参数有我们需要的信息,以它的 i_cdev 成员的形式,里面包含我们之前建立的 cdev 结 构。唯一的问题是通常我们不想要 cdev 结构本身,我们需要的是包含 cdev 结构的 scull_dev 结 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 261 - 构。C 语言使程序员玩弄各种技巧来做这种转换;但是,这种技巧编程是易出错的,并且导致别人 难于阅读和理解代码。幸运的是,在这种情况下,内核 hacker 已经为我们实现了这个技巧,以 container_of 宏的形式,在 中定义: container_of(pointer,container_type,container_field); 这个宏使用一个指向 container_field 类型的成员的指针,它在一个 container_type 类型的结 构中,并且返回一个指针指向包含结构。在 scull_open,这个宏用来找到适当的设备结构: struct scull_dev *dev; // 设备信息 dev = container_of(inode->i_cdev,struct scull_dev,cdev); filp->private_data = dev; // 供其他方法 一旦它找到 scull_dev 结构,scull 在文件结构的 private_data 成员中存储一个它的指针,为 以后更易存取。 识别打开的设备的另外的方法是查看存储在 inode 结构的次编号。如果你使用 register_chrdev 注册你的设备,你必须使用这个技术。确认使用 iminor 从 inode 结构中获取次编 号,并且确定它对应一个你的驱动真正准备好处理的设备。 scull_open 的代码(稍微简化过)是: int scull_open(struct inode *inode,struct file *filp) { struct scull_dev *dev; // 设备信息 dev = container_of(inode->i_cdev,struct scull_dev,cdev); filp->private_data = dev; // 供其他方法 // 如果设备以只读方式打开,则截取为0 if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { scull_trim(dev); // 忽视错误 } return 0; // 成功 } 代码看来相当稀疏,因为在调用 open 时它没有做任何特别的设备处理。它不需要,因为 scull 设备设计为全局的和永久的。特别地,没有如“在第一次打开时初始化设备”等动作,因为我们不 为 scull 保持打开计数。 唯一在设备上的真实操作是当设备为写而打开时将它截取为长度为 0。这样做是因为,在设计 上,用一个短的文件覆盖一个 scull 设备导致一个短的设备数据区。这类似于为写而打开一个常规 文件,将其截短为 0。如果设备为读而打开,这个操作什么都不做。 在我们查看其他 scull 特性的代码时将看到一个真实的初始化如何起作用的。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 262 - 2)release 方法 release 方法的角色是 open 的反面。有时你会发现方法的实现称为 device_close,而不是 device_release。任一方式,设备方法应当进行下面的任务: ¾ 释放 open 分配在 filp->private_data 中的任何东西; ¾ 在最后的 close 关闭设备。 scull 的基本形式没有硬件去关闭,因此需要的代码是最少的: int scull_release(struct inode *inode,struct file *filp) { return 0; } 你可能想知道当一个设备文件关闭次数超过它被打开的次数会发生什么。毕竟,dup 和 fork 系 统调用并不调用 open 来创建打开文件的拷贝;每个拷贝接着在程序终止时被关闭。例如,大部分程 序不打开它们的 stdin 文件(或设备),但是它们都以关闭它结束。当一个打开的设备文件已经真正被 关闭时驱动如何知道? 答案简单:不是每个 close 系统调用都引起调用 release 方法。只有真正释放设备数据结构的调 用会调用这个方法。内核维持一个文件结构被使用多少次的计数。fork 和 dup 都不创建新文件(只 有 open 这样);它们只递增正存在的结构中的计数。close 系统调用仅在文件结构计数掉到 0 时执 行 release 方法,这在结构被销毁时发生。release 方法和 close 系统调用之间的这种关系保证了你 的驱动一次 open 只看到一次 release。 注意,flush 方法在每次应用程序调用 close 时都被调用。但是,很少驱动实现 flush,因为常常 在 close 时没有什么要做,除非调用 release。 比如你会想到的,前面的讨论即便是应用程序没有明显地关闭它打开的文件也适用:内核在进 程 exit 时自动关闭了任何文件,通过在内部使用 close 系统调用。 5. 2. 4 读写操作 读和写方法都进行类似的任务,就是从内核的应用程序代码拷贝数据。因此,它们的原型相当 类似,可以同时介绍它们: ssize_t read(struct file *filp,char __user *buff,size_t count,loff_t *offp); ssize_t write(struct file *filp,const char __user *buff,size_t count,loff_t *offp); 对于 2个方法,filp 是文件指针,buff 参数指向持有被写入数据的缓存,或者放入新数据的空 缓存,count 是请求的传输数据大小。最后,offp 是一个指针指向一个“long offset type”对象,它 指出用户正在存取的文件位置。返回值是一个“signed size type”,它的使用在后面讨论。 让我们重复一下,read 和 write 方法的 buff 参数是用户空间指针。因此,它不能被内核代码直 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 263 - 接引用。这个限制有几个理由: 依赖于你的驱动运行的体系,以及内核被如何配置的,用户空间指针在运行于内核模式时可能 根本是无效的。可能没有那个地址的映射,或者它可能指向一些其他的随机数据。 就算这个指针在内核空间是同样的东西,用户空间内存是分页的,在做系统调用时这个内存可 能没有在 RAM 中。试图直接引用用户空间内存可能产生一个页面错,这是内核代码不允许做的事情。 结果可能是一个“oops”,导致进行系统调用的进程死亡。 置疑中的指针由一个用户程序提供,它可能是错误的或者恶意的。如果你的驱动盲目地直接引 用一个用户提供的指针,它提供了一个打开的门路使用户空间程序存取或覆盖系统任何地方的内存。 如果你不想你的用户的系统有安全危险,你就不能直接引用用户空间指针。 显然,你的驱动必须能够存取用户空间缓存以完成它的工作。但是,为安全起见这个存取必须 使用特殊的,内核提供的函数。它们使用一些特殊的,依赖体系的技巧来确保内核和用户空间的数 据传输安全和正确。 scull 中的读写代码需要拷贝一整段数据到或者从用户地址空间。这个能力由下列内核函数提供, 它们拷贝一个任意的字节数组,并且位于大部分读写实现的核心中。 unsigned long copy_to_user(void __user *to,const void *from,unsigned long count); unsigned long copy_from_user(void *to,const void __user *from,unsigned long count); 尽管这些函数表现像正常的 memcpy 函数,在从内核代码中存取用户空间时必须加一点小心。 寻址的用户当前也可能不在内存,虚拟内存子系统会使进程睡眠在这个页被传送到位时。例如,这 发生在必须从交换空间获取页的时候。对于驱动编写者来说,最终结果是任何存取用户空间的函数 必须是可重入的,必须能够和其他驱动函数并行执行,并且,特别的,必须在一个它能够合法地睡 眠的位置。 这 2 个函数的角色不限于拷贝数据到和从用户空间:它们还检查用户空间指针是否有效。如果 指针无效,不进行拷贝;如果在拷贝中遇到一个无效地址,另一方面,只拷贝部分数据。在这 2 种 情况下,返回值是还要拷贝的数据量。scull 代码查看这个错误返回,并且如果它不是 0 就返回-EFAULT 给用户。 然而,值得注意的是如果你不需要检查用户空间指针,你可以调用__copy_to_user 和 __copy_from_user 来代替,这是有用处的。例如,如果你知道你已经检查了这些参数,但是要小心, 事实上,如果你不检查你传递给这些函数的用户空间指针,那么你可能造成内核崩溃或安全漏洞。 至于实际的设备方法,read 方法的任务是从设备拷贝数据到用户空间(使用 copy_to_user),而 write 方法必须从用户空间拷贝数据到设备(使用 copy_from_user)。每个 read 或 write 系统调用请 求一个特定数目字节的传送,但是驱动可自由传送较少数据--对读和写这确切的规则稍微不同。 不管这些方法传送多少数据,它们通常应当更新*offp 中的文件位置来表示在系统调用成功完成 后当前的文件位置。内核接着在适当时候传播文件位置的改变到文件结构。pread 和 pwrite 系统调 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 264 - 用有不同的语义,它们从一个给定的文件偏移操作,并且不改变其他的系统调用看到的文件位置。 这些调用传递一个指向用户提供的位置的指针,并且放弃你的驱动所做的改变。 图 5-3-1 给 read 的参数表示了一个典型读实现是如何使用它的参数。 图 5-3-1 给 read 的参数 read 和 write 方法都在发生错误时返回一个负值。相反,大于或等于 0 的返回值告知调用程序 有多少字节已经成功传送。如果一些数据成功传送接着发生错误,返回值必须是成功传送的字节数, 错误直到函数下一次调用时才报告。实现这个传统,当然,要求你的驱动记住错误已经发生,以便 它们可以在以后返回错误状态。 尽管内核函数返回一个负数指示一个错误,这个数的值指出所发生的错误类型,用户空间运行 的程序常常看到-1 作为错误返回值。它们需要存取 errno 变量来找出发生了什么。用户空间的行为 由 POSIX 标准来规定,但是这个标准没有规定内核内部如何操作。 1)read 方法 read 的返回值由调用的应用程序解释: ¾ 如果这个值等于传递给 read 系统调用的 count 参数,请求的字节数已经被传送。这是最好 的情况; ¾ 如果是正数,但是小于 count,只有部分数据被传送。这可能由于几个原因,依赖于设备。 常常,应用程序重新试着读取。例如,如果你使用 fread 函数来读取,库函数重新发出系 统调用直到请求的数据传送完成; ¾ 如果值为 0,到达了文件末尾(没有读取数据); ¾ 一个负值表示有一个错误。这个值指出了什么错误,根据。出错的典型返 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 265 - 回值包括-EINTR(被打断的系统调用)或者-EFAULT(坏地址)。 前面列表中漏掉的是这种情况“没有数据,但是可能后来到达”。在这种情况下,read 系统调 用应当阻塞。 scull 代码利用了这些规则,特别地,它利用了部分读规则。每个 scull_read 调用只处理单个数 据量子,不实现一个循环来收集所有的数据,这使得代码更短更易读。如果读程序确实需要更多数 据,它重新调用。如果标准 I/O 库(例如,fread)用来读取设备,应用程序甚至不会注意到数据传送 的量子化。 如果当前读取位置大于设备大小,scull 的 read 方法返回 0 来表示没有可用的数据(换句话说, 我们在文件尾)。这个情况发生在如果进程 A 在读设备,同时进程 B 打开它写,这样将设备截短为 0。 进程 A 突然发现自己过了文件尾,下一个读调用返回 0。 这是 read 的代码(忽略对 down_interruptible 的调用并且现在为 up): ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; // 第一个列表项目 int quantum = dev->quantum,qset = dev->qset; int itemsize = quantum * qset; // 列表项目中的字节数 int item,s_pos,q_pos,rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) goto out; if (*f_pos + count > dev->size) count = dev->size - *f_pos; // 找到列表,qset索引以及偏移 item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; // 将dev插到列表的正确位置 dptr = scull_follow(dev,item); if (dptr == NULL || !dptr->data || ! dptr->data[s_pos]) goto out; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 266 - // 只读 if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf,dptr->data[s_pos] + q_pos,count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; out: up(&dev->sem); return retval; } 2)write 方法 write,像 read,可以传送少于要求的数据,根据返回值有下列规则: ¾ 如果值等于 count,要求的字节数已被传送。 ¾ 如果正值,但是小于 count,只有部分数据被传送。程序最可能重试写入剩下的数据。 ¾ 如果值为 0,什么没有写。这个结果不是一个错误,没有理由返回一个错误码。再一次, 标准库重试写调用。 ¾ 一个负值表示发生一个错误;如同对于读,有效的错误值是定义于中。 不幸的是,仍然可能有发出错误消息的不当行为程序,它在进行了部分传送时终止。这是因为 一些程序员习惯看写调用要么完全失败要么完全成功,这实际上是大部分时间的情况,应当也被设 备支持。scull 实现的这个限制可以修改,但是我们不想使代码不必要地复杂。 write 的 scull 代码一次处理单个量子,如 read 方法做的: ssize_t scull_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos) { struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum,qset = dev->qset; int itemsize = quantum * qset; int item,s_pos,q_pos,rest; ssize_t retval = -ENOMEM; // 用于“Goto”条件判断 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 267 - if (down_interruptible(&dev->sem)) return -ERESTARTSYS; // 找到列表,qset索引以及偏移 item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; // 将dev插到列表的正确位置 dptr = scull_follow(dev,item); if (dptr == NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data,0,qset * sizeof(char *)); } if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum,GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } // 只写 if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos]+q_pos,buf,count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 268 - // 更新size if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; } 3)readv 和 writev Unix 系统已经长时间支持名为 readv 和 writev 的 2 个系统调用。这些 read 和 write 的“矢量” 版本使用一个结构数组,其中每个包含一个缓存的指针和一个长度值。一个 readv 调用被期望来轮 流读取指示的数量到每个缓存。相反,writev 要收集每个缓存的内容到一起并且作为单个写操作送 出它们。 如果你的驱动不提供方法来处理矢量操作,readv 和 writev 由多次调用你的 read 和 write 方法 来实现。在许多情况,直接实现 readv 和 writev 能获得更大的效率。 矢量操作的原型是: ssize_t (*readv) (struct file *filp,const struct iovec *iov,unsigned long count,loff_t *ppos); ssize_t (*writev) (struct file *filp,const struct iovec *iov,unsigned long count,loff_t *ppos); 这里,filp 和 ppos 参数与 read 和 write 的相同。iovec 结构,定义于,如同: struct iovec { void __user *iov_base; __kernel_size_t iov_len; }; 每个 iovec 描述了一块要传送的数据,它开始于 iov_base(在用户空间)并且有 iov_len 字节长。 count 参数告诉有多少 iovec 结构。这些结构由应用程序创建,但是内核在调用驱动之前拷贝它们到 内核空间。 矢量操作的最简单实现是一个直接的循环,只是传递出去每个 iovec 的地址和长度给驱动的 read 和 write 函数。然而,有效的和正确的行为常常需要驱动更聪明。例如,一个磁带驱动上的 writev 应当将全部 iovec 结构中的内容作为磁带上的单个记录。 很多驱动,没有从自己实现这些方法中获益。因此,scull 省略它们。内核使用 read 和 write 来 模拟它们,最终结果是相同的。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 269 - 5. 2. 5 ioctl 接口 大部分驱动需要--除了读写设备的能力--通过设备驱动进行各种硬件控制的能力。大部分设备可 进行超出简单的数据传输之外的操作,用户空间必须常常能够请求,例如,设备锁上它的门,弹出 它的介质,报告错误信息,改变波特率,或者自我销毁。这些操作常常通过 ioctl 方法来支持,它通 过相同名字的系统调用来实现。 在用户空间,ioctl 系统调用有下面的原型: int ioctl(int fd,unsigned long cmd,…); 这个原型由于这些点而凸现于 Unix 系统调用列表,这些点常常表示函数有数目不定的参数。在 实际系统中,一个系统调用不能真正有可变数目的参数。系统调用必须有一个很好定义的原型,因 为用户程序可存取它们只能通过硬件的“门”。因此,原型中的点不表示一个可变数目的参数,而是 一个单个可选的参数,传统上标识为 char *argp。这些点在那里只是为了阻止在编译时的类型检查。 第 3 个参数的实际特点依赖所发出的特定的控制命令(第 2 个参数)。一些命令不用参数,一些用一 个整数值,以及一些使用指向其他数据的指针。使用一个指针是传递任意数据到 ioctl 调用的方法; 设备接着可与用户空间交换任何数量的数据。 ioctl 调用的非结构化特性使它在内核开发者中失宠。每个 ioctl 命令,基本上是一个单独的常常 无文档的系统调用,并且没有方法以任何类型的全面的方式核查这些调用。也难于使非结构化的 ioctl 参数在所有系统上一致工作,例如,考虑运行在 32-位模式的一个用户进程的 64-位系统。结果,有 很大的压力来实现混杂的控制操作,只能通过任何其他的方法。可能的选择包括嵌入命令到数据流 或者使用虚拟文件系统,要么是 sysfs,要么是设备特定的文件系统。但是,事实上对于真正的设备 操作,ioctl 常常是最容易和最直接的选择。 ioctl 驱动方法有和用户空间版本不同的原型: int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg); inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值,和传递给 open 方法的相同参数。 cmd 参数从用户那里不改变地传下来,并且可选的 arg 参数以一个 unsigned long 的形式传递,不 管它是否由用户给定为一个整数或一个指针。如果调用程序不传递第 3 个参数,被驱动操作收到的 arg 值是无定义的。因为类型检查在这个额外参数上被关闭,编译器不能警告你是否一个无效的参数 被传递给 ioctl,并且任何关联的错误将难以查找。 你可能想象到,大部分 ioctl 实现包括一个大的 switch 语句来根据 cmd 参数选择正确的做法。 不同的命令有不同的数值,它们常常被给予符号名来简化编码。符号名通过一个预处理定义来安排。 定制的驱动常常在它们的头文件中声明这样的符号,scull.h 为 scull 声明它们。用户程序必须包含那 个头文件来存取这些符号。 1)选择 ioctl 命令 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 270 - 在为 ioctl 编写代码之前,你需要选择对应命令的数字。许多程序员的第一个本能的反应是选择 一组小数从 0 或 1 开始,并且从此开始向上。但是,有充分的理由不这样做。ioctl 命令数字应当在 这个系统是唯一的,为了阻止向错误的设备发出正确的命令而引起的错误。这样的不匹配有可能发 生,并且一个程序可能发现它自己试图改变一个非串口输入系统的波特率,例如一个 FIFO 或者一个 音频设备。如果这样的 ioctl 号是唯一的,这个应用程序将得到一个 EINVAL 错误而不是继续做不应 当做的事情。 为帮助程序员创建唯一的 ioctl 命令代码,这些编码已被划分为几个位段。Linux 的第一个版本 使用 16-位数:高 8 位是关联这个设备的“模”数,低 8 位是一个顺序号,在设备内唯一。这样做 是因为 Linus 是“无能”的(他自己的话),一个更好的位段划分仅在后来被设想。不幸的是,许多驱 动仍然使用老传统。它们不得不改变命令编码,这样会破坏大量的二进制程序,这不是内核开发者 愿意见到的。 根据 Linux 内核惯例来为你的驱动选择 ioctl 号,你应当首先检查 include/asm/ioctl.h 和 Documentation/ioctl-number.txt。这个头文件定义你将使用的位段:type(模数),序号,传输方向, 和参数大小。ioctl-number.txt 文件列举了在内核中使用的模数,因此你将可选择你自己的模数并且 避免交叠。这个文本文件也列举了为什么应当使用惯例的原因。 定义 ioctl 命令号的正确方法使用 4 个位段,它们有下列的含义。这个列表中介绍的新符号定义 在。 ¾ type:模数。选择一个数(在参考了 ioctl-number.txt 之后)并且使用它在整个驱动中,这个 成员是 8 位宽(_IOC_TYPEBITS); ¾ number:序(顺序)号。它是 8 位(_IOC_NRBITS)宽; ¾ direction:数据传送的方向,如果这个特殊的命令涉及数据传送。可能的值是_IOC_NONE(没 有数据传输),_IOC_READ,_IOC_WRITE,和_IOC_READ|_IOC_WRITE(数据在 2 个方向 被传送)。数据传送是从应用程序的观点来看待的,_IOC_READ 意思是从设备读,设备必 须写到用户空间。注意这个成员是一个位掩码,因此_IOC_READ 和_IOC_WRITE 可使用一 个逻辑 AND 操作来抽取; ¾ size:涉及到的用户数据的大小。这个成员的宽度是依赖体系的,但是常常是 13 或者 14 位。你可为你的特定体系在宏_IOC_SIZEBITS 中找到它的值。你使用这个 size 成员不是强 制的--内核不检查它--但是它是一个好主意。如果你曾需要改变相关数据项的大小,正确使 用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容。如果你需要更大的数据 结构,你可忽略这个 size 成员。我们很快见到如何使用这个成员。 头文件 包含在 中,定义宏来帮助建立命令号,如下: _IO(type,nr)( 给没有参数的命令) , _IOR(type , nre , datatype)( 给从驱动中读数据的) , _IOW(type,nr,datatype)(给写数据),和_IOWR(type,nr,datatype)(给双向传送)。type 和 number 成 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 271 - 员作为参数被传递,并且 size 成员通过应用 sizeof 从 datatype 参数而得到。 这个头文件还定义宏,可被用在你的驱动中来解码这个号:_IOC_DIR(nr),_IOC_TYPE(nr), _IOC_NR(nr),和_IOC_SIZE(nr)。我们不进入任何这些宏的细节,因为头文件是清楚的,并且在本 节稍后有例子代码展示。 这里是一些 ioctl 命令如何在 scull 被定义的。特别地,这些命令设置和获得驱动的可配置参数。 // 使用’K’为模数 #define SCULL_IOC_MAGIC 'k' // 请在自己的代码中使用一个不同的8位数 #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC,0) #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC,2,int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC,4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,5,int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC,6,int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC,8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC,9,int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10,int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC,12) #define SCULL_IOC_MAXNR 14 真正的源文件定义几个这里没有出现的额外的命令。 我们选择实现 2 种方法传递整数参数:通过指针和通过明确的值(尽管,由于一个已存在的惯例, ioclt 应当通过指针交换值)。类似地,2 种方法被用来返回一个整数值:通过指针和通过设置返回值。 只要返回值是一个正整数就有效,如同你现在所知道的,在从任何系统调用返回时,一个正值被保 留(如同我们在 read 和 write 中见到的),而一个负值被看作一个错误而被用来在用户空间设置 errno。 “exchange”和“shift”操作对于 scull 没有特别的用处。我们实现“exchange”来显示驱动 如何结合独立的操作到单个的原子的操作,并且“shift”来连接“tell”和“query”。有时需要像这 样的原子的测试-和-设置操作,特别是当应用程序需要设置和释放锁。 命令的明确的序号没有特别的含义,它只用来区分命令。实际上,你甚至可使用相同的序号给 一个读命令和一个写命令,因为实际的 ioctl 号在“方向”位是不同的,但是你没有理由这样做。我 们选择在除了声明外的任何地方不使用命令的序号,因此我们不分配一个返回值给它。这就是为什 么明确的序号出现在之前给定的定义中。这个例子展示了一个使用命令号的方法,但是你有自由不 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 272 - 这样做。 除了少数几个预定义的命令,ioctl 的 cmd 参数的值当前不被内核使用,并且在将来也不太可能。 因此,如果你想偷懒,可以避免前面展示的复杂的声明并明确声明一组调整数字。另一方面,如果 你做了,你不会从使用这些位段中获益,并且你会遇到困难,如果你曾提交你的代码来包含在主线 内核中。头文件是这个老式方法的例子,使用 16-位的调整值来定义 ioctl 命令。这个 源代码依靠调整数,这是由于使用那个时候遵循的惯例。现在改变它可能导致不兼容。 2)返回值 ioctl 的实现常常是一个基于命令号的 switch 语句。但是当命令号没有匹配一个有效的操作时缺 省的选择应当是什么?这个问题是有争议的。几个内核函数返回-ENIVAL("Invalid argument"),它 有意义是因为命令参数确实不是一个有效的 POSIX 标准,但是如果一个不合适的 ioctl 命令被发出, 那么-ENOTTY 应当被返回。这个错误码被 C 库解释为“设备的不适当的 ioctl”,这常常正是程序员 需要听到的。然而,它对于响应一个无效的 ioctl 命令,仍然是返回-EINVAL。 3)预定义的命令 尽管 ioctl 系统调用最常用来作用于设备,让内核能识别几个命令。注意这些命令,当用到你的 设备时,在你自己的文件操作被调用之前被解码。因此,如果你选择相同的号给一个你的 ioctl 命令, 你不会看到任何的给那个命令的请求,并且应用程序将获得某些不期望的东西,这是因为在 ioctl 号 之间存在冲突。 预定义命令分为 3 类: ¾ 可对任何文件发出的(常规,设备,FIFO,或者 socket)的命令; ¾ 只对常规文件发出的命令; ¾ 对文件系统类型特殊的命令。 最后一类的命令由宿主文件系统的实现来执行(这是 chattr 命令如何工作的)。设备驱动编写者 只对第一类命令感兴趣,它们的模数是“T”。查看其他类的工作留给读者作为练习;ext2_ioctl 是最 有趣的函数(并且比预期的要容易理解),因为它实现 append-only 标志和 immutable 标志。 下列 ioctl 命令是预定义给任何文件,包括设备特殊的文件: ¾ FIOCLEX:设置 close-on-exec 标志(FileIOctlCloseonEXec)。当调用进程执行一个新程序时, 设置这个标志使文件描述符被关闭; ¾ FIONCLEX:清除 close-no-exec 标志(FileIOctlNot CLose on EXec)。这个命令恢复普通文 件行为,复原上面 FIOCLEX 所做的。FIOASYNC 为这个文件设置或者复位异步通知。注意 直到 Linux2.2.4 版本的内核不正确地使用这个命令来修改 O_SYNC 标志。因为两个动作都 可通过 fcntl 来完成,没有人真正使用 FIOASYNC 命令,它在这里出现只是为了完整性; ¾ FIOQSIZE:这个命令返回一个文件或者目录的大小,当作一个设备文件,但是它返回一个 ENOTTY 错误; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 273 - ¾ FIONBIO:“File IOctl Non-Blocking I/O”。这个调用修改在 filp->f_flags 中的 O_NONBLOCK 标志。给这个系统调用的第 3 个参数用作是否这个标志被置位或者清除的指示。注意常用 的改变这个标志的方法是使用 fcntl 系统调用,使用 F_SETFL 命令。 列表中的最后一项介绍了一个新的系统调用,fcntl。它看来像 ioctl,事实上 fcntl 调用非常类似 ioctl,它也是获得一个命令参数和一个额外的(可选地)参数。它保持和 ioctl 独立主要是由于历史原 因:当 Unix 开发者面对控制 I/O 操作的问题时,他们决定文件和设备是不同的。那时,有 ioctl 实 现的唯一设备是 ttys,它解释了为什么-ENOTTY 是对不正确 ioctl 命令的回答。情况到现在已经改变, 但是 fcntl 仍保留为一个独立的系统调用。 4)使用 ioctl 参数 在看 scull 驱动的 ioctl 代码之前,我们需要涉及的另一点是如何使用这个额外的参数。如果它 是一个整数,它可以直接使用。如果它是一个指针,必须小心点。 当用一个指针引用用户空间,我们必须确保用户地址是有效的。试图存取一个没验证过的用户 提供的指针可能导致不正确的行为,如一个内核 oops,系统崩溃,或者安全问题。它是驱动的任务, 要求对每个它使用的用户空间地址进行正确的检查,如果它是无效的则返回一个错误。 copy_from_user 和 copy_to_user 函数,它们可用来安全地移动数据到和从用户空间。这些函 数也可用在 ioctl 方法中,但是 ioctl 调用常常包含小数据项,可通过其他方法更有效地操作。开始, 地址校验(不传送数据)由函数 access_ok 实现,它定义在: int access_ok(int type,const void *addr,unsigned long size); 第一个参数应当是 VERIFY_READ 或者 VERIFY_WRITE,依据这个要进行的动作是否是读用户 空间内存区或者写它。addr 参数持有一个用户空间地址,size 是一个字节量。例如,如果 ioctl 需要 从用户空间读一个整数,size 是 sizeof(int)。如果你需要读和写给定地址,使用 VERIFY_WRITE,因 为它是 VERIRY_READ 的超集。 不像大部分的内核函数,access_ok 返回一个布尔值:1 是成功(存取没问题)和 0 是失败(存取 有问题)。如果它返回假,驱动应当返回-EFAULT 给调用者。 关于 access_ok 有多个有趣的东西要注意。首先,它不做校验内存存取的完整工作,它只检查 这个内存引用是否在这个进程有合理权限的内存范围中。特别地,access_ok 确保这个地址不指向 内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok。后面描述的内存存取函数为你 负责这个。但是,我们演示它的使用,以便你可以见到它如何完成。 scull 源码利用了 ioclt 号中的位段来检查参数,在 switch 之前: int err = 0,tmp; int retval = 0; if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 274 - if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE,(void __user *)arg,_IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ,(void __user *)arg,_IOC_SIZE(cmd)); if (err) return -EFAULT; 在调用 access_ok 之后,驱动可安全地进行真正的传输。加上 copy_from_user 和 copy_to_user_ 函数,程序员可利用一组为被最多使用的数据大小(1,2,4,和 8 字节)而优化过的函数。这些函数 在下面列表中描述,它们定义在: put_user(datum,ptr) __put_user(datum,ptr) 这些宏定义写 datum 到用户空间,它们速度很快,并且无论何时要传送单个值时,都应当被调 用来代替 copy_to_user。这些宏已被编写来允许传递任何类型的指针到 put_user,只要该指针是一 个用户空间地址。传送的数据大小依赖 prt 参数的类型,并且在编译时使用 sizeof 和 typeof 等编译 器内建宏。结果是,如果 prt 是一个 char 指针,传送一个字节,以及对于 2,4,和可能的 8 字节。 put_user 检查来确保这个进程能够写入给定的内存地址。它在成功时返回 0,并且在错误时返 回-EFAULT。__put_user 进行更少的检查(它不调用 access_ok),但是如果被指向的内存对用户是不 可写时仍然能够导致失败。因此,__put_user 应当只用在内存区已经用 access_ok 检查过的时候。 作为一个通用的规则,当你实现一个 read 方法时,调用__put_user 来节省几个周期,或者当你 拷贝几个项时也可以这样。因此,在第一次数据传送之前调用 access_ok 一次,如同上面 ioctl 所示。 get_user(local,ptr) __get_user(local,ptr) 这些宏定义用来从用户空间接收单个数据。它们像 put_user 和__put_user,但是在相反方向传 递数据。获取的值存储于本地变量 local,返回值指出这个操作是否成功。再次,__get_user 应当只 用在已经使用 access_ok 校验过的地址。 如果尝试使用一个列出的函数来传送一个不适合特定大小的值,结果常常是一个来自编译器的 奇怪消息,例如“coversion tonon-scalartype requested”。在这些情况中,必须使用 copy_to_user 或者 copy_from_user。 5)兼容性和受限操作 存取一个设备是由设备文件上的许可权进行控制,并且驱动一般不涉及到许可权的检查。但是, 有些情形,在保证给任何用户对设备的读写许可的地方,一些控制操作仍然应当被拒绝。例如,不 是所有的磁带驱动器的用户都应当能够设置它的缺省块大小,并且一个已经被给予了对一个磁盘设 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 275 - 备读写权限的用户应当仍然可能被拒绝来格式化它。在这样的情况下,驱动必须进行额外的检查来 确保用户能够进行被请求的操作。 传统上 unix 系统对超级用户帐户限制了特权操作。这意味着特权是一个全有-或-全无的东西-- 超级用户可能任意做任何事情,但是所有其他的用户被高度限制了。Linux 内核提供了一个更加灵活 的系统,称为能力。一个基于能力的系统丢弃了全有-或全无模式,并且打破特权操作为独立的子类。 这种方式,一个特殊的用户(或者是程序)可被授权来进行一个特定的特权操作而不必泄漏进行其他 的,无关的操作的能力。内核在许可权管理上排他地使用能力,并且输出 2 个系统调用 capget 和 capset 来允许它们被从用户空间管理。 全部能力可在中找到。这些是对系统唯一可用的能力;对于驱动作者或者 系统管理员,不可能不修改内核源码而来定义新的设备驱动。设备驱动编写者可能感兴趣的是这些 能力的一个子集,包括下面: ¾ CAP_DAC_OVERRIDE:这个能力来推翻在文件和目录上的存取的限制(数据存取控制,或 者 DAC); ¾ CAP_NET_ADMIN:进行网络管理任务的能力,包括那些能够影响网络接口的; ¾ CAP_SYS_MODULE:加载或去除内核模块的能力; ¾ CAP_SYS_RAWIO:进行“raw”I/O 操作的能力。例子包括存取设备端口或者直接和 USB 设备通讯; ¾ CAP_SYS_ADMIN:一个捕获全部的能力,提供对许多系统管理操作的存取; ¾ CAP_SYS_TTY_CONFIG:进行 tty 配置任务的能力。 在进行一个特权操作之前,一个设备驱动应当检查调用进程是否有合适的能力,不这样做可能 导致用户进程进行非法的操作,对系统的稳定和安全有不好的后果。能力检查是通过 capable 函数 来进行的(定义在): int capable(int capability); 在 scull 例子驱动中,任何用户都被许可来查询 quantum 和 quantum 集的大小。但是只有特权 用户可改变这些值,因为不适当的值可能很坏地影响系统性能。当需要时,ioctl 的 scull 实现检查用 户的特权级别,如下: if (! capable (CAP_SYS_ADMIN)) return -EPERM; 在这个任务缺乏一个更加特定的能力时,CAP_SYS_ADMIN 被选择来做这个测试。 6)ioctl 命令的实现 ioctl 的 scull 实现只传递设备的配置参数,并且像下面这样容易: switch(cmd) { case SCULL_IOCRESET: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 276 - scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: // Set: 参数为指向值的指针 if (! capable (CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_quantum,(int __user *)arg); break; case SCULL_IOCTQUANTUM: // Tell: 参数为数值 if (! capable (CAP_SYS_ADMIN)) return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: // Get: 参数为指向结构的指针 retval = __put_user(scull_quantum,(int __user *)arg); break; case SCULL_IOCQQUANTUM: // Query: 返回scull_quantum return scull_quantum; case SCULL_IOCXQUANTUM: // eXchange: 将参数作为指针 if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; retval = __get_user(scull_quantum,(int __user *)arg); if (retval == 0) retval = __put_user(tmp,(int __user *)arg); break; case SCULL_IOCHQUANTUM: // sHift: 如同Tell + Query if (! capable (CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; scull_quantum = arg; return tmp; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 277 - default: return -ENOTTY; } return retval; scull 还包含 6 个入口项作用于 scull_qset。这些入口项和给 scull_quantum 的是一致的,并且 不值得展示出来。 从调用者的观点看(即从用户空间),这 6 种传递和接收参数的方法看来如下: int quantum; ioctl(fd,SCULL_IOCSQUANTUM,&quantum); // 通过指针设置 ioctl(fd,SCULL_IOCTQUANTUM,quantum); // 通过值设置 ioctl(fd,SCULL_IOCGQUANTUM,&quantum); // 通过指针获取 quantum = ioctl(fd,SCULL_IOCQQUANTUM); // 通过返回值获取 ioctl(fd,SCULL_IOCXQUANTUM,&quantum); // 通过指针交换 quantum = ioctl(fd,SCULL_IOCHQUANTUM,quantum); // 通过数值交换 当然,一个正常的驱动不可能实现这样一个调用模式的混合体。我们这里这样做只是为了演示 做事情的不同方式。但是,正常地数据交换将通过指针或者通过值一致地进行,并且要避免混合这 2 种技术。 7)不用 ioctl 的设备控制 有时控制设备最好是通过写控制序列到设备自身来实现。例如,这个技术用在控制台驱动中, 这里所谓的 escape 序列被用来移动光标,改变缺省的颜色,或者进行其他的配置任务。这样实现设 备控制的好处是用户可仅仅通过写数据控制设备,不必使用(或者有时候写)只为配置设备而建立的 程序。当设备可这样来控制时,发出命令的程序甚至常常不需要运行在和它要控制的设备所在的同 一个系统上。 例如,setterm 程序作用于控制台(或者其他终端)配置,通过打印 escape 序列。控制程序可位 于和被控制的设备不同的一台计算机上,因为一个简单的数据流重定向可完成这个配置工作。这是 每次你运行一个远程 tty 会话时所发生的事情:escape 序列在远端被打印但是影响到本地的 tty,然 而,这个技术不局限于 ttys。 通过打印来控制的缺点是它给设备增加了策略限制,例如,它仅仅当你确信在正常操作时控制 序列不会出现在正被写入设备的数据中。这对于 ttys 只是部分正确的。尽管一个文本显示意味着只 显示 ASCII 字符,有时控制字符可插入正被写入的数据中,因此影响控制台的配置。例如,这可能 发生在你显示一个二进制文件到屏幕时,产生的乱码可能包含任何东西,最后常常在你的控制台上 出现错误的字体。 对于不用传送数据而只是响应命令的设备,通过写来控制是当然的使用方法了,例如遥控设备。 例如,编写一个好玩的驱动,移动一个 2 轴上的摄像机。在这个驱动里,这个“设备”是一对 老式步进电机,它们不能真正读或写。给一个步进电机“发送数据流”的概念没有任何意义。在这 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 278 - 个情况下,驱动解释正被写入的数据作为 ASCII 命令并且转换这个请求为脉冲序列,从而来操纵步 进电机。这个概念类似于,你发给 Modem AT 命令来建立通讯,主要的不同是和 Modem 通讯的串 口必须也传送真正的数据。直接设备控制的好处是你可以使用 cat 来移动摄像机,而不必写和编译 特殊的代码来发出 ioctl 调用。 当编写面向命令的驱动,没有理由实现 ioctl 命令,一个解释器中的额外命令更容易实现和使用。 有时,你可能选择使用其他的方法:不必转变 write 方法为一个解释器和避免 ioctl,你可能选 择完全避免写专门使用的 ioctl 命令,而实现驱动为使用一个特殊的命令行工具来发送这些命令到驱 动。这个方法把复杂性从内核空间转移到用户空间,并且帮助保持驱动小,而拒绝使用简单的 cat 或者 echo 命令。 5. 2. 6 Hello World 模块 本书涉及的是内核模块而不是程序,下面的代码是一个完整的“hello world”模块: #include #include MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_ALERT "Hello,world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye,cruel world\n"); } module_init(hello_init); module_exit(hello_exit); 这个模块定义了两个函数,一个在模块加载到内核时被调用(hello_init)以及一个在模块被去 除时被调用(hello_exit)。moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函 数的角色。另一个特别的宏(MODULE_LICENSE)是用来告知内核,该模块带有一个自由的许可证; 没有这样的说明,在模块加载时内核会有点问题。 printk 函数在 Linux 内核中定义并且对模块可用,它与标准 C 库函数 printf 的行为相似。内核需 要它自己的打印函数,因为它靠自己运行,没有 C 库的帮助。模块能够调用 printk 是因为,在 insmod 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 279 - 加载了它之后,模块被连接到内核并且可存取内核的公用符号。字串 KERN_ALERT 是消息的优先级。 我们在此模块中指定了一个高优先级,因为使用缺省优先级的消息可能不会在任何有用的地方 显示,这依赖于你运行的内核版本,klogd 守护进程的版本,以及你的配置。现在你可以忽略这个因 素。 你可以用 insmod 和 rmmod 工具来测试这个模块。注意只有超级用户可以加载和卸载模块。 $ make make[1]: Entering directory `/usr/src/Linux-2.6.14' CC [M] /home/ldd3/src/misc-modules/hello.o Building modules,stage 2. MODPOST CC /home/ldd3/src/misc-modules/hello.mod.o LD [M] /home/ldd3/src/misc-modules/hello.ko make[1]: Leaving directory `/usr/src/Linux-2.6.14' $ insmod ./hello.ko Hello,world $ rmmod hello Goodbye cruel world 请再一次注意,为使上面的操作命令顺序工作,你必须在某个地方有正确配置和建立的内核树, 在那里可以找到 makefile(/usr/src/Linux-2.6.10,在展示的例子里面)。 依据你的系统用来递交消息行的机制,你的输出可能不同。特别地,前面的屏幕输出是来自一 个字符控制台,如果你从一个终端模拟器或者在窗口系统中运行 insmod 和 rmmod,你不会在你的 屏幕上看到任何东西。消息进入了其中一个系统日志文件中,例如/var/log/messages(实际文件名字 随 Linux 发布而变化)。 如你能见到的,编写一个模块不是如你想像的困难——至少在模块没有要求做任何有用的事情 时。困难的部分是理解你的设备,以及如何获得最高性能。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 280 - 第六章 基于 Mini2410 的 Linux 内核开发 本章节主要介绍 Linux 内核开发的基本过程,包括 vivi 的编译和固化,kernel 的编译和固化, 如何编译 busybox,制作根文件目录以及搭建 nfs 文件系统,通过本章节的学习,可以了解 Linux 下 内核开发的方法。 6. 1 vivi 的编译与运行实验 6. 1. 1 实验目的 ¾ 熟悉 vivi 相关知识及应用; ¾ 学会使用交叉编译器编译 vivi; ¾ 掌握 vivi 命令将文件固化到目标板上。 6. 1. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 6. 1. 3 实验内容 ¾ 在 Ubuntu 系统下,使用交叉编译器编译 vivi; ¾ 利用 vivi 命令固化 Linux 文件到目标板上。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 281 - 6. 1. 4 实验原理 bootloader( 引导加载程序 ) 是系统加电后运行的第一段代码,一般运行的时间非常短,但是 对于嵌入式系统来说,这段代码非常重要。在我们的台式电脑当中,引导加载程序由 BIOS (固件 程序)和位于硬盘 MBR 中的操作系统引导加载程序(比如 NTLOADER,GRUB 和 LILO)一起组成。 在嵌入式系统当中没有像 BIOS 这样的固件程序,不过也有一些嵌入式 CPU 会在芯片内部嵌入 一小段程序,一般用来将 bootloader 装进 RAM 中,有点类似 BIOS,但是功能比 BIOS 弱很多。在 一般的典型系统中,整个系统的加载启动任务全由 bootloader 来完成。在 ARM 中,系统上电或复 位时通常从地址 0x00000000 处开始执行,而在这个位置,通常安排的就是系统的 BOOTLOADER。 通过这小段程序可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境设置到一个 合适的状态,从而最终为调用操作系统内核准备好正确的环境。 vivi 是由 mizi 公司为 ARM 处理器系列设计的一个 bootloader,因 为 vivi 目前只支持使用串口和 主机通信,所以您必须使用一条串口电缆来连接目标板和主机。主要功能为:复制并启动内核,初 始化硬件,下载目标板到 flash 等。 vivi 有两种工作模式:启动加载模式和下载模式。启动加载模式可以在一段时间后(这个时间 可更改)自行启动 linux 内核,这时 vivi 的默认模式。在下载模式下,vivi 为用户提供一个命令行 接口,通过接口可以使用 vivi 提供的一些命令,见下表: 表 6-1-1 vivi 使用命令表 命令 功能 Load 把二进制文件载入 Flash 或 RAM Part 操作 MTD 分区信息。显示、增加、删除、复位、保存 MTD 分区 Param 设置参数 Boot 启动系统 Flash 管理 Flash ,如删除 Flash 的数据 6. 1. 5 实验步骤 1.编译 vivi 1)在 Ubuntu 中单击菜单应用程序->附件->终端打开终端,在终端中输入命令设置环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/local/arm/2.95.3/path.sh 2)进入 vivi 实验工作目录: $ cd $VIVIDIR 3)清除早前可能存在的配置信息: $ make distclean 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 282 - 4)执行配置命令: $ make menuconfig 图 6-1-1 vivi 配置界面 选择 Load an Alternate Configuration File 选项,回车。在弹出的对话框中输入配置文件, 这里我们提供了 vivi 的配置文件:config-eduk4。 图 6-1-2 选择配置文件 选择< Ok >,回车退出。接下来在选择< Exit >退出配置,然后再在如图 6-1-3 中选择< Yes >,保存并退出。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 283 - 图 6-1-3 vivi 配置退出保存界面 5)执行 make 命令生成可执行文件,文件存放在当前目录下: $ make 6)拷贝文件: $ make install 执行完命令后,将自动拷贝 vivi 到/home/example/目录下。 2.固化 vivi 用户可以根据前面章节内容(章节 4.6.1)更新刚编译的 vivi 到 Mini2410-IV 的 Nand Flash 中, 或者在 Ubuntu 下采用 minicom 终端更新,下面的步骤将具体介绍: 1)正确连接交叉串口于实验平台的 COM2 和 PC 端的串口。 2)运行 ubuntu 终端,在终端中输入命令打开 minicom(必须先按照第三章 3.1.3 正确设置好 minicom): $ sudo minicom 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 284 - 图 6-1-4 启动 minicom 终端 3)给实验平台上电,启动内置 viv 并按空格键进入到 vivi 的命令行界面: 图 6-1-5 进入 vivi 命令行 4)输入命令使用 xmodem 协议来传送新编译好的 vivi(/home/example/vivi): vivi> load flash vivi x 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 285 - 图 6-1-6 进入 vivi 命令行 输入完命令后,回车,终端提示等待 xmodem 协议传送文件,此时按下“Ctrl + a”键,然后 再输入“z”键进入到 minicom 的功能菜单: 图 6-1-7 minicom 功能菜单 从键盘输入“s”,选择发送文件选项,出现协议选择菜单: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 286 - 图 6-1-8 minicom 传送协议选择 通过键盘的上下方向键选中 xmodem 协议,回车进入所需传送文件的默认路径界面: 图 6-1-9 minicom 传送默认文件夹 此时通过键盘的左右方向键,选中“[ 转到 ]”选项,回车,在弹出的界面输入新的 vivi 映像 所在目录路径/home/example: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 287 - 图 6-1-10 输入目标传送文件所在目录 输入完成后回车,将进入到新 vivi 所在目录: 图 6-1-11 进入到目标传送文件所在目录 通过键盘方向键选择“[ OK ]”项,弹出目标传送文件界面,输入目标文件名 vivi: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 288 - 图 6-1-12 进入到目标传送文件名 输入正确的目标传送文件名后,回车,将开始传送新的 vivi 映像,并显示传送进度: 图 6-1-13 传送中 传送完毕将提示传送成功,按任意键退出: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 289 - 图 6-1-13 传送完成 在 minicom 终端将打印出新 vivi 的固化信息,并提示成功: 图 6-1-14 完成 vivi 的传送 注意:等待传送的时间不宜太久,否则就超时,如果出现选中了所需传输的文件,但是传送进 度无反应的,即时超时,可按“Ctrl + c”退出,重新传送。 5)可以重启实验系统验证新固化进去的 vivi,minicom 终端打印如下: VIVI version 0.1.4 (embest@embest-laptop) (gcc version 2.95.3 20010315 (release)) #0.1.4 2008 骞?11 鏈?25 鏃?鏄熸湡浜?16:38:19 CST MMU table base address = 0x33DFC000 Succeed memory mapping. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Found saved vivi parameters. Press Return to start the LINUX now, any other key for vivi type "help" for help. 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 290 - 6. 2 内核编译与运行实验 6. 2. 1 实验目的 ¾ 学习和掌握 Linux 编译的基本步骤; ¾ 通过实验掌握 Linux 配置、编译过程。 6. 2. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 6. 2. 3 实验内容 ¾ 对 Linux 的内核及用户程序进行配置; ¾ 编译生成内核映象文件; ¾ 把编译的映象文件烧写到 FALSH 中,查看运行结果。 6. 2. 4 实验原理 内核是 Linux 操作系统的核心,它管理所有的系统线程、进程、资源和资源分配。与其它操作 系统不同的是,Linux 操作系统允许用户对内核进行重新设置。用户可以对内核进行“瘦身”,增加或 消除对某些特定设备或子系统的支持。在开发嵌入式系统时,开发人员经常会减少系统对一些无用 设备的支持,将节省下来的内存分配给各种应用软件。 Linux 内核对各种硬件和端口的支持要靠各种硬件驱动程序来实现。这些驱动程序可以被直接写 入内核,也可以针对某些特定硬件在需要时自动加载。通常情况下,可以被自动加载进内核的内核 编码称为自动加载内核模块。 Linux 内核的设置是通过内核设置编辑器完成的。内核设置编辑器可对每个内核设置变量进行描 述,帮助用户决定哪些变量需要被清除,哪些需要写入内核,或者编成一个可加载内核模块在需要 时进行加载。 内核是为众多应用程序提供对计算机硬件的安全访问的一部分软件,这种访问是有限的,并且 内核决定一个程序在什么时候对某部分硬件操作多长时间。直接对硬件操作是非常复杂的,所以内 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 291 - 核通常提供一种硬件抽象的方法来完成这些操作。硬件抽象隐藏了复杂性,为应用软件和硬件提供 了一套简洁,统一的接口,使程序设计更为简单。 用户可以根据自己的需要编译内核。 6. 2. 5 实验步骤 1.编译内核 1)单击菜单应用程序->附件->终端打开终端,设置环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)执行命令切换到 linux 内核目录下: $cd $KERNELDIR 3)清除早前可能存在的配置信息: $ make distclean 4)执行配置命令: $ make menuconfig 图 6-2-1 kernel 配置界面 选择 Load an Alternate Configuration File 选项,添加配置文件,回车。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 292 - 图 6-2-2 选择 Load an Alternate Configuration File 将弹出的窗体中的.config 替换成./config-eduk4。 图 6-2-3 选择 kernel 配置文件 选择< ok >,回车退出。 在弹出的主配置界面中,选择< Exit >,回车。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 293 - 图 6-2-4 kernel 配置退出保存界面 选择< Yes >,回车。 5)编译内核: $ make zImage 编译完成后将在 arch/arm/boot 目录下生成 zImage 文件,并自动拷贝到/home/example 目录 下。 6)生成驱动模块: $ make modules 2.固化内核映像 用户可以根据前面章节内容(章节 4.6.1)更新刚编译生成的 zImage 到 Mini2410-IV 的 Nand Flash 中,或者在 Ubuntu 下采用 minicom 终端更新。 使用 minicom 固化内核映像的方法跟前节固化 vivi 映像的步骤相似,启动 vivi 后,输入命令使 用 xmodem 协议来传送新编译好的内核映像 zImage(/home/example/zImage): vivi> load flash kernel x 后面的步骤参考前节 vivi 的传送,仅需要把需要传送的文件名替换为 zImage 即可,传送完毕 minicom 串口终端打印信息如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 294 - 图 6-2-5 内核映像传送完毕 重启实验系统将可以看到新的内核引导信息。 6. 3 busybox 编译实验 6. 3. 1 实验目的 ¾ 熟悉 busybox 相关知识及应用; ¾ 学会使用交叉编译器定制一个 busybox; ¾ 利用该 busybox 制作一个文件系统。 6. 3. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 6. 3. 3 实验内容 ¾ 利用交叉编译器编译 busybox; ¾ 利用已经配置好的 busybox 制作一个文件系统。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 295 - 6. 3. 4 实验原理 busybox 是一个集成了一百多个最常用 linux 命令和工具的软件,他甚至还集成了一个 http 服 务器和一个 telnet 服务器,而所有这一切功能却只有区区 1M 左右的大小。我们平时用的那些 linux 命令就好比是分立式的电子元件,而 busybox 就好比是一个集成电路,把常用的工具和命令集成压 缩在一个可执行文件里,功能基本不变,而大小却小很多倍,在嵌入式 linux 应用中,busybox 有非 常广的应用。另外,大多数 linux 发行版的安装程序中都有 busybox 的身影,安装 linux 的时候按 ctrl+alt+F2 就能得到一个控制台,而这个控制台中的所有命令都是指向 busybox 的链接。busybox 的小身材大作用的特性,给制作一张软盘的 linux 带来了及大方便。 busybox 是标准 Linux 工具的一个单个可执行实现。busybox 包含了一些简单的工具,例如 cat 和 echo,还包含了一些更大、更复杂的工具,例如 grep、find、mount 以及 telnet。有些人将 busybox 称为 Linux 工具里的瑞士军刀。简单的说 busybox 就好像是个大工具箱,它集成压缩了 Linux 的 许多工具和命令,用户可以根据自己的需要定制一个 busybox。 6. 3. 5 实验步骤 1.编译 busybox 1)单击菜单应用程序->附件->终端打开终端,设置环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)执行命令切换到 busybox 实验目录下: $ cd $SIMPLEDIR/6.3-busybox 3)解压 busybox-1.1.2.tar.bz2: $ tar jxvf busybox-1.1.2.tar.bz2 4)进入目录,并打上补丁: $ cd busybox-1.1.2 $ patch -p1 <../1.1.2.patch 5)清除早前可能存在的配置信息: $ make distclean 6)执行配置命令: $ make menuconfig 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 296 - 图 6-3-1 busybox 配置界面 选择 Load an Alternate Configuration File 选项,添加配置文件,回车。 图 6-3-2 选择 Load an Alternate Configuration File 界面 将弹出的窗体中的.config 替换成 eduk4。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 297 - 图 6-3-3 选择 busybox 配置文件 选择< ok >,回车退出。 在弹出的主配置界面中,选择< Exit >,回车。 图 6-3-4 busybox 配置退出保存界面 7)编译 busybox: $ make $ make install 执行完命令后,将在 busybox-1.1.2 下面生成一个_install 文件夹。 2.基于 busybox 生成文件系统包 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 298 - 1)建立 root-mini 文件系统目录,并拷贝 busybox 生成的文件到该目录内: $ cd $SIMPLEDIR/6.3-busybox $ mv busybox-1.1.2/_install root-mini 2)创建文件系统目录树结构,并拷贝必要的文件到文件系统目录内: $ cd root-mini $ mkdir -p dev etc/init.d home mnt proc root sys tmp var media $ cd .. $ cp rz root-mini/bin/ $ cp flash_eraseall root-mini/sbin/ 3)拷贝 inittab 文件到 etc 目录下: $ cp busybox-1.1.2/examples/inittab root-mini/etc/ 4)在 etc/init.d 目录下建立 rcS 文件,内容如下: #! /bin/sh echo "running /etc/init.d/rcS" # mount the /proc file system /bin/mount -t proc proc /proc echo "mount tmpfs filesystem to /tmp" /bin/mount -t tmpfs none /tmp echo "mount ramfs filesystem to /var" /bin/mount -t ramfs none /var /bin/ln -s /dev/fb/0 /dev/fb0 /bin/ln -s /dev/ub/a/part1 /dev/sda1 /bin/ln -s /dev/sound/dsp /dev/dsp /bin/ln -s /dev/tts/0 /dev/ttyS0 /bin/ln -s /dev/tts/1 /dev/ttyS1 /bin/ln -s /dev/tts/2 /dev/ttyS2 /bin/mknod /dev/mtd0 c 90 0 /bin/mknod /dev/mtd1 c 90 2 /bin/mknod /dev/mtd2 c 90 4 /bin/mknod /dev/mtd3 c 90 6 /bin/mknod /dev/mtd4 c 90 8 /bin/mknod /dev/mtd5 c 90 10 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 299 - /bin/hostname EDUK4 /sbin/ifconfig lo 127.0.0.1 netmask 255.0.0.0 #/sbin/ifconfig eth0 192.192.192.200 netmask 255.255.255.0 /sbin/ifconfig eth1 192.192.192.201 netmask 255.255.255.0 #exec /etc/init.d/rc.local 5)修改新建的文件系统文件夹属性: $ chmod -R 777 root-mini 这样基于 busybox 所建立的文件系统就做好了。 6. 4 ramdisk 根文件系统的制作 6. 4. 1 实验目的 ¾ 熟悉根文件系统组织结构; ¾ 定制、编译 ramdisk 根文件系统。 6. 4. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 6. 4. 3 实验内容 ¾ 利用 6.3 中的已经完成的文件系统,生成一个根文件系统镜像。 6. 4. 4 实验原理 ramdisk 是内核初始化的时候用到的一个临时文件系统,是一个最小的 linux rootfs 系统,它包 含了除内核以外的所有 linux 系统在引导和管理时需要的工具,做为启动引导驱动,包含如下目录: bin,dev,etc,home,lib,mnt,proc,sbin,usr,var。还需要有一些基本的工具:sh,ls,cp, mv……(位 于/bin 目录中);必要的配置文件:inittab,rc,fstab……位于(/etc 目录种);必要的设 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 300 - 备文件:/dev/tty*,/dev /console,/dev/men……(位于/dev 目录中);sh,ls 等工具必要的运行库: glibc。 6. 4. 5 实验步骤 1.制作 ramdisk 根文件系统映像 1)单击菜单应用程序->附件->终端打开终端,设置环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)执行命令切换到 ramdisk 实验目录下: $cd $SIMPLEDIR/6.4-ramdisk 3)运行脚本文件: $ sudo sh ramdisk-install.sh shell 脚本命令说明: #!/bin/bash # # ramdisk-install.sh - Make ramdisk filesystem. # # Copyright (C) 2002-2007 # Created. lusi sudo dd if=/dev/zero of=ramdisk bs=1M count=2 echo "y" | sudo mke2fs -m 0 -N 3500 ramdisk sudo mkdir -p temp sudo mount -o loop ramdisk temp # Please do the 6.3-busybox experiment first, and get root-mini filesystem code. sudo cp -av ../6.3-busybox/root-mini/* temp/ sudo umount temp sudo rm -fr ramdisk.gz sudo gzip -v9 ramdisk sudo chmod 777 ramdisk.gz sudo rmdir temp sudo cp -av ramdisk.gz /home/example ¾ 创建一个空的 ramdisk 镜像: sudo dd if=/dev/zero of=ramdisk bs=1M count=2 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 301 - 用 dd 命令建立了一个名为 ramdisk 的 ramdisk 基本文件;ramdisk image 文件名为 ramdisk, 大小为 2M。 ¾ 格式化文件系统: sudo echo "y" | sudo mke2fs -m 0 -N 3500 ramdisk 把它格式化为你需要的文件系统,比如 ext2,ext3 等。 在 rootfs 上面建立 ext2 文件系统。这里的-N 3500 表示最大的 node 数为 3500。因为 linux 的 设备也是基于文件系统的,需要占用文件系统的 node,如果数目建立的太少,以后可能会不够用。 ¾ 挂载文件系统: sudo mkdir -p temp sudo mount -o loop ramdisk temp 把 ramdisk 文件系统挂载到 temp 目录下面。 ¾ 拷贝 6.3 节中已经创建好的文件结构到 temp 文件夹: sudo cp -av root-mini/* temp/ ¾ 卸载; sudo umount temp ¾ 生成压缩文件; sudo gzip -v9 ramdisk ¾ 修改 root-mini.gz 的执行权限; sudo chmod 777 ramdisk.gz ¾ 删除临时文件夹 ramdisk,拷贝生成的 ramdisk 根文件系统映像到/home/example 目录。 sudo rmdir temp sudo cp -av ramdisk.gz /home/example 这样生成的 ramdisk.gz 即为 ramdisk 根文件系统映像,同时拷贝到/home/example 目录下。 2.固化引导 ramdisk 文件系统 用户可以根据前面章节内容(章节 4.6.1)更新刚制作生成的 ramdisk.gz 到 Mini2410-IV 的 Nand Flash 中,或者在 Ubuntu 下采用 minicom 终端更新。 使用 minicom 固化 ramdisk 文件系统映像的方法跟前节固化 vivi 映像的步骤相似,启动 vivi 后, 输入命令使用 xmodem 协议来传送新制作好的 ramdisk 文件系统映像 ramdisk.gz (/home/example/ramdisk.gz): vivi> load flash ramdisk x 后面的步骤参考前节 vivi 的传送,仅需要把需要传送的文件名替换为 ramdisk.gz 即可,传送完 毕 minicom 串口终端打印信息如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 302 - 图 6-4-1 ramdisk 映像传送完毕 重启实验系统,并按空格进入到 vivi 的命令行界面,输入命令修改 vivi 启动参数为引导 ramdisk 根文件系统启动: vivi> param set ramdisk_copy 1 vivi> param set linux_cmd_line "initrd=0x30800000,0x200000 root=/dev/ram console=ttySAC1" vivi> param save 重新启动实验系统,将可以在 minicom 终端看到 ramdisk 文件系统的完成信息: VIVI version 0.1.4 (embest@ embest -laptop) (gcc version 2.95.3 20010315 (release)) #TMMU table base address = 0x33DFC000 Succeed memory mapping. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Could not found stored vivi parameters. Use default vivi parameters. Press Return to start the LINUX now, any other key for vivi Copy linux kernel from 0x00030000 to 0x30008000, size = 0x001d0000 ... size = 14 done zImage magic = 0x016f2818 Copy ramdisk from 0x00200000 to 0x30800000, size = 0x00200000 ... size = 2097152 done Setup linux parameters at 0x30000100 linux command line is: "initrd=0x30800000,0x200000 root=/dev/ram console=ttySAC" MACH_TYPE = 193 NOW, Booting Linux...... Uncompressing Linux............................................................. Linux version 2.6.14 (embest @ embest -laptop) (gcc version 3.4.5) #2 Wed Nov 26 09:308 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 303 - CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T) Machine: SMDK2410 ATAG_INITRD is deprecated; please update your bootloader. Memory policy: ECC disabled, Data cache writeback CPU S3C2410A (id 0x32410002) S3C2410: core 200.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz S3C2410 Clocks, (c) 2004 Simtec Electronics CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on USB Control, (c) 2006 pc104 CPU0: D VIVT write-back cache CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets Built 1 zonelists Kernel command line: initrd=0x30800000,0x200000 root=/dev/ram console=ttySAC1 irq: clearing subpending status 00000010 PID hash table entries: 512 (order: 9, 8192 bytes) timer tcon=00000000, tcnt a2c1, tcfg 00000200,00000000, usec 00001eb8 Console: colour dummy device 80x30 Dentry cache hash table entries: 16384 (order: 4, 65536 bytes) Inode-cache hash table entries: 8192 (order: 3, 32768 bytes) Memory: 64MB = 64MB total Memory: 58880KB available (2803K code, 688K data, 420K init) Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok checking if image is initramfs...it isn't (no cpio magic); looks like an initrd softlockup thread 0 started up. Freeing initrd memory: 2048K NET: Registered protocol family 16 S3C2410: Initialising architecture SCSI subsystem initialized usbcore: registered new driver usbfs usbcore: registered new driver hub Bluetooth: Core ver 2.7 NET: Registered protocol family 31 Bluetooth: HCI device and connection manager initialized Bluetooth: HCI socket layer initialized S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics DMA channel 0 at c4800000, irq 33 DMA channel 1 at c4800040, irq 34 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 304 - DMA channel 2 at c4800080, irq 35 DMA channel 3 at c48000c0, irq 36 NetWinder Floating Point Emulator V0.97 (double precision) devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au) devfs: boot_options: 0x1 JFFS2 version 2.2. (NAND) (C) 2001-2003 Red Hat, Inc. yaffs Nov 25 2008 16:42:48 Installing. Initializing Cryptographic API Console: switching to colour frame buffer device 80x30 fb0: s3c2410fb frame buffer device S3C2410 RTC, (c) 2004 Simtec Electronics s3c2410-rtc s3c2410-rtc: rtc disabled, re-enabling s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410 s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410 s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410 io scheduler noop registered io scheduler anticipatory registered io scheduler deadline registered io scheduler cfq registered RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize loop: loaded (max 8 devices) nbd: registered device at major 43 usbcore: registered new driver ub DM9000A eth0 found DM9000A eth1 found Linux video capture interface: v1.00 ovcamchip: v2.27 for Linux 2.6 : OV camera chip I2C driver S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2410-nand: mapped registers at c4880000 s3c2410-nand: timing: Tacls 10ns, Twrph0 40ns, Twrph1 10ns NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bi) Scanning device for bad blocks Creating 6 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00030000 : "bootloader" 0x00030000-0x00200000 : "kernel" 0x00200000-0x00400000 : "ramdisk" 0x00400000-0x03c00000 : "yaffs" 0x03c00000-0x03d00000 : "jffs2" 0x03d00000-0x03ff0000 : "data" 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 305 - usbmon: debugfs is not available s3c2410-ohci s3c2410-ohci: S3C24XX OHCI s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected Initializing USB Mass Storage driver... usbcore: registered new driver usb-storage USB Mass Storage support registered. usbcore: registered new driver hiddev usbcore: registered new driver usbhid drivers/usb/input/hid-core.c: v2.6:USB HID core driver usbcore: registered new driver dabusb drivers/usb/media/dabusb.c: v1.54:DAB-USB Interface Driver for Linux (c)1999 s3c2410_udc: version 28 Aug 2005 s3c2410_udc_probe got and enabled clock s3c2410_udc: got irq 41 mice: PS/2 mouse device common for all mice ts: Compaq touchscreen protocol output s3c2410 TouchScreen successfully loaded i2c /dev entries driver s3c2410-i2c s3c2410-i2c: slave address 0x10 s3c2410-i2c s3c2410-i2c: bus frequency set to 390 KHz s3c2410-i2c s3c2410-i2c: i2c-0: S3C I2C adapter Bluetooth: HCI USB driver ver 2.9 usbcore: registered new driver hci_usb s3c2410-sdi driver initialisation done. UDA1341 audio driver initialized NET: Registered protocol family 26 NET: Registered protocol family 2 IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 4096 (order: 2, 16384 bytes) TCP bind hash table entries: 4096 (order: 2, 16384 bytes) TCP: Hash tables configured (established 4096 bind 4096) TCP reno registered TCP bic registered NET: Registered protocol family 1 Bluetooth: L2CAP ver 2.7 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 306 - Bluetooth: L2CAP socket layer initialized Bluetooth: RFCOMM ver 1.5 Bluetooth: RFCOMM socket layer initialized Bluetooth: RFCOMM TTY layer initialized Bluetooth: BNEP (Ethernet Emulation) ver 1.2 Bluetooth: BNEP filters: protocol multicast 802.1Q VLAN Support v1.8 Ben Greear All bugs added by David S. Miller RAMDISK: Compressed image found at block 0 EXT2-fs warning: maximal mount count reached, running e2fsck is recommended VFS: Mounted root (ext2 filesystem). // 正确加载ramdisk根文件系统 Mounted devfs on /dev Freeing init memory: 420K init started: BusyBox v1.4.2 (2008-05-28 09:24:20 CST) multi-call binary Starting pid 762, console /dev/console: '/etc/init.d/rcS' Please press Enter to activate this console. Starting pid 772, console /dev/console: '/bin/sh' running /etc/profile ~ # 6. 5 NFS 文件系统实验 6. 5. 1 实验目的 ¾ 掌握 Linux 系统之间资源共享和互访方法; ¾ 掌握 NFS 服务器和客户端的安装与配置。 6. 5. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机、交叉网线; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 307 - 6. 5. 3 实验内容 ¾ 利用 rpm 命令安装软件包的方法; ¾ 安装、配置 NFS 服务器并启动; ¾ Linux 系统之间资源共享。 6. 5. 4 实验原理 NFS(Network File System)是由 SUN 公司发展, 并于 1984 年推出,NFS 是一个 RPC service, 它使我们能够达到档案的共享,它的设计是为了在不同的系统间使用,所以它的通讯协议设计与主 机及作业系统无关。当使用者想用远端档案时只要用“mount”就可把 remote 档案系统挂接在自己的 档案系统之下,使得远端的档案使用和 local 机器的档案没两样。 NFS 至少包括两个主要的部分:一台服务器,以及至少一台客户机,客户机远程地访问保存在 服务器上的数据。要让这一切运转起来,需要配置并运行几个程序。 服务器必须运行以下服务: 表 6-5-1 服务器运行的服务 服务 描述 Nfsd NFS,为来自 NFS 客户端的请求服务。 Mountd NFS 挂载服务,处理 nfsd(8)递交过来的请求 rpcbind 此服务允许 NFS 客户程序查询正在被 NFS 服务使用的端口 客户端同样运行一些进程,比如 nfsiod,nfsiod 处理来自 NFS 的请求。这是可选的,而且可以 提高性能,对于普通和正确的操作来说并不是必须的。 6. 5. 5 实验步骤 1)单击菜单应用程序->附件->终端打开终端,设置环境变量; $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh 2)安装配置 nfs 服务: $ cd $SIMPLEDIR/6.5-nfs-server $ sudo sh local-nfs-install.sh 安装脚本如下: #!/bin/bash # # E-pack-install.sh - Install NFS server. # 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 308 - # Copyright (C) 2002-2007 # Created. lusi sudo apt-get install nfs-kernel-server nfs-common portmap cd /etc/default/ sudo sed 's/OPTIONS="-i 127.0.0.1"/# OPTIONS="-i 127.0.0.1"/' portmap >portmap-temp sudo rm -f portmap sudo mv portmap-temp portmap cd /etc/ sudo sed 's/# \/srv\/nfs4\/homes gss\/krb5i(rw,sync)/\/home\/example\/nfs *(rw,sync)/' exports >exports-temp sudo rm -f exports sudo mv exports-temp exports sudo exportfs -r sudo /etc/init.d/nfs-kernel-server restart 3)拷贝 6.3 节中已经创建好的文件结构$NFSDIR 目录下: $ cp -av ../6.3-busybox/root-mini/* $NFSDIR 4)连接好交叉串口线(连接实验平台 COM2 到 PC 端串口),连接好交叉网线(连接实验平台 主板网卡接口到 PC 端网卡)。 5)点击 ubuntu 菜单栏右边的网络图标“ ”,弹出以下界面: 图 6-5-1 网络设置界面 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 309 - 6)在界面中点击解锁按钮,弹出对话框要求输入当前用户密码,正确输入后,将激活该菜单, 如下图所示: 图 6-5-2 激活网络设置界面 7)选中“有线连接”,点击属性按钮,在弹出的界面中按照下图所示设置网络 IP(其中 IP 地址 可以任意设置,必须为跟实验平台主板网卡 IP 在同一网段): 图 6-5-3 设置网络 IP 8)设置完成后点击确定按钮退出,则网络设置界面将显示如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 310 - 图 6-5-4 设置正确后的网络 9)此时运行 ubuntu 的终端,在终端中输入命令查看当前网络设置,如果正确则显示如下: $ ifconfig eth0 Link encap:以太网 硬件地址 00:16:36:3b:c5:1e inet 地址:192.192.192.190 广播:192.192.192.255 掩码:255.255.255.0 inet6 地址: fe80::216:36ff:fe3b:c51e/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1 接收数据包:773 错误:0 丢弃:0 过载:0 帧数:0 发送数据包:1722 错误:0 丢弃:0 过载:0 载波:0 碰撞:0 发送队列长度:1000 接收字节:113752 (111.0 KB) 发送字节:1978362 (1.8 MB) 中断:20 lo Link encap:本地环回 inet 地址:127.0.0.1 掩码:255.0.0.0 inet6 地址: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 跃点数:1 接收数据包:1488 错误:0 丢弃:0 过载:0 帧数:0 发送数据包:1488 错误:0 丢弃:0 过载:0 载波:0 碰撞:0 发送队列长度:0 接收字节:81664 (79.7 KB) 发送字节:81664 (79.7 KB) 10)运行 minicom,给实验平台加电,启动 vivi 并按空格键进入到 vivi 的命令行界面,修改 vivi 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 311 - 引导参数: vivi> param set linux_cmd_line "root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs ip=192.192.192.200:192.192.192.190:192.192.192.1:255.255.255.0:EDUK4:eth1:off console=ttySAC1,115200 mem=64M init=/linuxrc noinitrd" vivi>param save // 注释:输入次命令将会保存修改后的vivi参数信息到Flash,可以不保存 6)输入命令启动内核,可以看到正确引导 nfs 文件系统。 vivi>boot 7)minicom 终端将正确启动 linux: VIVI version 0.1.4 (embest@embest-laptop) (gcc version 2.95.3 20010315 (release)) #TMMU table base address = 0x33DFC000 Succeed memory mapping. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Could not found stored vivi parameters. Use default vivi parameters. Press Return to start the LINUX now, any other key for vivi type "help" for help. vivi> param set linux_cmd_line "root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs ip=192.192.192.200:192.192.192.190:192.192.192.1:255.255.255.0:EDUK4:eth1:off console=ttySAC1,115200 mem=64M init=/linuxrc noinitrd" Change linux command line to "root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs ip=192.192.192.200:192.192.192.190:192.192.192.1:255.255.255.0:EDUK4:eth1:off console=ttySAC1,115200 mem=64M init=/linuxrc noinitrd" vivi> boot Copy linux kernel from 0x00030000 to 0x30008000, size = 0x001d0000 ... size = 14 done zImage magic = 0x016f2818 Copy ramdisk from 0x00200000 to 0x30800000, size = 0x00200000 ... size = 2097152 done Setup linux parameters at 0x30000100 linux command line is: "root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs" MACH_TYPE = 193 NOW, Booting Linux...... Uncompressing Linux............................................................. Linux version 2.6.14 (embest@embest-laptop) (gcc version 3.4.5) #2 Wed Nov 26 09:308 CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T) Machine: SMDK2410 ATAG_INITRD is deprecated; please update your bootloader. 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 312 - Memory policy: ECC disabled, Data cache writeback CPU S3C2410A (id 0x32410002) S3C2410: core 200.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz S3C2410 Clocks, (c) 2004 Simtec Electronics CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on USB Control, (c) 2006 pc104 CPU0: D VIVT write-back cache CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets Built 1 zonelists Kernel command line: root=/dev/nfs nfsroot=192.192.192.190:/home/example/nfs ipd irq: clearing subpending status 00000038 irq: clearing subpending status 00000010 PID hash table entries: 512 (order: 9, 8192 bytes) timer tcon=00000000, tcnt a2c1, tcfg 00000200,00000000, usec 00001eb8 Console: colour dummy device 80x30 Dentry cache hash table entries: 16384 (order: 4, 65536 bytes) Inode-cache hash table entries: 8192 (order: 3, 32768 bytes) Memory: 64MB = 64MB total Memory: 60928KB available (2803K code, 688K data, 420K init) Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok softlockup thread 0 started up. NET: Registered protocol family 16 S3C2410: Initialising architecture SCSI subsystem initialized usbcore: registered new driver usbfs usbcore: registered new driver hub Bluetooth: Core ver 2.7 NET: Registered protocol family 31 Bluetooth: HCI device and connection manager initialized Bluetooth: HCI socket layer initialized S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics DMA channel 0 at c4800000, irq 33 DMA channel 1 at c4800040, irq 34 DMA channel 2 at c4800080, irq 35 DMA channel 3 at c48000c0, irq 36 NetWinder Floating Point Emulator V0.97 (double precision) devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 313 - devfs: boot_options: 0x1 JFFS2 version 2.2. (NAND) (C) 2001-2003 Red Hat, Inc. yaffs Nov 25 2008 16:42:48 Installing. Initializing Cryptographic API Console: switching to colour frame buffer device 80x30 fb0: s3c2410fb frame buffer device S3C2410 RTC, (c) 2004 Simtec Electronics s3c2410-rtc s3c2410-rtc: rtc disabled, re-enabling s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410 s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410 s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410 io scheduler noop registered io scheduler anticipatory registered io scheduler deadline registered io scheduler cfq registered RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize loop: loaded (max 8 devices) nbd: registered device at major 43 usbcore: registered new driver ub DM9000A eth0 found DM9000A eth1 found Linux video capture interface: v1.00 ovcamchip: v2.27 for Linux 2.6 : OV camera chip I2C driver S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2410-nand: mapped registers at c4880000 s3c2410-nand: timing: Tacls 10ns, Twrph0 40ns, Twrph1 10ns NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bi) Scanning device for bad blocks Creating 6 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00030000 : "bootloader" 0x00030000-0x00200000 : "kernel" 0x00200000-0x00400000 : "ramdisk" 0x00400000-0x03c00000 : "yaffs" 0x03c00000-0x03d00000 : "jffs2" 0x03d00000-0x03ff0000 : "data" usbmon: debugfs is not available s3c2410-ohci s3c2410-ohci: S3C24XX OHCI s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 314 - hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected Initializing USB Mass Storage driver... usbcore: registered new driver usb-storage USB Mass Storage support registered. usbcore: registered new driver hiddev usbcore: registered new driver usbhid drivers/usb/input/hid-core.c: v2.6:USB HID core driver usbcore: registered new driver dabusb drivers/usb/media/dabusb.c: v1.54:DAB-USB Interface Driver for Linux (c)1999 s3c2410_udc: version 28 Aug 2005 s3c2410_udc_probe got and enabled clock s3c2410_udc: got irq 41 mice: PS/2 mouse device common for all mice ts: Compaq touchscreen protocol output s3c2410 TouchScreen successfully loaded i2c /dev entries driver s3c2410-i2c s3c2410-i2c: slave address 0x10 s3c2410-i2c s3c2410-i2c: bus frequency set to 390 KHz s3c2410-i2c s3c2410-i2c: i2c-0: S3C I2C adapter Bluetooth: HCI USB driver ver 2.9 usbcore: registered new driver hci_usb s3c2410-sdi driver initialisation done. UDA1341 audio driver initialized NET: Registered protocol family 26 NET: Registered protocol family 2 IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 4096 (order: 2, 16384 bytes) TCP bind hash table entries: 4096 (order: 2, 16384 bytes) TCP: Hash tables configured (established 4096 bind 4096) TCP reno registered TCP bic registered NET: Registered protocol family 1 Bluetooth: L2CAP ver 2.7 Bluetooth: L2CAP socket layer initialized Bluetooth: RFCOMM ver 1.5 Bluetooth: RFCOMM socket layer initialized Bluetooth: RFCOMM TTY layer initialized 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 315 - Bluetooth: BNEP (Ethernet Emulation) ver 1.2 Bluetooth: BNEP filters: protocol multicast 802.1Q VLAN Support v1.8 Ben Greear All bugs added by David S. Miller IP-Config: Complete: device=eth1, addr=192.192.192.200, mask=255.255.255.0, gw=192.192.192.1, host=EDUK4, domain=, nis-domain=(none), bootserver=192.192.192.190, rootserver=192.192.192.190, rootpath= Looking up port of RPC 100003/2 on 192.192.192.190 Looking up port of RPC 100005/1 on 192.192.192.190 VFS: Mounted root (nfs filesystem). // 此处正确加载nfs根文件系统 Mounted devfs on /dev Freeing init memory: 420K init started: BusyBox v1.1.2 (2008.11.25-08:48+0000) multi-call binary Starting pid 763, console /dev/console: '/etc/init.d/rcS' running /etc/init.d/rcS mount tmpfs filesystem to /tmp mount ramfs filesystem to /var Please press Enter to activate this console. Starting pid 782, console /dev/console: '/bin/sh' / $ 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 316 - 下册:实践篇 实验环境构建 本教材分为基础篇和实践篇上下两册,册主要是介绍 linux 的基本知识、linux 环境的构建,基 础内核的开发等内容,下册主要是介绍基于 EduKit-IV 平台的 2410 Linux2.6 的驱动应用实验内容。 在做实验之前,先统一一下实验环境,本教材 linux 下的实验均脱离 GUI 图形界面,均为 linux 命令行下操作,因此提供了专用于实验用的精简文件系统。 在实验光盘中提供的 rootfs-eduk4-base.tgz 根文件系统映像即为实验用文件系统 (DISK3_S3C2410\02-Images\02-Linux),可参照前面章节(上册 4.6.2)更新 yaffs 文件系统映像 为 rootfs-eduk4-base.tgz。可以在 ubuntu 下操作,串口终端使用 minicom。 并修改 vivi 的启动参数为 yaffs 根文件系统引导: vivi> param reset vivi> param save 文件系统更新完毕,并修改好 vivi 启动参数后,启动实验系统,minicom 的终端串口信息如下: VIVI version 0.1.4 (embest@ embest -laptop) (gcc version 2.95.3 20010315 (release)) #0.1.4 2008 骞?11鏈?25鏃?鏄熸湡浜?16:38:19 CST MMU table base address = 0x33DFC000 Succeed memory mapping. NAND device: Manufacture ID: 0xec, Chip ID: 0x76 (Samsung K9D1208V0M) Found saved vivi parameters. Press Return to start the LINUX now, any other key for vivi Copy linux kernel from 0x00030000 to 0x30008000, size = 0x001d0000 ... size = 1900544 done zImage magic = 0x016f2818 Copy ramdisk from 0x00200000 to 0x30800000, size = 0x00200000 ... size = 2097152 done Setup linux parameters at 0x30000100 linux command line is: "noinitrd root=/dev/mtdblock/3 rootfstype=yaffs console=ttySAC1" MACH_TYPE = 193 NOW, Booting Linux...... Uncompressing 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 317 - Linux......................................................................................................................... done, booting the kernel. Linux version 2.6.14 (embest @ embest -laptop) (gcc version 3.4.5) #2 Wed Nov 26 09:30:51 CST 2008 CPU: ARM920Tid(wb) [41129200] revision 0 (ARMv4T) Machine: SMDK2410 ATAG_INITRD is deprecated; please update your bootloader. Memory policy: ECC disabled, Data cache writeback CPU S3C2410A (id 0x32410002) S3C2410: core 200.000 MHz, memory 100.000 MHz, peripheral 50.000 MHz S3C2410 Clocks, (c) 2004 Simtec Electronics CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on USB Control, (c) 2006 pc104 CPU0: D VIVT write-back cache CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets Built 1 zonelists Kernel command line: noinitrd root=/dev/mtdblock/3 rootfstype=yaffs console=ttySAC1 irq: clearing subpending status 00000010 PID hash table entries: 512 (order: 9, 8192 bytes) timer tcon=00000000, tcnt a2c1, tcfg 00000200,00000000, usec 00001eb8 Console: colour dummy device 80x30 Dentry cache hash table entries: 16384 (order: 4, 65536 bytes) Inode-cache hash table entries: 8192 (order: 3, 32768 bytes) Memory: 64MB = 64MB total Memory: 60928KB available (2803K code, 688K data, 420K init) Mount-cache hash table entries: 512 CPU: Testing write buffer coherency: ok softlockup thread 0 started up. NET: Registered protocol family 16 S3C2410: Initialising architecture SCSI subsystem initialized usbcore: registered new driver usbfs usbcore: registered new driver hub Bluetooth: Core ver 2.7 NET: Registered protocol family 31 Bluetooth: HCI device and connection manager initialized Bluetooth: HCI socket layer initialized S3C2410 DMA Driver, (c) 2003-2004 Simtec Electronics 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 318 - DMA channel 0 at c4800000, irq 33 DMA channel 1 at c4800040, irq 34 DMA channel 2 at c4800080, irq 35 DMA channel 3 at c48000c0, irq 36 NetWinder Floating Point Emulator V0.97 (double precision) devfs: 2004-01-31 Richard Gooch (rgooch@atnf.csiro.au) devfs: boot_options: 0x1 JFFS2 version 2.2. (NAND) (C) 2001-2003 Red Hat, Inc. yaffs Nov 25 2008 16:42:48 Installing. Initializing Cryptographic API Console: switching to colour frame buffer device 80x30 fb0: s3c2410fb frame buffer device S3C2410 RTC, (c) 2004 Simtec Electronics s3c2410-rtc s3c2410-rtc: rtc disabled, re-enabling s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2410 s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2410 s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2410 io scheduler noop registered io scheduler anticipatory registered io scheduler deadline registered io scheduler cfq registered RAMDISK driver initialized: 16 RAM disks of 4096K size 1024 blocksize loop: loaded (max 8 devices) nbd: registered device at major 43 usbcore: registered new driver ub DM9000A eth0 found DM9000A eth1 found Linux video capture interface: v1.00 ovcamchip: v2.27 for Linux 2.6 : OV camera chip I2C driver S3C24XX NAND Driver, (c) 2004 Simtec Electronics s3c2410-nand: mapped registers at c4880000 s3c2410-nand: timing: Tacls 10ns, Twrph0 40ns, Twrph1 10ns NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit) Scanning device for bad blocks Creating 6 MTD partitions on "NAND 64MiB 3,3V 8-bit": 0x00000000-0x00030000 : "bootloader" 0x00030000-0x00200000 : "kernel" 0x00200000-0x00400000 : "ramdisk" 0x00400000-0x03c00000 : "yaffs" 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 319 - 0x03c00000-0x03d00000 : "jffs2" 0x03d00000-0x03ff0000 : "data" usbmon: debugfs is not available s3c2410-ohci s3c2410-ohci: S3C24XX OHCI s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1 s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000 hub 1-0:1.0: USB hub found hub 1-0:1.0: 2 ports detected Initializing USB Mass Storage driver... usbcore: registered new driver usb-storage USB Mass Storage support registered. usbcore: registered new driver hiddev usbcore: registered new driver usbhid drivers/usb/input/hid-core.c: v2.6:USB HID core driver usbcore: registered new driver dabusb drivers/usb/media/dabusb.c: v1.54:DAB-USB Interface Driver for Linux (c)1999 s3c2410_udc: version 28 Aug 2005 s3c2410_udc_probe got and enabled clock s3c2410_udc: got irq 41 mice: PS/2 mouse device common for all mice ts: Compaq touchscreen protocol output s3c2410 TouchScreen successfully loaded i2c /dev entries driver s3c2410-i2c s3c2410-i2c: slave address 0x10 s3c2410-i2c s3c2410-i2c: bus frequency set to 390 KHz s3c2410-i2c s3c2410-i2c: i2c-0: S3C I2C adapter Bluetooth: HCI USB driver ver 2.9 usbcore: registered new driver hci_usb s3c2410-sdi driver initialisation done. UDA1341 audio driver initialized NET: Registered protocol family 26 NET: Registered protocol family 2 IP route cache hash table entries: 1024 (order: 0, 4096 bytes) TCP established hash table entries: 4096 (order: 2, 16384 bytes) TCP bind hash table entries: 4096 (order: 2, 16384 bytes) TCP: Hash tables configured (established 4096 bind 4096) TCP reno registered TCP bic registered 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 320 - NET: Registered protocol family 1 Bluetooth: L2CAP ver 2.7 Bluetooth: L2CAP socket layer initialized Bluetooth: RFCOMM ver 1.5 Bluetooth: RFCOMM socket layer initialized Bluetooth: RFCOMM TTY layer initialized Bluetooth: BNEP (Ethernet Emulation) ver 1.2 Bluetooth: BNEP filters: protocol multicast 802.1Q VLAN Support v1.8 Ben Greear All bugs added by David S. Miller mmc0: host does not support reading read-only switch. assuming write-enable. mmcblk0: mmc0:3594 SD02G 1921024KiB /dev/mmc/blk0: p1 Root-NFS: No NFS server available, giving up. VFS: Unable to mount root fs via NFS, trying floppy. yaffs: dev is 32505859 name is "mtdblock3" yaffs: Attempting MTD mount on 31.3, "mtdblock3" VFS: Mounted root (yaffs filesystem). Mounted devfs on /dev Freeing init memory: 420K init started: BusyBox v1.12.0 (2008-11-07 11:32:47 CST) starting pid 765, tty '': '/etc/init.d/rcS' running /etc/init.d/rcS mount tmpfs filesystem to /tmp mount ramfs filesystem to /var Please press Enter to activate this console. starting pid 787, tty '': '-/bin/sh' running /etc/profile # 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 321 - 第七章 基于 Mini2410 的应用程序开发 本章节主要介绍嵌入式 Linux 应用程序的编写,内容涵盖 HelloWorld 入门操作,文件操作,进 程和线程控制,计时器实验,tcp、udp 和 webserver 实验等,通过本章节的学习,可以了解 Linux 下基础应用程序的开发方法。 7. 1 HelloWorld 运行实验 7. 1. 1 实验目的 ¾ 掌握嵌入式应用程序的编写、编译和运行。 7. 1. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 1. 3 实验内容 ¾ 编写、编译并运行 HelloWorld 程序。 7. 1. 4 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 322 - 图 7-1-1 终端运行界面 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 7.1-helloWorld 实验例程目录: $ cd $SIMPLEDIR/7.1-helloworld 4)编译并拷贝 HelloWorld 程序: $ make $ make install $ make clean 并自动拷贝 hello 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 323 - 图 7-1-2 启动 minicom 终端 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 hello 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./hello -l./hello 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 hello 添加可执行权限: # chmod 777 hello 6)运行 hello: # ./hello HelloWorld! HelloWorld! HelloWorld! HelloWorld! HelloWorld! 7. 1. 5 参考程序 #include /****************************************************************************** ** Function name: main() ** Descriptions : printf "HelloWorld" on srceen. ** Input : NONE ** Output : NONE 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 324 - ** Created by : Chenxibing ** Created Date : 2005-12-29 **----------------------------------------------------------------------------------------------------- ** Modified by : ** Modified Date: **----------------------------------------------------------------------------------------------------- ******************************************************************************/ int main(int argc, char **argv) { int i; for (i=0; i<5; i++) { printf("HelloWorld!\n"); } return 0; } 7. 1. 6 练习题 利用所学知识自己编写一个 Linux 下简单的应用程序。 7. 2 文件操作实验 7. 2. 1 实验目的 ¾ 学习和掌握文件操作具体过程。 7. 2. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 325 - 7. 2. 3 实验内容 ¾ 利用系统提供的文件操作函数,打开、读写、关闭文件,并创建一个新的文件夹。 7. 2. 4 实验原理 标准 I/O 库以及其他的头文件,提供了一个到底层 I/O 系统调用的一个万能接口,这个库并不 是 ANSI 标准 C 的一部分,但是这个库却提供了许多复杂的函数用来处理格式化输出以及描述输入, 同时也会小心的处理设备所要求的缓冲区。 在许多方式上,可以用使用低层文件描述符的方式来使用这个库。当需要打开文件建立访问路 径时,会返回一个值,并会作为一个调用其他 I/O 库函数的参数。这个与低层文件描述符等同的被 称之类流(stream),并且是作为一个指向结构的指针 FILE*来实现的。 当一个程序启动时会自动打开三个文件流。他们是 stdin,stdout,stderr。这些是在 stdio.h 中 定义,分别代表标准输入、标准输出和标准错误输出。相对的,他们分别与低层的文件描述符 0,1, 2 相对应。 ¾ fopen fopen 库函数是低层的 open 系统调用的模拟。我们主要将它用于文件或是终端输入与输出。然 而在我们需要显示的控制设备的地方,我们最好是使用低层的系统调用,因为它们可以消除由库所 造成的潜在的不良因素,如输入/输出缓冲区。 其语法格式如下: #include FILE *fopen(const char *filename, const char *mode); fopen 打开由 filename 参数所指定的文件,并建立一个与其相关的流。mode 参数指出如何来 打开这个文件,它可以是下列字符串中的一个: “r”或“rb”:以只读方式打开; “w”或“wb”:以只写方式打开; “a”或“ab”:以读方式打开,添加到文件的结尾处; “r+”或“rb+”或“r+b”:打开更新(读和写); “w+”或“wb+”或“w+b”:打开更新,将其长度变为零; “a+”或“ab+”或“a+b”:打开更新,添加到文件结尾处; b 表明这个文件是二进制文件而不是文本文件。 在这里我们要注意,与 MS-DOS 不同,Unix 和 Linux 并不会在文本文件与二进制文件之间进行 区别。Unix 与 Linux 将所有文件看成是一样的,尤其是二进制文件。另外要注意的一点就是 mode 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 326 - 参数必须是一个字符串,而不是一个字符。我们要总是使用“r”,而绝不可以是‘r’。 如果函数调用成功,fopen 会返回一个非空的文件指针。如果失败,它会返回NULL,这是在stdio.h 中定义的。 ¾ fread fread 库函数可以用来从一个文件流中读取数据。由 stream 流中读取的数据将会放在由 prt 所 指定的数据缓冲区中。fread 和 fwrite 都处理数据记录。这些是由块的尺寸 size,读取的次数 nitems 来指定要传送的记录块的。如果成功则返回值为实际读入到数据缓冲区中的块数,而不是字节数。 在文件的结尾处,也许会返回少于 nitems 的值,包括零。 其语法格式如下: #include size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream); 与所有要写入到缓冲区中的标准 I/O 函数一样,程序员要负责分配数据空间以及检查错误。 ¾ fwrite fwrite 调用一个与 fread 相类似的函数接口,它将会从指定的数据区读取数据记录并写入到输出 流中。它的返回值为成功写入的记录数。 其语法格式如下: #include size_t fwrite (const void *ptr, size_t size, size_t nitems, FILE *stream); 在这里我们要注意就是我们并不推荐在使用结构数据时使用 fread 与 fwrite。一部分的原因就是 因为用 fwrite 写入的文件潜在的存在着在不同的机器间不兼容的问题。 ¾ fclose fclose 库函数关闭指定的文件流,并将所有未写入的数据写入文件中。使用 fclose 是相当重要 的,因为 stdio 库会缓存数据。如果程序需要确定已经完整的写入了所有的数据,这时就应调用 fclose。 然而,当一个程序正常结束时,fclose 就自动调用,从而关闭所有仍然打开的文件流。当然,在这 样的情况下,我们就没有机会来检查由 fclose 报告的错误。与文件描述符所有的限制一样,可用的 流数目也是有限制的。实际的限制是 FOPEN_MAX,这是在 stdio.h 中定义,而且至少为 8 个。 其语法格式如下: #include int fclose(FILE *stream); ¾ mkdir 可以使用 mkdir 和 rmdir 来创建和移除目录。 其语法如下: #include int mkdir(const char *path, mode_t mode); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 327 - mkdir 系统调用可以用来创建目录,而这是与 mkdir 程序相等同的。mkdir 以 path 为名字创建 一个新的目录。目录的权限是由参数 mode 来指定的,而这也与 open 系统调用中的 O_CREAT 的选 项是一样的,而且这也是要受到 umask 的影响。 7. 2. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端。 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 file 工作目录: $ cd $SIMPLEDIR/7.2-file 4)编译并拷贝 File 程序: $ make $ make install $ make clean 并自动拷贝 file 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 file 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./file -l./file 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 file 添加可执行权限: # chmod 777 file 6)运行 file,并查看文件内容: # ./file 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 328 - 17 byte data has written to linux.txt Folder linux created success! # ls file linux linux.txt # cat linux.txt Hello ARM Linux! 7. 2. 6 参考程序 #include #include int main(void) { FILE *fp; int num; int folder; char a[] = "Hello ARM Linux!"; fp = fopen("/tmp/Linux.txt","w+"); // 打开文件/tmp/Linux.txt if(fp == NULL) { printf("\nFail to open Linux.txt!\n"); exit(-1); } num = fwrite(a, sizeof(a), 1, fp); // 将字符串"Hello ARM Linux!"写入到文件中 printf("%d byte data has written to Linux.txt\n", num*sizeof(a)); folder = mkdir("/tmp/Linux", 1); // 创建目录"/tmp/Linux" if(folder == -1) { printf("\n Fail to create folder Linux!\nIt has existed or the path is error!\n"); exit(-1); } printf("Folder Linux created success!\n"); close(fp); // 关闭文件 return 0; } 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 329 - 7. 2. 7 练习题 利用所学的知识自己建立一个目录,在该目录中创建一个文本文件,并对该文本文件进行读写 操作。 7. 3 进程控制实验 7. 3. 1 实验目的 ¾ 通过实验理解 Linux 下进程的概念; ¾ 通过该实验掌握进程的编程方法。 7. 3. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 3. 3 实验内容 ¾ 编写一个程序,父进程创建子进程,并得到相关信息并打印之后退出。 7. 3. 4 实验原理 1. Linux 进程基础知识 进程作为构成系统的基本细胞,不仅是系统内部独立运行的实体,而且是独立竞争资源的基本 实体。了解进程的本质,对于理解、描述和设计操作系统有着极为重要的意义。了解进程的活动、 状态,也有利于编制复杂程序。 首先我们看看进程的定义,进程是一个具有独立功能的程序关于某个数据集合的一次可以并发 执行的运行活动,是处于活动状态的计算机程序。 1)进程状态和状态转换 现在我们来看看,进程在生存周期中的各种状态及状态的转换。下面是 Linux 系统的进程状态 模型的各种状态: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 330 - ¾ 用户状态:进程在用户状态下运行的状态; ¾ 内核状态:进程在内核状态下运行的状态; ¾ 内存中就绪:进程没有执行,但处于就绪状态,只要内核调度它,就可以执行; ¾ 内存中睡眠:进程正在睡眠并且进程存储在内存中,没有被交换到 SWAP 设备; ¾ 就绪且换出:进程处于就绪状态,但是必须把它换入内存,内核才能再次调度它进行运行; ¾ 睡眠且换出:进程正在睡眠,且被换出内存; ¾ 被抢先:进程从内核状态返回用户状态时,内核抢先于它,做了上下文切换,调度了另一 个进程。原先这个进程就处于被抢先状态; ¾ 创建状态:进程刚被创建。该进程存在,但既不是就绪状态,也不是睡眠状态。这个状态 是除了进程 0 以外的所有进程的最初状态; ¾ 僵死状态(zombie):进程调用 exit 结束,进程不再存在,但在进程表项中仍有纪录,该 纪录可由父进程收集。 现在我们从进程的创建到退出来看看进程的状态转化。需要说明的是,进程在它的生命周期里 并不一定要经历所有的状态。 首先父进程通过系统调用 fork()来创建子进程,调用 fork()时,子进程首先处于创建态,fork() 调用为子进程配置好内核数据结构和子进程私有数据结构后,子进程就要进入就绪态 3 或 5,即在 内存中就绪,或者因为内存不够,而导致在 SWAP 设备中就绪。 假设进程在内存中就绪,这时子进程就可以被内核调度程序调度上 CPU 运行。内核调度该进程 进入内核状态,再由内核状态返回用户状态执行。该进程在用户状态运行一定时间后,又会被调度 程序所调度而进入内核状态,由此转入就绪态。有时进程在用户状态运行时,也会因为需要内核服 务,使用系统调用而进入内核状态,服务完毕,会由内核状态转回用户状态。要注意的是,进程在 从内核状态向用户状态返回时可能被抢占,进入状态 7,这是由于有优先级更高的进程急需使用 CPU, 不能等到下一次调度时机,从而造成抢占。 进程还会因为请求的资源不能得到满足,进入睡眠状态,直到它请求的资源被释放,才会被内 核唤醒而进入就绪态。如果进程在内存中睡眠时,内存不足,当进程睡眠时间达到一个阀值,进程 会被 SWAP 出内存,使得进程在 SWAP 设备上睡眠。这种状况同样可能发生在就绪的进程上。 进程调用 exit 系统调用,将使得进程进入内核状态,执行 exit 调用,进入僵死状态而结束。以 上就是进程状态转换的简单描述。 进程的上下文是由用户级上下文、寄存器上下文以及系统级上下文组成。主要内容是该进程用 户空间内容、寄存器内容以及与该进程有关的内核数据结构。当系统收到一个中断,执行系统调用 或内核做上下文切换时,就会保存进程的上下文。一个进程是在它的上下文中运行的,若要调度进 程,就要进行上下文切换。内核在四种情况下允许发生上下文切换: ¾ 当进程自己进入睡眠时; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 331 - ¾ 当进程执行完系统调用要返回用户状态,但发现该进程不是最有资格运行的进程时; ¾ 当内核完成中断处理后要返回用户状态,但发现该进程不是最有资格运行的进程时; ¾ 当进程退出(执行系统调用 exit 后)时。 有时内核要求必须终止当前的执行,立即从先前保存的上下文处执行。这可由 setjmp 和 longjmp 实现,setjmp 将保存的上下文存入进程自身的数据空间(u 区)中,并继续在当前的上下文中执行, 一旦碰到了 longjmp,内核就从该进程的 u 区,取出先前保存的上下文,并恢复该进程的上下文为 原先保存的。这时内核将使得进程从 setjmp 处执行,并给 setjmp 返回 1。 进程因等待资源或其他原因,进入睡眠态是通过内核的 sleep 算法。该算法与本章后面要讲到 的 sleep 函数是两个概念。算法 sleep 记录进程原先的处理机优先级,置进程为睡眠态,将进程放入 睡眠队列,记录睡眠的原因,给该进程进行上下文切换。内核通过算法 wakeup 来唤醒进程,如某 资源被释放,则唤醒所有因等待该资源而进入睡眠的进程。如果进程睡眠在一个可以接收软中断信 号(signal)的级别上,则进程的睡眠可由软中断信号的到来而被唤醒。 2)进程控制 现在我们开始讲述一下进程的控制,主要介绍内核对 fork、exec、wait、exit 的处理过程,为学 习这些调用打下概念上的基础,并介绍系统启动(boot)的过程以及进程 init 的作用。 在 Linux 系统中,用户创建一个进程的唯一方法就是使用系统调用 fork。内核为完成系统调用 fork 要进行几步操作。第一步,为新进程在进程表中分配一个表项。系统对一个用户可以同时运行 的进程数是有限制的,对超级用户没有该限制,但也不能超过进程表的最大表项的数目。第二步, 给子进程一个唯一的进程标识号(PID)。该进程标识号其实就是该表项在进程表中的索引号。第三 步,复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是从父进程处 拷贝的。所以子进程拥有与父进程一样的 uid、euid、gid、用于计算优先权的 nice 值、当前目录、 当前根、用户文件描述符表等。第四步,把与父进程相连的文件表和索引节点表的引用数加 1。这 些文件自动地与该子进程相连。第五步,内核为子进程创建用户级上下文。内核为子进程的 u 区及 辅助页表分配内存,并复制父进程的区内容。这样生成的是进程的静态部分。第六步,生成进程的 动态部分,内核复制父进程的上下文的第一层,即寄存器上下文和内核栈,内核再为子进程虚设一 个上下文层,这是为了子进程能“恢复”它的上下文。这时,该调用会对父进程返回子进程的 pid, 对子进程返回 0。 Linux 系统的系统调用 exit,是进程用来终止执行时调用的。进程发出该调用,内核就会释放该 进程所占的资源,释放进程上下文所占的内存空间,保留进程表项,将进程表项中纪录进程状态的 字段设为僵死状态。内核在进程收到不可捕捉的信号时,会从内核内部调用 exit,使得进程退出。 父进程通过 wait 得到其子进程的进程表项中纪录的计时数据,并释放进程表项。最后,内核使得进 程 1(init 进程)接收终止执行的进程的所有子进程。如果有子进程僵死,就向 init 进程发出一个 SIGCHLD 的软中断信号。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 332 - 一个进程通过调用 wait 来与它的子进程同步,如果发出调用的进程没有子进程则返回一个错误, 如果找到一个僵死的子进程就取子进程的 PID 及退出时提供给父进程的参数。如果有子进程,但没 有僵死的子进程,发出调用的进程就睡眠在一个可中断的级别上,直到收到一个子进程僵死 (SIGCLD)的信号或其他信号。 进程控制的另一个主要内容就是对其他程序引用。该功能是通过系统调用 exec 来实现的,该调 用将一个可执行的程序文件读入,代替发出调用的进程执行。内核读入程序文件的正文,清除原先 进程的数据区,清除原先用户软中断信号处理函数的地址,当 exec 调用返回时,进程执行新的正文。 一个系统启动的过程,也称作是自举的过程。该过程因机器的不同而有所差异。但该过程的目 的对所有机器都相同:将操作系统装入内存并开始执行。计算机先由硬件将引导块的内容读到内存 并执行,自举块的程序将内核从文件系统中装入内存,并将控制转入内核的入口,内核开始运行。 内核首先初始化它的数据结构,并将根文件系统安装到根“/”,为进程 0 形成执行环境。设置好进 程 0 的环境后,内核便作为进程 0 开始执行,并调用系统调用 fork。因为这时进程 0 运行在内核状 态,所以新的进程也运行在内核状态。新的进程(进程 1)创建自己的用户级上下文,设置并保存 好用户寄存器上下文。这时,进程 1 就从内核状态返回用户状态执行从内核拷贝的代码(exec),并 调用 exec 执行/sbin/init 程序。进程 1 通常称为初始化进程,它负责初始化新的进程。 进程 init 除了产生新的进程外,还负责一些使用户在系统上注册的进程。例如,进程 init 一般 要产生一些 getty 的子进程来监视终端。如果一个终端被打开,getty 子进程就要求在这个终端上执 行一个注册的过程,当成功注册后,执行一个 shell 程序,来使得用户与系统交互。同时,进程 init 执行系统调用 wait 来监视子进程的死亡,以及由于父进程的退出而产生的孤儿进程的移交。以上是 系统启动和进程 init 的一个粗略的模型。 3)进程调度的概念 Linux 系统是一个分时系统,内核给每个进程分一个时间片,该进程的时间片用完就会调度另一 个进程执行。Linux 系统上的调度程序属于多级反馈循环调度。该调度方法是,给一个进程分一个时 间片,抢先一个运行超过时间片的进程,并把进程反馈到若干优先级队列中的一个队列。进程在执 行完之前,要经过这样多次反馈循环。 进程调度分成两个部分,一个是调度的时机,即什么时候调度;一个是调度的算法,即如何调 度和调度哪个进程。我们先来看看调度的算法,假设目前内核要求进行调度,调度程序从“在内存 中就绪”和“被抢先”状态的进程中选择一个优先权最高的进程,如果有若干优先。 4)多进程编程 在传统的 Unix 环境下,有两个基本的操作用于创建和修改进程:函数 fork()用来创建一个新的 进程,该进程几乎是当前进程的一个完全拷贝;函数族 exec()用来启动另外的进程以取代当前运行 的进程。Linux 的进程控制和传统的 Unix 进程控制基本一致,只在一些细节的地方有些区别,例如 在 Linux 系统中调用 vfork 和 fork 完全相同,而在有些版本的 Unix 系统中,vfork 调用有不同的功能。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 333 - 由于这些差别几乎不影响我们大多数的编程,在这里我们不予考虑。 fork 在英文中是“分叉”的意思。为什么取这个名字呢?因为一个进程在运行中,如果使 用了 fork,就产生了另一个进程,于是进程就“分叉”了,所以这个名字取得很形象。下面就看看 如何具体使用 fork,这段程序演示了使用 fork 的基本框架: void main() { int i; if ( fork() == 0 ) { // 子进程程序 for ( i = 1; i <1000; i ++ ) printf("This is child process\n"); } else { // 父进程程序 for ( i = 1; i <1000; i ++ ) printf("This is process process\n"); } } 程序运行后,你就能看到屏幕上交替出现子进程与父进程各打印出的一千条信息了。如果程序 还在运行中,你用 ps 命令就能看到系统中有两个它在运行了。 那么调用这个 fork 函数时发生了什么呢?fork 函数启动一个新的进程,前面我们说过,这个进 程几乎是当前进程的一个拷贝:子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和 数据段。这样,父进程的所有数据都可以留给子进程。但是,子进程一旦开始运行,虽然它继承了 父进程的一切数据,但实际上数据却已经分开,相互之间不再有影响了,也就是说,它们之间不再 共享任何数据了。它们再要交互信息时,只有通过进程间通信来实现,这将是我们下面的内容。既 然它们如此相象,系统如何来区分它们呢?这是由函数的返回值来决定的。对于父进程,fork 函数 返回了子程序的进程号,而对于子程序,fork 函数则返回零。在操作系统中,我们用 ps 函数就可以 看到不同的进程号,对父进程而言,它的进程号是由比它更低层的系统调用赋予的,而对于子进程 而言,它的进程号即是 fork 函数对父进程的返回值。在程序设计中,父进程和子进程都要调用函数 fork()下面的代码,而我们就是利用 fork()函数对父子进程的不同返回值用 if...else...语句来实现让父 子进程完成不同的功能,正如我们上面举的例子一样。我们看到,上面例子执行时两条信息是交互 无规则的打印出来的,这是父子进程独立执行的结果,虽然我们的代码似乎和串行的代码没有什么 区别。 读者也许会问,如果一个大程序在运行中,它的数据段和堆栈都很大,一次 fork 就要复制一次, 那么 fork 的系统开销不是很大吗?其实 UNIX 自有其解决的办法,大家知道,一般 CPU 都是以“页” 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 334 - 为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,像 Intel 的 CPU,其一页在通常 情况下是 4086 字节大小,而无论是数据段还是堆栈段都是由许多“页”构成的,fork 函数复制这两 个段,只是“逻辑”上的,并非“物理”上的,也就是说,实际执行 fork 时,物理空间上两个进程 的数据段和堆栈段都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有 了区别,系统就将有区别的“页”从物理上也分开。系统在空间上的开销就可以达到最小。 下面演示一个足以“搞死”Linux 的小程序,其源代码非常简单: void main() { for( ; ; ) fork(); } 这个程序什么也不做,就是死循环地 fork,其结果是程序不断产生进程,而这些进程又不断产 生新的进程,很快,系统的进程就满了,系统就被这么多不断产生的进程“撑死了”。当然只要系统 管理员预先给每个用户设置可运行的最大进程数,这个恶意的程序就完成不了企图了。 5)进程间通信 首先,进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递 信息。事实上,在很多应用系统里,都使用了这种方法。但一般说来,进程间通信(IPC:Inter Process Communication)不包括这种似乎比较低级的通信方法。Unix 系统中实现进程间通信的方法很多, 而且不幸的是,极少方法能在所有的 Unix 系统中进行移植(唯一一种是半双工的管道,这也是最原 始的一种通信方式)。而 Linux 作为一种新兴的操作系统,几乎支持所有的 Unix 下常用的进程间通 信方法:管道、消息队列、共享内存、信号量、套接口等等。下面我们将逐一介绍。 ¾ 管道 管道是进程间通信中最古老的方式,它包括无名管道和有名管道两种,前者用于父进程和子进 程间的通信,后者用于运行于同一台机器上的任意两个进程间的通信。 无名管道由 pipe()函数创建: #include int pipe(int filedes[2]); 参数 filedes 返回两个文件描述符:filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的 输出是 filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。 #define INPUT 0 #define OUTPUT 1 void main() { int file_descriptors[2]; // 定义子进程号 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 335 - pid_t pid; char buf[256]; int returned_count; // 创建无名管道 pipe(file_descriptors); // 创建子进程 if((pid = fork()) == -1) { printf("Error in fork\n"); exit(1); } // 执行子进程 if(pid == 0) { printf("in the spawned (child) process...\n"); // 子进程向父进程写数据,关闭管道的读端 close(file_descriptors[INPUT]); write(file_descriptors[OUTPUT], "test data", strlen("test data")); exit(0); } else { // 执行父进程 printf("in the spawning (parent) process...\n"); // 父进程从管道读取子进程写的数据,关闭管道的写端 close(file_descriptors[OUTPUT]); returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); printf("%d bytes of data received from spawned process: %s\n", returned_count, buf); } } 在 Linux 系统下,有名管道可由两种方式创建:命令行方式 mknod 系统调用和函数 mkfifo。下 面的两种途径都在当前目录下生成了一个名为 myfifo 的有名管道: 方式一:mkfifo("myfifo","rw"); 方式二:mknod myfifo p。 生成了有名管道后,就可以使用一般的文件 I/O 函数如 open、close、read、write 等来对它进 行操作。 ¾ 消息队列 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 336 - 消息队列用于运行于同一台机器上的进程间通信,它和管道很相似。事实上,它是一种正逐渐 被淘汰的通信方式,我们可以用流管道或者套接口的方式来取代它。所以,我们对此方式也不再解 释,也建议读者忽略这种方式。 ¾ 共享内存 共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复 制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。得到共享内存有两种 方式:映射/dev/mem 设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并 不常用,因为它控制存取的将是实际的物理内存,在 Linux 系统下,这只有通过限制 Linux 系统存取 的内存才可以做到,这当然不太实际。常用的方式是通过 shmXXX 函数族来实现利用共享内存进行 存储的。 首先要用的函数是 shmget,它获得一个共享存储标识符。 #include #include #include int shmget(key_t key, int size, int flag); 这个函数有点类似大家熟悉的 malloc 函数,系统按照请求分配 size 大小的内存用作共享内存。 Linux 系统内核中每个 IPC 结构都有一个非负整数的标识符,这样对一个消息队列发送消息时只要引 用标识符就可以了。这个标识符是内核由 IPC 结构的关键字得到的,这个关键字,就是上面第一个 函数的 key。数据类型 key_t 是在头文件 sys/types.h 中定义的,它是一个长整形的数据。在我们后 面的章节中,还会碰到这个关键字。 当共享内存创建后,其余进程可以调用 shmat()将其连接到自身的地址空间中。 void *shmat(int shmid, void *addr, int flag); shmid 为 shmget 函数返回的共享存储标识符,addr 和 flag 参数决定了以什么方式来确定连接 的地址,函数的返回值即是该进程数据段所连接的实际地址,进程可以对此进程进行读写操作。 使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数 据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存储数据存取的同步,另外, 可以通过使用 shmctl 函数设置共享存储内存的某些标志位如 SHM_LOCK、SHM_UNLOCK 等来实现。 ¾ 信号量 信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共 享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存) 的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作: (1)测试控制该资源的信号量; (2)若此信号量的值为正,则允许进行使用该资源,进程将进号量减 1; (3)若此信号量为 0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于 0,进程 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 337 - 被唤醒,转入步骤(1); (4)当进程不再使用一个信号量控制的资源时,信号量值加 1。如果此时有进程正在睡眠等待 此信号量,则唤醒此进程。 维护信号量状态的是 Linux 内核操作系统而不是用户进程。我们可以从头文件 /usr/src/Linux/include/Linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一 个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是 semget,用以获得一 个信号量 ID。 #include #include #include int semget(key_t key, int nsems, int flag); key 是前面讲过的 IPC 结构的关键字,它将来决定是创建新的信号量集合,还是引用一个现有 的信号量集合。nsems 是该集合中的信号量数。如果是创建新集合(一般在服务器中),则必须指定 nsems;如果是引用一个现有的信号量集合(一般在客户机中)则将 nsems 指定为 0。 semctl 函数用来对信号量进行操作。 int semctl(int semid, int semnum, int cmd, union semun arg); 不同的操作是通过 cmd 参数来实现的,在头文件 sem.h 中定义了 7 种不同的操作,实际编程时 可以参照使用。 semop 函数自动执行信号量集合上的操作数组。 int semop(int semid, struct sembuf semoparray[], size_t nops); semoparray 是一个指针,它指向一个信号量操作数组。nops 规定该数组中操作的数量。 下面,我们看一个具体的例子,它创建一个特定的 IPC 结构的关键字和一个信号量,建立此信 号量的索引,修改索引指向的信号量的值,最后我们清除信号量。在下面的代码中,函数 ftok 生成 我们上文所说的唯一的 IPC 关键字。 #include #include #include #include void main() { key_t unique_key; // 定义一个IPC关键字 int id; struct sembuf lock_it; union semun options; int i; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 338 - unique_key = ftok(".", 'a'); // 生成关键字,字符'a'是一个随机种子 // 创建一个新的信号量集合 id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666); printf("semaphore id=%d\n", id); options.val = 1; // 设置变量值 semctl(id, 0, SETVAL, options); // 设置索引 0 的信号量 // 打印出信号量的值 i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i); // 下面重新设置信号量 lock_it.sem_num = 0; // 设置哪个信号量 lock_it.sem_op = -1; // 定义操作 lock_it.sem_flg = IPC_NOWAIT; //操作方式 if (semop(id, &lock_it, 1) == -1) { printf("can not lock semaphore.\n"); exit(1); } i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i); // 清除信号量 semctl(id, 0, IPC_RMID, 0); } ¾ 套接口 套接口(socket)编程是实现 Linux 系统和其他大多数操作系统中进程间通信的主要方式之一。 我们熟知的 WWW 服务、FTP 服务、TELNET 服务等都是基于套接口编程来实现的。除了在异地的 计算机进程间以外,套接口同样适用于本地同一台计算机内部的进程间通信。 2. Linux 的进程和 Win32 的进程/线程比较 熟悉 WIN32 编程的人一定知道,WIN32 的进程管理方式与 Linux 上有着很大区别,那么 Linux 和 WIN32 在这里究竟有着什么区别呢? WIN32 里的进程/线程是继承自 OS/2 的。在 WIN32 里,“进程”是指一个程序,而“线程”是 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 339 - 一个“进程”里的一个执行“线索”。从核心上讲,WIN32 的多进程与 Linux 并无多大的区别,在 WIN32 里的线程才相当于 Linux 的进程,是一个实际正在执行的代码。但是,WIN32 里同一个进程 里各个线程之间是共享数据段的。这才是与 Linux 的进程最大的不同。 在 WIN32 下,使用 CreateThread 函数创建线程,与 Linux 下创建进程不同,WIN32 线程不是 从创建处开始运行的,而是由 CreateThread 指定一个函数,线程就从那个函数处开始运行。此程序 同前面的 UNIX 程序一样,由两个线程各打印 1000 条信息。threadID 是子线程的线程号,另外,全 局变量 g 是子线程与父线程共享的,这就是与 Linux 最大的不同之处。大家可以看出,WIN32 的进 程/线程要比 Linux 复杂,在 Linux 要实现类似 WIN32 的线程并不难,只要 fork 以后,让子进程调 用 ThreadProc 函数,并且为全局变量开设共享数据区就行了,但在 WIN32 下就无法实现类似 fork 的功能了。所以现在 WIN32 下的 C 语言编译器所提供的库函数虽然已经能兼容大多数 Linux/UNIX 的库函数,但却仍无法实现 fork。 对于多任务系统,共享数据区是必要的,但也是一个容易引起混乱的问题,在 WIN32 下,一个 程序员很容易忘记线程之间的数据是共享的这一情况,一个线程修改过一个变量后,另一个线程却 又修改了它,结果引起程序出问题。但在 Linux 下,由于变量本来并不共享,而由程序员来显式地 指定要共享的数据,使程序变得更清晰与安全。 至于 WIN32 的“进程”概念,其含义则是“应用程序”,也就是相当于 UNIX 下的 exec 了。 Linux 也有自己的多线程函数 pthread,它既不同于 Linux 的进程,也不同于 WIN32 下的进程, 关于 pthread 的介绍请看其线程相关分析与介绍。 3. 关键函数分析 child = fork(); 创建新进程,如果返回为 0 表示子进程,否则为父进程。 printf("\tchild pid is %d\n", getpid()); printf("\tchild ppid is %d\n", getppid()); getpid 返回当前进程标识,getppid 返回父进程标识。 waitpid 函数原型: #include #include pid_t waitpid(pid_t pid,int *status,int options); 从本质上讲,系统调用 waitpid 和 wait 的作用是完全相同的,但 waitpid 多出了两个可由用户控 制的参数 pid 和 options,从而为我们编程提供了另一种更灵活的方式。下面我们就来详细介绍一下 这两个参数: ¾ pid 从参数的名字 pid 和类型 pid_t 中就可以看出,这里需要的是一个进程 ID。但 当 pid 取不同的值 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 340 - 时,在这里有不同的意义。 1. pid>0 时,只等待进程 ID 等于 pid 的子进程,不管其它已经有多少子进程运行结束退出了, 只要指定的子进程还没有结束,waitpid 就会一直等下去。 2. pid=-1 时,等待任何一个子进程退出,没有任何限制,此时 waitpid 和 wait 的作用一模一样。 3. pid=0 时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid 不会对它做任何理睬。 4. pid<-1 时,等待一个指定进程组中的任何子进程,这个进程组的 ID 等于 pid 的绝对值。 ¾ options options 提供了一些额外的选项来控制 waitpid,目前在 Linux 中只支持 WNOHANG 和 WUNTRACED 两个选项,这是两个常数,可以用“|”运算符把它们连接起来使用,比如: ret=waitpid(-1,NULL,WNOHANG|WUNTRACED); 如果我们不想使用它们,也可以把 options 设为 0,如: ret=waitpid(-1,NULL,0); 如果使用了 WNOHANG 参数调用 waitpid,即使没有子进程退出,它也会立即返回,不会像 wait 那样永远等下去。 waitpid 的返回值比 wait 稍微复杂一些,一共有 3 种情况: 1. 当正常返回的时候,waitpid 返回收集到的子进程的进程 ID; 2. 如果设置了选项 WNOHANG,而调用中 waitpid 发现没有已退出的子进程可收集,返回 0; 3. 如果调用中出错,则返回-1,这时 errno 会被设置成相应的值以指示错误所在。 当 pid 所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid 就会出错返 回,这时 errno 被设置为 ECHILD。 7. 3. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端。 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 process 工作目录: $ cd $SIMPLEDIR/7.3-process 4)编译并拷贝 process 程序: $ make $ make install 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 341 - $ make clean 并自动拷贝 process 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 process 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./process -l./process 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 process 添加可执行权限: # chmod 777 process 6)运行 process: # ./process Try to create new process. This is child. child pid is 853 child ppid is 852 This is parent. parent pid is 852 parent ppid is 847 child exited with 0 7. 3. 6 参考程序 #include #include #include #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 342 - int main(int argc, char **argv) { int val, stat; pid_t child; printf("\nTry to create new process.\n"); child = fork(); switch(child) { case -1: // 出错 perror("fork.\n"); exit(EXIT_FAILURE); case 0: // 子进程 printf("This is child.\n"); printf("\tchild pid is %d\n", getpid()); printf("\tchild ppid is %d\n", getppid()); exit(EXIT_SUCCESS); default: // 父进程 waitpid(child, &stat, 0); printf("This is parent.\n"); printf("\tparent pid is %d\n", getpid()); printf("\tparent ppid is %d\n", getppid()); printf("\tchild exited with %d\n", stat); } exit(EXIT_SUCCESS); } 7. 3. 7 练习题 编写一个程序,实现一个父进程创建多个子进程,并与之进行数据通信。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 343 - 7. 4 线程控制实验 7. 4. 1 实验目的 ¾ 通过实验理解 Linux 下线程的概念; ¾ 通过该实验掌握线程的编程方法。 7. 4. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 4. 3 实验内容 ¾ 编写一个程序,程序里创建两个线程,并且实时输出两个线程的运行状态。 7. 4. 4 实验原理 1.Linux 线程基础知识 线程(thread)技术早在 60 年代就被提出,但真正应用多线程到操作系统中去,是在 80 年代 中期,solaris 是这方面的佼佼者。传统的 Unix 也支持线程的概念,但是在一个进程(process)中 只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持, 包括 Windows/NT,当然,也包括 Linux。 为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么样的系统应 该选用多线程?我们首先必须回答这些问题。 使用多线程的理由之一是和进程相比,它是一种非常“节俭”的多任务操作方式。我们知道, 在 Linux 系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的 代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程, 它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一 个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据 统计,总的说来,一个进程的开销大约是一个线程开销的 30 倍左右,当然,在具体的系统上,这个 数据可能会有较大的区别。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 344 - 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间, 要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由 于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快 捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有 的子程序中声明为 static 的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程 序时最需要注意的地方。 除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然 有以下的优点: 1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都 会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操 作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。 2) 使多 CPU 系统更加有效。操作系统会保证当线程数不大于 CPU 数目时,不同的线程运行于 不同的 CPU 上。 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运 行部分,这样的程序会利于理解和修改。 下面我们先来尝试编写一个简单的多线程程序。 Linux 系统下的多线程遵循 POSIX 线程接口,称为 pthread。编写 Linux 下的多线程程序,需要 使用头文件 pthread.h,连接时需要使用库 libpthread.a。顺便说一下,Linux 下 pthread 的实现是通 过系统调用 clone()来实现的。clone()是Linux 所特有的系统调用,它的使用方式类似 fork,关于 clone() 的详细情况,有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 example.c。 #include #include void thread(void) { int i; for(i=0;i<3;i++) printf("This is a pthread.\n"); } int main(void) { pthread_t id; int i,ret; ret=pthread_create(&id,NULL,(void *) thread,NULL); if(ret!=0) { 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 345 - printf ("Create pthread error!\n"); exit (1); } for(i=0;i<3;i++) printf("This is the main process.\n"); pthread_join(id,NULL); return (0); } 我们编译此程序: gcc example.c -lpthread -o example 运行 example,我们得到如下结果: This is the main process. This is a pthread. This is the main process. This is the main process. This is a pthread. This is a pthread. 再次运行,我们可能得到如下结果: This is a pthread. This is the main process. This is a pthread. This is the main process. This is a pthread. This is the main process. 前后两次结果不一样,这是两个线程争夺 CPU 资源的结果。上面的示例中,我们使用到了两个 函数,pthread_create 和 pthread_join,并声明了一个 pthread_t 型的变量。 pthread_t 在头文件/usr/include/bits/pthreadtypes.h 中定义: typedef unsigned long int pthread_t; 它是一个线程的标识符。函数 pthread_create 用来创建一个线程,它的原型为: extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, void *(*__start_routine) (void *), void *__arg)); 第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行 函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数 thread 不需要参数,所以最 后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程 成功时,函数返回 0,若不为 0 则说明创建线程失败,常见的错误返回代码为 EAGAIN 和 EINVAL。 前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 346 - 法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下 一行代码。下面介绍线程创建属性: pthread_create()中的 attr 参数是一个结构指针,结构中的元素分别对应着新线程的运行属性。 如果创建线程时使用该参数,则必须先初始化该参数 pthread_attr_init(&attr); 然后再设置参数中各元素。该结构中定义在/usr/include/bits/pthreadtypes.h 中: typedef struct __pthread_attr_s { int __detachstate; int __schedpolicy; struct __sched_param __schedparam; int __inheritsched; int __scope; size_t __guardsize; int __stackaddr_set; void *__stackaddr; size_t __stacksize; } pthread_attr_t; 其中各元素含义: __detachstate,表示新线程是否与进程中其他线程脱离同步,如果置位则新线程不能用 pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为 PTHREAD_CREATE_JOINABLE 状态。这个属性也可以在线程创建并运行以后用 pthread_detach()来设置,而一旦设置为 PTHREAD_CREATE_DETACH 状态(不论是创建时设置还是运行时设置)则不能再恢复到 PTHREAD_CREATE_JOINABLE 状态。该参数涉及函数: int pthread_attr_setdetachstate (pthread_attr_t *__attr,int __detachstate); int pthread_attr_getdetachstate (__const pthread_attr_t *__attr,int *__detachstate); __detachstate 可取值:PTHREAD_CREATE_JOINABLE, PTHREAD_CREATE_DETACH。 __schedpolicy,表示新线程的调度策略,主要包括 SCHED_OTHER(正常、非实时)、SCHED_RR (实时、轮转法)和 SCHED_FIFO(实时、先入先出)三种,缺省为 SCHED_OTHER,后两种调度 策略仅对超级用户有效。运行时可以用过 pthread_setschedparam()来改变。该参数涉及函数: int pthread_attr_setschedpolicy (pthread_attr_t *__attr, int __policy); int pthread_attr_getschedpolicy (__const pthread_attr_t *__restrict__attr, int *__restrict __policy); __schedparam,一个 struct sched_param 结构,其中有一个 sched_priority 整型变量表示线 程的运行优先级。这个参数仅当调度策略为实时(即 SCHED_RR 或 SCHED_FIFO)时才有效,并可 以在运行时通过 pthread_setschedparam()函数来改变,缺省为 0。该参数涉及函数: nt pthread_attr_setschedparam (pthread_attr_t *__restrict __attr,__const struct sched_param *__restrict __param); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 347 - int pthread_attr_getschedparam (__const pthread_attr_t *__restrict__attr,struct sched_param *__restrict __param); __inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED 和 PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即 attr 中的值), 而后者表示继承调用者线程的值。缺省为 PTHREAD_EXPLICIT_SCHED。 int pthread_attr_setinheritsched (pthread_attr_t *__attr,int __inherit); int pthread_attr_getinheritsched (__const pthread_attr_t *__restrict__attr, int *__restrict __inherit); __scope,表示线程间竞争 CPU 的范围,也就是说线程优先级的有效范围。POSIX 的标准中定 义了两个值:PTHREAD_SCOPE_SYSTEM 和 PTHREAD_SCOPE_PROCESS,前者表示与系统中所有 线程一起竞争 CPU 时间,后者表示仅与同进程中的线程竞争 CPU。该参数涉及函数: int pthread_attr_setscope (pthread_attr_t *__attr, int __scope); int pthread_attr_getscope (__const pthread_attr_t *__restrict __attr,int *__restrict __scope); pthread_attr_t 结构中还有一些值,但不使用 pthread_create()来设置。 函数 pthread_join 用来等待一个线程的结束。函数原型为: extern int pthread_join __P ((pthread_t __th, void **__thread_return)); 第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等 待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束 为止,当函数返回时,被等待线程的资源被收回。一个线程的结束有两种途径,一种是象我们上面 的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数 pthread_exit 来实现。 它的函数原型为: extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__)); 唯一的参数是函数的返回代码,只要 pthread_join 中的第二个参数 thread_return 不是 NULL, 这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接 收到信号的线程成功返回,其余调用 pthread_join 的线程则返回错误代码 ESRCH。 2.关键函数分析 result = pthread_create(&thread1, PTHREAD_CREATE_JOINABLE, (void *)task1, (void *)&t1); 创建新线程,其中参数 thread1 为线程标识号,PTHREAD_CREATE_JOINABLE 为线程属性,task1 为函数名,t1 传递参数。 pthread_join(thread1, (void *)&rt1); 函数 pthread_join 用来等待一个线程的结束,参数 thread1 为线程标识,表示等待这个线程结 束,rt1 用来保存被等待线程的返回值。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 348 - 7. 4. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端。 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 thread 工作目录: $ cd $SIMPLEDIR/7.4-thread 4)编译并拷贝 thread 程序: $ make $ make install $ make clean 并自动拷贝 thread 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 thread 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./thread -l./thread 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 thread 添加可执行权限: # chmod 777 thread 6)运行 thread: # ./thread task1 cnt = 1. task2 cnt = 1. task1 cnt = 2. task1 cnt = 3. task2 cnt = 2. 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 349 - task1 cnt = 4. task1 cnt = 5. task2 cnt = 3. task2 cnt = 4. task2 cnt = 5. total 10 times. return value of task1: 5. return value of task2: 5. 7. 4. 6 参考程序 #include #include #include #include #include int task1(int *cnt) { while(*cnt < 5) { sleep(1); (*cnt)++; printf("task1 cnt = %d.\n", *cnt); } return (*cnt); } int task2(int *cnt) { while(*cnt < 5) { sleep(2); (*cnt)++; printf("task2 cnt = %d.\n", *cnt); } return (*cnt); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 350 - } int main(int argc, char **argv) { int result; int t1 = 0; int t2 = 0; int rt1, rt2; pthread_t thread1, thread2; // 创建第一个线程 result = pthread_create(&thread1, PTHREAD_CREATE_JOINABLE, (void *)task1, (void *)&t1); if(result) { perror("pthread_create: task1.\n"); exit(EXIT_FAILURE); } // 创建第二个线程 result = pthread_create(&thread2, PTHREAD_CREATE_JOINABLE, (void *)task2, (void *)&t2); if(result) { perror("pthread_create: task2.\n"); exit(EXIT_FAILURE); } pthread_join(thread1, (void *)&rt1); pthread_join(thread2, (void *)&rt2); printf("total %d times.\n", t1+t2); printf("return value of task1: %d.\n", rt1); printf("return value of task2: %d.\n", rt2); exit(EXIT_SUCCESS); } 7. 4. 7 练习题 编写一个多线程运行程序,并在线程的运行中查看内存值的变化。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 351 - 7. 5 计时器实验 7. 5. 1 实验目的 ¾ 掌握 Linux 下计时器操作。 7. 5. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 5. 3 实验内容 ¾ 编写一个 Linux 计时器应用程序,掌握 Linux 计时器实现机制。 7. 5. 4 实验原理 在程序当中,我们经常要输出系统当前的时间,比如我们使用 date 命令的输出结果。这个时候 我们可以使用下面两个函数: #include time_t time(time_t *tloc); char *ctime(const time_t *clock); time 函数返回从 1970 年 1 月 1 日 0 点以来的秒数。存储在 time_t 结构之中。不过这个函数的 返回值对于我们来说没有什么实际意义。这个时候我们使用第二个函数将秒数转化为字符串。这个 函数的返回类型是固定的:一个可能值为 Thu Dec 7 14:58:59 2000,这个字符串的长度是固定的为 26。 有时候我们要计算程序执行的时间,比如我们要对算法进行时间分析。这个时候可以使用下面 这些函数: ¾ void init_time(void) 该函数设定间歇计时器的值。系统为进程提供三种类型的计时器,每一类以不同的时间域递减 其值。当计时器超时,信号被发送到进程,之后计时器重新启动。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 352 - // 初始化定时器 void init_time(void) { struct itimerval val; val.it_value.tv_sec = 1; val.it_value.tv_usec = 0; val.it_interval = val.it_value; setitimer(ITIMER_PROF, &val, NULL); } ¾ itimerval 结构体 struct itimerval { struct timeval it_interval; // 计时器重启动的间歇值 struct timeval it_value; // 计时器安装后首先启动的初始值 }; struct timeval { time_t tv_sec; // 秒 suseconds_t tv_usec; // 微秒 }; ¾ int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue); which:间歇计时器类型,有三种选择: ITIMER_REAL:数值为 0,计时器的值实时递减,发送的信号是 SIGALRM。 ITIMER_VIRTUAL:数值为 1,进程执行时递减计时器的值,发送的信号是 SIGVTALRM。 ITIMER_PROF:数值为 2,进程和系统执行时都递减计时器的值,发送的信号是 SIGPROF。 value:指向的结构体设为计时器的当前值。 ovalue:如果不是 NULL,将返回计时器的原有值。 ¾ void init_sigaction(void) 该函数用作检查/修改与指定信号相关联的处理动作: // 初始化sigaction void init_sigaction(void) { struct sigaction act; act.sa_handler = timeout_info; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 353 - act.sa_flags = 0; sigemptyset(&act.sa_mask); // 将信号集合设置为空 sigaction(SIGPROF, &act, NULL); // 检查/修改与指定信号相关联的处理动作 } ¾ sigaction 结构体 struct sigaction { __sighandler_t sa_handler; // 一个带有int参数的函数指针, 或者SIG_IGN(忽略), 或者 SIG_DFL(默认). unsigned long sa_flags; // 信号操作 sigset_t sa_mask; // 信号屏蔽字(集). 当该信号处理函数返回时, 屏蔽字恢复 }; int sigemptyset (sigset_t *set); 函数初始化信号集合 set,将 set 设置为空。 int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact); 函数原型: #include int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact); 成功则返回 0,出错则返回-1。 ¾ void timeout_info(int signo) void timeout_info(int signo) { if(limit == 0) { printf("Sorry, time limit reached.\n"); exit(0); } printf("only %d senconds left.\n", limit--); } 用于处理计时器到时的操作。通过结构体&act 被调用。 7. 5. 5 实验步骤 z 编译: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 354 - 1)单击菜单应用程序->附件->终端,打开终端: 2)在终端中输入以下命令行设置开发所需的环境变量; $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 timer 工作目录: $ cd $SIMPLEDIR/7.4-timer 4)编译并拷贝 timer 程序: $ make $ make install $ make clean 并自动拷贝 timer 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 timer 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./timer -l./timer 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 timer 添加可执行权限: # chmod 777 timer 6)运行 timer: # ./timer You have only 10 seconds for thinking. only 10 senconds left. only 9 senconds left. only 8 senconds left. only 7 senconds left. only 6 senconds left. only 5 senconds left. only 4 senconds left. only 3 senconds left. 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 355 - only 2 senconds left. only 1 senconds left. Sorry, time limit reached. 7. 5. 6 参考程序 #include #include #include int limit = 10; void timeout_info(int signo) // 信号操作 { if(limit == 0) { printf("Sorry, time limit reached.\n"); exit(0); } printf("only %d senconds left.\n", limit--); } void init_sigaction(void) // 初始化sigaction { struct sigaction act; act.sa_handler = timeout_info; act.sa_flags = 0; sigemptyset(&act.sa_mask); sigaction(SIGPROF, &act, NULL); } void init_time(void) // 初始化计时器 { struct itimerval val; val.it_value.tv_sec = 1; val.it_value.tv_usec = 0; val.it_interval = val.it_value; setitimer(ITIMER_PROF, &val, NULL); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 356 - } int main(void) { char *str; char c; init_sigaction(); init_time(); printf("You have only 10 seconds for thinking.\n"); while(1); exit(0); } 7. 5. 7 练习题 参考实验程序,修改定时时间间隔,并重新编译后下载到实验平台 Linux 中运行,观察显示结 果。 7. 6 Tcp 实验 7. 6. 1 实验目的 ¾ 通过实验掌握 S3C2410X 以太网的使用方法; ¾ 通过该实验可测试以太网是否已通,具有测试作用。 7. 6. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 357 - 7. 6. 3 实验内容 ¾ 在 PC 机端即服务端运行服务程序,然后在实验平台即客户端运行客户程序。要求用 TCP 协议进行通信,服务端程序在监听某个端口,等待客户的连接,一旦运行客户程序之后, 将连接上服务,此时从服务程序中可发送字符串给客户端,并在客户端显示出来。 7. 6. 4 实验原理 1. TCP 基础知识 TCP 是“Transmission Control Protocol”的简写,中文译名为传输控制协议。TCP 和 UDP 处在 同一层——运输层,但是 TCP 和 UDP 最不同的地方是,TCP 提供了一种可靠的数据传输服务,TCP 是面向连接的,也就是说,利用 TCP 通信的两台主机首先要经历一个“拨打电话”的过程,等到通 信准备结束才开始传输数据,最后结束通话。所以 TCP 要比 UDP 可靠的多,UDP 是把数据直接发 出去,而不管对方是不是在收信,就算是 UDP 无法送达,也不会产生 ICMP 差错报文。 TCP 保证可靠性的简单工作原理如下: 应用数据被分割成 TCP 认为最适合发送的数据块。这和 UDP 完全不同,应用程序产生的数据报 长度将保持不变。由 TCP 传递给 IP 的信息单位称为报文段或段(segment)。当 TCP 发出一个段后, 它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报 文段。 当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通 常将推迟一段时间。 TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中 的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段(希望发 端超时并重发)。 既然 TCP 报文段作为 IP 数据报来传输,而 IP 数据报的到达可能会失序,因此 TCP 报文段的到 达也可能会失序。如果必要,TCP 将对收到的数据进行重新排序,将收到的数据以正确的顺序交给 应用层。 TCP 还能提供流量控制。TCP 连接的每一方都有固定大小的缓冲空间。TCP 的接收端只允许另 一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。 从这段话中可以看到,TCP 中保持可靠性的方式就是超时重发,这是有道理的,虽然 TCP 也可 以用各种各样的 ICMP 报文来处理这些,但是这也不是可靠的,最可靠的方式就是只要不得到确认, 就重新发送数据报,直到得到对方的确认为止。 TCP 的首部和 UDP 首部一样,都有发送端口号和接收端口号。但是显然,TCP 的首部信息要比 UDP 的多,可以看到,TCP 协议提供了发送和确认所需要的所有必要的信息。可以想象一个 TCP 数 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 358 - 据的发送应该是如下的一个过程。 z 双方建立连接 发送方给接收方 TCP 数据报,然后等待对方的确认 TCP 数据报,如果没有,就重新发,如果有, 就发送下一个数据报。 接收方等待发送方的数据报,如果得到数据报并检验无误,就发送 ACK(确认)数据报,并等 待下一个 TCP 数据报的到来。直到接收到 FIN(发送完成数据报)。 z 中止连接 可以想见,为了建立一个 TCP 连接,系统可能会建立一个新的进程来进行数据的传送。 目前建立在 TCP 协议上的网络协议特别多,有 telnet,ssh,有 ftp,有 http 等等。这些协议又 可以根据数据吞吐量来大致分成两大类:(1)交互数据类型,例如 telnet,ssh,这种类型的协议在 大多数情况下只是做小流量的数据交换,比如说按一下键盘,回显一些文字等等。(2)数据成块类 型,例如 ftp,这种类型的协议要求 TCP 能尽量的运载数据,把数据的吞吐量做到最大,并尽可能的 提高效率。针对这两种情况,TCP 给出了两种不同的策略来进行数据传输。 1)TCP 的交互数据流 对于交互性要求比较高的应用,TCP 给出两个策略来提高发送效率和减低网络负担:(1)捎带 ACK。(2)Nagle 算法(一次尽量多的发数据)。通常,在网络速度很快的情况下,比如用 IO 接口进 行 telnet 通信,当按下字母键并要求回显的时候,客户端和服务器将经历发送按键数据->服务器发 送按键数据的 ack->服务器端发送回显数据->客户端发送回显数据的 ACK 的过程,而其中的数据流 量将是 40bit + 41bit+41bit+40bit = 162bit,如果在广域网里面,这种小分组的 TCP 流量将会造成 很大的网络负担。 „ 捎带 ACK 的发送方式 这个策略是说,当主机收到远程主机的 TCP 数据报之后,通常不马上发送 ACK 数据报,而是等 上一个短暂的时间,如果这段时间里面主机还有发送到远程主机的 TCP 数据报,那么就把这个 ACK 数据报“捎带”着发送出去,把本来两个 TCP 数据报整合成一个发送。一般的,这个时间是 200ms。 可以明显地看到这个策略可以把 TCP 数据报的利用率提高很多。 „ Nagle 算法 上过 bbs 的人应该都会有感受,就是在网络慢的时候发贴,有时键入一串字符串以后,经过一 段时间,客户端“发疯”一样突然回显出很多内容,就好像数据一下子传过来了一样,这就是 Nagle 算法的作用。 Nagle 算法是说,当主机 A 给主机 B 发送了一个 TCP 数据报并进入等待主机 B 的 ACK 数据报的 状态时,TCP 的输出缓冲区里面只能有一个 TCP 数据报,并且,这个数据报不断地收集后来的数据, 整合成一个大的数据报,等到 B 主机的 ACK 包一到,就把这些数据“一股脑”的发送出去。虽然这 样的描述有些不准确,但还算形象和易于理解,我们同样可以体会到这个策略对于减低网络负担的 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 359 - 好处。 在编写插口程序的时候,可以通过 TCP_NODELAY 来关闭这个算法。并且,使用这个算法看情 况的,比如基于 TCP 的 X 窗口协议,如果处理鼠标事件时还是用这个算法,那么“延迟”可就非常 大了。 2)TCP 的成块数据流 对于 FTP 这样对于数据吞吐量有较高要求的要求,将总是希望每次尽量多的发送数据到对方主 机,就算是有点“延迟”也无所谓。TCP 也提供了一整套的策略来支持这样的需求。TCP 协议中有 16 个 bit 表示“窗口”的大小,这是这些策略的核心。 „ 传输数据时 ACK 的问题 在解释滑动窗口前,需要看看 ACK 的应答策略,一般来说,发送端发送一个 TCP 数据报,那么 接收端就应该发送一个 ACK 数据报。但是事实上却不是这样,发送端将会连续发送数据尽量填满接 收方的缓冲区,而接收方对这些数据只要发送一个 ACK 报文来回应就可以了,这就是 ACK 的累积特 性,这个特性大大减少了发送端和接收端的负担。 „ 滑动窗口 滑动窗口本质上是描述接收方的 TCP 数据报缓冲区大小的数据,发送方根据这个数据来计算自 己最多能发送多长的数据。如果发送方收到接收方的窗口大小为 0 的 TCP 数据报,那么发送方将停 止发送数据,等到接收方发送窗口大小不为 0 的数据报的到来。 TCP 就是用这个窗口,慢慢的从数据的左边移动到右边,把处于窗口范围内的数据发送出去(但 不用发送所有,只是处于窗口内的数据可以发送)。这就是窗口的意义。窗口的大小是可以通过 socket 来制定的,4096 并不是最理想的窗口大小,而 16384 则可以使吞吐量大大的增加。 „ 数据拥塞 上面的策略用于局域网内传输还可以,但是用在广域网中就可能会出现问题,最大的问题就是 当传输时出现了瓶颈(比如说一定要经过一个 slip 低速链路)所产生的大量数据堵塞问题(拥塞), 为了解决这个问题,TCP 发送方需要确认连接双方的线路的数据最大吞吐量是多少。这就是所谓的 拥塞窗口。 拥塞窗口的原理很简单,TCP 发送方首先发送一个数据报,然后等待对方的回应,得到回应后 就把这个窗口的大小加倍,然后连续发送两个数据报,等到对方回应以后,再把这个窗口加倍(先 是 2 的指数倍,到一定程度后就变成线性增长,这就是所谓的慢启动),发送更多的数据报,直到出 现超时错误,这样,发送端就了解到了通信双方的线路承载能力,也就确定了拥塞窗口的大小,发 送方就用这个拥塞窗口的大小发送数据。要观察这个现象是非常容易的,我们一般在下载数据的时 候,速度都是慢慢“冲起来的”。 2. 关键函数分析 该例程是基于 Linux 下 socket 编程,所以有必要先介绍一下 socket 的相关知识。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 360 - 1)什么是 Socket Socket 接口是 TCP/IP 网络的 API,Socket 接口定义了许多函数或例程,程序员可以用它们来开 发 TCP/IP 网络上的应用程序。要学 Internet 上的 TCP/IP 网络编程,必须理解 Socket 接口。 Socket 接口设计者最先是将接口放在 Unix 操作系统里面的。如果了解 Unix 系统的输入和输出 的话,就很容易了解 Socket 了。网络的 Socket 数据传输是一种特殊的 I/O,Socket 也是一种文件 描述符。Socket 也具有一个类似于打开文件的函数调用 Socket(),该函数返回一个整型的 Socket 描述符,随后的连接建立、数据传输等操作都是通过该 Socket 实现的。常用的 Socket 类型有两种: 流式 Socket(SOCK_STREAM)和数据报式 Socket(SOCK_DGRAM)。流式是一种面向连接的 Socket, 针对于面向连接的 TCP 服务应用;数据报式 Socket 是一种无连接的 Socket,对应于无连接的 UDP 服务应用。 2)Socket 建立 为了建立 Socket,程序可以调用 Socket 函数,该函数返回一个类似于文件描述符的句柄。socket 函数原型为: int socket(int domain, int type, int protocol); domain 指明所使用的协议族,通常为 PF_INET,表示互联网协议族(TCP/IP 协议族);type 参 数指定 socket 的类型:SOCK_STREAM 或 SOCK_DGRAM,Socket 接口还定义了原始 Socket (SOCK_RAW),允许程序使用低层协议;protocol 通常赋值“0”。Socket()调用返回一个整型 socket 描述符,你可以在后面的调用使用它。 Socket 描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用 Socket 函数时, socket 执行体将建立一个 Socket,实际上“建立一个 Socket”意味着为一个 Socket 数据结构分配 存储空间。Socket 执行体为你管理描述符表。 两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、 远端主机地址和远端协议端口。Socket 数据结构中包含这五种信息。 3)Socket 配置 通过 socket 调用返回一个 socket 描述符后,在使用 socket 进行网络传输以前,必须配置该 socket。面向连接的 socket 客户端通过调用 connect 函数在 socket 数据结构中保存本地和远端信息。 无连接socket的客户端和服务端以及面向连接socket的服务端通过调用 bind函数来配置本地信息。 bind 函数将 socket 与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。Bind 函数原型为: int bind(int sockfd,struct sockaddr *my_addr, int addrlen); sockfd 是调用 socket 函数返回的 socket 描述符,my_addr 是一个指向包含有本机 IP 地址及端 口号等信息的 sockaddr 类型的指针;addrlen 常被设置为 sizeof(struct sockaddr)。 struct sockaddr 结构类型是用来保存 socket 信息的: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 361 - struct sockaddr { unsigned short sa_family; // 地址族, AF_xxx char sa_data[14]; // 14 字节的协议地址 }; sa_family 一般为 AF_INET,代表 Internet(TCP/IP)地址族;sa_data 则包含该 socket 的 IP 地址和端口号。 另外还有一种结构类型: struct sockaddr_in { short int sin_family; // 地址族 unsigned short int sin_port; // 端口号 struct in_addr sin_addr; // IP地址 unsigned char sin_zero[8]; // 填充 0 以保持与struct sockaddr同样大小 }; 这个结构更方便使用。sin_zero 用来将 sockaddr_in 结构填充到与 struct sockaddr 同样的长度, 可以用 bzero()或 memset()函数将其置为零。指向 sockaddr_in 的指针和指向 sockaddr 的指针可以 相互转换,这意味着如果一个函数所需参数类型是 sockaddr 时,你可以在函数调用的时候将一个指 向 sockaddr_in 的指针转换为指向 sockaddr 的指针;或者相反。 使用 bind 函数时,可以用下面的赋值实现自动获得本机 IP 地址和随机获取一个没有被占用的 端口号: my_addr.sin_port = 0; // 系统随机选择一个未被使用的端口号 my_addr.sin_addr.s_addr = INADDR_ANY; // 填入本机IP地址 通过将 my_addr.sin_port 置为 0,函数会自动为你选择一个未占用的端口来使用。同样,通过 将 my_addr.sin_addr.s_addr 置为 INADDR_ANY,系统会自动填入本机 IP 地址。 注意使用 bind 函数需要将 sin_port 转换成为网络字节优先顺序;而 sin_addr 则不需要转换。 计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet 上数据以高位 字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在 Internet 上传输数据时就需要进行转换,否则就会出现数据不一致。 下面是几个字节顺序转换函数: ·htonl():把 32 位值从主机字节序转换成网络字节序; ·htons():把 16 位值从主机字节序转换成网络字节序; ·ntohl():把 32 位值从网络字节序转换成主机字节序; ·ntohs():把 16 位值从网络字节序转换成主机字节序。 bind()函数在成功被调用时返回 0;出现错误时返回“-1”并将 errno 置为相应的错误号。需要 注意的是,在调用 bind 函数时一般不要将端口号置为小于 1024 的值,因为 1 到 1024 是保留端口 号,你可以选择大于 1024 中的任何一个没有被占用的端口号。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 362 - 4)连接建立 面向连接的客户程序使用 connect 函数来配置 socket 并与远端服务器建立一个 TCP 连接,其函 数原型为: int connect(int sockfd, struct sockaddr *serv_addr,int addrlen); sockfd 是 socket 函数返回的 socket 描述符;serv_addr 是包含远端主机 IP 地址和端口号的指 针;addrlen 是远端地址结构的长度。connect 函数在出现错误时返回-1,并且设置 errno 为相应的 错误码。进行客户端程序设计无须调用 bind(),因为这种情况下只需知道目的机器的 IP 地址,而客 户通过哪个端口与服务器建立连接并不需要关心,socket 执行体为你的程序自动选择一个未被占用 的端口,并通知你的程序,数据什么时候到达端口。 connect 函数启动和远端主机的直接连接。只有面向连接的客户程序使用 socket 时才需要将此 socket 与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接, 它只是被动的在协议端口监听客户的请求。 listen 函数使 socket 处于被动的监听模式,并为该 socket 建立一个输入数据队列,将到达的服 务请求保存在此队列中,直到程序处理它们。 int listen(int sockfd, int backlog); sockfd 是 Socket 系统调用返回的 socket 描述符;backlog 指定在请求队列中允许的最大请求 数,进入的连接请求将在队列中等待 accept()它们(参考下文)。backlog 对队列中等待服务的请求 的数目进行了限制,大多数系统缺省值为 20。如果一个服务请求到来时,输入队列已满,该 socket 将拒绝连接请求,客户将收到一个出错信息。 当出现错误时 listen 函数返回-1,并置相应的 errno 错误码。 accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用 accept 函数, 然后睡眠并等待客户的连接请求。 int accept(int sockfd, void *addr, int *addrlen); sockfd 是被监听的 socket 描述符,addr 通常是一个指向 sockaddr_in 变量的指针,该变量用来 存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);addrten 通常为一个指向 值为 sizeof(struct sockaddr_in)的整型指针变量。出现错误时 accept 函数返回-1 并置相应的 errno 值。 首先,当 accept 函数监视的 socket 收到连接请求时,socket 执行体将建立一个新的 socket, 执行体将这个新 socket 和请求连接进程的地址联系起来,收到服务请求的初始 socket 仍可以继续在 以前的 socket 上监听,同时可以在新的 socket 描述符上进行数据传输操作。 5)数据传输 send()和 recv()这两个函数用于面向连接的 socket 上进行数据传输。 send()函数原型为: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 363 - int send(int sockfd, const void *msg, int len, int flags); sockfd 是你想用来传输数据的 socket 描述符;msg 是一个指向要发送数据的指针;len 是以字 节为单位的数据的长度;flags 一般情况下置为 0(关于该参数的用法可参照 man 手册)。 send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将 send() 的返回值与欲发送的字节数进行比较。当 send()返回值与 len 不匹配时,应该对这种情况进行处理。 char *msg = "Hello!"; int len, bytes_sent; …… len = strlen(msg); bytes_sent = send(sockfd, msg,len,0); …… recv()函数原型为: int recv(int sockfd,void *buf,int len,unsigned int flags); sockfd 是接收数据的 socket 描述符;buf 是存放接收数据的缓冲区;len 是缓冲的长度。flags 也被置为 0。recv()返回实际上接收的字节数,当出现错误时,返回-1 并置相应的 errno 值。 sendto()和 recvfrom()用于在无连接的数据报 socket 方式下进行数据传输。由于本地 socket 并 没有与远端机器建立连接,所以在发送数据时应指明目的地址。 sendto()函数原型为: int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); 该函数比 send()函数多了两个参数,to 表示目地机的 IP 地址和端口号信息,而 tolen 常常被赋 值为 sizeof (struct sockaddr)。sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返 回-1。 recvfrom()函数原型为: int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); from 是一个 struct sockaddr 类型的变量,该变量保存源机的 IP 地址及端口号。fromlen 常置 为 sizeof (struct sockaddr)。当 recvfrom()返回时,fromlen 包含实际存入 from 中的数据字节数。 recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的 errno。 如果你对数据报 socket 调用了 connect()函数时,你也可以利用 send()和 recv()进行数据传输, 但该 socket 仍然是数据报 socket,并且利用传输层的 UDP 服务。但在发送或接收数据报时,内核 会自动为之加上目地和源地址信息。 6)结束传输 当所有的数据操作结束以后,你可以调用 close()函数来释放该 socket,从而停止在该 socket 上的任何数据操作: close(sockfd); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 364 - 你也可以调用 shutdown()函数来关闭该 socket。该函数允许你只停止在某个方向上的数据传输, 而一个方向上的数据传输继续进行。如你可以关闭某 socket 的写操作而允许继续在该 socket 上接收 数据,直至读入所有数据。 int shutdown(int sockfd,int how); Sockfd 是需要关闭的 socket 的描述符。参数 how 允许为 shutdown 操作选择以下几种方式: ·0-------不允许继续接收数据; ·1-------不允许继续发送数据; ·2-------不允许继续发送和接收数据; ·均为允许则调用 close ()。 shutdown 在操作成功时返回 0,在出现错误时返回-1 并置相应 errno。 有了上面的基础,就可以读懂一般的 socket 编程了,该例程中只用到上面 TCP 通信常用的函数。 7. 6. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端。 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 编译主机端程序 server.c 3)在终端中输入以下命令进入 server 目录: $ cd $SIMPLEDIR/7.6-tcp/server 4)编译并拷贝 server 应用程序: $ make $ make install $ make clean 并自动拷贝 server 到/home/example/tftp/。 编译实验平台端程序 client.c 5)在终端中输入以下命令进入 uclient 工作目录; $ cd $SIMPLEDIR/7.6-tcp/uclient 6)编译并拷贝 client 应用程序: $ make $ make install $ make clean 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 365 - 并自动拷贝 client 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在 ubuntu 终端中输入命令运行主机端 server 程序: $ cd $TFTPDIR $ ./server OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 5000 sucessfully. OK: Listening the Port 5000 sucessfully. 3)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 4)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 5)启动完成后,在 minicom 下执行以下命令将 client 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./client -l./client 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 6)给 client 添加可执行权限: # chmod 777 client 7)运行 client: # ./client 192.192.192.190 OK: Have connected to the 192.192.192.190 8)client 正常运行时,PC 端正在运行的 server 将打印以下信息,并提示输入字符信息: $ ./server OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 5000 sucessfully. OK: Listening the Port 5000 sucessfully. OK: Server has got connect from 192.192.192.200. You can enter string, and press 'exit' to end the connect. 在 PC 端输入 123456789 后回车,将打印以下信息: $ ./server OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 5000 sucessfully. OK: Listening the Port 5000 sucessfully. 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 366 - OK: Server has got connect from 192.192.192.200. You can enter string, and press 'exit' to end the connect. 123456789 OK: Sent 9 bytes sucessful, please enter again. 同时实验平台端 client 程序会接收到传送的数据,并在 minicom 串口终端上打印出来: # ./client 192.192.192.190 OK: Have connected to the 192.192.192.190 OK: Receviced numbytes = 9 OK: Receviced string is: 123456789 在 PC 端输入 exit 字符将退出程序。 7. 6. 6 参考程序 1)server.c #include #include #include #include #include #include #include #include #define PORT 5000 // The port which is communicate with server #define BACKLOG 10 #define LENGTH 512 // Buffer length int main () { int sockfd; // Socket file descriptor int nsockfd; // New Socket file descriptor int num; int sin_size; // to store struct size char sdbuf[LENGTH]; // Send buffer struct sockaddr_in addr_local; struct sockaddr_in addr_remote; /* Get the Socket file descriptor */ if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 367 - { printf ("ERROR: Failed to obtain Socket Despcritor.\n"); return (0); } else { printf ("OK: Obtain Socket Despcritor sucessfully.\n"); } /* Fill the local socket address struct */ addr_local.sin_family = AF_INET; // Protocol Family addr_local.sin_port = htons(PORT); // Port number addr_local.sin_addr.s_addr = INADDR_ANY; // AutoFill local address bzero(&(addr_local.sin_zero), 8); // Flush the rest of struct /* Blind a special Port */ if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 ) { printf ("ERROR: Failed to bind Port %d.\n",PORT); return (0); } else { printf("OK: Bind the Port %d sucessfully.\n",PORT); } /* Listen remote connect/calling */ if(listen(sockfd,BACKLOG) == -1) { printf ("ERROR: Failed to listen Port %d.\n", PORT); return (0); } else { printf ("OK: Listening the Port %d sucessfully.\n", PORT); } while(1) { 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 368 - sin_size = sizeof(struct sockaddr_in); /* Wait a connection, and obtain a new socket file despriptor for single connection */ if ((nsockfd = accept(sockfd, (struct sockaddr *)&addr_remote, &sin_size)) == -1) { printf ("ERROR: Obtain new Socket Despcritor error.\n"); continue; } else { printf ("OK: Server has got connect from %s.\n", inet_ntoa(addr_remote.sin_addr)); } /* Child process */ if(!fork()) { printf("You can enter string, and press 'exit' to end the connect.\n"); while(strcmp(sdbuf,"exit") != 0) { scanf("%s", sdbuf); if((num = send(nsockfd, sdbuf, strlen(sdbuf), 0)) == -1) { printf("ERROR: Failed to sent string.\n"); close(nsockfd); exit(1); } printf("OK: Sent %d bytes sucessful, please enter again.\n", num); } } close(nsockfd); while(waitpid(-1, NULL, WNOHANG) > 0); } } 2)client.c #include #include #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 369 - #include #include #include #define PORT 5000 // 连接服务器端口号 #define LENGTH 256 // 缓冲区大小 int main(int argc, char *argv[]) { int sockfd; // Socket文件描述符 int num; // 收到的字节数计数 char revbuf[LENGTH]; // 接收缓冲区 struct sockaddr_in remote_addr; // 主机地址信息 /* Check parameters number */ if (argc != 2) { printf ("Usage: client HOST IP (ex: ./client 192.168.0.94).\n"); return (0); } /* Get the Socket file descriptor */ if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("ERROR: Failed to obtain Socket Descriptor!\n"); return (0); } /* Fill the socket address struct */ remote_addr.sin_family = AF_INET; // 协议族 remote_addr.sin_port = htons(PORT); // 端口号 inet_pton(AF_INET, argv[1], &remote_addr.sin_addr); // 网络地址 bzero(&(remote_addr.sin_zero), 8); /* Try to connect the remote */ if (connect(sockfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) == -1) { printf ("ERROR: Failed to connect to the host!\n"); return (0); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 370 - } else { printf ("OK: Have connected to the %s\n",argv[1]); } /* Try to connect the server */ while (strcmp(revbuf,"exit") != 0) // Check remoter command { bzero(revbuf,LENGTH); num = recv(sockfd, revbuf, LENGTH, 0); switch(num) { case -1: printf("ERROR: Receive string error!\n"); close(sockfd); return (0); case 0: close(sockfd); return(0); default: printf ("OK: Receviced numbytes = %d\n", num); break; } revbuf[num] = '\0'; printf ("OK: Receviced string is: %s\n", revbuf); } close (sockfd); return (0); } 7. 6. 7 练习题 掌握 TCP 传输数据原理,尝试使用 TCP 进行文件传输。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 371 - 7. 7 Webserver 实验 7. 7. 1 实验目的 ¾ 通过实验掌握 S3C2410X 以太网的使用方法; ¾ 通过该实验可测试以太网是否已通,具有测试作用。 7. 7. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 7. 3 实验内容 ¾ 在实验平台端即服务端运行服务程序,然后在 PC 端打开浏览器,输入 http://192.168.1.199:81/回车,即可打开网页,看到相关信息。 7. 7. 4 实验原理 这里 webserver 采用 TCP 进行通信,TCP 相关工作原理详见 7.6 节实验原理介绍。 7. 7. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端: 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 3)进入 webserver 工作目录: $ cd $SIMPLEDIR/7.7-webserver 4)编译并拷贝 webserver 程序: $ make $ make install 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 372 - $ make clean 并自动拷贝 webserver 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 3)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 4)启动完成后,在 minicom 下执行以下命令将 webserver 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./webserver -l./webserver 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 5)给 webserver 添加可执行权限: # chmod 777 webserver 9)运行 webserver。 # ./webserver OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 81 sucessfully. OK: Listening the Port 81 sucessfully 正常运行时,在 PC 端打开浏览器,在地址栏输入 http://192.192.192.200:81/回车,即可在浏 览器中看到以下信息,实验平台的主板网卡 IP 地址是 192.192.192.200。 Welcome To Embest EduKit Embedded Teaching Platform EDUKIT-IV 实验教学平台 7. 7. 6 参考程序 #include #include #include #include #include #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 373 - #define PORT 81 // The port which is communicate with server #define BACKLOG 10 #define LENGTH 1024 // Buffer length char httpweb[]={ "HTTP/1.0 200 OK\r\n" "Date: Mon, 24 Nov 2005 10:26:00 GMT\r\n" "Server: microHttp/1.0 EMBEST INFO&TECH CO.,LTD\r\n" "Accept-Ranges: bytes\r\n" "Connection: Keep-Close\r\n" "Content-Type: text/html\r\n" "\r\n"}; char web[]={ "\r\n" "\r\n" "EduKit IV 2410 演示网页(深圳市英蓓特信息技术有限公司)\r\n" "\r\n" "

Welcome To Embest EduKit Embedded Teaching Platform

\r\n" "
    \r\n" "
  • EDUKIT-IV实验教学平台 \r\n" "\r\n" "\r\n" }; char httpgif[]={ "HTTP/1.0 200 OK\r\n" "Date: Mon, 24 Nov 2005 10:26:00 GMT\r\n" "Server: microHttp/1.0 EMBEST INFO&TECH CO.,LTD\r\n" "Accept-Ranges: bytes\r\n" "Connection: Keep-Close\r\n" "Content-Type: image/bmp\r\n" "\r\n"}; int main () { int sockfd; // Socket file descriptor 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 374 - int nsockfd; // New Socket file descriptor int i,num; int flag = 1; int sin_size; // to store struct size char revbuf[LENGTH]; struct sockaddr_in addr_local; struct sockaddr_in addr_remote; /* Get the Socket file descriptor */ if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { printf ("ERROR: Cannot obtain Socket Despcritor.\n"); return (0); } else { printf ("OK: Obtain Socket Despcritor sucessfully.\n"); } /* Fill the local socket address struct */ addr_local.sin_family = AF_INET; // Protocol Family addr_local.sin_port = htons(PORT); // Port number addr_local.sin_addr.s_addr = INADDR_ANY; // AutoFill local address bzero(&(addr_local.sin_zero), 8); // Flush the rest of struct /* Blind a special Port */ if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 ) { printf ("ERROR: Cannot bind Port %d\n.",PORT); return (0); } else { printf("OK: Bind the Port %d sucessfully.\n",PORT); } /* Listen remote connect/calling */ if(listen(sockfd,BACKLOG) == -1) { 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 375 - printf ("ERROR: Cannot listen Port %d\n.", PORT); return (0); } else { printf ("OK: Listening the Port %d sucessfully\n.", PORT); } while(1) { sin_size = sizeof(struct sockaddr_in); /* Wait a connection, and obtain a new socket file despriptor for single connection */ if ((nsockfd = accept(sockfd, (struct sockaddr *)&addr_remote, &sin_size)) == -1) { printf ("ERROR: Obtain new Socket Despcritor error\n"); continue; } else { printf ("OK: Server has got connect from %s\n", inet_ntoa(addr_remote.sin_addr)); } num = recv(nsockfd, revbuf, LENGTH, 0); /* Child process */ if(!fork()) { printf("OK: Http web is servering.\n"); if(revbuf[5]==' ') { send(nsockfd, httpweb, sizeof(httpweb), 0); send(nsockfd, web, sizeof(web), 0); } else if(revbuf[5]=='1') send(nsockfd, httpgif, sizeof(httpgif), 0); } close(nsockfd); while(waitpid(-1, NULL, WNOHANG) > 0); } } 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 376 - 7. 7. 7 练习题 利用以上介绍的知识,创建一个较为复杂的个人网页,并通过 pc 机进行访问。 7. 8 Udp 实验 7. 8. 1 实验目的 ¾ 通过实验掌握 S3C2410X 以太网的使用方法; ¾ 通过该实验可测试以太网是否已通,具有测试作用。 7. 8. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 7. 8. 3 实验内容 ¾ 在 PC 机端即服务端运行一个接收程序,然后在实验平台即客户端运行一个发送程序。要求 发送程序能将一个字符串发送到服务端,并在服务端打印结果。 7. 8. 4 实验原理 1.UDP 基础知识 UDP 协议是英文 User Datagram Protocol 的缩写,即用户数据报协议,主要用来支持那些需要 在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应 用都需要使用 UDP 协议。UDP 协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些 类似协议所掩盖,但是即使是在今天,UDP 仍然不失为一项非常实用和可行的网络传输层协议。 与我们所熟知的 TCP(传输控制协议)协议一样,UDP 协议直接位于 IP(网际协议)协议的顶 层。根据 OSI(开放系统互连)参考模型,UDP 和 TCP 都属于传输层协议。 UDP 协议的主要作用是将网络数据流量压缩成数据报的形式。一个典型的数据报就是一个二进 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 377 - 制数据的传输单位。每一个数据报的前 8 个字节用来包含报头信息,剩余字节则用来包含具体的传 输数据。 UDP 协议使用端口号为不同的应用,并保留其各自的数据传输通道。UDP 和 TCP 协议正是采用 这一机制实现对同一时刻内多项应用同时发送和接收数据的支持。数据发送一方(可以是客户端或 服务器端)将 UDP 数据报通过原端口发送出去,而数据接收一方则通过目标端口接收数据。有的网 络应用只能使用预先为其预留或注册的静态端口;而另外一些网络应用则可以使用未被注册的动态 端口。因为 UDP 报头使用两个字节存放端口号,所以端口号的有效范围是从 0 到 65535。一般来说, 大于 49151 的端口号都代表动态端口。 数据报的长度是指包括报头和数据部分在内的总的字节数。因为报头的长度是固定的,所以该 域主要被用来计算可变长度的数据部分(又称为数据负载)。数据报的最大长度根据操作环境的不同 而各异。从理论上说,包含报头在内的数据报的最大长度为 65535 字节。不过,一些实际应用往往 会限制数据报的大小,有时会降低到 8192 字节。 UDP 协议使用报头中的校验值来保证数据的安全。校验值首先在数据发送方通过特殊的算法计 算得出,在传递到接收方之后,还需要再重新计算。如果某个数据报在传输过程中被第三方篡改或 者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此 UDP 协议可以检测 是否出错。这与 TCP 协议是不同的,后者要求必须具有校验值。 UDP 和 TCP 协议的主要区别是两者在如何实现信息的可靠传递方面不同。TCP 协议中包含了专 门的传递保证机制,当数据接收方收到发送方传来的信息时,会自动向发送方发出确认消息;发送 方只有在接收到该确认消息之后才继续传送其它信息,否则将一直等待直到收到确认信息为止。 与 TCP 不同,UDP 协议并不提供数据传送的保证机制。如果在从发送方到接收方的传递过程中 出现数据报的丢失,协议本身并不能做出任何检测或提示。因此,通常人们把 UDP 协议称为不可靠 的传输协议。 相对于 TCP 协议,UDP 协议的另外一个不同之处在于如何接收突发性的多个数据报。不同于 TCP,UDP 并不能确保数据的发送和接收顺序。 也许有的读者会问,既然 UDP 是一种不可靠的网络协议,那么还有什么使用价值或必要呢?其 实不然,在有些情况下 UDP 协议可能会变得非常有用。因为 UDP 具有 TCP 所望尘莫及的速度优势。 虽然 TCP 协议中植入了各种安全保障功能,但是在实际执行的过程中会占用大量的系统开销,无疑 使速度受到严重的影响。反观 UDP 由于排除了信息可靠传递机制,将安全和排序等功能移交给上层 应用来完成,极大降低了执行时间,使速度得到了保证。 关于 UDP 协议的最早规范是 RFC768,1980 年发布。尽管时间已经很长,但是 UDP 协议仍然 继续在主流应用中发挥着作用。包括视频电话会议系统在内的许多应用都证明了 UDP 协议的存在价 值。因为相对于可靠性来说,这些应用更加注重实际性能,所以为了获得更好的使用效果(例如, 更高的画面帧刷新速率)往往可以牺牲一定的可靠性(例如,会面质量)。这就是 UDP 和 TCP 两种 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 378 - 协议的权衡之处。根据不同的环境和特点,两种传输协议都将在今后的网络世界中发挥更加重要的 作用。 2.关键函数分析 该例程是基于 Linux 下 socket 编程,关于 socket 相关知识的介绍详见 7.6 节。 7. 8. 5 实验步骤 z 编译: 1)单击菜单应用程序->附件->终端,打开终端。 2)在终端中输入以下命令行设置开发所需的环境变量: $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 编译主机端程序 listener.c 3)在终端中输入以下命令进入 listener 目录: $ cd $SIMPLEDIR/7.8-udp/listener 4)编译并拷贝 listener 应用程序: $ make $ make install $ make clean 并自动拷贝 listener 到/home/example/tftp/。 编译实验平台端程序 talker.c 5)在终端中输入以下命令进入 utalker 工作目录; $ cd $SIMPLEDIR/7.8-udp/utalker 6)编译并拷贝 talker 应用程序: $ make $ make install $ make clean 并自动拷贝 talker 到/home/example/tftp/。 z 运行: 1)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 2)在 ubuntu 终端中输入命令运行主机端 listener 程序: $ cd $TFTPDIR 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 379 - $ ./listener OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 5000 sucessfully. 3)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 4)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 5)启动完成后,在 minicom 下执行以下命令将 talker 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./talker-l./talker 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 6)给 talker 添加可执行权限: # chmod 777 talker 7)运行 talker。 # ./talker 192.192.192.190 OK: Sent to 192.192.192.190 total 31 bytes ! 8)talker 正常运行时,PC 端正在运行的 listener 将打印以下信息,并提示输入字符信息: $ ./listener OK: Obtain Socket Despcritor sucessfully. OK: Bind the Port 5000 sucessfully. OK: Welcome to Embest EduKit IV ^_^. 7. 8. 6 参考程序 1)listener.c #include #include #include #include #include #include #include #include #define PORT 5000 // 定义服务器端口 #define BACKLOG 10 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 380 - #define LENGTH 512 // 定义缓存大小 int main () { int sockfd; // Socket描述符 int num; int sin_size; char revbuf[LENGTH]; // 接收缓存 struct sockaddr_in addr_local; struct sockaddr_in addr_remote; // 获取Socket描述符 if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1 ) { printf ("ERROR: Failed to obtain Socket Despcritor.\n"); return (0); } else { printf ("OK: Obtain Socket Despcritor sucessfully.\n"); } // 填充本地socket结构体 addr_local.sin_family = AF_INET; // 协义簇 addr_local.sin_port = htons(PORT); // 端口号 addr_local.sin_addr.s_addr = INADDR_ANY; // 自动填充本地地址 bzero(&(addr_local.sin_zero), 8); // 对socket结构体的其部分清 0 // 绑定指定端口 if( bind(sockfd, (struct sockaddr*)&addr_local, sizeof(struct sockaddr)) == -1 ) { printf ("ERROR: Failed to bind Port %d.\n",PORT); return (0); } else { printf("OK: Bind the Port %d sucessfully.\n",PORT); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 381 - } sin_size = sizeof(struct sockaddr); if(num = recvfrom(sockfd, revbuf, LENGTH, 0, (struct sockaddr *)&addr_remote, &sin_size) == -1) { printf("ERROR!\n"); // 接收出错 } else { printf("OK: %s.\n",revbuf); // 接收到数据并打印 } close(sockfd); // 关闭套接字 return (0); } 2)talker.c #include #include #include #include #include #include #include #include #define PORT 5000 // 服务器端口号,和listner中一致 #define LENGTH 512 // 缓冲大小 int main(int argc, char *argv[]) { int sockfd; // Socket描述符 int num; // 接收到的字节总数 char sdbuf[LENGTH]; // 发送缓存 struct sockaddr_in addr_remote; // 服务端主机信息 char sdstr[]= {"Welcome to Embest EduKit IV ^_^"}; // 即将发送的数据 // 检查参数 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 382 - if (argc != 2) { printf ("Usage: talker HOST IP (ex: ./talker 192.168.2.80).\n"); return (0); } // 获取Socket描述符 if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { printf("ERROR: Failed to obtain Socket Descriptor!\n"); return (0); } // 填充socket结构体 addr_remote.sin_family = AF_INET; // 协义簇 addr_remote.sin_port = htons(PORT); // 端口号 inet_pton(AF_INET, argv[1], &addr_remote.sin_addr); // 网络地址 bzero(&(addr_remote.sin_zero), 8); // 对socket结构体的其部分清 0 // 试图连接服务器,并发送数据 bzero(sdbuf,LENGTH); num = sendto(sockfd, sdstr, strlen(sdstr), 0, (struct sockaddr *)&addr_remote, sizeof(struct sockaddr_in)); if( num < 0 ) { printf ("ERROR: Failed to send your data!\n", argv[1], num);// 连接成功,并发送数据 } else { printf ("OK: Sent to %s total %d bytes !\n", argv[1], num); // 连接失败 } close (sockfd); // 关闭套接字 return (0); } 7. 8. 7 练习题 编写一个程序,采用 udp 进行简单的数据通信。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 383 - 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 384 - 第八章 基于 Mini2410 的基础驱动开发 本章节主要介绍 Mini2410 下基础驱动开发,内容包括 LED 控制实验、中断控制实验、ADC 操 作实验、RTC 实时时钟实验、串口通信实验、IIC 通信实验等一些基础驱动程序的编写,通过这些可 以了解 Linux 下基础驱动程序的开发方法。 8. 1 LED 控制实验 8. 1. 1 实验目的 ¾ 通过实验学习如何将一个驱动添加到 Kconfig,编译到内核; ¾ 通过实验掌握在 Linux 下驱动程序的编写方法。 8. 1. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 8. 1. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下 LED 灯的驱动; ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下 LED 灯的应用程序。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 385 - 8. 1. 4 实验原理 在开发 LED 驱动之前,首先了解本实验的原理图:EduKit-IV 设计了 5 个 LED(D1~D5)用于 指示和控制系统的状态,其中 D2 指示电源的状态,其他 4 个的状态是用户可编程的(SYSLED1~ SYSLED4),在 EduKit-IV 中,这 4 个 LED 的状态通过扩展 I/O 接口进行控制。 EduKit-IV LED 所用到的扩展 I/O 如图 8-1-1 所示: 图 8-1-1 片选信号的产生 利用 3/8 译码器将 A18-A20 扩展了 7 个外设片选信号 CS1-CS7。CS1 和 CS2 引出到外部扩展接 口 EXCON_B3,CS3 和 CS4 为总线扩展输入的芯片 74HC541 的片选。CS5,CS6,和 CS7 为总线扩 展输出的芯片 74HC573 的片选。 片选信号在接入 74HC573 前经过了如下处理: 图 8-1-2 OLE 信号的产生 其中 CS5,CS6,CS7 这 3 个片选信号和写使能信号通过 74HC32 或门输出一个选通信号 LE 为 低电平。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 386 - 图 8-1-3 LE 信号的产生 前面或门输出的 LE 选通信号经过 74HC04 反相得到高电平后再连接到扩展输出芯片 74HC573。 EduKit-IV LED 接口电路如图 8-1-4 和图 8-1-5 所示。在本实验平台上,如图 8-1-4,芯片 74VHC573DT 的选通物理地址为 0x21180000,当访问这个物理地址的时候,就可以访问其上的硬件资源了。这里 可以把其理解为一个寄存器,寄存器地址是 0x21180000,它的低 4 位控制了 4 个 LED 灯,通过访 问地址为 0x21180000 的寄存器,往其低 4 位置高/低电平,从而控制相应的 4 个 LED 灯的亮/灭。 注意:寄存器 0x21180000 是只写的,在软件编程时只能往里写数据,不能从里读数据。 图 8-1-4 向 LED 写入数据 图 8-1-5 LED1-4 连接图 如图 8-1-5 所示,LED1-4 这 4 个 LED 采用了共阳极的接法,分别与 SYSLED1-4 相连,通过 SYSLED1-4 引脚的高低电平来控制发光二极管的亮与灭。当这几个管脚输出高电平的时候发光二极 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 387 - 管熄灭,反之,发光二极管亮。 8. 1. 5 实验步骤 下面介绍如何将一个驱动添加到内核中,并且在配置选项中能够通过 menuconfig 配置内核时 选择该驱动: 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将实验目录$SIMPLEDIR/8.1-led_test/driver 下的 eduk4-led.c 复制到目录内核目录 $KERNELDIR/drivers/char 下。 3)修改$KERNELDIR/drivers/char 目录下的 Kconfig 文件,在文件的末尾按照如下内容修改并 保存: … … config MMTIMER tristate "MMTIMER Memory mapped RTC for SGI Altix" depends on IA64_GENERIC || IA64_SGI_SN2 default y help The mmtimer device allows direct userspace access to the Altix system timer. config EDUKIT4_LED tristate "Edukit4 Led" source "drivers/char/tpm/Kconfig" endmenu 这样当 make menuconfig 时,将会出现 Edukit4 Led 选项。 4)修改$KERNELDIR/drivers/char 目录下的 Makefile 文档,在文件的适当位置按照如下内容修 改并保存: … … obj-$(CONFIG_HANGCHECK_TIMER) += hangcheck-timer.o obj-$(CONFIG_TCG_TPM) += tpm/ obj-$(CONFIG_EDUKIT4_LED) += eduk4-led.o 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 388 - # Files generated that shall be removed upon make clean … … 5)单击菜单应用程序->附件->终端打开终端,在终端命令行输入以下命令配置裁剪内核。 $ cd $KERNELDIR $ make menuconfig 稍后将弹出以下菜单: 图 8-1-6 make menuconfig 后弹出的菜单 我们选择出厂提供的配置文件,选择 Load an Alternate Configuration File,将弹出配置文件选 择对话框,在文本框中输入配置文件 config-eduk4,如下图,选择“OK”按回车键完成配置文件的 选择: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 389 - 图 8-1-7 出厂默认配置文件 6)完成配置文件的选择后,菜单将进入到初始界面,接下来将根据下面的步骤来完成对 LED 驱动的支持,通过计算机键盘上下键选择 Device Drivers 并按回车,将出现以下菜单: 图 8-1-8 选择 Device Drivers 后的菜单 按上下键选择 Character devices 并按回车,将出现以下菜单: 图 8-1-9 选择 Character devices 后菜单 按上下键选择 Edukit4 Led 并按 y 键,前面的<>内将有*号,表明选中 Led 驱动,如下图: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 390 - 图 8-1-10 选择 Led 驱动 按左右键选择 Exit 回到上一级菜单,依次,当最后一次选择 Exit 时,会弹出以下对话框提示是 否保存配置: 图 8-1-11 保存配置 直接选择“Yes”回车,配置完成。 7)执行以下命令编译内核: $ make 编译完成之后将在路径$KERNELDIR/arch/arm/boot 下生成 zImage 镜象,同时也会自动拷贝到 $EXPDIR 目录下。按照前面章节的介绍将该镜象下载到 flash 中。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 391 - 8)将路径切换到$SIMPLEDIR/8.1-led_test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.1-led_test/app $ make $ make install $ make clean 在该目录下将生成可执行文件 led_test,并自动将该文件拷贝到/home/example/tftp/目录下。 9)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 10)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 11)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 12)启动完成后,在 minicom 下执行以下命令将 led_test 下载到 tmp 目录下: # cd /tmp # tftp -g 192.192.192.190 -r./led_test -l./led_test 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 13)给 led_test 添加可执行权限。 # chmod 777 led_test 14)运行 led_test。 # ./led_test Please look at the leds 这时观看主板上的四个 LED 灯在循环闪烁,表明应用程序运行成功,进一步表明驱动添加成功! 8. 1. 6 参考程序 1.驱动程序 #include #include #include #include #include #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 392 - #include #include #include #include #define DEVICE_NAME "led" #define LED_MAJOR 231 #define LED_BASE (0xE1180000) static int eduk4_led_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { unsigned char status; switch(cmd) { case 0: case 1: if (arg > 4) { return -EINVAL; } status = inb(LED_BASE); if(0 == cmd){ status &= ~(0x1 << arg); }else if(1 == cmd){ status |= (0x1 << arg); } outb(status, LED_BASE); return 0; default: return -EINVAL; } } static struct file_operations eduk4_led_fops = { .owner = THIS_MODULE, .ioctl = eduk4_led_ioctl, }; static int __init eduk4_led_init(void) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 393 - { int ret; unsigned char status; ret = register_chrdev(LED_MAJOR, DEVICE_NAME, &eduk4_led_fops); if (ret < 0) { printk(DEVICE_NAME " can't register major number\n"); return ret; } devfs_mk_cdev(MKDEV(LED_MAJOR, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME); status = inb(LED_BASE); outb(status & 0xf0,LED_BASE); printk(DEVICE_NAME " initialized\n"); return 0; } static void __exit eduk4_led_exit(void) { unsigned char status; status = inb(LED_BASE); outb(status | 0x0f,LED_BASE); printk(DEVICE_NAME " remove\n"); devfs_remove(DEVICE_NAME); unregister_chrdev(LED_MAJOR, DEVICE_NAME); } module_init(eduk4_led_init); module_exit(eduk4_led_exit); MODULE_LICENSE("BSD/GPL"); 2.应用程序 #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 394 - #include #include #include #include #include #include static int led_fd; int main(void) { // open device led_fd = open("/dev/led", 0); if (led_fd < 0) { perror("open device led"); exit(1); } printf("Please look at the leds\n"); // led all off ioctl(led_fd, 1, 0); ioctl(led_fd, 1, 1); ioctl(led_fd, 1, 2); ioctl(led_fd, 1, 3); for(;;) { // led on one by one ioctl(led_fd, 0, 0); usleep(500000); ioctl(led_fd, 0, 1); usleep(500000); ioctl(led_fd, 0, 2); usleep(500000); ioctl(led_fd, 0, 3); usleep(500000); // led off one by one 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 395 - ioctl(led_fd, 1, 0); usleep(500000); ioctl(led_fd, 1, 1); usleep(500000); ioctl(led_fd, 1, 2); usleep(500000); ioctl(led_fd, 1, 3); usleep(500000); // all led on ioctl(led_fd, 0, 0); ioctl(led_fd, 0, 1); ioctl(led_fd, 0, 2); ioctl(led_fd, 0, 3); usleep(500000); // all led off ioctl(led_fd, 1, 0); ioctl(led_fd, 1, 1); ioctl(led_fd, 1, 2); ioctl(led_fd, 1, 3); usleep(500000); } close(led_fd); return 0; } 8. 1. 7 练习题 更改驱动程序,将控制由 ioctl 形式改为 write 形式,并添加 read 接口,通过 read 可获取 LED 状态。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 396 - 8. 2 中断控制实验 8. 2. 1 实验目的 ¾ 通过实验重点理解 Linux 下中断驱动的写法; ¾ 通过实验掌握在 Linux 下应用程序的编写方法。 8. 2. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 8. 2. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下按键 key 的驱动; ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下按键 key 的应用程序。 8. 2. 4 实验原理 在本实验平台的主板上设计了两路外部按键,当键被按下时,会产生按键中断信号。按键产生 的中断信号经过 CPLD 逻辑处理后连接到 CPU 的中断引脚。电路原理图如下: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 397 - 图 8-2-1 按键电路 图 8-2-2 按键电路 电路图 8-2-1、8-2-2 是主板上设计的两路按键,分别输出了两个按键信号:EXINT0 与 EXINT1。 两路按键的原理是一样的。这里以主板上的按键 KEY2 为例说明。如图 8-2-1 右边的 KEY2 按键,其 导出了一个输出信号 KEY1,信号 KEY1 对应了标号 EXINT0 。在没有键按下时,EXINT0 信号为高 电平,当有键按下时,EXINT0 变为低电平。EXINT0 信号作为 CPLD 芯片的输入信号。如下图 8-2-3。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 398 - 图 8-2-3 CPLD 扩展电路 在图 8-2-3 中,按键信号 EXINT0 直接输入到 CPLD 芯片。CPLD 是可编程逻辑芯片。在本实验 平台中,EXINT0 信号经过 CPLD 逻辑处理后,最终连接到 CPU 的中断引脚上。本实验平台的 CPLD 内部逻辑如下图: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 399 - 图 8-2-4 CPLD 内部逻辑 了解 CPLD 逻辑便于理解按键的中断机制。如上图 8-2-4 为 CPLD 扩展中断。ISAIRQ0~ISAIRQ7、 IRQNET、IRQKEY、EINT0、EINT1 等信号是外部设备的中断信号,它们作为 CPLD 芯片的输入。CPLD 芯片上设计了两个中断控制器:CtrlReg0 与 CtrlReg1,设计了两个状态寄存器 StatusReg0 与 StatusReg1。从上图 8-2-4 可以看出,按键中断 EINT0 是由状态寄存器 StatusReg1 与中断控制器 CtrlReg1 来控制的,并且按键中断 EINT0 与其它外部中断(如 IRQCF、IRQKEY 等)共享了一个 CPU 中断,在初始状态,这些引脚信号为高电平。 下面说明 CPLD 芯片上与按键中断相关的寄存器: 表 8-2-1 中断控制寄存器 CtrlReg1(地址 0x06600000) BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0 IRQNET IRQKEY IRQCF IRQCAN Reserved EINT1 EINT0 Reserved 中断控制寄存器 CtrlReg1 是 8 位只写寄存器。它的每个位分别控制了一个外部中断。其中按键 中断 EINT0 位于 BIT1 位。往寄存器相应位写 1,则相应中断被屏蔽;相应位清零,则相应中断被打 开。 表 8-2-2 中断状态寄存器 StatusReg1(地址 0x06200000) BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 400 - IRQNET IRQKEY IRQCF IRQCAN Reserved EINT1 EINT0 Reserved 状态寄存器 StatusReg1 是 8 位只读寄存器。它的每个位分别实时反映了一个外部中断信号的状 态,其中 BIT1 位反映了按键中断 EINT0 的状态,比如当前按建 KEY2 没有键按下,则中断信号引脚 EINT0 为高电平,此时寄存器 StatusReg1 的 BIT1 位也为高电平;当有键按下,EINT0 变为低电平, 则 StatusReg1 的 BIT1 位也变为低电平。 8. 2. 5 驱动分析 1. 模块的加载和卸载 module_init(eduk4_ key_init); module_exit(eduk4_ key_exit); 系统使用这两个函数实现 Beep 模块的加载和卸载工作,分别调用 eduk4_ key_init(),eduk4_ key_exit()来实现。 z static int __init s3c2410_key_init(void) 该函数初始化硬件,并通过 module_init(s3c2410_key_init)完成硬件模块的加载。该函数完成 的工作包括: ¾ register_chrdev(KEY_MAJOR, DEVICE_NAME, &eduk4_ key_fops); 该函数用于设备注册,各参数含义: — KEY_MAJOR:需要动态分配一个设备号,如果注册成功的话,把设备号返回出来,当 然这个设备号以后卸载时要用到的; — DEVICE_NAME:所定义的设备名,自己定义,加载模块后就可以查看到; — &eduk4_beep_fops:一个应用文件表,其中定义了通过映射怎么操作设备等等。 ¾ devfs_mk_cdev(MKDEV(keyMajor, 0), S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP, DEVICE_NAME);加载设备到文件系统。 static int __init s3c2410_key_init(void) { int ret; // 初始化Bank6设置 unsigned int bswcon = inl((unsigned int)S3C2410_BWSCON); bswcon = (bswcon & 0xFFFCFFFF) | 0x00000000; outl(bswcon,(unsigned int)S3C2410_BWSCON); bswcon = inl((unsigned int)S3C2410_BWSCON); outb(0xFF,0xE0000000+0x02600000); // 关闭cpld的EINT0,EINT1中断控制 outb(0xF9,0xE0000000+0x02600000); // 打开cpld的EINT0,EINT1中断控制 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 401 - // 注册设备 ret = register_chrdev(keyMajor, DEVICE_NAME, &s3c2410_key_fops); if (ret < 0) { printk(DEVICE_NAME " can't get major number\n"); return ret; } keyMajor = ret; // 加载设备到文件系统 #ifdef CONFIG_DEVFS_FS devfs_mk_cdev(MKDEV(keyMajor,keyMajor),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME); #endif printk(DEVICE_NAME " initialized\n"); return 0; } z static void __exit s3c2410_key_exit(void) 该函数完成相反的过程,并通过 module_exit (s3c2410_key_exit)完成硬件模块的卸载。该函数 完成工作包括: ¾ devfs_remove(DEVICE_NAME),将设备从文件系统中卸载; ¾ unregister_chrdev(keyMajor,DEVICE_NAME),卸载设备的函数,主要传递了设备号和设备 名。 static void __exit s3c2410_key_exit(void) { // 从文件系统中卸载设备 #ifdef CONFIG_DEVFS_FS devfs_remove(DEVICE_NAME); #endif // 卸载设备 unregister_chrdev(keyMajor, DEVICE_NAME); printk(DEVICE_NAME " removed\n"); } 2.file_operations 结构体 static struct file_operations s3c2410_key_fops = { .owner = THIS_MODULE, 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 402 - .open = s3c2410_key_open, .release= s3c2410_key_release, }; 这是一个 file_operations 结构体,给出与虚拟文件系统中文件操作函数相对应的、操作设备的 具体函数。例如,在程序中,对 Key 设备的打开操作实际上是通过 s3c2410_key_open()函数完成的。 在结构体内声明了它与标准打开操作 open()的对应关系后,程序员就可以利用标准的文件打开函数 open(),打开 Key 设备了。也就是说,通过这个结构表的建立我们可以在应用程序中通过 open, release 来调用 s3c2410_key_open(),s3c2410_key_release()。在驱动程序中,实现了这两个函数。 z s3c2410_key_open() 该函数完成了在应用程序中 open()函数对应的操作,也就是对各种参数的初始化,主要是对中 断的申请在 open,close 函数中,其实系统已经默认的做了很多的调用,也就是说如果只是打开文 件关闭文件的话,里面置空就行了,系统会自动完成你所需要的。 ¾ set_irq_type(IRQ_EINT9,IRQT_FALLING),设置中断触发模式; 该函数声明于 include/linux/interrupt.h,而定义于 kernel/irq/chip.c。用于设置中断触发类型。 第一个参数是中断号,第二个参数是中断类型,表示上升沿触发、下降沿触发、高电平触发或者低 电平触发,定义在 include/linux/interrupt.h 中。 ¾ ret = request_irq(IRQ_EINT9, key_interrupt, SA_SHIRQ, DEVICE_NAME, inode),申请中 断。 该函数声明于 include/linux/interrupt.h 中,而它的定义位于 kernel/irq/manage.c 中。用于向系 统申请中断。第一个参数是申请的中断号;第二个参数是一个函数指针,当发生相应的中断时,系 统将调用该函数处理中断;第三个参数是中断的标志;第四个参数就是设备的简称,可以在 /proc/interrupts 列表中找到;第五个参数在中断共享时会用到。一般设置为这个设备的 device 结构 本身或者 NULL。中断处理程序可以用 dev_id 找到相应的控制这个中断的设备,或者用 irq2dev_map 找到中断对应的设备。 static int s3c2410_key_open(struct inode *inode, struct file *filp) { int ret; DPRINTK("open\n"); printk("open\n"); set_irq_type(IRQ_EINT9,IRQT_FALLING); // 设置中断触发类型 // 向内核请求中断 ret = request_irq(IRQ_EINT9, key_interrupt, SA_SHIRQ, DEVICE_NAME, inode); if(ret) { 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 403 - printk("IRQ_EINT9: could not register interrupt\n"); return ret; } printk("register IRQ_EINT9 success\n"); return 0; } z s3c2410_key_release() 该函数完成了在应用程序中 release()函数对应的操作。在本程序中,调用 free_irq()释放分配给 已定中断的内存。 void free_irq(unsigned int irq,void *dev_id) 该函数声明于 include/linux/interrupt.h 中,而它的定义位于 kernel/irq/manage.c 中。用于释放 分配给已定中断的内存。第一个参数是申请的中断号;第二个参数在中断共享时会用到。一般设置 为这个设备的 device 结构本身或者 NULL。中断处理程序可以用 dev_id 找到相应的控制这个中断的 设备,或者用 irq2dev_map 找到中断对应的设备。 static int s3c2410_key_release(struct inode *inode, struct file *filp) { DPRINTK("release\n"); free_irq(IRQ_EINT9,NULL); // 释放中断 return 0; } 3.其他函数 z inl(),outl()和 outb(),inb() 头文件:。这些函数用于避开设备驱动程序,直接去操作 IO 端口。但在之前需先获 取要操作端口地址的权限,可以通过函数 ioperm()函数实现。如果要获取高位端口地址权限,必须 调用 iopl()函数。 z printk() 在内核编程中,我们不能使用用户态 C 库函数中的 printf()函数输出信息,而只能使用 printk()。 但是,内核中 printk()函数的设计目的并不是为了和用户交流,它实际上是内核的一种日志机制,用 来记录下日志信息或者给出警告提示。 每个 printk 都会有个优先级,内核一共有 8 个优先级,它们都有对应的宏定义。如果未指定优 先级,内核会选择默认的优先级 DEFAULT_MESSAGE_LOGLEVEL 。如果优先级数字比 int console_loglevel 变量小的话,消息就会打印到控制台上。如果 syslogd 和 klogd 守护进程在运行的 话,则不管是否向控制台输出,消息都会被追加进/var/log/messages 文件。klogd 只处理内核消息, syslogd 处理其他系统消息,比如应用程序。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 404 - z static irqreturn_t key_interrupt(int irq, void *dev_id, struct pt_regs *regs) 该函数是 Key 中断处理函数,在程序中主要是判断哪个键被按下,并打开对应 cpld 外部中断。 static irqreturn_t key_interrupt(int irq, void *dev_id, struct pt_regs *regs) { unsigned char vector,tmp; vector = inb(CPLD_BANK0_BASE + 0x02200000); // 读取cpld中断状态寄存器 tmp = inb(CPLD_BANK0_BASE + 0x02600000); // 读取cpld中断控制寄存器 if(0 == (vector & 0x4)){ printk("key1\n"); outb(tmp | (1<<2), CPLD_BANK0_BASE+0x02600000); // 屏蔽EINT1中断 inb(CPLD_BANK0_BASE + 0x02600000); outb(tmp & (~(1<<2)), CPLD_BANK0_BASE+0x02600000); // 打开EINT1中断 } else if(0 == (vector & 0x2)){ printk("key2\n"); outb(tmp | (1<<1), CPLD_BANK0_BASE+0x02600000); // 屏蔽EINT0中断 inb(CPLD_BANK0_BASE + 0x02600000); outb(tmp & (~(1<<1)), CPLD_BANK0_BASE+0x02600000); // 打开EINT0中断 } return IRQ_HANDLED; } z static int iskey_down(unsigned long irq) 该函数用来判断是否有键被按下。 static int iskey_down(unsigned long irq){ int reg,gpio_no; irq_no = (int)irq; irq_no = irq_no - 44; // EINT4~9,20~23 if (irq_no < 8){ reg = __raw_readl(S3C2410_GPFDAT); gpio_no = irq_no; } else{ reg = __raw_readl(S3C2410_GPGDAT); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 405 - gpio_no = irq_no - 8; } if (reg & (1 << gpio_no)){ return 0; // 释放按键 } else{ return 1; // 按键未被释放 } } 8. 2. 6 实验步骤 下面介绍如何编译该例程,以及编译好之后,如何下载到开发板上运行(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/): 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将路径切换到$SIMPLEDIR/8.2-key_test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.2-key_test/app $ make $ make install $ make clean 将生成可执行文件 key_test,并自动将该文件拷贝到/home/example/tftp/目录下。 3)将路径切换到$SIMPLEDIR/8.2-key_test/driver 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.2-key_test/driver $ make $ make install $ make clean 将生成驱动模块 eduk4-key.ko,并自动将该文件拷贝到/home/example/tftp/目录下。 4)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 5)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 406 - 6)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 7)启动完成后,在 minicom 下执行以下命令将 led_test、eduk4-key.ko 两个文件下载到相应实 验目录下: # cd /tmp # tftp -g 192.192.192.190 -r./eduk4-key.ko -l/lib/modules/2.6.14/eduk4-key.ko # tftp -g 192.192.192.190 -r./key_test -l./key_test 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 8)给 key_test 添加可执行权限。 # chmod 777 key_test 9)加载驱动模块: # insmod eduk4-key key initialized 10)运行测试 key_test: # ./key_test open register IRQ_EINT9 success Trying to free free IRQ53 此时按下实验平台上的中断按键,minicom 终端将会打印出中断信息: # ./key_test open register IRQ_EINT9 success Trying to free free IRQ53 # key1 key1 key1 key1 key1 key1 key2 key2 key2 key2 key2 key2 10)在 minicom 终端中输入命令卸载驱动模块: # rmmod eduk4-key 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 407 - 8. 2. 7 参考程序 1.驱动程序 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME "key" #define keyRAW_MINOR 1 #define KEY_UP 0 #define KEY_DOWN 1 #define MAX_KEY_BUF 4 #define KEY_TIMER_DELAY (HZ/100) #define BUF_HEAD (keydev.buf[keydev.head]) #define BUF_TAIL (keydev.buf[keydev.tail]) #define INCBUF(x,mod) ((++(x)) & ((mod) - 1)) #define CPLD_BANK0_BASE (0xE0000000) static int irq_no = 0; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 408 - typedef struct { unsigned int keystatus; int buf[MAX_KEY_BUF]; unsigned int head, tail; wait_queue_head_t wq; struct timer_list key_timer; spinlock_t lock; } KEY_DEV; static KEY_DEV keydev; static int keyMajor = 0; #undef DEBUG //#define DEBUG #ifdef DEBUG #define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);} #else #define DPRINTK(x...) (void)(0) #endif #define MAX_TIME 5 //MAX LED wait time void init_gpio(void); static int iskey_down(unsigned long irq){ int reg,gpio_no; irq_no = (int)irq; irq_no = irq_no - 44;//EINT4~9,20~23 if (irq_no < 8){ reg = __raw_readl(S3C2410_GPFDAT); gpio_no = irq_no; } else{ reg = __raw_readl(S3C2410_GPGDAT); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 409 - gpio_no = irq_no - 8; } if (reg & (1 << gpio_no)){ return 0;//key up } else{ return 1;//key still down } } static irqreturn_t key_interrupt(int irq, void *dev_id, struct pt_regs *regs) { unsigned char vector,tmp; vector = inb(CPLD_BANK0_BASE + 0x02200000); tmp = inb(CPLD_BANK0_BASE + 0x02600000); if(0 == (vector & 0x4)){ printk("key1\n"); outb(tmp | (1<<2), CPLD_BANK0_BASE+0x02600000); inb(CPLD_BANK0_BASE + 0x02600000); outb(tmp & (~(1<<2)), CPLD_BANK0_BASE+0x02600000); } else if(0 == (vector & 0x2)){ printk("key2\n"); outb(tmp | (1<<1), CPLD_BANK0_BASE+0x02600000); inb(CPLD_BANK0_BASE + 0x02600000); outb(tmp & (~(1<<1)), CPLD_BANK0_BASE+0x02600000); } return IRQ_HANDLED; } static int s3c2410_key_open(struct inode *inode, struct file *filp) { int ret; DPRINTK("open\n"); printk("open\n"); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 410 - set_irq_type(IRQ_EINT9,IRQT_FALLING); ret = request_irq(IRQ_EINT9, key_interrupt, SA_SHIRQ, DEVICE_NAME, inode); if(ret) { printk("IRQ_EINT9: could not register interrupt\n"); return ret; } printk("register IRQ_EINT9 success\n"); return 0; } static int s3c2410_key_release(struct inode *inode, struct file *filp) { DPRINTK("release\n"); free_irq(IRQ_EINT9,NULL); return 0; } static struct file_operations s3c2410_key_fops = { .owner = THIS_MODULE, .open = s3c2410_key_open, .release = s3c2410_key_release, }; static int __init s3c2410_key_init(void) { int ret; unsigned int bswcon = inl((unsigned int)S3C2410_BWSCON); bswcon = (bswcon & 0xFFFCFFFF) | 0x00000000; outl(bswcon,(unsigned int)S3C2410_BWSCON); bswcon = inl((unsigned int)S3C2410_BWSCON); outb(0xFF,0xE0000000+0x02600000);//关闭cpld的网卡中断控制 outb(0xF9,0xE0000000+0x02600000);//打开cpld的网卡中断控制 ret = register_chrdev(keyMajor, DEVICE_NAME, &s3c2410_key_fops); if (ret < 0) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 411 - { printk(DEVICE_NAME " can't get major number\n"); return ret; } keyMajor = ret; #ifdef CONFIG_DEVFS_FS devfs_mk_cdev(MKDEV(keyMajor,keyMajor),S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP,DEVICE_NAME); #endif printk(DEVICE_NAME " initialized\n"); return 0; } static void __exit s3c2410_key_exit(void) { #ifdef CONFIG_DEVFS_FS devfs_remove(DEVICE_NAME); #endif unregister_chrdev(keyMajor, DEVICE_NAME); printk(DEVICE_NAME " removed\n"); } module_init(s3c2410_key_init); module_exit(s3c2410_key_exit); MODULE_ALIAS("key"); MODULE_DESCRIPTION("S3C2410 KEY DRIVER"); MODULE_LICENSE("GPL"); 2.应用程序 #include #include #include #include #include #include #include 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 412 - #include #include int main(int argc, char *argv[]) { int time; int key_fd; key_fd = open("/dev/key", 0); if (key_fd < 0) { perror("open device key"); exit(1); } return 0; } 8. 2. 8 练习题 改写中断服务子程序,当第一次按 key1/key2 时,点亮某个 LED 灯,当再次按下该 key 时,熄 灭该灯。 8. 3 ADC 操作实验 8. 3. 1 实验目的 ¾ 通过实验理解 S3C2410 下 ADC 接口的操作方法; ¾ 通过实验掌握在 Linux 下应用程序的编写方法。 8. 3. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 413 - 8. 3. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下 ADC 的应用程序,要求隔一小段时间打印得到的 ADC 值。 8. 3. 4 实验原理 1. A/D 转换器(ADC) 随着数字技术,特别是计算机技术的飞速发展与普及,在现代控制、通信及检测领域中,对信 号的处理广泛采用了数字计算机技术。由于系统的实际处理对象往往都是一些模拟量(如温度、压 力、位移、图像等),要使计算机或数字仪表能识别和处理这些信号,必须首先将这些模拟信号转换 成数字信号,这就必须用到 A/D 转换器。 2. A/D 转换的一般步骤 CPS S ADC 取样保持电路 ADC的 量化编码电路 ... D D D n-1 1 0 Iv(t) vI(t) 输入模拟电压 取样展宽信号 数字量输出(n位) 图 8-3-1 模拟量到数字量的转换过程 模拟信号进行 A/D 转换的时候,从启动转换到转换结束输出数字量,需要一定的转换时间,在 这个转换时间内,模拟信号要基本保持不变。否则转换精度没有保证,特别当输入信号频率较高时, 会造成很大的转换误差。要防止这种误差的产生,必须在 A/D 转换开始时将输入信号的电平保持住, 而在 A/D 转换结束后,又能跟踪输入信号的变化。因此,一般的 A/D 转换过程是通过取样、保持、 量化和编码这四个步骤完成的。一般取样和保持主要由采样保持器来完成,而量化编码就由 A/D 转 换器完成。 3. S3C2410X 处理器的 A/D 转换 处理器内部集成了采用近似比较算法(计数式)的 8 路 10 位 ADC,集成零比较器,内部产生 比较时钟信号;支持软件使能休眠模式,以减少电源损耗。其主要特性: z 精度(Resolution):10-bit 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 414 - z 微分线性误差(Differential Linearity Error):± 1.5 LSB z 积分线性误差(Integral Linearity Error):± 2.0 LSB z 最大转换速率(Maximum Conversion Rate):500 KSPS z 输入电压(Input voltage range):0-3.3V z 片上采样保持电路 z 正常模式 z 单独 X,Y 坐标转换模式 z 自动 X,Y 坐标顺序转换模式 z 等待中断模式 4. S3C2410X 处理器 A/D 转换器的使用 处理器集成的 ADC 只使用到两个寄存器,即 ADC 控制寄存器(ADCCON)、ADC 数据寄存器 (ADCDAT)。 z ADC 控制寄存器(ADCCON) 表 8-3-1 ADC 控制寄存器 寄存器 地址 R/W 功能描述 复位值 ADCCON 0x58000000 R/W ADC 控制寄存器 0x3FC4 ADCCON[15]:A/D 转换结束标志 0:A/D 转换正在进行 1:A/D 转换结束 ADCCON[14]: AD 转换预分频允许 0:不允许预分频 1:允许预分频 ADCCON[13:6]:预分频值 PRSCVL PRSCVL在 0 到 255 之间,实际的分频值为 PRSCVL+1 ADCCON[5:3]: 模拟信道输入选择 000 = AIN0 001 = AIN1 010 = AIN2 011 = AIN3 100 = AIN4 101 = AIN5 110 = AIN6 111 = AIN7 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 415 - ADCCON[2]: 待机模式选择位 0:正常模式 1:待机模式 ADCCON[1]: A/D 转换读-启动选择位 0:禁止 Start-by-read 1:允许 Start-by-read ADCCON[0]: A/D 转换器启动 0:A/D 转换器不工作 1:A/D 转换器开始工作 z ADC 数据寄存器(ADCDAT0,ADCDAT1) 表 8-3-2 ADC 数据寄存器(ADCDAT0) 寄存器 地址 R/W 功能描述 复位值 ADCDAT0 0x5800000C R ADC 数据寄存器 - ADCDAT0[15]: 等待中断模式,Stylus 电平选择 0:低电平 1:高电平 ADCDAT0[14]: 自动按照先后顺序转换 X,Y 坐标 0:正常 ADC 顺序 1:按照先后顺序转换 ADCDAT0[13:12]: 自定义 X,Y 位置 00:无操作模式 01:测量 X 位置 10:测量 Y 位置 11:等待中断模式 ADCDAT0[11:10]: 保留 ADCDAT0[9:0]: X坐标转换数据值 表 8-3-3 ADC 数据寄存器(ADCDAT1) 寄存器 地址 R/W 功能描述 复位值 ADCDAT1 0x58000010 R ADC 数据寄存器 - ADCDAT1[15:10]与 ADCDAT0[15:10]功能相同。 ADCDAT0[9:0]: Y坐标转换数据值 A/D 转换的转换时间计算: 例如 PCLK 为 50MHz,PRESCALER=49;所有 10 位转换时间为: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 416 - 50 MHz / (49+1) =1MHz 转换时间为 1/(1M/5 cycles)=5us。 注:A/D 转换器的最大工作时钟为 2.5MHz,所以最大的采样率可以达到 500ksps。 8. 3. 5 实验步骤 下面介绍如何编译该例程,以及编译好之后,如何下载到开发板上运行(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/): 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将路径切换到$SIMPLEDIR/8.3-adc_test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.3-adc_test/app $ make $ make install $ make clean 将生成可执行文件 adc_test,并自动将该文件拷贝到/home/example/tftp/目录下。 3)将路径切换到$SIMPLEDIR/8.3-adc_test/driver 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.3-adc_test/driver $ make $ make install $ make clean 将生成驱动模块 eduk4-adc.ko,并自动将该文件拷贝到/home/example/tftp/目录下。 4)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 5)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 6)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 7)启动完成后,在 minicom 下执行以下命令将 adc_test、eduk4-adc.ko 两个文件下载到相应 实验目录下: # cd /tmp # tftp -g 192.192.192.190 -r./eduk4-adc.ko -l/lib/modules/2.6.14/eduk4-adc.ko # tftp -g 192.192.192.190 -r./adc_test -l./adc_test 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 417 - 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 8)给 adc_test 添加可执行权限。 # chmod 777 adc_test 9)加载驱动模块: # insmod eduk4-adc s3c2410_adc driver initial 10)运行 adc_test,在 minicom 下将输出 ADC 0 通道的值,拨动主板上的滑竿,调整 ADC 的 输入,可看到输出的变化。 /tmp # ./adc_test ADC Channel 0 data = 2.8258 ADC Channel 0 data = 2.8323 ADC Channel 0 data = 2.8258 ADC Channel 0 data = 2.8258 ADC Channel 0 data = 2.8355 ADC Channel 0 data = 0.2581 ADC Channel 0 data = 0.2742 ADC Channel 0 data = 0.2645 ADC Channel 0 data = 0.2613 ADC Channel 0 data = 0.2645 ADC Channel 0 data = 0.0194 ADC Channel 0 data = 0.0226 ADC Channel 0 data = 0.0323 ADC Channel 0 data = 0.0355 ADC Channel 0 data = 0.1387 ADC Channel 0 data = 1.5097 ADC Channel 0 data = 1.5097 ADC Channel 0 data = 1.5032 ADC Channel 0 data = 1.5097 ADC Channel 0 data = 1.5065 ADC Channel 0 data = 1.5000 11)在 minicom 终端中输入命令卸载驱动模块: # rmmod eduk4-adc s3c2410_adc driver removed 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 418 - 8. 3. 6 参考程序 1.应用程序 #include #include #include #include #include #include #include #include #include #include #include #include #include #include "adc.h" int main(int argc, char** argv) { int fd; int buf; float result; struct ADC_DEV adcdev; fd = open("/dev/adc",O_RDWR); if(fd < 0) { printf("Can't open the s3c2410_adc drivers!\n"); return fd; } while(1) { { adcdev.channel = 0; adcdev.prescale = 49; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 419 - write(fd,&adcdev,sizeof(struct ADC_DEV)); read(fd,&buf,sizeof(int)); result = buf*3.3000/0x3FF; printf("ADC Channel %d data = %0.4f \n",adcdev.channel,result); usleep(1000*500); } } close(fd); return 0; } 8. 3. 7 练习题 修改应用程序,当 ADC 转换值改变的幅度在某个范围之外时,显示新值,否则不显示新值。 8. 4 RTC 实时时钟实验 8. 4. 1 实验目的 ¾ 通过实验理解 Linux 与时间相关的系统调用; ¾ 通过实验掌握在 Linux 下应用程序的编写方法。 8. 4. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 8. 4. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统对时间的修改并保存到 RTC 中,系统掉电之后 RTC 仍然正常工作; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 420 - ¾ 读出当前系统时间并显示出来。 8. 4. 4 实验原理 1. 实时时钟(RTC) 实时时钟(RTC)器件是一种能提供日历/时钟、数据存储等功能的专用集成电路,常用作各种 计算机系统的时钟信号源和参数设置存储电路。RTC 具有计时准确、耗电低和体积小等特点,特别 适合在各种嵌入式系统中用于记录事件发生的时间和相关信息,如通信工程、电力自动化、工业控 制等自动化程度高的领域的无人值守环境。随着集成电路技术的不断发展,RTC 器件的新品也不断 推出,这些新品不仅具有准确的 RTC,还有大容量的存储器、温度传感器和 A/D 数据采集通道等, 已成为集 RTC、数据采集和存储于一体的综合功能器件,特别适用于以微控制器为核心的嵌入式系 统。 RTC 器件与微控制器之间的接口大都采用连线简单的串行接口,诸如 I2C、SPI、MICROWIRE 和 CAN 等串行总线接口。这些串口由 2~3 根线连接,分为同步和异步。 2. S3C2410X 实时时钟(RTC)单元 S3C2410X 实时时钟(RTC)单元是处理器集成的片内外设。由开发板上的后备电池供电,可 以在系统电源关闭的情况下运行。RTC 发送 8 位 BCD 码数据到 CPU,传送的数据包括秒、分、小时、 星期、日期、月份和年份。RTC 单元时钟源由外部 32.768KHz 晶振提供,可以实现闹钟(报警)功 能。 S3C2410X 实时时钟(RTC)单元特性: z BCD 数据:秒、分、小时、星期、日期、月份和年份; z 闹钟(报警)功能:产生定时中断或激活系统; z 自动计算闰年; z 无 2000 年问题; z 独立的电源输入; z 支持毫秒级时间片中断,为 RTOS 提供时间基准。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 421 - 图 8-4-1 S3C2410X 处理器 RTC 功能框图 1)读/写寄存器 访问 RTC 模块的寄存器,首先要设 RTCCON 的 bit0 为 1。CPU 通过读取 RTC 模块中寄存器 BCDSEC、BCDMIN、BCDHOUR、BCDDAY、BCDDATE、BCDMON 和 BCDYEAR 的值,得到当前相 应的时间值。然而,由于多个寄存器依次读出,所以有可能产生错误。比如:用户依次读取年(1989)、 月(12)、日(31)、时(23)、分(59)、秒(59)。当秒数为 1 到 59 时,没有任何问题,但是,当 秒数为 0 时,当前时间和日期就变成了 1990 年 1 月 1 日 0 时 0 分。这种情况下(秒数为 0),用户 应该重新读取年份到分钟的值(参考程序设计)。 2)后备电池 RTC 单元可以使用后备电池通过管脚 RTCVDD 供电。当系统关闭电源以后,CPU 和 RTC 的接口 电路被阻断,后备电池只需要驱动晶振和 BCD 计数器,从而达到最小的功耗。 3)闹钟功能 RTC 在指定的时间产生报警信号,包括 CPU 工作在正常模式和休眠(power down)模式下。 在正常工作模式,报警中断信号(ALMINT)被激活。在休眠模式,报警中断信号和唤醒信号(PMWKUP) 同时被激活。RTC 报警寄存器(RTCALM)决定报警功能的使能/屏蔽和完成报警时间检测。 4)时间片中断 RTC 时间片中断用于中断请求。寄存器 TICNT 有一个中断使能位和中断计数。该中断计数自动 递减,当达到 0 时,则产生中断。中断周期按照下列公式计算: Period =(n+1)/ 128 second 其中,n 为 RTC 时钟中断计数,可取值为(1~127)。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 422 - 5)置零计数功能 RTC 的置零计数功能可以实现 30、40 和 50 秒步长重新计数,供某些专用系统使用。当使用 50 秒置零设置时,如果当前时间是 11:59:49,则下一秒后时间将变为 12:00:00。 注意:所有的 RTC 寄存器都是字节型的,必须使用字节访问指令(STRB、LDRB)或字符型指 针访问。 3.硬件电路 R72 10K C54 104 BAT1 BATTERY D9 1N4148 VDDRTC VDD33 GND C47 15P C46 15P X2 CRYSTAL GND EXTAL1 XTAL1 32.768k 图 8-4-2 实时时钟外围电路 8. 4. 5 实验步骤 下面介绍如何编译该例程,以及编译好之后,如何下载到开发板上运行(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/): 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将路径切换到$SIMPLEDIR/8.4-rtc_test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.4-rtc_test/app $ make $ make install $ make clean 将生成可执行文件 rtc_test,并自动将该文件拷贝到/home/example/tftp/目录下。 3)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 423 - Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 4)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 5)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 6)启动完成后,在 minicom 下执行以下命令将 rtc_test 文件下载到相应实验目录下: # cd /tmp # tftp -g 192.192.192.190 -r./rtc_test -l./rtc_test 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 7)给 rtc_test 添加可执行权限。 # chmod 777 rtc_test 8)运行 rtc_test 设置系统时间并保存到 RTC 中,在 minicom 下输入命令./rtc_test –s 回车之后, 根据提示依次输入年份、月份、日期、小时、分钟之后显示时间,然后自动保存到 RTC 中。 /tmp # ./rtc_test -s input year: 2008 input month: 10 input date: 18 input hour: 10 input minute: 01 Sat Oct 18 10:01:00 UTC 2008 9)运行 rtc_test 显示系统时间,在 minicom 下输入命令./rtc_test 回车之后即回显时间。 /tmp # ./rtc_test 2008-10-18 10:15:11 8. 4. 6 参考程序 1.应用程序 #include #include #include int gets_pda(char *buf, int n); int main(int argc, char **argv) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 424 - { char year[5], mon[3], day[3], hour[3], minu[3]; char cmd[40]; if (argc != 1) { if (strcmp(argv[1], "-s") == 0) { // set the time do { printf("input year: "); gets_pda(year, 4); } while(999 > atoi(year) || atoi(year) > 9999); do { printf("input month: "); gets_pda(mon, 2); } while(0 == atoi(mon) || atoi(mon) > 12); if (atoi(mon) < 10) { if (mon[0] != '0') { mon[1] = mon[0]; mon[0] = '0'; mon[2] = 0; } } do { printf("input date: "); gets_pda(day, 2); } while(0 == atoi(day) || atoi(day) > 31); if (atoi(day) < 10) { if (day[0] != '0') { day[1] = day[0]; day[0] = '0'; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 425 - day[2] = 0; } } do { printf("input hour: "); gets_pda(hour, 2); } while(atoi(hour) > 23); if (atoi(hour) < 10) { if (atoi(hour) == 0) { hour[1] = '0'; hour[0] = '0'; hour[2] = 0; } if (hour[0] != '0') { hour[1] = hour[0]; hour[0] = '0'; hour[2] = 0; } } do { printf("input minute: "); gets_pda(minu, 2); } while(atoi(minu) > 60); if (atoi(minu) < 10) { if (atoi(minu) == 0) { minu[1] = '0'; minu[0] = '0'; minu[2] = 0; } if (minu[0] != '0') { minu[1] = minu[0]; minu[0] = '0'; minu[2] = 0; } } 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 426 - strcpy(cmd, "date -s "); strcat(cmd, mon); strcat(cmd, day); strcat(cmd, hour); strcat(cmd, minu); strcat(cmd, year); system(cmd); strcpy(cmd, "hwclock -w"); system(cmd); } } else { // print the time time_t t; struct tm *p; time(&t); p = localtime(&t); printf("%d-%d-%d %d:%d:%d\n", p->tm_year+1900, p->tm_mon+1, p->tm_mday, p->tm_hour, p->tm_min, p->tm_sec); } return 0; } int gets_pda(char *buf, int n) { char c; short i = 0; while((c = getchar()) != '\n' && c != '\r') { if (i < n) { buf[i++] = c; } else { buf[i++] = 0; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 427 - } } buf[n] = 0; return i; } 2.应用程序中系统调用分析 z system(cmd) 该系统调用即将字符串 cmd 当作一条在 shell 中执行的命令,即在程序中调用 system(“date”) 和在命令行下执行 date 命令是一样的。 z p = localtime(&t) localtime 系统调用获取系统当前时间,并保存到变量 t 中,其中 t 的类型为 time_t。 8. 4. 7 练习题 编写一个应用程序,打开设备/dev/misc/rtc,然后实现对其的控制与操作。 8. 5 串口通信实验 8. 5. 1 实验目的 ¾ 通过实验掌握 Linux 下串口的操作方法; ¾ 通过实验掌握在 Linux 下应用程序的编写方法。 8. 5. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 8. 5. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下串口的应用程序,运行时只需将串口线的一端连到 开发板上的 COM1,另一端用一根导线将 RXD1 与 TXD1 短接,即 2 号引脚和 3 号引脚短 接。然后以自发自收的形式,看能否正确收到所发出的数据。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 428 - 8. 5. 4 实验原理 1.S3C2410X 串行通讯(UART)单元 S3C2410X UART 单元提供了三个独立的异步串行通信接口,皆可工作于中断和 DMA 模式。使 用系统时钟最高波特率达 230.4Kbps,如果使用外部设备提供的时钟,可以达到更高的速率。每一 个 UART 单元包含一个 16 字节的 FIFO,用于数据的接收和发送。 S3C2410X UART 支持可编程波特率,红外发送/接收,一个或两个停止位,5bit/6bit/ 7bit/或 8bit 数据宽度和奇偶校验。 2.波特率的产生 波特率由一个专用的 UART 波特率分频寄存器(UBRDIVn)控制,计算公式如下: UBRDIVn =(int)(ULK/(bps x 16))–1 或者 UBRDIVn =(int)(PLK/(bps x 16))–1 其中:时钟选用 ULK 还是 PLK 由 UART 控制寄存器 UCONn[10]的状态决定。如果 UCONn[10]=0, 用 PLK 作为波特率发生,否则选用 ULK 做波特率发生。UBRDIVn 的值必须在 1 到(216-1)之间。 例如:ULK 或者 PLK 等于 40MHz,当波特率为 115200 时, UBRDIVn = (int)(40000000/(115200 x 16) ) -1= (int)(21.7) -1= 21-1 = 20 3.UART 通信操作 下面简略介绍 UART 操作,关于数据发送,数据接收,中断产生,波特率产生,轮流检测模式, 红外模式和自动流控制的详细介绍,请参照相关教材和数据手册。 发送数据帧是可编程的。一个数据帧包含一个起始位,5 到 8 个数据位,一个可选的奇偶校验 位和 1 到 2 位停止位,停止位通过行控制寄存器 ULCONn 配置。 与发送类似,接收帧也是可编程的。接收帧由一个起始位,5 到 8 个数据位,一个可选的奇偶 校验和 1 到 2 位行控制寄存器 ULCONn 里的停止位组成。接收器还可以检测溢出错,奇偶校验错, 帧错误和传输中断,每一个错误均可以设置一个错误标志。 溢出错误(Overrun error)是指已接收到的数据在读取之前被新接收的数据覆盖。 奇偶校验错是指接收器检测到的校验和与设置的不符。 帧错误指没有接收到有效的停止位。 传输中断表示接收数据 RxDn 保持逻辑 0 超过一帧的传输时间。 在 FIFO 模式下,如果 RxFIFO 非空,而在 3 个字的传输时间内没有接收到数据,则产生超时。 4.UART 控制寄存器 z UART 行控制寄存器 ULCONn 该寄存器的第 6 位决定是否使用红外模式,位 5~3 决定校验方式,位 2 决定停止位长度,位 1 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 429 - 和0决定每帧的数据位数。 z UART 控制寄存器 UCONn 该寄存器决定 UART 的各种模式。 UCONn[10]= 1:ULK 做比特率发生;0:PLK 做比特率发生。 UCONn[9] = 1:Tx 中断电平触发;0:Tx 中断脉冲触发。 UCONn[8] = 1:Rx 中断电平触发;0:Rx 中断脉冲触发。 UCONn[7] = 1:接收超时中断允许;0:接收超时中断不允许。 UCONn[6] = 1:产生接收错误中断;0:不产生接收错误中断。 UCONn[5] = 1:发送直接传给接收方式(Loopback);0:正常模式。 UCONn[4] = 1:发送间断信号;0:正常模式发送。 UCONn[3:2] :发送模式选择 00:不允许发送; 01:中断或查询模式 10:DMA0 请求(UART0) DMA3 请求(UART2) 11:DMA1 请求(UART1) UCONn[1:0] :接收模式选择 00:不允许接收 01:中断或查询模式 10:DMA0 请求(UART0) DMA3 请求(UART2) 11:DMA1 请求(UART1) z UART FIFO 控制寄存器 UFCONn UFCONn[7:6] 00:Tx FIFO 寄存器中有 0 个字节就触发中断 01:Tx FIFO 寄存器中有 4 个字节就触发中断 10:Tx FIFO 寄存器中有 8 个字节就触发中断 11:Tx FIFO 寄存器中有 0 个字节就触发中断 UFCONn[5:4] 00:Rx FIFO 寄存器中有 0 个字节就触发中断 01:Rx FIFO 寄存器中有 4 个字节就触发中断 10:Rx FIFO 寄存器中有 8 个字节就触发中断 11:Rx FIFO 寄存器中有 0 个字节就触发中断 UFCONn[3]:保留。 UFCONn[2] = 1:FIFO 复位清零 Tx FIFO;0:FIFO 复位不清零 Tx FIFO 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 430 - UFCONn[1] = 1:FIFO 复位清零 Rx FIFO;0:FIFO 复位不清零 Rx FIFO UFCONn[0] = 1:允许 FIFO 功能;0:不允许 FIFO 功能 z UART MODEM 控制寄存器 UMCONn(n=0 或 1) UMCONn[7:5] 保留,必须全为 0 UMCONn[4] =1:允许使用 AFC 模式; 0:不允许使用 AFC UMCONn[3:1] 保留,必须全为 0 UMCONn[0] =1:激活 nRTS;0:不激活 nRTS z 发送寄存器 UTXH 和接收寄存器 URXH 这两个寄存器存放着发送和接收的数据,当然只有一个字节,8位数据。需要注意的是在发生 溢出错误的时候,接收的数据必须被读出来,否则会引发下次溢出错误。 5.RS232 接口电路 本教学实验平台的电路中,UART1 串口电路如图 8-5-1 所示, UART1 只采用两根接线 RXD1 和 TXD1,因此只能进行简单的数据传输及接收功能。UART1 采用 MAX232 作为电平转换器。 图 8-5-1 UART1 与 S3C2410 的连接图 8. 5. 5 实验步骤 下面介绍如何编译该例程,以及编译好之后,如何下载到开发板上运行(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/): 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 431 - 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将路径切换到$SIMPLEDIR/8.5-uart_test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.5-uart_test/app $ make $ make install $ make clean 将生成可执行文件 uart_test,并自动将该文件拷贝到/home/example/tftp/目录下。 3)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 4)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 5)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 6)启动完成后,在 minicom 下执行以下命令将 uart_test 文件下载到相应实验目录下: # cd /tmp # tftp -g 192.192.192.190 -r./uart_test -l./uart_test 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 7)给 uart_test 添加可执行权限。 # chmod 777 uart_test 8)使用实验附带的单线连接器将位于实验平台主板Area11区的COM_JUMP跳线的RXD与 TXD 短接。 9)运行 uart_test,根据提示在 minicom 下输入一些字符串,如 123445677889900Vcxvcbbzv, 则会在 minicom 下输出发送和接收到的字符串。 # ./uart_test Enter The Sending Data :SEND : 123456789 RECV : 123456789 please Enter the next sending data :123445677889900Vcxvcbbzv SEND : 123445677889900Vcxvcbbzv RECV : 123445677889900Vcxvcbbzv please Enter the next sending data : 如果输入字符串和收到的字符串一致则表明成功。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 432 - 8. 5. 6 参考程序 1.应用程序 #include #include #include #include #include #include #include #include #include #include void init_ttyS(int fd) { struct termios newtio; bzero(&newtio, sizeof(newtio)); tcgetattr(fd, &newtio); // 得到当前串口的参数 cfsetispeed(&newtio, B115200); // 将输入波特率设为115200 cfsetospeed(&newtio, B115200); // 将输出波特率设为115200 newtio.c_cflag |= (CLOCAL | CREAD); // 使能接收并使能本地状态 newtio.c_cflag &= ~PARENB; // 无校验 8位数据位 1位停止位 newtio.c_cflag &= ~CSTOPB; newtio.c_cflag &= ~CSIZE; newtio.c_cflag |= CS8; newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // 原始数据输入 newtio.c_oflag &= ~(OPOST); newtio.c_cc[VTIME] = 0; // 设置等待时间和最小接收字符数 newtio.c_cc[VMIN] = 0; tcflush(fd, TCIFLUSH); // 处理未接收的字符 tcsetattr(fd,TCSANOW,&newtio); // 激活新配置 } int main(int argc, char ** argv) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 433 - { int t; int fd,ret; fd_set uart_w,uart_r; struct timeval tv ; char rcv_buf[256]; // 接收缓存 char wr_buf[256] ="123456789"; // 接收缓存 char *device = "/dev/tts/0"; // 设备路径,初始使用UART0 tv.tv_sec = 3; tv.tv_usec = 0; printf("\nThis is a test about UART \n\n"); // 从UART1打印信息给终端 for(t=1;t (t+1))) { device = argv[t+1]; } } if(!strcmp(device,"/dev/tts/1")) // 不允许使用UART1,因为它已和PC相连。 { printf("can not use /dev/tts/1\n"); return -1; } fd = open(device, O_RDWR); // 打开设备 if (fd < 0) // 设备打开失败 { printf("open device error\n"); return -1; } FD_ZERO(&uart_r); // 清空串口接收端口集 FD_ZERO(&uart_w); // 清空串口发送端口集 FD_SET(fd,&uart_r); // 设置串口接收端口集 FD_SET(fd,&uart_w); // 设置串口发送端口集 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 434 - init_ttyS(fd); // 初始化设备 while(1) { printf("\nEnter The Sending Data :"); while(FD_ISSET(fd,&uart_r)||FD_ISSET(fd,&uart_w)) // 检测串口是否有读写动作 { if(select(fd+1,&uart_r,&uart_w,NULL,&tv)<0) perror("select"); else { if(FD_ISSET(fd,&uart_w)) // 检测串口是否有写动作 { ret = write(fd,wr_buf,256); // 将数据从串口发出去 if(ret>0) // 发送成功,并回显已发送的信息 { *(wr_buf+ret) = '\0'; printf("SEND : %s\n",wr_buf); ret = 0; } else perror("write"); } sleep(1); // 延时,以让串口发送完成 ret = read(fd, rcv_buf, 256); // 试图从串口接收数据 if(ret>0) // 确实接收到了数据,并打印出来 { *(rcv_buf+ret)='\0'; printf("RECV : %s\n",rcv_buf); ret = 0; } else // 没有接收到数据,打印相关信息,并提示输入字符 printf("Havn't received data from uart !\n"); printf("please Enter the next sending data :"); scanf("%s",wr_buf); // 将输入字符保存到发送缓存 } } } 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 435 - close(fd); // 关闭打开的设备 return 0; // 正常返回 } 8. 5. 7 练习题 更改 uart0 的波特率分别设置为不同的值,测试发送和接收的情况。 8. 6 eeprom 读写实验 8. 6. 1 实验目的 ¾ 通过实验学会 Linux 下对 IIC 的操作方法; ¾ 通过实验掌握在 Linux 下驱动和应用程序的编写方法。 8. 6. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 8. 6. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下 IIC 的驱动; ¾ 在 Linux 操作系统下编写应用程序实现将数据写入 EEPROM。 8. 6. 4 实验原理 1. IIC 接口以及 EEPROM IIC 总线为同步串行数据传输总线,其标准总线传输速率为 100kb/s,增强总线可达 400kb/s。 总线驱动能力为 400pF。S3C2410X RISC 微处理器能支持多主 IIC 总线串行接口。下图为 IIC 总线 的内部结构框图。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 436 - 图 8-6-1 IIC 总线内部结构框图 IIC 总线可构成多主和主从系统。在多主系统结构中,系统通过硬件或软件仲裁获得总线控制 使用权。应用系统中 IIC 总线多采用主从结构,即总线上只有一个主控节点,总线上的其它设备都 作为从设备。IIC 总线上的设备寻址由器件地址接线决定,并且通过访问地址最低位来控制读写方向。 目前,通用存储器芯片多为 EEPROM,其常用的协议主要有两线串行连接协议(IIC)和三线串 行连接协议。带 IIC 总线接口的 EEPROM 有许多型号,其中 AT24CXX 系列使用十分普遍。产品包 括 AT2401/02/04/08/16 等,其容量(字节数 x 页)分别为 128x8/256x8/512x8/1024x8/2048x8, 适用于 2V~5V 的低电压的操作,具有低功耗和高可靠性等优点。 AT24 系列存储器芯片采用 CMOS 工艺制造,内置有高压泵,可在单电压供电条件下工作。其标 准封装为 8 脚 DIP 封装形式,如图 8-6-2。 图 8-6-2 AT24 系列 EEPROM 的 DIP8 封装示意图 各引脚的功能说明如下: z SCL ---- 串行时钟。遵循 ISO/IEC7816 同步协议;漏极开路,需接上拉电阻。在该引脚的 上升沿,系统将数据输入到每个 EEPROM 器件,在下降沿输出; z SDA ---- 串行数据线。漏极开路,需接上拉电阻。双向串行数据线,漏极开路,可与其他 开路器件“线或”; z A0、A1、A2 ---- 器件/页面寻址地址输入端。在 AT24C01/02 中,引脚被硬连接;其他 AT24Cxx 均可接寻址地址线; z WP ---- 读写保护。接低电平时可对整片空间进行读写;高电平时不能读写受保护区; z Vcc/GND ---- 一般输入+5V 的工作电压。 2. IIC 总线的读写控制逻辑 z 开始条件(START_C)在开始条件下,当 SCL 为高电平时,SDA 由高转为低。 z 停止条件(STOP_C)在停止条件下,当 SCL 为高电平时,SDA 由低转为高。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 437 - z 确认信号(ACK) 在接收方应答下,每收到一个字节后便将 SDA 电平拉低。 z 数据传送(Read/Write) IIC 总线启动或应答后 SCL 高电平期间数据串行传送;低电平期 间为数据准备,并允许 SDA 线上数据电平变换。总线以字节(8bit)为单位传送数据,且 高有效位(MSB)在前。IIC 数据传送时序如图 8-6-3 所示: 图 8-6-3 IIC 总线信号的时序 3. EEPROM 读写操作 本实验平台设计了 IIC 总线扩展接口,用于各 IIC 通信设备,也通过扩展接口提供给用户定制 模块。本实验中使用 S3C2410X 处理器内置的 IIC 控制器作为 IIC 通信主设备,AT24C02 EEPROM 为从设备。电路设计如图 8-6-4 所示: 图 8-6-4 AT24C02 控制电路 AT24C02 的存储容量为 256×8 个字节(2K),器件地址是 1010,A0、A1、A2 三位地址线决定 了芯片的访问地址与要访问的部件。在本实验平台中,A0、A1、A2 分别接地,故在本实验平台中只 能操作 256 个字节的存储单元。 AT24C02 由输入缓冲器和 EEPROM 阵列组成。由于 EEPROM 的半导体工艺特性写入时间为 5-10ms,如果从外部直接写入 EEPROM ,每写一个字节都要等候 5-10ms,成批数据写入时则要等 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 438 - 候更长的时间。具有 SRAM 输入缓冲器的 EEPROM 器件,其写入操作变成对 SRAM 缓冲器的装载, 装载完后启动一个自动写入逻辑将缓冲器中的全部数据一次写入 EEPROM 阵列中。对缓冲器的输入 称为页写,缓冲器的容量称为页写字节数。AT24C02 的页写字节数为 8,占用最低 3 位地址。写入 不超过页写字节数时,对 EEPROM 器件的写入操作与对 SRAM 的写入操作相同;若超过页写字节数 时,应等候 5-10ms 后再启动一次写操作。 由于 EEPROM 器件缓冲区容量较小(只占据最低 3 位),且不具备溢出进位检测功能,所以, 从非零地址写入 8 个字节数或从零地址写入超过 8 个字节数会形成地址翻卷,导致写入出错。 1)AT24C02 写操作 AT24C02 支持字节写和页写两种模式。 在字节写模式下,主器件发送起始命令和从器件地址信息(R/W 位置零)给从器件,在从器件 产生应答信号后,主器件发送 AT24C02 的字节地址,主器件在收到从器件的另一个应答信号后,再 发送数据到被寻址的存储单元。AT24C02 再次应答,并在主器件产生停止信号后开始内部数据的擦 写,在内部擦写过程中,AT24C02 不再应答主器件的任何请求。如图 8-6-5,是 AT24C02 的字节写 模式的时序图。 图 8-6-5 AT24C02 的字节写时序图 在页写模式下,AT24C02 可以一次写入 8 个字节的数据。页写操作的启动和字节写一样,不同 在于传送了一字节数据后并不产生停止信号,主器件被允许发送 7 个额外的字节,每发送一个字节 数据后 AT2402 产生一个应答位并将字节地址低位加 1,高位保持不变。如果在发送停止信号之前, 主器件发送超过 8 个字节,地址计数器将自动翻转,先前写入的数据被覆盖。接收到 8 个字节数据 和主器件发送的停止信号后,AT24C02 启动内部写周期将数据写到数据区,所有接收的数据在一个 写周期内写入 AT24C02。如图 8-6-6,是 AT24C02 的页写模式的时序图。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 439 - 图 8-6-6 AT24C02 的页写时序图 2)AT24C02 读操作 AT24C02 支持立即地址读、选择读和连续读三种模式。 在立即地址读模式下,AT24C02 的地址计数器内容为最后操作字节的地址加 1,如是上次读/写 的操作地址为 N,则立即读的地址为 N+1。如果 N=255,则计数器将翻转到 0 且继续输出数据。 AT24C02 接收到从器件地址信号后(R/W 位置 1),它首先发送一个应答信号,然后发送一个 8 位字 节数据。主器件不需要发送一个应答信号,但要产生一个停止信号。 图 8-6-7 AT24C02 的立即地址读模式时序图 在选择读模式下,允许主器件对 AT24C02 的任意字节进行读操作,主器件首先通过发送起始信 号、从器件地址和它想读取的字节数据的地址执行一下伪写操作。在 AT24C02 应答之后,主器件重 新发送起始信号和从器件地址,此时 R/W 置 1,AT24C02 响应并发送应答信号,然后输出所要求的 一个 8 位字节数据,主器件不能发送应答信号但产生一个停止信号。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 440 - 图 8-6-8 AT24C02 的选择读模式时序图 在连续读模式下(连续读操作可通过立即读或选择性读操作启动),AT24C02 发送完一个 8 位 字节数据后,主器件产生一个应答信号为响应,告知 AT24C02 主器件要求更多的数据,对应每个主 机产生的应答信号 AT24C02 将发送一个 8 位数据字节。当主器件不发送应答信号而发送停止位时结 束此操作。从 AT24C02 输出的数据按顺序由 N 到 N+1 输出。读操作时地址计数器在 AT24C02 整个 地址内增加,这样整个寄存器区域都可以在一个读操作内全部读出。当读取的字节超过 255,计数 器将翻转到零并继续输出数据字节。 图 8-6-9 AT24C02 的连续读模式时序图 4. S3C2410 处理器 IIC 接口 S3C2410X 处理器为用户进行应用设计提供了支持多主总线的 IIC 接口。处理器提供符合 IIC 协 议的设备连接的双向数据线 IICSDA 和 IICSCL,在 IICSCL 高电平期间,IICSDA 的下降沿启动上升 沿停止。S3C2410X 处理器可以支持主发送、主接收、从发送、从接收四种工作模式。在主发送模 式下,处理器通过 IIC 接口与外部串行器件进行数据传送,需要使用到如下寄存器: z IIC 总线控制寄存器 IICCON 寄存器 地址 读/写 描述 复位值 IICCON 0x54000000 R/W IIC 总线控制寄存器 0x0X IICCON 位 描述 初始值 应答使能 [注 1] [7] IIC 总线应答使能位 0 :禁止,1 :使能 在输出模式下,IICSDA 在 ACK 时间被释放 在输入模式下,IICSDA 在 ACK 时间被拉低 0 输出时钟源选择 [6] IIC 总线发送时钟预分频选择位 0:IICCLK = fPCLK /16 1:IICCLK = fPCLK /512 0 发送/接收中断使能 [注 3] [5] IIC 总线中断使能位 0:禁止,1 :使能 0 中断未决位[注 2] [4] IIC 总线未处理中断标志。不能对这一位写入 1,置 1 是系统 0 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 441 - 自动产生的。当这位被置 1,IICSCL 信号将被拉低,IIC 传 输也停止了。如果想要恢复操作,将该位清零。 0:1) 当读出 0 时,没有发生中断 2) 当写入 0 时,清除未决条件并恢复中断响应 1:1) 当读出 1 时,发生了未决中断 2) 不可以进行写入操作 发送时钟值 [3:0] 发送时钟预分频器的值,这四位预分频器的值决定了 IIC 总 线进行发送的时钟频率,对应关系如下: Tx clock = IICCLK/(IICCON[3:0]+1). Undefined 注意: 1.在 Rx 模式下访问 EEPROM 时,为了产生停止条件,在读取最后一个字节数据之后不允许产 生 ACK 信号。 2.IIC 总线上发生中断的条件:1)当一个字节的读写操作完成时;2)当一个通常的通话发生 或者是从地址匹配上时;3)总线仲裁失败时。 3.如果 IICON[5]=0,IICON[4]就不能够正常工作了。因此,建议务必将 IICCON[5]设置为 1, 即使暂时并不用 IIC 中断。 z IIC 总线状态寄存器 IICSTAT(地址:0x54000004) IICSTAT 位 描述 初始值 模式选择 [7:6] IIC 总线主从,发送/接收模式选择位。 00:从接收模式;01:从发送模式;10:主接收模式;11: 主发送模式 00 忙信号状态/起始/停 止条件 [5] IIC 总线忙信号状态位 0:读出为 0,表示状态不忙;写入 0,产生停止条件 1:读出为 1,表示状态忙;写入 1,产生起始条件 IICDS 中的数据在起始条件之后自动被送出 0 串行数据输出使能 [4] IIC 总线串行数据输出使能/禁止位 0 :禁止发送/接收;1:使能发送接收 0 仲裁状态位 [3] IIC 总线仲裁程序状态标志位 0 :总线仲裁成功 1:总线仲裁失败 0 从地址状态标志位 [2] IIC 总线从地址状态标志位 0:在探测到起始或停止条件时,被清零; 1:如果接收到的从器件地址与保存在 IICADD 中的地址相 符,则置 1 0 0 地址状态标志位 [1] IIC 总线 0 地址状态标志位 0:在探测到起始或停止条件时,被清零; 1:如果接收到的从器件地址为 0,则置 1 0 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 442 - 应答位状态标志 [0] 应答位(最后接收到的位)状态标志 0:最后接收到的位为 0 (ACK 接收到了) 1:最后接收到的位为 1 (ACK 没有接收到) 0 z IIC 总线地址寄存器 IICADD(地址:0x54000008) IICADD 位 描述 初始值 从器件地址 [7:0] 7 位从器件地址:如果 IICSTAT 中的串行数据输出使能位为 0, IICADD 就变为写使能。IICADD 总为可读 XXXXXXXX z IIC 总线发送接收移位寄存器 IICDS(地址:0x5400000C) IICDS 位 描述 初始值 数据移位寄存器 [7:0] IIC 接口发送/接收数据所使用的 8 位数据移位寄存器:当 IICSTAT 中的串行数据输出使能位为 1,则 IICDS 写使能。IICDS 总为可读 XXXXXXXX 5. 驱动程序分析 1)Linux 中 IIC 总线驱动体系结构 Linux 中 I2C 总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。其中总线驱 动的职责,是为系统中每个 I2C 总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通 讯,它只是存在在那里,等待设备驱动调用其函数。 设备驱动则是与挂在 I2C 总线上的具体的设备通讯的驱动。通过 I2C 总线驱动提供的函数,设 备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。 在 Linux 系统中,对于一个给定的 I2C 总线硬件配置系统,I2C 总线驱动程序体系结构由 I2C 总线驱动和 I2C 设备驱动组成。其中 I2C 总线驱动包括一个具体的控制器驱动和 I2C 总线的算法驱 动。一个算法驱动适用于一类总线控制器。而一个具体的总线控制器驱动要使用某一种算法。例如, Linux 内核中提供的算法 i2e-algo-8260 可以用在 MPC82xx 系列处理器提供的 I2C 总线控制器上。 Linux 内核中提供了一些常见处理器如 MPC82xx 系列的算法驱动。对于 I2C 设备,基本上每种具体 设备都有自己的基本特性,其驱动程序一般都需要特别设计。 在I2C总线驱动程序体系结构中,使用数据结构Driver来表示I2C设备驱动,使用数据结构Client 表示一个具体的 I2C 设备。而对于 I2C 总线控制器,各种总线控制器在进行数据传输时采用的算法 有好多种,使用相同算法的控制器提供的控制接口也可能不同。在 I2C 总线驱动程序体系结构中, 用数据结构 Algorithm 来表示算法,用数据结构 Adapter 来表示不同的总线控制器。Linux 内核的 I2C 总线驱动程序体系结构如图 8-6-10 所示。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 443 - 图 8-6-10 Linux 内核 I2C 总线驱动程序体系结构 在图 8-6-10 中,一个 Client 对象对应一个具体的 I2C 总线设备,而一种 I2C 设备的 Driver 可以 同时支持多个 Client。每个 Adapter 对应一个具体的 I2C 总线控制器,不同的 I2C 总线控制器可以 使用相同的算法 Algorithm。i2c-core 是 I2C 总线驱动程序体系结构的核心,在这个模块中,除了为 总线设备驱动提供了一些统一的调用接口来访问具体的总线驱动程序功能,以进行读写或设置操作 外,还提供了将各种支持的总线设备驱动和总线驱动添加到这个体系中的方法,以及当不再使用这 些驱动时将其从体系中删除的方法。i2c-core 将总线驱动程序体系一分为二,相互独立。可以针对 某个 I2C 总线设备来设计一个 I2C 设备驱动程序,而不需要关心系统的 I2C 总线控制器是何种类型, 所以提高了其可移植性。另一方面,在设计 I2C 总线驱动时也可以不要考虑其将用来支持何种设备。 因为 i2c-core 提供了统一的接口,所以也为设计这两类驱动提供了方便。 该驱动程序简单函数调用如下所示: |---module_init(at24c_init) |---at24c_init() |---i2c_add_driver(&at24c_driver) |---module_exit(at24c_exit) |---at24c_exit(); |---i2c_del_driver(&at24c_driver) |---at24c_driver |-->attach_adapter: . |---at24c_attach_adapter(struct i2c_adapter *adapter) . |---i2c_check_functionality(adapter, I2C_FUNC_I2C) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 444 - . . |---dev_dbg(&adapter->dev, "%s probe, no I2C\n",at24c_driver.name) . |---i2c_probe(adapter, &addr_data, at24c_old_probe) . . |---at24c_old_probe(struct i2c_adapter *adapter, int address, int kind) . . |---which_chip(adapter, address, &writable) . . . |---dev_err(&adapter->dev, "no chipname for addr %d\n",address) . . . |---find_chip(name) . . . |---dev_dbg(&adapter->dev, "addr %d not in probe[]\n", address) . . |---at24c_activate(adapter, address, chip, writable) . . . |---i2c_set_clientdata(&at24c->client, at24c) . . . |---i2c_smbus_write_quick(&at24c->client, 0) . . . |---i2c_attach_client(&at24c->client) . . . |---sysfs_create_bin_file(&at24c->client.dev.kobj, &at24c->bin) . . |---at24c->bin . . |---at24c_bin_read() . . . |---to_i2c_client() . . . |---i2c_get_clientdata(client) . . . |---at24c_ee_read(at24c, buf, off, count) . . . |--- at24c_ee_address() . . . |---i2c_transfer(at24c->client.adapter, &msg, 1) . . |---at24c_bin_write() . . |---to_i2c_client() . . |---i2c_get_clientdata(client) . . |---at24c_ee_write(at24c, buf, off, count) . . |---at24c_ee_address() . . |---i2c_transfer(at24c->client.adapter, &msg, 1) |-->detach_client: |---at24c_detach_client(struct i2c_client *client) |---i2c_detach_client(client) |---i2c_get_clientdata(client) 2)模块的加载和卸载 module_init(at24c_init); module_exit(at24c_exit); 系统使用这两个函数实现 Beep 模块的加载和卸载工作,分别调用 at24c_init(void), at24c_exit(void)来实现。 z static int __init at24c_init(void) 在该函数中,主要进行一些参数的初始化,以及通过调用 i2c-core 提供的 i2c_add_driver 函数 来注册该设备。 static int __init at24c_init(void) 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 445 - { // 修正一下系统参数 n_chip_names = 1; n_probe = 1; chip_names[0] = "24c02"; probe[0] = 0; probe[1] = 0x50; // 注册at24c_driver return i2c_add_driver(&at24c_driver); } z static void __exit at24c_exit(void) 在该函数中,在模块退出函数中调用 i2c_del_driver 函数来注销该设备。 static void __exit at24c_exit(void) { i2c_del_driver(&at24c_driver); } 3)I2C 设备驱动的设计 I2C 设备 EEPROM 驱动除了要根据 EEPROM 的具体特性进行设计外。还要考虑 I2C 总线驱动程 序体系结构的特性。在 EEPROM 设备驱动程序中需要实现一个 i2c_driver 结构。每个对应于具体设 备的 Client 都从这个结构来构造。在 i2c_driver 结构中有两个函数 attach_adapter 和 detach_client 必须要实现。i2c_driver 结构的定义如下: static struct i2c_driver at24c_driver = { .owner = THIS_MODULE, .name = "at24c", .flags = I2C_DF_NOTIFY, .attach_adapter = at24c_attach_adapter, .detach_client = __devexit_p(at24c_detach_client), }; 在设备驱动中,向 EEPROM 写数据通过调用 i2c-core 提供的 i2c_master_send 函数来完成。从 EEPROM 读取数据通过另一个函数 i2c_master_read 来完成。与一般设备驱动不同的地方就是在 EEPROM 驱动模块初始函数中要调用 i2c-core 提供的 i2c_add_driver 函数来注册该设备。在模块退 出函数中调用 i2c_del_driver 函数来注销该设备。 i2c_driver 一旦装入完成,其中的 attach_adapter 函数即函数 at24c_attach_adapter()就会被调 用。在其中可以遍历系统中的每个 i2c 总线驱动,探测想要访问的设备。注意探测可能会找到多个 设备,因而不仅一个 I2C 总线可以挂多个不同类型的设备,一个设备驱动也可以同时为挂在多个不 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 446 - 同 I2C 总线上的设备服务。每当设备驱动探测到了一个它能支持的设备,它就创建一个 struct i2c_client 来标识这个设备: new_client->addr = address; new_client->adapter = adapter; new_client->driver = &driver; // 发现新设备 err = i2c_attach_client(new_client); if (err) goto error; 可见,一个 i2c_client 代表着位于 adapter 总线上,地址为 address,使用 driver 来驱动的一个 设备。它将总线驱动与设备驱动,以及设备地址绑定在了一起。一个 i2c_client 就代表着一个 I2C 设备。 z static int __devinit at24c_attach_adapter(struct i2c_adapter *adapter) 该函数 i2c_driver 数据结构中的 attach_adapter,主要功能是调用 I2C core 层提供的 i2c_probe 函数查找一条 I2C 总线,看是否有 at24c 设备存在,如果存在 at24c,则将对于的 I2C adapter 和 at24c 设备挂接在一起,调用函数 which_chip()识别设备。调用函数 at24c_activate()使能 at24c,调用 i2c_attach_client()向 I2C core 层注册 at24c,并 通 过 该 I2C adapter 来实现对 at24c 的访问,即通过 函数 at24c_bin_read(),at24c_bin_write()实现对 at24c 的读写操作。 static int __devinit at24c_attach_adapter(struct i2c_adapter *adapter) { // 如果适配器支持所有功能,返回1,否则返回0 if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) { dev_dbg(&adapter->dev, "%s probe, no I2C\n",at24c_driver.name); return 0; } // 该函数在i2c_core中定义,查找一条I2C总线,看是否有at24c设备存在 return i2c_probe(adapter, &addr_data, at24c_old_probe); } z static int __devinit at24c_old_probe(struct i2c_adapter *adapter, int address, int kind) static int __devinit at24c_old_probe(struct i2c_adapter *adapter, int address, int kind) { const struct chip_desc *chip; int writable; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 447 - // 识别设备 chip = which_chip(adapter, address, &writable); if (chip) at24c_activate(adapter, address, chip, writable); // 使能设备 return 0; } z static int __devexit at24c_detach_client(struct i2c_client *client) 该函数对应 i2c_driver 数据结构中的 detach_client,主要功能调用 i2c_detach_client()向 I2C core 层注销 at24c。 static int __devexit at24c_detach_client(struct i2c_client *client) { int err; struct at24c_data *at24c; // 将设备从i2c core层中注销 err = i2c_detach_client(client); if (err) { dev_err(&client->dev, "deregistration failed, %d\n", err); return err; } at24c = i2c_get_clientdata(client); if (at24c->users-- == 0) kfree(at24c); return 0; } 8. 6. 5 实验步骤 下面介绍如何编译该例程,以及编译好之后,如何下载到开发板上运行(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/): 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh $ source /usr/crosstool/gcc-3.4.5-glibc-2.3.6/arm-linux/path.sh 2)将路径切换到$SIMPLEDIR/8.6- eeprom _test/app 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.6-eeprom_test/app 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 448 - $ make $ make install $ make clean 将生成可执行文件 eeprom_test,并自动将该文件拷贝到/home/example/tftp/目录下。 3)将路径切换到$SIMPLEDIR/8.6-eeprom_test/driver 下,执行以下命令编译应用程序: $ cd $SIMPLEDIR/8.6-eeprom_test/driver $ make $ make install $ make clean 将生成驱动模块 eduk4-eeprom.ko,并自动将该文件拷贝到/home/example/tftp/目录下。 4)准备好 EduKit-IV + Mini2410-IV 平台,注意 Mini2410-IV 板上的跳线为闭合状态,且确保 Mini2410-IV 板载 Linux 映像为实验映像(请参照下册开篇:实验环境构建),连接好交叉串口线于 板载 COM2 和 PC 端串口,连接好交叉网线与板载主板网卡接口和 PC 端网口。 5)在终端执行以下命令打开 minicom 串口终端: $ sudo minicom 6)给实验平台加电,进入 Linux 系统,可以看到 minicom 终端的启动打印信息。 7)启动完成后,在 minicom 下执行以下命令将 eeprom_test、eduk4-eeprom.ko 两个文件下载 到相应实验目录下: # cd /tmp # tftp -g 192.192.192.190 -r./eduk4-eeprom.ko -l/lib/modules/2.6.14/eduk4-eeprom.ko # tftp -g 192.192.192.190 -r./eeprom _test -l./eeprom _test 附:请用交叉网线连接主机和实验平台,注意实验平台上的网卡是打开的,并核对主机网卡的 ip 地址是否为 192.192.192.190,设置方法可参照上册第六章节 6.5。 8)给 eeprom_test 添加可执行权限。 # chmod 777 eeprom_test 9)加载驱动模块: # insmod eduk4-eeprom at24c 0-0050: 256 byte 24c02 I2C EEPROM (writable) 10)运行测试 eeprom_test: 11)运行 eeprom_test 之后,将 0-7 这八个数字写入 EEPROM,然后再读出来,并显示操作结 果。 # ./eeprom_test Write 9 byte at 0x0 Read 8 byte at 0x0 0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 449 - 如果读到的数据为 0-7,则表明成功。 12)在 minicom 终端中输入命令卸载驱动模块: # rmmod eduk4-eeprom 8. 6. 6 参考程序 1.应用程序 #include #include #include #include "i2c.h" #define CHIP_ADDR 0x50 #define PAGE_SIZE 8 #define I2C_DEV "/dev/i2c/0" static int read_eeprom(int fd, int addr, char buff[], int count) { int res; if(write(fd, &addr, 1) != 1){ printf("write addr error!\n"); return -1; } res = read(fd, buff, count); printf("read %d byte at 0x%x\n", res, addr); return res; } // size of buffer can't exceed 1 page_size static int write_eeprom(int fd, int addr, char buff[], int count) { int res; static char sendbuffer[PAGE_SIZE+1]; sendbuffer[0] = addr; memcpy(sendbuffer+1, buff, count); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 450 - res = write(fd, sendbuffer, count+1); printf("write %d byte at 0x%x\n", res, addr); } int main(void) { int fd, n, res; unsigned char buf[PAGE_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7}; fd = open(I2C_DEV, O_RDWR); if(fd < 0){ printf("###i2c test device open failed###\n"); return -1; } res = ioctl(fd, I2C_TENBIT, 0); res = ioctl(fd, I2C_SLAVE, CHIP_ADDR); write_eeprom(fd, 0, buf, sizeof(buf)); memset(buf, 0, sizeof(buf)); read_eeprom(fd, 0, buf, sizeof(buf)); for(n = 0; n < sizeof(buf); n++){ printf("0x%x, ", buf[n]); } close(fd); return 0; } 8. 6. 7 练习题 试着创建一个文本文件,然后输入一些内容,编写程序将该文件的内容保存到 EEPROM 中,并 将内容读出来与原文件对比。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 451 - 第九章 基于 Mini2410 的高级驱动开发 本章节主要介绍 Mini2410 下高级驱动开发,内容包括 LCD 显示驱动开发实验、TSP 控制实验、 SD 操作实验、IIS 通信实验、USB 通信实验、CAN 通信实验等一些基础驱动程序的编写,通过这些 可以了解 Linux 下高级驱动程序的开发方法。 9. 1 LCD 显示驱动开发实验 9. 1. 1 实验目的 ¾ 通过实验学会 Linux 下对 IIC 的操作方法; ¾ 通过实验掌握在 Linux 下驱动和应用程序的编写方法。 9. 1. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 9. 1. 3 实验内容 ¾ 编写 EduKit-IV 实验箱 Linux 操作系统下 IIC 的驱动; ¾ 在 Linux 操作系统下编写应用程序实现将数据写入 EEPROM。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 452 - 9. 1. 4 实验原理 1.液晶显示屏(LCD) 液晶屏(LCD:Liquid Crystal Display)主要用于显示文本及图形信息。液晶显示屏具有轻薄、 体积小、低耗电量、无辐射危险、平面直角显示以及影像稳定不闪烁等特点,因此在许多电子应用 系统中,常使用液晶屏作为人机界面。 z 主要类型及性能参数 液晶显示屏按显示原理分为 STN 和 TFT 两种: STN(Super Twisted Nematic,超扭曲向列)液晶屏。 STN 液晶显示器与液晶材料、光线的干涉现象有关,因此显示的色调以淡绿色与橘色为主。STN 液晶显示器中,使用 X、Y 轴交叉的单纯电极驱动方式,即 X、Y 轴由垂直与水平方向的驱动电极构 成,水平方向驱动电压控制显示部分为亮或暗,垂直方向的电极则负责驱动液晶分子的显示。STN 液晶显示屏加上彩色滤光片,并将单色显示矩阵中的每一像素分成三个子像素,分别通过彩色滤光 片显示红、绿、蓝三原色,也可以显示出色彩。单色液晶屏及灰度液晶屏都是 STN 液晶屏。 TFT(Thin Film Transistor,薄膜晶体管)彩色液晶屏。 随着液晶显示技术的不断发展和进步,TFT 液晶显示屏被广泛用于制作成电脑中的液晶显示设 备。TFT 液晶显示屏既可在笔记本电脑上应用(现在大多数笔记本电脑都使用 TFT 显示屏),也 常 用 于主流台式显示器。分 65536 色及 26 万色,1600 万色三种,其显示效果非常出色。TFT 的显示采 用“背透式”照射方式——假想的光源路径不是像 TN 液晶那样从上至下,而是从下向上。这样的作法 是在液晶的背部设置特殊光管,光源照射时通过下偏光板向上透出。由于上下夹层的电极改成 FET 电极和共通电极,在 FET 电极导通时,液晶分子的表现也会发生改变,可以通过遮光和透光来达到 显示的目的,响应时间大大提高到 80ms 左右。 使用液晶显示屏时,主要考虑的参数有外形尺寸、象素、点距、色彩等。以下是 Embest EduKit-IV 实验板所选用的液晶屏(LQ080V3DG01 TFT)主要参数: 表 9-1-1 LQ080V3DG01 TFT 液晶屏主要技术参数 型号 LQ080V3DG01 外形尺寸 183×141×14 mm 重量 390g 像素 640×480 点距 0.2535 X0.2535 mm 色彩 262144 电压 5V(25℃) 对比度 250 附加 带驱动逻辑 液晶屏外形如图 9-1-1 所示: 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 453 - 图 9-1-1 LQ080V3DG01 TFT 液晶屏外形 z 驱动与显示 液晶屏的显示要求设计专门的驱动与显示控制电路。驱动电路包括提供液晶屏的驱动电源和液 晶分子偏置电压,以及液晶显示屏的驱动逻辑;显示控制部分可由专门的硬件电路组成,也可以采 用集成电路(IC)模块,比如 EPSON 的视频驱动器等;还可以使用处理器外围 LCD 控制模块。实 验板的驱动与显示系统包括 S3C2410 片内外设 LCD 控制器、液晶显示屏的驱动逻辑以及外围驱动电 路。 2.S3C2410 LCD 控制器 1)LCD 控制器特点 S3C2410 处理器集成了 LCD 控制器,主要功能是 S3C2410 LCD 控制器用于传输显示数据和产 生控制信号,并且它支持屏幕水平和垂直滚动显示。数据的传送采用 DMA(直接内存访问)方式, 以达到最小的延迟。它可以支持多种液晶屏: z STN LCD: — 支持 3 种类型的扫描方式:4 位单扫描,4 位双扫描和 8 位单扫描; — 支持单色,4 级灰度和 16 级灰度显示; — 支持 256 色和 4096 色彩色 STN LCD; — 支持多种屏幕大小。 典型的实际屏幕大小是:640×480,320×240,160×160 和其它; 最大虚拟屏幕占内存大小为 4M 字节; 256 色模式下最大虚拟屏幕大小:4096×1024,2048×2048,1024×4096 和其它。 z TFT LCD: — 支持 1,2,4 或 8bpp 彩色调色显示; 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 454 - — 支持 16bpp 和 24bpp 非调色真彩显示; — 在 24bpp 模式下,最多支持 16M 种颜色; — 支持多种屏幕大小。 典型的实际屏幕大小是:640×480,320×240,160×160 和其它; 最大虚拟屏幕占内存大小为 4M 字节; 64K 色模式下最大虚拟屏幕大小:2048×1024 和其它。 2)LCD 控制器内部结构 LCD 控制器主要提供液晶屏显示数据的传送、时钟和各种信号的产生与控制功能。S3C2410 处 理器的 LCD 控制器主要部分框图如图 9-1-2 所示: 图 9-1-2 LCD 控制器框图 S3C2410 LCD 控制器用于传输显示数据和产生控制信号,例如 VFRAME,VLINE,VCLK,VM 等等。除了控制信号之外,S3C2410 还提供数据端口供显示数据传输,也就是 VD[23:0],如 图 9-1-2 所示。LCD 控制器包含 REGBANK,LCDCDMA,VIDPRCS,TIMEGEN 和 LPC3600 等控制模块。REGBANK 中有 17 个可编程的寄存器组和 256X16 调色板内存用于配置 LCD 控制器。LCDCDMA 是一个专用的 DMA,它负责自动的将帧缓冲区中的显示数据发往 LCD 驱动器。通过特定的 DMA,显示数据可以不 需要 CPU 的干涉,自动的发送到屏幕上。VIDPRCS 将 LCDCDMA 发送过来的数据变换为合适的格式 (例如 4/8 位单扫描或 4 位双扫描显示模式)之后通过 VD[23:0]发送到 LCD 驱动器。TMIEGEN 包 含可编程逻辑用于支持不同 LCD 驱动器对时序以及速率的需求。VFRAME,VLINE,VCLK,VM 等控 制信号由 TIMEGEN 产生。在 LCD 控制器的 33 个输出接口中有 24 个用户数据输出,9 个用于控制。 如下表所示。 表 9-1-2 S3C2410 LCD 控制器输出接口说明 输出接口信号 描述 VFRAME/VSYNC/STV 帧同步信号(STN)/垂直同步信号(TFT)/SEC TFT 信号 VLINE/HSYNC/CPV 行同步信号(STN)/水平同步信号(TFT)/ SEC TFT 信号 VCLK/LCD_HCLK 时钟信号(STN/TFT)/SEC TFT 信号 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 455 - VD[23:0] LCD 显示数据输出端口(STN/TFT/SEC TFT) VM/VDEN/TP 交流控制信号(STN)/数据使能信号(TFT)/SEC TFT 信号 LEND/STH 行结束信号(TFT)/SEC TFT 信号 LCD_PWREN LCD 电源使能 LCDVF0 SEC TFT 信号 OE LCDVF1 SEC TFT 信号 REV LCDVF2 SEC TFT 信号 REVB 表 9-1-3 LCD 控制器寄存器列表 寄存器名 内存地址 读写 说明 复位值 LCDCON1 0X4D000000 R/W LCD 控制寄存器 1 0x00000000 LCDCON2 0X4D000004 R/W LCD 控制寄存器 2 0x00000000 LCDCON3 0X4D000008 R/W LCD 控制寄存器 3 0x00000000 LCDCON4 0X4D00000C R/W LCD 控制寄存器 4 0x00000000 LCDCON5 0X4D000010 R/W LCD 控制寄存器 5 0x00000000 LCDSADDR1 0X4D000014 R/W STN/TFT:高位帧缓存地址寄存器 1 0x00000000 LCDSADDR2 0X4D000018 R/W STN/TFT:低位帧缓存地址寄存器 2 0x00000000 LCDSADDR3 0X4D00001C R/W STN/TFT:虚屏地址寄存器 0x00000000 REDLUT 0X4D000020 R/W STN:红色定义寄存器 0x00000000 GREENLUT 0X4D000024 R/W STN:绿色定义寄存器 0x00000000 BLUELUT 0X4D000028 R/W STN:蓝色定义寄存器 0x0000 DITHMODE 0X4D00004C R/W STN:抖动模式寄存器 0x00000 TPAL 0X4D000050 R/W TFT:临时调色板寄存器 0x00000000 LCDINTPND 0X4D000054 R/W 指示 LCD 中断 pending 寄存器 0x0 LCDSRCPND 0X4D000058 R/W 指示 LCD 中断源 pending 寄存器 0x0 LCDINTMSK 0X4D00005C R/W 中断屏蔽寄存器(屏蔽哪个中断源) 0x3 LPCSEL 0X4D000060 R/W LPC3600 模式控制寄存器 0x4 注: 1.以下实验说明中只是简单地介绍控制寄存器的含义,详细使用请参考 S3C2410 处理器数据手 册。 2.地址从 0x14A0002C 到 0x14A00048 禁止使用,因为这个区域用作测试用保留地址。 S3C2410 能够支持 STN LCD 和 TFT LCD,这两种 LCD 屏在显示的时候有很大的差别,而且所 涉及到的寄存器也会不同。Embest EduKit-IV 实验平台采用的是 TFT LCD,下面对 TFT LCD 的显示 过程进行详细的介绍。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 456 - 3)TFT LCD 显示 z LCD 控制器时间相关参数设定 TIMEGEN 产生 LCD 驱动器所需要的控制信号,例如 VSYNC,HSYNC,VCLK,VDEN 和 LEND。 这些控制信号又和 REGBANK 中的寄存器 LCDCON1/2/3/4/5 的设置密切相关。可以对 REGBANK 中 的这些寄存器进行设置以产生适合于不同种类 LCD 驱动器的控制信号。 VSYNC 是帧同步信号,VSYNC 每发出 1 个脉冲,都意味着新的 1 屏视频资料开始发送,而 HSYNC 为行同步信号,每个 HSYNC 脉冲都表明新的 1 行视频资料开始发送。而 VSYNC 和 HSYNC 脉冲的 产生则依赖于 LCDCON2/3 寄存器的 HOZVAL 域和 LINEVAL 域的配置。HOZVAL 和 LNEVAL 的值由 LCD 屏的尺寸决定: HOZVAL=水平显示尺寸-1 LINEVAL=垂直显示尺寸-1 VCLK 信号的频率取决于 LCDCON1 寄存器中的 CLKVAL 域。VCLK 和 CLKVAL 的关系如下(其中 CLKVAL 的最小值是 0): VCLK(Hz)=HCLK/[(CLKVAL+1)x2] 一般情况下,帧频率就是 VSYNC 信号的频率,它与 LCDCON1 和 LCDCON2/3/4 寄存器的 VSYNC、VB2PD、VFPD、LINEVAL、HSYNC、HBPD、HFPD、HOZVAL 和 CLKVAL 都有关系。大多 数 LCD 驱动器都需要与显示器相匹配的帧频率,帧频率计算公式如下: Frame Rate = 1/ [ { (VSPW+1) + (VBPD+1) + (LIINEVAL + 1) + (VFPD+1) }×{(HSPW+1) + (HBPD +1)+ (HFPD+1) + (HOZVAL + 1) } × { 2×( CLKVAL+1 ) / ( HCLK ) } ] 针对 16 位 TFT 屏 BSWP,HWSWP 这两位用来控制字节交换和半字交换,主要用于大小头的问 题,如果输出到屏上的汉字左右互换了,或者输出到屏上的图花屏了,可以更改这个选项。图 9-1-3 是 LCD 屏幕上点的象素在内存中表示的示意图。 图 9-1-3 象素在内存中表示的示意图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 457 - 图 9-1-4 说明了 16 位 TFT 如何表示 RGB。 图 9-1-4 16 位 TFT 表示 RGB 示意图 写一个 16 位数据的颜色数据(为了分析的方便,把它写成二进制); RGB = 10101101 10111001; 根据上面的结构可以得出分析一下 RGB 各是多少。 a) blue:{offset:0,length:5} 偏移量为 0,长度为 5,从 RGB 中提取出来便是“11001”; b) green:{offset:5,length:6} 偏移量为 5,长度为 6,从 RGB 中提取出来便是“101101”; c) red:{offset:11,length:5 } 偏移量为 11,长度为 5,从 RGB 中提取出来便是“10101”。 图 9-1-5 是表示了对应 16 位 TFT,一个象素点的 RGB 示意图,屏幕上 1 个象素用 16 位表示。 图 9-1-5 象素点 RGB 示意图 z TFT LCD 控制器信号时序 TFT 屏的典型时序。其中 VSYNC 是帧同步信号,VSYNC 每发出 1 个脉冲,都意味着新的 1 屏 视频资料开始发送。而 HSYNC 为行同步信号,每个 HSYNC 脉冲都表明新的 1 行视频资料开始发送。 而 VDEN 则用来标明视频资料的有效,VCLK 是用来锁存视频资料的像数时钟。在帧同步以及行同步 的头尾都必须留有回扫时间,例如对于 VSYNC 来说前回扫时间就是(VSPW+1)+(VBPD+1),后 回扫时间就是(VFPD+1);HSYNC 亦类同。这样的时序要求是当初 CRT 显示器由于电子枪偏转需 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 458 - 要时间,但后来成了实际上的工业标准,乃至于后来出现的 TFT 屏为了在时序上于 CRT 兼容,也采 用了这样的控制时序。 图 9-1-6 LCD 时序图 3.LCD 电路连接图 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 459 - 图 9-1-7 LCD 电路连接图 9. 1. 5 实验步骤 下面介绍实验步聚(工作目录$WORKDIR 为:/usr/local/src/EduKit-IV/,$SIMPLEDIR 为: $WORKDIR/Mini2410/simple): 1)单击菜单应用程序->附件->终端打开终端,在终端中输入以下命令设置开发所需的环境变 量。 $ source /usr/local/src/EduKit-IV/Mini2410/set_env_linux.sh 2)将$SIMPLEDIR/9.1-lcd_test 目录下的 test.bin 拷贝到$TFTPDIR/下($TFTPDIR 为: /home/example/tftp/)。 $ cp $SIMPLEDIR/9.1-lcd_test/test.bin $TFTPDIR/ 附:test.bin 是通过工具 Image2Lcd 来将图片生成 bin 文件制作出来的,工具见出厂光盘 DISK3_S3C2410\04-Tools。 3)在终端中执行以下命令打开 minicom,如果已经打开,则略过此步。并重启开发板,进入操 作系统,如开发板上操作系统运行正常,则不需要重启开发板。 $ sudo minicom 4)进入操作系统后,在 minicom 下执行以下命令将 test.bin 下载到 tmp 目录下。 # cd /tmp 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 460 - # tftp -g 192.192.192.190 -r./test.bin -l./test.bin 5)将 test.lcd 复制到/dev/fb0 下。 # cp /tmp/test.bin /dev/fb/0 复制完成以后,我们将在 LCD 屏上显示以下图片: 图 9-1-8 实验运行后在 LCD 上显示的图片 9. 1. 6 练习题 读者可将其它图片用转换工作,将其转换成二进制文件,然后按实验步聚下载到开发板上显示。 9. 2 TSP 触摸屏实验 9. 2. 1 实验目的 ¾ 掌握 Linux 下触摸屏驱动程序的原理; ¾ 掌握 Linux 下触摸屏应用程序的编写方法。 9. 2. 2 实验设备 ¾ 硬件:EduKit-IV 嵌入式教学实验平台、Mini2410 核心子板、PC 机、触摸笔; ¾ 软件:Windows 2000/NT/XP、Ubuntu 8.04、其他嵌入式软件包。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 461 - 9. 2. 3 实验内容 ¾ 学习触摸屏的工作原理和硬件原理; ¾ 掌握 Linux 下触摸屏驱动开发的原理; ¾ 编写 TSP 触摸屏应用程序代码。 9. 2. 4 实验原理 为了操作上的方便,人们用触摸屏来代替鼠标或键盘。工作时,我们必须首先用手指或其它物 体触摸安装在显示器前端的触摸屏,然后系统根据手指触摸的图标或菜单位置来定位选择信息输入。 触摸屏由触摸检测部件和触摸屏控制器组成;触摸检测部件安装在显示器屏幕前面,用于检测用户 触摸位置,接受后送触摸屏控制器;而触摸屏控制器的主要作用是从触摸点检测装置上接收触摸信 息,并将它转换成触点坐标,再送给 CPU,它同时能接收 CPU 发来的命令并加以执行。在手机、PDA 等手持产品及公共服务设备中大量采用触摸屏。本实验将学习 Linux 操作系统下的触摸屏驱动程序 原理及实现方法。 1.触摸屏(TSP) 触摸屏(TSP:Touch Screen Panel)按其技术原理可分为五类:矢量压力传感式、电阻式、电 容式、红外线式和表面声波式。每一类触摸屏都有其各自的优缺点,要了解哪种触摸屏适用于哪种 场合,关键就在于要懂得每一类触摸屏技术的工作原理和特点。其中电阻式触摸屏在嵌入式系统中 用的较多。 电阻式触摸屏利用压力感应进行控制。它的主要部分是一块与显示器表面非常配合的电阻薄膜 屏,这是一种多层的复合薄膜,它以一层玻璃或硬塑料平板作为基层,表面涂有一层透明氧化金属 (透明的导电电阻)导电层,上面再盖有一层外表面硬化处理、光滑防擦的塑料层、它的内表面也 涂有一层涂层、在他们之间有许多细小的(小于 1/1000 英寸)的透明隔离点把两层导电层隔开绝缘。 当手指触摸屏幕时,平常绝缘的两层导电层在触摸点位置就有了一个接触,控制器检测到这个接通 后,其中一面导电层接通 Y 轴方向的 5V 均匀电压场,另一导电层将接触点的电压引至控制电路进 行 A/D 转换,得到电压值后与 5V 相比即可得触摸点的 Y 轴坐标,同理得出 X 轴的坐标。这是所有 电阻技术触摸屏共同的基本原理。电阻式触摸屏根据信号线数又分为四线、五线、六线……电阻触摸 屏等类型。信号线数越多,技术越复杂,坐标定位也越精确。由于电阻值的变化而得到触摸的 X,Y 坐标,再根据模拟鼠标的方式运作。这就是电阻技术触摸屏的最基本的原理。 本实验平台采用四线式电阻式触摸屏,点数为 640×480,实验系统由触摸屏、触摸屏控制电路 和数据采集处理三部分组成。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 462 - 四线电阻屏在表面保护涂层和基层之间覆着两层透明电导层 ITO(ITO:氧化铟,弱导电体,特 性是当厚度降到 1800 个埃(埃=10-10 米)以下时会突然变得透明,再薄下去透光率反而下降,到 300 埃厚度时透光率又上升。是所有电阻屏及电容屏的主要材料)。两层分别对应 X,Y 轴,它们之 间用细微透明绝缘颗粒绝缘,当触摸时产生的压力使两导电层接通。四线电阻模拟量技术的两层透 明金属层工作时每层均增加 5V 恒定电压:一个竖直方向,一个水平方向。总共需四根电缆。特点: 高解析度,高速传输反应。 表面硬度处理,减少擦伤、刮伤及防化学处理。 具有光面及雾面处理。 一次校正,稳定性高,永不漂移。 四线电阻触摸屏,采用国际上评价很高的电阻专利技术:包括大规模成型的玻璃屏和一层透明 的防刮塑料,或经过硬化、清晰或抗眩光处理的尼龙,内层是透明的导体层,表层和底层之间夹着 拥有专利技术的分离点(Separator Dots)。这类触摸屏适合于需要相对固定人员触摸的高精度触摸 屏的应用场合,精度超过 4096×4096,有良好的清晰度和极微小的视差。主要优点还表现在:不漂 移,精度高,响应快,可以用手指或其它物体触摸,防尘防油污等,主要用于专业工程师或工业现 场。 2.硬件原理 电阻式触摸屏利用压力感应进行控制,包含上下叠合的两个透明层,通常还要用一种弹性材料 来将两层隔开。在触摸某点时,两层会在此点接通。四线和八线触摸屏由两层具有相同表面电阻的 透明阻性材料组成,五线和七线触摸屏由一个阻性层和一个导电层组成。 所有的电阻式触摸屏都采用分压器原理来产生代表 X 坐标和 Y 坐标的电压。如图 12.4 所示,分 压器是通过将两个电阻进行串联来实现的。电阻 R1 连接正参考电压 VREF,电阻 R2 接地。两个电 阻连接点处的电压测量值与 R2 的阻值成正比。 图 9-2-1 电阻触摸屏分压 为了在电阻式触摸屏上的特定方向测量一个坐标,需要对一个阻性层进行偏置:将它的一边接 VREF,另一边接地。同时,将未偏置的那一层连接到一个 ADC 的高阻抗输入端。当触摸屏上的压力 足够大,两层之间发生接触时,电阻性表面被分隔为两个电阻。它们的阻值与触摸点到偏置边缘的 距离成正比。触摸点与接地边之间的电阻相当于分压器中下面的那个电阻。因此,在未偏置层上测 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 463 - 得的电压与触摸点到接地边之间的距离成正比。 四线触摸屏包含两个阻性层。其中一层在屏幕的左右边缘各有一条垂直总线,另一层在屏幕的 底部和顶部各有一条水平总线,如图 12.5 所示。为了在 X 轴方向进行测量,将左侧总线偏置为 0V, 右侧总线偏置为 VREF。将顶部或底部总线连接到 ADC,当顶层和底层相接触时即可作一次测量。为 了在 Y 轴方向进行测量,将顶部总线偏置为 VREF,底部总线偏置为 0V。将 ADC 输入端接左侧总线 或右侧总线,当顶层与底层相接触时即可对电压进行测量。 图 9-2-2 四线电阻式触摸屏 S3C2410 接 4 线电阻式触摸屏的电路原理如图 12.6 所示。S3C2410 提供了 nYMON、YMON、 nXPON 和 XMON 直接作为触摸屏的控制信号,它通过连接 FDC6321 场效应管触摸屏驱动器控制触 摸屏。输入信号在经过阻容式低通滤器滤除坐标信号噪声后被接入 S3C2410 内集成的 ADC(模数转 换器)的模拟信号输入通道 AIN5、AIN7。 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 464 - 图 9-2-3 s3c2410 连接 4 线电阻式触摸屏 S3C2410 内置了一个 8 信道的 10 位 ADC,该 ADC 能以 500KS/S 的采样速率将外部的模拟信号 转换为 10 位分辨率的数字量。因此,ADC 能与触摸屏控制器协同工作,完成对触摸屏绝对地址的 测量。 S3C2410 的 ADC 和触摸屏接口可工作于 5 种模式,分别如下。 1.普通转换模式(Normal Converson Mode) 普通转换模式(AUTO_PST = 0,XY_PST = 0)用来进行一般的 ADC 转换,例如通过 ADC 测 量电池电压等。 2.独立 X/Y 位置转换模式(Separate X/Y Position Conversion Mode) 独立 X/Y 轴坐标转换模式其实包含了 X 轴模式和 Y 轴模式。为获得 X、Y 坐标,需首先进行 X 轴的坐标转换(AUTO_PST = 0,XY_PST = 1),X 轴的转换资料会写到 ADCDAT0 寄存器的 XPDAT 中,等待转换完成后,触摸屏控制器会产生 INT_ADC 中断。然后,进行 Y 轴的坐标转换(AUTO_PST 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 465 - = 0,XY_PST = 2),Y 轴的转换资料会写到 ADCDAT1 寄存器的 YPDAT 中,等待转换完成后,触摸 屏控制器也会产生 INT_ADC 中断。 3.自动(连续)X/Y 位置转换模式(Auto X/Y Position Conversion Mode) 自动(连续)X/Y 位置转换模式(AUTO_PST = 1,XY_PST = 0)运行方式是触摸屏控制自动 转换 X 位置和 Y 位置。触摸屏控制器在 ADCDAT0 的 XPDATA 位写入 X 测定数据,在 ADCDAT1 的 YPADATA 位写入 Y 测定数据。自动(连续)位置转换后,触摸屏控制器产生 INT_ADC 中断。 4.等待中断模式(Wait for Interrupt Mode) 当触摸屏控制器等待中断模式时,它等待触摸屏触点信号的到来。当触点信号到来时,控制器 产生 INT_TC 中断信号。然后,X 位置和 Y 位置能被适当地转换模式(独立 X/Y 位置转换模式或自 动 X/Y 位置转换模式)读取到。 5.待机模式(Standby Mode) 当 ADCCON 寄存器的 STDBM 位置 1 时,待机模式被激活。在这种模式下,A/D 转换动作被禁 止,ADCDAT0 的 XPDATA 位和 ADXDATA1 的 YPDAT 保留以前被转换的数据。 5. 1 触摸屏设备驱动中数据结构 触摸屏设备结构体的成员与按键设备结构体的成员类似,也包含一个缓冲区,同时包括自旋锁、 等待队列和 fasync_struct 指针,如代码清单 9-2-1 所示。 代码清单 9-2-1 触摸屏设备结构体 1 typedef struct 2 { 3 unsigned int penStatus; /* PEN_UP, PEN_DOWN, PEN_SAMPLE */ 4 TS_RET buf[MAX_TS_BUF]; /* 缓冲区 */ 5 unsigned int head, tail; /* 缓冲区头和尾 */ 6 wait_queue_head_t wq; /*等待队列*/ 7 spinlock_t lock; 8 #ifdef USE_ASYNC 9 struct fasync_struct *aq; 10 #endif 11 struct cdev cdev; 12 } TS_DEV; 触摸屏结构体中包含的 TS_RET 值的类型定义如代码清单 9-2-2 所示,包含 X、Y 坐标和状态 (PEN_DOWN、PEN_UP)等信息,这个信息会在用户读取触摸信息时复制到用户空间。 代码清单 9-2-2 TS_RET 结构体 1 typedef struct 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 466 - 2 { 3 unsigned short pressure; // PEN_DOWN、PEN_UP 4 unsigned short x; // x坐标 5 unsigned short y; // y坐标 6 unsigned short pad; 7 } TS_RET; 在触摸屏设备驱动中,将实现 open()、release()、read()、fasync()和 poll()函数,因此,其文 件操作结构体定义如代码清单 9-2-3 所示。 代码清单 9-2-3 触摸屏驱动文件操作结构体 1 static struct file_operations s3c2410_fops = 2 { 3 owner: THIS_MODULE, 4 open: s3c2410_ts_open, // 打开 5 read: s3c2410_ts_read, // 读坐标 6 release: 7 s3c2410_ts_release, 8 #ifdef USE_ASYNC 9 fasync: s3c2410_ts_fasync, // fasync()函数 10 #endif 11 poll: s3c2410_ts_poll, // 轮询 12 }; 5. 2 触摸屏驱动中的硬件控制 代码清单 9-2-4 中的一组宏用于控制触摸屏和 ADC 进入不同的工作模式,如等待中断、X/Y 位 置转换等。 代码清单 9-2-4 触摸屏和 ADC 硬件控制 1 #define wait_down_int(){ ADCTSC = DOWN_INT | XP_PULL_UP_EN |\ 2 XP_AIN | XM_HIZ | YP_AIN | YM_GND | \ 3 XP_PST(WAIT_INT_MODE); } 4 #define wait_up_int(){ ADCTSC = UP_INT | XP_PULL_UP_EN | XP_AIN |\ 5 XM_HIZ |YP_AIN | YM_GND | XP_PST(WAIT_INT_MODE); } 6 #define mode_x_axis(){ ADCTSC = XP_EXTVLT | XM_GND | YP_AIN \ 7 | YM_HIZ |XP_PULL_UP_DIS | XP_PST(X_AXIS_MODE); } 8 #define mode_x_axis_n(){ ADCTSC = XP_EXTVLT | XM_GND | YP_AIN | \ 9 YM_HIZ |XP_PULL_UP_DIS | XP_PST(NOP_MODE); } 10 #define mode_y_axis(){ ADCTSC = XP_AIN | XM_HIZ | YP_EXTVLT \ 11 | YM_GND |XP_PULL_UP_DIS | XP_PST(Y_AXIS_MODE); } 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 467 - 12 #define start_adc_x(){ ADCCON = PRESCALE_EN | PRSCVL(49) | \ 13 ADC_INPUT(ADC_IN5) | ADC_START_BY_RD_EN | \ 14 ADC_NORMAL_MODE; \ 15 ADCDAT0; } 16 #define start_adc_y(){ ADCCON = PRESCALE_EN | PRSCVL(49) | \ 17 ADC_INPUT(ADC_IN7) | ADC_START_BY_RD_EN | \ 18 ADC_NORMAL_MODE; \ 19 ADCDAT1; } 20 #define disable_ts_adc(){ ADCCON &= ~(ADCCON_READ_START); } 5. 3 触摸屏驱动模块加载和卸载函数 在触摸屏设备驱动的模块加载函数中,要完成申请设备号、添加 cdev、申请中断、设置触摸屏 控制引脚(YPON、YMON、XPON、XMON)等多项工作,如代码清单 9-2-5 所示。 代码清单 9-2-5 触摸屏设备驱动的模块加载函数 1 static int __init s3c2410_ts_init(void) 2 { 3 int ret; 4 tsEvent = tsEvent_dummy; 5 ...// 申请设备号,添加cdev 6 7 /* 设置XP、YM、YP和YM对应引脚 */ 8 set_gpio_ctrl(GPIO_YPON); 9 set_gpio_ctrl(GPIO_YMON); 10 set_gpio_ctrl(GPIO_XPON); 11 set_gpio_ctrl(GPIO_XMON); 12 13 /* 使能触摸屏中断 */ 14 ret = request_irq(IRQ_ADC_DONE, s3c2410_isr_adc, 15 SA_INTERRUPT, DEVICE_NAME,s3c2410_isr_adc); 16 if (ret) 17 goto adc_failed; 18 ret = request_irq(IRQ_TC, s3c2410_isr_tc, SA_INTERRUPT, 19 DEVICE_NAME,s3c2410_isr_tc); 20 if (ret) 21 goto tc_failed; 22 23 /*置于等待触点中断模式*/ 24 wait_down_int(); 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 468 - 25 26 printk(DEVICE_NAME " initialized\n"); 27 28 return 0; 29 tc_failed: 30 free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); 31 adc_failed: 32 return ret; 33 } 在触摸屏设备驱动的模块卸载函数中,要完成释放设备号、删除 cdev、释放中断等工作,如代 码清单 9-2-6 所示。 代码清单 9-2-6 触摸屏设备驱动模块卸载函数 1 static void __exit s3c2410_ts_exit(void) 2 { 3 ...// 释放设备号,删除cdev 4 free_irq(IRQ_ADC_DONE, s3c2410_isr_adc); 5 free_irq(IRQ_TC, s3c2410_isr_tc); 6 } 5. 4 触摸屏驱动中断、定时器处理程序 触摸屏驱动中会产生两类中断,一类是触点中断(INT-TC),一类是 X/Y 位置转换中断(INT-ADC)。 在前一类中断发生后,若之前处于 PEN_UP 状态,则应该启动 X/Y 位置转换。另外,将抬起中断也 放在 INT-TC 处理程序中,它会调用 tsEvent()完成等待队列和信号的释放,如代码清单 9-2-7 所示。 代码清单 9-2-7 触摸屏设备驱动的触点/抬起中断处理程序 1 static void s3c2410_isr_tc(int irq, void *dev_id, struct pt_regs *reg) 2 { 3 spin_lock_irq(&(tsdev.lock)); 4 if (tsdev.penStatus == PEN_UP) 5 { 6 start_ts_adc(); // 开始X/Y位置转换 7 } 8 else 9 { 10 tsdev.penStatus = PEN_UP; 11 DPRINTK("PEN UP: x: %08d, y: %08d\n", x, y); 12 wait_down_int(); // 置于等待触点中断模式 基于 S3C2410 嵌入式 Linux 开发实验与实践――EduKit 系列丛书 Email:support@edukit.com.cn Tel:0755-25631365 - 469 - 13 tsEvent(); 14 } 15 spin_unlock_irq(&(tsdev.lock)); 16 } 当 X/Y 位置转换中断发生后,应读取 X、Y 的坐标值,填入缓冲区,如代码清单 9-2-8 所示。 代码清单 9-2-8 触摸屏设备驱动 X/Y 位置转换中断处理程序 1 static void s3c2410_isr_adc(int irq, void *dev_id, struct pt_regs *reg) 2 { 3 spin_lock_irq(&(tsdev.lock)); 4 if (tsdev.penStatus == PEN_UP) 5 s3c2410_get_XY(); // 读取坐标 6 #ifdef HOOK_FOR_DRAG 7 else 8 s3c2410_get_XY(); 9 #endif 10 spin_unlock_irq(&(tsdev.lock)); 11 } 上述程序中调用的 s3c2410_get_XY()用于获得 X、Y 坐标,如代码清单 9-2-9 所示。 代码清单 9-2-9 触摸屏设备驱动中获得 X、Y 坐标 1 static inline void s3c2410_get_XY(void) 2 { 3 if (adc_state == 0) 4 { 5 adc_state = 1; 6 disable_ts_adc(); // 禁止INT-ADC 7 y = (ADCDAT0 &0x3ff); // 读取坐标值 8 mode_y_axis(); 9 start_adc_y(); // 开始y位置转换 10 } 11 else if (adc_state == 1) 12 { 13 adc_state = 0; 14 disable_ts_adc(); // 禁止INT-ADC 15 x = (ADCDAT1 &0x3ff); // 读取坐标值 16 tsdev.penStatus = PEN_DOWN; 17 DPRINTK("PEN DOWN: x: %08d, y: %08d\n", x, y); 18 wait_up_int();