Chinaunix首页 | 论坛 | 博客
  • 博客访问: 566872
  • 博文数量: 287
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 547
  • 用 户 组: 普通用户
  • 注册时间: 2012-01-01 20:20
文章分类

全部博文(287)

文章存档

2015年(1)

2014年(95)

2013年(90)

2012年(101)

分类: LINUX

2013-08-23 09:07:36

        这里不只是讲怎样编译、安装linux内核的,更主要的是介绍内核的编译系统和各个重要的文件。最后还利用学到的编译、安装linux内核去修改linux的01调度变成随机调度。如果你只是需要编译、安装内核的几条指令,那么翻到文章中后部分吧。如果有哪里写错或者写得不太清楚的请指正。谢谢你的阅读!
        内核嘛,就是linux的核心,如果你用linux只是为了听听歌,看看电影,上上网,那么我想linux是不太适合你的。但能打开这篇文章,那么你应该就是对linux有兴趣了。这里我们一起编译下这个世界上IT里最完美的艺术品!----linux内核
      译前先来看看linux内核的源码目录结构。有助于认识linux内核,熟悉了她,就不会再有恐惧与无助的感觉。
       内核源码录目结构

               1)    Documentation     这里没有代码,有的只是一些各种各样文档,但可以给我们足够多的帮助。

               2)    arch     所有与体系结构有关的源代码都在这里,还有在include/asm-*/目录里。所支持的体系结构都在arch目录下有对应的子目录,而且最少都包含3个子目录。
                        kernel:支持体系结构特有的如信号处理、SMP等的实现。
                        lib:体系结构特有的对strlen、memcpy之类的通用函数的实现。
                        mm:很明显啦,这个是体系结构相关的内存管理的实现。
                        大多数的子目录都包含boot这个子目录,在硬件平台上启动内核的所使用的部分或全部代码。

               3)    drivers    这里有显卡、scsi适配器pic总线、usb总线和其他的linux支持的外围设备和总线的驱动程序。是内核中最大的一个目录。

               4)    fs    文件系统。这里有VFS、各个不同文件系统的代码都在这里。

               5)    include     包含了内核中大部分的头文件。

               6)    ipc    进程间通信,包含了信号量、共享内存和其他形式的ipc的代码。

               7)    kernel    包括了进程的调度、创建、撤销和平台相关的的另一部分的核心代码。是内核最核心的部分。

               8)    init    内核初始化部分的代码。包括main.c及创建早期用户空间的代码等。

               9)    lib    库代码

               10)    mm    与体系结构无关的内存管理部分的代码。

               11)    net    网络部分的实现代码,常见的协议如TCP/IP、IPX。

               12)    scripts    这里没有代码,只有一些用来配置内核的脚本文件。当我们编译内核的时候,运行make menuconfig 之类的命令时我们就是与这个目录下的脚本在交互。

               13)    block    block层的实现。

               14)    security    linux安全模型的代码。

               15)    crypto。    内核本身的加密API,实现了常用的加密算法和散列算法,和一些压缩、CRC校验算法。

               16)    sound    声卡驱动及其他声音相关的代码。

               17)    usr     用于打包的与压缩的cpio等。
               各个文件如图:

                        

     到这里,当你打开linux源代码时就不会再
觉得那么无助了。下面我们继续。

     下面介绍几个重要文件
               1)    vmlinuz  内核引导文件
                        vmlinuz是可引导的压缩内核,“vm”代表“Virtual Memory”。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz不是可执行 的Linux内核(网上说是可以执行的内核,可能有误。因为是压缩的,要执行必须解压。望大神指教!),因此在启动阶段首要的工作就是自解压内核映像,它位于/boot/vmlinuz,它一般是一个软链接。zImage(vmlinuz,小内核小于512kb)和bzImage(vmlinuz,大内核大于512kb)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip –dc解包vmlinuz。内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。

                   2)    vmlinux
                            vmlinuz 是vmlinux的压缩版。
                            vmlinuz结构如图:
                      
                    3)     initrd.img
                            initrd.img,即"initrd RAM disk",是一个小的映象,包含一个最小的linux系统。通常的步骤是先启动内核,对vmlinuz内核文件解压后但在真正根文件系统启动前,initrd.img文件会被加载到内存中。内核挂载initrd.img,并执行里面的脚本来进一步挂载各种各样的模块,然后发现真正的root分区,挂载并执行/sbin/init。如果没有initrd.img,那么内核就试图直接挂载root分区。
            linux的根文件系统可以存储在很多的介质上,如SCSI、IDE、USB等,如果将这些驱动都编译进了内核,那么内核将会变得非常臃肿、庞大啊!所以linux的kernel只保留了最基本的启动代码,而把各种的硬件设备的支持以模块的形式放在了initrd.img中。这样的好处是在启动的过程中可以从initrd所挂载的根文件系统中装载所需要的模块,从而可以在kernnel不变的情况下,修改initrd的内容达到灵活地支持不同的硬件。在启动完成的最后阶段,根文件系统重新挂载到其他设备上去。
            举个例子,你的硬盘是SCSI接口但你的内核又不支持这种接口,你的内核就没有办法访问硬盘,也就没法加载硬盘上的文件系统,这个怎么办呢?? initrd.img是个ram disk的映像文件。ram disk是占用一部分的内存模拟成磁盘,让我们的操作系统访问。ram disk是标准内核文件认识的设备(/dev/ram0)文件系统也是标准内核认识的文件系统。内核加载这个ram disk作为根文件系统并开始执行其中的某个文件--init(2.6以上的内核是 init文件,位于/sbin/)来加载各种模块,服务等。经过一些配置和运行后,就可以去物理磁盘加载真正的root分区了,然后又是一些配置等,最后启动成功。
            所以initrd.img的作用就是将一些驱动程序和命令工具打包到img里从而简化内核,这完全符合linux的设计思想和linux的哲学思想啊!

                        4)     system.map,内核符号表,位于/boot/System.map 。当你编译一个新的内核的时候,内核的各个符号的地址就会变化,旧的内核符号表的信息对于新的内核来说是错误的,如果还用旧的内核符号表就会出错,所以会产生一个新的内核符号表即system.map
        下面是其他文件或者目录,也是挺重要的
                        /lib/modules/    包括自己编译的和系统自带的内核模块,以及其他文件
                        /lib/modules//build/    存放编译新模块需要的文件。有Makefile、.config、modules.symVers以及内核头文件。
                        /lib/modules//kernel/    存放模块ko文件
                        /lib/modules//modules.alias    模块的别名定义,模块加载工具使用其来加载相应的模块。
                        /lib/modules//
