linux编译内核步骤

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux编译内核步骤相关的知识,希望对你有一定的参考价值。

参考技术A 一、准备工作
a) 首先,你要有一台PC(这不废话么^_^),装好了Linux。
b) 安装好GCC(这个指的是host gcc,用于编译生成运行于pc机程序的)、make、ncurses等工具。
c) 下载一份纯净的Linux内核源码包,并解压好。

注意,如果你是为当前PC机编译内核,最好使用相应的Linux发行版的源码包。

不过这应该也不是必须的,因为我在我的Fedora 13上(其自带的内核版本是2.6.33.3),就下载了一个标准的内核linux-2.6.32.65.tar.xz,并且顺利的编译安装成功了,上电重启都OK的。不过,我使用的.config配置文件,是Fedora 13自带内核的配置文件,即/lib/modules/`uname -r`/build/.config

d) 如果你是移植Linux到嵌入式系统,则还要再下载安装交叉编译工具链。

例如,你的目标单板CPU可能是arm或mips等cpu,则安装相应的交叉编译工具链。安装后,需要将工具链路径添加到PATH环境变量中。例如,你安装的是arm工具链,那么你在shell中执行类似如下的命令,假如有类似的输出,就说明安装好了。
[root@localhost linux-2.6.33.i686]# arm-linux-gcc --version
arm-linux-gcc (Buildroot 2010.11) 4.3.5
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
注:arm的工具链,可以从这里下载:回复“ARM”即可查看。

二、设置编译目标

在配置或编译内核之前,首先要确定目标CPU架构,以及编译时采用什么工具链。这是最最基础的信息,首先要确定的。
如果你是为当前使用的PC机编译内核,则无须设置。
否则的话,就要明确设置。
这里以arm为例,来说明。
有两种设置方法():

a) 修改Makefile
打开内核源码根目录下的Makefile,修改如下两个Makefile变量并保存。
ARCH := arm
CROSS_COMPILE := arm-linux-

注意,这里cross_compile的设置,是假定所用的交叉工具链的gcc程序名称为arm-linux-gcc。如果实际使用的gcc名称是some-thing-else-gcc,则这里照葫芦画瓢填some-thing-else-即可。总之,要省去名称中最后的gcc那3个字母。

b) 每次执行make命令时,都通过命令行参数传入这些信息。
这其实是通过make工具的命令行参数指定变量的值。
例如
配置内核时时,使用
make ARCH=arm CROSS_COMPILE=arm-linux- menuconfig
编译内核时使用
make ARCH=arm CROSS_COMPILE=arm-linux-

注意,实际上,对于编译PC机内核的情况,虽然用户没有明确设置,但并不是这两项没有配置。因为如果用户没有设置这两项,内核源码顶层Makefile(位于源码根目录下)会通过如下方式生成这两个变量的值。
SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ )
ARCH?= $(SUBARCH)
CROSS_COMPILE ?=

经过上面的代码,ARCH变成了PC编译机的arch,即SUBARCH。因此,如果PC机上uname -m输出的是ix86,则ARCH的值就成了i386。

而CROSS_COMPILE的值,如果没配置,则为空字符串。这样一来所使用的工具链程序的名称,就不再有类似arm-linux-这样的前缀,就相当于使用了PC机上的gcc。

最后再多说两句,ARCH的值还需要再进一步做泛化。因为内核源码的arch目录下,不存在i386这个目录,也没有sparc64这样的目录。

因此顶层makefile中又构造了一个SRCARCH变量,通过如下代码,生成他的值。这样一来,SRCARCH变量,才最终匹配到内核源码arch目录中的某一个架构名。

SRCARCH := $(ARCH)

ifeq ($(ARCH),i386)
SRCARCH := x86
endif

ifeq ($(ARCH),x86_64)
SRCARCH := x86
endif

ifeq ($(ARCH),sparc64)
SRCARCH := sparc
endif

ifeq ($(ARCH),sh64)
SRCARCH := sh
endif

三、配置内核

内核的功能那么多,我们需要哪些部分,每个部分编译成什么形式(编进内核还是编成模块),每个部分的工作参数如何,这些都是可以配置的。因此,在开始编译之前,我们需要构建出一份配置清单,放到内核源码根目录下,命名为.config文件,然后根据此.config文件,编译出我们需要的内核。

