如何将一个模块文件编译到Linux内核中?
很多粉丝在群里提问,如何把一个模块文件编译到内核中或者独立变异成ko文件。本文给大家详解讲解。
1. 内核目录
Linux内核源代码非常庞大,随着版本的发展不断增加。它使用目录树结构,并且使用Makefile组织配置、编译。
初次接触Linux内核,好仔细阅读顶层目录的readme文件,它是Linux内核的概述和编译命令说明。readme的说明侧重于X86等通用的平台,对于某些特殊的体系结构,可能有些特殊的说明。
顶层目录的Makefile是整个内核配置编译的核心文件,负责组织目录树中子目录的编译管理,还可以设置体系结构和版本号等。
内核源码的顶层有许多子目录,分别组织存放各种内核子系统或者文件。
具体的目录说明如下表所示。
| 目录 | 内容 |
|---|---|
| arch/ | 体系结构相关的代码,如arch/i386、arch/arm、arch/ppc |
| crypto | 常用加密和散列算法(如AES、SHA等),以及一些压缩和CRC校验算法 |
| drivers/ | 各种设备驱动程序,如drivers/char、drivers/block…… |
| documentation/ | 内核文档 |
| fs/ | 文件系统,如fs/ext3、fs/jffs2…… |
| include/ | 内核头文件:include/asm是体系结构相关的头文件,它是include/asm-arm、include/asm-i386等目录的链接;include/linux是Linux内核基本的头文件 |
| init/ | Linux初始化,如main.c |
| ipc/ | 进程间通信的代码 |
| kernel/ | Linux内核核心代码(这部分比较小) |
| lib/ | 各种库子程序,如zlib、crc32 |
| mm/ | 内存管理代码 |
| net/ | 网络支持代码,主要是网络协议 |
| sound | 声音驱动的支持 |
| scripts/ | 内部或者外部使用的脚本 |
| usr/ | 用户的代码 |
2. 编译工具
make mrproper: 清除内核生成的配置文件与目标文件等,一般在第一次编译时使用
导入默认配置信息(在内核根目录中)
a) make xxx_deconfig
b) cp arch/arm/configs/xx_deconfig .config
生成默认配置文件
- 配置命令
make xxxxconfig 修改配置文件
make xconfig (图形界面 qt库)
make menuconfig (常用 libncurses库)
sudo apt-get install libncurses5-dev
make config (精简)
- 编译内核
make uImage ---生成内核镜像 /arch/arm/boot/uImage
- 编译设备树
make dtbs ---生成设备树文件 /arch/arm/boot/dtb/xxxxxx.dtb
- 编译生成模块文件
make modules ---把配置值选成M的代码编译生成模块文件。(.ko) 放在对应的源码目录下。
3. 内核编译
现在很多基于Linux的产品开发,通常厂家都会提供集成开发环境SDK。builroot使我们搭建环境变得更加方便,但是作为初学者我们还是要掌握如何独立编译内核源码。
0) 前提条件
必须先安装交叉编译工具链,关于交叉编译工具链的安装可以参考
《linux环境搭建-ubuntu16.04安装》
在这里我们使用的是arm-none-linux-gnueabi-gcc。
1)下载内核源码
下载地址:
https://mirrors.edge.kernel.org/pub/linux/kernel/
我们下载Linux-3.14内核(可以是更高的版本)至/home/peng目录。

或者直接点击下面链接
https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/linux-3.14.10.tar.xz
解开压缩包,并进入内核源码目录,具体过程如下:
$ tar xvf linux-3.14.tar.xz
$ cd linux-3.14
2)修改内核目录树根下的Makefile,指明交叉编译器:
$ vim Makefile
找到ARCH和CROSS_COMPILE,
修改:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
为
ARCH ?= arm
CROSS_COMPILE ?= arm-none-linux-gnueabi-

4)配置内核产生.config文件:
导入默认配置
$ make exynos_defconfig
这里我们假定要编译的内核最终在三星的板子上运行,soc名字是exynos,三星公司其实已经将自己的配置文件放置在
./arch/arm/configs/exynos_defconfig
执行这个命令,最终会在内核根目录下生成.config文件,

