之前本来想直接看Padavan的源码,后面发现好多Linux系统函数非常模糊,还是需要系统的学习一下。现在就从零开始学习,在这记录一下学习的轨迹。之前为了编译Padavan的系统,安装了一个Ubuntu 16.4的系统,刚好这里面比较干净,虽然有一些知识之前都了解过了,我还是按照开发指南介绍的一步步来做吧。在这里推荐一个Linux笔记,里面有常用的Linux命令,很方便。

一、 Linux 基本操作

  1. 创建编程的文件夹,W22代表第22周,现计划每周更新一篇。
    mkdir -r C_Program//W22
  2. 配置vim的一些参数。无意中发现在github里面搜vim config会有很多项目可以把vim配置的很花哨,我暂时还是按照简单的来吧,这里推荐一个VIM配置, 我的配置如下:

    " 在家目录下创建 .vimrc 文件,这样这个配置不会影响其他用户
    
    vi ~/.vimrc
    " 配置如下
    set tabstop=4 
    
    " 设置softtabstop有一个好处是可以用Backspace键来一次
    " 删除4个空格. softtabstop的值为负数,会使用shiftwidth
    " 的值,两者保持一致,方便统一缩进.
    set softtabstop=-1 
    
    " 自动缩进时,缩进长度为4
    set shiftwidth=4 
    
    set noexpandtab 
    set nu  
    set autoindent 
  3. 来完成第一个hello world

    cheney@ubuntu:~/C_Program/W22$ gcc hellow_world.c -o hellow_world
    cheney@ubuntu:~/C_Program/W22$ ls
    hellow_world  hellow_world.c
    cheney@ubuntu:~/C_Program/W22$ ./hellow_world 
    Hello World!    

二、 Makefile

Makefile 记录一些重点吧,这个之前也也些许了解,推荐一个视频 [linux从零到精通] gcc和Makefile

  • 命令列表中的每条命令必须以 TAB 键开始,不能使用空格!
  • 执行过程

    1. make 命令会在当前目录下查找以 Makefile(makefile 其实也可以)命名的文件。
    2. 当找到 Makefile 文件以后就会按照 Makefile 中定义的规则去编译生成最终的目标文件。
    3. 当发现目标文件不存在,或者目标所依赖的文件比目标文件新(也就是最后修改时间比目标文件晚)的话就会执行后面的命令来更新目标。
  • 自动化变量
    自动化变量就是这种变量会把模式中所定义的一系列的文件自动的挨个取出,直至所有的符合模式的文件都取完,自动化变量只应该出现在规则的命令中
  • 伪目标
    一般的目标名都是要生成的文件,而伪目标不代表真正的目标名,在执行 make 命令的时候通过指定这个伪目标来执行其所在规则的定义的命令。
  • 函数
    Makefile 中的函数是已经定义好的,我们直接使用,不支持我们自定义函数
# Makefile基本规格
main: main.o input.o calcu.o
    gcc main.o input.o calcu.o -o main 
main.o: main.c
    gcc -c main.c
input.o: input.c
    gcc -c input.c
calcu.o: calcu.c
    gcc -c calcu.c

clean:
    rm -rf main.o input.o calcu.o
    rm -rf main

(1) Makefile变量

  • =(替换,以最后面后面出现的为最终值)
  • +=(追加,可以在原变量上增加)
  • :=(恒等于,常量)
  • ?= (若之前没有赋值,则赋值)
  • $(var) (引用变量)
  • $(var:a=b) 或 ${var:a=b},或用静态模式 $(var:%.a=%.b) 变量替换,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串

# Makefile 变量

TAR = main
obj = main.o input.o calcu.o
CC := gcc

objc := $(obj:.o=.c) # objc =>main.c input.c calcu.c

$(TAR): $(OBJ)
    $(CC) $(OBJ) -o $(TAR)
main.o: main.c
    $(CC) -c main.c -o main.o
input.o: input.c
    $(CC) -c input.c -o input.o
calcu.o: calcu.c
    $(CC) -c calcu.c -o input.o

clean:
    rm -rf $(tar) $(obj)

(2)Makefile 模式规则

  • % 任意的 (%.c %.o 任意的.c 或者.o)
  • * 所有的 (.c .o 所有的.c 或者 .o)
# Makefile 模式规则

TAR = main
obj = main.o input.o calcu.o
CC := gcc

$(TAR): $(OBJ)
    $(CC) $(OBJ) -o $(TAR)
%.o: %.c
    $(CC) -c %.c -o %.o  #这句不能真执行,通配符“%”只能用在规则中,只有在规则中它才会展开,如果在变量定义和函数使用时,通配符不会自动展开