modules.dep    定义了模块间依赖的关系。
                        /lib/modules//modules.symbols    标识符号属于哪个模块。

        内核的配置系统与机制:主要由3个文件来控制,一个是Makefile,一个是.config,一个是kconfig。Makefile分布在内核源代码的根录目和各层子目录中,规定了内核是怎样编译的。配置文件.config是在配置后产生的文件,记录了配置的结果。而kconfig是产生配置界面要用的文件。配置时在这里读取选项。
        make menuconfig 的过程
                        1) 前面提过scripts还记得吗?它是用来存放与make menuconfig有关的界面绘图文件的。
                        2) 当我们执行make menuconfig时系统会在arch/$(ARCH)/目录下的读取kconfig文件,生成界面的配置选项。而ARCH是什么呢?它由根目录下的Makefile文件决定的。Makefile里有这个环境变量的定义:

                                    
                        3) kconfig为我们生成可选的配置选项,但不免有些人不会配置,所以在arch/$(ARCH)/configs文件夹下为我们准备了默认的配置文件,这里有很多的选项,系统会选那个呢?其实内核会默认读取根目录的.config文件作为默认的选项。
                                                    
                        4) .config 对于不同的内核,我们选择的选项会不同。我们在配置界面上通过空格键选择或者不选择某个选项,最后在退出时我们的配置会记录在.config里。到这里我们的配置过程已经完成了。但我们的配置怎样跟编译联系起来呢?不急,我们继续!
                        5) 配置保存在.config里的同时系统会将所有的选项以宏的形式保存在include/generated/autoconf.h文件下。编译时就会根据这些宏来进行。
                        到这里编译机制基本讲完了。下面回头看看vmlinux这个文件。。。。。

            vmlinux的框架:vmlinux的重要性前面已经讲过了。linux的内核的编译系统是非常复杂的,这里只能简单地看看vmlinux是怎么来的。如图,首先内核会编译出框框中的五大组建,这五大组建又分成很多小的组建,最终这五大组建会链接成vmlinux。对于kallsyms.o其记录了内核非栈变量的地址,包括了变量和函数,而且其涉及到最后链接得到vmlinux。
            kallsyms.o模块的编译过程(在网上看到的文章,学了在这里写出来!这里写得比较简单):
                                      1) 编译器首先会将内核绝大部分的组件链接成.tmp_vmlinux1文件。
                                      2) 命令nm将.tmp_vmlinux1中的符号和对应的地址导出来,并使用kallsyms工具生成tmp_kallsyms1.S的文件。
                                      3) 对.tmp_kallsyms1.S进行编译,生成.tmp_kallsyms1.o文件
                                      4) 重复1)的链接过程,将3)得到的.tmp_kallsyms1.o链接进内核,得到.tmp_vmlinux2文件。
                                      5) 与2差不多,命令nm将.tmp_vmlinux2中的符号和对应的地址导出来,并使用kallsyms工具生成tmp_kallsyms2.S的文件。
                                      6) 对.tmp_kallsyms2.S进行编译,生成.tmp_kallsyms2.o文件
                                      7)将.tmp_kallsyms2.o作为kallsyms模块链接入内核形成vmlinux。

                                     vmlinux的框架图:

        
            下面动手编译内核!实验来了。。。。。。
            编译内核实验过程
                        先列出需要的口令和要安装的软件吧。先到kernel.org  具体地址 下载需要安装的内核,解压在主文件夹。以2.6.24来说。打开终端(ubuntu的快捷键是: ctrl +Alt+ t)。
                        口令:
                                    1) cd linux-2.6.24    进入目录
                                    2) make menconfig 配置内核选项
                                    3) sudo make  编译内核 如果你的计算机是多核的可以用 sudo make -jN (N为你计算机的核数)
                                    4) sudo make modules 编译模块。
                                    5) sudo make modules_install 安装模块
                                    6) sudo make install 安装内核
                        要安装的软件: 1) sudo apt-get install libncurses5-dev   2) sudo apt-get install modutils   3) sudo apt-get install kernel-package   4) sudo apt-get install build-essential
                                                libncurses5-dev--->在make menuconfig 时需要用到ncurses库的支持。
                                                modutils---->模块工具。
                                                kernel-package---->包括make-kpkg等工具
                                                build-essential----->提供c/c++编译环境,有gcc、make等。
                        现在对各条口令进行解释!
                                    1) 不用多说了吧。。。用过命令行的都知道的。
                                    2)  在这里会出现配置内核的界面,进行配置内核。这里为了方便,我只是找一个旧的内核配置文件,----当前系统的.config。在/lib/modules//built 目录里,打开这个目录,细心的会发现这是个链接文件,实际路径是/usr/src/linux-headers-version-generic文件夹。将.config复制到要编译的内核的根目录下。之后执行make menuconfig 在配置界面里先选定倒数第二个,按保存,之后选定最后一个保存配置,退出即可。如果要手动配置,那么选项可以是“*”、"M"、数字或者不选。使用空格可以进行选择,enter键可以进入子选项。“*”是使用这个选项,而空格就不选,“M”是这个选项作为模块进行编译。如果是数字就看具体的选项吧,一般是对大小的选择。对于每一个选项的意思这里很难作说明!!太多了啊!!!!!!如果你要在3.8的内核上编译较低版本的内核的话,那么就要手动配置了。。我试过在ubuntu 3.8的内核上编译成功2.6.39的内核,但2.6.20就不行了。而且虽然是成功了但桌面的背景显示就有点问题了。。。。所以不太建议在在高版本的内核下编译过低版本的内核。还有一个就是配置文件.config是一个隐藏文件,而可以手动修改的。。打开了你就知道该怎么去修改啦。。。。
       
                                3) 对内核进行编译。跟平时的编译程序没什么不同的,只是内核比较大,编译的时间会比较长,如果i5的机也要20分钟多吧。。。没有认真计算过。。
                                4) 编译模块。没有什么好说的。
                                5) 安装模块,到这里所有步骤都成功后,系统会在/lib/modules/目录下生成一个2.6.24子目录,里面存放着新内核的所有可加载模块。
                                6) 安装内核。make install主要完成3个工作。
                                            1) 复制生成的内核映像到/boot/目录。
                                            2) 生成initrd-.img 文件。
                                            3) 配置引导程序(grub or LILO),系统为我们自动配置了!!!!
                重新启动,进入新的内核。其实在这里,新的内核是默认启动的。。因为在配置引导程序的时候应该总是把新内核设置为第一启动项。在/boot/grub/grub.cfg 查看吧。。重新启动计算机,打开终端输入 uname -r 查看内核的版本。。如果是你编译的那个,那恭喜你。。。成功了。。。。虽说成功了但很无趣啊。。。
                下面来点有趣的,对内核进行修改。前面说过不要在高版本的内核上编译低版本的内核。但这里我需要在2.6.22进行实验。这样需要一个旧点的系统,可以去下载个内核比较旧点的系统,cenos6.4是2.6.32的内核,可以试试。下载2.6.22内核。这里我们对内核的调度进行操作,之所以用交低版本的内核是因为在2.6.23开始内核的调度部分引入了cfs,01调度(我喜欢这样叫,其实是O(1)调度)开始退出历史舞台。因为调度对linux的重要性不言而喻,而且01调度的诞生对linux调度历史来说是个里程碑式的纪念,我一开始接触内核就是学习01调度开始的,所以就拿01调度来做实验。因为还没有对cfs吃透所以用2.6.22对调度部分进行修改。好吧开始了。
                修改内核首先要找到要修改的地方,由于我们要修改调度算法所以我们在linux-2.6.22/kernel/sched.c 下找到asmlinkage void __sched schedule(void) 函数(大约在中间)找到如图的代码:
                                

                 之后在idx = sched_find_first_bit(array->bitmap);后面加入以下代码:

点击(此处)折叠或打开

  1. if(idx>=MAX_RT_PRIO){
  2.         int seed = jiffies;//产生随机数,如果你想使用随机函数get_random_bytes()是不行的,因为其通过/dev/random 设备产生随机数的。
  3.         int mod = MAX_PRIO - MAX_RT_PRIO;
  4.         seed = (seed+7) % mod;
  5.         while(!test_bit(MAX_RT_PRIO+seed, (void*) &array->bitmap)){
  6.             seed = (seed+7) % mod;        
  7.         }
  8.         idx=MAX_RT_PRIO + seed;
  9.     }
                如图:
                                
                保存,编译安装内核后重启。。。。。看看什么情况?
             好了文章到这里已经完成了!谢谢你的阅读!



































阅读(440) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~