嵌入式linux学习笔记(2)

2021/10/14 7:16:48

本文主要是介绍嵌入式linux学习笔记(2),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

笔记目录

  • 学习目标
  • 学习内容
    • 一、VI 编辑器的设置
      • 1、设置 TAB 键为 4 字节
      • 2、VIM 编辑器显示行号
      • 3.VI/VIM 编辑器使用空格代替了 TAB 键
    • 二、存储
      • 1、ROM
      • 2、RAM
      • 3、FLASH
    • 三、Makefile语法
      • 重要提醒
      • 1、Makefile作用
      • 2、Makefile 规则格式
      • 3、gcc命令
      • 4、Makefile 变量
    • 四、U-Boot
      • 1、U-Boot 简介
      • 2、U-Boot编译
      • 3、U-Boot 一些命令
      • 4、uboot启动linux测试
        • 从emmc启动
        • 从网络启动
          • 在ubuntu上搭建tftp服务器
          • 配置网络
      • 5、U-Boot 顶层makefile部分
      • 6、U-Boot 图形化配置
      • 7、uboot移植
    • 五、linux内核
      • 1、linux内核编译
      • 2、重要的文件夹
    • 六、根文件系统
      • 1、根文件系统介绍
      • 2、根文件系统的目录
      • 3、BusyBox
        • 编译 busybox
          • busybox编译
          • 向根文件系统添加 lib 库
    • 七、Linux 驱动开发
      • 1.字符设备驱动
    • 八、设备树
  • 学习时间
  • 学习产出

学习目标

学习linux开发


学习内容

一、VI 编辑器的设置

vi 打开文件/etc/vim/vimrc

1、设置 TAB 键为 4 字节

VI 编辑器默认 TAB 键为 8 空格,为了使代码更好看我们改成 4 空格。
在此文件最后面输入如下代码:set ts=4

2、VIM 编辑器显示行号

在此文件最后面输入如下代码:set nu

3.VI/VIM 编辑器使用空格代替了 TAB 键

在此文件最后面输入如下代码:set noexpandtab



二、存储

1、ROM

ROM是只读内存,其特性是一旦储存资料就无法再将之改变或删除,存储的资料不会因为电源关闭而消失。

2、RAM

RAM是随机存储,掉电不会保存数据。
  SRAM(静态随机访问存储器)不需要刷新电路即能保存它内部存储的数据
  DRAM (动态随机访问存储器)只能将数据保持很短的时间。为了保持数据,DRAM使用电容存储,所以 必须隔一段时间刷新(refresh)一次,如果存储单元没有被刷新,存储的信息就会丢失。
  SDRAM(同步动态随机访问存储器)同步是指 Memory工作需要同步时钟,内部的命令的发送与数据的传输都以它为基准。传统的DRAM在两个读周期之间需要等待一段时间,用于充电操作。而SDRAM一个模组有两个bank,在对一个bank充电时,可以操作另一个bank,实现流水线。SDRAM的发展已经经历了五代:分别是SDR SDRAM、 DDR SDRAM、 DDR2 SDRAM、 DDR3 SDRAM、 DDR4 SDRAM。

3、FLASH

FLASH 存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM 的优势)。

  NOR Flash和NAND Flash区别
    NOR的读速度比NAND稍快一些。
    NAND的写入速度比NOR快很多。
    NAND的4ms擦除速度远比NOR的5s快。
    大多数写入操作需要先进行擦除操作。
    NAND的擦除单元更小,相应的擦除电路更少。


EMMC=NAND闪存+闪存控制芯片+标准接口封装(对厂家而言简化了电路设计,降低了成本。)
DDR属于SDRAM
NANO FLASH属于flash

使用emmc的好处是,除了得到大容量的空间(这一点,只用NAND FLASH多堆叠也可以做到),还有就是emmc可以管理NAND (坏块处理,ECC,FFS)等。



三、Makefile语法

重要提醒

Makefile 里面是由一系列的规则组成的。
Makefile在编写时不能使用空格只能用TAB键,否则会报错。
Makefile:12: *** 遗漏分隔符 (null)。 停止。

1、Makefile作用

make 的执行过程,make 工具就是在 Makefile 中一层一层的查找依赖关系,并执行
相应的命令。编译出最终的可执行文件。
Makefile 的好处就是“自动化编译”,一旦写好了 Makefile文件,以后只需要一个 make 命令即可完成整个工程的编译,极大的提高了开发效率。

2、Makefile 规则格式

 目标… : 依赖文件集合…
  命令 1
  命令 2
  …

3、gcc命令