clean:
    rm -rf $(tar) $(obj)

(3)Makefile 自动化变量

  • $@ 规则中的目标集合,在模式规则中,如果有多个目标的话,“$@”表示匹配模
    式中定义的目标集合。
  • $^ 所有依赖文件的集合,使用空格分开,如果在依赖文件中有多个重复的文件,
    “$^”会去除重复的依赖文件,值保留一份。
  • $< 依赖文件集合中的第一个文件,如果依赖文件是以模式(即“%” )定义的,那么
    “$<”就是符合模式的一系列的文件集合。
# Makefile 自动化变量

TAR = main
obj = main.o input.o calcu.o
CC := gcc

$(TAR): $(OBJ)
    $(CC) $(OBJ) -o $(TAR)
%.o: %.c
    $(CC) -c $^ -o $@ 
clean:
    rm -rf $(tar) $(obj)

(4)Makefile 伪目标

在上面的Makefile中,若执行的目录下有一个clean的文件,那他的依赖为空,而假如clean又没有任何变化的话,下面的语句就不会执行。为了让其执行,可以使用伪目标。

# Makefile 自动化变量

TAR = main
obj = main.o input.o calcu.o
CC := gcc

$(TAR): $(OBJ)
    $(CC) $(OBJ) -o $(TAR)

.PHONY : clean                #使用伪目标,让clean后面的语句一定执行

%.o: %.c
    $(CC) -c $^ -o $@ 
    
clean:
    rm -rf $(tar) $(obj)

(4)Makefile 条件判断

  • ifeq / ifneq 判断相等/不等

    • ifeq(var1, var2)
    • ifeq 'var1', 'var2'
    • ifeq "var1", "var2"
    • ifeq "var1", 'var2'
  • ifdef/ifndef 判断是否定义

    • ifdef var

(5)Makefile 函数

函数使用方法 $(fun var1,var2 ...) 或者 ${fun var1,var2 ...}

    # $(subst <from>, <to>, text)  实现字符替换
       eg: $(subst zzk,zhangsan,my name is lisi)  #--> my name is zhangsan
       
    # $(patsubst <pattern>,<replacement>,<text>) 模式字符串替换。查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,
    # 如果匹配的话,则以<replacement>替换。 如果<replacement>中也包含“%”, 那么, <replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。
       eg: $(patsubst %.c,%.o,a.c b.c c.c)  #--> a.o b.o c.o
       
    # $(dir<names...>) 获取names中的目录部分
       eg: $(dir</src/a.c>)  #--> /src
       
    # $(notdir<names...>) 获取names中的文件名
       eg: $(notdir</src/a.c>)  #--> a.c
       
    # $(foreach<var>, <list>, <test>)  把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式
       eg: 
       names := a b c d
       file := $(foreach n, names, $(n).o)  #--> a.o b.o c.o d.o
       
    # $(wildcard pattern) 相当于通配符,变量定义和函数使用中使用
       eg: $(wildcard *.c)  #--> 获取当前目录下所有.c文件
       

三、 交叉工具链

(1) 交叉编译器介绍

交叉编译器是一种GCC编译器,运行在X86架构的PC上,这个 GCC 编译器是编译 ARM 架构代码的,也就是编译出来的可执行文件是在 ARM 芯片上运行的。
这里推荐的交叉工具链是Linaro公司出品的免费工具链,下载地址为 这里 。学习指南中选择的是 arm-linux-gnueabihf,我简单查了一下不同交叉编译器的 区别

  • ABI是二进制应用程序接口(Application Binary Interface (ABI) for the ARM Architecture),应用二进制接口描述了应用程序(或者其他类型)和操作系统之间或其他应用程序的低级接口。
  • EABI是 嵌入式ABI。

接下来两个gcc-arm-linux-gnueabi和 gcc-arm-linux-gnueabihf 不同只是在于gcc的选项-mfloat-abi的默认值不同。gcc的选项-mfloat-abi有三种值soft,softfp,hard(其中后两者都要求arm里有fpu浮点运算单元,soft与后两者是兼容的,但softfp和hard两种模式互不兼容)

  • soft 不用fpu进行浮点计算,即使有fpu浮点运算单元也不用,而是使用软件模式。
  • softfp armel架构(对应的编译器为gcc-arm-linux-gnueabi)采用的默认值,用fpu计算,但是传参数用普通寄存器传,这样中断的时候,只需要保存普通寄存器,中断负荷小,但是参数需要转换成浮点的再计算。
  • hard armhf架构(对应的编译器gcc-arm-linux-gnueabihf)采用的默认值,用fpu计算,传参数也用fpu中的浮点寄存器传,省去了转换, 性能最好,但是中断负荷高。