但是,内核的配置项太多了,一个一个配,太麻烦了。而且,不同的CPU架构,所能配置的配置项集合,是不一样的。例如,某种CPU的某个功能特性要不要支持的配置项,就是与CPU架构有关的配置项。所以,内核提供了一种简单的配置方法。

以arm为例,具体做法如下。

a) 根据我们的目标CPU架构,从内核源码arch/arm/configs目录下,找一个与目标系统最接近的配置文件(例如s3c2410_defconfig),拷贝到内核源码根目录下,命名为.config。

注意,如果你是为当前PC机编译内核,最好拷贝如下文件到内核源码根目录下,做为初始配置文件。这个文件,是PC机当前运行的内核编译时使用的配置文件。
/lib/modules/`uname -r`/build/.config
这里顺便多说两句,PC机内核的配置文件,选择的功能真是多。不编不知道,一编才知道。Linux发行方这样做的目的,可能是想让所发行的Linux能够满足用户的各种需求吧。

b) 执行make menuconfig对此配置做一些需要的修改,退出时选择保存,就将新的配置更新到.config文件中了。

Linux 内核编译步骤及配置详解

linux 系统体系结构:

linux kernel体系结构:
arm有7种工作模式,x86也实现了4个不同级别RING0-RING3,RING0级别最高,
这样linux用户代码运行在RING3下,内核运行在RING0,这样系统本身就得到了
充分的保护

用户空间(用户模式)转到内核空间(系统模式)方法:
·系统调用
·硬件中断

linux kernel 体系结构:

虚拟文件系统VFS:
VFS(虚拟文件系统)隐藏各种文件系统的具体细节,为文件操作提供统一的接口


二.Linux内核源代码
linux内核下载www.kernel.org
目录结构:
解压linux kernel tar后目录
·arch:根据cpu体系结构不同而分的代码
·block:部分块设备驱动程序
·crypto:加密,压缩,CRC校验算法
·documentation:内核文档
·drivers:设备驱动程序
·fs(虚拟文件系统vfs):文件系统
·include:内核所需的头文件,(与平台无关的头文件在include/linux中)
·lib:库文件代码(与平台相关的)
·mm:实现内存管理,与硬件体系结构无关的(与硬件体系结构相关的在arch中)
·net:网络协议的代码
·samples:一些内核编程的范例
·scripts:配置内核的脚本
·security:SElinux的模块
·sound:音频设备的驱动程序
·usr:cpio命令实现,用于制作根文件系统的命令(文件系统与内核放到一块的命令)
·virt:内核虚拟机

linux DOC 编译生成:

linux源根目录/Documentation/00-INDEX:目录索引
linux源根目录/Documentation/HOWTO:指南
·生成linux内核帮助文档:在linux源根目录(Documentation) 执行make htmldocs

ubuntu16下需要执行sudo apt-get install xmlto安装插件才可生成doc文档

后面开发中经常要改的是arch,drivers中的代码


三.Linux内核配置与编译
清理文件(在linux源码根目录):
·make clean:只清理所有产生的文件
·make mrproper:清理所有产生的文件与config配置文件
·make distclean:清理所有产生的文件与config配置文件,并且编辑过的与补丁文件

配置(收集硬件信息如cpu型号,网卡等...):
·make config:基于文本模式的交互配置
·make menuconfig:基于文本模式的菜单模式(推荐使用)
·make oldconfig:使用已有的.config,但会询问新增的配置项
·make xconfig:图形化的配置(需要安装图形化系统)
配置方法:
1)使用make menuconfig操作方法:
1>按y:编译>连接>镜像文件
2>按m:编译
3>按n:什么都不做
4>按"空格键":y,n轮换
配置完并保存后会在linux源码根目录下生成一个.config文件
注意:在ubuntu11上要执行apt-get install libncurses5-dev来安装支持包
2)利用已有的配置文件模板(.config)
1>linux源码根目录/arch/<cpu架构>/configs/<具体某一的CPU文件>,把里面对应的文件copy并改名为.config至linux源码根目录下
2>利用当前运行已有的文件(要用ls /boot/ -a查看)把/boot/config-2.6.18-53.e15拷贝并改名为.config至linux源码根目录下执行以上操作就可以用make menuconfig在拷贝
.config文件上面修改文件了

