﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-旅途-随笔分类-Linux内核</title><link>http://www.cppblog.com/mydriverc/category/5935.html</link><description>如果想飞得高，就该把地平线忘掉</description><language>zh-cn</language><lastBuildDate>Sat, 28 Mar 2009 18:40:12 GMT</lastBuildDate><pubDate>Sat, 28 Mar 2009 18:40:12 GMT</pubDate><ttl>60</ttl><item><title>独家：深度介绍Linux内核是如何工作的</title><link>http://www.cppblog.com/mydriverc/archive/2009/03/27/78014.html</link><dc:creator>旅途</dc:creator><author>旅途</author><pubDate>Fri, 27 Mar 2009 03:01:00 GMT</pubDate><guid>http://www.cppblog.com/mydriverc/archive/2009/03/27/78014.html</guid><wfw:comment>http://www.cppblog.com/mydriverc/comments/78014.html</wfw:comment><comments>http://www.cppblog.com/mydriverc/archive/2009/03/27/78014.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/mydriverc/comments/commentRss/78014.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/mydriverc/services/trackbacks/78014.html</trackback:ping><description><![CDATA[【Csdn 3月27日编译】本文发表于Linux Format magazine杂志，作者从技术深度上解释了Linux
Kernel是如何工作的。相信对Linux开发者来说有不小的帮助。<br><br>牛津字典中对"kernel"一词的定义是："较软的、通常是一个坚果可食用的部分。"当然还有第二种定义："某个东西核心或者最重要的部分。"对Linux来说，它的Kernel无疑属于第二种解释。让我们来看看这个重要的东西是如何工作的，先从一点理论说起。<br><br>广义地来说kernel就是一个软件，它在硬件和运行在计算机上的应用程序之间提供了一个层。严格点从计算机科学的角度来说，Linux中的Kernel指的是Linus
Torvalds在90年代初期写的那点代码。<br><br>&nbsp;&nbsp;&nbsp; 所有的你在Linux各版本中看到的其他东西--Bash
shell、KDE窗口管理器、web浏览器、X服务器、Tux
Racer以及所有的其他，都不过是运行在Linux上的应用而已，而不是操作系统自身的一部分。为了给大家一个更加直观的感觉，我来举个例子，比如RHEL5的安装大概要占据2.5GB的硬盘空间（具体多大当然视你的选择安装来定），在这其中，kernel以及它的各个模块组件，只有47MB，所占比例约为2%。<br><br><strong>在kernel内部</strong><br><br>那么kernel到底是如何工作的呢?如下面的图表。Kernel通过许多的进入端口也就是我们从技术角度所说的系统调用，来使得运行在它上面的应用程序可用。Kernel使用的系统调用比如"读"和"写"来提供你硬件的抽象（abstraction）。<br><br><img  src="http://info-database.csdn.net/Upload/2009-03-27/LXF107.tut_adv.diagram.jpg" alt=""><br><br>从程序员的视角来看，这些看起来只是普通的功能调用，然而实际上系统调用在处理器的操作模式上，从用户空间到Kernel空间有一个明显的切换。同时，系统调用提供了一个"Linux虚拟机"，可以被认为是对硬件的抽象。<br><br>Kernel提供的更明显的抽象之一是文件系统。举例来说，这里有一段短的程序是用C写的，它打开了一个文件并将内容拷贝到标准的输出：<br><br>#include
&lt;fcntl.h&gt;<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp; int fd, count; char buf[1000];<br>&nbsp;&nbsp;&nbsp;
fd=open("mydata", O_RDONLY);<br>&nbsp;&nbsp;&nbsp; count = read(fd, buf, 1000);<br>&nbsp;&nbsp;&nbsp; write(1,
buf, count);<br>&nbsp;&nbsp;&nbsp; close(fd);<br>}<br><br>&nbsp;&nbsp;&nbsp;
在这里，你可以看到四个系统调用的例子：打开、读、写和关闭。不谈这段程序语法的细节，重点是：通过这些系统调用Linux
Kernel提供了一个文件的"错觉"，而实际上它不过是一堆数据有了个名字，这样一来你就不必去与硬件底层的堆栈、分区、头和指针、分区等交涉了，而是直接以例子中的方式与硬件"交流"，这也就是我们所说的抽象（abstraction），将底层的东西以更易懂的方式表达出来。<br><br><strong>台前幕后</strong><br><br>系统文件是Kernel提供的较为明显的一种抽象。还有一些特性不是这么的明显，比如进程调度。任何一个时间，都可能有好几个进程或者程序等待着运行。Kernel的时间调度给每个进程分配CPU时间，所以就一段时间内来说，我们会有种错觉：电脑同一时间运行好几个程序。这是另外一个C程序：<br><br>#include
&lt;stdlib.h&gt;<br>main()<br>{<br>&nbsp; if (fork()) {<br>&nbsp;&nbsp;&nbsp; write(1, "Parent\n",
7);<br>&nbsp;&nbsp;&nbsp; wait(0);<br>&nbsp;&nbsp;&nbsp; exit(0);<br>&nbsp; }<br>&nbsp; else {<br>&nbsp;&nbsp;&nbsp; write(1,
"Child\n", 6);<br>&nbsp;&nbsp;&nbsp; exit(0);<br>&nbsp; }<br>}<br>&nbsp;&nbsp;&nbsp;
<br>在这个程序中创建了一个新进程，而原来的进程（父进程）和新进程（子进程）都编写了标准输出然后结束。注意系统调用fork(), exit() 以及
wait()执行程序的创建、结束和各自同步。这是进程管理和调度中最典型的简单调用。<br><br>Kernel还有一个更加不易见到的功能，连程序员都不易察觉，那就是存储管理。每个程序运行得都好像它有个自己的地址空间来调用一样，实际上它跟其他进程一样共享计算机的物理存储，如果系统运行的存储过低，它的地址空间甚至会被磁盘的交互区暂时寄用。存储管理的另外一个方面是防止一个进程访问其他进程的地址空间--对于多进程操作系统来说这是很必要的一个防范措施。<br><br>Kernel同样还配置网络链接协议比如IP、TCP和UDP等，它们在网络上提供机器对机器（machine-to-machine）和进程对进程（process-to-process）的通信。这里又会造成一种假象，即TCP在两个进程之间提供了一个固定连接--就好像连接两个电话的铜线一样，实际中却并没有固定的连接，特殊的引用协议比如FTP、DNS和HTTP是通过用户级程序来实施的，而并非Kernel的一部分。<br><br>Linux（像之前的Unix）在安全方面口碑很好，这是因为Kernel跟踪记录了每个运行进程的user
ID和group
ID，每次当一个应用企图访问资源（比如打开一个文件来写入）的时候，Kernel就会核对文件上的访问许可然后做出允许/禁止的命令。这种访问控制模式最终对整个Linux系统的安全作用很大。<br><br>Kernel还提供了一大套模块的集合，其功能包括如何处理与硬件设备交流的诸多细节、如何从磁盘读取一个分区、如果从网络接口卡获取数据包等。有时我们称这些为设备驱动。<br><br><strong>模块化的Kernel</strong><br><br>现在我们队Kernel是做什么的已经有了一些了解，让我们再来简单看下它的物理组成。早期版本的Linux
Kernel是整体式的，也就是说所有的部件都静态地连接成一个（很大的）执行文件。<br><br>相比较而言，现在的Linux
Kernel是模块化的：许多功能包含在模块内，然后动态地载入kernel中。这使得kernel的内核很小，而且在运行kernel时可以不必reboot就能载入和替代模块。<br><br>Kernel的内核在boot
time时从位于/boot 目录的一个文件加载进存储中，通常这个/boot
目录会被叫做KERNELVERSION，KERNELVERSION与kernel版本有关。（如果你想知道你的kernel版本是什么，运行命令行显示系统信息-r。）kernel的模块位于目录/lib/modules/KERNELVERSION之下，所有的组件都会在kernel安装时被拷贝。<br><br><strong>管理模块</strong><br><br>大部分情况下，Linux管理它的模块不需要你的帮忙，但是如果必要的时候有命令行可以来手动检查和管理模块。比如，为了查清楚当前到底哪个模块在载入kernel。这里有一个输出的例子：<br><br>#
lsmod<br>pcspkr&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 4224&nbsp; 0 <br>hci_usb&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 18204&nbsp; 2
<br>psmouse&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 38920&nbsp; 0 <br>bluetooth&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 55908&nbsp; 7
rfcomm,l2cap,hci_usb<br>yenta_socket&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 27532&nbsp; 5 <br>rsrc_nonstatic&nbsp;&nbsp;&nbsp;&nbsp;
14080&nbsp; 1 yenta_socket<br>isofs&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 36284&nbsp; 0
<br><br>输出的内容包括：模块的名字、大小、使用次数和依赖于它的模块列表。使用次数对防止卸载当前活跃的模块非常总要。Linux只允许使用次数为零的模块被移除。<br><br>你可以使用modprobe来手动加载和卸载模块，（还有两个命令行叫做insmod和rmmod，但modprobe更易于使用因为它自动移除了模块依赖）。比如lsmod的输出在我们的电脑上显示了一个名叫isofs的卸载模块，它的使用次数是零而且没有依赖模块，（isofs是一个模块，它支持CD上使用的ISO系统文件格式）这种情况下，kernel会允许我们卸载模块：<br><br>#
modprobe -r
isofs<br><br>现在，isofs不再显示在Ismod的输出中，kernel由此节省了36,284字节的存储。如果你放入CD并且让它自动安装，kernel将自动重新载入isofs模块，而且isofs的使用次数增加到1次。如果这时候你还试图移除模块，就不会成功了因为它正在被使用：<br><br>#
modprobe -r isofs <br>FATAL: Module isofs is in use.<br>&nbsp;&nbsp;&nbsp;
<br>Lsmod只是列出了当前被载入的模块，modprobe则将列出所有可用的模块，它实际上输出了/lib/modules/KERNELVERSION目录下所有的模块，名单会很长！<br><br>实际上，使用modprobe来手动加载一个模块并不常见，但确实可以通过modprobe命令行来对模块设置参数，例如：<br><br>#
modprobe usbcore
blinkenlights=1<br><br>我们并不是在创建blinkenlights，而是usbcore模块的实参数。<br><br>那么如何知道一个模块会接受什么参数呢？一个比较好的方法是使用modinfo命令，它列出了关于模块的种种信息。这里有一个关于模块snd-hda-intel的例子<br><br>#
modinfo snd-hda-intel <br>filename:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
/lib/modules/2.6.20-16-generic/kernel/sound/pci/hda/snd-hda-intel.ko<br>description:&nbsp;&nbsp;&nbsp;
Intel HDA driver<br>license:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GPL<br>srcversion:&nbsp;&nbsp;&nbsp;&nbsp;
A3552B2DF3A932D88FFC00C<br>alias:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
pci:v000010DEd0000055Dsv*sd*bc*sc*i*<br>alias:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
pci:v000010DEd0000055Csv*sd*bc*sc*i*<br>depends:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
snd-pcm,snd-page-alloc,snd-hda-codec,snd<br>vermagic:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2.6.20-16-generic
SMP mod_unload 586 <br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; index:Index value for Intel HD audio
interface. (int)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id:ID string for Intel HD audio interface.
(charp)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; model:Use the given board model.
(charp)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; position_fix:Fix DMA pointer (0 = auto, 1 = none, 2 =
POSBUF, 3 = FIFO size). (int)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; probe_mask:Bitmask to probe
codecs (default = -1). (int)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; single_cmd:Use single command to
communicate with codecs (for debugging only). (bool)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
enable_msi:Enable Message Signaled Interrupt (MSI) (int)<br>parm:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
enable:bool<br><br>对我们来说比较有兴趣的以"parm"开头的那些部分：显示了模块所接受的参数。这些描述都比较简明，如果想要更多的信息，那就安装kernel的源代码，在类似于/usr/src/KERNELVERSION/Documentation的目录下你会找到。<br><br>里面会有一些有趣的东西，比如文件/usr/src/KERNELVERSION/Documentation/sound/alsa/ALSA-Configuration.txt描述的是被许多ALSA声音模块承认的参数；/usr/src/KERNELVERSION/Documentation/kernel-parameters.txt这个文件也很有用。<br><br>前几天在Ubuntu论坛有一个例子，说的是如何将参数传递到一个模块（详见https://help.ubuntu.com/community/HdaIntelSoundHowto）。实际上问题的关键是snd-hda-intel参数在正确驱动声音硬件时需要一点操作，而且在boot
time加载时会中止。解决方法的一部分是将probe_mask=1选项赋给模块，如果你是手动加载模块，你需要输入：<br><br># modprobe
snd-hda-intel probe_mask=1<br><br>更有可能，你在文件/etc/modprobe.conf中放置这样类似的一行：options
snd-hda-intel
probe_mask=1<br><br>这"告诉"modprobe每次在加载snd-hda-intel模块时包含probe_mask=1选项。现在的有些Linux版本将这一信息分离进/etc/modprobe.d下的不同文件中了，而不是放入modprobe.conf中。<br><br>/proc系统文件<br><br>Linux
kernel同样通过/proc系统文件来展示了许多细节。为了说明/proc，我们首先需要扩展我们对于文件的理解。除了认为文件就是存储在硬盘或者CD或者存储空间上的持久信息之外，我们还应当把它理解为任何可以通过传统系统调用如：打开、读、写、关闭等访问的信息，当然它也可以被常见的程序访问。<br><br>/proc之下的"文件"完全是kernel虚拟的一个部分，给我们一个视角可以看到kernel内部的数据结构。实际上，许多Linux的报告工具均能够很好地呈现在/proc下的文件中寻到的格式化版本的信息。比如，一列/proc/modules将展示一列当前加载的模块。<br><br>同样的，/proc/meminfo提供了关于虚拟存储系统当前状态的更多细节信息，而类如vmstat的工具则是以一种更加可理解的方式提供了相同的一些信息；/proc/net/arp显示了系统ARP
cache的当前内容，从命令行来说，arp
-a显示的也是相同的信息。<br><br>尤其有意思的是/proc/sys下的"文件"。/proc/sys/net/ipv4/ip_forward下的设置告诉我们kernel是否将转发IP数据包，也就是说是否扮演网关的作用。现在，kernel告诉我们这是关闭的：<br><br>#
cat /proc/sys/net/ipv4/ip_forward
<br>0<br><br>当你发现你可以对这些文件写入的时候，你会觉得更加有意思。继续举例来说：<br><br># echo 1 &gt;
/proc/sys/net/ipv4/ip_forward<br><br>将在运行的kernel中打开IP 转发（IP
forwarding）<br><br>除了使用cat和echo来检查和更正/proc/sys下的设置以外，你也可以使用sysctl命令：<br><br>#
sysctl net.ipv4.ip_forward <br>net.ipv4.ip_forward = 0<br><br>这等同于：<br># cat
/proc/sys/net/ipv4/ip_forward <br>0<br><br>也等同于：<br># sysctl -w
net.ipv4.ip_forward=1<br>net.ipv4.ip_forward = 1<br><br>还等同于：<br># echo 1 &gt;
/proc/sys/net/ipv4/ip_forward<br>&nbsp;&nbsp;&nbsp;
<br><br>需要注意的是，以这种方式你所做的设置改变只能影响当前运行的kernel的，当reboot的时候就不再有效。如果想让设置永久有效，将它们放置在/etc/sysctl.conf文件中。在boot
time时，sysctl将自动重新确定它在此文件下找到的任何设置。<br><br>/etc/sysctl.conf下的代码行大概是这样的：net.ipv4.ip_forward=1<br><br><strong>性能调优（performance
tuning）</strong><br><br>有这样一个说法：/proc/sys下可写入的参数孕育了整个Linux性能调优的亚文化。我个人觉得这种说法有点过夸，但这里会有几个你确实很想一试的例子：Oracle
10g的安装说明（www.oracle.com/technology/obe/obe10gdb/install/linuxpreinst/linuxpreinst.htm）要求你设置一组参数，包括：kernel.shmmax=2147483648
这将公用存储器的大小设置为2GB。（公用存储器是处理期内的通信机制，允许存储单元在多个进程的地址空间内同时可用）<br><br>IBM
'Redpaper'在Linux性能和调优方面的说明（www.redbooks.ibm.com/abstracts/redp4285.html）在调教/proc/sys下的参数方面给出了不少建议，包括：vm.swappiness=100
这个参数控制着存储页如何被交换到磁盘。<br><br>一些参数可以被设置从而提高安全性，如net.ipv4.icmp_echo_ignore_broadcasts=1
它"告诉"kernel不必响应ICMP请求，从而使得你的网络免受类如Smurf攻击之类的拒绝服务器（denial-of-service）型攻击。<br>net.ipv4.conf.all.rp_filter=1
则是"告诉"kernel加强入站过滤（ingress filtering）和出站过滤（egress
filtering）<br><br>那么有没有一个说明能涵盖这所有的参数？好吧，这有一行命令：# sysctl -a
它将展示所有的参数名字和当前值。列表很长，但是你无法知道这些参数是做什么的。另外比较有用的参考是Red Hat Enterprise Linux
Reference Guide，对此有整章节的描述，你可以从<a  href="http://www.redhat.com/docs/manuals/enterprise">www.redhat.com/docs/manuals/enterprise</a>上下载。（<img src ="http://www.cppblog.com/mydriverc/aggbug/78014.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/mydriverc/" target="_blank">旅途</a> 2009-03-27 11:01 <a href="http://www.cppblog.com/mydriverc/archive/2009/03/27/78014.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>