(2) 交叉编译器安装

  1. 将交叉编译器下载到电脑,下载地址为 这里 ,我装的虚拟机是64位版本,下载gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz,若安装的32位版本,则安装gcc-linaro-4.9.4-2017.01-i686_arm-linux-gnueabihf.tar.xz。
  2. 按照我之前的文章,将下载的交叉编译器共享到Ubuntu系统。
  3. 执行命令创建文件夹 sudo mkdir /usr/local/arm
  4. 将共享到Ubuntu的交叉编译器拷贝到刚创建的文件夹 sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm
  5. 执行命令解压缩 sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
  6. 接下来修改环境变量,执行sudo vi /etc/profile ,在打开的文件最后一行添加 export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin ,完成后保存退出即可。
  7. 然后是安装两个相关的库 sudo apt install lsb-core lib32stdc++6
  8. 接下来输入 arm-linux-gnueabihf-gcc -v ,可以查看到其版本号等信息则表示安装成功,如果提示没有该命令,可能是因为更新的环境变量后需要重启才能配置成功,重启一下即可。

四、 Cortex-A7 MPCore 架构

这一部分先简单了解一下将要使用的内核。

(1) Cortex-A7 MPCore主要特性

主要特性如下:

  1. SIMDv2 扩展整形和浮点向量操作。
  2. 提供了与 ARM VFPv4 体系结构兼容的高性能的单双精度浮点指令,支持全功能的IEEE754。
  3. 支持大物理扩展(LPAE),最高可以访问 40 位存储地址,也就是最高可以支持 1TB 的内存。
  4. 支持硬件虚拟化。
  5. 支持 Generic Interrupt Controller(GIC)V2.0。
  6. 支持 NEON,可以加速多媒体和信号处理算法。

(2) Cortex-A7 MPCore运行模式

除了 User(USR)用户模式以外,其它 8 种运行模式都是特权模式。这几个运行模式可以通过软件进行任意切换,也可以通过中断或者异常来进行切换。大多数的程序都运行在用户模式,用户模式下是不能访问系统所有资源的,要想访问这些受限的资源就必须进行模式切换。但是用户模式是不能直接进行切换的,用户模式下需要借助异常来完成模式切换,当要切换模式的时候,应用程序可以产生异常,在异常的处理过程中完成处理器模式切换。当中断或者异常发生以后,处理器就会进入到相应的异常模式种,每一种模式都有一组寄存器供异常处理程序使用,这样的目的是为了保证在进入异常模式以后,用户模式下的寄存器不会被破坏。

模式描述
User(USR)用户模式,非特权模式,大部分程序运行的时候就处于此模式。
FIQ快速中断模式,进入 FIQ 中断异常
IRQ一般中断模式。
Supervisor(SVC)超级管理员模式,特权模式,供操作系统使用。
Monitor(MON)监视模式?这个模式用于安全扩展模式。
Abort(ABT)数据访问终止模式,用于虚拟存储以及存储保护。
Hyp(HYP)超级监视模式?用于虚拟化扩展。
Undef(UND)未定义指令终止模式。
System(SYS)系统模式,用于运行特权级的操作系统任务

(3) Cortex-A 寄存器组

Cortex-A7 有 9 种运行模式,每一种运行模式都有一组与之对应的寄存器组。每一种模式可见的寄存器包括 15 个通用寄存器(R0~R14)、一两个程序状态寄存器(CPSR 当前程序状态寄存器 和 SPSR 备份程序状态寄存器)和一个程序计数器 PC。在这些寄存器中,有些是所有模式所共用的同一个物理寄存器,有一些是各模式自己所独立拥有的。

各个模式所拥有的寄存器如下表,表中浅色字体的是与 User 模式所共有的寄存器,蓝绿色背景的是各个模式所独有的寄存器。可以看出,在所有的模式中,低寄存器组(R0~R7)是共享同一组物理寄存器的,只是一些高寄存器组在不同的模式有自己独有的寄存器。
九种模式所对应的寄存器

通用寄存器
通用寄存器分三类,
①、 未备份寄存器,即 R0~R7。
②、 备份寄存器,即 R8~R14。
③、 程序计数器 PC,即 R15。

Ⅰ、未备份寄存器
未备份寄存器指的是 R0~R7 这 8 个寄存器,因为在所有的处理器模式下这 8 个寄存器都是同一个物理寄存器,在不同的模式下,这 8 个寄存器中的数据就会被破坏。所以这 8 个寄存器并没有被用作特殊用途。

