标签 GNU 下的文章

也谈Linux Kernel Hacking – 内核配置、编译与安装

Linux Kernel之于C程序员,就好比世界之巅珠穆朗玛之于专业登山客。 — Tony Bai^_^

 
作为到目前为止最为成功的开源项目,Linux Kernel总是散发着无穷的魅力,就好比那珠穆朗玛,让人魂牵梦绕,心潮澎湃并总是想尝试征服。
 
记得2006年初我曾花了些时间研究Linux Kernel,但后来迷失在了Linux Kernel引导阶段,无法自拔,最终选择了"知难而退"。如今,随着我们的产品越来越多地运行在Linux主机上,我又愈发感觉自己对Linux底层了解得不够深入,于是我又一次开启了Linux Kernel Hacking的征程。
 
经过这几年的发展,Linux Kernel变得更加复杂了,版本也从当时的2.6.x演进到今天的3.2.x(3.3正在开发中)。但相应地,Linux Kernel方面的资料也多了许多,这对我的Hacking显然是利好消息,至少目前手头上就有几本"大砖头"可作为参考(Linux Kernel方面的书籍均具有防身之特性^_^)。
 
这次Hacking前先给自己设定了几个目标(也算是想清楚为何要这么做):
* 追溯本源
用Linux内核运转原理解释上层应用的行为并指导上层应用的开发。
 
* 定制优化
在对Linux Kernel有了深入了解之后,尝试定制适合产品特性的Linux内核。
 
* 走进内核开发,尝试提交补丁
对我个人来说,这算是在Linux Kernel领域的终极目标了。每天重复念叨这个目标,就相当于给自己打鸡血了,让自己始终保持兴奋劲儿。
 
以上这些目标显然不是短期内能达成的,饭还得一口一口吃,路还得一步一步走。今天我就迈出这第一步:编译一个属于自己的Linux内核。
 
经过这么多年的发展,Linux Kernel的编译已经简化了许多了,甚至简化到了让我觉得有些吃惊的地步(在我原先的意识中,Linux Kernel的编译是件很复杂的事情^_^)。
 
编译内核是Linux Kernel开发者的基本活动,几乎所有Kernel开发者都是在自己编译的内核上工作的。下面我就详细说明一下Linux Kernel的配置、编译和安装过程。
 
一、准备工作
1、准备一台装有Linux的PC
不建议在Windows或Solaris下编译Linux Kernel,那样只会自找麻烦。Linux Kernel在Linux下编译才是正路(除非你真的要做跨平台交叉编译)。我这里用了一台运行在XenServer 5.6 p2上的装有Red Hat Enterprise Linux(RHEL) 5.5的虚拟机。在该虚拟机上执行'uname -r',可以得到当前Linux内核版本信息为:2.6.18-194.el5xen。
 
2、获取内核源码包
Linux Kernel,特别是之前发布的稳定版内核,几乎都可以100%的顺利通过编译。为了能与手头资料"兼容",我选择了2.6.28版本内核。Linux Kernel的发布版本可在http://www.kernel.org/pub/linux/kernel下找到,这里执行下面命令获取源码:
 
wget -c http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.28.tar.gz
 
下载后的源码包无需放在系统目录(/usr/src/linux)下,在你自己的普通权限用户下建立一个临时目录存放源码包即可,比如我们在/home/tonybai下建立linux-kernel目录,将下载的linux-2.6.28.tar.gz放入该目录中,解压后(tar xvzf linux-2.6.28.tar.gz),我们会看到:
 
/home/tonybai/linux-kernel$ ls 
linux-2.6.28/  linux-2.6.28.tar.gz
 
3、检查编译内核所依赖的工具及版本是否满足要求
在linux-2.6.28/Documentation/Changes中有该版本内核编译所依赖的工具以及最低版本信息列表,需确认一下当前主机上是否安装了这些工具,版本是否满足最低要求。通过linux-2.6.28/scripts/ver_linux可以快速获取当前主机上各个工具以及当前版本的信息,可将这些信息与编译该内核的最低版本比对,以确定是否需要安装或升级工具版本。
 