gcc [选项] [文件名字]
 主要选项如下:
  -c:只编译不链接为可执行文件,编译器将输入的.c 文件编译为.o 的目标文件。
  -o:<输出文件名> 用来指定编译结束以后的输出文件名,如果使用这个选项的话 GCC 默
认编译出来的可执行文件名字为 a.out。
  -g:添加调试信息,如果要使用调试工具(如 GDB)的话就必须加入此选项,此选项指示编
译的时候生成调试所需的符号信息。
  -O:对程序进行优化编译,如果使用此选项的话整个源代码在编译、链接的的时候都会进
行优化,这样产生的可执行文件执行效率就高。
  -O2:比-O 更幅度更大的优化,生成的可执行效率更高,但是整个编译过程会很慢。

简单的例程:

main: main.o input.o calcu.o      #需要main.o input.o等文件生成main
	gcc -o main                   #编译的最终目标是生成一个可执行文件main
main.o: main.c                    #需要的main.o文件由main.c文件生成
	gcc -c main.c                 #编译main.c生成main.o
input.o: input.c
	gcc -c input.c
calcu.o: calcu.c
	gcc -c calcu.c

clean:                           #执行make clean清理文件
	rm *.o
	rm main

  在第一次编译的时候由于 main 还不存在,因此第一条规则会执行,第一条规则依赖于文件 main.o、 input.o 和 calcu.o这个三个.o 文件,这三个.o 文件目前还都没有,因此必须先更新这三个文件。make 会查找以这三个.o 文件为目标的规则并执行。以 main.o 为例,发现更新 main.o 的是第二条规则,因此会执行第二条规则,第二条规则里面的命令为“gcc –c main.c”,这行命令很熟悉了吧,就是不链接编译 main.c,生成 main.o,其它两个.o 文件同理。最后一个规则目标是 clean,它没有依赖文件,因此会默认为依赖文件都是最新的,所以其对应的命令不会执行,当我们想要执行 clean 的话可以直接使用命令“make clean”,执行以后就会删除当前目录下所有的.o 文件以及 main。

4、Makefile 变量

Makefile 中变量的引用方法是"$(变量名)"
注释"#"

#Makefile 变量的使用
objects = main.o input.o calcu.o
	main: $(objects)
gcc -o main $(objects)
赋值符“?=”
变量追加“+=”


四、U-Boot

1、U-Boot 简介

在这里插入图片描述
Linux 系统要启动就必须需要一个 bootloader 程序,也就说芯片上电以后先运行一段bootloader 程序。这段 bootloader 程序会先初始化 DDR 等外设,然后将 Linux 内核从 flash(NAND,NOR FLASH,SD,MMC 等)拷贝到 DDR 中,最后启动 Linux 内核。

2、U-Boot编译

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- (加空格)
mx6ull_14x14_ddr512_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12

ARCH=arm 设置目标为 arm 架构,CROSS_COMPILE 指定所使用的交叉编译器。
第一条命令相当于“make distclean”,目的是清除工程,一般在第一次编译的时候最好清理一下工程。
第二条指令相当于“make mx6ull_14x14_ddr512_emmc_defconfig”,用于配置 uboot,配置文件为mx6ull_14x14_ddr512_emmc_defconfig。(14x14代表芯片的封装大小)
最后一条指令相当于 “make -j12”也就是使用 12 核来编译 uboot。

在顶层的makefile输入

ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
可以make后面不用加
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

3、U-Boot 一些命令

uboot 命令中的数字都是十六进制的!不是十进制的!
进入 uboot 的命令行模式

