linux内核模块编译步骤_linux内核模块开发_linux内核模块开发

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤


好多粉丝提问,怎样把一个模块文件编译到内核中或则独立变异成ko文件。本文给你们解读讲解。

1.内核目录

Linux内核源代码十分庞大,随着版本的发展不断降低。它使用目录树结构,但是使用Makefile组织配置、编译。

初次接触Linux内核红旗linux6.0,好仔细阅读顶楼目录的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初始化linux内核模块开发,如main.c

ipc/

进程间通讯的代码

kernel/

Linux内核核心代码(这部份比较小)

lib/

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

各类库子程序,如zlib、crc32

mm/

显存管理代码

net/

网路支持代码,主要是网路合同

sound

声音驱动的支持

scripts/

内部或则外部使用的

usr/

用户的代码

2.编译工具

makemrproper:消除内核生成的配置文件与目标文件等,通常在第一次编译时使用

导出默认配置信息(在内核根目录中)

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内核模块编译步骤_linux内核模块开发_linux内核模块开发

必须先安装交叉编译工具链,关于交叉编译工具链的安装可以参考《linux环境搭建-ubuntu16.04安装》

在这儿我们使用的是arm-none-linux-gnueabi-gcc。

1)下载内核源码

下载地址:

我们下载Linux-3.14内核(可以是更高的版本)至/home/peng目录。

或则直接点击下边链接

解开压缩包,并步入内核源码目录,具体过程如下:

$ 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-

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

4)配置内核形成.config文件:

导出默认配置

$ make  exynos_defconfig

这儿我们假设要编译的内核最终在三星的板子上运行ubuntu linux,soc名子是exynos,三星公司虽然早已将自己的配置文件放置在./arch/arm/configs/exynos_defconfig

执行这个,最终会在内核根目录下生成.config文件,

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

我们编译内核就完全依赖这个文件。该文件是exynos开发板所须要的一些内核模块宏定义和参数设置,这种值是厂商给的一个初始配置。实际项目开发中,须要在这个配置文件基础之上再重新移植自己须要的对应的驱动模块。

5)配置内核模块

输入内核配置命令,进行内核选项的选择,命令如下:

$ make menuconfig

命令执行成功之后,会听到如右图所示的界面。虽然我们在图1.5中见到过同样功能的界面,那种图也是内核选项配置界面,只不过那种界面在X-window下才会执行。

linux内核模块编译步骤_linux内核模块开发_linux内核模块开发

其中:

子菜单--->

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

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

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

用空格键可以作出选择。

尖括弧

选择相应的配置时,有3种选择,它们代表的涵义分别如下。

● *:将该功能编译进内核。
● 空:不将该功能编译进内核。
● M:将该功能编译成可以在需要时动态插入到内核中的模块。

模块配置圆括弧()而圆括弧的内容是要你在所提供的几个选项中选择一项。

假如使用的是makexconfig,使用键盘就可以选择对应的选项。假如使用的是makemenuconfig,则须要使用回车键进行选定。

在编译内核的过程中linux内核模块开发,麻烦的事情就是配置这步工作了。初次接触Linux内核的开发者常常弄不清楚该怎么选定这种选项。

实际上,在配置时,大部份选项可以使用其默认值,只有小部份须要依照用户不同的须要选择。

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

6)编译内核:

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

uImage

倘若依照默认的配置,没有改动的话,编译后系统会在arch/arm/boot目录下生成一个uImage文件,这个文件就是刚才生成的。

7)下载Linux内核

linux内核模块编译步骤_linux内核模块开发_linux内核模块开发

由于不同的板子对应的uboot版本都不一样,所以下载程序的uboot命令也会有所差别,关于验证,本文暂不讨论。

4.独立驱动程序的编译1.编译成独立模块

假设我们有以下驱动程序,要编译成可以加载到开发板的独立ko文件

hello.c

#include 
#include 
#include 
#include 
#include 
//#include 
#include 
#include 
#include 

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("%sn",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 clsn");
  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  :=$( 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

必须是我们刚刚编译过的内核源码根目录。

编译时,程序可以放在其他目录下:

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

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

2.编译到内核

步骤:

字符设备可以考虑放在以下目录:

linux-3.14/drivers/char

root@ubuntu:/home/peng/linux-3.14/drivers/char# vim Makefile 

更改如下:

该行内容是依据宏CONFIG_HELLO来决定是否编译hello.c这个文件。

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

7HELLO取上面步骤CONFIG_HELLO顿号前面的字符串8tristate表示该模块最终有3个选项空*M9表示该模块依赖的模块,假如ARCH_EXYNOS4模块没有被选中,这么HELLO模块也不会被编译到内核10帮助信息

make menuconfig

步入配置页面,

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

输入/可以按照关键字查找模块所在位置。

linux内核模块开发_linux内核模块编译步骤_linux内核模块开发

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

linux内核模块开发_linux内核模块编译步骤_linux内核模块开发

按照路径

-> Device Drivers 
   -> Character devices

找到我们昨天的模块配置路径

linux内核模块编译步骤_linux内核模块开发_linux内核模块开发

此处是尖括弧,由于我们设置的属性是tristate

联通到Help处,可以见到上面我们填充的帮助信息

我们可以按下空格键设置为*,编译到内核中。

选择Save,

linux内核模块编译步骤_linux内核模块开发_linux内核模块开发

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

之后再点击2次Exit,就可以退出。

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

这样,我们的模块编译到了新生成的内核模块文件中。

3.补充

后面一节虽然最终目的是生成CONFIG_HELLO=y这个定义信息,并把该信息保存到内核根目录的.config文件中。

linux内核模块开发_linux内核模块开发_linux内核模块编译步骤

虽然我们倘若不更改Kconfig,直接在.config中降低这个宏定义也是可以的。

明天内容就到这儿,还等哪些?抓紧操练上去吧。

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

良许个人陌陌

本公众号全部博文已整理成一个目录,请在公众号里回复「m」获取!

本文原创地址://gulass.cn/xjrhjmkwjbyd.html编辑:刘遄,审核员:暂无