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

一、linux体系结构

从大面上来说,linux体系结构分为:
1 )用户空间:C库、用户应用程序;
2 )内核空间:系统调用接口、内核、硬件平台依赖代码。

Linux 内核编译步骤及配置详解_第1张图片

具体来讲,Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序

内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。如下图:

Linux 内核编译步骤及配置详解_第2张图片
1、linux内核
内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。

Linux 内核编译步骤及配置详解_第3张图片

系统调用接口(SCI层):给应用用户提供一套标准的系统调用函数,上层用户可以通过这一套标准接口来访问底层;
Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。

内存管理

1)作用:管理物理内存、创建和管理虚拟内存

为了使有限的物理内存满足应用程序对内存的需求,linux采用“虚拟内存”的内存管理方式实现,实现原理:
交换空间:内核在硬盘上化一段存储空间来实现虚拟内存,这段存储空间称为“交换空间”
页面:内存存储单元被分割成很多块,称为“页面”
页面内存表:内核会维护一张表,来指明哪些页面位于物理内存,那么页面位于交换空间

换出:物理内存---->交换空间
换入:交换空间---->物理内存

工作过程:

Linux 内核编译步骤及配置详解_第4张图片
2)内核将程序运行用到的页面就放到内存里,暂时不用就放到交换空间中(换出)。
3)当用到交换空间的页面时,就把它们调到内存中(换入),然后把物理内存其他用不到的页面换出到交换空间。

查看内存
#cat /proc/meminfo

MemTotal: 1035244 kB #物理内存1G
MemFree: 786616 kB #空闲内存700M左右

SwapTotal: 2096472 kB #有2G的交换空间

查看内存使用情况
#free

total used free shared buffers cached
Mem: 502360 489964 12396 0 53852 283372
-/+ buffers/cache: 152740 349620
Swap: 1015800 0 1015800

共享内存页面:
创建一写共享内存页面,用于多个进程共享使用。
#ipcs -m #查看共享内存页面
key shmid owner perms bytes nattch status
0x00000000 0 rich 600 52228 6 dest
#owner:共享内存段的所有者
#perms:权限

进程管理
进程实际是某特定应用程序的一个运行实体。在 Linux 系统中,能够同时运行多个进程,Linux 通过在短的时间间隔内轮流运行这些进程而实现“多任务”。这一短的时间间隔称为“时间片”,让进程轮流运行的方法称为“进程调度” ,完成调度的程序称为调度程序。
进程调度控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际上是仅等待CPU资源的进程,如果某个进程在等待其它资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。
内核通过 SCI 提供了一个应用程序编程接口(API)来创建一个新进程(fork、exec 或 Portable Operating System Interface [POSⅨ] 函数),停止进程(kill、exit),并在它们之间进行通信和同步(signal 或者 POSⅨ 机制)。

文件管理
linux内核文件管理采用虚拟文件系统(VFS),隐藏各种文件系统的具体细节,为文件操作提供统一的接口。Linux提供了一个大的通用模型,如下:
Linux 内核编译步骤及配置详解_第5张图片

常见文件系统:
ext3:第3扩展文件系统,支持日志功能
ext4 :第4扩展文件系统,支持高级日志功能

iso9660:ISO 9660文件系统(CD-ROM)

nfs :网络文件系统

ntfs:支持Microsoft NT文件系统

proc:访问系统信息

smb:支持网络访问的Samba SMB文件系统

vfat:Windows 95文件系统(FAT32)

硬件设备管理:
设备驱动程序是 Linux 内核的主要部分。和操作系统的其它部分类似,设备驱动程序运行在高特权级的处理器环境中,从而可以直接对硬件进行操作,但正因为如此,任何一个设备驱动程序的错误都可能导致操作系统的崩溃。设备驱动程序实际控制操作系统和硬件设备之间的交互。设备驱动程序提供一组操作系统可理解的抽象接口完成和操作系统之间的交互,而与硬件相关的具体操作细节由设备驱动程序完成。一般而言,设备驱动程序和设备
的控制芯片有关,例如,如果计算机硬盘是 SCSI 硬盘,则需要使用 SCSI 驱动程序,而不是 IDE 驱动程序。

内核代码中必须加入设备驱动程序代码,才能与硬件进行通信,linux内核提供2种方法来插入设备驱动代码:
1)编译进内核的设备驱动代码,缺点:添加新设备时,需要重新编译内核。
2)可插入内核的设备驱动模块:增加新设备,驱动模板插入内核中,无需重新编译。设备不可用时,也可从内核模块中移走。

网络接口(NET)
提供了对各种网络标准的存取和各种网络硬件的支持。网络接口可分为网络协议和网络驱动程序。网络协议部分负责实现每一种可能的网络传输协议。众所周知,TCP/IP 协议是 Internet 的标准协议,同时也是事实上的工业标准。Linux 的网络实现支持 BSD 套接字,支持全部的TCP/IP协议。Linux内核的网络部分由BSD套接字、网络协议层和网络设备驱动程序组成。
网络设备驱动程序负责与硬件设备通讯,每一种可能的硬件设备都有相应的设备驱动程序。

2 . linux shell
shell是系统的用户界面,提供了用户与内核进行交互操作的一种接口。它接收用户输入的命令并把它送入内核去执行,是一个命令解释器。另外,shell编程语言具有普通编程语言的很多特点,用这种编程语言编写的shell程序与其他应用程序具有同样的效果。
目前主要有下列版本的shell:
1)Bourne Shell:是贝尔实验室开发的。

