为Raspberry Pi 2编译内核模块

2015-04-21更新:原始的rpi-source项目已经由PeterOGB 接手维护,所以无须再用我下文中提到的我改过的rpi-source脚本,直接用原始的就可以了。其它文中提到的背景知识都仍然有效。

即把第一个命令改为:
$ wget https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source
&& chmod a+x rpi-source

2015-07-29更新:Raspbian的内核版本已经升级到4.x,rpi-source还不能正确处理,需要进行以下额外的工作:

1. rpi-source需要获取的/proc/config.gz默认不存在了,需要额外加载模块来实现:

$ sudo modprobe configs

2. rpi-source在4.x内核下无法正确检测gcc版本,运行rpi-source时请加–skip-gcc选项。


 

在Linux下使用“360随身WiFi 2”》一文的留言区中,曾经有人问过,为什么编译出来的模块insmod/modprobe时报“Exec format error”,我不假思索的回复,请他检查编译模块时用的内核头文件与实际运行的内核是否完全匹配。这个答案倒也不算错,不过其实并没有解决问题,因为遇到的这个问题的人一般都已经用了“正确”的方式去编译他的模块,就算再重新做几遍,还是会遇到一样的问题。

最近我给Raspberry Pi 2编译内核模块时,遇到了一样的问题,花了很多时间才真正解决,在这里总结一下。以下描述的方法和内容,对Raspberry Pi (A/A+/B/B+)和Raspberry Pi 2都适用。

准备编译模块需要的内核树的方法(适用于Raspbian):

1. 下载我改过的rpi-source脚本
$ wget https://raw.githubusercontent.com/lifanxi/rpi-source/master/rpi-source
&& chmod a+x rpi-source

2. 运行rpi-source
$ ./rpi-source

3. 好了,可以进入模块源代码的目录进行模块编译了。

疑难排解:

1. rpi-source报gcc版本不一致

截止2015-03-12,Raspbian最新的内核是用gcc 4.8编译的(可以查看/proc/version确认),而Raspbian中自带的gcc是4.6的,需要升级到4.8。因为4.8的gcc已经backport了,所以可以直接sudo apt-get install gcc-4.8 g++-4.8,然后用update-alternatives设置优先级即可[1]。

2. 如果用rpi-source –skip-gcc忽略gcc版本检查,并强行用4.6的gcc会编译模块怎么样?

我的试验结果是模块可以编译,但在加载模块时会造成kernel oops,然后再用insmod/modprobe/rmmod/lsmod等命令时会挂住,只能重启解决。如果你编的模块是会自动加载的,重启前先把它删掉,不然启动时就会挂住。

3. rpi-source无法正常下载内核代码或Modules.symvers文件

有可能是你的内核版本太老,rpi-source只支持Raspberry Pi 3.10.37以上的内核。对于Raspberry Pi 2,它只支持3.18.6以上的内核。解决办法是先运行sudo rpi-update更新内核和固件,更新后请重启系统,然后再重新运行rpi-source。

4. 编译模块时报找不到arch/armv6l或arch/armv7l目录

尝试在make命令前加ARCH=arm参数,或尝试把/lib/modules/`uname -r`/build/arch中的arm软链为armv6l或armv7l后再编译。

背景知识:

1. Raspbian的内核包

不要按照使用Debian的习惯去找什么linux-image、linux-source之类的包,Raspbian的内核包是raspberrypi-bootloader,里面包含了内核、模块和一些启动文件,但没有Module.symvers和头文件。

2. rpi-update是啥

rpi-update是Raspbian内置的更新内核和相关固件的脚本,它的逻辑是去https://github.com/Hexxeh/rpi-firmware这个仓库下载最新的内核和固件,替换现有的版本。更新完成后会更新/boot/.firmware_revision,记下最新版本对应的Git Hash,以后rpi-update或rpi-source都会根据这个Hash去GitHub找对应文件。

3. Raspberry Pi的官方内核去哪里找

http://github.com/raspberrypi,里面的linux对应内核源代码,firmware是编译好的内核和相关文件。而rpi-update用的https://github.com/Hexxeh/rpi-firmware其实是firmware中部分文件的一个镜像,分出一个镜像仓库可以让rpi-update脚本的实现变得比较简单[2]。