Ⅱ、备份寄存器

  • 备份寄存器中的 R8~R12 这 5 个寄存器有两种物理寄存器。FIQ 模式下中断处理程序可以使用 R8fiq~R12fiq寄存器,因为 FIQ 模式下的 R8~R12 是独立的,因此中断处理程序可以不用执行保存和恢复中断现场的指令,从而加速中断的执行过程。
  • 备份寄存器 R13(堆栈指针) 一共有 8 个物理寄存器,只有用户模式(User)和系统模式(Sys)共用的,初始化时需要初始化每个模式对应的堆栈指针。
  • 备份寄存器 R14 (链接寄存器)一共有 7 个物理寄存器,只有用户模式(User)、系统模式(Sys)和超级监视模式(Hyp)所共有的。每种处理器模式使用 R14(LR)来存放当前子程序的返回地址;当异常发生以后,该异常模式对应的 R14 寄存器被设置成该异常模式将要返回的地址,R14 也可以当作普通寄存器使用。

Ⅲ、程序计数器PC
程序计数器 R15 也叫做 PC,R15 保存着当前执行的指令地址值加 8 个字节,这是因为 ARM的流水线机制导致的。ARM 处理器 3 级流水线:取指->译码->执行,这三级流水线循环执行,比如当前正在执行第一条指令的同时也对第二条指令进行译码,第三条指令也同时被取出存放在 R15(PC)中。对于 32 位的 ARM 处理器,每条指令是 4 个字节,以正在执行的指令为参考点,R15 (PC)值 = 当前执行的程序位置 + 8 个字节。

程序状态寄存器
所有的处理器模式都共用一个 CPSR 物理寄存器,因此 CPSR 可以在任何模式下被访问。除了 User 和 Sys 这两个模式以外,其他 7 个模式每个都配备了一个专用的物理状态寄存器,叫做 SPSR(备份程序状态寄存器),当特定的异常中断发生时, SPSR 寄存器用来保存当前程序状态寄存器(CPSR)的值,当异常退出以后可以用 SPSR 中保存的值来恢复 CPSR。

CPSR和SPSR的结构如图:
CPRS寄存器结构

  • N(bit31):当两个补码表示的有符号整数运算的时候, N=1 表示运算对的结果为负数,N=0 表示结果为正数。
  • Z(bit30): Z=1 表示运算结果为零, Z=0 表示运算结果不为零;对于 CMP 指令,Z=1 表示进行比较的两个数大小相等。
  • C(bit29):在加法指令中,当结果产生了进位,则 C=1,表示无符号数运算发生上溢,其它情况下 C=0。在减法指令中,当运算中发生借位,则 C=0,表示无符号数运算发生下溢,其它情况下 C=1。对于包含移位操作的非加/减法运算指令, C 中包含最后一次溢出的位的数值,对于其它非加/减运算指令, C 位的值通常不受影响。
  • V(bit28): 对于加/减法运算指令,当操作数和运算结果表示为二进制的补码表示的带符号数时, V=1 表示符号位溢出,通常其他位不影响 V 位。
  • Q(bit27): 仅 ARM v5TE_J 架构支持,表示饱和状态, Q=1 表示累积饱和, Q=0 表示累积不饱和。
  • IT[1:0](bit26:25):和 IT[7:2](bit15:bit10)一起组成 IT[7:0],作为 IF-THEN 指令执行状态。
  • J(bit24): 仅 ARM_v5TE-J 架构支持, J=1 表示处于 Jazelle 状态,此位通常和T(bit5)位一起表示当前所使用的指令集,如下表所示:

JT描述
00ARM
01Thumb
11ThumbEE
10Jazelle
  • GE[3:0](bit19:16): SIMD 指令有效,大于或等于。
  • IT[7:2](bit15:10): 参考 IT[1:0]。
  • E(bit9): 大小端控制位, E=1 表示大端模式, E=0 表示小端模式。
  • A(bit8): 禁止异步中断位, A=1 表示禁止异步中断。
  • I(bit7): I=1 禁止 IRQ, I=0 使能 IRQ。
  • F(bit6): F=1 禁止 FIQ, F=0 使能 FIQ。
  • T(bit5): 控制指令执行状态,表明本指令是 ARM 指令还是 Thumb 指令,通常和 J(bit24)一起表明指令类型,参考 J(bit24)位。
  • M[4:0]: 处理器模式控制位,含义如下表所示:
M[4:0]处理器模式
10000User 模式
10001FIQ 模式
10010IRQ 模式
10011Supervisor(SVC) 模式
10110Monitor(MON)模式
10111Abort(ABT)模式
11010Hyp(HYP)模式
11011Undef(UND)模式
11111System(SYS)模式

cheney
7 声望0 粉丝

引用和评论

0 条评论