100ASK-IMX6ULL開發板使用busybox構建根文件系統
1. 爲什麼使用busybox?
現在可以用於構建根文件系統的軟件有很多,比如buildroot,yocto,而且這些軟件構建的根文件系統軟件更全,爲什麼要使用busybox呢,因爲比較簡單,對於我這種初學者來說,從簡單開始學習纔是最好的,可以瞭解原理,原理清楚了,再使用其他軟件來構建就簡單多了。
2. 編譯busybox構建根文件系統
2.1 構建環境:
- gcc: 100ask_imx6ull-sdk\ToolChain\gcc-linaro-6.2.1-2016.11-x86_64_arm-linux- gnueabihf
- linux: linux-imx-rel_imx_4.1.15_2.1.0_ga.tar.bz2
- 主機系統: ubuntu18.04
gcc可以在百問網提供的bsp包找到,busybox使用的版本是nxp官方的busybox-1.29.0.tar.bz2, 點擊下載[busybox-1.29.0.tar.bz2][1]。
2.2 配置編譯busybox
- 解壓busybox-1.29.0.tar.bz2:
tar -vxjf busybox-1.29.0.tar.bz2
- 修改Makefile,添加編譯器,打開busybox的根目錄中的Makefile,將164行修改爲:
CROSS_COMPILE ?= /home/book/share/100ask_imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
將路徑修改爲自己的交叉編譯工具的絕對路徑,將190行修改爲:
ARCH ?= arm
- 配置busybox,busybox有三種配置選項:
- defconfig,默認配置
- allyesconfig,全選配置,使能busybox中的所有功能
- allnoconfig,最小配置
使用默認配置,執行:
make defconfig
然後打開圖形配置界面,執行:
make menuconfig
分別配置下面的路徑:
Location:
-> Settings
-> Build static binary (no shared libs) //取消選中,動態編譯,不然編譯出來的根文件系統很大
Location:
-> Settings
-> vi-style line editing commands //選中
Location:
-> Linux Module Utilities
-> Simplified modutils //取消選中
Location:
-> Linux System Utilities
-> mdev (16 kb) //確保下面的全部選中,默認都是選中的
- 配置完busybox就可以編譯了,執行:
make install CONFIG_PREFIX=/home/book/nfs_rootfs/blogrootfs
CONFIG_PREFIX指定輸出路徑,指向nfs服務器的路徑即可。編譯完成後可以查看rootfs路徑下的文件:
- 向根文件系統添加lib庫
在rootfs下創建lib文件夾,在usr下創建lib文件夾。
- 進入交叉編譯器存放的目錄,將arm-linuxgnueabihf/libc/lib下的*so*和*.a拷貝到rootfs/lib下:
cp *so* *.a ~/nfs_rootfs/blogrootfs/lib/ -d
後面的“-d”表示拷貝符號鏈接。在rootfs中lib執行ls ld-linux-armhf.so.3 -l,可以看到ld-linux-armhf.so.3是一個軟鏈接,所以需要重新複製一下。先刪除rootfs/lib下的ld-linux-armhf.so.3,然後進入arm-linuxgnueabihf/libc/lib執行:
cp ld-linux-armhf.so.3 ~/nfs_rootfs/blogrootfs/lib/
再次查看rootfs/lib下的ld-linux-armhf.so.3,已經不是軟鏈接了。
- 進入交叉編譯器存放的目錄,將arm-linuxgnueabihf/lib下的*so*和*.a拷貝到rootfs/lib下:
cp *so* *.a ~/nfs_rootfs/blogrootfs/lib/ -d
- 進入交叉編譯器存放的目錄,將arm-linuxgnueabihf/libc/usr/lib下的*so*和*.a拷貝到rootfs/usr/lib下:
cp *so* *.a ~/nfs_rootfs/blogrootfs/usr/lib/ -d
可能會有警告,不用管。
- 創建其它文件夾
在rootfs根目錄下創建文件夾:dev、 proc、 mnt、 sys、 tmp、root,創建完後如下圖示:
- 測試根文件系統
測試根文件系統一般都使用NFS,在內核啓動時指定使用的nfs服務器以及路徑,在uboot啓動後,設置bootargs爲:
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rootwait rw nfsroot=192.168.101.5:/home/book/nfs_rootfs/rootfs ip=192.168.101.6:192.168.101.5:192.168.101.1:255.255.255.0::eth0:off'
/* root=/dev/nfs nfsroot=[<server-ip>:]<root-dir>[,<nfs-options>] ip=<client-ip>:<server-ip>:<gwip>:<netmask>:<hostname>:<device>:<autoconf>:<dns0-ip>:<dns1-ip>*/
設置好後保存環境變量,然後使用tftp加載內核和設備樹並啓動:
tftp 80800000 zImage
tftp 83000000 imx6ull-100ask-emmc.dtb
bootz 80800000 - 83000000
啓動完成後如下圖:
可以執行基本的命令了,但是提示沒有/etc/init.d/rcS文件,所以需要完善一下文件系統。
3. 完善根文件系統
3.1 創建/etc/init.d/rcS文件
linux啓動後需要運行一些服務,而 /etc/init.d/rcS文件就是規定啓動哪些服務,創建該文件並添加如下內容:
#!/bin/sh
PATH=/sbin:/bin:/usr/sbin:/usr/bin:$PATH
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/lib:/usr/lib
export PATH LD_LIBRARY_PATH
mount -a
mkdir /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug #通過這兩行,linux內核就可以在/dev目錄下自動創建設備節點
mdev -s
將rcS添加可執行權限chmod 777 rcS。
3.2 創建/etc/fstab文件
fstab 在 Linux 開機以後自動配置哪些需要自動掛載的分區,創建該文件並添加如下內容:
#<file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
3.3 創建/etc/inittab文件
init 程序會讀取/etc/inittab這個文件, inittab 由若干條指令組成,創建該文件並添加如下內容:
::sysinit:/etc/init.d/rcS
console::askfirst:-/bin/sh
::restart:/sbin/init
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/umount -a -r
::shutdown:/sbin/swapoff -a
3.4 創建/etc/resolv.conf文件
/etc/resolv.conf文件用於配置域名服務器,沒有域名服務器就不能將網址轉換成ip地址,創建該文件並添加如下內容:
nameserver 114.114.114.114
nameserver 192.168.101.1
創建完以上三個文件後,重啓linux,發現沒有錯誤提示了。ping一下百度也可以ping通,網絡也可以使用了,此時可以通過交叉編譯器來編譯可以在100ASK開發板上運行的linux程序了。
4. 添加自己的開機log
其實並沒有修改內核在啓動的時候顯示的小企鵝,只是在linux啓動完後,運行一個app,這個app可以顯示bmp圖片,設置該app開機自動運行即可(在rcS中添加兩行代碼就可以了)。
bmp圖片的格式網上有詳解,編寫代碼displaybmp.c:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define SCREEN_HEIGHT 600
#define SCREEN_WIDTH 1024
#pragma pack(1) // 讓編譯器做1字節對齊
struct bmp_file //BMP文件頭結構
{
char type[2]; //位圖文件的類型,必須爲BM,我這裏類型不對,所以顯示有誤。
unsigned int size; //位圖文件的大小,以字節爲單位
short rd1; // 位圖文件保留字,必須爲0
short rd2; // 位圖文件保留字,必須爲0
unsigned int offset; // 位圖數據的起始位置,以相對於位圖
};
struct bmp_info //圖像信息區
{
unsigned int bsize; //本結構體所佔用字節數,即40個字節
int width; // 位圖的寬度,以像素爲單位,像素數量是4字節對齊的
int height; // 位圖的高度,以像素爲單位,如果該位爲正,說明圖像是倒向的,爲負則說明是正向,大多數都是正向,但是QQ截圖保存的BMP好像都是正向的
unsigned short planes; // 目標設備的級別,必須爲1
unsigned short count; // 每個像素所需的位數,必須是1(雙色)// 4(16色),8(256色)或24(真彩色)之一
unsigned int compression; // 位圖壓縮類型,必須是 0(不壓縮),// 1(BI_RLE8壓縮類型)或2(BI_RLE4壓縮類型)之一
unsigned int sizeimage; // 位圖的大小,以字節爲單位
unsigned int xmeter; // 位圖水平分辨率,每米像素數
unsigned int ymeter; // 位圖垂直分辨率,每米像素數
unsigned int cused; // 位圖實際使用的顏色表中的顏色數
unsigned int cimportant; // 位圖顯示過程中重要的顏色數
};
struct bmp_head {
struct bmp_file file;
struct bmp_info info;
};
#pragma pack() // 取消1字節對齊,恢復爲默認對齊
struct bmp_head bmphead;
unsigned char *bmpBuff = NULL;
unsigned int *fbBuff = NULL;
int bmp_width = 0;
int bmp_height = 0;
int actual_width = 0, actual_height = 0; /* 實際顯在屏幕上的大小,如果BMP圖片比屏幕大,那麼就只顯示屏幕分辨率的大小*/
int flag = 0; /* 指示圖像是否是倒向的,倒向爲0,正向爲1 */
int pix_count = 0; /* 指示每個像素佔用的大小,字節爲單位 */
void draw_screenbuf_point(int x, int y, unsigned int color) //在LCD buf上x, y處畫一個點
{
if(flag)
fbBuff[y * SCREEN_WIDTH + x] = color;
else
fbBuff[(SCREEN_HEIGHT - 1 - y) * SCREEN_WIDTH + x] = color;
}
unsigned int read_bmp_point(int x, int y) //從bmp文件中取出x,y座標處的點
{
unsigned int color = 0;
unsigned char R,G,B;
if(pix_count == 24) { /* bmp圖片count不一樣,數據區的格式就不一樣 */
B = bmpBuff[(x + y*bmp_width)*3];
G = bmpBuff[(x + y*bmp_width)*3+1];
R = bmpBuff[(x + y*bmp_width)*3+2]; /* (x + y*bmp_width)*4+3存放alpha值,不使用 */
color = B | G<<8 | R<<16 | 0x80<<24; /* 0x80爲透明度 */
return color;
}
else if(pix_count == 32) {
B = bmpBuff[(x + y*bmp_width)*4];
G = bmpBuff[(x + y*bmp_width)*4+1];
R = bmpBuff[(x + y*bmp_width)*4+2]; /* (x + y*bmp_width)*4+3存放alpha值,不使用 */
color = B | G<<8 | R<<16 | 0x80<<24; /* 0x80爲透明度 */
return color;
}
else
return 0x800000ff;
}
int main(int argc,char *argv[])
{
if(2 != argc) {
printf("Usage:%s xxx.bmp\n",argv[0]);
return -1;
}
int bmpfd = open(argv[1],O_RDWR);
if(-1 == bmpfd) {
perror("open faild\n");
return -1;
}
int fbdev = open("/dev/fb0",O_RDWR);
if(-1 == fbdev) {
perror("open failed\n");
return -1;
}
if (-1 == read(bmpfd, &bmphead, sizeof(bmphead))) {
perror("read failed\n");
return -1;
}
pix_count = bmphead.info.count;
if((pix_count != 24) && (pix_count != 32) ) {
printf("This format is not supported! pix_count:pix_count:%d\n", pix_count);
return 0;
}
bmp_width = bmphead.info.width;
if(bmphead.info.height < 0) {
bmp_height = 0-bmphead.info.height; //換成正數
flag = 1;
}
else {
bmp_height = bmphead.info.height;
flag = 0;
}
actual_width = bmp_width;
actual_height = bmp_height;
if(bmp_width > SCREEN_WIDTH) {
actual_width = SCREEN_WIDTH;
}
if(bmp_height > SCREEN_HEIGHT) {
actual_height = SCREEN_HEIGHT;
}
// printf("%s:%dx%d, flag:%d\n", argv[1], bmp_width, bmp_height, flag);
// printf("screen size:%dx%d\n", SCREEN_WIDTH, SCREEN_HEIGHT);
// printf("display size:%dx%d\n", actual_width, actual_height);
bmpBuff = (unsigned char *)malloc(bmphead.file.size - sizeof(struct bmp_head));
if(NULL == bmpBuff) {
perror("malloc failed\n");
return -1;
}
fbBuff = (unsigned int *)malloc(SCREEN_WIDTH*SCREEN_HEIGHT*sizeof(int));
if(NULL == fbBuff) {
perror("malloc failed\n");
return -1;
}
if (-1 == read(bmpfd, bmpBuff, bmphead.file.size - sizeof(struct bmp_head))) {
perror("read failed\n");
return -1;
}
int x = 0,y = 0;
unsigned int color;
for(y=0; y < actual_height; y++) {
for(x=0; x < actual_width; x++) {
color = read_bmp_point(x, y);
draw_screenbuf_point(x, y, color);
}
}
write(fbdev, fbBuff, SCREEN_WIDTH*SCREEN_HEIGHT*4); /* 寫入frambuffer */
free(fbBuff);
free(bmpBuff);
close(fbdev);
close(bmpfd);
return 0;
}
該程序的原理就是根據bmp的格式讀取RGB數據,然後寫入linux的framebuffer就可以顯示了,只支持位圖數據爲32位或者24位的bmp圖像顯示。
編譯測試:
arm-linux-gnueabihf-gcc disbmp.c -o displaybmp
sudo cp displaybmp ~/nfs_rootfs/blogrootfs/
使用QQ或者起他工具截圖並保存爲bmp格式,然後將bmp圖片放入roots目錄下,執行:
displaybmp xxx.bmp
一切正常的話LCD上應該就顯示截圖了。然後在/etc/init.d/rcS末尾添加:
./displaybmp xxx.bmp &
重啓後就會自動顯示了。
[1]: https://download.csdn.net/download/u014783685/12255517
[2]: https://www.bilibili.com/read/cv5121765