编译内核:
1)make zImage
2)make bzImage
区别:在X86平台上,zimage只能用于小于512k的内核
获取详细编译信息:make zimage V=1 或 make bzimage V=1
编译好的内核在:arch/<cpu>/boot/目录下
注意:在把.config配置文件cp到根目录编译内核前,必须进入make menuconfig并保存退出(否则生不了效)

编译并安装模块:
1)编译内核模块:make modules
2)安装内核模块:make modules_install INSTALL_MOD_PATH=/lib/modules
更换本机器内核:将编译好的内核模块从内核源码目录copy至/lib/modules下
制作init ramdisk():输入执行命令mkinitrd initrd-2.6.39(任意) 2.6.39(可通过查询/lib/modules下的目录得到)
注意:
mkinitrd命令为redhat里面的,ubuntu的命令为:mkinitramfs -k /lib/modules/模块安装位置 -o initrd-2.6.39(任意) 2.6.39(可通过查询/lib/modules下的目录得到)
如果ubuntu里面没有mkinitramfs命令可以用apt-get install initrd-tools进行安装

安装内核模块:
1)手动
1>cp linux根目录/arch/x86/boot/bzImage /boot/mylinux-2.6.39
2>cp linux根目录/initrd-2.6.39 /boot/initrd-2.6.39
最后修改/etc/grub.conf或/etc/lilo.conf文件
2)自动
1>make install:这个命令会自动完成上面的操作(查看当前内核版本:uname -r)
-----------------------------------------------------------------------------
四.linux内核模块开发
描述:
linux内核组件非常庞大,内核ximage并不包含某组件,而是在该组件需要被使用的时候,动态的添加到正在运行的内核中(也可以卸载),这种机制叫做“内核模块”的机制。内核模块通常通过使用makefile文件对模块进行编译

模块安装与卸载:
1)加载:insmod hello.ko
2)卸载:rmmod hello
3)查看:lsmod
4)加载(自动寻找模块依赖):modprobe hello
modprobe会根据文件/lib/modules/version/modules.dep来查看要加载的模块,看它是否还依赖于其他模块,如果是,会先找到这些模块,把它们先加载到内核

实例分析:
1)moduleDep/1(一个模块的编译)

 1 #include <linux/module.h>
 2 #include <linux/init.h>
 3 
 4 //模块入口函数
 5 //__init:表示代码段中的子段,里面的内容只运行一次并且回收内存.
 6 static int __init hello_init(void)
 7 
 8     printk(KERN_EMERG "hello world!\\n");
 9     return 0;
10 
11 //模块卸载函数
12 //__exit:
13 static void __exit hello_exit(void)
14 
15     printk(KERN_EMERG "hello exit!\\n");
16 
17 //内核符号导出 函数
18 int add_integar(int a,int b)
19 
20     return a+b; 
21 
22 int sub_integar(int a,int b)
23 
24     return a-b; 
25 
26 
27 module_init(hello_init);
28 module_exit(hello_exit);
29 //函数导出
30 EXPORT_SYMBOL(add_integar);
31 EXPORT_SYMBOL(sub_integar);

makefile:

#第一次执行KERNELRELEASE是空的,所以执行else里面的
ifneq ($(KERNELRELEASE),)

obj-m :=hello.o

#else块
else

KDIR:= /lib/modules/2.6.18-53.el5/build

all:
#KDIR    依赖内核模块源代码路径(内核编译安装路径)
#PWD     表示内核代码在哪(当前目录)
#modules 编译的是模块
    make -C $(KDIR) M=$(PWD) modules 

clean:
    rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order

endif

2)moduleDep/2(两个模块的编译)

 1 #include <linux/module.h>
 2 #include <linux/init.h>
 3 //模块可选信息
 4 MODULE_LICENSE("GPL");//许可证声明
 5 MODULE_AUTHOR("liyuan");//作者声明
 6 MODULE_DESCRIPTION("This module is a param example.");//模块描述
 7 MODULE_VERSION("V1.0");//模块别名
 8 MODULE_ALIAS("a simple module");//模块别名
 9 
