




下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第5章
嵌入式Linux应用程序开发
5.1开发环境的建立进行项目开发前,首先要做的是搭建一套基于Linux操作系统的应用开发环境,一般由目标板和宿主机所构成。目标板用于运行操作系统和系统应用软件,而目标板所用到的操作系统的内核编译、应用程序的开发和调试则需要通过宿主机来完成。开发环境对硬件没有特殊的要求,但是为了双方之间建立连接关系,关键的接口包括串口、以太网口和USB口等是必不可少的。嵌入式Linux的开发环境传统的嵌入式开发环境需要单片机的仿真器,包含C语言、汇编语言、调试工具等的集成开发环境IDE和实时操作系统等,整个开发系统建立起来至少需要几万元。由于Flash技术的发展,特别是一些CPU可以用JTAG接口下载调试,故仿真器已可以省去。随着标准化的推广,JTAG调试工具变得越来越简单、越来越通用。一些BDM调试工具已经简单到只需在PC机并行口和单片机的JTAG接口之间加一级5V到3.3V的电平转换,这使得自制调试工具变得越来越容易。软件方面,Linux下的自由软件GNUgcc可以完成几乎所有知名CPU,以及DSP的交叉C编译和调试,故可以省去IDE。个人用Linux开发嵌入式应用程序,可以在自己的PC机上安装一套Linux操作系统,使用Linux中的XWindows打开若干个窗口用于编译、下载、调试等。如果整个研发小组由多名工程师组成,常采用的办法是:用1台PC机运行Linux作为服务器;开发小组中每个成员都通过局域网用Telnet登录到这台Linux服务器上去;被开发的目标板也挂在网上。在服务器的Linux环境下,各个工程师用GNUgcc编译生成目标代码,再用FTP传回到自己的PC机上,然后通过串行口或网络下载到目标机上。利用Cygwin建立模拟环境交叉编译环境的建立交叉编译就是在一个平台上生成可以在另一个平台上执行的代码。在宿主机上对即将运行在目标机上的应用程序进行编译,生成可在目标机上运行的代码格式。交叉编译环境是一个由编译器、连接器和解释器组成的综合开发环境。交叉编译工具主要包括针对目标系统的编译器gcc、目标系统的二进制工具binutils、目标系统的标准c库glibc和目标系统的Linux内核头文件。在建立交叉编译环境之前,首先需要下载包括binutils、gcc、glibc及Linux内核在内的源代码,尽量选用较新版本,glibc和内核源代码的版本必须与目标机上实际使用的版本保持一致,并设定shell变量PREFIX指定可执行程序的安装路径。(1)编译binutils。运行configure文件,并使用--prefix=$PREFIX参数指定安装路径,使用--target=arm-linux参数指定目标机类型,然后执行makeinstall。(2)配置Linux内核头文件。执行makemrproper进行清理工作,然后执行makeconfigARCH=arm(或makemenuconfig/xconfigARCH=arm)进行配置。一定要在命令行中使用ARCH=arm指定CPU架构,因为默认架构为主机的CPU架构,这一步需要根据目标机的实际情况进行详细的配置。配置完成之后,需要将内核头文件拷贝到安装目录:cp-dRinclude/asm-arm$PREFIX/arm-linux/include/asmcp-dRinclude/linux$PREFIX/arm-linux/include/linux(3)第一次编译gcc。configure的运行参数设置:--prefix=$PREFIX--target=arm-linux--disable-threads--disable-shared--enable-languages=c执行makeinstall,将生成一个最简单的gcc。由于编译整个gcc是需要目标机的glibc库的,它现在还不存在,因此需要首先生成一个最简单的gcc,它只需要具备编译目标机glibc库的能力即可。(4)交叉编译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和makeinstall,glibc的交叉编译过程就算完成了,这里需要指出的是,glibc的安装路径设置为$PREFIXARCH=arm/arm-linux,如果此处设置不当,第二次编译gcc时可能找不到glibc的头文件和库。(5)第二次编译gcc。运行configure,参数设置为--prefix=$PREFIX--target=arm-linux--enable-languages=c,c++。运行makeinstall。到此为止整个交叉编译环境就完全生成了。建立一个交叉编译工具链是一个相当复杂的过程,为了节省时间,网上有一些编译好的可用的交叉编译工具链可以下载。本书所附光盘也带有编译好的交叉编译工具链arm-linux-toolchains.tgz,只需简单地解压缩即可使用:tarxvzfarm-linux-toolchains.tgz–C/arm9假设工具链解压缩到目录/arm9。解压完毕后把工具链目录加入到环境变量PATH中即可。交叉编译工具做完后,简单验证一下。首先用文字输入软件建立一个helloworld.c文件:#include
<stdio.h>int
main(void){
printf("hello
world\n");
return
0;}然后在命令行执行:$arm-linux-gcc
helloworld.c
-o
helloworld$file
helloworld如果输出以下信息,说明成功建立了编译工具。helloworld:
ELF
32-bit
LSB
executable,
ARM,
version
1,
dynamically
linked
(uses
shared
libs),
not
stripped5.2Linux及开发工具的使用GNU提供的编译工具包括汇编器as、C编译器gcc、C++编译器g++、链接器ld和二进制转换工具objcopy。基于ARM平台的工具分别为arm-linux-as、arm-linux-gcc、arm-linux-g++、arm-linux-ld和arm-linux-objcopy。GNU的所有开发工具都可以从上下载,基于ARM的工具可以从获得。GNU的编译器功能非常强大,共有上百个操作选项,这也是这类工具让初学者头痛的原因。不过,实际开发中只需要用到有限的几个,大部分可以采用默认选项。GNU工具的开发流程如下:编写C、C++语言或汇编源程序,用gcc或g++生成目标文件,编写链接脚本文件,用链接器生成最终目标文件(elf格式),用二进制转换工具生成可下载的二进制代码。Linux下的GNU调试工具主要是gdb、gdbserver和kgdb。其中gdb和gdbserver可完成对目标板上Linux应用程序的远程调试。gdbserver是一个很小的应用程序,运行于目标板上,可监控被调试进程的运行,并通过串口与上位机上的gdb通信。开发者可以通过上位机的gdb输入命令,控制目标板上进程的运行,查看内存和寄存器的内容。gdb5.1.1以后的版本加入了对ARM处理器的支持,在初始化时加入-target==arm参数可直接生成基于ARM平台的gdbserver。gdb工具可以从/pub/gnu/gdb/上下载Linux常用命令用户在命令行输入命令后,由外壳进行解释。外壳是一种命令解释器,提供了用户和操作系统之间的交互接口。外壳是面向命令行的,而XWindow则是图形界面。外壳解释的命令送往操作系统执行。外壳可以执行Linux的系统内部命令,也可以执行应用程序。用户还可以利用外壳编程,执行复杂的命令程序。Linux提供几种外壳程序以供选择。常用的有Bourne外壳、C外壳和Korn外壳。各个外壳都能提供基本的功能,又有其各自的特点。Bourne外壳是由StevenBourne编写的,是UNIX的默认外壳。Bourne外壳的外壳编程能力很强,但是不能处理命令的用户交互特征,bash是Bourne外壳的增强版。C外壳是由加利福尼亚大学伯克利分校的BillJoy编写的。能提供Bourne外壳所不能处理的用户交互特征,如命令补全、命令别名、历史命令替换等。C外壳的编程能力不如Bourne外壳,但由于它的语法和C语言类似,所以C程序员将发现C外壳很顺手。Korn外壳是由DaveKorn编写的。Korn外壳融合了C外壳和Bourne外壳的优点,并与Bourne外壳完全兼容。Korn外壳的效率很高,其命令交互界面和编程交互界面都很不错。Bash是大多数Linux系统的默认外壳。其克服了Bourne外壳的缺点,又和Bourne外壳完全兼容。1.登录和退出Linux启动后,给出login命令,等待用户登录。Login:<输入用户名>Password:<输入密码>使用logout命令退出外壳。2.更改账号密码使用passwd命令来设置新用户的口令。在设置口令之后,账号即能正常工作。语法:passwdOldpassword:<输入旧密码>Newpassword:<输入新密码(最好为6~8字,英文字母与数字混合)>Retypenewpassword:<再输入一次密码>3.联机帮助系统上几乎每条命令都带有相关的Manpage。在有困难时,可以立刻找到文件。语法:man命令例如,如果使用ls命令时遇到困难,可以输入:manls4.远程登录用来连接到其他机器执行工作。在Linux上,由于对TCP/IP协议的完全支持,用户可以很容易的从Linux主机连接其他的计算机系统。语法:rlogin主机名[-l用户名]例如:rlogindoc-luser使用user账号登录到工作站doc中。语法:telnet主机名或telnetIP地址例如:telnet515.列出文件或目录语法:ls[-atFlgR][name]其中name是文件名或目录名。ls命令用来浏览文件与目录,对于每个目录,该命令将列出其中所有的子目录与文件。6.改变工作目录语法:cd[name]其中name是目录名、路径或目录缩写。cd除了有切换目录的功能外,还有一个功能就是,不管在哪个目录内,只要输入cd命令,不加任何参数,即可回到用户目录内。7.复制文件语法:cp[-r]源地址目的地址带目录的拷贝,相当于DOS内的xcopy。8.移动或更改文件、目录名称语法:mv源地址目的地址可以为文件或目录改名,也可以将文件由一个目录移入另一个目录。9.建立新目录语法:mkdir目录名10.删除目录语法:rmdir目录名或rm目录名11.删除文件语法:rm文件名12.列出当前所在的目录位置语法:pwd13.查看文件内容语法:cat文件名14.分页查看文件内容语法:more文件名或cat文件名|more15.查看目录所占磁盘容量语法:du[-s]目录16.文件传输(1)拷贝文件或目录至远程工作站。语法:rcp[-r]源地址目的主机名:目的地址。(2)自远程工作站拷贝文件或目录。语法:rcp[-r]源主机名:源地址目的地址。(3)本地工作站与远程工作站之间的文件传输,必须拥有远程工作站的账号及密码,才可进行传输工作。语法:ftp主机名或ftpip地址。17.文件权限的设定(1)改变文件或目录的读、写、执行权限。语法:chmod[-R]modenamename:文件名或目录名。mode:3位8进制数字或rwx的组合。r-read(读),w–write(写),x–execute(执行),u–user(当前用户),g-group(组),o-other(其他用户)。(2)改变文件或目录的所有权。语法:chown[-R]用户名name18.检查自己所属的工作组名称语法:groups19.改变文件或目录工作组所有权语法:chgrp[-R]工作组名name20.改变文件或目录的最后修改时间语法:touchname21.文件的链接同一文件,可拥有一个以上的名称,也就是把一个文件进行链接。语法:ln老文件名新文件名22.文件中字符串的查找语法:grepstringfile23.查寻文件或命令的路径语法:whereiscommand显示命令的路径。语法:whichcommand显示命令的路径,及使用者所定义的别名。语法:whatiscommand显示命令功能的摘要。语法:findsearch-path-namefilename-print搜寻指定路径下某文件的路径。24.比较文件或目录的内容语法:diff[-r]name1name225.文件打印输出用户可用.login文件中的setenvPRINTER命令来设定打印机名。26.一般文件的打印语法:lpr[-P打印机名]文件名27.ptroff文件的打印语法:ptroff[-P打印机名][-man][-ms]文件名28.打印机控制命令(1)检查打印机状态、打印作业顺序号和用户名。语法:lpq[-P打印机名](2)删除打印机内的打印作业(用户仅可删除自己的打印作业)。语法:lprm[-P打印机名]用户名或作业编号29.进程控制(1)查看系统中的进程。语法:ps[-aux](2)结束或终止进程。语法:kill[-9]PIDPID:利用ps命令所查出的进程号。(3)在后台执行进程的方式。语法:命令&(4)查看正在后台中执行的进程。语法:jobs(5)结束或终止后台中的进程。语法:kill%nn:利用jobs命令查看出的后台作业号30.外壳变量(1)查看外壳变量的设定值。语法:set查看所有外壳变量的设定值。语法:echo$变量名显示指定的外壳变量的设定值。(2)设定外壳变量。语法:setvar=value(3)删除外壳变量。语法:unsetvar31.环境变量(1)查看环境变量的设定值。语法:setenv查看所有环境变量的设定值。语法:echo$NAME显示指定的环境变量NAME的设定值。(2)设定环境变量。语法:setenvNAMEword(3)删除环境变量。语法:unsetenvNAME32.别名(1)查看所定义的命令的别名语法:alias查看自己目前定义的所有命令,以及所对应的别名。语法:aliasname查看指定的name命令的别名。(2)定义命令的别名。语法:aliasname'commandline'(3)删除所定义的别名。语法:unaliasname33.历史命令(1)设定命令记录表的长度。语法:sethistory=n(2)查看命令记录表的内容。语法:history(3)使用命令记录表。语法:!!重复执行前一个命令。语法:!nn:命令记录表的命令编号。语法:!string重复前面执行过的以string为起始字符串的命令。(4)显示前一个命令的内容。语法:!!:p(5)更改前一个命令的内容并执行。语法:^oldstring^newstring将前一个命令中oldstring的部分改成newstring并执行。34.文件的压缩(1)压缩文件。语法:compress文件名压缩文件语法:compressdir目录名压缩目录(2)解压缩文件。语法:press文件名解压缩文件语法:pressdir目录名解压缩目录35.管道命令的使用语法:命令1|命令2将命令1的执行结果送到命令2,作为命令2的输入。36.输入/输出控制(1)标准输入的控制。语法:命令<文件将文件作为命令的输入。例如:mail-s"mailtest"<file1将文件file1当做信件的内容,主题名称为mailtest,送给收信人。(2)标准输出的控制。语法:命令>文件将命令的执行结果送至指定的文件中。例如:ls-l>list将执行“ls-l”命令的结果写入文件list中。37.查看系统中的用户语法:who或finger语法:w语法:finger用户名或finger用户名@域名38.改变用户名语法:su用户名例如:suuser进入用户user的账号,passwrod:<输入用户user的密码>39.查看用户名语法:whoami查看登录时的用户名。语法:whoami查看当前的用户名。若已执行过su命令,则显示出此用户的用户名。40.检查远程系统是否正常语法:ping主机名或pingIP地址例如:pingdocgcc编译器的使用gcc最基本的用法是:gcc[options]file...其中的option是以“-”开始的各种选项,file是相关的文件名。在使用gcc的时候,必须要给出必要的选项和文件名。gcc的整个编译过程分别是:预处理,编译,汇编和链接。gcc编译器几个最常用的选项:
-o表示要求编译器生成指定文件名的可执行文件;
-c表示只要求编译器进行编译,而不要进行链接,生成以源文件的文件名命名但把其后缀由.c或.cc变成.o的目标文件;
-g要求编译器在编译的时候提供以后对程序进行调试的信息;
-E表示编译器对源文件只进行预处理就停止,而不做编译、汇编和链接;
-S表示编译器只进行编译,而不做汇编和链接;
-O是编译器对程序提供的编译优化选项,在编译的时候使用该选项,可以使生成的执行文件的执行效率提高;
-Wall指定产生全部的警告信息。$gcc-ohellohello.cgcc编译器就会生成一个hello的可执行文件。在hello.c的当前目录下执行./hello就可以看到程序的输出结果,在屏幕上打印出“Hellotheworld”的字符串来。GNU编译器生成的目标文件默认格式为elf(executivelinkedfile)格式,这是Linux系统所采用的可执行链接文件的通用文件格式。elf格式由若干个段(section)组成,如果没有特别指明,由标准c源代码生成的目标文件中包含以下段:
.text(正文段)包含程序的指令代码,
.data(数据段)包含固定的数据,如常量,字符串等,
.bss(未初始化数据段)包含未初始化的变量和数组等。Makefile文件和Make命令Makefile文件描述了目标文件之间的依赖关系,以及指定编译过程中使用的工具。一个工程中的源文件不计其数,按其类型、功能、模块分别放在若干个目录中。Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作。Makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。Makefile带来的好处就是“自动化编译”,一旦写好,只需要一个Make命令,整个工程完全自动编译,极大地提高了软件开发的效率。Makefile的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成可执行的二进制文件。Makefile中一般包含如下内容:
需要由make工具创建的项目,通常是目标(target)文件和可执行文件。
要创建的项目依赖于哪些文件。
创建每个项目时需要运行的命令。例如:example.o:example.cexample.hg++-c-gexample.c从上面的例子可以看出,第一行指定example.o为目标,并且依赖于example.c和example.h文件。随后的行指定了如何从目标所依赖的文件建立目标。当example.c或example.h文件在编译之后又被修改,则make工具可自动重新编译example.o,如果在前后两次编译之间,example.c和example.h均没有被修改,而且example.o还存在的话,就没有必要重新编译。1.Makefile中的变量顶层Makefile定义并向环境中输出了许多变量,为各个子目录下的Makefile传递一些信息。有些变量,比如,SUBDIRS,不仅在顶层Makefile中定义并且赋初值,而且在arch/arm/Makefile还做了扩充。常用的变量有以下几类:
版本信息版本信息有VERSION、PATCHLEVEL、SUBLEVEL、EXTRAVERSION和KERNELRELEASE等变量,用来定义当前内核的版本。比如,VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXTRAVERSION=-rmk7,共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk7。
CPU体系结构:ARCH在顶层Makefile的开头,用ARCH定义目标CPU的体系结构,比如,ARCH:=arm。许多子目录的Makefile中,要根据ARCH的定义选择编译源文件的列表。
路径信息:TOPDIR和SUBDIRSTOPDIR定义了Linux内核源代码所在的根目录。例如,各个子目录下的Makefile通过$(TOPDIR)/Rules.make就可以找到Rules.make的位置。SUBDIRS定义了一个目录列表,在编译内核或模块时,顶层Makefile就是根据SUBDIRS来决定进入哪些子目录。SUBDIRS的值取决于内核的配置,在顶层Makefile中SUBDIRS赋值为kerneldriversmmfsnetipclib;根据内核的配置情况,在arch/*/Makefile中扩充了SUBDIRS的值,可参考arch/arm/Makefile的例子。
内核组成信息:HEAD,CORE_FILES,NETWORKS,DRIVERS,LIBS。Linux内核文件vmlinux是由以下规则产生的:vmlinux:$(CONFIGURATION)init/main.oinit/version.olinuxsubdirs$(LD)$(LINKFLAGS)$(HEAD)init/main.oinit/version.o\--start-group\$(CORE_FILES)\$(DRIVERS)\$(NETWORKS)\$(LIBS)\--end-group\-ovmlinux可以看出,vmlinux是由HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS和LIBS组成的。这些变量(如HEAD)都是用来定义链接生成vmlinux所需的目标文件和库文件列表。其中,HEAD在arch/arm/Makefile中定义,用来确定最先链接进vmlinux的文件列表。比如,对于ARM系列HEAD的定义为:HEAD:=arch/arm/kernel/head-$(PROCESSOR).o\arch/arm/kernel/init_task.o表明head-$(PROCESSOR).o和init_task.o需要最先被链接到vmlinux中。PROCESSOR为armv或armo,取决于目标CPU。编译信息:CPP,CC,AS,LD,AR,CFLAGS,LINKFLAGS在Rules.make中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了PILE。比如:PILE
=
arm-linux-CC
=
$(PILE)gccLD
=
$(PILE)ld由于PILE定义了交叉编译器前缀arm-linux-,表明所有的交叉编译工具都是以arm-linux-开头的,所以在各个交叉编译器工具之前,都加入了$(PILE),以组成一个完整的交叉编译工具文件名,比如,arm-linux-gcc。CFLAGS定义了传递给C编译器的参数。LINKFLAGS是链接生成vmlinux时,由链接器使用的参数。LINKFLAGS在arm/*/Makefile中定义,比如:#
arch/arm/MakefileLINKFLAGS
:=-p
-X
-T
arch/arm/vmlinux.lds配置变量CONFIG_*.config文件中有许多的配置变量等式,用来说明用户配置的结果。例如,CONFIG_MODULES=y表明用户选择了Linux内核的模块功能。.config被顶层Makefile包含后,就形成许多的配置变量,每个配置变量具有确定的值:y表示本编译选项对应的内核代码被静态编译进Linux内核;m表示本编译选项对应的内核代码被编译成模块;n表示不选择此编译选项;如果没有赋值,那么配置变量的值为空。2.Rules.make变量Rules.make定义了所有Makefile共用的编译规则。Linux把共用的编译规则统一放置到Rules.make中,并在各自的Makefile中通过语句“includeRules.make”包含Rules.make。这样就避免了在多个Makefile中重复同样的规则。Rules.make文件定义了许多变量,最为重要的是编译、链接列表变量。
O_OBJS、L_OBJS、OX_OBJS和LX_OBJS:这些变量代表的是本级目录下需要编译进Linux内核vmlinux的目标文件列表,其中OX_OBJS和LX_OBJS中的“X”表明目标文件使用了EXPORT_SYMBOL输出符号。
M_OBJS和MX_OBJS:定义本级目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS中的“X”表明目标文件使用了EXPORT_SYMBOL输出符号。
O_TARGET和L_TARGET:每个子目录下都有一个O_TARGET或L_TARGET,Rules.make首先从源代码编译生成O_OBJS和OX_OBJS中所有的目标文件,然后使用$(LD)-r把它们链接成一个O_TARGET或L_TARGET。O_TARGET以.o结尾,而L_TARGET以.a结尾。3.子目录Makefile子目录Makefile用来控制本级目录下源代码的编译规则。#
Makefile
for
the
linux
kernel.#
All
of
the
(potential)
objects
that
export
symbols.#
This
list
comes
from
'grep
-l
EXPORT_SYMBOL
*.[hc]'.export-objs
:=
tc.o#
Object
file
lists.obj-y
:=obj-m
:=obj-n
:=obj-
:=obj-$(CONFIG_TC)
+=
tc.oobj-$(CONFIG_ZS)
+=
zs.oobj-$(CONFIG_VT)
+=
lk201.o
lk201-map.o
lk201-remap.o#
Files
that
are
both
resident
and
modular:
remove
from
modular.obj-m
:=
$(filter-out
$(obj-y),
$(obj-m))#
Translate
to
Rules.make
lists.L_TARGET
:=
tc.aL_OBJS
:=
$(sort
$(filter-out
$(export-objs),
$(obj-y)))LX_OBJS
:=
$(sort
$(filter
$(export-objs),
$(obj-y)))M_OBJS
:=
$(sort
$(filter-out
$(export-objs),
$(obj-m)))MX_OBJS
:=
$(sort
$(filter
$(export-objs),
$(obj-m)))include
$(TOPDIR)/Rules.make4.makemake是一个命令工具,是一个解释Makefile中指令的命令工具。一般来说,大多数的IDE都有这个命令,比如:Delphi的make,VisualC++的nmake,以及Linux下GNU的make。make命令执行时,需要一个Makefile文件,以告诉make命令怎么去编译和链接程序。一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的Makefile来执行,一切都是自动的。GNU的make工作时的执行步骤入下:(1)读入所有的Makefile。(2)读入被include的其他Makefile。(3)初始化文件中的变量。(4)推导隐含规则,并分析所有规则。(5)为所有的目标文件创建依赖关系链。(6)根据依赖关系,决定哪些目标要重新生成。(7)执行生成命令。1~5步为第一个阶段,6~7步为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖关系被决定要使用了,变量才会在其内部展开。配置文件在Linux内核中,配置命令有多种方式:Make configMake oldconfig
scripts/Configure
Make menuconfig
scripts/Menuconfig
Make xconfig
scripts/tkparse
以字符界面配置(makeconfig)为例,顶层Makefile调用scripts/Configure,按照arch/arm/config.in来进行配置。命令执行完后产生文件.config,其中保存着配置信息。下一次再做makeconfig将产生新的.config文件,原.config被改名为.config.old。下面对配置文件所支持的配置语句做简要的说明。1.顶层菜单mainmenu_name
/prompt/
/prompt/
其中/prompt/是一串提示符,mainmenu_name设置最高层菜单的名字,只在makexconfig时才会显示。2.询问语句bool
/prompt/
/symbol/hex
/prompt/
/symbol/
/word/int
/prompt/
/symbol/
/word/string
/prompt/
/symbol/
/word/tristate
/prompt/
/symbol/询问语句首先显示一串提示符/prompt/,等待用户输入,并把输入的结果赋给/symbol/所代表的配置变量。不同的询问语句的区别在于它们接受的输入数据类型不同,比如,bool接受布尔类型(y或n),hex接受16进制数据。有些询问语句还有第三个参数/word/,用来给出默认值。3.定义语句define_bool
/symbol/
/word/define_hex
/symbol/
/word/define_int
/symbol/
/word/define_string
/symbol/
/word/define_tristate
/symbol/
/word/不同于询问语句的等待用户输入,定义语句显式的给配置变量/symbol/赋值/word/。4.依赖语句dep_bool
/prompt/
/symbol/
/dep/
...dep_mbool
/prompt/
/symbol/
/dep/
...dep_hex
/prompt/
/symbol/
/word/
/dep/
...dep_int
/prompt/
/symbol/
/word/
/dep/
...dep_string
/prompt/
/symbol/
/word/
/dep/
...dep_tristate
/prompt/
/symbol/
/dep/
...与询问语句类似,依赖语句也是定义新的配置变量。不同的是,配置变量/symbol/的取值将依赖于配置变量列表/dep/…。这就意味着,被定义的配置变量所对应功能的取舍取决于依赖列表所对应功能的选择。5.选择语句choice
/prompt/
/word/
/word/choice语句首先给出一串选择列表,供用户选择其中一种。比如,LinuxforARM支持多种基于ARM核的CPU,Linux使用choice语句提供一个CPU列表,供用户选择:choice
‘ARM
system
type’
\"Anakin
CONFIG_ARCH_ANAKIN
\……SA1100-based
CONFIG_ARCH_SA1100
6.if语句if
[
/expr/
]
then/statement/
...
fi
if
[
/expr/
]
then
/statement/
...
else
/statement/
...
fi
if语句对配置变量进行判断,并做出不同的处理。判断条件/expr/可以是单个配置变量或字符串,也可以是带操作符的表达式。操作符有:=,!=,-o,-a等。7.菜单块(menublock)语句mainmenu_option
mentcomment
'…endmenu8.Source语句source
/word//word/是文件名,source的作用是调入新的文件。Vi编辑器的使用1.工作模式Vi是“Visualinterface”的简称,可以执行输出、删除、查找、替换、块操作等众多文本操作,而且用户可以根据自己的需要对其进行定制。Vi有三种基本工作模式:命令行模式、文本输入模式和末行模式。
命令行模式任何时候,不管用户处于何种模式,只要按一下键,即可使Vi进入命令行模式;我们在shell环境(提示符为$)下输入启动命令“vi”,进入编辑器时,也是处于该模式下。在该模式下,用户可以输入各种合法的Vi命令,用于管理自己的文档。此时从键盘上输入的任何字符都被当做编辑命令来解释,若输入的字符是合法的Vi命令,则Vi在接受用户命令之后完成相应的动作。但需注意的是,所输入的命令并不在屏幕上显示出来。若输入的字符不是Vi的合法命令,Vi会响铃报警。
文本输入模式在命令模式下输入插入命令i、附加命令a、打开命令o、修改命令c、取代命令r或替换命令s都可以进入文本输入模式。在该模式下,用户输入的任何字符都被Vi当做文件内容保存起来,并将其显示在屏幕上。在文本输入过程中,若想回到命令模式下,按Esc键即可。
末行模式在命令模式下,用户按“:”键即可进入末行模式下,此时Vi会在显示窗口的最后一行,显示一个“:”作为末行模式的提示符,等待用户输入命令。多数文件管理命令都是在此模式下执行的。末行命令执行完后,Vi自动回到命令模式。2.进入在提示符“$”后键入Vi和想要编辑的文件名,便可进入Vi。例如:$
vi
example.c如果只键入Vi,而不带文件名,也可以进入Vi,之后在光标处键入文件内容。进入Vi后,首先进入的就是命令模式,进入Vi时,用户不仅可以指定一个待编辑的文件名,而且还有许多附加操作。如果希望在进入Vi之后,光标处于文件中特定的某行上,可在Vi后加上选项+n,其中n为指定的行数。例如:$
vi
+5
example1.c3.退出在命令模式中,连按两次大写字母Z,若当前编辑的文件曾被修改过,则Vi保存该文件后退出,返回到shell;若当前编辑的文件没被修改过,则Vi直接退出,返回到shell。在末行模式下,输入命令::wVi保存当前编辑文件,但并不退出,而是继续等待用户输入命令。在使用w命令时,可以再给编辑文件起一个新的文件名。:w
newfile此时Vi将把当前文件的内容保存到指定的newfile中,而原有文件保持不变。在末行模式下,输入命令::q系统退出Vi返回到shell。若在用此命令退出Vi时,编辑文件没有被保存,则Vi在显示窗口的最末行显示如下信息:No
write
since
last
change
(use
!
to
overrides)提示用户该文件被修改后没有保存,然后Vi并不退出,继续等待用户命令。若用户就是不想保存被修改后的文件而要强行退出Vi时,可使用命令::q!Linux下C语言编程1.文件读写当需要打开一个文件进行读写操作的时候,可以使用系统调用函数open。文件操作完成后,调用close函数关闭文件。int
open(const
char
*pathname,int
flags);
int
open(const
char
*pathname,int
flags,mode_t
mode);
int
close(int
fd);
open函数有两个形式,在此只介绍第一种方式。其中pathname是我们要打开的文件名,包含路径名称,默认是在当前路径下。Flags取值可以是下面的一个值或者是几个值的组合。
O_RDONLY:以只读的方式打开文件;
O_WRONLY:以只写的方式打开文件;
O_RDWR:以读写的方式打开文件;
O_APPEND:以追加的方式打开文件;
O_CREAT:创建一个文件;要采用open的第二种形式;
O_EXEC:使用该选项时,如果使用了O_CREAT而且文件已经存在,就会发生一个错误;
O_NOBLOCK:以非阻塞的方式打开一个文件;
O_TRUNC:如果文件已经存在,则删除文件的内容。如果打开文件成功,open会返回一个文件描述符。以后对文件的所有操作就可以对这个文件描述符进行操作了。当操作完成以后,要关闭文件时,只要调用close就可以了,其中fd是要关闭的文件描述符。文件打开后,就可以调用函数read和write进行文件的读写。ssize_t
read(int
fd,
void
*buffer,size_t
count);
ssize_t
write(int
fd,
const
void
*buffer,size_t
count);
fd是要进行读写操作的文件描述符,buffer是要写入文件内容或读出文件内容的内存地址,count是要读写的字节数。对于普通的文件,read从指定的文件(fd)中读取count个字节到buffer缓冲区中,同时返回count。如果read读到了文件的结尾或者被一个信号所中断,返回值会小于count。如果是由信号中断引起返回,而且没有读出数据,read会返回1,且设置errno为EINTR。当程序读到了文件结尾的时候,read会返回0。write从buffer中写count字节到文件fd中,成功时返回实际所写的字节数。#define
BUFFER_SIZE
1024
int
main(int
argc,char
**argv)
{
int
from_fd,to_fd;int
bytes_read,bytes_write;
char
buffer[BUFFER_SIZE];
char
*ptr;
if(argc!=3)
{
fprintf(stderr,"Usage:%s
fromfile
tofile
a",argv[0]);
exit(1);
}
if((from_fd=open(argv[1],O_RDONLY))==-1)
{
fprintf(stderr,"Open
%s
Error:%s
",argv[1],strerror(errno));
exit(1);
}
if((to_fd=open(argv[2],O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1)
{
fprintf(stderr,"Open
%s
Error:%s
",argv[2],strerror(errno));
exit(1);
}
while(bytes_read=read(from_fd,buffer,BUFFER_SIZE))
{
if((bytes_read==-1)&&(errno!=EINTR))
break;
else
if(bytes_read>0)
{
ptr=buffer;
while(bytes_write=write(to_fd,ptr,bytes_read))
{
if((bytes_write==-1)&&(errno!=EINTR))break;
else
if(bytes_write==bytes_read)
break;
else
if(bytes_write>0)
{
ptr+=bytes_write;
bytes_read-=bytes_write;
}
}
if(bytes_write==-1)break;
}
}
close(from_fd);
close(to_fd);
exit(0);
}2.文件属性文件具有各种各样的属性,除了上面提到的文件权限以外,文件还有创建时间、大小等属性。有时候要判断文件是否可以进行某种操作,此时可以使用access函数。int
access(const
char
*pathname,int
mode);
其中,pathname是文件名称,mode是所要判断的属性。可以取以下值或者是其组合:
R_OK文件可以读;
W_OK文件可以写;
X_OK文件可以执行;
F_OK文件存在。当测试成功时,函数返回0;如果有一个条件不符时,返回1。如果要获得文件的其他属性,可以使用函数stat或者
stat(const
char
*file_name,struct
stat
*buf);
int
fstat(int
filedes,struct
stat
*buf);
struct
stat
{
dev_t
st_dev;
/*
设备
*/
ino_t
st_ino;
/*
节点
*/
mode_t
st_mode;
/*
模式
*/
nlink_t
st_nlink;
/*
硬连接
*/
uid_t
st_uid;
/*
用户ID
*/
gid_t
st_gid;
/*
组ID
*/
dev_t
st_rdev;
/*
设备类型
*/
off_t
st_off;
/*
文件字节数
*/
unsigned
long
st_blksize;
/*
块大小
*/
unsigned
long
st_blocks;
/*
块数
*/
time_t
st_atime;
/*
最后一次访问时间
*/
time_t
st_mtime;
/*
最后一次修改时间
*/
time_t
st_ctime;
/*
最后一次改变时间(指属性)*/
};stat用来判断没有打开的文件,而fstat用来判断打开的文件。使用最多的属性一般是st_mode,通过该属性可以判断给定的文件是一个普通文件,还是一个目录或者连接。可以使用下面几个宏来判断:
S_ISLNK(st_mode),是否是一个连接;
S_ISREG,是否是一个常规文件;
S_ISDIR,是否是一个目录;
S_ISCHR,是否是一个字符设备;
S_ISBLK,是否是一个块设备;
S_ISFIFO,是否是一个FIFO文件;
S_ISSOCK,是否是一个SOCKET文件。3.目录文件的操作在编写程序的时候,有时候想得到当前的工作路径。C库函数提供了getcwd来解决这个问题:char
*getcwd(char
*buffer,size_t
size);
如果提供一个size大小的buffer,getcwd会把当前的路径拷贝到buffer中,如果buffer太小,函数会返回1和一个错误号。Linux提供了大量的目录操作函数,下面列出了几个比较简单和常用的函数:int
mkdir(const
char
*path,mode_t
mode);DIR
*opendir(const
char
*path);
struct
dirent
*readdir(DIR
*dir);
void
rewinddir(DIR
*dir);
int
closedir(DIR
*dir);
mkdir就是创建一个目录,opendir打开一个目录为以后读做准备,readdir读一个打开的目录,rewinddir是用来重读目录的,closedir是关闭一个目录。下面的程序有一个输入参数,如果这个参数是一个文件名,输出这个文件的大小和最后修改的时间;如果是一个目录,输出这个目录下所有文件的大小和修改时间。static
int
get_file_size_time(const
char
*filename)
{
struct
stat
statbuf;
if(stat(filename,&statbuf)==-1)
{
printf("Get
stat
on
%s
Error:%s
",
filename,strerror(errno));
return(-1);
}
if(S_ISDIR(statbuf.st_mode))return(1);
if(S_ISREG(statbuf.st_mode))
printf("%ssize:%ldbytesmodifiedat%s",filename,statbuf.st_size,ctime(&statbuf.st_mtime));return(0);
}
int
main(int
argc,char
**argv)
{
DIR
*dirp;
struct
dirent
*direntp;
int
stats;
if(argc!=2)
{
printf("Usage:%s
filename
a",argv[0]);
exit(1);
}
if(((stats=get_file_size_time(argv[1]))==0)||(stats==-1))exit(1);
if((dirp=opendir(argv[1]))==NULL)
{
printf("Open
Directory
%s
Error:%s
",
argv[1],strerror(errno));
exit(1);
}
while((direntp=readdir(dirp))!=NULL)if(get_file_size_time(direntp-closedir(dirp);
exit(1);
}
5.3引导程序的移植在掌握GNU开发工具后,就需要对系统的引导程序进行移植了。嵌入式的引导程序是与处理器体系结构紧密联系的,是嵌入式系统开发的难点之一,同时也是系统运行的一个基本前提条件,没有这段和硬件紧密相连的代码,多么精悍的内核也发挥不了作用。
嵌入式Linux的引导机理分析一个嵌入式软件系统通常可以分为引导程序BootLoader、操作系统内核、文件系统和用户应用程序4个层次。1.BootLoader工作原理BootLoader是系统加电启动运行的第一段软件代码。通过PC的体系结构我们可以知道,PC机中的引导加载程序由BIOS(其本质就是一段固件程序)和位于硬盘MBR中的引导程序一起组成。BIOS在完成硬件检测和资源分配后,将硬盘MBR中的引导程序读到系统的RAM中,然后将控制权交给引导程序。引导程序的主要运行任务是将内核映像从硬盘上读到RAM中,然后跳转到内核的入口点去运行,也即开始启动操作系统。由于在嵌入式系统中,通常并没有像BIOS那样的固件程序(有的嵌入式系统也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。对于一个嵌入式系统来说,可能有的包括操作系统,有的小型系统也可以只包括应用程序,但是在这之前都需要BootLoader为它准备一个正确的环境。通常,BootLoader是依赖于硬件而实现的,特别是在嵌入式领域,为嵌入式系统建立一个通用的BootLoader是很困难的。但是可以归纳一些通用的概念出来,以便简化特定BootLoader的设计与实现。简单地说,BootLoader是在操作系统内核或用户应用程序运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,为最终调用操作系统内核或用户应用程序准备好正确的环境。大多数BootLoader都包含两种不同的操作模式:“启动加载”模式和“下载”模式,这两种模式的区别仅对于开发人员才有意义。从最终用户的角度看,BootLoader的作用是用来加载操作系统,并不存在所谓的启动加载模式与下载工作模式的区别。启动加载(BootLoading)模式:这种模式也称为“自主”(Autonomous)模式。也即BootLoader从目标机上的某个固态存储设备上将操作系统加载到RAM中运行,整个过程并没有用户的介入。这种模式是BootLoader的正常工作模式,在嵌入式产品发布的时候,BootLoader显然必须工作在这种模式下。下载(Downloading)模式:在这种模式下,目标机上的BootLoader将通过串口连接或网络连接等通信手段从主机下载文件,比如,下载内核映像和根文件系统映像等。从主机下载的文件通常首先被BootLoader保存到目标机的RAM中,然后再被BootLoader写到目标机上的FLASH类固态存储设备中。BootLoader的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用BootLoader的这种工作模式。工作于这种模式下的BootLoader通常都会向它的终端用户提供一个简单的命令行接口。最常见的情况是,目标机上的BootLoader通过串口与主机之间进行文件传输,传输协议通常是xmodem/ymodem/zmodem协议中的一种。但是,由于串口传输的速度是有限的,因此通过以太网连接并借助TFTP协议来下载文件是个更好的选择。但是,在通过以太网连接和TFTP协议来下载文件时,因为主机方必须有一个软件用来的提供TFTP服务,所以操作相对复杂。2.BootLoader的启动过程从操作系统的角度看,BootLoader的总目标就是正确地调用内核来执行。BootLoader的启动过程大多数分为阶段1和阶段2。阶段1主要包含依赖于CPU体系结构的硬件初始化代码,而且通常都是用汇编语言来实现的,以达到短小精悍的目的。这个阶段通常包括以下步骤:(1)硬件设备初始化。这是BootLoader开始就执行的操作,其目的是为阶段2的执行,以及随后内核的执行准备好基本的硬件环境。(2)为加载BootLoader的阶段2准备RAM空间。为了获得更快的执行速度,通常把阶段2加载到RAM空间来执行。(3)拷贝BootLoader阶段2的代码到RAM空间中。(4)设置好堆栈。(5)跳转到阶段2的C程序入口点。BootLoader的阶段2通常用C语言来实现,这样可以实现更复杂的功能,而且代码会具有更好的可读性和可移植性。通常包括以下步骤:(1)初始化本阶段要使用到的硬件设备。(2)检测系统内存映射。(3)将内核映像和根文件系统映像从Flash上读到RAM空间中。(4)为内核设置启动参数。(5)调用内核。3.BootLoader代码分析下面对引导程序2410INIT.S进行分析,以加深对BootLoader的理解。在第一阶段完成依赖于体系结构硬件初始化的代码,包括禁止看门狗、禁止中断、初始化各控制寄存器拷贝自身到RAM等。
…… IMPORTMain AREAInit,CODE,READONLY ENTRY …… b ResetHandler ……ResetHandler ldr r0,=WTCON ldr r1,=0x0 str r1,[r0] ldr r0,=INTMSK ldr r1,=0xffffffff str r1,[r0] ldr r0,=INTSUBMSK ldr r1,=0x7ff str r1,[r0] …… ldr r0,=LOCKTIME ldr r1,=0xffffff str r1,[r0] …… ldr r0,=SMRDATA ldr r1,=BWSCON add r2,r0,#52 0 ldr r3,[r0],#4 str r3,[r1],#4 cmp r2,r0 bne %B0
bl InitStacks ldr r0,=HandleIRQ ldr r1,=IsrIRQ str r1,[r0] ;拷贝自身代码到RAM ldr r0,=|Image$$RO$$Limit| ; ldr r1,=|Image$$RW$$Base| ; ldr r3,=|Image$$ZI$$Base| cmp r0,r1 beq %F21 cmp r1,r3 ldrcc r2,[r0],#4 strcc r2,[r1],#4 bcc %B12 ldr r1,=|Image$$ZI$$Limit| mov r2,#03 cmp r3,r1 strcc r2,[r3],#4 bcc %B3 …… bl Main b . …… END第二阶段通常用C语言实现,包括内存管理单元初始化、时钟设置、端口设置和串口初始化等。voidIsr_Init(void){rINTMOD=0x0;//工作在IRQ模式
rINTMSK=BIT_ALLMSK;//屏蔽中断
rINTSUBMSK=BIT_SUB_ALLMSK;//屏蔽子中断}voidMain(void){MMU_Init(); //MMU初始化
ChangeClockDivider(1,1);//设置时钟除法器->1:2:4ChangeMPllValue(0xa1,0x3,0x1);//时钟值FCLK=202.8MHzPort_Init(); Isr_Init();Uart_Init(0,115200);Uart_Select(0);while(1){ …… Uart_Printf("\n\nSMDK2410Board(MCUS3C2410)ExampleProgramVer1.0(20020521)FCLK=%dHz\n\n",FCLK); ……}}与一般的C语言程序一样,第二阶段程序从Main()函数开始。因为所采用的S3C2410X处理器内含存储器管理单元MMU,所以首先需要进行存储器管理单元的初始化。紧接着设置系统工作主时钟,若使用USB设备则USB时钟也需要做相应初始化。其次对端口工作状态进行设定。然后设定处理器工作模式,以及中断控制。最后初始化串口,与宿主机建立联系,以利于显示调试信息。启动成功后,可以执行主程序。VIVI简介VIVI是韩国Mizi公司开发的BootLoader,可用于ARM9处理器的引导。VIVI利用串行通信为用户提供接口。为连接VIVI,首先利用串口电缆连接宿主机和目标板,然后在主机上运行串口通信程序,并在目标板上正确设置VIVI以支持串口。正确连接后,就可以由串口通信程序显示提示信息,提示信息的最后一行如下所示:PressReturntostarttheLINUXnow,anyotherkeyforvivi.VIVI也有前面说过的两种工作模式,启动模式可以在一段时间后自行启动Linux内核,这是VIVI的默认方式。出现上述信息后,如果按除回车键外的任意键,即可进入下载模式,出现“vivi>”提示符。在下载模式下,VIVI为用户提供了一个命令行接口,通过该接口可以使用VIVI提供的一些命令。1.load命令将二进制文件载入到Flash或者RAM,命令格式:load<media_type>[<partname>|<addr><size>]<x|y|z>其中命令行参数<media_type>描述装载位置,有flash和ram两种选项;参数[<partname>]或[<addr><size>]描述装载的地址,如果有提前定义的mtd分区信息,可以只输入分区名称,否则需要指定地址和大小;参数<x|y|z>确定文件的传输协议,常采用的选项“x”用来指定采用xmodem协议
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2018春人教版七年级生物下册第四单元第4章同步教学设计:4.4.2血流的管道-血管
- 2025年强电安全题库及答案解析
- 人教版八年级历史上册第5课《八国联军侵华战争》教学设计3份 (3份打包)
- 医疗废物减量化策略研究-洞察与解读
- 银行从业考试中级教程及答案解析
- 工地电工安全员考试题库及答案解析
- 安全B证考试题库与A证相同及答案解析
- 2024-2025学年河南省郑州市高二下学期期末考试语文试题(解析版)
- 2024-2025学年河北省部分名校高二下学期期末考试语文试题(解析版)
- 基于NS2平台的移动Agent仿真方法深度剖析与创新拓展
- 部编版四年级语文上册第六单元教学分析及全部备课教案(共6份教案)
- DB23T 3719-2024 火灾调查物证提取与管理
- 全国学科专业目录及名称代码表
- 项目安全管理考核表
- 食品生产企业安全检查表含日管控、周排查及月调度检查记录表
- 单病种住院诊疗费用控制表
- 2023年工业地产市场分析报告
- 老旧小区改造监理实施细则
- 礼品售后服务承诺书
- 音乐治疗和心理剧演出你内心的音乐
- 掩耳盗铃儿童故事绘本PPT
评论
0/150
提交评论