《嵌入式软件开发简介以linux为例》由会员分享,可在线阅读,更多相关《嵌入式软件开发简介以linux为例(223页珍藏版)》请在金锄头文库上搜索。
1、嵌入式软件开发简介以linux为例Stillwatersrundeep.流静水深流静水深,人静心深人静心深Wherethereislife,thereishope。有生命必有希望。有生命必有希望2课程安排嵌入式Linux开发环境搭建嵌入式Linux概述Linux快速入门熟悉嵌入式开发平台熟悉引导启动代码和内核的烧写NFS环境构建3课程安排嵌入式Linux下的应用程序开发嵌入式Linux开发工具与开发流程理解交叉编译 嵌入式Linux下的应用程序调试技巧Make工程管理器4嵌入式系统的特点嵌入式系统低功耗、体积小,专用性强。嵌入式系统与PC机的最大不同就是嵌入式CPU大多工作在为特定用户群设计的
2、系统中,能够把PC机中许多由板卡完成的任务集成在芯片内部,从而有利于嵌入式系统设计趋于小型化。 为了提高执行速度和系统可靠性,嵌入式系统中的软件一般都固化在存储器芯片或单片机本身中,而不是存贮于磁盘等载体中。嵌入式系统的硬件和软件都必须高效率地设计,系统要精简。操作系统一般和应用软件集成在一起。对软件代码质量要求很高。应该尽最大可能避免死机的情况发生。嵌入式系统开发需要专门的开发工具和开发环境。5如何选择嵌入式CPU?ARMPowerPCMIPSXscaleDSPX866典型的嵌入式操作系统LinuxuC/OS Windows CE VxWorks Palm OS Symbian7嵌入式系统开
3、发流程8嵌入式系统开发的内容9嵌入式Linux开发模式BootLoader内核内核FlashTargetHostNFS模式模式Root fs10Linux快速入门11Linux简介Linus Torvalds于1991编写Linux是一个Unix兼容的系统,大部分通用的Unix工具和程序都可以在Linux系统下运行。UNIX-堂兄 Linux表弟使用GNU工具开发:gcc,glibc,binutils,make等GNU = GNU is Not UnixGPL = General Public License12一些发行版RedHatFedora CoreSuseDebianUbuntuCen
4、t OSAisanux13认识Linux内核版本主版本主版本1.02.02.22.42.6 2,3年更新年更新稳定版稳定版2.0.402.2.12 2.4.18 2.6.7 2.6.301,2月更新月更新稳定版更新稳定版更新2.6.23.1 2.6.23.71,2周更新周更新14Linux体系结构用户空间用户空间内核空间内核空间文件文件系统系统I/O内存内存管理管理Cache调度调度内核内核15内核结构16Linux优点提供了先进的网络支持多任务、多用户符合IEEE POSIX标准核心能仿真FPU支持数十种文件系统格式完全运行于保护模式开放源代码采用先进的内存管理机制,更加有效地利用物理内存
5、17Linux分区Linux分区的形式Swap分区根分区/Boot分区其他18Linux的基本命令Shell命令行使用技巧 :Tab自动补齐、 ctrl+a+e等cd:转换目录ls:列出文件名(根目录浏览)ll:查看文件属性(文件属性与权限 )cp:复制文件(cp /mnt/hgfs/share/*.* /root/)mv:移动文件(可用于文件或目录的改名)rm:删除文件(rm rf (目标))mkdir:创建目录rmdir:删除目录19Linux的基本命令(2)cat:显示文本文件less/more:分屏显示文件grep:查找字符串(ls all | grep *)head:显示文件开头ta
6、il:显示文件结尾sort:按次序显示文件uniq:去掉文件中重复的行diff:比较两个文件file:测试文件内容20Linux的基本命令(3)echo:显示文本date:显示时间和日期gzip:压缩文件gunzip:解压缩文件bzip2:压缩文件tar:打包和解包文件whereis:查找命令which:查找实用程序21Linux的基本命令(4)ps:列出进程df :检查文件系统空间占用情况 du:显示磁盘空间的使用情况 kill:杀死进程chmod :改变文件或目录的访问权限 chown:改变文件或目录的属主和组 chgrp:改变文件或目录所属的组 info:获得帮助22Linux的基本命令
7、(5)pwd:显示当前工作目录的绝对路径rpm:文件包管理clear:清屏ln:创建文件链接who:列出系统上的用户名单write:发送消息talk:与其他用户通信man:获取帮助23熟悉嵌入式开发平台熟悉引导启动代码和内核的烧写24需要熟悉的内容开发板硬件基本配置开发板与PC机连接方式PC机相关配置熟悉bootloader的使用深入理解开发内容巩固开发流程25Bootloader烧写示范Linux环境(虚拟机)简介超级终端和DNW的配置Bootloader的显示信息简介(标准BIOS)内核、文件系统的演示26嵌入式Linux下的应用程序开发27开发工具-编辑器vivi的基本使用方法 输入模式
8、输入模式 命令模式命令模式28vi-开始编辑开始编辑新增新增 (append)- a a 从 光 标 所 在 位 置 後 面 开 始 新 增 资 料, 光 标 後 的 资 料 随 新 增 资 料 向 後 移 动。- A A 从 光 标 所 在 列 最 後 面 的 地 方 开 始 新 增 资 料。 插插 入入 (insert) - i i 从 光 标 所 在 位 置 前 面 开 始 插 入 资 料, 光 标 後 的 资 料 随 新 增 资 料 向 後 移 动。- I I 从 光 标列 的 第 一 个 非 空 白 字 符 前 面 开 始 插 入 资 料。 开开 始始 (open)- o o 在 光
9、 标 所 在 列 下 新 增 一 列 并 进 入 输 入 模 式。- O O 在 光 标 所 在 列 上 方 新 增 一 列 并 进 入 输 入 模 式。 29vi-删除与修改删除与修改 x x 删除光标所在字符。 dddd 删除光标所在的列。 r r 修改光标所在字符,r後接著要修正的字符 R R进入取代状态,新增资料会覆改原先资料, 直到按ESC回到指令模式下为止。 s s 删除光标所在字符,并进入输入模式。 S S 删除光标所在的列,并进入输入模式。30vi-退出命令退出命令不保存退出不保存强制性退出保存编辑存入文件filename 中强制性存入文件filename 中()保存并退出(s
10、hift+zz)31vi-光标移动命令光标移动命令移动到当前单词的开始移动到当前单词的结尾向前移动一个单词向前移动一个字符向上移动一行向下移动一行向后移动一个字符32vi-改变与替换操作命令改变与替换操作命令替换光标所在的字符替换字符序列替换一个单词同替换光标所在的前一字符替换自光标位置至行尾的所有字符同替换当前行33vi-拷贝与粘贴命令拷贝与粘贴命令将光标所在单词拷入剪贴板将光标至行尾的字符拷入剪贴板同将当前行拷入剪贴板将剪贴板中的内容粘贴在光标后将剪贴板中的内容粘贴在光标前利用利用Vi编写一个编写一个helloworld程序!程序!3435开发工具-编译器GCC全称为GNU CC ,GNU
11、项目中符合ANSI C标准的编译系统 编译如C、C+、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言一个交叉平台编译器 ,适合在嵌入式领域的开发编译 36GCC编译器GNU Compiler CollectionC, C+, Objective-C, Fortran, Java, Adahttp:/gcc.gnu.orgGCC 4.2.2 GCC 2.95 July 31, 1999 GCC 3.0 June 18, 2001 GCC 3.2 August 14, 2002 GCC 3.3 May 13, 2003 Oct 9, 2007 Dece
12、mber 3, 1997 37GCC简介gcc所支持后缀名解释 .c C原始程序.C/.cc/.cxxC+原始程序.mObjective-C原始程序.i 已经过预处理的C原始程序.ii 已经过预处理的C+原始程序.s/.S 汇编语言原始程序.h 预处理文件(头文件).o 目标文件.a/.so编译后的库文件38编译器的作用源代码 *.c预处理器头文件 *.h编译器目标代码 *.o可执行文件引导代码库函数汇编处理链接Gcc的编译流程分为四个步骤 预处理(Pre-Processing) 编译(Compiling) 汇编(Assembling) 链接(Linking)39“hello”的演变历程Pre
13、-processor(cpp/Gcc-E)Compiler(cc1/Gcc-S)Assembler(as/Gcc-c)Linker(ld)hello.c(text)hello.i(text)hello.s(text)hello.o(binary)hello(binary)printf.o(binary)40理解交叉编译及环境建立41GCC交叉编译器GNU BinutilsGNU Binutils 的主要工具有两个,一个是连接程序ld,另外一个是汇编程序as。其主要目的是为GNU 系统,提供汇编和连接工具。GNU GCCGNU GCC 就是上面提到的GCC,GCC 主要是为GNU 系统提供C 编
14、译器。现在支持多种语言,这其中包括C/C+、Fortran、Java、Objective-C、甚至还有Ada。GNU GLibc用于定义系统调用和其它一些基本的函数调用。42GCC交叉编译器的生成第1步,取得Binutils、GCC、Glibc 源码。第2步,配置并编译Binutils 取得我们所需要的汇编和连接程序。第3步,配置并编译GCC 源码生成GCC 编译器。一般是C 编译器首先生成,然后以这个为基础在结合下一步生成的Glibc 的C 函数库,再编译生成其它编译器。第4步,配置Glibc 并编译生成Glibc 的C 函数库。第5步,再次配置和编译GCC 源码,生成其它语言的编译器,如C
15、+编译器等。43工具链代码结构44TARGET常用的值实际平台TARGET的值PowerPCpowerpc-linuxARMarm-linuxMIPS (big endian)mips-linuxMIPS (little endian)mipsel-linuxSuperH 4sh4-linux45交叉编译流程crosstool介绍crosstool是由Dan Kegel开发的一套脚本程序,可以自动编译不同版本的gcc、glibc,并做出测试。在crosstool软件包中,提供了从互联网下载软件包的脚本,例如glibc-get.sh脚本可以下载glibc软件包。有关crosstool的详细信息,
16、读者可以访问其官方网站和其他一些网络资源:http:/ profile。然后arm-linux-gcc v,如果显示是2.9.53。则说明环境配置成功。更改环境变量有几种方法,其中之一是更改etc/profile文件(开机启动时配置)中的PATH,将相冲突的屏蔽掉。另一种方法是重新定义PATH,然后用export导出(export命令可以修改,但重启之后?):可编辑/etc/bashrc文件,在最后增加路径 export PATH=/usr/local/arm/2.95.3/bin:|$PATH,则以后编译内核或其它程序均可用arm-linux- 来制定交叉编译器。 49嵌入式Linux下的应
17、用程序调试技巧50 调试器-Gdb调试流程 首先使用gcc对test.c进行编译,注意一定要加上选项-g # gcc -g test.c -o test # gdb test GNU gdb Red Hat Linux (6.3.0.0-1.21rh)Copyright 2004 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it u
18、nder certain conditions.Type show copying to see the conditions.There is absolutely no warranty for GDB. Type show warranty for details.This GDB was configured as i386-redhat-linux-gnu.Using host libthread_db library /lib/libthread_db.so.1.(gdb)51 Gdb调试流程 查看文件(gdb) l设置断点 (gdb) b 6查看断点情况查看断点情况 (gdb)
19、info b运行代码运行代码 (gdb) rv查看变量值(gdb)pnv单步运行(gdb)n(gdb)sv恢复程序运行(gdb)cv帮助(gdb)helpcommand52Gdb的使用切记点在Gcc编译选项中一定要加入-g。只有在代码处于“运行”或“暂停”状态时才能查看变量值。设置断点后程序在指定行之前停止 53Gdb调试运行被调试程序,设置所有的能影响该程序的参数和变量。保证被调试程序在指定的条件下停止运行。当被调试程序停止时,让开发工程师检查发生了什么。根据每次调试器的提示信息来做响应的改变,以便修正某个错误引起的问题54嵌入式程序调试-NFS熟悉Uboot的烧写方法和使用按照实验指导配置
20、tftp服务按照实验指导配置NFS服务利用NFS调试经交叉编译后的程序55 Makefile简介56工程管理器-Make工程管理器,顾名思义,是指管理较多的文件 Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能构根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件文件的内容来执行大量的编译工作 MakeMake将只编译改动的代码文件,而不用完全编译。将只编译改动的代码文件,而不用完全编译。将只编译改动的代码文件,而不用完全编译。将只编译改动的代码文件,而不用完全编译。57Makefile基本结构Makefile是Make读入的唯一配置文
21、件 由make工具创建的目标体(target),通常是目标文件或可执行文件要创建的目标体所依赖的文件(dependency_file)创建每个目标体时需要运行的命令(command)Makefile格式target: dependency_filescommand例子hello.o: hello.c hello.hgcc c hello.c o hello.o58Makefile规则 隐式规则隐含规则能够告诉make怎样使用传统的技术完成任务,这样,当用户使用它们时就不必详细指定编译的具体细节,而只需把目标文件列出即可 C编译:.c变为.o$(CC) c $(CPPFLAGS) $(CFLAG
22、S)C+编译:.cc或或.C变为.o$(CXX) -c $(CPPFLAGS) $(CXXFLAGS)59Makefile规则 模式规则 模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用make默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化了Makefile的编写 模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明 60Make使用直接运行make 选项-C dir读入指定目录下的Makefile-f file读入当前目录下的file文件作为Makefile-i忽略所有的命令执行错误-I dir指定被
23、包含的Makefile所在目录-n只打印要执行的命令,但不执行这些命令-p显示make变量数据库和隐含规则-s在执行命令时不显示命令-w如果make在执行过程中改变目录,打印当前目录名61Autotools简介对于一个较大的项目而言编写Makefile难度较大autotools系列工具只需用户输入简单的目标文件、依赖文件、文件目录等就可以轻松地生成Makefileautotools工具还可以完成系统配置信息的收集,从而可以方便地处理各种移植性的问题Linux上的软件开发一般都用autotools来制作Makefile 62 autotools使用流程 aclocalaclocalautosca
24、nautoscanautoconfautoconfautoheaderautoheaderautomakeautomake63使用Makefilemake 键入make默认执行make all命令,即目标体为all make install 把该程序安装到系统目录中去 make clean 清除之前所编译的可执行文件及目标文件(object file, *.o) make dist 将程序和相关的文档打包为一个压缩文档以供发布 Shell脚本简介Shell脚本的本质编译型语言解释型语言Shell脚本语言是解释型语言Shell脚本的本质: ShellShell命令的有序集合命令的有序集合命令的有
25、序集合命令的有序集合。shell 编程的基本过程编程的基本过程基本过程分为三步:Step1. 建立 shell 文件 包含任意多行操作系统命令或shell命令的文本文件;Step2. 赋予shell文件执行权限 用chmod命令修改权限;Step3. 执行shell文件 直接在命令行上调用shell程序.Step1: 建立建立shell文件文件 (可用任何建立文本文件的方法可用任何建立文本文件的方法) $ cat prog1 who | grep $1Step2: 赋予执行权限赋予执行权限: (初始文本文件无执行权限初始文本文件无执行权限) $ chmod 740 prog1Step3: 执行
26、该执行该shell程序程序 $ prog1 wdl prog1: not found (shell在标准搜索目录中找不到prog1命令)Step4: 指定路径或修改环境变量指定路径或修改环境变量PATH后执行后执行shell程序程序 $ ./prog1 wdl wdl tty06 Feb 8 09:12实例Shell变量Shell允许用户建立变量存储数据,但不支持数据类型(整型、字符、浮点型),将任何赋给变量的值都解释为一串字符Variable=value命名规则同C+中的命名规则count=1Echo $countDATE=dateEcho $DATEshell变量Bourne Shell主
27、要有如下三种变量:用户自定义变量 位置变量即 shell script之参数 环境变量用户自定义变量在在shell编程中通常使用全大写变量,方便识别编程中通常使用全大写变量,方便识别 $ COUNT=1变量的调用:在变量前加变量的调用:在变量前加$ $ echo $HOME Linux Shell/bash从右向左赋值从右向左赋值$Y=y$ X=$Y$ echo $X y 使用使用unset命令删除变量的赋值命令删除变量的赋值 $ Z=hello $ echo $Z hello $ unset Z $ echo $Z 位置变量$0 与键入的命令行一样,包含脚本文件名$1,$2,$9 分别包含第
28、一个到第九个命令行参数$# 包含命令行参数的个数$ 包含所有命令行参数:“$1,$2,$9”$? 包含前一个命令的退出状态$* 包含所有命令行参数:“$1,$2,$9”$ 包含正在执行进程的ID号shell环境变量CDPATH:用于cd命令的查找路径 HOME: /etc/passwd文件中列出的用户主目录 IFS:Internal Field Separator,默认为空格,tab及换行符 MAIL:/var/mail/$USERNAME mail等程序使用 PATH :路径PS1,PS2:默认提示符($)及换行提示符() TERM:终端类型,常用的有vt100,ansi,vt200,xte
29、rm等 文件测试-d name 测试name 是否为一个目录-f name 测试name 是否为普通文件-L name测试name 是否为符号链接-r name 测试name 文件是否存在且为可读-w name 测试name 文件是否存在且为可写-x name 测试name 文件是否存在且为可执行-s name 测试name 文件是否存在且其长度不为0f1 -nt f2 测试f1 是否比f2 更新f1 -ot f2 测试f1 是否比f2 更旧ifthenfi语法结构: if 表达式表达式 then 命令表命令表 fi 如果表达式为真, 则执行命令表中的命令; 否则退出if语句, 即执行fi后面
30、的语句. if和fi是条件语句的语句括号, 必须成对使用;命令表中的命令可以是一条, 也可以是若干条.条件语句caseesac 多路分支语句case用于多重条件测试, 语法结构清晰自然. 其语法结构为: case 字符串变量 in 模式1) 命令表1 ; 模式2) 命令表2 ; 模式n) 命令表n ; esaccase语句只能检测字符串变量各模式中可用文件名元字符,以右括号结束命令表以单独的双分号行结束,退出case语句模式 n常写为字符* 表示所有其它模式最后一个双分号行可以省略多路分支语句fordodone 当循环次数已知或确定时, 使用for循环语句来多次执行一条或一组命令。 循环体由语
31、句括号do和done来限定。格式为: for 变量名 in 单词表 do 命令表 done变量依次取单词表中的各个单词, 每取一次单词, 就执行一次循环体中的命令. 循环次数由单词表中的单词数确定. 命令表中的命令可以是一条, 也可以是由分号或换行符分开的多条。如果单词表是命令行上的所有位置参数时, 可以在for语句中省略 “in 单词表” 部分。循环语句whiledodone语法结构为: while 命令或表达式 do 命令表 donewhile语句首先测试其后的命令或表达式的值,如果为真,就执行一次循环体中的命令,然后再测试该命令或表达式的值,执行循环体,直到该命令或表达式为假时退出循环。
32、while语句的退出状态为命令表中被执行的最后一条命令的退出状态。循环语句78实验内容熟悉开发环境的搭建熟悉BIOS、Uboot的使用,NFS的配置编写简单的C程序使用GDB调试有问题的C代码编写简单的Makefile编写简单的SHELL脚步Bootloaderwww.embedu.org80内容提纲Bootloader简介简介1U-Boot2移植移植U-Boot3什么是BootloaderBootloader是硬件启动的引导程序,是启动操作系统的根本;是在操作系统内核或用户应用程序运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一
33、个合适的状态,以便为最终调用准备好环境;在一般典型的系统中,整个系统的加载启动任务就完全由Bootloader来完成。 www.embedu.org81Bootloader不属于操作系统内核,采用汇编语言编写,因此针对不同的CPU体系结构,这一部分代码不具有可移植性;在移植操作系统时,这部分代码必须加以改写。Bootloader不但依赖于CPU的体系结构,而且依赖于嵌入式系统板级设备的配置。 Bootloader的特点启动加载模式启动加载模式:在这种模式下,Bootloader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。下载模式下载模式:在这种模式下,
34、目标机上的Bootloader将通过串口或网络等通信手段从开发主机(Host)上下载内核映像和根文件系统映像等到RAM中。然后可以再被 Bootloader写到目标机上的固态存储媒质中,或者直接进行系统的引导。Bootloader的操作模式初始化硬件将操作系统内核从Flash拷贝到SDRAM中,如果是压缩格式的内核,还要解压缩改写系统的内存映射,原先Flash起始地址映射为0地址,这时需要将RAM的起始地址映射为0提供Linux内核的启动参数启动Linux内核设置堆栈指针并将bss段清零将来执行C语言程序和调用子函数时要用到改变pc值,使得CPU开始执行真正的操作系统内核。Bootloader
35、的特征Bootloader的启动系统加电或复位后,所有CPU都会从某个地址开始执行嵌入式系统的开发板都要把板上ROM或FLASH映射到这个地址。因此,必须把Bootloader程序存储在相应的FLASH位置。系统加电后,CPU将首先执行它。www.embedu.org85屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断 设置 CPU 的速度和时钟频率 RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等初始化 LED。其目的是表明系统的状态。如果板子上没有 LED,可以通过初始化 U
36、ART 向串口打印 Bootloader 的 Logo 字符信息来完成这一点 关闭CPU内部I/D cache 为加载 stage2 准备 RAM 空间,跳转到 stage2的C入口点 Bootloader的第一阶段初始化本阶段要使用到的硬件设备;检测系统内存映射;将内核映像和根文件系统映像从Flash读到RAM;为内核设置启动参数;调用内核。 Bootloader的第二阶段BootloaderMonitor描述x86ARMPowerPCLILO否Linux磁盘引导程序是否否GRUB否GNU的LILO替代程序是否否Loadlin否从DOS引导Linux是否否ROLO否从ROM引导Linux而不
37、需要BIOS是否否Etherboot否通过以太网卡启动Linux系统的固件是否否LinuxBIOS否完全替代BUIS的Linux引导程序是否否BLOB否LART等硬件平台的引导程序否是否一些bootloader介绍Configuration file/boot/grub/menu.lst /boot/grub/grub.confdefault=0timeout=10splashimage=(hd0,1)/boot/grub/splash.xpm.gztitle MicroSoft Windows XProotnoverify (hd0,0)chainloader +1title Red Hat
38、 Linux (2.4.20-8)root (hd0,1)kernel /boot/vmlinuz-2.4.20-8 ro root=LABEL=/initrd /boot/initrd-2.4.20-8.img配置GRUBU-Boot是ARM bootloader标准armboot 加入到 ppcboot 形成了 u-boot支持arm720, arm920, arm926, sa1100, xscalehttp:/ ARMBootloadersFS2410开发板提供的BIOSwww.embedu.org910号功能:号功能:USB下载下载1号功能:号功能:串口下载串口下载2号功能:号功能:
39、执行执行NAND Flash的烧的烧写写3号功能:号功能:从从NAND Flash启动启动程序程序4号功能:号功能:擦除擦除NAND Flash分区分区5号功能:号功能:烧写烧写Nor Flash6号功能:号功能:设置启动参设置启动参数数7号功能:号功能:设置自启动设置自启动参数参数DRAM控制器DRAM芯片不能向其它微处理器总线资源那样直接读写,它们需要特殊的硬件控制器使能读写循环。设置DRAM控制器需要详细了解DRAM体系结构、控制器本身、 DRAM芯片使用的规范和全面的硬件设计。Bootloader首先要完成的就是使能内存子系统。初始化内存以后,内存可以作为一种资源进行使用。www.em
40、bedu.org92www.embedu.org93内容提纲1U-Boot2移植移植U-Boot3u-boot(Universal Boot Loader)是德国DENX小组的开发用于多种嵌入式CPU的bootloader程序。遵循GPL条款;从FADSROM、8xxROM 、PPCBOOT逐步发展演化而来;当前版本号:见include/version.h中的定义。 U-boot介绍平台相关board, cpu, lib_ppc, lib_arm, include平台无关common, net, fs, drivers工具和文档tools, docU-Boot程序结构boardBoard de
41、pendent files,RPXlite(mpc8xx), smdk2410(arm920t), sc520_cdp(x86) cpuCPU specific files,mpc8xx, ppc4xx, arm720t, arm920t, xscale, i386lib_ppcFiles generic to PowerPC architecturelib_armFiles generic to ARM architecturelib_i386Files generic to X86 architectureincludeHeader Files and board configsU-Boo
42、t程序结构commonMisc functionslib_genericGeneric library functionsnetNetworking codefsFile System CodepostPower On Self TestdriversCommon used device driversdiskHard disk interface codertcReal Time Clock driversdttDigital Thermometer and Thermostat driversU-Boot程序结构在 Flash 中运行汇编程序,将 Flash 中的启动代码部分复制到 SDR
43、AM 中,同时创造环境准备运行 C 程序;在 SDRAM 中执行,对硬件进行初始化;设置内核参数的标记列表,复制镜像文件,进入内核的入口函数 U-Boot启动过程的几个阶段99初始化硬件设置UART设置网口设置USB检测存储器设置启动参数内核硬件信息波特率 跳转到Linux内核的首地址消亡 BootLoader的生命周期设置异常的入口地址和异常处理函数;配置 PLLCON 寄存器,确定系统的主频;屏蔽看门狗和中断;初始化 I/O 寄存器;关闭 MMU 功能;调用 /board/smdk2410 中的lowlevel_init.S ,初始化存储器空间,设置刷新频率;将 U-Boot 的内容复制到
44、 SDRAM 中;设置堆栈的大小( ldr pc, _start_armboot ) ; 设置程序编译连接的起始地址( config.mk: TEXT_BASE = 0x33F80000 )。U-Boot入口函数start.S系统复位代码ldr pc, _start_armboot_start_armboot: .word start_armboot表示u-boot完成cpu初始化,将跳转到C程序中间。 start_armboot完成设备初始化过程,进入main_loop循环。start_armboot - lib_arm/board.ccpu/arm920t/start.Smrsr0,cps
45、rbicr0,r0,#0x1f ; 位清零 orrr0,r0,#0xd3 ; 逻辑或0xd3= 1101 0011msrcpsr,r0复位模式位含义ldr r0, =pWTCONmov r1, #0x0str r1, r0关闭看门狗mov r1, #0xffffffffldrr0, =INTMRstrr1, r0关闭中断movr0, #0mcrp15, 0, r0, c7, c7, 0 /* flush v3/v4 cache */mcrp15, 0, r0, c8, c7, 0 /* flush v4 TLB */刷新指令/数据缓存mrcp15, 0, r0, c1, c0, 0bicr0,
46、 r0, #0x00002300 clear bits 13, 9:8 (-V- -RS)bicr0, r0, #0x00000087 clear bits 7, 2:0 (B- -CAMorrr0, r0, #0x00000002 set bit 2 (A) Alignorrr0, r0, #0x00001000 set bit 12 (I) I-Cachemcrp15, 0, r0, c1, c0, 0禁用MMU和缓存板级相关,/board/lowlevel_init.Smovip, lrbllowlevel_initmovlr, ipmovpc, lr设置RAM1.globl lowle
47、vel_initlowlevel_init:2.ldr r0, =SMRDATA3.ldrr1, _TEXT_BASE4.subr0, r0, r15.ldrr1, =BWSCON6.add r2, r0, #13*40:7.ldr r3, r0, #48.str r3, r1, #49.cmp r2, r010.bne 0blowlevel_init调试u-boot时,开始一般是让它在RAM中运行,当RAM运行通过后才将其固化到FLASH中;这样如果我们当前的代码是通过BDI2000等Load到内存直接运行的话,u-boot就不需要去将自己从Flash搬移到内存了;而如果u-boot是固化在
48、Flash中在CPU复位后由第一个片选信号指向开始执行的话,则有一个从Flash搬移到内存的过程。 重定位(Relocate)的概念1.relocate:/* relocate U-Boot to RAM */2.adrr0, _start/*当前代码的地址信息 */3.ldrr1, _TEXT_BASE /* test if we run from flash or RAM */4.cmp r0, r1 /* dont reloc during debug */5.beq stack_setup6.ldrr2, _armboot_start7.ldrr3, _armboot_end8.sub
49、r2, r3, r2/* r2 - size of armboot */9.addr2, r0, r2/* r2 printenvbaudrate=115200ipaddr=192.168.1.1ethaddr=12:34:56:78:9A:BCserverip=192.168.1.5www.embedu.org116U-boot命令介绍setenv 设置新的变量Uboot setenv myboard AT91RM9200DKUboot printenvbaudrate=115200ipaddr=192.168.1.1ethaddr=12:34:56:78:9A:BCserverip=192
50、.168.1.5myboard=AT91RM9200DKEnvironment size: 102/8188 bytessaveenv 将当前定义的所有的变量及其值存入flash中。www.embedu.org117U-boot命令介绍tftp 通过网络下载程序Uboot setenv ethaddr 12:34:56:78:9A:BCUboot setenv ipaddr 192.168.1.1Uboot setenv serverip 192.168.1.254Uboot tftp 20000000 application.binUboot tftp 30200000 zImageLoad
51、b 通过串口Kermit协议下载二进制数据。www.embedu.org118U-boot命令介绍md 显示内存区的内容。mm 修改内存,地址自动递增。nm 修改内存,地址不自动递增。mw 填充内存。mtest 测试内存。cp 拷贝一块内存到另一块。cmp 比较两块内存区。mw 0x32000000 ff 0x10000www.embedu.org119U-boot命令介绍Protect 写保护操作protect on 1:0-3(就是对第一块FLASH的0-3扇区进行保护)protect off 1:0-3取消写保护erase 擦除扇区erase: 删除FLASH的扇区erase 1:0-2
52、(就是对每一块FLASH的0-2扇区进行删除)www.embedu.org120U-boot命令介绍run 执行设置好的脚本Uboot setenv flashit tftp 20000000 mycode.bin; erase 10020000 1002FFFF;cp.b 20000000 10020000 8000Uboot saveenvUboot run flashitbootcmd 保留的环境变量如果定义了该变量,在autoboot模式下,将会执行该脚本的内容。www.embedu.org121U-boot命令介绍go 执行内存中的二进制代码,一个简单的跳转到指定地址bootm 执行
53、内存中的二进制代码要求二进制代码为制定格式的。通常为mkimage处理过的二进制文件。启动Uboot工具制作的压缩Linux内核bootp 通过网络启动,需要提前设置好硬件地址。 www.embedu.org122U-boot命令介绍usb reset 复位(rescan) USB 控制器usb stop f - 停止USB usb tree - 显示USB 设备树usb info dev 显示可用的USB设备usb storage - 显示USB存储设备详细信息usb dev dev - show or set current USB storage deviceusb part dev -
54、 print partition table of one or all USB storage devicesusb read addr blk# cnt - read cnt blocks starting at block blk#to memory address addrwww.embedu.org123U-boot命令介绍nand info: 显示NAND 设备nand device dev:显示或设置当前设备nand bad 显示坏块nand read.jffs2s addr off sizenand write.jffs2 addr off sizenand erase cle
55、an off size nand read.oob addr off sizenand write.oob addr off sizewww.embedu.org124U-boot命令介绍nfsnfs 32000000 192.168.0.2:aa.txt把192.168.0.2中的NFS文件系统中的aa.txt 读入内存0x32000000处。www.embedu.org125配置编译LINUX内核内容提纲Linux内核介绍与特点1Linux内核源码结构2Linux内核编译3Linux内核启动流程4http:/kernel.orgwget http:/kernel.org/pub/linux
56、/kernel/.wget http:/kernel.org/././linux-*.bz2.signgpg verify linux-2.xx.tar.bz2.signtar jxvf linux-2.xx.tar.bz2下载内核内核配置与编译内核配置与编译使用补丁升级使用补丁升级如果你有一个版本可以到kernel.org下载相应的升级补丁如果想从2.4.26 升级到 2.4.27 则可以wget -c ftp:/ftp.kernel.org/pub/linux/kernel/v2.4/patch-2.4.27.bz2wget ftp:/ftp.kernel.org/pub/linux/ke
57、rnel/v2.4/patch-2.4.27.bz2.sign如果想从 2.6.11 升级到 2.6.11.12 则可以wget http:/www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.11.12.bz2wget http:/www.kernel.org/pub/linux/kernel/v2.6/patch-2.6.11.12.bz2.sign确认下载的补丁是否正确gpg verify patch-2.4.27.bz2.sign为内核代码打补丁 cd linux2.4.26/bzcat ./patch-2.4.27.bz2 | patch -
58、p1 cd .mv linux-2.4.26 linux-2.4.27initkernelmmipclibfsnetdriversLinux内核结构archi386armppcm68kshincludecharusbblockpcikernelmmlib平台无关平台无关平台相关平台相关查看完整命令行make V = 1删除生成的文件make clean删除全部生成的文件,包括.configmake mrproper编译技巧# make mrproper删除不必要的文件和目录. # make config基于文本的最为传统的配置界面,不推荐使用 # make menuconfig基于文本选单的配
59、置界面,字符终端下推荐使用# make xconfig基于图形窗口模式的配置界面,Xwindow下推荐使用 # make oldconfig如果只想在原来内核配置的基础上修改一些小地方,会省去不少麻烦设置内核makexconfig内核配置模块处理器类型电源管理可执行文件格式设备驱动文件系统内核调试make zImage编译安装vmlinux未压缩的Linux内核arch/boot/zImage经zlib压缩后的Linux内核arch/boot/bzImage经zlib压缩后的Linux内核,b是指big,压缩比更高/boot/vmlinuz-经zlib压缩后的Linux内核编译生成的文件/bo
60、ot/System.map-内核符号地址/boot/initrd-.imgramdisk,要挂载的根文件系统/lib/modules/模块modules.dep模块依赖文件编译生成的文件(2)Kconfig 简单语法简单语法-菜单菜单menu 、endmenumenu 围endmenuchoice endchoice choice endchoice“arch/arm/Kconfigsourcesource drivers/Kconfig详细信息参看相关内核文档:DocumentationkbuildLinux内核Makefile分析Linux内核Makefile的作用:决定编译哪些文件怎么样
61、编译这些文件怎么样连接这些文件,最重要的是它们的顺序如何Documentation/kbuild/makefiles.txt 移植的概念如果一个系统可以在不同硬件平台上运行,那么这个系统就是可移植的。使某一个平台的代码运行在其他平台上的过程就叫做移植。Linux操作系统就可以通过移植,使之运行在ARM、PowerPC、M68k等多种硬件平台上。www.embedu.org147字符设备驱动(1)Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 file_oprationfile_opration结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传
62、输基本字符设备驱动函数模板设备驱动程序基础Linux设备驱动概念驱动程序为操作硬件提供良好内部接口驱动程序为应用程序提供了访问设备的机制Linux设备驱动分类字符设备:键盘、鼠标、串口块设备:硬盘、Flash 网络接口:以太网特定类型设备:audio设备系统调用和设备I/O 设备驱动健壮性和安全性驱动程序是内核的一部分驱动程序的漏洞和缺陷直接危及内核留心未初始化的指针,恶意用户程序,缓冲区溢出驱动程序与内核版本号Linux内核版本号简述 2.0.x 2.2.x 2.4.x 2.6.x版本号在内核编译过程中的影响模块加载时的版本号检查Linux下构建和运行模块为什么用模块?模块和应用程序有什么不
63、同#ifdef _KERNEL_#ifdef MODULELinux内核与模块中的并发可重入简介共享资源带来的问题并发与同步问题抢占式内核对并发的影响模块的编译和装载编译模块都需要什么编译模块相关的宏模块工具insmod,rmmod,lsmod,modprobe,modinfoLinux驱动程序模块加载 模块的版本依赖版本号定义可以区分不同版本内核的接口函数可以在linux/version.h找到版本定义KERNEL_VERSION模块初始化和关闭模块加载调用的第一个函数init_module模块所使用资源的分配与释放使用计数模块卸载和cleanup_module显式指定初始化和清除函数一个简
64、单的Linux内核模块1 #include 2 #include 3 MODULE_LICENSE(Dual BSD/GPL);4 static int hello_init(void)5 6 printk(KERN_ALERT Hello World entern);7 return 0;8 9 static void hello_exit(void)10 11 printk(KERN_ALERT Hello World exitn );12 13 module_init(hello_init);14 module_exit(hello_exit);15 16 MODULE_AUTHOR(S
65、ong Baohua);17 MODULE_DESCRIPTION(A simple Hello World Module);18 MODULE_ALIAS(a simplest module);Linux内核模块的程序结构模块加载函数(必须) 模块卸载函数(必须) 模块许可证声明(必须)大多数情况下,内核模块应遵循GPL兼容许可权。Linux 2.6内核模块最常见的是以MODULE_LICENSE( Dual BSD/GPL )语句声明模块采用BSD/GPL双LICENSE。 模块参数(可选)。 模块导出符号(可选) 模块作者等信息声明(可选) 模块参数module_param(参数名,参数
66、类型,参数读/写权限)module_param (myshort, short, 0000);MODULE_PARM_DESC (myshort, A short integer);module_param (myint, int, 0000); MODULE_PARM_DESC (myint, An integer); # insmod hello.ko myshort=55 myint=456导出符号Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。模块可以使用如下宏导出符号到内核符号表:EXPORT_SYMBOL(符号名);EX
67、PORT_SYMBOL_GPL(符号名);模块的使用计数Linux 2.4内核 :MOD_INC_USE_COUNT、MOD_DEC_USE_COUNT Linux 2.6内核 :try_module_get(&module)该函数用于增加模块使用计数 module_put (&module) 该函数用于减少模块使用计数 Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 file_oprationfile_opration结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板Linux字符设备驱动基础驱动注册与初始化主设备
68、号和次设备号设备名设备文件节点操作与file operation结构用户系统调用与驱动函数集合设备名与主次设备号字符设备文件例子crw-rw- 1 root uucp 4, 64 2005-03-20 03:36 /dev/ttyS0主设备号区分设备驱动程序次设备号区分同一个驱动程序创建的多个设备常见于多个串口,硬盘分区等mknod 创建设备文件: mknod /dev/mydevice c 254 0open、close等操作/dev/下设备文件,内核根据文件的主设备号找到对应驱动程序主设备号可以分为动态、静态申请动态分配主设备号register_chrdev注册动态主设备号动态分配主设备号
69、的优缺点/proc/devices和lsmod动态生成设备文件系统节点dev_t和kdev_tfile_opration结构分析file 在内核中定义linux/fs.hfile struct Mode_t f_modeLoff_t f_posUnsigned int f_flagsStruct file_operations *f_opvoid *private_dataStruct dentry *f_dentryLinux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 file_oprationfile_opration结构分析结构分析 用户空间与内核空间数据传输
70、用户空间与内核空间数据传输基本字符设备驱动函数模板file_opration结构分析file_operations 在内核中定义linux/fs.h 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 (*write) (struct file *, const char _user *, size_t, loff_t *)
71、;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 *);int (*open) (struct inode *, struct file *);int (*release) (struct inode *, struct file *);int (*fsync) (st
72、ruct file *, struct dentry *, int datasync);file_operationsint(*open)(struct inode*,struct file *)增加使用计数,检查错误如果未初始化,则调用初始化识别次设备号,如果必要,更新f_op指针分配并填写被置于filp-private_data的数据结构int(*realse)(struct inode*,struct file*)open逆操作file_operationsssize_t (*read)(struct file*,char *,size_t,loff_t*)用户空间和内核空间数据交互用户
73、空间指针和内核指针驱动调用copy_to_user()将数据返回给用户ssize_t(*write)(struct file*,const char*,size_t,loff_t*)驱动调用copy_from_user()将用户数据读到本地bufferfile_operationsint (*mmap)(struct file*, struct vm_area_struct*)驱动中提供该方法用于支持用户mmap操作用户将设备内存区映射到进程的地址空间,直接操作该物理内存提高效率典型例子:framebuffer,sound,capture等驱动file_operationsint(*ioctl
74、)(struct inode*,struct file*,unsigned int cmd,unsigned long arg)驱动程序一般需支持通过Ioctl实现各种控制与参数设置,如串口可设置波特率等多参数cmd变量存放命令,驱动代码根据cmd里面的值进行switch-cas处理分支arg存放参数,如为整数,可直接使用。如为指针,驱动程序首先要检查指针的合法性int access_ok(int type, const void *addr, unsigned long size); 检查通过后可以使用驱动程序还可通过int capable(int capability)函数来确定调用进程是
75、否有权执行操作怎样使用ioctl用户系统调用:int ioctl(int fd, int cmd, .);cmd命令码格式用户空间与Linux内核中定义需一致: _ | 设备类型 | 序列号 | 方向 |数据尺寸| |- |- |- |-| | 8 bit | 8 bit |2 bit |814 bit|Ioctl权能和受限操作驱动程序的访问控制采用linux文件系统的权限机制驱动程序采用权能机制来控制特殊的操作权限驱动程序通过int capable(int capability);函数来确定调用进程是否有权执行操作用ioctl控制驱动程序实例分析int scull_ioctl(struct
76、inode *inode, struct file *filp,unsigned int cmd, unsigned long arg)int err = 0, tmp;int retval = 0; if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;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);e
77、lse if (_IOC_DIR(cmd) & _IOC_WRITE)err = !access_ok(VERIFY_READ, (void _user *)arg, _IOC_SIZE(cmd);if (err) return -EFAULT;用ioctl控制驱动程序实例分析switch(cmd) case SCULL_IOCRESET:scull_quantum = SCULL_QUANTUM;scull_qset = SCULL_QSET;break; case SCULL_IOCSQUANTUM: /* Set: arg points to the value */if (! capa
78、ble (CAP_SYS_ADMIN)return -EPERM;retval = _get_user(scull_quantum, (int _user *)arg);break; case SCULL_IOCTQUANTUM: /* Tell: arg is the value */if (! capable (CAP_SYS_ADMIN)return -EPERM;scull_quantum = arg;break; case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */retval = _put_user(scull_qu
79、antum, (int _user *)arg);break;Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 file_oprationfile_opration结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板用户空间与内核空间数据传输用户(进程)空间受保护的空间,执行于“用户模式”所有地址都是“逻辑”的,不能访问不属于自己的内存需要转换成物理的(真实的)地址执行指令时由硬件辅助完成转换发生页面错误时间接地由操作系统完成内核(系统)空间不受保护的空间,执行于“超级模式”能访问任何内存,所有地址都是“逻辑”的需要转换
80、成物理的(真实的)地址执行指令时由硬件辅助完成转换发生页面错误时,间接地由操作系统完成驱动程序使用的内存获取内存区(kmalloc分配物理内存)BufferDMA bufferioport和iomem映射及使用用户静态映射(iodesc,iomap)动态映射(ioremap)在用户和内核之间传递数据unsigned long copy_from_user(void *to, const void _ _user *from, unsigned long count);unsigned long copy_to_user(void _ _user *to, const void *from, u
81、nsigned long count);典型的嵌入式设备存储器映射 Linux驱动程序基础知识 LinuxLinux字符设备驱动基础字符设备驱动基础 file_oprationfile_opration结构分析结构分析 用户空间与内核空间数据传输用户空间与内核空间数据传输基本字符设备驱动函数模板模块加载与卸载函数模板 struct xxx_dev_t /设备结构体 struct cdev cdev; . xxx_dev; static int _ _init xxx_init(void) /设备驱动模块加载函数 . cdev_init(&xxx_dev.cdev, &xxx_fops); /初
82、始化cdev xxx_dev.cdev.owner = THIS_MODULE; if (xxx_major) /获取字符设备号 register_chrdev_region(xxx_dev_no, 1, DEV_NAME); else alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME); ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); /注册设备 . static void _ _exit xxx_exit(void) /*设备驱动模块卸载函数*/ unregister_chrdev_region(xx
83、x_dev_no, 1); /释放占用的设备号 cdev_del(&xxx_dev.cdev); /注销设备 .读、写、I/O控制函数模板/* 读设备*/ssize_t xxx_read(struct file *filp, char _ _user *buf, size_t count,loff_t*f_pos) copy_to_user(buf, ., .); /* 写设备*/ssize_t xxx_write(struct file *filp, const char _ _user *buf, size_t count,loff_t *f_pos) . copy_from_user(.
84、, buf, .); . /* ioctl函数 */int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) . switch (cmd) case XXX_CMD1: . break; case XXX_CMD2: . break; default: /* 不能支持的命令 */ return - ENOTTY; return 0; 字符设备驱动(2)字符设备访问控制同步、互斥、阻塞、睡眠Poll和select操作异步通知机制字符设备访问控制访问控制可以防止未授权用户使用
85、设备,这种控制通过设置文件系统权限来实现最简单实现访问控制是一次只允许一个进程打开设备用一个标志变量来指示当前设备是否已经被打开若已经打开则拒绝新的打开操作全局标志的竞争问题在上例中,如果有两个进程试图同时打开设备,则他们有可能同时测试全局标志,并同时成功打开。为了避免这种情况发生,我们可以以原子操作的形式修改标志使用自旋锁比较适合此处使用限制每次只有一个用户访问此种实现允许一个用户多次打开设备维护数据结构的完整性由用户来完成用户第一次打开设备时授权,并记录下设备的属主。该用户可以对此打开设备其他用户试图再打开设备时,UID检查结果会拒绝打开,并返回-EBUSY,指示设备忙用阻塞open代替E
86、BUSY有些情况下,设备忙时最好让用户稍延迟一会儿而不是返回失败可以用阻塞型的open来实现当用户试图打开设备时,如果设备忙,则把当前进程置入等待队列当前一个用户使用完设备,使用计数减到0时,下一个用户睡眠的进程将被唤醒字符设备访问控制同步、互斥、阻塞、睡眠poll和select操作异步通知机制竟态产生的原因对称多处理器(SMP)的多个CPU单CPU内进程与抢占它的进程中断(硬中断、软中断、Tasklet、底半部)与进程之间原子操作原子操作指的是在执行过程中不会被别的代码路径所中断的操作。常用原子操作函数举例:atomic_t v = ATOMIC_INIT(0); /定义原子变量v并初始化为
87、0atomic_read(atomic_t *v); /返回原子变量的值void atomic_inc(atomic_t *v); /原子变量增加1void atomic_dec(atomic_t *v); /原子变量减少1int atomic_dec_and_test(atomic_t *v); /自减操作后测试其是否为0,为0则返回true,否则返回false。原子变量的使用实例原子变量的使用实例:最多只能被一个进程打开 static atomic_t xxx_available = ATOMIC_INIT(1); /*定义原子变量*/static int xxx_open(struct
88、inode *inode, struct file *filp) . if (!atomic_dec_and_test(&xxx_available) atomic_inc(&xxx_available); return - EBUSY; /*已经打开*/ . return 0; /* 成功 */ static int xxx_release(struct inode *inode, struct file *filp) atomic_inc(&xxx_available); /* 释放设备 */ return 0; 自旋锁自旋锁(spin lock)是一种对临界资源进行互斥手访问的典型手段L
89、inux系统中与自旋锁相关的操作主要有如下4种。1定义自旋锁spinlock_t spin;2初始化自旋锁spin_lock_init(lock)3获得自旋锁spin_lock(lock)/该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋 在那里,直到该自旋锁的保持者释放;spin_trylock(lock)/该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再“在原地打转”;4释放自旋锁spin_unlock(lock)/该宏释放自旋锁lock,它与spin_trylock或spin_lock配对使用。自旋锁使用举例使用自旋锁
90、使设备只能被一个进程打开 int xxx_count = 0;/*定义文件打开次数计数*/ static int xxx_open(struct inode *inode, struct file *filp) . spinlock(&xxx_lock); if (xxx_count)/*已经打开*/ spin_unlock(&xxx_lock); return - EBUSY; xxx_count+;/*增加使用计数*/ spin_unlock(&xxx_lock); . return 0; /* 成功 */ static int xxx_release(struct inode *inod
91、e, struct file *filp) . spinlock(&xxx_lock); xxx_count-; /*减少使用计数*/ spin_unlock(&xxx_lock); return 0; 信号量信号量(semaphore)是用于保护临界区的一种常用方法,它的使用方式和自旋锁类似。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态信号量相关操作定义信号量struct semaphore sem;初始化信号量void sema_init (struct semaphore *sem, int val
92、);void init_MUTEX(struct semaphore *sem);/初始化为0获得信号量void down(struct semaphore * sem);int down_interruptible(struct semaphore * sem); int down_trylock(struct semaphore * sem);释放信号量void up(struct semaphore * sem);信号量使用实例使用信号量实现设备只能被一个进程打开 static DECLARE_MUTEX(xxx_lock);/定义互斥锁static int xxx_open(struc
93、t inode *inode, struct file *filp) . if (down_trylock(&xxx_lock) /获得打开锁 return - EBUSY; /设备忙 . return 0; /* 成功 */ static int xxx_release(struct inode *inode, struct file *filp) up(&xxx_lock); /释放打开锁 return 0; 自旋锁vs信号量自旋锁和信号量选用的3项原则1、判断进程切换时间Tsw,和等待获取自旋锁(由临界区执行时间决定)Tcs。如果若Tcs比较小,应使用自旋锁,若Tcs很大,应使用信号量。
94、2、信号量所保护的临界区可包含可能引起阻塞的代码,而自旋锁则绝对要避免用来保护包含这样代码的临界区。因为阻塞意味着要进行进程的切换,如果进程被切换出去后,另一个进程企图获取本自旋锁,死锁就会发生3、信号量存在于进程上下文,因此,如果被保护的共享资源需要在中断或软中断情况下使用,则在信号量和自旋锁之间只能选择自旋锁。当然,如果一定要使用信号量,则只能通过down_trylock()方式进行,不能获取就立即返回以避免阻塞。阻塞与非阻塞I/O阻塞操作阻塞操作 是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的
95、条件被满足。非阻塞操作非阻塞操作 的进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。阻塞地读取串口一个字符char buf;fd = open(/dev/ttyS1, O_RDWR); .res = read(fd,&buf,1); /当串口上有输入时才返回if(res=1) printf(%cn, buf);非阻塞地读取串口一个字符char buf;fd = open(/dev/ttyS1, O_RDWR| O_NONBLOCK); .while(read(fd,&buf,1)!=1); /串口上无输入也返回,所以要循环尝试读取串口printf(%cn,
96、buf);等待队列等待队列等待队列 以队列为基础数据结构,与进程调度机制紧密结合,能够用于实现内核中的异步事件通知机制,也可以用来同步对系统资源的访问(如信号量)等待队列的操作(1)定义“等待队列头”wait_queue_head_t my_queue;初始化“等待队列头”init_waitqueue_head(&my_queue);定义等待队列DECLARE_WAITQUEUE(name, tsk)添加/移除等待队列void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);void fastcall remo
97、ve_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);等待队列的操作(2)等待事件wait_event(queue, condition)wait_event_interruptible(queue, condition)wait_event_timeout(queue, condition, timeout)wait_event_interruptible_timeout(queue, condition, timeout)唤醒队列void wake_up(wait_queue_head_t *queue);void wake_up_
98、interruptible(wait_queue_head_t *queue);等待队列的操作(3)在等待队列上睡眠sleep_on(wait_queue_head_t *q );Interruptible_sleep_on(wait_queue_head_t *q );字符设备访问控制同步、互斥、阻塞、睡眠Poll和select操作异步通知机制poll和select操作进程中调用poll和select操来查询打开的I/O设备文件是否可做非阻塞读写驱动程序中unsigned int (*poll) (struct file *, poll_table *)来实现poll和select操作通过p
99、oll_wait可以向驱动向poll_table结构添加一个等待队列驱动的poll函数应该实现返回那个操作可以立即完成而无需休眠POLLIN,POLLOUT,POLLDNORM,POLLERRLinux/poll.h应用程序中的轮询编程int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合,numfds的值是需要检查的号码最高的文
100、件描述符加1。timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval数据结构的定义如代码清单8.10所示。poll()函数典型模板 static unsigned int xxx_poll(struct file *filp, poll_table *wait) unsigned int mask = 0;struct xxx_dev *dev = filp-private_data; /*获得设备结构体指针*/ . poll_wait(filp, &dev-r_wait,
101、 wait);/加读等待队列头poll_wait(filp, &dev-w_wait, wait);/加写等待队列头 if (.)/可读 mask |= POLLIN | POLLRDNORM; /*标示数据可获得*/ if (.)/可写 mask |= POLLOUT | POLLWRNORM; /*标示数据可写入*/ . return mask; 字符设备访问控制同步、互斥、阻塞、睡眠Poll和select操作异步通知机制异步通知的概念与作用异步通知的意思是:一旦设备就绪,则主动通知应用程序,这样应用程序根本就不需要查询设备状态,这一点非常类似于硬件上“中断”的概念,比较准确的称谓是“信号
102、驱动的异步I/O”。Linux信号使用信号进行进程间通信(IPC)是UNIX系统中的一种传统机制,Linux系统也支持这种机制。在Linux系统中,异步通知使用信号来实现。除了SIGSTOP和SIGKILL两个信号外,进程能够忽略或捕获其他的全部信号。例如:在进程执行时,按下Ctrl+c组合键将向其发出SIGINT信号,kill正在运行的进程将向其发出SIGTERM信号SIGHUP 1挂起 SIGINT 2终端中断 SIGQUIT 3终端退出 信号的接收在用户程序中,为了捕获信号,可以使用signal()函数来设置对应信号的处理函数,如下所示:void (*signal(int signum,
103、 void (*handler)(int) (int) ; signal()捕获信号范例void sigterm_handler(int signo) printf(Have caught sig N.O. %dn, signo); exit(0); int main(void) signal(SIGINT, sigterm_handler); signal(SIGTERM, sigterm_handler); while(1); return 0; 异步通知的应用程序首先应用程序要指定进程为文件的属主,通过fcntl执行F_SETOWN其次,应用程序要在设备中设置FASYNC标志,这是通过f
104、cntl执行F_SETFL,例如:signal(SIGIO, &input_handler);fcntl(STDIN_FILENO, F_SETOWN,getpid();Oflags = fcntl(STDIN_FILENO,F_GETFL); fcntl(STDIN_FILENO, F_SETFL,oflags | FASYNC);异步通知的内核程序为了使设备支持异步通知机制,驱动程序中涉及以下3项工作。支持F_SETOWN命令,能在这个控制命令处理中设置filp-f_owner为对应进程ID。不过此项工作已由内核完成,设备驱动无须处理。支持F_SETFL命令的处理,每当FASYNC标志改变时,驱动程序中的fasync()函数将得以执行。因此,驱动中应该实现fasync()函数。在设备资源可获得时,调用kill_fasync()函数激发相应的信号。实现图例 阻塞I/O,poll()非阻塞I/O、异步通知谢谢!223