命令含义备注
help/?帮助help/?命令名
bdinfo查看板子信息DRAM 的起始地址和大小、启动参数保存起始地址、波特率、sp(堆栈指针)起始地址等信息
printenv输出环境变量信息
version查看 uboot 的版本号
setenv设置环境变量字符串中有空格需要使用单引号‘’将其括起来
saveenv保存修改的环境变量删除一个环境变量只要给这个环境变量赋空值
md显示内存值md[.b, .w, .l] address [# of objects]命令中的[.b .w .l]对应 byte、word 和 long,也就是分别以 1 个字节、2 个字节、4 个字节来显示内存值。address 就是要查看的内存起始地址,[# of objects]表示要查看的数据长度,这个数据长度单位不是字节,而是跟你所选择的显示格式有关。(md.b 80000000 14)
nm修改指定地址的内存值nm [.b, .w, .l] address(nm.l 80000000)
mm修改指定地址内存值的使用 mm 修改内存值的时候地址会自增,而使用命令 nm 的话地址不会自增
mw用于使用一个指定的数据填充一段内存mw [.b, .w, .l] address value [count]mw 命令同样可以以.b、.w 和.l 来指定操作格式, address 表示要填充的内存起始地址, value为要填充的数据, count 是填充的长度。
cp数据拷贝命令cp [.b, .w, .l] source target count cp 命令同样可以以.b、.w 和.l 来指定操作格式,source 为源地址,target 为目的地址,count为拷贝的长度。
cmp比较两段内存的数据是否相等cmp [.b, .w, .l] addr1 addr2 count 其中cmp 命令同样可以以.b、.w 和.l 来指定操作格式,addr1 为第一段内存首地址,addr2 为第二段内存首地址, count 为要比较的长度。
ipaddr开发板 ip 地址使用 dhcp 命令来从路由器获取 IP 地址
ethaddr开发板的 MAC 地址一定要设置
gatewayip网关地址
netmask子网掩码
serverip服务器 IP 地址,也就是 Ubuntu 主机 IP 地址用于调试代码
ping测试开发板的网络能否使用
dhcp用于从路由器获取 IP 地址
nfs计算机之间通过网络来分享资源nfs [loadAddress] [[hostIPaddr:]bootfilename]其中loadAddress 是要保存的 DRAM 地址,[[hostIPaddr:]bootfilename]是要下载的文件地址。分析

4、uboot启动linux测试

从emmc启动

emmc三个分区,第一个分区存放uboot,第二个分区格式化成FAT文件系统在里面存放.dtb和zimage文件,第三个分区放根文件系统。

首先查看emmc里面是否有系统,linux镜像zimage和.dtb文件。先将当前设备切换到emmc

mmc dev 1     //切换到EMMC
fatls mmc 1:1    //查看EMMC分区1里面的文件
fatload mmc 1:1 80800000 zImage    //将zimage下载到DDR的0x80800000
fatload mmc 1:1 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb  //将dtb下载到DDR的0x83000000
bootz 80800000 - 83000000     //启动内核

如果内核启动成功,说明uboot支持emmc启动,验证成功。

从网络启动

tftp服务器
需要确定ubuntu的~/linux/tftpboot文件夹下有.dtb和zimage文件

在ubuntu上搭建tftp服务器

tftp 命令的作用和 nfs 命令一样,都是用于通过网络下载东西到 DRAM 中,只是 tftp 命令使用的 TFTP 协议,Ubuntu 主机作为 TFTP 服务器。因此需要在 Ubuntu 上搭建 TFTP 服务器,需要安装 tftp-hpa 和 tftpd-hpa,命令如下:

sudo apt-get install tftp-hpa tftpd-hpa
sudo apt-get install xinetd

和 NFS 一样,TFTP 也需要一个文件夹来存放文件,在用户目录下新建一个目录,命令如下:

mkdir /home/wyd/linux/tftpboot
chmod 777 /home/wyd/linux/tftpboot

最后配置 tftp,安装完成以后新建文件/etc/xinetd.d/tftp,如果没有/etc/xinetd.d 目录的话自行创建sudo vi /etc/xinetd.d/tftp,然后在里面输入如下内容:

service tftp
{
        socket_type=dgram
        protocol=udp
        wait=yes
        user=root
        server=/usr/sbin/in.tftpd
        server_args=-s /home/wyd/linux/tftpboot/
        disable=no
        per_source=11
        cps=100 2
        flags=IPv4

}

sudo vi /etc/default/tftpd-hpa,然后在里面输入如下内容:

# /etc/default/tftpd-hpa
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/home/wyd/linux/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="-1 -c -s"

最后输入如下命令, 重启 tftp 服务器:
sudo service tftpd-hpa restart
将 zImage 镜像等文件拷贝到 tftpboot 文件夹中,并且给予 zImage 相应的权限

cp zImage /home/wyd/linux/tftpboot/
cd /home/wyd/linux/tftpboot/
chmod 777 zImage
chmod 777 tftpboot
chmod 777 。。。。。

进入 uboot 的命令行模式

tftp 80800000 zImage //将 tftpboot 文件夹里面的 zImage 文件下载到开发板 DRAM 的 0X80800000 地址处
tftp 83000000 imx6ull-14x14-emmc-7-1024x600-c.dtb  //将 tftpboot 文件夹里面的 dtb 文件下载到开发板 DRAM 的 83000000 地址处
bootz 80800000 - 83000000     //启动内核

设置 bootargs 和 bootcmd 这两个环境变量,设置如下:

setenv bootargs 'console=ttymxc0,115200 root=/dev/mmcblk1p2 rootwait rw'
setenv bootcmd 'tftp 80800000 zImage; tftp 83000000 imx6ull-alientek-emmc.dtb; bootz 80800000 - 83000000'
saveenv

一开始是通过 tftp 下载 zImage 和 imx6ull-alientek-emmc.dtb 这两个文件

配置网络
setenv ipaddr 192.168.1.10     //开发板 IP 地址
setenv ethaddr 00:04:9f:04:d2:35  //开发板网卡 MAC 地址
setenv gatewayip 192.168.1.1   //开发板默认网关
setenv netmask 255.255.255.0//开发板子网掩码
setenv serverip 192.168.1.6 //服务器地址,也就是 Ubuntu 地址(如果不是静态地址可能需要长期更改)
saveenv //保存环境变量

ping测试

=> ping 192.168.1.6
 Using FEC1 device
 host 192.168.1.6 is alive

5、U-Boot 顶层makefile部分

#版本号
VERSION = 2016             #主版本号
PATCHLEVEL = 03          #修补版本号
SUBLEVEL =                      #次版本号
EXTRAVERSION =           #附加信息
NAME =                               #名字

#隐含规则
#隐含规则则是内建在make 中,为make 提供了重建某一类目标文件(.o 等)的通用方法,同时这些隐含规则所用到的变量也就是所谓的隐含变量。
#隐含规则的好处是在Makefile 中不需要明确给出重建某一个目标的命令,甚至可以不需要规则。make会为你自动搜寻匹配的隐含规则链。
#隐含规则的代价之一就是低效,系统必须搜索可能的隐含规则链。同时隐含规则也有可能应用了不是你想要的规则而引入很难debug的错误。
#变量SHELL与MAKEFLAGS一样,默认情况(没有用“unexport”声明)下在整个make的执行过程中被自动的传递给所有的子make。
#“+=”来给变量 MAKEFLAGS 追加了一些值,“-rR”表示禁止使用内置的隐含规则和变量定义,“--include-dir”指明搜索路径,”$(CURDIR)”表示当前目录
MAKEFLAGS += -rR --include-dir=$(CURDIR)                      #CURDIR是make的内嵌变量,自动设置为当前目录

#export将变量传递到子make过程,unexport禁止将变量传递到子make过程。
#在locale环境中,有一组变量,代表国际化环境中的不同设置,"C"是系统默认的locale:
#LC_ALL是一个宏,如果该值设置了,则该值会覆盖所有LC_*的设置值。注意,LANG的值不受该宏影响。
#LC_COLLATE定义该环境的排序和比较规则
#LC_NUMERIC非货币的数字显示格式
unexport LC_ALL
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC

# Avoid interference with shell env settings
#根据注释可以看到为了避免当前shell环境变量对编译的影响,去除grep的配置选项GREP_OPTIONS;
unexport GREP_OPTIONS


#输入make -j12 V=1 打印详细信息 
#判断V的代码是不是来自于命令行
#origin 用于告诉你变量是哪来的   
ifeq ("$(origin V)", "command line")
  KBUILD_VERBOSE = $(V)                        #如果是,KBUILD_VERBOSE=1
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0
endif

ifeq ($(KBUILD_VERBOSE),1)               #如果 KBUILD_VERBOSE 为 1
  quiet =                                                             #quiet和 Q 都为空
  Q =  
else
  quiet=quiet_
  Q = @                                                             #加 @ 命令不显示在终端
endif

# If the user is running make -s (silent mode), suppress echoing of
# commands
 #make -s 静默输出
 #判断当前正在使用的编译器版本号是否为 4.x
 #filter 是个过滤函数,函数格式如下:
#$(filter <pattern...>,<text>)
#filter 函数表示以 pattern 模式过滤 text 字符串中的单词,仅保留符合模式 pattern 的单词,
#可以有多个模式。函数返回值就是符合 pattern 的字符串。因此$(filter 4.%,$(MAKE_VERSION))
#的 含 义 就 是 在 字 符 串 “ MAKE_VERSION ” 中 找 出 符 合 “ 4.% ” 的 字 符 (% 为 通 配 符 ) ,
#MAKE_VERSION 是 make 工具的版本号,
#ubuntu16.04 里面默认自带的 make 工具版本号为 4.1,
ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4
#firstword 是获取首单词,函数格式如下:
#$(firstword <text>)
#firstword 函数用于取出 text 字符串中的第一个单词,函数的返回值就是获取到的单词。
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)
  quiet=silent_
