独家:深度介绍Linux内核是如何工作的

 

比如lsmod的输出在我们的电脑上显示了一个名叫isofs的卸载模块,它的使用次数是零而且没有依赖模块,(isofs是一个模块,它支持CD上使用的ISO系统文件格式)这种情况下,kernel会允许我们卸载模块:# modprobe -r is
CSDN欲打造最真实高端技术交流SNS
架构师、项目经理、产品经理俱乐部 欢迎加入真公司、真职位、实名的网络
加入项目经理俱乐部,与六百位项目经理交流
如果您也是项目经理,欢迎加入! 这里全部是真公司、实名的项目经理
想学3D游戏开发?没问题!还是免费的~
ITCast微软专区 3D游戏开发步步高系列
加入架构师俱乐部,与四百位架构师交流
如果你也是架构师,欢迎加入! 全部是真公司、实名的架构师

【Csdn 3月27日编译】本文发表于Linux Format magazine杂志,作者从技术深度上解释了Linux Kernel是如何工作的。相信对Linux开发者来说有不小的帮助。

牛津字典中对"kernel"一词的定义是:"较软的、通常是一个坚果可食用的部分。"当然还有第二种定义:"某个东西核心或者最重要的部分。"对Linux来说,它的Kernel无疑属于第二种解释。让我们来看看这个重要的东西是如何工作的,先从一点理论说起。

广义地来说kernel就是一个软件,它在硬件和运行在计算机上的应用程序之间提供了一个层。严格点从计算机科学的角度来说,Linux中的Kernel指的是Linus Torvalds在90年代初期写的那点代码。

    所有的你在Linux各版本中看到的其他东西--Bash shell、KDE窗口管理器、web浏览器、X服务器、Tux Racer以及所有的其他,都不过是运行在Linux上的应用而已,而不是操作系统自身的一部分。为了给大家一个更加直观的感觉,我来举个例子,比如RHEL5的安装大概要占据2.5GB的硬盘空间(具体多大当然视你的选择安装来定),在这其中,kernel以及它的各个模块组件,只有47MB,所占比例约为2%。

在kernel内部

那么kernel到底是如何工作的呢?如下面的图表。Kernel通过许多的进入端口也就是我们从技术角度所说的系统调用,来使得运行在它上面的应用程序可用。Kernel使用的系统调用比如"读"和"写"来提供你硬件的抽象(abstraction)。



从程序员的视角来看,这些看起来只是普通的功能调用,然而实际上系统调用在处理器的操作模式上,从用户空间到Kernel空间有一个明显的切换。同时,系统调用提供了一个"Linux虚拟机",可以被认为是对硬件的抽象。

Kernel提供的更明显的抽象之一是文件系统。举例来说,这里有一段短的程序是用C写的,它打开了一个文件并将内容拷贝到标准的输出:

#include <fcntl.h>
int main()
{
    int fd, count; char buf[1000];
    fd=open("mydata", O_RDONLY);
    count = read(fd, buf, 1000);
    write(1, buf, count);
    close(fd);
}

    在这里,你可以看到四个系统调用的例子:打开、读、写和关闭。不谈这段程序语法的细节,重点是:通过这些系统调用Linux Kernel提供了一个文件的"错觉",而实际上它不过是一堆数据有了个名字,这样一来你就不必去与硬件底层的堆栈、分区、头和指针、分区等交涉了,而是直接以例子中的方式与硬件"交流",这也就是我们所说的抽象(abstraction),将底层的东西以更易懂的方式表达出来。

台前幕后

系统文件是Kernel提供的较为明显的一种抽象。还有一些特性不是这么的明显,比如进程调度。任何一个时间,都可能有好几个进程或者程序等待着运行。Kernel的时间调度给每个进程分配CPU时间,所以就一段时间内来说,我们会有种错觉:电脑同一时间运行好几个程序。这是另外一个C程序:

#include <stdlib.h>
main()
{
  if (fork()) {
    write(1, "Parent/n", 7);
    wait(0);
    exit(0);
  }
  else {
    write(1, "Child/n", 6);
    exit(0);
  }
}
    
在这个程序中创建了一个新进程,而原来的进程(父进程)和新进程(子进程)都编写了标准输出然后结束。注意系统调用fork(), exit() 以及 wait()执行程序的创建、结束和各自同步。这是进程管理和调度中最典型的简单调用。

Kernel还有一个更加不易见到的功能,连程序员都不易察觉,那就是存储管理。每个程序运行得都好像它有个自己的地址空间来调用一样,实际上它跟其他进程一样共享计算机的物理存储,如果系统运行的存储过低,它的地址空间甚至会被磁盘的交互区暂时寄用。存储管理的另外一个方面是防止一个进程访问其他进程的地址空间--对于多进程操作系统来说这是很必要的一个防范措施。

Kernel同样还配置网络链接协议比如IP、TCP和UDP等,它们在网络上提供机器对机器(machine-to-machine)和进程对进程(process-to-process)的通信。这里又会造成一种假象,即TCP在两个进程之间提供了一个固定连接--就好像连接两个电话的铜线一样,实际中却并没有固定的连接,特殊的引用协议比如FTP、DNS和HTTP是通过用户级程序来实施的,而并非Kernel的一部分。

Linux(像之前的Unix)在安全方面口碑很好,这是因为Kernel跟踪记录了每个运行进程的user ID和group ID,每次当一个应用企图访问资源(比如打开一个文件来写入)的时候,Kernel就会核对文件上的访问许可然后做出允许/禁止的命令。这种访问控制模式最终对整个Linux系统的安全作用很大。

Kernel还提供了一大套模块的集合,其功能包括如何处理与硬件设备交流的诸多细节、如何从磁盘读取一个分区、如果从网络接口卡获取数据包等。有时我们称这些为设备驱动。

模块化的Kernel

现在我们队Kernel是做什么的已经有了一些了解,让我们再来简单看下它的物理组成。早期版本的Linux Kernel是整体式的,也就是说所有的部件都静态地连接成一个(很大的)执行文件。

相比较而言,现在的Linux Kernel是模块化的:许多功能包含在模块内,然后动态地载入kernel中。这使得kernel的内核很小,而且在运行kernel时可以不必reboot就能载入和替代模块。

Kernel的内核在boot time时从位于/boot 目录的一个文件加载进存储中,通常这个/boot 目录会被叫做KERNELVERSION,KERNELVERSION与kernel版本有关。(如果你想知道你的kernel版本是什么,运行命令行显示系统信息-r。)kernel的模块位于目录/lib/modules/KERNELVERSION之下,所有的组件都会在kernel安装时被拷贝。

管理模块

大部分情况下,Linux管理它的模块不需要你的帮忙,但是如果必要的时候有命令行可以来手动检查和管理模块。比如,为了查清楚当前到底哪个模块在载入kernel。这里有一个输出的例子:

# lsmod
pcspkr              4224  0 
hci_usb            18204  2 
psmouse            38920  0 
bluetooth          55908  7 rfcomm,l2cap,hci_usb
yenta_socket       27532  5 
rsrc_nonstatic     14080  1 yenta_socket
isofs              36284  0 

输出的内容包括:模块的名字、大小、使用次数和依赖于它的模块列表。使用次数对防止卸载当前活跃的模块非常总要。Linux只允许使用次数为零的模块被移除。

你可以使用modprobe来手动加载和卸载模块,(还有两个命令行叫做insmod和rmmod,但modprobe更易于使用因为它自动移除了模块依赖)。比如lsmod的输出在我们的电脑上显示了一个名叫isofs的卸载模块,它的使用次数是零而且没有依赖模块,(isofs是一个模块,它支持CD上使用的ISO系统文件格式)这种情况下,kernel会允许我们卸载模块:

# modprobe -r isofs

