第七章 内核开发
如果需要编写Linux设备驱动程序,就必须了解操作系统内核的内部设计。有些开发者需要将Linux内核移植到新的平台、修复错误、添加新功能、解决性能瓶颈、改善其在大型或者小型系统中的可扩展性等,理所当然他们需要剖析决定Linux运行机制的核心算法。
需要注意的是,Linux内核并不遵循你在编写应用程序代码时所使用的准则。编写内核代码的时候,需要明确处理所有的内存分配----没有人会为你释放内存----而且需要应对非常实际的编程错误或可能导致整个系统崩溃的死/活锁。
完全明白Linux内核源码树的全部内容几乎是不可能的,而且也没有人懂得内核的所有内容。这并不重要,重要的是你理解源代码树的布局并知道到哪里去查找影响内核工作的部分。kernel.org上有内核下载。
Documentation下有一篇不错的关于内核编码风格指南的文档CodingStyle,以及如何提交关于内核中许多内部实现的纯技术方面的缺陷和概述文档的指南。
内核源码结构及说明:
[root@develop linux-2.6.24.4]# ls arch CREDITS drivers init kernel Makefile README scripts usr block crypto fs ipc lib mm REPORTING-BUGS security COPYING Documentation include Kbuild MAINTAINERS net samples sound
大部分核心功能依然保存在arch/ kernel/ mm/目录中。大部分内核驱动程序位于drivers目录中,几个明显的例外,例如sound/ net/依然保留在顶层目录中。
顶层目录包括的内容
目录名 目录内容
arch 硬件体系结构相关文件,这是底层启动代码以及支持处理器特性所需关键代码所在位置。主要用汇编语言编写,可以查看汇编文件head.S以找到底层内核入口
block 支持硬盘等块设备。包含用于I/O操作请求的结构bio和bio_vec的Linux内核2.6版本的实现,以及绝大多数在Linux内核中支持硬盘所需的代码。
crypto 加密库函数
init 在非常底层的体系结构相关代码运行结束后负责初始化内核的高层启动代码.内核调用这个目录的底层函数以建立内核虚拟内存(分页)支持,开始运行进程(第一个进程或任务是init),最终exec()进入init进程以开始运行常规的Linux启动脚本和第一个用户程序。
ipc 各种进程间通信(IPC)原语的代码位置
kernel 高层核心内核函数。包含Linux进程或任务的调度程序,以及各种支持代码,如printk()通用例程,它允许内核开发者在内核运行时发送消息给内核日志进程(klogd根据配置情况将接收到的每条信息记录到/var/log目录中)
lib 库例程,如CRC32校验和计算和用于内核中各处的各种高层库例程代码
mm 虚拟内存的Linux实现的内存管理例程
security 对SElinux功能的支持。原本由NSA开发。
sound ALSA音频子系统的实现代码
usr 用于建立初始化内存盘的各种支持工具
scripts 用于配置和编译Linux内核的各种脚本
有关内核编译
无论是使用make menuconfig/ make xconfig/ make config等,最终是为了得到一个.config文件。
有的Linux发行版下提供了有关内核的配置文件,例如FC4的/boot下有:config-2.6.11-1.1369_FC4文件。/proc/config.gz文件可以直接解开,得到有关的选项。
交叉编译的选择:CROSS_COMPILE的设置
$ export CROSS_COMPILE="powerpc-eabi-405-"
$ ARCH=powerpc make menuconfig
$ ARCH=powerpc make
$ make V=1 # 详细地输出每行输出
make modules_install后的结果:
[root@develop modules]# pwd /lib/modules [root@develop modules]# ls 2.6.11-1.1369_FC4 2.6.24.4
depmod命令可以装载/lib/modules下的内核模块----这样就不需要对一个具体的.ko模块使用更特殊的insmod/rmmode命令,modprobe还会自动解决模块之间的依赖关系.
$ depmod -a 2.6.x.y
grub: pc 启动装载 yaboot: powerpc 启动加载
安装内核:
[root@develop linux-2.6.24.4]# cp arch/i386/boot/bzImage /boot/vmlinuz-2.6.24.4 [root@develop linux-2.6.24.4]# cp System.map /boot/System.map-2.6.24.4 $ make install会自动生成initrd-2.6.24.4.img等文件.
如果你使用RPM的发行版本,则可以使用RPM spec文件以包含针对这个发行版的配置和编译过程.只需要安装内核的源代码RPM软件包,对用于编译内核的源文件作出一些修改,然后重新建立内核RPM即可.
内核扮演的角色 :资源仲裁者(独一无二),设计目的是为了避免所有运行在内核之上的应用程序之间的互相干扰.负责提供一个特殊的多进程抽象,这个抽象使得每个应用程序产生一种自己拥有整个Linux系统的错觉.作为一种结果,应用程序通常不需要互相担心彼此的运行状态,除非它们需要进行交互.
应用程序通常不会关心在使用完自己公平分享的处理器时间片后如何放弃处理器,或者中断等等.由Linux内核来负责确保每个应用程序获取一定的CPU时间,对外围资源的合理访问以及它有资格访问的任何其他资源.
可以把内核看成是一个有特权的库例程集,随时准备接受调用.
对基本抽象的理解很重要,Linux内核的基本抽象:任务抽象和虚拟内存简化了从事内核和应用程序开发的程序员的工作.
任务抽象:
允许每个应用程序独立于其他程序运行----除非这些程序的开发者刻意在程序中引入交互.任务封装了在Linux系统中运行的应用程序的完整状态,包括这个任务所需的所有资源(打开的文件,分配的内存等等),处理器的状态以及其他细节等等.
struct task_struct { }; include/linux/sched.h头文件中有定义.
fork()出一个新进程的时候,task_struct被创建,或者,创建出新线程的时候也可以被创建.Linux不像其他操作系统那样有一个专门针对线程的内部抽象,而是以大致相同的方式来对待进程和线程。对Linux内核来说,一个线程就是一个与现有任务共享相同内存和其他系统资源的任务结构,但它有自己的处理器状态。任务还可以针对被称为内核线程的特殊线程来分配。
任务一旦被创建就将一直存在,除非它成功结束或者被一个信号杀死或试图执行一个非法操作。Linux负责调度每个任务,根据系统所用的调度策略来保证每个任务都能公平地分享处理器时间。每当任务被调度出处理器时,任务结构中的特殊域将被用于保存当时的处理器状态以便当任务再度获取处理器时间时,它可以恢复运行。
用进程或者线程来表示Linux中的任务,用top或ps这样的工具来查看当前系统中运行的任务。一个典型的Linux桌面上可能同时有数百个进程或线程在运行。
在内核代码中,current宏总是指向处理器当前执行的任务。
调度器
Linux调度器是所有Linux系统的核心。这段内核代码负责跟踪给予任务系统CPU时间的频率以及每次分配多长时间----当前任务的时间片或量子。调度是对系统为满足用户交互所产生的各种差别的强烈需求的一种巨大的折中,它为用户应用程序提供最大限度的处理吞吐量,同时又能与其他各种系统活动在各种复杂的记账环境中保持协调一致。
schedule()函数可用来手工调用调度器。当在一个长期运行的代码路径中为系统调用服务时,我们偶尔会使用它来主动放弃CPU,将CPU让给另一个任务。然后,Linux可能会根据各种因素选择运行另一个任务并将当前任务置于睡眠状态,直到稍后的某个时间为止。“稍后的某个时间”:你无法确定调度器什么时候会再次唤醒一个阻塞的任务。
编写内核代码时,通常不需要担心Linux调度器的内部活动----调度器的内部设计是那些开发调度器的家伙们所需要关心的。不过你必须意识到:Linux系统必须支持许多并发执行的任务以向系统用户一种多处理的错觉。只有当调度器被调用得足够频繁时才能提供这样一种能力以允许其他任务能够公平分享系统处理器的时间。
内核的抢占性:一个更重要的任务在准备好之后可以尽可能快的开始运行,而不用等待另一个任务中的任何已有的活动执行完毕。但内核中的抢占并非总是可能的。
系统调用
内核和任务之间的关系类似于软件函数库和应用程序之间的关系。用户程序通过一个被称为系统调用的已定义的软件接口与内核进行通信。它们通过改变使用的系统调用编号以请求Linux内核代表它们执行特定的操作。当内核完成该操作之后,将把执行结果放入一个适当的响应中以返回给应用程序。
往往不是直接调用系统调用来完成,会依赖于标准GNU C函数库,该库为应用程序提供一系列的实用函数,而反过来它们又依赖于系统调用来执行它们的部分操作。例如,往一个打开的文件中写入文本就依赖于write()系统调用,它被包裹在同名的C库函数中。C库函数负责设置系统调用并获取返回代码。
通常系统调用的代价被认为是太高,所以GNU C函数库做了很多优化以提高运行速度。可以在命令行中使用strace命令来跟踪一个正在运行的程序中的系统调用,该命令将显示程序中的所有系统调用、它们的返回值和一个特定程序接收到的所有信号。它还将显示出由其他系统进程活动或因某些时间的发生而导致Linux内核发送给该程序的任何信号。
ls首先会使用execve()来启动一个新的进程,然后会调用brk()和mmap()来扩展进程的内存地址空间。最后系统通过调用open()、getdents64()和使用其他文件操作来获得当前目录中的每个文件的信息。
执行上下文
非特权用户上下文:任务可以在各种特权上下文中执行。一个普通的用户程序不能直接与系统硬件交互,也不能干扰系统中其他运行程序的操作。
特权用户上下文:系统中的超级用户root。root用户并不直接拥有对机器中的硬件的某种特权,只能通过其具备的可信任能力来要求内核为它执行某些特殊操作(关机、杀死任务、其他特权用户操作等)
进入内核时,系统在被称为内核上下文的特权上下文中执行代码,程序进入了内核态。在内核态中,代码可以和硬件设备直接交互,并操纵敏感的内核数据结构以满足内核给出的各种请求。
进程上下文&中断上下文
内核线程
拥有在一个进程环境中运行特权内核代码的独特能力的特殊任务,被称为内核线程。
当内核程序员需要一个可独立调度的实体(它偶尔能够处理一些数据并在被再次唤醒之前返回睡眠状态),他们会使用内核线程。
线程标志 ps的名称显示里面加 "[]"的是内核线程
线程pid 系统的启动是从硬件->内核->用户态进程的,pid的分配是一个往前的循环的过程,所以内核线程的pid往往很小。
内核线程 Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求) 内核需要多个执行流并行,为了防止可能的阻塞,多线程化是必要的;因为各个执行流共享地址空间,所以是多线程
微内核 Linux/FreeBSD/Solaris/Windows 200 都不能归为微内核的----微内核的系统,是将文件系统等核心服务归为独立的_进程_,而非_线程_,一些类别(如 ksoftirqd)的线程的数目和处理器数目关联
典型的内核线程:
events 处理内核事件。很多软硬件事件(比如断电,文件变更)被转换为events,并分发给对相应事件感兴趣的线程进行响应
ksoftirqd 处理软中断。硬件中断处理往往需要关中断,而这个时间不能太长,否则会丢失新的中断。所以中断处理的很大一部分工作移出,转给任劳任怨的ksoftirqd在中断之外进行处理。比如一个网络包,从网卡里面取出这个过程可能需要关中断,但是TCP/IP协议处理就不必关中断了
kblockd 管理磁盘块读写
kseriod
khubd
khelper
scsi_eh_0
ahc_dv_0
kjournald Ext3文件系统的日志管理。通常每个已mount的 Ext3分区会有一个 kjournald看管,各分区的日志是独立的
pdflush dirty内存页面的回写。太多dirty的页面意味着风险,比如故障时候的内容丢失,以及对突发的大量物理内存请求的响应(大量回写会导致糟糕的响应时间)
kswapd 内存回收。确保系统空闲物理内存的数量在一个合适的范围
aio 代替用户进程管理io。用以支持用户态的AIO。
migration 管理多核心(包括HypterThreading衍生的那个不大管用的、大家都不愿迁过去的核心)系统之中,线程在各核心的迁移
典型的用户进程:
crond 执行定时任务
init 引导用户空间服务,管理孤儿线程,以及运行级别的转换(init N, 或者 kill -SIG pidof(init))。如果kernel thread是仙界的神灵的话,那么init应该是神灵用泥土捏出的尘世的第一个子民 ($ pstree),init肩负使命,开创了尘世的繁荣。(init按照配置文件,安排自己的后代执行相关服务)
mingetty 等待用户从tty登录,对于习惯远程登录的Linux server,不难想象,自出生起,等待那些等候在tty窗口的mingetty的,往往是没有尽头的等待。或许故障处理的时候,才有人依据系统报警的提示,在tty敲几个故障处理的命令。
bash shell进程,一个命令行形式的系统接口;接受用户的命令,并进行解释、执行。
sshd ssh登录、文件传输、命令执行 等操作的服务进程
klogd 从内核信息缓冲区获取打印信息。内核在发现异常的时候,往往会输出一些消息给用户,这个对于故障处理很有用
syslogd 系统日志进程
udevd 支持用户态设备操作 (userspace device) 越来越多的东西在往用户态转移,又如用户态文件系统,GmailFS就把Gmail当作了一个文件系统来用;很容易用它写一个 helloworld fs。
Linux内核使用内核线程来将内核分成几个功能模块,像kswapd,kflushd等,系统中的init进程也是由idle进程调用kernel_thread()来实现产生的. 内核线程被作为一种手段,在内核里提供某些类型的重量级处理。例如,内核调页守护进程就作为一个内核线程在运行,它偶尔会调整可用内存并将未用的数据交换到系统的缓存空间中。
虚拟内存
每个任务自己的内存环境,被称为地址空间。每个进程的地址空间通过被称为虚拟内存的基本抽象与其他进程的地址空间分开。虚拟内存使得进程不需要关心系统中其他应用程序的内存使用情况,同时也在用户程序和Linux内核之间构成一部分屏障,这要归功于系统MMU中的各种电路。
虚拟内存使得每个程序独立于其他程序进行编译和连接。每个程序都拥有一个范围广泛的内存空间,地址从0开始一直到达一个特定系统或者单独处理器存在的上限(由管理员定义的每个用户可以使用的资源上限通常被用来设置一个指定进程的实际限制)。大多数32位Linux系统中,每个进程地址空间的上限是3GB,这允许每个用户进程的大小达到3GB。在所需内存受到各种系统资源限制的情况下,每个进程可以对其自己的地址空间进行随意的操作,尽管在实际情况中不同的事实标准确实会影响进程可以进行的操作。例如,对进程中的栈、程序代码和数据都由事实上的标准化位置。
1. 页抽象
一个页只是一组连续的内存地址,它的容量通常都非常小----在Linux系统中是4K字节大小的页,确切的大小和体系结构、平台相关。页构成了虚拟内存概念实现的关键部分。系统中的物理内存被分为数以千计的页(它们根据所选择页的大小在内存中对齐----例如在4K字节的边界对齐),这些页由一系列内核中的列表来跟踪空闲页列表、已分配页列表,等等。然后,在系统MMU中的虚拟内存硬件负责在页面级别将虚拟内存地址转换为物理地址。
0x10 001 234 地址(虚拟地址) --> MMU --> 物理地址
当进程需要读取存储在制定虚拟地址0x10 001 234中的数据时,该虚拟地址会先由系统中的虚拟内存硬件通过从该虚拟地址中提取出适当的页帧并查找对应的物理页,从而将它转换成为一个物理地址。例如,如果页面大小是4K,则虚拟页帧号0x10 001将首先被映射到系统内存中某处的物理页,然后在该物理页的偏移位置0x234对数据进行任何内存读写操作。
系统虚拟内存硬件并不是通过一些神奇的过程就知道虚拟地址和物理地址之间的具体转换关系的。内核维护了一系列的硬件飚,当MMU需要执行转换时,它会在必要的时候直接查询这些表。Linux本身也实现了各种页跟踪机制和列表以在必要的时候更新硬件表。当编写普通内核代码的时候,是不需要担心页管理过程中的精确机制的,只需要意识到页抽象就行了。
2. 内存映射
装载程序时,Linux内核的ELF解析器会从二进制文件中提取出程序代码、数据和其他程序段在进程地址空间中装载的地址。作为应用程序启动过程中的一部分,动态连接器负责确保任何共享函数库也被动态映射到进程地址空间中所需要的地址。
正在运行的程序可以利用mmap()系统调用来改变它们的内存映射。这个特殊的系统调用可以将一个特定的文件映射到指定的内存范围来读、写、执行。mmap()用于映射共享函数库,并作为一种快速机制用于直接处理数据文件,而不是调用C函数库中的文件读写例程来处理文件。它还可以被用于映射匿名内存----没有磁盘上任何真实文件支持的内存,即为各类一般程序使用而调用如malloc()这样的C库例程分配的内存。
cat /proc/$$/mmaps可以看到系统中执行的当前程序的地址空间,准确说,是cat /proc/pid/mmaps可以看到pid号的程序的地址空间。 例如:
root@Rachel:~# cat /proc/20233/maps 00400000-004c2000 r-xp 00000000 08:01 1392645 /bin/bash 006c1000-006cb000 rw-p 000c1000 08:01 1392645 /bin/bash 006cb000-006d0000 rw-p 006cb000 00:00 0 00bc7000-00c1a000 rw-p 00bc7000 00:00 0 [heap] 7f5b009e2000-7f5b009ed000 r-xp 00000000 08:01 794709 /lib/libnss_files-2.8.90.so 7f5b009ed000-7f5b00bec000 ---p 0000b000 08:01 794709 /lib/libnss_files-2.8.90.so 7f5b00bec000-7f5b00bed000 r--p 0000a000 08:01 794709 /lib/libnss_files-2.8.90.so 7f5b00bed000-7f5b00bee000 rw-p 0000b000 08:01 794709 /lib/libnss_files-2.8.90.so 7f5b00bee000-7f5b00bf8000 r-xp 00000000 08:01 794719 /lib/libnss_nis-2.8.90.so 7f5b00bf8000-7f5b00df7000 ---p 0000a000 08:01 794719 /lib/libnss_nis-2.8.90.so 7f5b00df7000-7f5b00df8000 r--p 00009000 08:01 794719 /lib/libnss_nis-2.8.90.so 7f5b00df8000-7f5b00df9000 rw-p 0000a000 08:01 794719 /lib/libnss_nis-2.8.90.so 7f5b00df9000-7f5b00e0f000 r-xp 00000000 08:01 794703 /lib/libnsl-2.8.90.so 7f5b00e0f000-7f5b0100e000 ---p 00016000 08:01 794703 /lib/libnsl-2.8.90.so 7f5b0100e000-7f5b0100f000 r--p 00015000 08:01 794703 /lib/libnsl-2.8.90.so 7f5b0100f000-7f5b01010000 rw-p 00016000 08:01 794703 /lib/libnsl-2.8.90.so 7f5b01010000-7f5b01012000 rw-p 7f5b01010000 00:00 0 7f5b01012000-7f5b0101a000 r-xp 00000000 08:01 794705 /lib/libnss_compat-2.8.90.so 7f5b0101a000-7f5b01219000 ---p 00008000 08:01 794705 /lib/libnss_compat-2.8.90.so 7f5b01219000-7f5b0121a000 r--p 00007000 08:01 794705 /lib/libnss_compat-2.8.90.so 7f5b0121a000-7f5b0121b000 rw-p 00008000 08:01 794705 /lib/libnss_compat-2.8.90.so 7f5b0121b000-7f5b01384000 r-xp 00000000 08:01 794662 /lib/libc-2.8.90.so 7f5b01384000-7f5b01583000 ---p 00169000 08:01 794662 /lib/libc-2.8.90.so 7f5b01583000-7f5b01587000 r--p 00168000 08:01 794662 /lib/libc-2.8.90.so 7f5b01587000-7f5b01588000 rw-p 0016c000 08:01 794662 /lib/libc-2.8.90.so 7f5b01588000-7f5b0158d000 rw-p 7f5b01588000 00:00 0 7f5b0158d000-7f5b0158f000 r-xp 00000000 08:01 794677 /lib/libdl-2.8.90.so 7f5b0158f000-7f5b0178f000 ---p 00002000 08:01 794677 /lib/libdl-2.8.90.so 7f5b0178f000-7f5b01790000 r--p 00002000 08:01 794677 /lib/libdl-2.8.90.so 7f5b01790000-7f5b01791000 rw-p 00003000 08:01 794677 /lib/libdl-2.8.90.so 7f5b01791000-7f5b017c8000 r-xp 00000000 08:01 794700 /lib/libncurses.so.5.6 7f5b017c8000-7f5b019c7000 ---p 00037000 08:01 794700 /lib/libncurses.so.5.6 7f5b019c7000-7f5b019cc000 rw-p 00036000 08:01 794700 /lib/libncurses.so.5.6 7f5b019cc000-7f5b019eb000 r-xp 00000000 08:01 794642 /lib/ld-2.8.90.so 7f5b01ab4000-7f5b01af3000 r--p 00000000 08:01 246596 /usr/lib/locale/en_US.utf8/LC_CTYPE 7f5b01af3000-7f5b01af4000 r--p 00000000 08:01 246601 /usr/lib/locale/en_US.utf8/LC_NUMERIC 7f5b01af4000-7f5b01af5000 r--p 00000000 08:01 246604 /usr/lib/locale/en_US.utf8/LC_TIME 7f5b01af5000-7f5b01bd6000 r--p 00000000 08:01 246595 /usr/lib/locale/en_US.utf8/LC_COLLATE 7f5b01bd6000-7f5b01bd7000 r--p 00000000 08:01 246599 /usr/lib/locale/en_US.utf8/LC_MONETARY 7f5b01bd7000-7f5b01bd9000 rw-p 7f5b01bd7000 00:00 0 7f5b01bd9000-7f5b01bda000 r--p 00000000 08:01 246605 /usr/lib/locale/en_US.utf8/LC_MESSAGES/SYS_LC_MESSAGES 7f5b01bda000-7f5b01bdb000 r--p 00000000 08:01 246602 /usr/lib/locale/en_US.utf8/LC_PAPER 7f5b01bdb000-7f5b01bdc000 r--p 00000000 08:01 246600 /usr/lib/locale/en_US.utf8/LC_NAME 7f5b01bdc000-7f5b01bdd000 r--p 00000000 08:01 246594 /usr/lib/locale/en_US.utf8/LC_ADDRESS 7f5b01bdd000-7f5b01bde000 r--p 00000000 08:01 246603 /usr/lib/locale/en_US.utf8/LC_TELEPHONE 7f5b01bde000-7f5b01bdf000 r--p 00000000 08:01 246598 /usr/lib/locale/en_US.utf8/LC_MEASUREMENT 7f5b01bdf000-7f5b01be6000 r--s 00000000 08:01 598259 /usr/lib/gconv/gconv-modules.cache 7f5b01be6000-7f5b01be7000 r--p 00000000 08:01 246597 /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION 7f5b01be7000-7f5b01bea000 rw-p 7f5b01be7000 00:00 0 7f5b01bea000-7f5b01beb000 r--p 0001e000 08:01 794642 /lib/ld-2.8.90.so 7f5b01beb000-7f5b01bec000 rw-p 0001f000 08:01 794642 /lib/ld-2.8.90.so 7fff09bd7000-7fff09bec000 rw-p 7ffffffea000 00:00 0 [stack] 7fff09bff000-7fff09c00000 r-xp 7fff09bff000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
可以看到,一个进程的地址空间看起来很复杂。左边显示了一个内存地址范围,其后是与该地址范围关联的权限,最后是支持改内存地址的磁盘文件。可以看到,共享地址是映射到bash进程的低端内存地址(这里好像不正确,64位系统)。最后是stack ,注意看一下64与32位系统的不同。
内核内存映射
通常程序不能使用地址空间内的所有的内存位置,32位Linux系统对用户程序的地址空间有3GB的上限限制,剩余的1GB空间留给内核使用。内核这样是为了能够提供更好的性能表现。
有系统调用时,处理器将切换到更高的特权级以执行内核代码,内核代码位于用户进程的高端地址空间中。内核代码使用它自己的内核栈并独立于它提供服务的程序而运行,不过它仍然保留对整个进程地址空间的完全访问权并可以自由操纵其中的数据。方便了通过系统调用在内核和进程之间传送缓冲数据,以及在必要的时候对程序中的其他数据进行访问。
特殊的硬件保护机制将组织用户应用程序访问内核数据。
任务地址空间
每个任务通过task_struct结构中的mm成员来说明它自己的内存使用情况。mm_struct又包括一组指向其他结构的指针,这些结构用于说明任务中映射的每个地址范围----每个结构对应/proc/pid/maps输出中看到的一个地址范围。任何一个任务的完整地址空间都可以变得非常复杂。任务的mm结构存储了有关任务地址空间的信息。每当内核需要告诉MMU一个制定任务的正确内存范围时,它就会查询这个结构。内核还通过查询mm_struct结构以确定是否应该告诉MMU允许对某个制定的内存位置进行读、写或执行操作。改结构还用于确定一个特定的内存范围是否需要在使用之前从磁盘交换进内存。
发生缺页时,任何试图对一个进程中的目前还不可用的地址中的数据进行读、写或执行操作都将导致缺页。系统的缺页处理程序将使用一个任务中的mm数据结构来确定内存访问是否合法,如果合法就产生特定的内存页。
并不是所有的任务都拥有mm_struct结构,例如线程有它们自己的task_struct结构,但是共享同一个内存地址空间。内核线程甚至在开始的时候还没有mm_struct结构。可以通过查看源代码来了解更多关于mm_struct结构的信息/usr/include/linux/sched.h中。
可装载模块的编写
TBD
内核开发过程
内核开发由三个关键的基础部件驱动----git内核SCM工具、LKML邮件列表和一个名字叫Andrew Morton的家伙。Andrew Morton维护他的-mm内核树作为递交给Linus的补丁的集结区
$ git clone git//git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git linus_26 $ git pull # 用来更新该树
gitk:图形化git树浏览器。好处是比较直观。
LKML: Linux内核邮件列表。lkml.org是一个不错的信件资料存档网站。补丁的提交,首先应该通过LKML提交给广泛的Linux内核社区供其评议。邮件里的标准礼节。
kernelnewbies组: kernelnewbies.org kernel janitor内核看门人
Andrew的-mm内核树被证明对于那些想要对还未进入到内核的实验性补丁进行测试的人们是一个好地方。Andrew有自己的用于快速应用和测试补丁的系统quilt: http://savannah.nongnu.org/projects/quilt
稳定内核小组: -stable小组。
LWN: Linux每周新闻http://www.lwn.net