2)BASH:是GNU的Bourne Again Shell,是GNU操作系统上默认的shell,大部分linux的发行套件使用的都是这种shell。

3)Korn Shell:是对Bourne SHell的发展,在大部分内容上与Bourne Shell兼容。

4)C Shell:是SUN公司Shell的BSD版本。

3 、linux 文件系统
文件系统是文件存放在磁盘等存储设备上的组织方法。Linux系统能支持多种目前流行的文件系统,如EXT2、 EXT3、 FAT、 FAT32、 VFAT和ISO9660等。
Linux 内核编译步骤及配置详解_第6张图片

4、linux桌面环境
标准的Linux系统一般都有一套都有称为应用程序的程序集,它包括文本编辑器、编程语言、X Window、办公套件、Internet工具和数据库等。

其他
GNU工具链:
操作系统还需要工具链来执行一些标准功能,将linux内核和GNU操作系统工具整合起来,就可以创造一个完整的、功能丰富的免费操作系统
GNU工具链包括:
1.核心GNU工具链:有一组核心工具,称为GNU软件包,包括:处理文件、操作文件、管理进程的工具。

2.shell:是用户和操作系统沟通的桥梁:(用户----shell----kernel----硬件)

linux kernel体系结构:
arm有7种工作模式,x86也实现了4个不同级别RING0-RING3,RING0级别最高,这样linux用户代码运行在RING3下,内核运行在RING0,这样系统本身就得到了充分的保护。
用户空间(用户模式)转到内核空间(系统模式)方法:、
·系统调用
·硬件中断

linux kernel 体系结构:
Linux 内核编译步骤及配置详解_第7张图片


二、Linux内核源代码
linux内核下载www.kernel.org

1、目录结构:
解压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:内核虚拟机

2、linux DOC 编译生成:

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

ubuntu16下需要执行sudo apt-get install xmlto安装插件才可生成doc文档
后面开发中经常要改的是arch,drivers中的代码。


三、Linux内核配置与编译

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

2、配置(收集硬件信息如cpu型号,网卡等…):
·make config:基于文本模式的交互配置
·make menuconfig:基于文本模式的菜单模式(推荐使用)
·make oldconfig:使用已有的.config,但会询问新增的配置项
·make xconfig:图形化的配置(需要安装图形化系统)

3、配置方法:
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//configs/<具体某一的CPU文件>,把里面对应的文件copy并改名为.config至linux源码根目录下
2>利用当前运行已有的文件(要用ls /boot/ -a查看)把/boot/config-2.6.18-53.e15拷贝并改名为.config至linux源码根目录下执行以上操作就可以用make menuconfig在拷贝.config文件上面修改文件了

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

5、编译并安装模块:
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进行安装

6、安装内核模块:
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(一个模块的编译)

 #include <linux/module.h>
 #include <linux/init.h>
 
 //模块入口函数
 //__init:表示代码段中的子段,里面的内容只运行一次并且回收内存.
 static int __init hello_init(void)
 {
     printk(KERN_EMERG "hello world!\n");
     return 0;
 }
 //模块卸载函数
 //__exit:
 static void __exit hello_exit(void)
 {
     printk(KERN_EMERG "hello exit!\n");
 }
 //内核符号导出 函数
 int add_integar(int a,int b)
 {
     return a+b; 
 }
 int sub_integar(int a,int b)
 {
     return a-b; 
 }
 
 module_init(hello_init);
 module_exit(hello_exit);
 //函数导出
 EXPORT_SYMBOL(add_integar);
 EXPORT_SYMBOL(sub_integar);

makefile:

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

obj-m :=hello.o

#elseelse

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(两个模块的编译)

 #include <linux/module.h>
 #include <linux/init.h>
 //模块可选信息
 MODULE_LICENSE("GPL");//许可证声明
 MODULE_AUTHOR("liyuan");//作者声明
 MODULE_DESCRIPTION("This module is a param example.");//模块描述
 MODULE_VERSION("V1.0");//模块别名
 MODULE_ALIAS("a simple module");//模块别名
  
  //模块参数
 static char *name = "liyuan arg";
 static int age = 30;
 //S_IRUGO是参数权限,也可以用数字
 module_param(age,int,S_IRUGO);
 module_param(name,charp,S_IRUGO);
 
 
 //使用外部文件函数
 extern int add(int a,int b);
 
 
 //声明 外部内核符号 函数
 extern int add_integar(int a,int b);
 extern int sub_integar(int a,int b);
 
 static int __init mains_init(void)
 {
      //多文件编译
 
     printk(KERN_EMERG"param hi");
     int vle=add(1,2);
     printk(KERN_EMERG"add value:%d\n",vle);
     //模块参数
 
      printk(KERN_EMERG" name : %s\n",name);
      printk(KERN_EMERG" age : %d\n",age);
 
     //使用其他模块的函数(内核符号导出)
     int adds=add_integar(3,1);
     int subs=sub_integar(3,1);
     printk(KERN_EMERG" add_integar : %d\n",adds);
     printk(KERN_EMERG" sub_integar : %d\n",subs);
     return 0;
 }
 
 static void __exit mains_exit(void)
 {
     printk("param exit!");
 }
 
 module_init(mains_init);
 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内核打印:
中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
查看新内核是否已经添加, 而后重启系统并测试。

你可能感兴趣的:(Ubuntu)