现在,isofs不再显示在Ismod的输出中,kernel由此节省了36,284字节的存储。如果你放入CD并且让它自动安装,kernel将自动重新载入isofs模块,而且isofs的使用次数增加到1次。如果这时候你还试图移除模块,就不会成功了因为它正在被使用:

# modprobe -r isofs 
FATAL: Module isofs is in use.
    
Lsmod只是列出了当前被载入的模块,modprobe则将列出所有可用的模块,它实际上输出了/lib/modules/KERNELVERSION目录下所有的模块,名单会很长!

实际上,使用modprobe来手动加载一个模块并不常见,但确实可以通过modprobe命令行来对模块设置参数,例如:

# modprobe usbcore blinkenlights=1

我们并不是在创建blinkenlights,而是usbcore模块的实参数。

那么如何知道一个模块会接受什么参数呢?一个比较好的方法是使用modinfo命令,它列出了关于模块的种种信息。这里有一个关于模块snd-hda-intel的例子

# modinfo snd-hda-intel 
filename:       /lib/modules/2.6.20-16-generic/kernel/sound/pci/hda/snd-hda-intel.ko
description:    Intel HDA driver
license:        GPL
srcversion:     A3552B2DF3A932D88FFC00C
alias:          pci:v000010DEd0000055Dsv*sd*bc*sc*i*
alias:          pci:v000010DEd0000055Csv*sd*bc*sc*i*
depends:        snd-pcm,snd-page-alloc,snd-hda-codec,snd
vermagic:       2.6.20-16-generic SMP mod_unload 586 
parm:           index:Index value for Intel HD audio interface. (int)
parm:           id:ID string for Intel HD audio interface. (charp)
parm:           model:Use the given board model. (charp)
parm:           position_fix:Fix DMA pointer (0 = auto, 1 = none, 2 = POSBUF, 3 = FIFO size). (int)
parm:           probe_mask:Bitmask to probe codecs (default = -1). (int)
parm:           single_cmd:Use single command to communicate with codecs (for debugging only). (bool)
parm:           enable_msi:Enable Message Signaled Interrupt (MSI) (int)
parm:           enable:bool

对我们来说比较有兴趣的以"parm"开头的那些部分:显示了模块所接受的参数。这些描述都比较简明,如果想要更多的信息,那就安装kernel的源代码,在类似于/usr/src/KERNELVERSION/Documentation的目录下你会找到。

里面会有一些有趣的东西,比如文件/usr/src/KERNELVERSION/Documentation/sound/alsa/ALSA-Configuration.txt描述的是被许多ALSA声音模块承认的参数;/usr/src/KERNELVERSION/Documentation/kernel-parameters.txt这个文件也很有用。