endif
else					# make-3.8x
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)
  quiet=silent_
endif
endif
#导出 quiet Q KBUILD_VERBOSE
export quiet Q KBUILD_VERBOSE

ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
#make -O   指定输出结果到某一个文件夹
ifeq ("$(origin O)", "command line")
  KBUILD_OUTPUT := $(O)
endif

# That's our default target when none is given on the command line
PHONY := _all
_all:

# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;

ifneq ($(KBUILD_OUTPUT),)
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
								&& /bin/pwd)
$(if $(KBUILD_OUTPUT),, \
     $(error failed to create output directory "$(saved-output)"))

PHONY += $(MAKECMDGOALS) sub-make

$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
	@:

sub-make: FORCE
	$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
	-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))

# Leave processing to above invocation of make
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)

# We process the rest of the Makefile if this is the final invocation of make
ifeq ($(skip-makefile),)

# Do not print "Entering directory ...",
# but we want to display it when entering to the output directory
# so that IDEs/editors are able to understand relative filenames.
MAKEFLAGS += --no-print-directory

# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/sparse.txt" for more details, including
# where to get the "sparse" utility.
#“make C=1”使能代码检查,检查那些需要重新编译的文件。
#“make C=2”用于检查所有的源码文件
ifeq ("$(origin C)", "command line")
  KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
  KBUILD_CHECKSRC = 0