二、配置内核
Linux Kernel的编译有些类似于那些使用autotools创建构建脚本的开源包,需要先Configure,然后make和make install。不同的是Linux Kernel的"Configure"要稍显"复杂",毕竟与普通开源包相比,Linux Kernel算得上是一个庞然大物了。不过Linux Kernel的开发者们显然在这方面也做了很多工作,通过提供各种命令和默认配置来简化配置过程,下面是常用的几个配置命令。
 
* make config
这个是最基本的配置命令,同时也是配置过程最复杂、耗时最长的配置命令。该命令会将Linux Kernel所有配置项逐一在控制台窗口输出,并让你作出yes、no或是module的选择。我查看了一下RHEL 5.5的配置项个数,总共有2300多项,想必这个过程下来,你已经筋疲力尽了。所以除了某些特殊情况,我们是不会使用这个命令的。该命令会在linux-2.6.28目录下面创建一个.config隐藏文件,该文件存储了你的配置选择,类似这样:
 
# .config
#
# Automatically generated make config: don't edit
# Linux kernel version: 2.6.28
# Wed Mar 14 17:13:23 2012
#
# CONFIG_64BIT is not set
CONFIG_X86_32=y
# CONFIG_X86_64 is not set
CONFIG_X86=y
CONFIG_ARCH_DEFCONFIG="arch/x86/configs/i386_defconfig"
CONFIG_GENERIC_TIME=y
… …
 
* make defconfig 
一个一个选择配置太累,内核开发者显然也不原意这样做,因此内核提供了另外一个命令make defconfig。这个命令会为你生成一份默认的.config文件,而整个过横无需你作出任何选择。而实际上该命令是直接将arch/x86/configs/i386_defconfig或x86_64_defconfig(以x86平台为例)拷贝为.config放在linux-2.6.28下面。
 
* make menuconfig
虽然有了默认配置,但开发者总是有修改配置的需求。内核提供了make menuconfig命令,允许开发者以图形界面(基于ncurses)的形式修改特定的配置项。根据大家的喜好不同,内核还提供了基于gtk+图形界面的make gconfig和基于X11图形界面的make xconfig来修改配置项,这两个命令在功用上与make menuconfig是等同的。
 
另外还有一种方法配置内核,那就是直接使用Linux发行版自带的.config或其他开发者的.config来配置你的内核。如果你是第一次配置内核,建议直接使用所在主机的Linux的.config。我所用的Linux的.config文件在/usr/src/kernels/2.6.18-194.el5-xen-x86_64下面。不过由于我下载的Kernel版本是2.6.28,与该.config不匹配,所以还需执行'make
oldconfig'命令来更新配置。该命令会保留.config已有的配置项的值,而对于新Kernel版本引入的新配置项提供交互式的选择。我用的就是这种方法:
 
$ make oldconfig
scripts/kconfig/conf -o arch/x86/Kconfig
#
# configuration written to .config
#
 
三、编译内核
配置好内核后,我们就可以执行内核编译了,和上层应用一样,只需一个Make就好。
 
$ make
… ..
  CC      arch/x86/boot/tty.o
  CC      arch/x86/boot/video.o
  CC      arch/x86/boot/video-mode.o
  CC      arch/x86/boot/version.o
  CC      arch/x86/boot/video-vga.o
  CC      arch/x86/boot/video-vesa.o
  CC      arch/x86/boot/video-bios.o
  LD      arch/x86/boot/setup.elf
  OBJCOPY arch/x86/boot/setup.bin
  OBJCOPY arch/x86/boot/vmlinux.bin
  HOSTCC  arch/x86/boot/tools/build
  BUILD   arch/x86/boot/bzImage
