Linux模块文件编译到内核与独立编译成.ko文件的方法

简介: Linux模块文件编译到内核与独立编译成.ko文件的方法

很多粉丝在群里提问,如何把一个模块文件编译到内核中或者独立变异成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. 编译工具

  1. make mrproper: 清除内核生成的配置文件与目标文件等,一般在第一次编译时使用
  2. 导入默认配置信息(在内核根目录中)
a) make xxx_deconfig
b) cp arch/arm/configs/xx_deconfig  .config
生成默认配置文件
  1. 配置命令
make xxxxconfig  修改配置文件
make xconfig (图形界面 qt库)
make menuconfig (常用 libncurses库)
sudo apt-get install libncurses5-dev
make config (精简)
  1. 编译内核
make uImage ---生成内核镜像  /arch/arm/boot/uImage
  1. 编译设备树
make dtbs ---生成设备树文件  /arch/arm/boot/dtb/xxxxxx.dtb
  1. 编译生成模块文件
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下才能执行。

其中:

  1. 子菜单--->

表示有子菜单,按下回车可以进入子菜单。

  1. 中括号[] 在每一个选项前都有个括号,有的是中括号,有的是尖括号,还有的是圆括号。

[] 表示该选项只有两种选项,中括号中要么是空,要么是“*”;

用空格键可以做出选择。

  1. 尖括号<>

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

如果使用的是make xconfig,使用鼠标就可以选择对应的选项。如果使用的是make menuconfig,则需要使用回车键进行选取。

在编译内核的过程中,麻烦的事情就是配置这步工作了。初次接触Linux内核的开发者往往弄不清楚该如何选取这些选项。

实际上,在配置时,大部分选项可以使用其默认值,只有小部分需要根据用户不同的需要选择。

选择的原则是将与内核其他部分关系较远且不经常使用的部分功能代码编译成为可加载模块,这有利于减小内核的长度,减少内核消耗的内存,简化该功能相应的环境改变时对内核的影响;不需要的功能就不要选;与内核关系紧密而且经常使用的部分功能代码直接编译到内核中。

6)编译内核:

root@ubuntu:/home/peng/linux-3.14# make uImage

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中增加这个宏定义也是可以的。

今天内容就到这里,还等什么?抓紧操练起来吧。

文中用到的虚拟机,叫交叉编译工具,还有源代码,

目录
相关文章
|
1天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
1天前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
3天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
24 4
|
4天前
|
网络协议 Linux
linux系统重要文件目录
本文介绍了Linux系统中的重要目录及其历史背景,包括根目录、/usr、/etc、/var/log和/proc等目录的结构和功能。其中,/etc目录下包含了许多关键配置文件,如网卡配置、DNS解析、主机名设置等。文章还详细解释了各目录和文件的作用,帮助读者更好地理解和管理Linux系统。
16 2
|
3天前
|
缓存 监控 Linux
|
4天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
6天前
|
Linux Shell 数据库
文件查找是Linux用户日常工作的重要技能介绍了几种不常见的文件查找方法
文件查找是Linux用户日常工作的重要技能。本文介绍了几种不常见的文件查找方法,包括使用`find`和`column`组合、`locate`和`mlocate`快速查找、编写Shell脚本、使用现代工具`fd`、结合`grep`搜索文件内容,以及图形界面工具如`Gnome Search Tool`和`Albert`。这些方法能显著提升文件查找的效率和准确性。
25 2
|
4天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
11 0
|
网络协议 NoSQL Linux
阿里云 Linux 内核优化实战(sysctl.conf 和 ulimits )
一、sysctl.conf优化Linux系统内核参数的配置文件为 /etc/sysctl.conf 和 /etc/sysctl.d/ 目录。其读取顺序为: /etc/sysctl.d/ 下面的文件按照字母排序;然后读取 /etc/sysctl.conf 。
8565 1
|
6月前
|
机器学习/深度学习 人工智能 负载均衡
深度解析:Linux内核调度策略的演变与优化
【5月更文挑战第30天】 随着计算技术的不断进步,操作系统的性能调优成为了提升计算机系统效率的关键。在众多操作系统中,Linux因其开源和高度可定制性而备受青睐。本文将深入剖析Linux操作系统的内核调度策略,追溯其历史演变过程,并重点探讨近年来为适应多核处理器和实时性要求而产生的调度策略优化。通过分析比较不同的调度算法,如CFS(完全公平调度器)、实时调度类和批处理作业的调度需求,本文旨在为系统管理员和开发者提供对Linux调度机制深层次理解,同时指出未来可能的发展趋势。