endif

# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
#编译模块
ifdef SUBDIRS
  KBUILD_EXTMOD ?= $(SUBDIRS)
endif

ifeq ("$(origin M)", "command line")
  KBUILD_EXTMOD := $(M)
endif

# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),)
_all: all
else
_all: modules
endif

ifeq ($(KBUILD_SRC),)
        # building in the source tree
        srctree := .
else
        ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
                # building in a subdirectory of the source tree
                srctree := ..
        else
                srctree := $(KBUILD_SRC)
        endif
endif
objtree		:= .
src		:= $(srctree)
obj		:= $(objtree)

VPATH		:= $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD))

#srctree 源码路径
export srctree objtree VPATH

#打印变量便于调试
mytest:
				echo srctree=$(srctree)
				echo objtree=$(objtree)
# Make sure CDPATH settings don't interfere
unexport CDPATH

#########################################################################
#获取主机架构和系统
HOSTARCH := $(shell uname -m | \
	sed -e s/i.86/x86/ \
	    -e s/sun4u/sparc64/ \
	    -e s/arm.*/arm/ \
	    -e s/sa110/arm/ \
	    -e s/ppc64/powerpc/ \
	    -e s/ppc/powerpc/ \
	    -e s/macppc/powerpc/\
	    -e s/sh.*/sh/)

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \
	    sed -e 's/\(cygwin\).*/cygwin/')

export	HOSTARCH HOSTOS

#########################################################################

# set default to nothing for native builds
#设置目标架构、交叉编译器
ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE ?=
endif

################
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-gnueabihf-
################

#设置配置文件
KCONFIG_CONFIG	?= .config
export KCONFIG_CONFIG

# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
	  else if [ -x /bin/bash ]; then echo /bin/bash; \
	  else echo sh; fi ; fi)

HOSTCC       = cc
HOSTCXX      = c++
HOSTCFLAGS   = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer
HOSTCXXFLAGS = -O2

ifeq ($(HOSTOS),cygwin)
HOSTCFLAGS	+= -ansi
endif


ifeq ($(HOSTOS),darwin)
# get major and minor product version (e.g. '10' and '6' for Snow Leopard)
DARWIN_MAJOR_VERSION	= $(shell sw_vers -productVersion | cut -f 1 -d '.')
DARWIN_MINOR_VERSION	= $(shell sw_vers -productVersion | cut -f 2 -d '.')

os_x_before	= $(shell if [ $(DARWIN_MAJOR_VERSION) -le $(1) -a \
	$(DARWIN_MINOR_VERSION) -le $(2) ] ; then echo "$(3)"; else echo "$(4)"; fi ;)

# Snow Leopards build environment has no longer restrictions as described above
HOSTCC       = $(call os_x_before, 10, 5, "cc", "gcc")
HOSTCFLAGS  += $(call os_x_before, 10, 4, "-traditional-cpp")
HOSTLDFLAGS += $(call os_x_before, 10, 5, "-multiply_defined suppress")

# since Lion (10.7) ASLR is on by default, but we use linker generated lists
# in some host tools which is a problem then ... so disable ASLR for these
# tools
HOSTLDFLAGS += $(call os_x_before, 10, 7, "", "-Xlinker -no_pie")
endif

# Decide whether to build built-in, modular, or both.
# Normally, just do built-in.

KBUILD_MODULES :=
KBUILD_BUILTIN := 1

# If we have only "make modules", don't compile built-in objects.
# When we're building modules with modversions, we need to consider
# the built-in objects during the descend as well, in order to
# make sure the checksums are up to date before we record them.

ifeq ($(MAKECMDGOALS),modules)
  KBUILD_BUILTIN := $(if $(CONFIG_MODVERSIONS),1)
endif

# If we have "make <whatever> modules", compile modules
# in addition to whatever we do anyway.
# Just "make" or "make all" shall build modules as well

# U-Boot does not need modules
#ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
#  KBUILD_MODULES := 1
#endif

