译:深入Linux内核架构(第一章)
注:选择性翻译原文。
1.3内核原理
本章节为大量的内核原理提供简要的概述,以及简要描述一下我们将会在下面章节中详述的大纲。尽管很庞大,Linux仍然构造得很出色。然而,单个元素之间的互相影响是不可避免的。他们分享数据结构和(性能的缘故)通过各类函数互相协调,也是在严格隔离的系统中所必须的。图1-1是一个粗略的初期的层次概括图,囊括着Linux系统,和一些内核关键的子系统。注意,在下图中没有给出单个子系统交互在各种各样的其他方式。
1.3.1进程、任务切换、调度
应用程序,服务器,以及其他一些在Unix下运行的程序,传统上统称为进程。每个进程的地址空间都由CPU分配虚拟内存。进程私有地址空间是完全独立的,以致进程之间完全不知道彼此间的工作,除非进程间取得联系,否则似乎是系统中唯一一个运行的进程一样。如果进程想通信交换数据,则必须使用特殊的内核机制。(进程间通信机制等)
因为Linux是一个多任务系统,它支持几个并发进程。由于在同一时间,只有多CPU系统才能同时运行对应的几个进程,因此内核以很短的时间在不同进程间切换(不被用户注意),让人觉得几个进程同时运行。这里,产生两个问题区:
1、在CPU的帮助下,内核负责的技术细节的任务切换。每个单独的进程必须被引导为觉得CPU总是可用的。这是通过在CPU资源被回收前保存所有相关元素的状态来实现的,并且进程是处于空闲状态。当进程被从新激活,就会恢复保存的状态。进程间的切换称为任务切换。
2、内核还必须决定如何在现有的进程间共享CPU时间。重要的进程将分配更多的CPU时间(时间片),分量没那么重的线程分配的时间片相对较少。决定哪些进程运行多长时间被称为调度。
1.3.2Unix进程
Linux采取分级方案,每个进程取决于他的父进程。内核启动init进程作为第一个进程,负责进一步的系统初始化操作和显示登录提示或(今天广泛使用)显示一个图形登录界面。因此init是所有进程获得root权限的来源,或多或少,如pstree所示。Init是树形结构的最高点,其分支向下延伸,扩展。
wolfgang@meitner> pstree
init-+-acpid
|-bonobo-activati
|-cron
|-cupsd
|-2*[dbus-daemon]
|-dbus-launch
|-dcopserver
|-dhcpcd
|-esd
|-eth1
|-events/0
|-gam_server
|-gconfd-2
|-gdm---gdm-+-X
| ‘-startkde-+-kwrapper
| ‘-ssh-agent
|-gnome-vfs-daemo
|-gpg-agent
|-hald-addon-acpi
|-kaccess
|-kded
|-kdeinit-+-amarokapp---2*[amarokapp]
| |-evolution-alarm
| |-kinternet
| |-kio_file
| |-klauncher
||-konqueror
||-konsole---bash-+-pstree
|| ‘-xemacs
| |-kwin
| |-nautilus
| ‘-netapplet
|-kdesktop
|-kgpg
|-khelper
|-kicker
|-klogd
|-kmix
|-knotify
|-kpowersave
|-kscd
|-ksmserver
|-ksoftirqd/0
|-kswapd0
|-kthread-+-aio/0
| |-ata/0
| |-kacpid
| |-kblockd/0
| |-kgameportd
| |-khubd
| -kseriod
| |-2*[pdflush]
| ‘-reiserfs/0
这棵树结构是如何随着新进程的产生而扩展并紧密相连的。对于这个目的,Unix用了两个机制:fork和exec。
1、fork——生成一个精确复制于当前进程的进程,唯一不同于父进程的只有在它的PID(过程识别)。在系统调用被执行后,系统中有两个进程,两个进程执行系统的操作。满足初始化进程的内存会被复制出来,至少是在进程的角度。Linux使用一个著名的技术:copy on write,通过推迟复制操作直到要么父或子进程写一页上来使操作更加高效,对于父子进程,只读权限可以满足访问同一页面。
一个适用用fork的场合是,当一个用户打开第二个浏览器窗口。如果相应的选项被选中, 浏览器执行一个fork复制它的代码,然后开始适当的行动在子进程中建立一个新的窗口。
2、exec——加载一个新的程序到一个现有的内容,然后执行它。旧进程保留内存页面被 冲洗掉,里面的内容 由新进程数据代替。执行新的进程。
线程
进程并非内核所支持的程序运行的唯一形式。除重量级进程外,经典Unix进程还有另外一个名字——线程,一些参考资料也叫轻量级进程。他们存在有一段时间了,从本质上来说,一个进程可能包括几个线程,其都能通共享相同的数据和资源,但在程序代码中扮演不同的角色。线程的概念被完全融入许多现代语言,如java。简单来说,一个进程可以看做为一个运行的程序,而一个线程是一个程序函数或程序,运行在平行于主程序的位置上。这是否有意,例如,当Web浏览器需要并发地加载几个图片。通常,浏览器将不得不执行几个fork和exec调用来形成并发事件。这些将负责加载图片和制作数据,以便主程序能通过一些通讯机制来接受到。线程使 这种情况更容易处理。浏览器定义了一个常规加载图片,和进程以多态形式开始运行(每个都有不同的参数)。因为线程和主程序共享相同的地址空间,接收到的数据自动驻留在主程序里面。因此不需要任何通讯手段,除了防止线程同时访问相同没存而造成冲突。如图1-2,阐述了进程与线程间的不同。
Linux提供了克隆的方法来生成线程。这工作在一个简单的fork原理,但使能一个精确的检查,其组成资源与父进程共享,是相对于线程独立产生的。这个细化分布的资源扩展了经典线程 概念和允许一个或多或少的线程和进程之间的连续过渡。
命名空间
在2.6内核的发展期间,对名称空间的支持被集成到众多的子系统。这允许系统中不同的进程有不同的形式。传统上,Linux使用许多全局的进程标识符:每个进程在系统配备一个唯一的标识符(ID),这个ID可以受用于用户(或其他进程)来引用进程——通过发送一个信号。跟命名空间比起来,以前的全局资源有显不同:每个命名空间可以包含一组特定的PID,或者可以提供文件系统的不同视图。当文件系统挂载到一个名称空间就不能传递到另一个去了。
命名空间很好用,例如,他们是有利于托管服务提供商:而不是为每个客户建立一个物理机,他们可以用容器与名称空间来实现创建多个视图的系统,其中每一个似乎都是来自内部容器而非其他容器的完整的Linux安装。他们是分开的,以及跟其他的隔离开来。每个实例看起来像一个运行Linux的机器,但事实上,许多这样的实例可以同时作用在一个物理机器上。这有助于更有效地使用资源。相比之下,与如KVM一样完全虚拟化解决方案相比,只需要一个运行在机器上的内核来负责管理所有容器。
内核中不是所有的部分都识到名称空间,我将讨论当我们分析各种子系统的时候,会讨论子系统对命名空间的支持度。