Root device is (8, 1)
Setup is 10988 bytes (padded to 11264 bytes).
System is 3561 kB
CRC f4d6ad54
Kernel: arch/x86/boot/bzImage is ready  (#1)
  Building modules, stage 2.
  MODPOST 3 modules
  CC      arch/x86/kernel/test_nx.mod.o
  LD [M]  arch/x86/kernel/test_nx.ko
  CC      drivers/hid/hid-dummy.mod.o
  LD [M]  drivers/hid/hid-dummy.ko
  CC      drivers/scsi/scsi_wait_scan.mod.o
  LD [M]  drivers/scsi/scsi_wait_scan.ko
 
整个编译过程(非跨平台交叉编译,只是本地编译)大约20多分钟,编译成功后,我们得到了许多新文件,其中重要的文件有:
 
linux-2.6.28/vmlinux
linux-2.6.28/System.map
linux-2.6.28/arch/x86/boot/bzImage
 
其中bzImage就是我们编译好的可引导的、压缩的Linux内核映像文件。而System.map则是内核符号表文件,vmlinux是未经压缩的内核文件。
 
四、安装内核
安装内核与配置、编译内核不同,它需要root权限。切换到root后,我们首先需要安装的是内核模块,内核模块将会被安装到/lib/modules下面:
 
$make modules_install
… …
$ls -l /lib/modules/
总计 8
drwxr-xr-x 6 root root 4096 11-17 15:14 2.6.18-194.el5xen/
drwxr-xr-x 3 root root 4096 03-14 08:58 2.6.28
 
接下来就可以安装内核了。不过在安装之前,我们先看看当前系统内核文件是什么样子、Grub的配置又是怎样的:
 
$ ls -l /boot
-rw-r–r– 1 root root   66548 2010-03-17 config-2.6.18-194.el5xen
-rw——- 1 root root 3397337 11-17 15:14 initrd-2.6.18-194.el5xen.img
-rw-r–r– 1 root root 1208685 2010-03-17 System.map-2.6.18-194.el5xen
-rw-r–r– 1 root root 2047518 2010-03-17 vmlinuz-2.6.18-194.el5xen
-rw-r–r– 1 root root  417317 2010-03-17 xen.gz-2.6.18-194.el5
-rwxr-xr-x 1 root root  969808 2010-03-17 xen-syms-2.6.18-194.el5
 
$ vi /boot/grub/grub.conf
#boot=/dev/hda
default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title Red Hat Enterprise Linux Server (2.6.18-194.el5xen)
        root (hd0,0)
        kernel /xen.gz-2.6.18-194.el5 crashkernel=128M@32M
        module /vmlinuz-2.6.18-194.el5xen ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        module /initrd-2.6.18-194.el5xen.img
 
可以看出vmlinuz-*这个文件就是内核映像文件,它其实就是arch/x86/boot/bzImage的拷贝;但我们无法通过直接压缩vmlinux来得到vmlinuz-*,据说vmlinuz在头部放置了gzip的解压代码。
 
我们通过make install进行内核安装:
$ make install
sh /home/tonybai/linux-kernel/linux-2.6.28/arch/x86/boot/install.sh 2.6.28 arch/x86/boot/bzImage System.map "/boot"
 
make install调用的是对应arch下提供的install.sh来安装内核。arch/x86/boot/install.sh检测系统中是否安装了installkernel脚本,如果有则调用installkernel工具安装内核,否则进行默认安装。至少在Red Hat的发行版上我们是可以找到installkernel这个脚本的。installkernel除了将bzImage和System.map安装到/boot下之外,还调用了/sbin/new-kernel-pkg制作了initrd-2.6.28.img,并修改了grub.conf(使用grubby配置grub)的内容:
 
$ ls -l /boot
-rw——- 1 root root 3369458 03-14 08:59 initrd-2.6.28.img
lrwxrwxrwx 1 root root      33 03-14 10:41 System.map -> /boot/System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:58 System.map-2.6.28
lrwxrwxrwx 1 root root      30 03-14 10:41 vmlinuz -> /boot/vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:58 vmlinuz-2.6.28
 
$ vi /boot/grub/grub.conf
default=1
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title Red Hat Enterprise Linux Server (2.6.28)
        root (hd0,0)
        kernel /vmlinuz-2.6.28 ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        initrd /initrd-2.6.28.img
title Red Hat Enterprise Linux Server (2.6.18-194.el5xen)
        root (hd0,0)
        kernel /xen.gz-2.6.18-194.el5 crashkernel=128M@32M
        module /vmlinuz-2.6.18-194.el5xen ro root=/dev/VolGroup00/LogVol00 rhgb quiet
        module /initrd-2.6.18-194.el5xen.img
 
make install虽然对grub.conf进行了修改,但默认引导的内核依旧是原先的内核,我们需要手工将default改为0来引导我们新编译的2.6.28内核。
 
五、引导新内核
安装了新内核后,我们尝试使用新内核引导启动。执行Reboot后,新内核引导一切顺利。用'uname -r'查看结果如下:
 
$ uname -r
2.6.28
 
至此,我们成功用上了自己编译的内核(后续应该会有关于内核引导阶段的详细Hacking描述^_^)。
 
六、升级内核
升级内核是内核开发者的日常活动之一。当有其他开发者发布新补丁或自己在现有内核上做了修改后,都会重新配置、编译和安装内核,也就是升级内核。
 
升级内核一般按如下如下命令序列执行:
$ make oldconfig
$ make
$ make install(如果有kernel module更新,应该先执行make modules_install)
 
对于版本号不变的内核重新执行install,我们会在/boot下看到如下内容:
$ ls -l /boot
-rw——- 1 root root 3369458 03-14 08:59 initrd-2.6.28.img
lrwxrwxrwx 1 root root      33 03-14 10:41 System.map -> /boot/System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:58 System.map-2.6.28
-rw-r–r– 1 root root 1397880 03-14 08:51 System.map-2.6.28.old
lrwxrwxrwx 1 root root      30 03-14 10:41 vmlinuz -> /boot/vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:58 vmlinuz-2.6.28
-rw-r–r– 1 root root 2080528 03-14 08:51 vmlinuz-2.6.28.old
 
安装脚本会将上一次安装的2.6.28内核改名为2.6.28.old,然后将新内核安装到/boot下。grub.conf内容没有被修改。再次反复执行install,安装脚本始终会将老内核改名为.old,然后保证最新同版本内核被安装。
 
七、定义自己的个性化内核版本号
XenServer下的Rhel 5.5的内核版本号为2.6.18-194.el5xen,Ubuntu 10.04下的内核版本号为2.6.32-30-generic(通过uname -r查看),我们如何定义一个属于自己的个性化内核版本号呢?其实很简单,修改顶层Makefile即可。
 
# Makefile 
 
VERSION = 2
PATCHLEVEL = 6
SUBLEVEL = 28
EXTRAVERSION = -tonybai-dev
 
一个Kernel的版本号KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION),因此我们可通过修改EXTRAVERSION的内容来定义一个个性化的版本号,就像上面代码中的那样。
 