#ifeq ($(MAKECMDGOALS),)
#  KBUILD_MODULES := 1
#endif

export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD

# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
#用文件 scripts/Kbuild.include 这个文件
include scripts/Kbuild.include

# Make variables (CC, etc...)
#交叉编译工具变量设置
AS		= $(CROSS_COMPILE)as
# Always use GNU ld
ifneq ($(shell $(CROSS_COMPILE)ld.bfd -v 2> /dev/null),)
LD		= $(CROSS_COMPILE)ld.bfd
else
LD		= $(CROSS_COMPILE)ld
endif
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm
LDR		= $(CROSS_COMPILE)ldr
STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump
AWK		= awk
PERL		= perl
PYTHON		= python
DTC		= dtc
CHECK		= sparse

CHECKFLAGS     := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ \
		  -Wbitwise -Wno-return-void -D__CHECK_ENDIAN__ $(CF)

KBUILD_CPPFLAGS := -D__KERNEL__ -D__UBOOT__

KBUILD_CFLAGS   := -Wall -Wstrict-prototypes \
		   -Wno-format-security \
		   -fno-builtin -ffreestanding
KBUILD_AFLAGS   := -D__ASSEMBLY__

# Read UBOOTRELEASE from include/config/uboot.release (if it exists)
UBOOTRELEASE = $(shell cat include/config/uboot.release 2> /dev/null)
UBOOTVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)

export VERSION PATCHLEVEL SUBLEVEL UBOOTRELEASE UBOOTVERSION
#架构(arm)cpu(arm7)板卡(mx6ullevk)供应商(freescale)soc(mx6)  cpu文件所处的目录  板子配置信息所处目录
#config.mk定义变量
export ARCH CPU BOARD VENDOR SOC CPUDIR BOARDDIR
export CONFIG_SHELL HOSTCC HOSTCFLAGS HOSTLDFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM LDR STRIP OBJCOPY OBJDUMP
export MAKE AWK PERL PYTHON
export HOSTCXX HOSTCXXFLAGS DTC CHECK CHECKFLAGS

export KBUILD_CPPFLAGS NOSTDINC_FLAGS UBOOTINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS KBUILD_AFLAGS

# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions

# Files to ignore in find ... statements

export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o    \
			  -name CVS -o -name .pc -o -name .hg -o -name .git \) \
			  -prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
			 --exclude CVS --exclude .pc --exclude .hg --exclude .git

6、U-Boot 图形化配置

menuconfig是一套图形化的配置工具,需要 ncurses 库支持。ncurses 库提供了一系列的 API 函数供调用者
sudo apt-get install build-essential
sudo apt-get install libncurses5-dev

在uboot源码的根目录下输入命令打开图形化窗口
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
如果要将某个功能编译为模块,那就按下“M”,此时“[ ]”就会变为“< M >。

设置完成后需要重新编译
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

7、uboot移植

uboot的烧写有两种方法,一种是烧写到SD卡,一种是烧写到EMMC中(也需要用到SD卡)。

下载NXP的uboot(NXP有一个例程板卡,仿照那个板卡改自己的板子)
编译NXP的uboot

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_emmc_defconfig
make V=1 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16

需要将SD卡格式化成FAT32的格式。
(正点原子专门编写了一个软件来将编译出来的.bin 文件烧写到 SD 卡中,这个软件叫做“imxdownload”,软件放到了开发板光盘中,路径为:开发板光盘->5、开发工具->2、Ubuntu 下裸机烧写软件->imxdownload,imxdownlaod 只能在 Ubuntu 下使用。)
将imxdownlaod拷贝到工程根目录下。
确定要烧写的 SD 卡(ls /dev/sd*(我的是/dev/sdb))
使用 imxdownload 软件将 u-boot.bin烧写到 SD 卡中。

chmod 777 imxdownload             //给予 imxdownload 可执行权限
./imxdownload u-boot.bin /dev/sdb         //烧写 u-boot.bin 到 SD 卡中

五、linux内核

1、linux内核编译

需要下载lzop库,用于打包和加载zimage
sudo apt-get install lzop

编译内核

 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean
 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- imx_v7_defconfig
 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig
 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

编译完成以后就会在 arch/arm/boot 这个目录下生成一个叫做 zImage 的文件,zImage 就是我们要用的 Linux 镜像文件。另外也会在 arch/arm/boot/dts 下生成很多.dtb 文件(imx6ull-alientek-emmc.dtb),这些.dtb 就是设备树文件。

2、重要的文件夹

