嵌入式Linux名词解析.docx
《嵌入式Linux名词解析.docx》由会员分享,可在线阅读,更多相关《嵌入式Linux名词解析.docx(28页珍藏版)》请在冰豆网上搜索。
嵌入式Linux名词解析
目录
Shell2
脚本Script2
目标程序3
交叉编译5
进程线程和任务7
工具链7
文件系统8
什么是ioctl8
根文件系统10
分散加载12
文件系统13
Busybox13
驱动程序和内核的关系14
文件描述符14
structfile17
structinode19
Shell
文字操作系统与外部最主要的接口就叫做shell。
shell是操作系统最外面的一层。
shell管理你与操作系统之间的交互:
等待你输入,向操作系统解释你的输入,并且处理各种各样的操作系统的输出结果。
GNU/Linux
由于GNU/Linux这个词太长,下面如果没有特别指明,“Linux”就是指“GNU/Linux”。
Bash
Bash(BourneAgainShell)是目前大多数Linux(RedHat,Slackware等)系统默认使用
的Shell,它由BrianFox和ChetRamey共同完成,内部命令一共有40个,它是BourneShell
的扩展,与BourneShell完全向后兼容,并且在BourneShell的基础上增加了很多特性。
Bash
是GNU计划的一部分,用来替代BourneShell。
Linux下使用Shell非常简单,打开终端就可以到Shell的提示符了,登录系统之后,系统将执行个称为Shell的程序,正是Shell进程提供了命令提示符。
作为Linux默认的Bash,对于普通用户“$”作为Shell提示符,而对于根用户(root)用“#”作提示符。
如图3.2。
脚本Script
动态程序一般有两种实现方式,一是二进制方式,一是脚本方式。
二进制方式是先将我们编写的程序进行编译,变成机器可识别的指令代码(如.exe文件),然后再执行。
这种编译好的程序我们只能执行、使用,却看不到他的程序内容。
脚本简单地说就是一条条的文字命令,这些文字命令是我们可以看到的(如可以用记事本打开查看、编辑),脚本程序在执行时,是由系统的一个解释器,将其一条条的翻译成机器可识别的指令,并按程序顺序执行。
因为脚本在执行时多了一道翻译的过程,所以它比二进制程序执行效率要稍低一些。
简单地说,脚本就是指通过记事本程序或其它文本编辑器(如WindowsScriptEditor,EditPlus等)创建,并保存为特定扩展名(如.reg,.vbs,.js,.inf等)的文件,对于注册表脚本文件就是利用特定的格式编辑的.reg文件;对于VBScript脚本编程语言来说,脚本文件扩展名就是.vbs;对于JScript脚本编程语言来说,脚本文件扩展名就是.js;另外,.wsf格式是Microsoft定义的一种脚本文件格式,即WindowScriptFile.
脚本同我们平时使用的VB、C语言的区别主要是:
1、脚本语法比较简单,比较容易掌握;
2、脚本与应用程序密切相关,所以包括相对应用程序自身的功能;
3、脚本一般不具备通用性,所能处理的问题范围有限。
目标程序
我们开始都是字符文件,就是源文件。
第一步先把源文件翻译成一种中间代码,这就是目标文件,然后再把目标文件翻译为机器代码,这就是可置执行的EXE文件了。
所以说,目标就是一个中间程序。
Makefile
这里先介绍makefile最基本的知识。
假设现有三个c程序main.c,mytool1.c,mytool2.c,其中主程序在main.c中。
gcc-cmain.c
gcc-cmytool1.c
gcc-cmytool2.c
gcc-omainmain.omytool1.omytool2.o
这样的话我们可以产生main程序,而且也不是很麻烦.但是我们考虑一下,如果有一天我们修改了其中的一个文件(比如说mytool1.c),那么我们难道还要重新输入上面的命令吗?
也许你会说,这个很容易解决啊,我写一个SHELL脚本,让它帮我去完成不就可以了.是的对于这个程序来说,是可以起到作用的.但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译吗?
为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make.
什么是make
linux系统中,提供了一个自动生成和维护目标程序的工具---make。
make是一个单独工作的程序,根据程序模块的修改情况重新编译链接目标代码,以保证目标代码总是由最新的模块组成。
源程序编译链接的顺序和范围取决于模块之间的关联关系。
make从指定文件中读取说明模块之间关系的信息,然后根据这些信息维护和更新目标文件。
具体地说,make首先判断哪些文件过时了。
所谓过时,就是一个文件生成以后,用来生成该文件的源文件或者所依赖的模块被修改了,而他自己没有修改。
其判断依据是文件生成时间。
找到过时的文件,就根据指定文件中的有关信息进行更新操作。
make默认从GNUmakefilemakefile或者Makefile读取信息。
可以用-f指定文件。
我们只要执行以下make,就可以把上面的问题解决掉.在我们执行make之前,我们要先编写一个非常重要的文件.--Makefile(或makefile).对于上面的那个程序来说,可能的一个Makefile的文件是:
#这是上面那个程序的Makefile文件
main:
main.omytool1.omytool2.o
gcc-omainmain.omytool1.omytool2.o
main.o:
main.cmytool1.hmytool2.h
gcc-cmain.c
mytool1.o:
mytool1.cmytool1.h
gcc-cmytool1.c
mytool2.o:
mytool2.cmytool2.h
gcc-cmytool2.c
有了这个Makefile文件,不过我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件它连理都不想去理的.
如何编写makefile文件
在Makefile中以#开始的行都是注释行.Makefile中最重要的是描述文件的依赖关系的说明.一般的格式是:
target:
components
TABrule(TAB表示tab键)
第一行表示的是依赖关系.第二行是规则(命令行). 每一个命令行的开头都必须是一个tab键。
比如说我们上面的那个Makefile文件的第二行 main:
main.omytool1.omytool2.o
表示我们的目标(target)main的依赖对象(components)是main.omytool1.omytool2.o当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.就象我们的上面那个Makefile第三行所说的一样要执行gcc-omainmain.omytool1.omytool2.o
Makefile有几个非常有用的变量:
分别是$@、$*、$^、$<。
代表的意义分别是:
$@--目标文件
$*--目标文件去掉后缀的部分
$^--所有的依赖文件
$<--比目标文件更新的依赖文件
如果我们使用上面几个变量,那么我们可以简化我们的Makefile文件为:
#这是简化后的Makefile
main:
main.omytool1.omytool2.o
gcc-o$@$^
main.o:
main.cmytool1.hmytool2.h
gcc-c$*.c
mytool1.o:
mytool1.cmytool1.h
gcc-c$*.c
mytool2.o:
mytool2.cmytool2.h
gcc-c$*.c
交叉编译
在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码,我们就称这种编译器支持交叉编译。
这个编译过程就叫交叉编译。
简单地说,就是在一个平台上生成另一个平台上的可执行代码。
这里需要注意的是所谓平台,实际上包含两个概念:
体系结构(Architecture)、操作系统(OperatingSystem)。
同一个体系结构可以运行不同的操作系统;同样,同一个操作系统也可以在不同的体系结构上运行。
举例来说,我们常说的x86Linux平台实际上是Intelx86体系结构和Linuxforx86操作系统的统称;而x86WinNT平台实际上是Intelx86体系结构和WindowsNTforx86操作系统的简称。
交叉编译这个概念的出现和流行是和嵌入式系统的广泛发展同步的。
我们常用的计算机软件,都需要通过编译的方式,把使用高级计算机语言编写的代码(比如C代码)编译(compile)成计算机可以识别和执行的二进制代码。
比如,我们在Windows平台上,可使用VisualC++开发环境,编写程序并编译成可执行程序。
这种方式下,我们使用PC平台上的Windows工具开发针对Windows本身的可执行程序,这种编译过程称为nativecompilation,中文可理解为本机编译。
然而,在进行嵌入式系统的开发时,运行程序的目标平台通常具有有限的存储空间和运算能力,比如常见的ARM平台,其一般的静态存储空间大概是16到32MB,而CPU的主频大概在100MHz到500MHz之间。
这种情况下,在ARM平台上进行本机编译就不太可能了,这是因为一般的编译工具链(compilationtoolchain)需要很大的存储空间,并需要很强的CPU运算能力。
为了解决这个问题,交叉编译工具就应运而生了。
通过交叉编译工具,我们就可以在CPU能力很强、存储控件足够的主机平台上(比如PC上)编译出针对其他平台的可执行程序。
要进行交叉编译,我们需要在主机平台上安装对应的交叉编译工具链(crosscompilationtoolchain),然后用这个交叉编译工具链编译我们的源代码,最终生成可在目标平台上运行的代码。
常见的交叉编译例子如下:
1、在WindowsPC上,利用ADS(ARM开发环境),使用armcc编译器,则可编译出针对ARMCPU的可执行代码。
2、在LinuxPC上,利用arm-linux-gcc编译器,可编译出针对LinuxARM平台的可执行代码。
3、在WindowsPC上,利用cygwin环境,运行arm-elf-gcc编译器,可编译出针对ARMCPU的可执行代码。
交叉编译通俗地讲就是在一种平台上编译出能运行在体系结构不同的另一种平台上,比如在我们地PC平台(X86 CPU)上编译出能运行在sparc CPU平台上的程序,编译得到的程序在X86 CPU平台上是不能运行的,必须放到sparc CPU平台上才能运行。
当然两个平台用的都是linux。
这种方法在异平台移植和嵌入式开发时用得非常普遍。
相对与交叉编译,我们平常做的编译就叫本地编译,也就是在当前平台编译,编译得到的程序也是在本地执行。
用来编译这种程序的编译器就叫交叉编译器,相对来说,用来做本地编译的就叫本地编译器.
动态加载和静态加载
说简单,并用打比喻的方式来解释.
好比有两个人a和m
a代表apache,m代表module。
要想让a使用m的东西
一个方法是把m的东西都放到a那里去,a使用的时候就是现成的了,就是所谓的静态编译。
还有一个方法,就是告诉a,m的住址,当a要使用m的东西的时候,a去找m,然后使用。
不过,这种方法要注意的一个问题就是:
m必须要有实际的住址,
否则a会找不到m而产生错误的,我此文开始提到的apachectlstartssl产生
的错误就是这个原因,应该再编译好ssl才可以的.这种方法也就是apache的动态(DSO)编译了。
静态:
在使用./configure 编译的时候,如果不指定某个模块为动态,即没有使用:
enable-mods-shared=module或者enable-module=shared这个2个中的一个,那么所有的默认模块为静态。
那么何谓静态?
其实就是编译的时候所有的模块自己编译进httpd这个文件中(我们启动可以使用这个执行文件,如:
./httpd&),启动的时候这些模块就已经加载进来了,也就是可以使用了,通常为:
来配置。
所以大家看到的配置都是 ,很显然,module.c这个东西已经存在httpd这个文件中了。
动态:
就是编译的时候,使用enable-module=shared或者enable-modules-shared=module来动态编译。
那么什么是动态?
静态是直接编译进httpd中,那么动态显然就不编译进去了,也就是你启动的时候根本不会加载这个模块,而是给你一个module.so文件,你一定要使用loadmodule这个语法来加载,这个模块才有效。
那么区别就出来了:
静态的模块通常是来配置,动态使用loadmoule来加载,然后再配置。
首先看看编译apache的选项含义
对于apache1.3.x
./configure–prefix=/usr/local/apache/
–enable-module=so/
–enable-module=most /
–enable-shared=max/
–enable-module=rewrite
对于apache2.0.x
./configure–prefix=/usr/local/apache2/
–enable-modules=most/
–enable-mods-shared=all/
–enable-so/
–enable-rewrite
对于apache2.2.0
./configure–prefix=/usr/local/apache2/
–enable-mods-shared=all/
–enable-so/
–enable-rewrite
举例一:
编译一个apache2.2.8版本
#./configure–prefix=/usr/local/apache–enable-so–enable-mods-shared=most–enable-rewrite–enable-forward
说明:
so模块用来提供DSO支持的apache核心模块.
–enable-so选项:
让Apache可以支持DSO模式,注意,这里采用的是Apache2.0的语法。
如果你的Apache是1.3版本,应改为–enable-module=so
–enable-mods-shared=most选项:
告诉编译器将所有标准模块都动态编译为DSO模块。
如果用的是Apache1.3,改为–enable-shared=max就可以。
-enable-rewrite选项:
支持地址重写功能,使用1.3版本的朋友请将它改为–enable-module=rewrite
–enable-module=most
用most可以将一些不常用的,不在缺省常用模块中的模块编译进来.
–enable-mods-shared=all意思是动态加载所有模块,如果去掉-shared话,是静态加载所有模块.
进程线程和任务
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
进程和线程的区别在于:
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程,使得多线程程序的并发性高。
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。
但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。
但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。
这就是进程和线程的重要区别。
简单的说,你每启动一个程序,就启动了一个进程。
在Windows3.x下,进程是最小运行单位。
在Windows95/NT下,每个进程还可以启动几个线程,比如每下载一个文件可以单独开一个线程。
在Windows95/NT下,线程是最小单位。
WINDOWS的多任务特性使得线程之间独立运行,但是它们彼此共享虚拟空间,也就是共用变量,线程有可能会同时操作一片内存。
所谓进程,本身不能执行,它只是一个资源的集合体,拥有地址空间,模块,内存,...
线程是真正的执行单元,一个进程如果没有线程,那么就没有存在的意义,因为不可能执行。
对于操作系统而言其调度单元是线程。
一个进程至少包括一个线程,通常将该线程称为主线程。
一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。
工具链
机器上。
两个机子有不同的机器指令。
工具链:
指编译、汇编、链接等一整套工具。
嵌入式设备由于不具备一定的处理器能力和存储空间,程序开发一般用PC来完成,然后将可执行文件下载到嵌入式系统中运行。
这是目前嵌入式程序开发的不二选择——Host/target模式。
但这引发了一个问题:
由于Host和Target的处理器体系结构不同,我们不能直接用PC上既有的程序开发工具,必须使用跨平台开发工具,即在Host上生成能在Target上运行格式的目标文件。
与在PC上进行程序开发类似,嵌入式系统开发也需要编译器、链接器、解释程序等。
本文讨论GNU跨平台开发工具链的建立,包括:
ld,gas,ar,gcc,glibc.
自己建立交叉编译环境是一件很头疼的事(处理版本的依赖性,漫长的编译过程...),如果你不想经历这样的痛苦,可以选择网上编译好了的工具链进行安装.如果你用的是Debian/Ubuntu的发行版,推荐使用Emdebian.如果使用uClinux,也可安装arm-elf-tools.
内核映像
内核映像就相当于内核保存成磁盘文件形式,系统启动时它会被启动程序原封不动(或仅进行解压)搬到内存中去运行。
文件系统
一种看待文件系统的方式是把它看作一个协议。
网络协议(比如IP)规定了互联网上传输的数据流的意义,同样,文件系统会给出特定存储媒体上数据的意义
挂装
在Linux中将一个文件系统与一个存储设备关联起来的过程称为挂装(mount)。
使用mount命令将一个文件系统附着到当前文件系统层次结构中(根)。
在执行挂装时,要提供文件系统类型、文件系统和一个挂装点。
伪文件
proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。
它以文件系统的方式为访问系统内核数据的操作提供接口。
用户和应用程序可以通过proc得到系统的信息,并可以改变内核的某些参数。
由于系统的信息,如进程,是动态改变的,所以用户或应用程序读取proc文件时,proc文件系统是动态从系统内核读出所需信息并提交的。
什么是ioctl
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。
所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。
它的调用个数如下:
intioctl(intfd,indcmd,…);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。
二、ioctl的必要性
如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。
例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。
但是如果这样做的话,会导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。
所以,我们就使用ioctl来实现控制的功能。
要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
ioctl如何实现
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。
怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。
关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。
命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。
这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。
所以在Linux核心中是这样定义一个命令码的:
____________________________________
|设备类型|序列号|方向|数据尺寸|
|----------|--------|------|--------|
|8bit | 8bit|2bit|8~14bit|
|----------|--------|------|--------|
这样一来,一个命令就变成了一个整数形式的命令码。
但是命令码非常的不直观,所以LinuxKernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给除了这些宏完整的定义。
这里我只多说一个地方,那就是"幻数"。
幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。
就是这样,再没有更复杂的了。
四、cmd参数如何得出
这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。
要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。
Cmd参数的组织还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最难的是对中断的理解。
五、小结
ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。
根文件系统
根文件系统首先是一种文件系统,但是相对于普通的文件