项目简介
嵌入式系统的开发过程较为复杂,编译,裁剪,定制等如果没有一套规范的流程将会难于管理和控制。本项目的目的是设计一个嵌入式Linux编译系统,实现代码的编译,定制和裁剪。Bootloader, 内核,驱动,文件系统,升级镜像等都可以自动化编译,打包。
本项目github:https://github.com/huangbin0709/easyLinux.git
EasyLinux平台编译系统架构
Buildroot
Buildroot是一个非常优秀的开源嵌入式编译系统,其本身已经非常完善。但其默认是在线编译的,即从网上下载源码包进行编译。对企业而言,本地编译可方便进行版本控制。EasyLinux平台除了工具软件之外,其他的软件包如内核,bootloader,app等都是采用本地编译的方式。此外,嵌入式系统模块之间的耦合还是比较大,如头文件,库文件的引用等,需要设计一个目录结构编译时方便地对这些文件进行引用。
EasyLinux平台目录结构
顶级目录结构
easyLinux
├── archive
│ └── gt2440
├── boot
│ └── u-boot-2015.01
├── buildroot
│ ├── arch
│ ├── board
│ ├── boot
│ ├── build
│ ├── CHANGES
│ ├── Config.in
│ ├── Config.in.legacy
│ ├── configs
│ ├── COPYING
│ ├── dl
│ ├── docs
│ ├── easylinux
│ ├── easylinux_patch_clean.sh
│ ├── easylinux_patch.sh
│ ├── ext
│ ├── fs
│ ├── linux
│ ├── Makefile
│ ├── Makefile.legacy
│ ├── package
│ ├── README
│ ├── support
│ ├── system
│ └── toolchain
├── kernel
│ └── linux-3.18.6
├── LICENSE
├── README.md
└── src
├──application
└── platform
EasyLinux平台有archive、boot、buildroot、kernel、src五个顶级目录,每个目录的设计如下:
Archive:存放src目录下编译生成的库文件,以机型为子目录存放,如archive/gt2440.
Boot:这个目录下存放bootloader源码,如uboot。
Buildroot:这个目录下添加了我们自己的目录easylinux,用于编译easylinux平台特有的软件包。
Kernel:存放内核。
Src:存放项目源代码
编译目录
easylinux
├── Config.in
├── core
│ ├── Config.in
│ └── core.mk
├── easylinux.mk
├── procmgr
│ ├── Config.in
│ └── procmgr.mk
└── watcher
├── Config.in
└── watcher.mk
EasyLinux编译目录中定义src目录下的源码包的编译规则。
EasyLinux/src目录结构
src
├── application
│ ├── adapter
│ ├── app
│ ├── drivers
│ ├── include
│ └── lib
└── platform
├── adapter
├── app
│ ├── core
│ │ ├── CMakeLists.txt
│ │ ├── include
│ │ └── src
│ ├── procmgr
│ └── watcher
├── drivers
├── include
└── lib
Src目录下存放我们自己开发的软件包源码,包括应用层App和内核驱动,所有软件包都以cmake组织。
库和头文件的引用
Buildroot中的package编译时会把源码拷贝到$(BUILD_DIR)目录下进行编译,为了便于管理,我们把easylinux的package拷贝到$(BUILD_DIR)/easylinux目录下进行编译。编译产生的库文件存放到easylinux/archive中。通过在$(BUILD_DIR)/easylinux目录下创建软连接arvhive,plat分别指向easylinux/arvhive中的库文件目录和src/application/include下的头文件目录。则编译软件包时可通过../arvhive和../plat目录引用头文件和库文件。
板级适配
为了使不同的开发板和芯片可以共用一套编译系统,需要进行一定的适配。
配置适配
buildroot/board/samsung
├── common
│ ├── busybox.config
│ ├── linux.config
│ ├── uboot.config
│ ├── uboot.mk
│ └── uClibc-0.9.33.config
└── yoka
└── uboot.mk
在vendor/board目录下存放各自的配置文件。
Xxx_defconfig文件中通过BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE等变量可以指定配置文件的路径,可以为不同的板指定不同的配置文件。
编译适配
1. mk文件中引入板级定制mk,以uboot为例:
#include board common mk files if any
-include $(BR2_BOARD_COMMON_DIR)/uboot.mk
#include board specify mk files if any
-include $(BR2_BOARD_CUSTOM_DIR)/uboot.mk
#include the mk file to fix the pkgdir in package/pkg-utils.mk
include $(TOPDIR)/boot/uboot/uboot-last.mk
$(eval $(generic-package))
在$(eval $(generic-package))之前插入上面的规则,则可以在板级的uboot.mk中重新设置一些环境变量,达到不同的板可以有不同的编译参数,编译路径的目的。
2. 使用全局编译参数
BR2_EASYLINUX_CFLAGS "-Werror -D_LITTLE_EDIAN=0x1234 -D_BIG_EDIAN=0x3412 -DBYTE_ORDER=0x1234 -D_MIPS_=2 -D_ARM_=1 -DCPU_ARCH=1"
Easylinux/Config.in中添加全局编译参数配置项,在所有package的mk文件中添加进他们的CFLAGS中。如在core.mk中
CORE_CFLAGS += $(BR2_EASYLINUX_CFLAGS)
#CORE_CFLAGS +=
CORE_CONF_OPTS += -DCMAKE_C_FLAGS="$(CORE_CFLAGS)"
在core_main.c中
#if CPU_ARCH == _ARM_
Do something
#else
Do something
#endif
3. 在模块的编译中定义宏
如在core.mk中
#如果是yoka
Ifeq ($(BR2_EASYLINUX_PROJECT_NAME),yoka)
CORE_CFLAGS += -DEASYLINUX_YOKA
endif
EasyLinux平台编译方法
编译所有的操作都在easylinux/buildroot目录下
2.4.1打入配置和编译
make O=build_dir xxx_defconfig
其中build_dir为想要输出到的目录
Xxx_defconfig为configs目录下的机型配置文件
make O=build_dir {target}
target是可选的,如果输入了target则只编译这个目标,否则编译配置的所有软件包。
make O=build_dir target-dirclean
target是目标,清理特定的软件包
make O=build_dir easylinux-clean
这个命令是easylinux平台的,用于清理easylinux平台的所有软件包。Buildroot全部编译需要较长的时间,这个命令可用于只编译easylinux软件包。
2.4.2定制
make O=build_dir menuconfig
make O=build_dir savedefconfig
定制package,保存到xxx_defconfig文件中
make O=build_dir linux-menuconfig
make O=build_dir linux-update-config
定制linux内核并保存
make O=build_dir busybox-menuconfig
make O=build_dir busybox-update-config
定制busybox并保存
make O=build_dir uboot-menuconfig
make O=build_dir uboot-update-config
定制uboot并保存
添加一个Package
2.5.1添加app
以添加应用程序procmgr为例
1. 在src/app相应目录下添加源代码,在源码下添加CMakeLists.txt文件
cmake_minimum_required(VERSION 3.0)
#project name
PROJECT(procmgr)
#head file path
#module目录下的头文件
#$(BUILD_DIR)/easylinux目录下的platform链接到$(TOPDIR)/../src/platform/include目录
#CMakeLists.txt编译时会拷贝到$(BUILD_DIR)/easylinux/{module}目录下,则../platform指向这个头文件目录
INCLUDE_DIRECTORIES(
include
../platform
)
#source directory
AUX_SOURCE_DIRECTORY(src DIR_SRCS)
#set environment variable
SET(TEST_MATH
${DIR_SRCS}
)
SET(CMAKE_INSTALL_PREFIX /easylinux)
#add executable file
ADD_EXECUTABLE(core ${TEST_MATH})
#
install(TARGETS procmgr RUNTIME DESTINATION app/bin)
2.在buildroot/easylinux/procmgr目录下添加Config.in
config BR2_EASYLINUX_PROCMGR
bool "easylinux app procmgr"
default n
3.在buildroot/easylinux/procmgr目录下添加procmgr.mk
################################################################################
#
# procmgr
#
################################################################################
PROCMGR_VERSION = 1.0
PROCMGR_SITE = $(TOPDIR)/../src/platform/app/procmgr
PROCMGR_SITE_METHOD = local
PROCMGR_INSTALL_STAGING = NO
PROCMGR_INSTALL_TARGET = YES
#PROCMGR_CONF_OPTS +=
#PROCMGR_DEPENDENCIES +=
PROCMGR_CFLAGS += $(BR2_EASYLINUX_CFLAGS)
#PROCMGR_CFLAGS +=
PROCMGR_CONF_OPTS += -DCMAKE_C_FLAGS="$(PROCMGR_CFLAGS)"
$(eval $(cmake-package))
2.5.2添加内核驱动
1.在src/app相应目录下添加源代码,在源码下添加Makefile文件
obj-m = demodriver.o
2.在buildroot/easylinux/demodriver目录下添加Config.in
3.在buildroot/easylinux/demodriver目录下添加demodriver.mk
################################################################################
#
# demodriver
#
################################################################################
DEMODRIVER_VERSION = 1.0
DEMODRIVER_SITE = $(TOPDIR)/../src/application/drivers/demodriver
DEMODRIVER_SITE_METHOD = local
DEMODRIVER_INSTALL_STAGING = NO
DEMODRIVER_INSTALL_TARGET = YES
#DEMODRIVER_CONFIG_SCRIPTS = DEMODRIVER-config
#DEMODRIVER_DEPENDENCIES = host-libaaa libbbb
define DEMODRIVER_BUILD_CMDS
$(TARGET_MAKE_ENV) $(MAKE) $(LINUX_MAKE_FLAGS) -C $(LINUX_DIR) M=$(@D) modules
Endef
define DEMODRIVER_INSTALL_TARGET_CMDS
cp $(@D)/*.ko $(TARGET_DIR)/easylinux/lib/modules
endef
$(eval $(generic-package))
2.5.3添加动态库
1.在src/app相应目录下添加源代码,在源码下添加CMakeLists.txt文件
cmake_minimum_required(VERSION 3.0)
#project name
PROJECT(platform)
#head file path
INCLUDE_DIRECTORIES(
../platform
)
#source directory
AUX_SOURCE_DIRECTORY(demo DEMO_FILES)
#set environment variable
#SET(CMAKE_INSTALL_PREFIX /easylinux)
#add executable file
ADD_LIBRARY(platform SHARED ${DEMO_FILES})
#add link library
#TARGET_LINK_LIBRARIES(core m)
install(TARGETS platform LIBRARY DESTINATION lib)
2.在buildroot/easylinux/platform目录下添加Config.in
3.在buildroot/easylinux/platform目录下添加platform.mk
################################################################################
#
# PLATFORM
#
################################################################################
PLATFORM_VERSION = 1.0
PLATFORM_SITE = $(TOPDIR)/../src/platform/lib
PLATFORM_SITE_METHOD = local
PLATFORM_INSTALL_STAGING = NO
PLATFORM_INSTALL_TARGET = YES
PLATFORM_INSTALL_TARGET_OPTS = DESTDIR=$(BR2_EASYLINUX_ARCHIVE_DIR) install
#PLATFORM_CONF_OPTS +=
#PLATFORM_DEPENDENCIES +=
PLATFORM_CFLAGS += $(BR2_EASYLINUX_CFLAGS)
#PLATFORM_CFLAGS +=
PLATFORM_CONF_OPTS += -DCMAKE_C_FLAGS="$(PLATFORM_CFLAGS)"
$(eval $(cmake-package))
文件系统编译和定制
目前easylinux平台设计为3个文件系统,根文件系统initramfs,用户镜像文件系统usrimage.jffs2和用户配置文件系统usrconf.jffs2。initramfs是最小根文件系统,和内核编译到一起挂载到内存。Usrimage.jffs2用来存放我们自己的可执行文件和库以及一些相应的数据。Usrconf.jffs用来保存配置,升级的时候可以保留这个分区,从而保存用户配置。
Initramfs基于cpio制作,我们在cpio.mk中对根文件系统进行定制
define ROOTFS_CPIO_CMD
#删掉不需要的文件
rm -rf $(TARGET_DIR)/usr/bin/top &&
rm -rf $(TARGET_DIR)/usr/bin/unzip &&
rm -rf $(TARGET_DIR)/usr/bin/wget &&
#先拷出easylinux文件夹,这些数据放入usrimage.jffs2中
cp -arf $(BR2_EASYLINUX_ARCHIVE_DIR)/usr/lib/* $(TARGET_DIR)/easylinux/lib &&
mkdir -p $(TARGET_DIR)/../tmptarget &&
cp -rf $(TARGET_DIR)/easylinux $(TARGET_DIR)/../tmptarget &&
rm -rf $(TARGET_DIR)/easylinux &&
#制作cpio镜像
cd $(TARGET_DIR) && find . | cpio --quiet -o -H newc > $@ &&
cp -rf $(TARGET_DIR)/../tmptarget/easylinux $(TARGET_DIR)
endef
制作usrimage.jffs2和usrconf.jffs2
define ROOTFS_JFFS2_CMD
mkdir -p $(TARGET_DIR)/../tmptarget/usrconf &&
mkdir -p $(TARGET_DIR)/../tmptarget/usrconf/conf &&
mkdir -p $(TARGET_DIR)/../tmptarget/usrconf/log &&
mkdir -p $(TARGET_DIR)/../tmptarget/usrconf/key &&
$(MKFS_JFFS2) $(JFFS2_OPTS) –d
$(TARGET_DIR)/../tmptarget/usrconf
$(BINARIES_DIR)/usrconf.jffs2 &&
$(MKFS_JFFS2) $(JFFS2_OPTS) –d
$(TARGET_DIR)/../tmptarget/easylinux –o
$(BINARIES_DIR)/usrimage.jffs2
endef
编译系统后即可得到3个文件系统镜像
启动脚本start_system.sh制作
#!/bin/sh
echo "start system,please wait..."
#更改printk打印级别
echo 4 > /proc/sys/kernel/printk
mkdir -p /mnt/easylinux
mkdir -p /mnt/usrconf
#挂载usrimage.jffs2
mount -t jffs2 /dev/mtdblock3 /mnt/easylinux
#挂载usrconf.jffs2
mount -t jffs2 /dev/mtdblock4 /mnt/usrconf
mkdir -p /easylinux
#将usrimage.jffs2也挂载到ramfs中
mount -t ramfs none /easylinux
cp -rf /mnt/easylinux/* /easylinux
chmod 0644 /easylinux/lib/*.so
#设置环境变量
export PATH=$PATH:/easylinux/app/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/easylinux/lib
#启动我们的第一个进程,其他进程都由他派生
/usr/bin/procmgr
echo "something error,we should not go here"
Start_system.sh放在src/platform/app/procmgr/etc中,编译procmgr时,和etc下的其他定制文件一起拷贝到根文件系统中。定制inittab文件
# now run any rc scripts
::sysinit:/etc/init.d/rcS > /dev/null
# Put a getty on the serial port
#console::respawn:/sbin/getty -L console 0 vt100 # GENERIC_SERIAL
#这里启动我们的脚本
console::sysinit:/etc/start_system.sh > /dev/null
# Stuff to do for the 3-finger salute
::ctrlaltdel:/sbin/reboot
升级镜像制作
Flash分区和文件系统设计见《EasyLinux平台文件系统设计》
有两种镜像类型,一种是up.bin,用于系统升级,仅包含有效数据和头部信息。一种是flash.bin,用于工厂生产时烧写,包含数据和分区之间填充的空洞。
Easylinux-mkimage目标用于制作镜像文件
make O=build/gt2440 easylinux-mkimage
生成的镜像在buildroot/image/img目录下,buildroot/image/source目录下是用于制作的源文件。其中,gt2440_all包含uboot,kernel,usrimage等所有分区数据,exclude_uboot去掉了uboot,
Exclude_uboot_kernel去掉了uboot和kernel。升级的时候可根据需要只升级部分区域。