4. rpi-source做了些啥

根据rpi-update记录在/boot/.firmware_revision中的内核版本Git Hash(如果没有用rpi-update更新过内核,就从raspberrypi-bootloaderq包的changlog中解析出Hash),去raspberrypi/linux仓库中获取对应的源代码,把/lib/modules/`uname -r`/build和/lib/modules/`uname -r`/source对应的软链建好,从/proc/config.gz获取当前内核配置,去raspberrypi/firmware仓库中获取对应的Modules.symvers跟内核代码放在一起,然后make modules_prepare准备好编译模块所需要的内核树。

5. 你改的rpi-source改了些啥

rpi-source的作者已经宣布不再维护这个脚本,并且这个脚本不支持Raspberry Pi 2,所以我在GitHub上Fork了一份,做了以下改动:

  • 修改了脚本自动更新URL到我Fork出来的版本;
  • 检查/proc/cpuinfo,判断当前硬件是Raspberry Pi还是Raspberry Pi 2;
  • 可以通过-b参数强行指定Raspberry Pi的硬件版本;
  • 根据不同的硬件,下载不同版本的Modules.symvers;
  • 如果用参数指定了要求用默认配置来配置内核树,则对不同硬件版本的Raspberry Pi调用不同的命令[3]。

6. Raspberry Pi和Raspberry Pi 2的内核有啥区别

Raspberry Pi 2的SOC是BCM2709,基于ARM 7(armv7l),而一代是BCM2708,ARM 6(armv6l),所以二代的内核中用了一些armv7l中特有特性。目前在打包的时候两个版本内核文件是打包在一起的,只是用后缀7或v7来区别,启动的时候会按实际硬件选择。

7. Module.symvers是干嘛用的?

一句话讲不清,有兴趣请参考[4]。总之,没有Module.symvers或用错了Module.symvers都可能会造成你加载模块时报Exec format error。如果你遇到了这样的情况,请确认rpi-source的执行过程中有没有失败的步骤。armv7l和armv6l版本的内核用的Module.symvers是不通用的,在raspberrypi/firmware中分别命名为Module.symvers和Modules7.symvers,但放到内核树中使用时需要命名为Module.symvers,如果是你自己准备内核树,务必要小心,我自己在这个问题上犯了错误,浪费了很多时间。当然,如果用我改过的rpi-source,那它已经帮你搞定了这件事。

8. 我用了rpi-update和rpi-source后编出来的模块还是无法加载。

目前我用本文描述的方法编译了过天猫魔盘(rtl8192eu)、360随身WiFi 2(mt7601u)这两种无线网卡的驱动,都工作正常。如果你遇到了别的问题,不妨在这里留言,可以一起讨论一下。

另外,终级大法一定是重新完整的编译整个内核,不过如果你想在Raspberry Pi上完成这个工作,那必须等有充分的耐心。所以,最好是在PC上进行交叉编译[3]。

[1] https://github.com/notro/rpi-source/wiki

[2] https://github.com/Hexxeh/rpi-firmware/blob/master/README.md

[3] https://github.com/raspberrypi/documentation/blob/master/linux/kernel/building.md

[4] http://www.ibm.com/developerworks/cn/linux/l-cn-kernelmodules/