名字描述
arch架构相关目录名
block块设备相关目录
crypto加密相关目录
Documentation文档相关目录
drivers驱动相关目录
firmeare固件相关目录
fs文件系统相关目录
include头文件相关目录
init初始化相关目录
ipc进程间通信相关目录
kernel内核相关目录
lib库相关目录
mm内存管理相关目录
net网络相关目录
samples例程相关目录
scripts脚本相关目录
security安全相关目录
sound音频处理相关目录
tools工具相关目录
usr与 initramfs 相关的目录,用于生成initramfs
virt提供虚拟机技术(KVM)

linux顶层的makefile和uboot非常相似



六、根文件系统

1、根文件系统介绍

根文件系统首先是内核启动时所 mount(挂载)的第一个文件系统,内核代码映像文件保存在根文件系统中,而系统引导启动程序会在根文件系统挂载之后从中把一些基本的初始化脚本和服务等加载到内存中去运行。

2、根文件系统的目录

名字描述
/bin系统需要的可执行文件(ls rm…)
/dev设备文 件(串口…)
/etc存放配置文件目录
/mnt临时挂载目录,可以将 SD 卡或者 U 盘挂载到/mnt/sd 或者/mnt/usb 目录中
/proc临时挂载目录,存储系统运行信息文件
/usr软件资源目录
/var此目录存放一些可以改变的数据
/sbin用户存放一些可执行文件
/sys系统启动以后此目录作为 sysfs 文件系统的挂载点,sysfs 是一个类似于 proc 文件系统的特殊文件系统,sysfs 也是基于 ram 的文件系统,也就是说它也没有实际的存储设备。此目录是系统设备管理的重要目录,此目录通过一定的组织结构向用户提供详细的内核数据结构信息。
/opt可选的文件、软件存放区(由用户决定)

3、BusyBox

BusyBox 是一个集成了大量的 Linux 命令和工具的软件,像 ls、mv、ifconfig 等命令 BusyBox 都会提供。BusyBox 就是一个大的工具箱,这个工具箱里面集成了 Linux 的许多工具和命令。

编译 busybox

busybox编译

make //将编译的结果放入/home/wyd/linux/mnt/rootfs文件夹下
make install CONFIG_PREFIX=/home/wyd/linux/mnt/rootfs

编译完成以后会在 busybox 的所有工具和文件就会被安装到 rootfs 目录中, rootfs 目录内容有:bin linuxrc sbin usr。

Linux 内核 init 进程最后会查找用户空间的 init 程序,找到以后就会运行这个用户空间的 init 程序,从而切换到用户态。如果 bootargs 设置 init=/linuxrc,那么 linuxrc 就是可以作为用户空间的 init 程序,所以用户态空间的 init 程序是 busybox 来生成的。busybox 的工作就完成了,但是此时的根文件系统还不能使用,还需要一些其他的文件。

向根文件系统添加 lib 库

在 rootfs 中创建一个名为“lib”的文件夹 mkdir lib
将交叉编译器的库文件放到根文件系统中。

cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/lib
//*so*(*是通配符)和.a 文件,这些就是库文件 “-d”表示拷贝符号链接
cp *so* *.a /home/wyd/linux/mnt/rootfs/lib/ -d

cd /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib

mkdir dev proc mnt sys tmp root

setenv bootargs ‘console=ttymxc0,115200 root=/dev/nfs nfsroot=192.168.1.250(服务器ip地址ubuntu):
/home/wyd/linux/nfs/rootfs,proto=tcp rw ip=192.168.1.251(开发板):192.168.1.250(服务器ip地址ubuntu):192.168.1.1:
255.255.255.0::eth0:off’ //设置 bootargs


七、Linux 驱动开发

1.字符设备驱动

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节
流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,
LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启
动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在
Linux 内核启动以后使用“insmod”命令加载驱动模块。

模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和
卸载注册函数如下:

module_init(xxx_init);
//注册模块加载函数
module_exit(xxx_exit);
//注册模块卸载函数

printk在内核源码中用来记录日志信息的函数,只能在内核源码范围内使用。用法和printf非常相似

.ko文件是kernel object文件(内核模块),该文件的意义就是把内核的一些功能移动到内核外边, 需要的时候插入内核,不需要时卸载。
insmod xxx.ko 装载驱动
modprobe xxx.ko 装载驱动
cat /proc/device
lsmod 内核中已经加载的设备程序
rmmod 驱动设备名称 卸载驱动

八、设备树

设备树(Device Tree),将这个词分开就是“设备”和“树”,描述设备树的文件叫做 DTS(Device
Tree Source),这个 DTS 文件采用树形结构描述板级设备,也就是开发板上的设备信息,比如
CPU 数量、 内存基地址、IIC 接口上接了哪些设备、SPI 接口上接了哪些设备等等。

