几个重要的RedHat Linux内核文件介绍

在网络中,不少服务器采用的是Linux系统。为了进一步提高服务器的性能,可能需要根据特定的硬件及需求重新编译Linux内核。编译Linux内核,需要根据规定的步骤进行,编译内核过程中涉及到几个重要的文件。比如对于RedHat Linux,在/boot目录下有一些与Linux内核有关的文件,进入/boot执行:ls –l,如图所示。编译过RedHat Linux内核的人对其中的System.map 、vmlinuz、initrd-2.4.7-10.img印象可能比较深刻,因为编译内核过程中涉及到这些文件的建立等操作。那么这几个文件是怎么产生的?又有什么作用呢?本文对此做些介绍。

一、vmlinuz
vmlinuz是可引导的、压缩的内核。“vm”代表 “Virtual Memory”。Linux 支持虚拟内存,不像老的操作系统比如DOS有640KB内存的限制。Linux能够使用硬盘空间作为虚拟内存,因此得名“vm”。vmlinuz是可执行的Linux内核,它位于/boot/vmlinuz,它一般是一个软链接,比如图中是vmlinuz-2.4.7-10的软链接。
vmlinuz的建立有两种方式。一是编译内核时通过“make zImage”创建,然后通过:
“cp /usr/src/linux-2.4/arch/i386/linux/boot/zImage /boot/vmlinuz”产生。zImage适用于小内核的情况,它的存在是为了向后的兼容性。二是内核编译时通过命令make bzImage创建,然后通过:“cp /usr/src/linux-2.4/arch/i386/linux/boot/bzImage /boot/vmlinuz”产生。bzImage是压缩的内核映像,需要注意,bzImage不是用bzip2压缩的,bzImage中的bz容易引起误解,bz表示“big zImage”。 bzImage中的b是“big”意思。
zImage(vmlinuz)和bzImage(vmlinuz)都是用gzip压缩的。它们不仅是一个压缩文件,而且在这两个文件的开头部分内嵌有gzip解压缩代码。所以你不能用gunzip 或 gzip –dc解包vmlinuz。
内核文件中包含一个微型的gzip用于解压缩内核并引导它。两者的不同之处在于,老的zImage解压缩内核到低端内存(第一个640K),bzImage 解压缩内核到高端内存(1M以上)。如果内核比较小,那么可以采用zImage 或bzImage之一,两种方式引导的系统运行时是相同的。大的内核采用bzImage,不能采用zImage。
vmlinux是未压缩的内核,vmlinuz是vmlinux的压缩文件。
二、 initrd-x.x.x.img
initrd 是“initial ramdisk”的简写。initrd一般被用来临时的引导硬件到实际内核vmlinuz能够接管并继续引导的状态。图中的initrd-2.4.7- 10.img主要是用于加载ext3等文件系统及scsi设备的驱动。比如,使用的是scsi硬盘,而内核vmlinuz中并没有这个scsi硬件的驱动,那么在装入scsi模块之前,内核不能加载根文件系统,但scsi模块存储在根文件系统的/lib/modules下。为了解决这个问题,可以引导一个能够读实际内核的initrd内核并用initrd修正scsi引导问题。initrd-2.4.7-10.img是用gzip压缩的文件,下面来看一看这个文件的内容,操作步骤如下图所示:

从图中linuxrc这个脚本的内容可以看到,initrd实现加载一些模块和安装文件系统等。
initrd映象文件是使用mkinitrd创建的。mkinitrd实用程序能够创建initrd映象文件。这个命令是RedHat专有的。其它Linux发行版或许有相应的命令。这是个很方便的实用程序。具体情况请看帮助:man mkinitrd

下面的命令创建initrd映象文件:

三、 System.map
System.map是一个特定内核的内核符号表。它是你当前运行的内核的System.map的链接。
内核符号表是怎么创建的呢? System.map是由“nm vmlinux”产生并且不相关的符号被滤出。对于本文中的例子,编译内核时,System.map创建在/usr/src/linux-2.4/System.map。像下面这样:
nm /boot/vmlinux-2.4.7-10 > System.map
下面几行来自/usr/src/linux-2.4/Makefile:
nm vmlinux | grep -v '(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)' | sort > System.map
然后复制到/boot:
cp /usr/src/linux/System.map /boot/System.map-2.4.7-10
下图是System.map文件的一部分:

