(一):linux系统根目录结构
Unix-like系统中的”文件系统”的概念包含两个意思,第一个是”根文件系统”,第二个是”存储类文件系统”.后者的概念基本等同于windows操作系统,而前者与windows差别较大,他并不是用于存储实际文件的.根文件系统简称rootfs,他的特点为:
1:"文件"不仅是指硬盘上的数据,他还包括任何设备资源.在Unix-like系统中,所有的硬件设备都被看作是文件,"文件"是内核范畴的概念,磁盘,U盘,内存,网络,甚至cpu都被内核抽象成文件.为了区别一般意义上的文件按,内核级文件被称为"设备文件"或"设备虚拟文件".这些设备文件在rootfs中可以被看到.
2:并不是所有的目录或文件都对应磁盘上的存储空间.比如,sys,proc,dev这三个目录,他们对应的不是存储空间,而是设备文件,这三个目录中的内容由内核及相应的驱动程序维护.
3:存储类我呢见系统不能和rootfs并列存在,而只能挂载到rootfs的一个子目录上
4:Unix-like中的"存储类文件系统"内部等同于windows的文件系统,包括文件系统类型.windows系统常见的文件系统类型包括FAT16,FAT32,NTFS,linux中也支持这些文件系统类习惯,但更常见的却是ext2,ext3,ext4,yaffs等.
Unix-like系统中,操作系统只能有一个根文件系统,但可以包含多个”存储类文件系统”.执行挂载,卸载”存储类文件系统”的操作可以在terminal下使用mount和umount命令.
(二):linux启动过程
从计算机系统的角度来看,启动过程一般分为三个步骤
首先是开机,开机就是开系统开始供电,此时,硬件电路会产生一个确定的复位时序,保证CPU是最后一个被复位的器件.CPU要最后被复位的原因就是,如果CPU第一个被复位,则当CPU复位后开始运行时,其他硬件内部的寄存器状态可能还没有准备好,比如磁盘或者内存,那么就可能出现外围硬件初始化错误.
当正确完成复位之后,CPU开始执行第一条指令,该指令所在的内存地址是固定的,这由CPU的制造者指定.不同的CPU可能由不同的地址获取指令,但是这个地址必须是固定的,这个固定地址所保存的程序往往被称为”引导程序(bootloader)”,其作用是装载真正的用户程序.
至于如何装载,是一个策略问题,不同的CPU会提供不同的装载方式,比如有的通过普通的并口存储器,有的则通过SD卡,还有的是通过RS232接口.无论是硬件上使用何种接口装载,装载过程必须提供以下信息,具体包括:
1:从哪里读取用户程序
2:用户程序的长度是多少
3:装载完成用户程序之后,应该跳转到哪里,即用户程序的执行入口在哪里
第二步是执行内核程序,这里所说的内核程序在上一步中指的就是”用户程序”.因为从CPU的角度来看,除bootloader之外的所有程序都是用户程序,只是从软件的角度来看,用户程序被分为”内核程序”和”应用程序”,而本步执行的是”内核程序”.
内核程序初始化的时候执行的操作包括,初始化各种硬件,包括内存,网络接口,显示器,输入设备,然后建立各种内部数据结构,这些数据结构将用于多线程调度及内存的管理等.当内核初始化完毕之后,就开始运行具体的应用程序了.在一般情况下,习惯于将第一个应用程序称为”Home程序”.
第三步就是运行Home程序,比如windows的系统桌面,就是一个典型的Home程序.之所以称其为Home程序,是因为通过该程序可以方便的启动其他应用程序.
下面看一下android的启动过程.
多数基于ARM的实际硬件系统,会从并口NADN Flash芯片中的0x00000000地址处装载程序.对于一些小型嵌入式系统而言,该地址处的程序就是最终要执行的用户程序,而对于android而言,该地址出的程序还不是android程序,而是一个叫做uboot或者fastboot的程序,其作用是初始化硬件设备,比如网口,SDRAM,RS232等,并提供一些调试功能.当uboot被装载后便开始运行,他一般会先检测用户是否按下了某些特别的按键,这些特别按键是uboot在编译的时候预先约定好的,用于进入调试模式.如果用户没有按这些特别的按钮,则uboot就会从NAND Flash中装载linux内核,装载的地址是在编译uboot的时候预先约定好的.
linux内核被装载之后,就开始进行内核初始化的过程,过程如下:
(三):Make脚本备忘
Linux系统中包含一个Make脚本的解释器,他可以读取Make脚本的内容,并执行之,Make脚本多用于自动编译过程,但并不是说Make脚本只能拥有自动编译.
Make脚本的基本语法如下:
目标(target): 条件(prerequest)
(Tab按键) 命令
在该语法中,目标可以是任意一个字符串名称,也可以是具体文件的名称.条件可以是其他目标的名称,也可以是具体文件的名称.执行Make脚本的时候,Make解释器会检查目标和文件中包含的文件时间戳是否相同,如果不同的话,解释器就会执行Tab键后面的’命令’,命令可以是任何可执行程序.
自动编译的基本原理就是将目标文件作为”目标”,将源文件作为”条件”,因此,当源文件修改之后,目标文件的时间戳会早于源文件,于是Make解释其就会自动执行相关指定”命令”.此时可以将执行编译的命令作为这里的”命令”,从而达到自动编译的目的.
1:一个简单的Makefile文件
源码如下:
#FileName Makefile
#this file is used for showing how to use makefile
$(info start working)
hello: hello.c
echo "nothing"
hello.bin: hello.c
@echo "now make hello.bin"
gcc hello.c -o hello.bin
.PHONY: he
he: hello.c
@echo "now make he"
gcc hello.c -o hello.bin
这段代码有以下特点:
1:#符号是注释符,可用在代码中任何地方
2:$是函数调用符号,info是一个函数名称,作用是输出一段信息.类似的信息输出函数还包括warning,error两个函数,不过error按书执行后会终止执行并退出
3:目标定以前不能加任何空格,而命令前面必须以Tab键开始
4:.PHONY关键字用于声明一个目标,被.PHONY声明的目标将总是执行其指定的命令,而如果不声明的话,则仅当目标后面的条件变动之后才执行
5:命令前面的@符号的作用是,不显示被执行的命令.因为默认情况下,Make解释器在执行命令的时候会打印出执行的命令
6:对于hello.bin目标,该目标本身就是一个文件,其依赖的文件是hello.c文件.因此当hello.c文件被修改后,将会执行gcc命令重新对该c文件编译,并输出hello.bin文件.
要执行以上脚本,可以运行以下命令:
$make -f Makefile hello
在该命令中,-f用于指定要执行的脚本文件名称.如果不指定文件名称,则解释器会自动从当前目录下寻找名称为Makefile的脚本文件.
2:变量的定义和赋值
Makefile中的变量不需要单独定义,可直接赋值,常见的赋值方式有:
Make解释器执行脚本的过程可分为两个步骤:
1:装载Makefile及Makefile中include的其他Makefile.装载完所有相关的脚本文件之后,系统内部会创建一个图,该图描述了各个Makefile的依赖关系.
2:根据用户指定的target找出该target的全部依赖关系,并判断依赖条件中的文件的时间戳.如果时间戳较新,就开始执行taget中所对应的命令.
而对于变量定义和赋值,解释器会根据不同的赋值方式选择是立即赋值还是延迟赋值,赋值过程也成为展开过程.所谓立即赋值,是指在读取脚本的时候就给变量进行赋值;而延迟赋值,是指读取的时候暂不赋值,只有在执行脚本的时候用到了该变量,才对其进行赋值.不同的赋值方式对应的展开时机:
3:条件控制语句
Make脚本的条件控制可分为两类,一类是在解释器解析脚本文件的时候处理,另一类是在执行脚本的时候处理.
下面看第一类条件控制语句的语法模型,如下代码:
if-condition
text if the condition is true
endif
//或者
if-condition
text if the condition is true
else
text if the condition is false
endif
其中,condition只能进行两种判断,一种是判断表达式是否相等,另一种是判断表达式是否被定义,如下:
1: ifdef var:判断变量是否被定义过.
2:ifndef var:与ifdef相反,判断变量是否还没有被定义
3:ifeq test:判断表达式test是否相等,表达式可写成”a” “b”或(a,b)
4:ifneq test,与ifeq相反
4:宏定义
Make 脚本中的函数,按被调用的方式分为三类:
第一类是内置函数,即Make解释器内部定义好的函数,在任何脚本文件中可直接调用,调用的格式为:
$(fname,param...)
fname是函数的名称,param是参数,多个参数用逗号隔开
第二类是用户定义的,带参数的函数,使用define关键字进行定义,调用的格式为:
$(call fname.param...)
call是调用的关键字, fname代表函数的名称,param是函数参数,多个参数使用逗号分隔.
第三类也是用户定义的,但不带参数,该类函数也称之为宏,其调用格式为:
$(fname)
既不使用call关键字,也不包含参数.
用户函数的定义方式如下:
define fname
//各种具体的命令
endef
在自定义函数中,命令钱不需要加Tab键,因为当宏被展开的时候,Make解释器会自动在没一行命令前加Tab键.函数内部使用
举个例子:
define showFirstName
@echo $(1)
endef
.PHONY: name
name:
$(call showFirstName,yuandan,ferr)
这段代码中定义了函数showFirstName,其作用是将传入的第一个参数返回给调用者.使用make name命令执行以上脚本,执行结果为:
$ make name
yuandan
常用的内置函数:
(1):字符串操作函数
内置的常用的字符串函数如表:
(2):文件名称操作
用于处理文件路径,名称的常用函数:
(3):过程控制函数
在C语言中if/else是语法本身定义的关键字,而脚本的过程控制确实由函数完成的.
过程控制函数表:
5:内置符号和变量
Make解释器内部定义了一些特别的符号和一些特别名称的变量,在编写用户脚本时,可以直接使用这些符号,变量,而不需要定义.
(1):内置符号
(2):内置变量
因为Make脚本主要是用于对C/C++源码的编译,因此,其内部也定义了一些专用于C/C++编译的变量.
6:模板目标
假设当前有一个C源码的项目,其中包含3个C源文件,名称分别为f1.c,f2.c,main.c,其中f1和f2中定义了两个函数,main.c中会使用这两个函数,然后可以编写一个脚本文件,脚本源码如下:
.PHONY: test
test: f1.o f2.o main.o
gcc -o main.bin f1.o f2.o main.o
f1.o: f1.c
gcc f1.c f1.o
f2.o: f2.c
gcc f2.c f2.o
main.o: main.c
gcc main.c -c main.o
当源码数量较多,则脚本文件的定义就会变得繁琐起来.这样我们使用一种”模板”来定义目标,使用模板目标后,以上的脚本可以简化为:
OBJ = f1.o f2.o f3.o
.PHONY: test
test: $(OBJ)
gcc $(OBJ) -o main.bin
%.o: %.c
gcc -c -o $@ $<
7:目标特定的变量赋值
在脚本文件中,当给某个变量赋值后,则之后无论Make哪个目标,该变量的值都是相同的,如以下代码所示:
CFLAGS = -c
.PHONY: tar1
tar1:
gcc $(CFLAGS) main.c
tar2 : CFLAGS=
tar2:
gcc $(CFLAGS) main.c
CFLAGS被赋值为-c,其值在整个脚本文件范围内都是有效的.比如当Make tar1的时候,就会执行gcc -c main.c.但是在tar2目标中,我们希望能够执行gcc main.c,因此可以在tar2目标中对CFLAGS变量重新赋值,该赋值仅在tar2目标的命令中有效,这就是所谓的”目标特定”变量赋值.因为该赋值仅针对该目标.
语法上需要注意,对tar2目标的赋值不能直接写成以下形式:
tar2 : CFALGS =
gcc $(CFLAGS) main.c
而是必须分开写,即把目标变量赋值和目标规则分开写.