修改Makefile后,执行make clean; make ;make modules_install; make install即可。执行后,你就会在/boot下面、/lib/modules下面以及grub.conf里面看到2.6.28-tonybai-dev这个版本的内核信息了。修改grub.conf使之默认引导2.6.28-tonybai-dev这个新内核,重新引导后,你执行'uname -r'的结果就会变成'2.6.28-tonybai-dev'了。
 
至此,内核配置、编译与安装的部分就暂时告一段落了。在这个过程中,我参考了许多资料,这其中包括:
 
相信后续的Hacking过程中,这些资料还将会发挥至关重要的作用。

C语言编码风格和标准

近期在为产品线的知识库编写一些指南类的文档,其中有一项就是对现有的C语言编码规范进行一些修订。为了"有米下锅",我还特意在网上找了一些相关资料。关于C语言编码风格和标准的资料大多都成稿于上个世纪90年代,也就是在C90发布之后的若干年里;在C99发布后,部分资料根据最新的规范做了修订,但也有些资料认为C99对整体风格影响不大,也就保持了原样。

 
在这些资料中,我重点关注了一下这份文档《Recommended C Style and Coding Standards》,它是著名的"Indian Hill C Style and Coding Standards"的更新版,从Google的搜索结果来看,似乎影响很广。这份文档内容不多,言简意赅,特别是后面的几个小节,例如宏、条件编译、可移植性以及ANSI C等章节很值得细致阅读和理解。
 
