前言
本次實驗和某操作系統實驗課好像啊,我又回憶起了那幾次被Linux支配著的恐懼。
本次試驗使用Ubuntu 14.04 LTS 64 bits進行交叉編譯,下位機是Raspberry Pi 2,編譯的內核版本是3.18.16-v7,而下載鏈接中提供的Raspbian-Wheezy-2015-02-17的內核也是3.18。編譯基本按照樹莓派官方文檔進行。
我本次實驗的整體思路是先在SD card上燒錄好打包好的系統鏡像,而之后編譯的內核可以直接放進去而不用重建整個根目錄。如果鏡像內核版本和編譯內核版本差別過大的話容易出問題。而我選擇的這個3.18版本和我原來操作系統實驗所用的版本也比較接近,所以基本步驟可以通用。
如果按照教程跑,而且選擇交叉編譯的話,那么推薦編譯安裝的時候選擇一個能夠直接接觸到樹莓派SD卡的電腦,各種遠程服務器以及虛擬機在編譯內核模塊的時候如果出了我文中的那個問題可能會比較難受。或者說其實是我方法有問題?(Update: 這幾天想了想,感覺自己好蠢啊,可以交叉編譯好了再scp過去,(:з」∠))
最后一節是我折騰Acadia的從入門到放棄之路,希望能對大家有些幫助。(翁老大說Acadia直接放棄好了,不需要入門)
試編譯
其實如果熟悉的話,直接在文件目錄底下改文件即可,本步驟不是必要的。
但是,拿到不會的東西不得先點個燈?
首先從git上把項目拽下來,解壓之后文件夾結構大概長這樣。
推薦所有的操作均在linux下完成。因為如果壓縮包內有一些軟鏈接什么的在windows底下會出問題,而在linux下才會被正確解析。
而如果在mac上姿勢不對也是會出問題的,比如說使用的文件系統是大小寫不敏感的,那這會導致到后面編譯的時候缺少某些文件或者缺少某個宏定義等等。文件系統大小寫問題的解決方案戳這位同學的在mac os x上進行嵌入式linux開發[編譯linux kernel]
然后是安裝交叉編譯工具,在Lab2中已經下載過,直接拿來使用即可。
還有還有,編譯之前有一些依賴,別忘記裝了,ubuntu還是可以apt-get大法拿下來的。
sudo apt-get install bc
樹莓派1和2之間的操作還是有一些區別的,注意看好型號,樹莓派1直接去找官方文檔順著做就好了。
準備好之后,就可以開始編譯了。
首先是config文件,可以使用現在樹莓派上使用的config文件進行編譯,樹莓派上的配置文件是/proc/config.gz,使用zcat命令可以直接查看。
而按照官方教程,源碼包內有相關配置可以直接拿來使用。
KERNEL=kernel7
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig
使用上述命令選擇好項目配置即可。我選擇使用的是源碼包內和我樹莓派對應的那個配置(官方的教程寫的就是這個)。
而后準備好之后就可以直接開始編譯了
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs
少女祈禱中………………
反正又是一段漫長的編譯之旅。
如果編譯中間有問題,一般來說先考慮是不是依賴沒有滿足,而后如果再有什么file missing之類的錯誤我傾向于是源碼包有問題。
總體編譯過程蠻順利的。不像某Acadia……一定是因為樹莓派長得更像我熟知的Linux……
編譯完成之后就是安裝,如果燒錄了樹莓派官方給出的SDcard鏡像,那么現在你的SD卡分區長這樣。SDcard使用讀卡器接入電腦。
/dev/sdd
/dev/sdd1 fat32 boot 啟動分區
/dev/sdd2 ext4 / 根目錄
由于目錄與教程一致,所以就直接按照步驟執行一遍即可。
# 建立掛載點
mkdir /mnt/fat32
mkdir /mnt/ext4
# 掛載
# fat32掛載boot
sudo mount -t vfat /dev/sdd1 /mnt/fat32
# ext4掛載根文件夾
sudo mount -t ext4 /dev/sdd2 /mnt/ext4
# 在文件系統中安裝編譯出來的模塊
sudo make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- INSTALL_MOD_PATH=/mnt/ext4 modules_install
# 備份原來的系統鏡像,其中$KERNEL在編譯前進行過設置,此時值為kernel7
sudo cp /mnt/fat32/$KERNEL.img /mnt/fat32/$KERNEL-backup.img
sudo scripts/mkknlimg arch/arm/boot/zImage /mnt/fat32/$KERNEL.img
# 將其他我不認識的文件拷到我不認識的地方
sudo cp arch/arm/boot/dts/*.dtb /mnt/fat32/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /mnt/fat32/overlays/
sudo cp arch/arm/boot/dts/overlays/README /mnt/fat32/overlays/
# 卸載
sudo umount /mnt/fat32
sudo umount /mnt/ext4
這個時候直接將SDcard插到樹莓派上,上電就可以啟動了。
修改系統調用
本節可以結合操作系統實驗2的實驗指導一起食用。
系統調用實際上是調用內核某個函數的過程。所以,為了告訴操作系統什么時候該用什么函數,需要在內核中進行一些修改。
首先,你需要在內核中有一個這樣的可執行的函數。在arch/arm/kernel中新建一個sys_mysyscall.c文件,只包含一個函數,其作用為在運行后輸出一條內核日志。
而后,修改Makefile中的obj-y字段,將sys_mysyscall.o加入目標文件中。即將該函數納入系統的編譯進程。
而后,你需要讓操作系統知道這個函數是處理某個系統調用的函數。此時,需要修改系統的中斷向量表。此時需要修改arch/arm/kernel/calls.S文件。
按照操作系統實驗的教程,選擇223號調用進行替換。223號調用在x86體系架構的系統上是沒有使用的,而arm的似乎這么替換也沒有問題?不是很懂,不過這么替換沒有遇到坑。
在include/uapi/asm-generic/unistd.h頭文件中將223號調用與某個宏進行關聯,在syscall()中注冊一個位置,方便調用。
然后接下來就是又一次的編譯了,不過此次由于改的東西比較少,編譯會快一些。
將鏡像載入到SDcard之后,開始編寫使用系統調用的程序。如下兩個分別使用了匯編的方式以及系統提供的syscall方式調用系統調用。
#include <stdio.h>
#define sys_call() {__asm__ __volatile__ ("swi 0x900000+223\n\t");} while(0)
int main(void) {
sys_call();
printf("Type \"dmesg | tail\" to see the result.\n");
return 0;
}
#include <linux/unistd.h>
#include <sys/syscall.h>
int main(){
syscall(223);
return 0;
}
內核模塊
首先需要寫一個內核模塊,我就偷懶直接使用當時操作系統實驗中寫的系統進程統計的程序了。
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lmzqwer2 <lmzqwerty@163.com>");
MODULE_DESCRIPTION("In-kernel processors infomation detector.");
#define show(id,arr,x) printk(KERN_INFO "%s%s %d\n", id, #x, arr[x]);
#define clean(arr,x) arr[x] = 0
#ifndef IDENTIFIER
#define IDENTIFIER aaaadfa
#endif
static int processorDetector_read(struct file *file, char __user *out,
size_t size, loff_t *off){
// identifier用于每次調用的輸出,每行的輸出均帶有此標識符,而后用戶程序在讀取系統日志的時候只識別帶有該標識符的日志。
static char identifier[] = "IDENTIFIER";
// 系統init進程指針,使用該指針可以將整個系統的所有進程遍歷一遍
struct task_struct *task = &init_task;
int i, taskTotal = 0;
// 用于統計每個狀態的進程的個數,開大數組為了能少寫點代碼……寫法比較蠢
static int stateCollection[2049];
// 每次進調用需要清除上一次的結果
clean(stateCollection, TASK_RUNNING);
clean(stateCollection, TASK_INTERRUPTIBLE);
clean(stateCollection, TASK_UNINTERRUPTIBLE);
clean(stateCollection, __TASK_STOPPED);
clean(stateCollection, __TASK_TRACED);
clean(stateCollection, EXIT_DEAD);
clean(stateCollection, EXIT_ZOMBIE);
clean(stateCollection, EXIT_TRACE);
clean(stateCollection, TASK_DEAD);
clean(stateCollection, TASK_WAKEKILL);
clean(stateCollection, TASK_WAKING);
clean(stateCollection, TASK_PARKED);
clean(stateCollection, TASK_STATE_MAX);
// 修改identifier,使每次讀取該設備的時候返回的值均不同。
identifier[0]++;
i = 0;
while (identifier[i] == 'z'+1){
identifier[i++] = 'a';
if (i < sizeof(identifier)){
identifier[i]++;
}else
break;
}
// 遍歷系統的進程,有宏next_task進行進程之間的跳轉
// linux的進程使用環形鏈表,從init_task到init_task即完成了一次遍歷
do{
printk(KERN_INFO "%s%s %d %ld %s\n", identifier, task->comm, task->pid, task->state, task->parent->comm);
stateCollection[task->state]++;
taskTotal++;
task = next_task(task);
}while (task != &init_task);
// 輸出遍歷之后的統計信息
printk(KERN_INFO "%sThere is %d processes in system.", identifier, taskTotal);
show(identifier, stateCollection, TASK_RUNNING);
show(identifier, stateCollection, TASK_INTERRUPTIBLE);
show(identifier, stateCollection, TASK_UNINTERRUPTIBLE);
show(identifier, stateCollection, __TASK_STOPPED);
show(identifier, stateCollection, __TASK_TRACED);
show(identifier, stateCollection, EXIT_DEAD);
show(identifier, stateCollection, EXIT_ZOMBIE);
show(identifier, stateCollection, EXIT_TRACE);
show(identifier, stateCollection, TASK_DEAD);
show(identifier, stateCollection, TASK_WAKEKILL);
show(identifier, stateCollection, TASK_WAKING);
show(identifier, stateCollection, TASK_PARKED);
show(identifier, stateCollection, TASK_STATE_MAX);
// 將標識符拷貝給用戶
copy_to_user(out, identifier, sizeof(identifier));
return 0;
}
// 只實現了讀取指令,返回identifier
static struct file_operations processorDetector_fops = {
.owner = THIS_MODULE,
.read = processorDetector_read,
.llseek = noop_llseek
};
// 模塊名為processorDetector
static struct miscdevice processorDetector_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "processorDetector",
.fops = &processorDetector_fops
};
// insmod的時候調用該函數進行一些處理
static int __init processorDetector_init(void){
// create a device file at "/dev/"
// named "processorDetector"
misc_register(&processorDetector_misc_device);
printk(KERN_INFO
"processorDetector device has been registed.\n");
return 0;
}
// rmmod的時候調用該函數進行一些清理
static void __exit processorDetector_exit(void){
misc_deregister(&processorDetector_misc_device);
printk(KERN_INFO
"processorDetector device has been unregisted.\n");
}
// 注冊模塊的init & exit函數
module_init(processorDetector_init);
module_exit(processorDetector_exit);
該內核模塊還需要一個使用者進行使用。
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <fcntl.h>
char identifier[100];
char buf[100000];
int inner(char* a, char* b){
while (*a++ == *b++);
return *a==0;
}
int main(int argc, char* argv[]){
// 這個fd打開的是上面編譯的內核模塊
int fd = open("/dev/processorDetector", O_RDWR);
// 這個FILE*打開的是系統log
FILE* log = fopen("/var/log/kern.log", "r");
int i, len, buflen;
// 首先從內核模塊中獲取輸出標識符
read(fd, identifier, sizeof(identifier));
printf("Identifier: %s\n", identifier);
// 為了方便以后性能優化,先睡個2s先
sleep(2);
// 讀取系統日志,判斷標識符后輸出
len = strlen(identifier);
while (!feof(log)){
fgets(buf, sizeof(buf), log);
buflen = strlen(buf);
i = 0;
// 不要吐槽暴力枚舉
while (i + len < buflen && !inner(identifier, buf+i)){
i++;
}
if (!feof(log) && i + len < buflen - 1){
printf("%s", buf+i+len);
}
}
return 0;
}
好了,現在有了內核模塊,也有了對應的的用戶程序。那么就是編譯運行了。
我所想的內核模塊編譯進程是這樣的,一切順利。
然后就崩了。一定是代碼又過保質期了……
既然崩了就解決嘍。
錯誤信息提示的是找不到build文件夾,原來還以為是安裝的時候沒有帶上,然后發現就是安裝的時候沒有帶上。但是這錯誤和我預想的不一樣……
我就一臉懵逼得看著這個錯誤。這個 build -> /home/lmuser/tmp/linux 的意思是它在安裝的時候只是送了一個軟鏈接過去?竟然沒有直接拷貝……
不過仔細一想可能是SDcard上沒有這么大的空間把整個項目拷貝進去,所以就使用了軟鏈接。
但是,但是,但是!現在SDcard在樹莓派上,并沒有/home/lmuser/這種東西。經過我一番深思熟慮,我決定——內核模塊也用交叉編譯。
既然要交叉編譯,那么makefile自然就不能像原來的那樣簡單了。手動加的特技有點多。主要是指定編譯文件夾以及指定編譯的參數等。
obj-m := processorDetector.o
# 其實不需要這么多特技,直接定位/home/lmuser/tmp/linux即可
KERNEL_VER := 3.18.16-v7
KERNEL_DIR := /media/lmuser/f24a4949-f4b2-4cad-a780-a138695079ec/lib/modules/$(KERNEL_VER)/build
PWD := $(shell pwd)
ARGS := ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
all:
make -C $(KERNEL_DIR) SUBDIRS=$(PWD) $(ARGS) modules
clean:
rm *.o *.ko *.mod.c
.PHONY:clean
感覺好蠢啊……一點都不優雅。不過還是好用的……
然后將樹莓派關機,拔出SDcard,讀卡器,cd,make,彈出SDcard,樹莓派開機。
編譯期報了個warning,華麗麗無視之。
接下來就是驗證成果的時候了,果斷運行之。
成功把整個操作系統當前運行的所有進程都輸出出來了。
撒花,慶祝!
關于Acadia
最終我使用樹莓派而不是Acadia完成了實驗,原因主要是因為樹莓派在網上的教程比較多,而且樹莓派看上去整個文件結構什么的就比較像我熟悉的Linux。(主要是因為我和Acadia相性不合,折騰不出來)
而且沒有樹莓派沒有板載的存儲設備,SDcard直接作為系統存儲,插拔讀取修改操作都很方便。最關鍵的是不用擔心刷機刷壞了,刷壞了再燒一個就是了,Acadia有板載的存儲不是很敢亂玩。
以下的實驗步驟我編譯成功進入過一次系統,只有一次。之后不管怎么操作都進不去,可能是那一步中間有啥特殊的地方我沒注意到吧。
實驗一開始就不順利。雖然pcDuino/kernel.git這個git倉庫比較小,但是linux-sunxi這個倉庫大啊,1.6G啊,500W+的文件啊。
每次git到1W+的時候就clone不下去了。后來解決方案是使用服務器git clone --recursive下載完全之后,tar -czvf一次性打個包,再wget / Thunder到本地,再丟給虛擬機linux中就可以進行編譯了。
拿下來之后就是開始編譯了,按照官方教程,一步一步跑下來。似乎也沒有叫我配置什么config之類的。_(:з」∠)_
然后編譯報了個錯。
arm-linux-gnueabihf-ld.bfd: error: required section '.rel.plt' not found in the linker script
網上找了一些資料,說是要下載這個包。
sudo apt-get install ia32-libs
然而我并沒能成功找到這個包,搜索了半天,最終結果是使用下面這個包進行替換。
sudo apt-get install gcc-multilib
編譯繼續。
fs/btrfs/ctree.c:26:21: fatal error: locking.h: No such file or directory
#include "locking.h"
這個fs找不到頭文件啊,拿很難辦啊,反正不認識,config里面去掉好了。編譯繼續。
而后,又報了個錯。
fel.c:21:20: fatal error: libusb.h: No such file or directory
#include <libusb.h>
網上找說是缺這個,
sudo apt-get install libusb-1.0-0-dev
但是還是找不到,最終發現少了一個配置類型的程序。
sudo apt-get install pkg-config
下下來之后就可以繼續編譯了。
然后就又編譯不下去了。
Make sys configs: /home/linux/kernel/allwinner-tools/livesuit/default/sys_config_linux.fex
/home/linux/kernel/allwinner-tools/bins/script: 1: /home/linux/kernel/allwinner-tools/bins/script: Syntax error: end of file unexpected
先用file看了一下這個文件,并看不出什么。
linux@linux-VBox:~/kernel/allwinner-tools/bins$ file script
script: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.15, BuildID[sha1]=7ce8c666545525b7459addd15d8d7b91c4e70009, not stripped
百度了一下發現這樣一句話
誒,然后發現確實是生成了hwpack這個文件,我就當是編譯成功沒再管了。(說不定就是這個問題)
接下來就是如何將其安裝到Acadia了。
首先解包,有如下文件結構。
Acadia有一個板載的系統以及倆SDcard插槽,通過配置可以從中任意選一個進行啟動。
我首先選擇使用SD1進行啟動。啟動的方式也很保守,使用的是官方給的鏡像,然后似乎是沒啟動起來還是怎么回事,反正最終我放棄了從SDcard直接啟動。
而如果要從板子啟動,由于有板子自帶存儲器,沒辦法備份,我選擇慫。
要把剛才那個hwpack中的文件全部放入系統,那么需要考慮一些東西。首先,板載系統自帶bootloader,那么bootloader這個文件夾應該是不用去管的。而rootfs是放系統模塊的,仔細看了一下,和原版系統沒有沖突,直接拷貝之。而kernel比較尷尬,想要無沖突解決的話得加一些特技。
比如說使用bootloader的的一些命令,將內核手動載入系統。
首先還是得把系統燒入SDcard,使用以下命令即可,注意seek是后續載入系統的時候的參數之一,要前后一致。
sudo dd if=uImage of=/dev/sdd bs=512 seek=2048
sync
sync了之后,把SDcard從電腦上轉到Acadia上,從emmc啟動,進入bootloader。
# 設置了一些控制臺的參數
setenv bootargs_base 'setenv bootargs console=ttymxc0,115200'
# 控制文件系統的位置,使用的root位置為emmc即可
setenv bootargs_mmc 'setenv bootargs ${bootargs} root=/dev/mmcblk0p1 rootwait rw'
# 載入系統內核,
# 表示讀取SDcard1的
# 地址為0x800后續0x2000的內容
# 讀入位置為$(loadaddr),即后續bootm所用的地址
setenv bootcmd_mmc 'run bootargs_base bootargs_mmc; mmc dev 1; mmc read ${loadaddr} 0x800 0x2000; bootm'
# 開始引導
setenv bootcmd 'run bootcmd_mmc'
boot
然后,接下來出現了三種錯誤。
-
第一種是根本讀不到
比較少見,重啟可破。
-
第二種是CRC校驗失敗
可以使用命令強行扭過去。
setenv verify no
不過這種方法基本上是會進第三種錯誤的。校驗本來就是為了能夠保證東西是對的。
未解決!
* 第三種是輸出了 Starting kernel ... 之后,完全沒有反應
這句話是bootloader輸出的最后一句話,在此之后,控制權轉交給內核。
然而內核一點反應都沒有,那這就很尷尬了。原因有很多,沒有再折騰了。
未解決。
最終我的Acadia之路在某次順利從編譯出的內核啟動之后,就停留在了那個尷尬的階段。
Starting kernel ...
總的來說,沒做出來可能的原因有幾點;
1. make沒有make完畢
2. make前沒有做相關的配置
3. 沒有直接燒錄板子,太慫
4. 虛擬機有毒
5. 和Acadia相性不合
6. 我太蠢
反正就是撲街了,哪來這么多原因……
#參考資料
* [git clone 一個比較大的 repo 出錯, 糾結我 1 天了, 求助](https://segmentfault.com/q/1010000000637171)
樹莓派相關
* [Raspberry Pi documentation: KERNEL BUILDING](https://www.raspberrypi.org/documentation/linux/kernel/building.md)
* [驅動開發的一些錯誤解決方法](http://blog.chinaunix.net/uid-24456535-id-2606924.html)
* [樹莓派開發系列教程8——樹莓派內核編譯與固件升級](http://blog.csdn.net/xdw1985829/article/details/39077611)
* [樹莓派上為內核添加系統調用](http://blog.csdn.net/rk2900/article/details/8848093)
* [在mac os x上進行嵌入式linux開發[編譯linux kernel]](http://es.hzypp.me/zai-mac-os-xshang-jin-xing-qian-ru-shi-linuxkai-fa-bian-yi-linux-kernel/)
Acadia相關
* [Cross build pcDuino kernel on X86-64 machine](http://learn.linksprite.com/pcduino/a10-based-pcduino1pcduino2pcduino-litepcduino-lite-wifi/how-to-cross-build-pcduino-kernel-on-x86-64-machine/)
* [Tutorial on Flashing LinkSprite Acadia](http://learn.linksprite.com/acadia/tutorial-on-flashing-linksprite-acadia/)
* [pcDuino的Linux移植心得筆記](http://www.linuxidc.com/Linux/2013-04/83606.htm)
* [pcDuino: How to compile Kernel for pcDuino](http://blog.chinaunix.net/uid-23381466-id-3821540.html)
* [How to build linux images by yourself for pcDuino?](http://learn.linksprite.com/?p=1048)
* [pcDuino無顯示器刷機與使用](http://www.cnblogs.com/damir/p/3200558.html)
* [【轉】有關pcduino 內核編譯問題](http://www.pcduino.org/forum.php?mod=viewthread&tid=147)
* [I.MX6Q(TQIMX6Q/TQE9)學習筆記——內核啟動與文件系統掛載](http://blog.csdn.net/girlkoo/article/details/44626011)
* [Get stuck at "Starting kernel ..." using imx-3.10.17-1.0.1_ga](https://community.nxp.com/thread/329129)
# 下載鏈接
樹莓派相關
* [raspberrypi / linux](https://github.com/raspberrypi/linux)
整個項目大概git clone下來至少有1G。用校內的小水管慢慢跑簡直難受。
我選擇的版本是rpi-3.18.y。如果有服務器直接git clone了之后git checkout到這個tag了之后,.git文件夾的歷史使命就結束了。
此時將.git文件夾直接刪了就可以了。大概整個目錄就剩下100多Mb了,這就能下載了。
當然,如果有別人下載好了你直接拷貝也是極好的。
官方文檔提供了這樣一個命令,也是能減少git文件夾的。
git clone --depth=1 https://github.com/raspberrypi/linux
* [raspbian-2015-02-17/](https://downloads.raspberrypi.org/raspbian/images/raspbian-2015-02-17/)
Acadia相關
* [Github: pcduino / kernel](https://github.com/pcduino/kernel)
* [Image for Acadia](http://www.linksprite.com/image-for-acadia/)