前言
本次实验和某操作系统实验课好像啊,我又回忆起了那几次被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
#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
#include
int main(){
syscall(223);
return 0;
}
内核模块
首先需要写一个内核模块,我就偷懒直接使用当时操作系统实验中写的系统进程统计的程序了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lmzqwer2 ");
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
#include
#include
#include
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
网上找说是缺这个,
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/)