为Raspberry Pi 2编译内核模块》上有37条评论

  1. Pingback引用通告: 在Linux下使用“360随身WiFi 2” | 李凡希的Blog

  2. 前辈你好,我是初学者。
    在make modules_prepare这里,出现如下错误,请指点。软硬件环境树莓派2,3.18.7-v7+
    arch/arm/kernel/asm-offsets.c:53:2: error: #error Your compiler is too buggy; it is known to miscompile kernels
    #error Your compiler is too buggy; it is known to miscompile kernels
    ^
    arch/arm/kernel/asm-offsets.c:54:2: error: #error and result in filesystem corruption and oopses.
    #error and result in filesystem corruption and oopses.
    ^
    Kbuild:81: recipe for target ‘arch/arm/kernel/asm-offsets.s’ failed
    make[1]: *** [arch/arm/kernel/asm-offsets.s] Error 1
    Makefile:980: recipe for target ‘prepare0′ failed
    make: *** [prepare0] Error 2

    • 这好像是一个已知问题,可惜我没遇到过,你尝试先sudo apt-get update/sudo apt-get upgrade把系统更新一下再试试看吧?如果曾经过了装gcc 4.8加过jessie源,就先把它去掉再更新系统。

      参考:https://github.com/raspberrypi/linux/issues/758

  3. 博主..我遇到一个奇怪的问题,也是编译驱动的rpi2的
    uname -a :
    Linux dietpi 3.18.11-v7+ #781 SMP PREEMPT Tue Apr 21 18:07:59 BST 2015 armv7l GNU/Linux
    源码也是最新下载的..
    自己编译的驱动无论怎么样.换成了V7的Module.symvers还是会出现在insmod的时候
    Error: could not insert module tft.ko: Invalid module format

    驱动信息
    filename: /root/lcd/tft.ko
    license: GPL
    author: FlandreUNX
    description: UNX LCD Driver
    srcversion: 9E2F3BC48302A8444747D86
    depends: fb_sys_fops,sysfillrect,syscopyarea,sysimgblt
    vermagic: 3.18.11-v7+ SMP mod_unload ARMv7 p2v8

    • 我自己试了一遍,用了以下命令:
      sudo rpi-update
      sudo reboot
      wget https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source
      chmod a+x rpi-source
      ./rpi-source
      cd /lib/modules/`uname -r`/build
      ln -sf arm arch/armv7l
      cd ~/v4.3.1.1_11320.20140505/driver/rtl8192EU_linux_v4.3.1.1_11320.20140505/
      make
      sudo make install
      sudo modprobe 8192eu

      这样编译出来的819eu的驱动完全可用。

      对应的vermagic是:
      vermagic: 3.18.11+-v7 SMP preempt mod_unload modversions ARMv7

      所以我还是非常怀疑你的内核树配置有没有问问题,请检查以下项:
      – 你的模块对别的模块有依赖,确认下依赖模块是否已经加载。或者别用insmod,改用modprobe来加载你的模块试试
      – /lib/modules/`uname -r`/build/.config与/proc/config.gz的内容是否一致
      – Module.symvers的md5sum(假设跟我一样是3.18.11+-v7的内核)是不是3308a9fd13846a980064e1ae12039378
      – 编译驱动时是否正确找到了正确的内核树

      • 首先非常感谢博主的帮助
        博主你好~.我跟你你提供的rpi-sources已经解决了
        Error: could not insert module tft.ko: Invalid module format
        的问题了…
        可是呢.现在遇到另外一个问题了…命名是根据linux驱动约定的..
        可是在insmod(modprobe找不到文件)的时候遇另外的错误.
        Error: could not insert module unxlcd.ko: Unknown symbol in module
        模块信息
        filename: /home/pi/lcd/unxlcd.ko
        license: GPL
        author: FlandreUNX
        description: UNX LCD Driver
        srcversion: B073286DEB71D287B2CA5CB
        depends: fb_sys_fops,sysfillrect,syscopyarea,sysimgblt
        vermagic: 3.18.11+-v7 SMP preempt mod_unload modversions ARMv7
        编译信息
        make -C /lib/modules/3.18.11-v7+/build SUBDIRS=/home/pi/lcd modules
        make[1]: Entering directory ‘/home/pi/linux-bb6b4b6b331680bed807605685572d727638bb51’
        CC [M] /home/pi/lcd/unxlcd.o
        In file included from include/linux/printk.h:5:0,
        from include/linux/kernel.h:13,
        from /home/pi/lcd/unxlcd.c:1:
        /home/pi/lcd/unxlcd.c: In function ‘__exittest’:
        include/linux/init.h:335:4: warning: return from incompatible pointer type [enabled by default]
        { return exitfn; } \
        ^
        /home/pi/lcd/unxlcd.c:684:1: note: in expansion of macro ‘module_exit’
        module_exit(fUNXLCD_Remove);
        ^
        Building modules, stage 2.
        MODPOST 1 modules
        CC /home/pi/lcd/unxlcd.mod.o
        LD [M] /home/pi/lcd/unxlcd.ko
        make[1]: Leaving directory ‘/home/pi/linux-bb6b4b6b331680bed807605685572d727638bb51’

      • 博主你好…问题解决了.希望后边的人能看到..主要问题是环境的配置..官方git下载的不一定是可以用的..rpi-update跟官方源是不一样的…

  4. 偶遇大神,请问有没有在树莓派2上创建个无线共享局域网的方案,我试了很多教程都是要树莓派2本身连上有线网络才可以,我是想创建个共享局域网,然后通过手机上传照片到树莓派打印照片,请教大神能否指点一二,谢谢

    • 没有理解你的具体需求。你是要在RPi2上创建一个无线局域网,然后手机接入后上传照片吗?如果是这样,可以用支持AP模式的无线网卡,加上hostapd等组件来组成一个无线局域网,手机就可以连上了。然后还可以用samba来共享打印机。

      • 我目前是这么做的,但是必须要有线网口连接上网的情况下才可以创建成功,但是我是想创建个局域网,不需要有线网联网,就会提示我没有联网启动服务失败,类似于将网线网卡变成一个路由器 请问360无线WIFI可以实现这样的功能吗

        • 你需要一个支持AP模式的无线网卡来实现AP的功能,360 Wi-Fi一代应该是可以,二代可能可以,但我没试过。你可以参考http://www.freemindworld.com/blog/2013/131010_360_wifi_in_linux.shtml这篇文章中我2014-08-12更新的部分,以及留言区中各位网友的讨论。看大家的讨论应该是可以的,但我没有试过。

  5. 博主你好:
    很高兴在你的教程的指导下一步一步的运行成功了,最后也能够成功的insmod了。但是之前直接从github上面下载源文件和Module.symvers却不能行。
    能不能给我们讲解一下如何直接下载并手动配置吗?

    • 手工操作可能造成的问题的原因,我能想到的在上文中都已经说明了。不知道你还想知道哪些方面的信息。

      具体rpi-source做了哪些事,其实并不复杂,文章中也已有过介绍,再要了解细节,可以直接看rpi-source的源代码。

  6. 前辈你好,看了你的博客为Rpi2编译内核,能讲的详细点么,比如SD卡上的文件系统如何解决,SD卡上的引导程序,难道是装了官方的Raspbian后把新编译的内核放进去覆盖原来的,直接使用原来的文件系统,引导? 官方的编译出来太大了3G多啊,我想自己优化编译,自己编译后的内核直接放进去的话那官方系统之前安的软件啊啥的都还在,依然没有达到给系统瘦身的目的啊,估计还很大吧?
    难道自己制作文件系统往里刷么,前辈能写篇博文讲讲么

    • 我这篇文章是讲怎么编内核模块的,不是说怎么编内核。
      编内核也不是很麻烦,在Raspberry Pi的GitHub上有相关文档,照着做就可以搭建出来一个交叉编译环境 。参考 https://github.com/raspberrypi/documentation/blob/master/linux/kernel/building.md。编译出来的内核和模块可以直接在RPi上安装,方法在上面的文档中同样有解释。
      你在说的3G多不是说内核吧?是包含了整个rootfs吧?如果你要从头搭建一个高度定制化的Linux系统,请学习LFS(www.linuxfromscratch.org)。

      • 太感谢你了,你给的资料很赞,我就是缺乏在国外网站上查找资料的能力,凡希大神能给我几个加强这方面能力的建议么。我是想从头搭建一个高度定制化的内核来着,之前折腾定制过s3c2440感觉和pi的不一样。 编过四轴裸板代码效果不错,可是功能太单一,玩起来乐趣太少O(∩_∩)O,这次想在pi上跑跑我写的飞控,效仿国外大神们的做法,再加点OPENCV增加点乐趣,可是现在一个问题是Linux实时性不好,你能推荐几个有关编写Linux实时性程序方法的网址么,还有就是我该打哪种实时性的补丁好呢

  7. 博主您好,最近在弄树莓派2的网卡驱动问题,TP-WN821N V5,芯片组恰好RTL8192EU,网上先是找到了您http://bbs.yunos.com/read/263281710 这个帖子,由于树莓派2是armv7而且系统版本已经升级,所以再次查找到这个帖子,没想到是同一个作者。
    根据提供的资料和评论里的信息,我在“sudo modprobe 8192eu”这条命令时出错,错误信息为ERROR: could not insert ‘8192eu’: Exec format error,不知道是什么地方不对。
    树莓派版本:Linux 3.18.11-v7+ #781 SMP PREEMPT Tue Apr 21 18:07:59 BST 2015 armv7l GNU/Linux

    希望得到您的回复!

    • 就是我文章里说的,出现Exec format error基本上就是因为Modules.symvers文件不匹配。

      建议你先sudo rpi-update更新内核,重启后用https://raw.githubusercontent.com/notro/rpi-source/master/rpi-source这个脚本装对应的头文件。然后再编译驱动,应该就问题不大了。

  8. 在debian8.2 64位中成功编译安装360wifi2,但是一打开网页内核就出错了,这个该怎么做。

    • 我怀疑是那个驱动太老了,对新内核支持有问题,不知道能不能找到新的驱动。我这里暂时没有更多的信息,我好久没用这个硬件了。

  9. 楼主你好,我现在需要加入一个4G驱动模块,网上编译内核教程都是说需要内核,fireware两个文件,除了把新编译的内核和模块替换后,还要替换fireware,vc库,我的意思是不改变内核版本,只是往里面加入模块,是不是只需要替换内核就行了!

    • 你是说把模块编译进内核?那就只要重新编译并替换内核好了。当然firmware可能还是需要先放到指定的位置。

      不过4G驱动有必要编译进内核么?用模块不是更合理一点?

  10. Pingback引用通告: 2016新年好 | 李凡希的Blog

  11. Pingback引用通告: 树莓派之uboot | 覃浩的小屋

  12. b-link wifi
    root@raspberrypi:/home/pi/Desktop/project/WIFI-AC-3/Linux/150M/linux# cd ./driver/rtl8192_8188CU_linux_v3.0.2164.20110715root@raspberrypi:/home/pi/Desktop/project/WIFI-AC-3/Linux/150M/linux/driver/rtl8192_8188CU_linux_v3.0.2164.20110715# make
    make ARCH=armv7l CROSS_COMPILE= -C /lib/modules/3.18.13-v7+/build M=/home/pi/Desktop/project/WIFI-AC-3/Linux/150M/linux/driver/rtl8192_8188CU_linux_v3.0.2164.20110715 modules
    make[1]: *** /lib/modules/3.18.13-v7+/build: No such file or directory. Stop.
    Makefile:437: recipe for target ‘modules’ failed
    make: *** [modules] Error 2
    root@raspberrypi:/home/pi/Desktop/project/WIFI-AC-3/Linux/150M/linux/driver/rtl8192_8188CU_linux_v3.0.2164.20110715#
    怎么办?

  13. 楼主你好,我成功编写了内核模块,并且也成功加载了,但在模块运行的过程中,内核崩溃了。我想请教一下有什么办法可以看内核崩溃的信息,从而能够帮助修改代码。

    • 一般来说,内核崩溃时会在终端上打出相关的信息,可以看到出问题的栈,用于分析问题。如果是不严重的oops,可能系统还可以运行,用dmesg可以看到相关信息。
      最保险的办法是配置kdump,在内核崩溃时可以自动重新加载一个内核,然后收集内存的Dump,供后续分析使用。

  14. 在rpiA+上无法使用华为ET302/3g无线网卡,安装华为通用Linux驱动失败。请指教。

    • 你先得确认下你的“通用”驱动是代码形式提供的还是以二进制的形式。如果是代码形式的,那就尝试编译一下看,如果有相关出错提示,可以再查原因。

  15. Pingback引用通告: 树莓派(Raspberry Pi)2B编译安装Robopeak USB屏幕驱动 – 默默的点滴

发表评论

电子邮件地址不会被公开。 必填项已用*标注