前几天在Ubuntu论坛有一个例子,说的是如何将参数传递到一个模块(详见https://help.ubuntu.com/community/HdaIntelSoundHowto)。实际上问题的关键是snd-hda-intel参数在正确驱动声音硬件时需要一点操作,而且在boot time加载时会中止。解决方法的一部分是将probe_mask=1选项赋给模块,如果你是手动加载模块,你需要输入:

# modprobe snd-hda-intel probe_mask=1

更有可能,你在文件/etc/modprobe.conf中放置这样类似的一行:options snd-hda-intel probe_mask=1

这"告诉"modprobe每次在加载snd-hda-intel模块时包含probe_mask=1选项。现在的有些Linux版本将这一信息分离进/etc/modprobe.d下的不同文件中了,而不是放入modprobe.conf中。

/proc系统文件

Linux kernel同样通过/proc系统文件来展示了许多细节。为了说明/proc,我们首先需要扩展我们对于文件的理解。除了认为文件就是存储在硬盘或者CD或者存储空间上的持久信息之外,我们还应当把它理解为任何可以通过传统系统调用如:打开、读、写、关闭等访问的信息,当然它也可以被常见的程序访问。

/proc之下的"文件"完全是kernel虚拟的一个部分,给我们一个视角可以看到kernel内部的数据结构。实际上,许多Linux的报告工具均能够很好地呈现在/proc下的文件中寻到的格式化版本的信息。比如,一列/proc/modules将展示一列当前加载的模块。

同样的,/proc/meminfo提供了关于虚拟存储系统当前状态的更多细节信息,而类如vmstat的工具则是以一种更加可理解的方式提供了相同的一些信息;/proc/net/arp显示了系统ARP cache的当前内容,从命令行来说,arp -a显示的也是相同的信息。

尤其有意思的是/proc/sys下的"文件"。/proc/sys/net/ipv4/ip_forward下的设置告诉我们kernel是否将转发IP数据包,也就是说是否扮演网关的作用。现在,kernel告诉我们这是关闭的:

# cat /proc/sys/net/ipv4/ip_forward 
0

当你发现你可以对这些文件写入的时候,你会觉得更加有意思。继续举例来说:

# echo 1 > /proc/sys/net/ipv4/ip_forward

将在运行的kernel中打开IP 转发(IP forwarding)

除了使用cat和echo来检查和更正/proc/sys下的设置以外,你也可以使用sysctl命令:

# sysctl net.ipv4.ip_forward 
net.ipv4.ip_forward = 0

这等同于:
# cat /proc/sys/net/ipv4/ip_forward 
0

也等同于:
# sysctl -w net.ipv4.ip_forward=1
net.ipv4.ip_forward = 1

还等同于:
# echo 1 > /proc/sys/net/ipv4/ip_forward
    

需要注意的是,以这种方式你所做的设置改变只能影响当前运行的kernel的,当reboot的时候就不再有效。如果想让设置永久有效,将它们放置在/etc/sysctl.conf文件中。在boot time时,sysctl将自动重新确定它在此文件下找到的任何设置。

/etc/sysctl.conf下的代码行大概是这样的:net.ipv4.ip_forward=1

性能调优(performance tuning)

有这样一个说法:/proc/sys下可写入的参数孕育了整个Linux性能调优的亚文化。我个人觉得这种说法有点过夸,但这里会有几个你确实很想一试的例子:Oracle 10g的安装说明(www.oracle.com/technology/obe/obe10gdb/install/linuxpreinst/linuxpreinst.htm)要求你设置一组参数,包括:kernel.shmmax=2147483648 这将公用存储器的大小设置为2GB。(公用存储器是处理期内的通信机制,允许存储单元在多个进程的地址空间内同时可用)

IBM 'Redpaper'在Linux性能和调优方面的说明(www.redbooks.ibm.com/abstracts/redp4285.html)在调教/proc/sys下的参数方面给出了不少建议,包括:vm.swappiness=100 这个参数控制着存储页如何被交换到磁盘。

一些参数可以被设置从而提高安全性,如net.ipv4.icmp_echo_ignore_broadcasts=1 它"告诉"kernel不必响应ICMP请求,从而使得你的网络免受类如Smurf攻击之类的拒绝服务器(denial-of-service)型攻击。
net.ipv4.conf.all.rp_filter=1 则是"告诉"kernel加强入站过滤(ingress filtering)和出站过滤(egress filtering)

那么有没有一个说明能涵盖这所有的参数?好吧,这有一行命令:# sysctl -a 它将展示所有的参数名字和当前值。列表很长,但是你无法知道这些参数是做什么的。另外比较有用的参考是Red Hat Enterprise Linux Reference Guide,对此有整章节的描述,你可以从www.redhat.com/docs/manuals/enterprise上下载。(译/王玉磊)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
前言 Linux是互连网上的独特现象。虽然它是由学生的业余爱好发展而来,但是现在它已经成为最为流行的免费操作系统。对很多人来说,Linux是一个谜。免费的东西怎么会变得如此有价值?在个由少数软件公司统治的世界,由一帮HACKER们编写的东西是怎样与那些公司的产品竞争的? 这些软件是如何分发给分布在世界各个角落,希望得到稳定产品的人们的?事实上Linux的确稳定而富有竞争力。许多大学与研究机构都使用Linux完成他们的日常计算任务。人们在家用PC上使用Linux,许多公司也在使用它--尽管他们并不总是乐意承认这点。Linux主要用来浏览WEB,管理WEB站点,撰写与发送EMAIL,以及玩游戏。Linux绝对不是玩具而是具有专业水平的操作系统,它的爱好者遍及世界。 Linux的源头要追溯到最古老的UNIX。1969年,Bell实验室的Ken Thompson开始利用一台闲置的PDP-7计算机开发了一种多用户,多任务操作系统。很快,Dennis Richie加入了这个项目,在他们共同努力下诞生了最早的UNIX。Richie受一个更早的项目——MULTICS的启发,将此操作系统命名为Unix。早期UNIX是用汇编语言编写的,但其第三个版本用一种崭新的编程语言C重新设计了。C是Richie设计出来并用于编写操作系统的程序语言。通过这次重新编写,Unix得以移植到更为强大的 DEC PDP-11/45与11/70计算机上运行。后来发生的一切,正如他们所说,已经成为历史。Unix从实验室走出来并成为了操作系统的主流,现在几乎每个主要的计算机厂商都有其自有版本的Unix. Linux起源于一个学生的简单需求。Linus Torvalds,Linux的作者与主要维护者,在其上大学时所买得起的唯一软件是Minix. Minix是一个类似Unix,被广泛用来辅助教学的简单操作系统。Linus 对Minix不是很满意,于是决定自己编写软件。他以学生时代熟悉的Unix作为原型, 在一台Intel 386 PC上开始了他的工作。他的进展很快,受工作成绩的鼓舞,他将这项成果通过互连网与其他同学共享,主要用于学术领域。有人看到了这个软件并开始分发。每当出现新问题时,有人会立刻找到解决办法并加入其中,很快的, Linux成为了一个操作系统。值得注意的是Linux并没有包括Unix源码。它是按照公开的POSIX标准重新编写的。Linux大量使用了由麻省剑桥免费软件基金的GNU软件,同时Linux自身也是用它们构造而成。 许多人将Linux视作简单工具并将其放入CDROM中来分发。很多Linux使用者使用它来编写应用程序或者运行别人编写的应用程序。这些人热切的阅读HOWTO手册,当系统的一部分被正确的设置时,他们总是激动不已,失败时则沮丧气馁。只有少部分人敢于编写设备驱动程序并将核心的补丁提供给Linus Torvalds,Linus Torvalds从每个志愿者那里接收补充代码与对核心的修改代码。 这种情形听起来象非常混乱,但Linus进行了非常严格的质量控制并由他负责将所有的新代码加入核心。只有少部分人对Linux 核心贡献了源代码。 大多数Linux的使用者并不关心系统是如何工作,或者如何组合在一起的。这种情况令人惋惜,因为阅读Linux源代码提供了一个学习操作系统的绝好机会。这不仅仅因为它写得好,还因为它的源码是可以免费得到的。因为虽然作者们对其软件保留版权,但是在免费软件基金的GNU公开授权下源代码是可以自由分发的。第一眼看去,源码是非常复杂的。但是通过进一步观察你可以发现源码目录中包含有Kernel,mm以及net的目录, 不过要想知道这些目录中包含了那些代码以及代码是如何工作的就需要对Linux的总体结构与目标有较深入的理解。简而言之,这也是本书所希望达到的目标,为读者提供一个Linux如何工作清晰的印象。当你将文件从一个目录拷到另一个目录或者阅读电子邮件时,不妨在脑海中勾勒一下系统中正在发生什么事情,我还清楚的记得当我感到第一次认识到操作系统真的在工作时的兴奋。这种兴奋正是我想将它带给本书的读者的。 我第一次接触Linux在1994年下半年当我拜访Jim Paradis时,当时他正在致力于将Linux移植到Alpha AXP处理器系统上。从1984年开始,我曾经在DEC公司任职,主要工作网络与通讯。1992年我开始为新成立的Digital Semiconductor分部工作。此分部的任务是全面进入商用芯片市场并销售芯片,特别是Alpha AXP系列处理器以及DEC以外的Alpha AXP系统板。当首次听到Linux时我便立刻意识到了这是一个有趣的机会。Jim的狂热是鼓惑人心的,我也开始帮他一起工作。在工作中,我越来越喜欢这个操作系统及创造它的工程师团体。 Alpha AXP仅仅是Linux可以运行的多种平台中的一个。大多数Linux核心工作在基于Intel处理器的系统上,但非Intel系统的Linux用户也越来越多。它们是Alpha AXP, ARM, MIPS, Sparc与Power PC。 虽然我可以根据上叙任何一种平台来编写本书的内容,但是我的技术知识与背景让我主要根据Alpha AXP处理器和ARM处理器来编写。这是本书有时使用非Intel硬件来描叙一些重要观点。值得注意的是,不管运行在哪种平台上,95%的Linux核心代码都是相同的。同样,本书95%的内容是关于Linux 内核的机器无关部分的讨论。 本书对读者的知识与经验没有任何要求。我相信对于某一事物的兴趣是鼓励自学的必要因素。不过对于计算机,或者PC和C程序语言的了解将有助于读者从有关材料中获益。 本书的组织 本书并不是特意一本Linux的内部手册。相反它是对操作系统的介绍,同时以Linux作为示例。书中每一章遵循“从共性到特性”的原则。它们将首先给出核心子系统的概叙,然后进行尽可能的详细描叙。 我不会用routine_X()调用routine_Y()来增加bar数据结构中foo域的值这种方式来描叙核心算法。 你自己可以通过阅读代码发现它。每当需要理解一段代码时,我总是将其数据结构画出来。这样我发现了许多相关的核心数据结构以及它们之间的关系。 每一章都是非常独立的,就象Linux核心子系统一样。当然有时它们还是有联系的,比如说,如果你没有理解虚拟内存工作原理就无法描叙进程。 硬件基本概念一章对现代PC做了简要介绍。操作系统必须与硬件系统紧密结合在一起协同工作。操作系统需要一些只能够由硬件提供的服务。为了全面理解Linux,你必须了解有关硬件的基础知识。 软件基本概念一章介绍了软件基本原理与C程序语言。讨论了建立Linux这样的操作系统的工具并且给出了操作系统的目标与功能的概叙。 内存管理这章描叙了Linux如何处理物理内存以及虚拟存储技术。 进程管理描叙了进程的概念以及Linux核心是如何创建、管理与删除系统中的进程。 进程间及进程与核心间通讯以协调它们的活动。Linux支持大量进程间通讯(IPC)机制。信号与管道是 其中的两种,Linux同时还支持系统V IPC机制。这些进程间通讯机制在IPC一章中描叙。 外部设备互连(PCI)标准已经成为PC上低价位高数传率的总线标准。PCI一章将描叙Linux核心是如何初始化并使用PCI总线及设备的。 中断及中断处理一章将着重于Linux核心对中断的处理。虽然处理中断有通用的机制与接口,但某些细节是与硬件及CPU体系结构相关的。 Linux的一个长处是其对现代PC的硬件设备强有力的支持。设备驱动程序一章将描叙Linux核心是如何控制系统中的物理设备。 文件系统一章描叙了Linux核心是如何维护它所支持的文件系统中的文件。同时还描叙了虚拟文件系统(VFS)及Linux核心的每种文件系统是如何得到支持。 网络Linux几乎是同义的。在某种意义上Linux是WWW时代互连网的产物。其开发者通过Web来交换信息及代码。网络一章描叙了Linux是如何支持TCP/IP这些网络协议。 核心机制一章主要讨论能使Linux核心其他部分有效工作而由核心所提供的一些通用任务与机制。 动态模块一章描叙Linux核心是如何仅在需要时动态加载某些模块,比如文件系统。 处理器一章给出了目前Linux可以在其上运行的一些处理器的简要介绍。 资源一章则提供了有关Linux核心资源的有用信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值