在进行程序设计时,会命名一些变量名或函数名之类的符号。Linux内核是一个很复杂的代码块,有许许多多的全局符号。
Linux内核不使用符号名,而是通过变量或函数的地址来识别变量或函数名。比如不是使用size_t BytesRead这样的符号,而是像c0343f20这样引用这个变量。
对于使用计算机的人来说,更喜欢使用那些像size_t BytesRead这样的名字,而不喜欢像c0343f20这样的名字。内核主要是用c写的,所以编译器/连接器允许我们编码时使用符号名,当内核运行时使用地址。
然而,在有的情况下,我们需要知道符号的地址,或者需要知道地址对应的符号。这由符号表来完成,符号表是所有符号连同它们的地址的列表。上图就是一个内核符号表,由上图可知变量名checkCPUtype在内核地址c01000a5。
Linux 符号表使用到2个文件:
/proc/ksyms
System.map
下图是/proc/ksyms的一部分。
/proc/ksyms 是一个“proc file”,在内核引导时创建。实际上,它并不真正的是一个文件,它只不过是内核数据的表示,却给人们是一个磁盘文件的假象,这从它的文件大小是0可以看出来。然而,System.map是存在于你的文件系统上的实际文件。当你编译一个新内核时,各个符号名的地址要发生变化,你的老的System.map 具有的是错误的符号信息。每次内核编译时产生一个新的System.map,你应当用新的System.map来取代老的System.map。


虽然内核本身并不真正使用System.map,但其它程序比如klogd, lsof和ps等软件需要一个正确的System.map。如果你使用错误的或没有System.map,klogd的输出将是不可靠的,这对于排除程序故障会带来困难。没有System.map,你可能会面临一些令人烦恼的提示信息。
另外少数驱动需要System.map来解析符号,没有为你当前运行的特定内核创建的System.map它们就不能正常工作。
Linux 的内核日志守护进程klogd为了执行名称-地址解析,klogd需要使用System.map。System.map应当放在使用它的软件能够找到它的地方。执行:man klogd可知,如果没有将System.map作为一个变量的位置给klogd,那么它将按照下面的顺序,在三个地方查找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map
System.map也有版本信息,klogd能够智能地查找正确的映象(map)文件。

 

 

这应该是一个很基本的内核概念,和模块、系统调用等一样基础,但牵涉的东西却一点也不窄,一毫也不浅。(但也不深:-)

【内核符号表,kernel symbol table】

Linux的内核是个单内核monolithic,任一函数都可以访问公共数据结构和函数调用。在设计程序时,需要命名一些函数名、变量名等;同样内核中就含有很多的全局符号。
内核不是人脑皮层,要使用变量和函数-地址(指针)-来访问对应的变量和函数。
内核符号表就是为程序员通过符号来访问程序体的对应地址(指针),建立了一个动态的,可变更的映射表格。

一个符号表例子:

  c03441a0 b dmi_broken
   c03441a4 b is_sony_vaio_laptop
  

可以看出变量dmi_broken位于内核地址c03441a0处。

这和gdb的按图索骥的功能很相似,不同的是内核采用文件为载体的形式。

【/proc/ksyms】

ksyms是内核数据映像文件,在内核引导时创建,其实就是内核数据(/proc文件系统的特性及详解文末),没有实际大小的。

ksyms中的每一个表项代表着一个全局内核符号。这些符号可被LKM引用的,即可以看出LKM可以调用哪些函数(这里有一个安全问题)


【System.map】

位于/或者/boot、/usr/src/linux/下

在每次重新编译内核时,各符号名及其对应的地址指针将有所变化。(有变的,也有不变的)。所以系统需要自行更新此文件。

(关于System.map和系统出错的关系,需要额外说明)。

System.map文件作为特定内核的内核符号表,其链接了系统所使用的System.map。

一般的创建步骤:
当编译后生成内核vmlinux-2.X.Y后,存于/usr/src/linux/下,这时编译脚本将运行“nm /usr/src/linux/vmlinux-2.X.Y > System.map”,并将其拷入/boot下。

具体的生成过程可以从内核编译脚本中得到启示:

/usr/src/linux-2.X.Y/Makefile

[I]
nm vmlinux | grep -v ’(compiled)|(.o$$)|( [aUw] )|(..ng$$)|(LASH[RL]DI)’ | sort > System.map

cp /usr/src/linux/System.map /boot/System.map-2.X.Y

[/I]

注:nm vmlinux的作用是过滤掉其中不需要的符号。


值得注意的是内核本身并不真正使用System.map,但其它程序比如klogd, lsof和ps等软件需要一个正确的System.map。某些与内核头连接而非glibc库的驱动也需要System.map来解析符号(模块加载是与内核版本有关,但与内核版本一致而符号表发生变化的编译后内核无关)。