10 //模块参数
11 static char *name = "liyuan arg";
12 static int age = 30;
13 //S_IRUGO是参数权限,也可以用数字
14 module_param(age,int,S_IRUGO);
15 module_param(name,charp,S_IRUGO);
16 
17 
18 //使用外部文件函数
19 extern int add(int a,int b);
20 
21 
22 //声明 外部内核符号 函数
23 extern int add_integar(int a,int b);
24 extern int sub_integar(int a,int b);
25 
26 static int __init mains_init(void)
27 
28      //多文件编译
29 
30     printk(KERN_EMERG"param hi");
31     int vle=add(1,2);
32     printk(KERN_EMERG"add value:%d\\n",vle);
33     //模块参数
34 
35      printk(KERN_EMERG" name : %s\\n",name);
36      printk(KERN_EMERG" age : %d\\n",age);
37 
38     //使用其他模块的函数(内核符号导出)
39     int adds=add_integar(3,1);
40     int subs=sub_integar(3,1);
41     printk(KERN_EMERG" add_integar : %d\\n",adds);
42     printk(KERN_EMERG" sub_integar : %d\\n",subs);
43     return 0;
44 
45 
46 static void __exit mains_exit(void)
47 
48     printk("param exit!");
49 
50 
51 module_init(mains_init);
52 module_exit(mains_exit);

add.c

int add(int a,int b)

    return a+b;

makefile

ifneq ($(KERNELRELEASE),)
#两个以上内核源文件 生成单独的内核模块名ma

#内核ma
obj-m :=ma.o
#下面的ma-objs前面必须和上面一样为ma
ma-objs := mains.o add.o

else

KDIR:= /lib/modules/2.6.18-53.el5/build

all:
        make -C $(KDIR) M=$(PWD) modules 
clean:
    rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order

endif

运行带参模块:insmod hello.ko name=yuan age=12
内核符号导出(/proc/kallsyms记录了内核中所有导出的符号的名字与地址):
一个内核模块的运行依赖另一个内核模块的函数实现,必须先运行第一个内核模块,这样就需要进行内核符号导出。

注意:
错误信息:disagrees about version of symbol struct_module insmod:error inserting ...
开发内核模块时会出现,内核模块不匹配的情况.是你当前运行的linux内核与编译连接所依赖的
内核版本不匹配,解决方法:
·使用modprobe --force-modversion强行插入
·可使用uname -r进行查看当前运行的内核版本

printk内核打印:
在<linux/kernel.h>中printk有8个优先级,按优先级递减的是:
·KERN_EMERG 0
用于紧急的消息,常常是那些崩溃的消息
·KERN_ALERT 1
需要立刻行动的消息
·KERN_CRIT 2
严重情况
·KERN_ERR 3
错误情况
·KERN_WARNING(printk默认级别) 4
有问题的警告
·KERN_NOTICE 5
正常情况,但是仍然值得注意
·KERN_INFO 6
信息消息
·KERN_DEBUG 7
用作调试消息

不管是哪个级别的都会在/var/log/messages里面打印出来(messages可以删除后,运行内核进行测试内核打印情况)控制台打印(优先级配置/proc/sys/kernel/printk)

总结一下我们的安装步骤:

       1、获取内核源码,解压至/usr/src
           # tar xf linux-3.13.5.tar.xz -C /usr/src
           # ln -sv /usr/src/linux-3.13.5  /usr/src/linux
       2、配置内核特性(选择一种方法就可以了)
           make config:遍历选择所要编译的内核特性
           make allyesconfig:配置所有可编译的内核特性
           make allnoconfig:并不是所有的都不编译
           make menuconfig:这种就是打开一个文件窗口选择菜单
           make kconfig(KDE桌面环境下,并且安装了qt开发环境)
           make gconfig(Gnome桌面环境,并且安装gtk开发环境)
       3、编译内核
           # make [-j #] : #号最多为CPU物理核心总数的两倍,这样会快点哦            
       4、安装内核模块
           # make modules_install
       5、安装内核
           # make install
       6、验正并测试
           # cat /boot/grub/grub.conf
           查看新内核是否已经添加, 而后重启系统并测试

以上是关于linux编译内核步骤的主要内容,如果未能解决你的问题,请参考以下文章

嵌入式Linux编译内核步骤 / 重点解决机器码问题

Linux 内核编译步骤及配置详解

linux内核编译相关

Linux 内核编译步骤及配置详解

树莓派Linux内核配置编译以及烧录详细步骤

树莓派Linux内核配置编译以及烧录详细步骤