我们编译内核就完全依赖这个文件。
该文件是exynos开发板所需要的一些内核模块宏定义和参数设置,这些值是厂商给的一个初始配置。
实际项目开发中,需要在这个配置文件基础之上再重新移植自己需要的对应的驱动模块。
5)配置内核模块
输入内核配置命令,进行内核选项的选择,命令如下:
$ make menuconfig
命令执行成功以后,会看到如下图所示的界面。其实我们在图1.5中看到过同样功能的界面,那个图也是内核选项配置界面,只不过那个界面在X-window下才能执行。
其中:
- 子菜单--->

表示有子菜单,按下回车可以进入子菜单。
- 中括号[]
在每一个选项前都有个括号,有的是中括号,有的是尖括号,还有的是圆括号。

[] 表示该选项只有两种选项,中括号中要么是空,要么是“*”;
用空格键可以做出选择。
- 尖括号<>

<>选择相应的配置时,有3种选择,它们代表的含义分别如下。
● *:将该功能编译进内核。
● 空:不将该功能编译进内核。
● M:将该功能编译成可以在需要时动态插入到内核中的模块。
- 模块配置圆括号()
而圆括号的内容是要你在所提供的几个选项中选择一项。

如果使用的是make xconfig,使用鼠标就可以选择对应的选项。如果使用的是make menuconfig,则需要使用回车键进行选取。
在编译内核的过程中,麻烦的事情就是配置这步工作了。初次接触Linux内核的开发者往往弄不清楚该如何选取这些选项。
实际上,在配置时,大部分选项可以使用其默认值,只有小部分需要根据用户不同的需要选择。
选择的原则是将与内核其他部分关系较远且不经常使用的部分功能代码编译成为可加载模块,这有利于减小内核的长度,减少内核消耗的内存,简化该功能相应的环境改变时对内核的影响;不需要的功能就不要选;与内核关系紧密而且经常使用的部分功能代码直接编译到内核中。
6)编译内核:
root@ubuntu:/home/peng/linux-3.14# make uImage

如果按照默认的配置,没有改动的话,编译后系统会在arch/arm/boot目录下生成一个uImage文件,这个文件就是刚刚生成的。
7)下载Linux内核
因为不同的板子对应的uboot版本都不一样,所以下载程序的uboot命令也会有所差异,关于验证,本文暂不讨论。
4. 独立驱动程序的编译
1. 编译成独立模块
假定我们有以下驱动程序,要编译成可以加载到开发板的独立ko文件
hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//#include <io/uaccess.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
static int major = 237;
static int minor = 0;
static dev_t devno;
struct device *class_dev = NULL;
struct class *cls;
static int hello_open (struct inode *inode, struct file *filep)
{
printk("hello_open()\n");
return 0;
}
static int hello_release (struct inode *inode, struct file *filep)
{
printk("hello_release()\n");
return 0;
}
#define KMAX_LEN 32
char kbuf[KMAX_LEN+1] = "kernel";
//read(fd,buff,40);
static ssize_t hello_read (struct file *filep, char __user *buf, size_t size, loff_t *pos)
{
int error;
if(size > strlen(kbuf))
{
size = strlen(kbuf);
}
if(copy_to_user(buf,kbuf, size))
{
error = -EFAULT;
return error;
}
return size;
}
//write(fd,buff,40);
static ssize_t hello_write (struct file *filep, const char __user *buf, size_t size, loff_t *pos)
{
int error;
if(size > KMAX_LEN)
{
size = KMAX_LEN;
}
memset(kbuf,0,sizeof(kbuf));
if(copy_from_user(kbuf, buf, size))
{
error = -EFAULT;
return error;
}
printk("%s\n",kbuf);
return size;
}
static struct file_operations hello_ops =
{
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static int hello_init(void)
{
int result;
printk("hello_init \n");
result = register_chrdev( major, "hello", &hello_ops);
if(result < 0)
{
printk("register_chrdev fail \n");
return result;
}
cls = class_create(THIS_MODULE, "hellocls");
if (IS_ERR(cls)) {
printk(KERN_ERR "class_create() failed for cls\n");
result = PTR_ERR(cls);
goto out_err_1;
}
devno = MKDEV(major, minor);
class_dev = device_create(cls, NULL, devno, NULL, "hellodev");
if (IS_ERR(class_dev)) {
result = PTR_ERR(class_dev);
goto out_err_2;
}
return 0;
out_err_2:
class_destroy(cls);
out_err_1:
unregister_chrdev(major,"hello");
return result;
}
static void hello_exit(void)
{
printk("hello_exit \n");
device_destroy(cls, devno);
class_destroy(cls);
unregister_chrdev(major,"hello");
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
//proc/devices
注意我们需要编写Makefile如下:
ifneq ($(KERNELRELEASE),)
obj-m:=hello.o
else
KDIR :=/home/peng/linux-3.14
PWD :=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -f *.ko *.o *.mod.o *.symvers *.cmd *.mod.c *.order
endif
关于Makefile的详解,大家可以参考我们之前的文章
《手把手教Linux驱动1-模块化编程》
其中内核路径:
KDIR :=/home/peng/linux-3.14
必须是我们刚才编译过的内核源码根目录。
编译时,程序可以放到其他目录下:

用file命令查看文件属性,是基于ARM的。该模块文件就是与前面编译的内核配套的驱动模块,如果开发板的内核版本与上面编译的版本号一致,那么该模块文件就可以在开发板上insmod。

2. 编译到内核
步骤:
- 1)拷贝文件
如果要将刚才的驱动程序直接编译到内核,那么我们必须把hello.c拷贝到内核的某个目录下。
字符设备可以考虑放到以下目录:
linux-3.14/drivers/char

- 2)修改Makefile
root@ubuntu:/home/peng/linux-3.14/drivers/char# vim Makefile
修改如下:

该行内容是根据宏CONFIG_HELLO来决定是否编译hello.c这个文件。
- 3)修改Kconfig

7 HELLO 取前面步骤CONFIG_HELLO下划线后面的字符串
8 tristate 表示该模块最终有3个选项 空 * M
9 表示该模块依赖的模块,如果ARCH_EXYNOS4模块没有被选中,那么HELLO模块也不会被编译到内核
10 帮助信息
- 4) 重新配置
执行
make menuconfig
进入配置页面,
输入 / 可以根据关键字查找模块所在位置。

我们添加的模块文件的位置:

根据路径
-> Device Drivers
-> Character devices
找到我们刚才的模块配置路径

此处是尖括号,因为我们设置的属性是tristate
移动到Help处,可以看到前面我们填充的帮助信息
我们可以按下空格键设置为*,编译到内核中。
选择Save,


然后再点击2次Exit,就可以退出。
- 5)重新编译内核
root@ubuntu:/home/peng/linux-3.14# make uImage
这样,我们的模块编译到了新生成的内核模块文件中。
3. 补充
前面一节其实最终目的是生成CONFIG_HELLO=y 这个定义信息,并把该信息保存到内核根目录的.config文件中。

其实我们如果不修改Kconfig,直接在.config中增加这个宏定义也是可以的。
今天内容就到这里,还等什么?抓紧操练起来吧。
文中用到的虚拟机,叫交叉编译工具,还有源代码,可以关注公众号,后台回复 ubuntu,即可获得。
如何将一个模块文件编译到Linux内核中?的更多相关文章
- 将MPLS编译进linux内核中
系统环境:linux kernel 2.6.35.(此环境是上一篇文章中将ubuntu内核替换后的环境) 编译过程如下: 1)首先需要下载patch文件:linux-kernel-v2.6.35-mp ...
- 【转】6.4.6 将驱动编译进Linux内核进行测试
原文网址:http://www.apkbus.com/android-98520-1-1.html 前面几节都是将Linux驱动编译成模块,然后动态装载进行测试.动态装载驱动模块不会随着Android ...
- 编译安装linux内核步骤
编译安装linux内核步骤: 一.获取内核源码 源码网址:www.kernel.org 二.解压内核源码 首先以root帐号登录,然后进入/usr/src子目录.如果用户在安装Linux时,安装了内核 ...
- 将驱动编译进Linux内核
*:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...
- linux内核系列(一)编译安装Linux内核 2.6.18
1.配置环境 操作系统:CentOS 5.2 下载linux-2.6.18版本的内核,网址:http://www.kernel.org 说明:该编译文档适合2.6.18以上的Linux内核版本,只需所 ...
- 【转】 Linux内核中读写文件数据的方法--不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8193954 Linux内核中读写文件数据的方法 有时候需要在Linuxkernel--大 ...
- 【转】在linux内核中读写文件 -- 不错
原文网址:http://blog.csdn.net/tommy_wxie/article/details/8194276 1. 序曲 在用户态,读写文件可以通过read和write这两个系统调用来完成 ...
- linux内核中链表代码分析---list.h头文件分析(一)【转】
转自:http://blog.chinaunix.net/uid-30254565-id-5637596.html linux内核中链表代码分析---list.h头文件分析(一) 16年2月27日17 ...
- 35、在编译Linux内核中增加程序需要完成以下3项工作
在编译Linux内核中增加程序需要完成以下3项工作: 将编写的源代码拷入Linux内核源代码的相应目录. 在目录的Kconfig文件中增加关于新源代码对应项目的编译配置选项 在目录的Makefile文 ...
- linux内核中链表代码分析---list.h头文件分析(二)【转】
转自:http://blog.chinaunix.net/uid-30254565-id-5637598.html linux内核中链表代码分析---list.h头文件分析(二) 16年2月28日16 ...
随机推荐
- Vim编辑的小技巧
Vim编辑的小技巧 如何快速纠错 Ctrl + h 删除上一个字符, Ctrl + w 删除上一个单词, Ctrl + u 删除当前行. 从编辑模式快速切换到Nornal模式 1.Esc 2.Ctrl ...
- Prometheus监控系统(三)Prometheus与Grafana集成
1. Prometheus和Grafana集成 Grafana是一款采用Go语言编写的开源应用,主要用于大规模指标数据的可视化展现,是网络架构和应用分析中最流行的时序数据展示工具.目前已支持绝大部分常 ...
- 【论文阅读】Trajectory-guided Control Prediction for End-to-end Autonomous Driving: A Simple yet Strong Baseline
参考与前言 Summary: leaderboard 现存第一名 TCP,非常simple的设置 取得了很好的效果 论文链接:Trajectory-guided Control Prediction ...
- 基于OMAPL138+FPGA核心板多核软件开发组件MCSDK开发入门(上)
本文测试板卡为创龙科技 SOM-TL138F 是一款基于 TI OMAP-L138(定点/浮点 DSP C674x + ARM9)+ 紫光同创 Logos/Xilinx Spartan-6 低功耗 F ...
- Linux Mint操作系统安装
1,Linux 发行版 什么是Linux 发行版呢?这要从Linux 来源说起.Unix操作系统后期,开始收费和商业闭源了.一个叫Richard Stallman 的人就发起 GNU 计划,想模仿U ...
- Windows Android 子系统(WSA)安装
除了Linux子系统WSL,微软还提供了安卓子系统WSA.不过对国内好像不太友好,安装也不方便. 这里说一下我的安装方法,但是可能时效性很强,现在是2022-01-20,如果日期离得太远可能不好使. ...
- 还在困惑需要多少数据吗?来看看这份估计指南 | CVPR 2022
论文基于实验验证,为数据需求预测这一问题提供了比较有用的建议,详情可以直接看看Conclusion部分. 来源:晓飞的算法工程笔记 公众号 论文: How Much More Data Do I Ne ...
- 网易数帆开源贡献获业界肯定,轻舟API网关获OSCAR尖峰开源技术创新奖
2020年10月16日,由中国信息通信研究院主办的"2020开源产业大会"在北京线下与线上同步召开,主办方在会上公布了"OSCAR尖峰开源奖项"各个奖项的评选结 ...
- 整数-笔记C
实际情况也确实如此,C语言并没有严格规定 short.int.long 的长度,只做了宽泛的限制: short 至少占用 2 个字节. int 建议为一个机器字长.32 位环境下机器字长为 4 字节, ...
- PixiJS源码分析系列:第三章 使用 canvas 作为渲染器
使用 canvasRenderer 渲染 上一章分析了一下 Sprite 在默认 webgl 渲染器上的渲染,这章让我们把目光聚集到 canvasRenderer 上 使用 canvas 渲染器渲染图 ...