klogd  内核日志守护进程
为了执行名称-地址解析,klogd需要使用System.map。

man klogd可知,klogd将从一下路径查找System.map:
/boot/System.map
/System.map
/usr/src/linux/System.map

如:
# strace -f /sbin/klogd | grep ’System.map’
    open("/boot/System.map-2.4.18", O_RDONLY|O_LARGEFILE) = 2

# strace lsof 2>&1 1> /dev/null | grep System
   readlink("/proc/22711/fd/4", "/boot/System.map-2.4.18", 4095) = 23

# strace ps 2>&1 1> /dev/null | grep System
   open("/boot/System.map-2.4.18", O_RDONLY|O_NONBLOCK|O_NOCTTY) = 6




【/proc文件系统】
一个伪文件系统,它只存在内存当中,而不占用外存空间。为访问系统内核数据的操作提供接口。有些是动态可变的。
以数字命名的目录是进程目录,以进程的PID号为目录名,而self目录则是读取进程本身的信息接口,是一个link,proc文件系统的由来也因为此。

apm     高级电源管理信息
cmdline  内核命令行
Cpuinfo    关于Cpu信息
Devices    可以用到的设备(块设备/字符设备)
Dma    Used DMS channels
Filesystems 支持的文件系统
Interrupts 中断的使用
Ioports    I/O端口的使用
Kcore    内核核心印象
Kmsg      内核消息
Ksyms    内核符号表
Loadavg      负载均衡
Locks     内核锁
Meminfo      内存信息
Misc    Miscellaneous
Modules    加载模块列表
Mounts    加载的文件系统
Partitions 系统识别的分区表
Rtc    Real time clock
Slabinfo Slab pool info
Stat    全面统计状态表s
Swaps    对换空间的利用情况
Version      内核版本
Uptime     系统正常运行时间



进程目录的结构如下:
目录名称 目录内容
Cmdline    命令行参数
Environ    环境变量值
Fd  一个包含所有文件描述符的目录
Mem    进程的内存被利用情况
Stat    进程状态
Status   Process status in human readable form
Cwd   当前工作目录的链接
Exe   Link to the executable of this process
Maps    内存印象
Statm     进程内存状态信息
Root     链接此进程的root目录

查看系统信息:cat /proc/mem

修改内核参数
/proc/sys不仅提供了内核信息,而且可以通过它修改内核参数。要改变内核的参数,可用Vi编辑或echo +参数重定向到文件中即可:
# echo 8192 > /proc/sys/fs/file-max



【关于ksyms被LKM调用的安全问题】
可以绕过LKM调用的符号表声明


/*定义自己的符号表!!*/

static struct symbol_table module_syms= {

#include <linux/symtab_begin.h>    

/*我们想引用的符号表*/

...                  

};

register_symtab(&module_syms);    

/*实际的注册工作,并不需要对外公开这里的符号,只要用如下的语句*/

register_symtab(NULL);

/*放在init_module()函数中*/



【关于System.map和内核出错Oops的关系】
※内核出错(oops)※

编程最常见的出错情况是段出错(segfault),信号11。

Linux内核中最常见的bug也是段出错。不过当内核引用了一个无效指针时,并不称其为段出错 -- 而被称为"oops"。一个oops表明内核存在一个bug。

当出现一个oops时,并不意味着内核肯定处于不稳定的状态;一个oops可能仅杀死了当前进程,并使余下的内核处于一个良好的、稳定的状态。 -健壮的Linux

一个oops并非是内核死循环(panic)
在内核调用了panic()函数后,内核将停止运行,必须重启;
如果系统中关键部分遭到破坏,如关键的驱动等, 那么一个oops也可能会导致内核进入死循环(panic)。

当出现一个oops时,系统就会显示出用于调试问题的相关信息,比如所有CPU寄存器中的内容以及页描述符表的位置等,尤其会象下面那样打印出EIP(指令指针)的内容:

   EIP: 0010:[<00000000>]
   Call Trace: []


※与System.map的关系※

Linux使用klogd截取内核oops并且使用syslogd将其记录下来。klogd是一个内核消息记录器(logger), 它可以通过System.map文件进行名字-地址之间的解析。通常是使用syslogd记录器。

深入说明: 其实klogd会执行两类地址解析活动。
静态转换,将使用System.map文件。
动态转换,该方式用于可加载模块,不使用System.map

Klogd动态转换

假设你加载了一个产生oops的内核模块。klogd就会截获消息。并解析出地址。如果该地址属于动态加载模块,在System.map文件中没有对应条目。此时klogd就会向内核查询该可加载模块输出的符号。

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值