DTS 是设备树源码文件,DTB 是将DTS 编译以后得到的二进制文件。
将.dts 编译为.dtb需要用到 DTC 工具

编译 DTS 文件的话只需要进入到 Linux 源码根目录下,然后执行如下命令:
make all或者:make dtbs
“make all”命令是编译 Linux 源码中的所有东西,包括 zImage,.ko 驱动模块以及设备树,如果只是编译设备树的话建议使用“make dtbs”命令。

DTS 语法
.dtsi 头文件:设备树的头文件扩展名为.dtsi,一般.dtsi 文件用于描述 SOC 的内部外设信息,比如 CPU 架构、主频、外设寄存器地址范围,比如 UART、IIC 等等。

设备节点:设备树是采用树形结构来描述板子上的设备信息的文件,每个设备都是一个节点,叫做设备节点,每个节点都通过一些属性信息来描述节点信息,属性就是键—值对。

节点标签(label) :节点名字(节点名字@首地址)
节点标签可以用来给节点追加信息。
例如:

/ {
	aliases {
		can0 = &flexcan1;
	};
	cpus {
		#address-cells = <1>;
 		#size-cells = <0>;
		cpu0: cpu@0 {
			compatible = "arm,cortex-a7";
			device_type = "cpu";
			reg = <0>;
		};
	};

	intc: interrupt-controller@00a01000 {
		compatible = "arm,cortex-a7-gic";
		#interrupt-cells = <3>;
		interrupt-controller;
 		reg = <0x00a01000 0x1000>,<0x00a02000 0x100>;
	};
}

每个节点都有不同属性,不同的属性又有不同的内容,属性都是键值对,值可为空或任意的字节流。设备树源码中常用的几种数据形式如下所示:
1、字符串
compatible = “arm,cortex-a7”;
2、32 位无符号整数
reg = <0>;
也可以设置为一组值,比如:
reg = <0 0x123456 100>;
3、字符串列表
属性值也可以为字符串列表,字符串和字符串之间采用“,”隔开,如下所示:
compatible = “fsl,imx6ull-gpmi-nand”, “fsl, imx6ul-gpmi-nand”;

compatible 属性
compatible 属性也叫做“兼容性”属性,compatible 属性的值是一个字符串列表,compatible 属性用于将设备和驱动绑定起来。一般驱动程序文件都会有一个 OF 匹配表,此 OF 匹配表保存着一些 compatible 值,如果设备节点的compatible 属性值和 OF 匹配表中的任何一个值相等,那么就表示设备可以用这个驱动。

model 属性
model 属性值也是一个字符串,一般 model 属性描述设备模块信息,比如名字之类的。

status 属性
status 属性看名字就知道是和设备状态有关的,status 属性值是字符串,字符串是设备的状态信息。常用的是“okay”和“disabled”
“okay”表明设备是可操作的。
“disabled” 表明设备当前是不可操作的,但是在未来可以变为可操作的,比如热插拔设备插入以后。至于 disabled 的具体含义还要看设备的绑定文档。

#address-cells 和#size-cells 属性
这两个属性的值都是无符号 32 位整形,#address-cells 和#size-cells 这两个属性可以用在任何拥有子节点的设备中,用于描述子节点的地址信息。 #address-cells 属性值决定了子节点 reg 属性中地址信息所占用的字长(32位)
#size-cells 属性值决定了子节点 reg 属性中长度信息所占的字长(32位)。
#address-cells 和#size-cells 表明了子节点应该如何编写 reg 属性值,一般 reg 属性都是和地址有关的内容,和地址相关的信息有两种:起始地址和地址长度。

reg 属性
reg 属性的值一般是(address,length),reg 属性一般用于描述设备地址空间资源信息,一般都是某个外设的寄存器地址范围信息。
reg 属性的格式一为:reg = <address1 length1 address2 length2 address3 length3......>
每个“address length”组合表示一个地址范围,其中 address 是起始地址,length 是地址长度,#address-cells 表明 address 这个数据所占用的字长,#size-cells 表明 length 这个数据所占用的字长。

开发板查看设备树:/proc/device-tree
cd /lib/modules/4.1.15-gb78e551/

root@ATK-IMX6U:~# cp /mnt/gpioled.ko /lib/modules/4.1.15-gb78e551/
root@ATK-IMX6U:~# cp /mnt/ledApp /lib/modules/4.1.15-gb78e551/
cd /lib/modules/4.1.15-gb78e551/

depmod: ERROR: could not open directory /lib/modules/4.1.15-g871ccc8: No such file or directory
depmod: FATAL: could not search modules: No such file or directory

学习时间

2021.4-2021.10


学习产出

1、 csdn笔记 1篇



这篇关于嵌入式linux学习笔记(2)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程