我试图google该文档的中文版,居然没有找到。也许是这个文档比较老了,或者是其中有些注意事项在当今C编程领域较少能遇到了,再或许就是C语言老了,关注的人少了,总而言之,网上没有该文档的中文版。于是乎我就花了一些时间翻译了一个粗糙的中文版,供那些看E文和我一样吃力的朋友们参考。中文版以Wiki的形式放在了Google code(http://code.google.com/p/recommended-c-style-and-coding-standards-cn/)上了。这里需要先说明的是:翻译过程不是很细致,较随意,有些地方我理解得也不慎透彻,欢迎大家提出自己的见解,后续有时间还会持续地修订。
 
这里提供一个快捷入口^_^:
 
12. 常量
13.
15. 调试
19. Lint
20. Make
22. 结论

为buildc添加安装包制作相关功能

在"也谈C应用安装包制作与部署"一文中,我提到了为每一个源码工程建立单独的安装包制作工程(setup project)的想法,这两天我就一直在折腾这件事儿^_^。

最初我并没有想去搞一个通用的安装包制作工具,只是为一个现有的源码工程建立了一个试验性质的安装包工程,并实现了其构建脚本(build.py)。但之后考虑到各个项目都要建立一个对应的安装包工程,安装包工程的构建脚本build.py势必会沦落成被copy来copy去的下场,这显然不是一个很好的解决问题的办法。那是否需要再单独设计和实现一个安装包制作工具呢?工具多了,大家用起来肯定会很烦,不能自找没趣^_^。要知道为程序员编写工具可是一件很困难、很头疼,需要你很谨慎的事情。现在我们已经有了源码工程构建工具buildc,我前几天还为buildc添加了安装脚本,并用之改造了一个真实的工程,并给大家做了讲解,可以说大家对buildc算是接受了。

那把安装包制作功能集成到buildc中呢?一个大胆的想法油然而生。这样做似乎扩展了原buildc的设计意图:不仅可以做源码工程的构建辅助工具,还可以用于辅助生成安装包制作工程并制作安装包,并且这样做也最大限度迎合了组织内部大家的需求,避免了日后被狠狠地拍板砖^_^。

之前的试验安装包工程的构建脚本已经实现,现在只需将其功能移植到buildc中即可。在"也谈C应用安装包制作与部署"一文中我提到了一个示例安装包工程的组织方式,其实这里的buildc所实现的安装包制作功能也是以这个安装包工程的组织方式作为前提的,不过这里又略做了些改动,示例如下:

setup_project/
      – setup.cfg
      – distributions/
         – cn-foo-2.14.0.0-x86-linux-64bit.tar.gz
      – src/
         – setup.py*
         – README
         – app/
         – env/
           – conf/
           – log/
           – bin/
           …
         – deps/
           – libs/
           – tools/
         – scripts/
           – deps_check.sh

         – others/

也就是说,buildc生成的C应用安装包工程目录组织方式就是这样的;反过来说只有这个模样的安装包工程才能使用buildc进行安装包构建。我为buildc添加了一个Command "pack"用于安装包工程相关操作,这个命令的使用方式如下:

一、安装包工程的创建
在任意路径下执行buildc pack create –project=YOUR_SETUP_PROJECT_NAME,你即可在当前目录下看到一个empty安装包工程:

$> buildc pack create –project=foo_setup
Setup project [foo_setup] create OK!
$> cd foo_setup
$> ls
distributions/  setup.cfg  src/
$> cd src; ls
$> app/  deps/  env/  others/  README  scripts/  setup.py*

二、配置安装包工程
在生成的安装包工程下面有一个配置文件setup.cfg,在使用buildc进行安装包构建之前,务必先进行该文件的配置,其内容示例如下:

distribution = {
  "packname"    : "cn-foo",
  "version"     : "2.14.0.1",
}

source = {
  "trunk"          : "svn://10.10.15.56:4444/cn/trunk/foo",
  "binary_prefix"  : "cn-foo"
}

其中:
distribution['packname'] – 最终安装包的名字前缀
distribution['version'] – 最终安装包的版本号

source['trunk'] – 该安装包工程所对应的源码工程的svn url
source['binary_prefix'] – 源码工程构建后所生成的二进制可执行文件的名字前缀

三、构建安装包
在已经配置好的安装包工程中,使用buildc pack build命令即可以进行安装包的制作,例如:

$> buildc pack build
Clean [.build] OK!
Clean [.package] OK!
Clean [./src/app] OK!
Clean [./distributions] OK!
Package distribution clean OK!
Create dir [.build] OK!
Export [svn://10.10.15.56:4444/cn/trunk/foo] OK!
Cd /home/tonybai/proj/foo_setup/.build/foo
Config Make.rules OK!
Make Ok!
Copy binary file to [/home/tonybai/proj/foo_setup/src/app] Ok!
Cd /home/tonybai/proj/foo_setup
Del [.build] OK!
Build source [svn://10.10.15.56:4444/cn/trunk/foo] OK!
Create dir [.package] OK!
Cd /home/tonybai/proj/foo_setup/.package
Generate cn-foo-2.14.0.1-x86-linux-64bit.tar OK!
Zip cn-foo-2.14.0.1-x86-linux-64bit.tar OK!
Cd /home/tonybai/proj/foo_setup
Del [.package] OK!
Make target [cn-foo-2.14.0.1-x86-linux-64bit.tar.gz] OK!

构建成功后,你会在distributions目录下看到最终的安装包。如果在buildc命令行中没有指定–tag=YOUR_SOURCE_TAG,buildc会使用setup.cfg中source['trunk']中的配置检出trunk代码并构建;如果指定了SOURCE TAG,那么buildc就会使用tag中提供的source svn url检出代码并构建,例如下面的命令将检出foo-2.14.0.2标签的代码并构建可执行程序:

$> bulidc pack build –tag=svn://10.10.15.56:4444/cn/tags/foo-2.14.0.2

四、清理安装包工程
在工程目录下,使用buildc pack clean命令可以对安装包工程进行清理:

$> buildc pack clean
Clean [.build] OK!
Clean [.package] OK!
Clean [./src/app] OK!
Clean [./distributions] OK!
Package distribution clean OK!

五、上传安装包文件
在制作完安装包后,我们一般会将其上传到一个指定的发布服务器上去。buildc提供了上传安装包的功能。使用buildc pack upload –host=HOST –user=USERNAME –passwd=PASSWD –dir=REMOTEDIR –port=FTP_PORT命令我们可以将构建完毕的安装包文件上传到远程服务器上面,例如:

$> buildc pack upload –host=10.10.1.191 –user=tony –passwd=tony –dir=dist
Cd distributions
Upload [cn-foo-2.14.0.0-x86-linux-64bit.tar.gz] OK!

BTW,在编写和使用buildc的过程中,我真实地体会到了用脚本语言源文件作为配置文件的强大,与典型的.ini或.xml等类型配置文件相比,其灵活性过之尤甚,特别是在配置文件中嵌入一些代码就可以改变配置行为,并且脚本语言提供的丰富且强大的数据结构可以充分满足你对配置文件数据组织的需求。




这里是Tony Bai的个人Blog,欢迎访问、订阅和留言!订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:


如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:


以太币:


如果您喜欢通过微信App浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:



本站Powered by Digital Ocean VPS。

选择Digital Ocean VPS主机,即可获得10美元现金充值,可免费使用两个月哟!

著名主机提供商Linode 10$优惠码:linode10,在这里注册即可免费获得。

阿里云推荐码:1WFZ0V立享9折!

View Tony Bai's profile on LinkedIn


文章

评论

  • 正在加载...

分类

标签

归档











更多