《Linux Kernel Development》读书笔记
http://www.cppblog.com/luckycat/archive/2010/03/03/108836.html
去书城买来,饱读之后置于书架之上,扮作有学问之人:) 本想买英文影印版,未能寻到,就买本中文的凑合着看,
看完之后发现有几处明显的翻译错误(好在有英文CHM作为对照).技术类书籍一直都是读的英文CHM或PDF,这一本是例外.
chapter 1
1.当应用程序向内核请求调用一个系统调用时,我们说内核正在代其执行,如果进一步解释,在这种情况下,应用程序被称为通过系统调用在内核空间运行;而内核则被称为运行在进程上下文中.
2. 硬件与内核的交互:当硬件设备想和系统进行交互时,它首先要向CPU发送一个异步的中断信号,然后由CPU去打断内核当前正在执行的工作,中断通常对应着一个中断号,内核通过这个中断号来查找对应的中断处理程序,并调用这个找到的中断处理程序来处理中断.为了保证同步,内核可以停用中断,也就是忽略某个中断,既可以停止所有的中断处理程序,也可以有选择性地停止某些中断处理程序。许多操作系统的中断处理程序都不在进程上下文中,而是在一个单独的与所有的进程都无关的中断上下文中执行,这样做是为了保证中断处理程序在第一时间响应和处理中断信号,并快速退出.
3.任一时刻,CPU的活动范围为以下三者之一:
4.单内核与多内核:操作系统的内核设计分为两大阵营:单内核和微内核(以及在科研中的外内核)
大部分的UNIX和Linux是单内核系统.Linux同时也吸收了微内核的优点:内核模块化设计,抢占式内核,支持内核线程,以及动态内核模块加载和卸载.
5.Linux内核并不区分进程和线程,对于内核来说,只有进程,而且所有的进程都一样,只不过是有的进程共享一些资源而已.
6.Linux内核的版本号:x.y.z
chapter 2
7.内核源码文件结构:
8.内核中的内核空间都不分页,所以,如果内核空间使用了一个字节的内存,那么实际的可用的物理内存就少了一个字节。
9.在内核中没有内存保护机制.
10. 不要轻易在内核中使用浮点数.在用户空间进行浮点数操作时,内核会完成从整数操作到浮点数操作的模式转换,在执行浮点数操作时到底会做些什么,因体系结构的不同,内核的选择也会不同,但是内核通常捕获陷阱并做相应的处理.和用户空间进程不同,内核并不能完美支持浮点操作,因为它本身不能陷入.在内核使用浮点数时,除了要人工保存和恢复浮点计数器,还有其它的一些琐碎的事情要做.所以:不要在内核中使用浮点数.
11.内核开发中,不能使用内核源代码之外的其它的外部库文件.
12.内核中没有printf函数,但是有printk函数可以用于打印调试信息.
13. 内核的栈空间很小:内核栈的准确大小随体系结构而变.在X86系统中,栈的大小可以在编译时配置,可以是4KB,也可以是8KB.从历史上说,内核栈的大小是两页,这也就意味着,在32位系统上内核栈是8K,在64位系统上,内核栈是16K,这是固定不变的,每个处理器都有自己的栈.
14.硬件中断是异步到来的,由CPU发送给内核,完全不顾及内核当前的操作.
15.Linux内核中常用的用于解决并发产生的竞争的办法是:自旋锁和信号量.
chapter 3
15.进程:就是包含各种资源的处于执行期的程序.
16.线程:进程中的活动对象.每个线程都有一个独立的程序计数器,进程栈和一组进程寄存器.
17.内核调度的是线程而不是进程.
18.Linux中,进程与线程并不特别进行区分,对于内核而言,线程只不过是一种特殊的进程而已.
19.进程的5种状态:
20.UNIX创建进程的方式:
21.Linux 的fork系统调用采用写时拷贝(copy-on-write)机制.写时拷贝是一种推迟甚至是免除数据拷贝的技术.内核并不是复制整个进程地址空间,而是让父进程和子进程以只读的方式共用一个拷贝。只在在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝.
22.fork系统调用是通过clone系统调用来实现的.
23.fork系统调用后,系统有意让子进程先执行。因为一般子进程会马上调用exec函数,这样可以避免写时拷贝的额外开销,如果父进程先执行,有可能会开始向地址空间写入.
24.vfork:vfork系统调用和fork系统调用的功能类似,除了不拷贝父进程的页表项,子进程作为父进程的一个单独的线程执行,父进程被阻塞,直到子进程退出运行,子进程不能向地址空间中写入。
25.concurrent与parallelism的区别:
26.Linux把所有的线程都当作是进程来处理.线程仅被视作一个与其它的进程共享一些资源的进程
27.Windows和Sun Solaris则是从内核的角度来支持线程.
28.内核线程:独立运行于内核空间的标准进程,用于执行一些后台操作.内核线程与用户线程的区别在于,它们没有独立的地址空间,它们只运行于内核空间,从来不会切换到用户空间去运行.内核线程也可以被调度和抢占.
29.孤儿进程的处理:在当前进程的进程组内找一个进程作为父进程,如果不行,那么就让init做为它们的父进程.
chapter 4
30.调度程序:可以看作是在可运行态的进程之间分配有限的处理器时间的内核子系统.
31.多任务系统分为:非抢占式和抢占式多任务系统.
32.抢占式多任务:由调度程序来决定什么时候停止一个正在运行的进程而使得其它的进程有可执行的机会.
33.进程时间片:分配给每个可运行进程的处理器时间段.
34.非抢占式多任务:除非进程自已退出运行,否则它会一直点用处理器.
35.进程分为:处理器消息耗型和I/O消耗型.
36.优先级高的进程所获得的时间片也更长,调度程序总是调度时间片未用尽而优先级又最高的进程运行.
37.调度程序会提高I/O消耗进程的优先级,降低处理器消耗进程的优先级.
38.Linux内核提供两组独立的优先级范围:
39.进程时间片:它是一个整数值,表示进程在被抢占之前可以连续运行的最长的时间.进程的时间片不需要一次性用尽,可以分多次用完,这样,一个进程可以被调度运行多次,这对于I/O消耗类型的进程非常有利
40.当一个进程的时间片耗尽时,就认为进程到期了。没有时间处的进程不会再次被调度运行,要等到其它的所有的进程都耗尽了它们的时间片,也就是说剩余的时间片为0,在那个时间,所有的进程的时间片会被重新计算.
50. 每个处理器都有一个任务队列,这个任务队列里面有一个“可运行进程优先队列”和一个“已过期进程优先级队列”,当一个进程的时间片耗尽时,它会被从“可运行队列”移动到“已过期队列”,在移动在过期队列之前,它的新的时间片会被重新计算好。当“可运行队列”为空,也即是当前的CPU上的可执行的进程的时间片都已经耗尽时,这个时候会交换“可运行队列”和“过期队列”,这样“过期队列”中的所有的“已重新计算好时间片”的进程已可以重新投入运行,这就是 Linux内核中O(1)调度程序的核心.
51.每一个CPU都有一个对应的schedule调度函数,用于决定当前的CPU上下一个可以执行的进程.
52.对于内核而言,如果一个进程的睡眠时间比运行时间长,那么这个进程是I/O消耗型的;如果一个进程的运行时间比睡眠时间长,那么这个进程是处理器消耗型的.
53.当一个新的子进程创建时,子进程会和父进程均分剩余的时间片,这样就可以避免用户通过不停地创建子进程来不停地攫取时间片.
54.重新计算进程的时间片时,只依据进程的静态优先级,这个优先级在进程状态时由用户指定,一旦指定,这个优先级就不会被改变.优先级越高,进程所获得的进程运行时间片就越长.
55.内核对进程“休眠”和“唤醒”的处理:
56.进程的用户抢占:是指进程在内核空间返回到用户空间时或是从中断处理程序中返回到用户空间时,如果进程的need_resched标志被重新标记了,那么进程就需要被重新调度.
57.在Linux系统中,内核进程也可以被抢占.只要没有锁,内核就可以进行抢占.
chapter 5
58.系统调用的作用:
59.UNIX的设计原则是:提供机制而不是策略.换句话说,UNIX系统抽象出了用于完成某种确定目的的函数,至于这些函数怎么用完全不需要内核去关心.
60.系统调用的实现原理:
61.当内核接收一个用户空间的指针时,内核 必须保证:
62. 硬件与内核通信的机制是:中断机制,当硬件需要和系统通信时,硬件向系统发送一个中断请求,中断在本质上是一种特殊的电信号。这个中断请求实际上并不是硬件直接发送给内核的,而是硬件先发送给CPU,再由CPU向内核发送中断信号,内核再中断当前的工作调用相应的中断处理程序,从而完成内核与硬件的通信. 硬件的中断可以随时产生,因此,处理器也需要随时响应中断信号.硬件中断不用考虑与处理器的时钟同步问题,但是“异常”需要考虑与处理器的时钟同步问题.
63.每个中断信号都有一个唯一的数值.这样内核才能区分中断信号来自于哪个硬件,更进一步,内核才能为对应的硬件中断信号调用其中断处理程序.
64.中断信号的值被称为“中断请求线”(IRQ).
65."异常"必须考虑与处理器的时钟同步问题,因此“异常”也被称作为“同步中断”.在处理器遇到编程失误或是特殊情况而需要由内核来处理的时候,处理器就产生一个“异常”,因为许多体系结构处理“异常”与处理“中断”的方式很相似,所以,内核对于它们的处理也很相似.
66.“中断”必须是由硬件产生的,而“异常”可以是由软件产生的.
67. 在响应一个中断的时候,内核会执行一个函数,这个函数叫做“中断处理程序”(interrupt handler)或者是“中断服务例程”(interrupt service routine,ISR)。中断处理程序是和特定的中断关联,而不是和硬件关联的,这样一来,如果一个硬件可以产生多种中断,那么它的设备驱动程序就要提供多个中断处理程序.
68.中断处理程序与其它的内核函数的区别在于:中断处理程序是被内核用来响应中断的,而且它们只运行在我们称之为“中断上下文”的特殊上下文中.
69.中断处理程序一般被切分为两部分完成:上半部和下半部.
70.设备驱动程序:实际上就是对设备所产生的中断进行处理的中断处理程序的集合,所有的这个硬件设备的中断处理程序被一起提供给内核用于内核完成该硬件设备的中断处理.
71.中断共享:中断共享的函数就是“一个中断信号值可以由多个硬件产生”.
72.如果某个中断信号被屏蔽,那么当硬件设备向处理器发送对应的中断电信号时,在处理器将这个中断通知内核时,内核就不会去响应这个中断信号.
73.Linux 中的中断处理程序是无需重入的.当一个给定的中断处理程序正在执行时,这个中断在所有的处理器上都会被屏蔽掉,以防止在同一个中断线上接收另一个新的同样的中断信号.通常情况下,所有的其它的中断都没有被屏蔽而是打开的,所有这些不同的中断线上的其它中断都能够被响应,但当前的中断线总是被禁止的.由此可以看出,同一个中断处理程序绝对不会被同时调用以处理嵌套的中断,这极大地简化了中断处理程序的编写.
73.内核接收到中断后,会依次调用当前的中断线上注册的每一个中断处理程序.
74.进程上下文是针对内核而言的,它是指内核所处的操作模式,此时内核代表进程执行.
75.中断处理机制的实现.
76.Linux内核提供了一组接口用于操作机器上的中断状态,这些接口为我们提供了能够禁止当前处理器的中断系统,或屏蔽掉整个机器的一条中断线的能力.
chapter 7
77.中断处理流程分为:上半部和下半部两部分.
78.上半部和下半部的区分取决于开发者自己的判断,通常的规则是:
79. Linux内核提供的3种下半部机制:
执行软中断:一个注册的软中断必须在被标记为才会执行,这被称作“触发软中断”,通常中断处理程序会在返回前标记它的软中断,使其在稍后执行.在以下时刻,软中断会被检查和执行:
内核定时器和tasklet都是建立在软中断的基础之上的.
80.当你需要保证工作被推迟到某一个指定的时间去执行时,那么你需要使用内核定时器机制.
81.每个处理器都有一个对应的“软中断辅助处理线程”:ksoftirqd/n。
82.临界区:就是访问和操作共享数据的代码断.代码在执行完成之前不能够被打断.
83.处理器会保证任何的两个处理器原子指令不会同时执行.也就是说:如果有两个原子A,B指令要执行,那么要么A执行完之后再执行B,要么B执行完后再执行A,不可以出现A执行一半再执行B然后再执行A,最后又执行B的情况.
84.内核中有可能造成并发执行原因:
85.对称多处理器--两个或多个处理器可以同时执行代码.以及如何安排同步的顺序.
86.并发开发的难点就在于找出所有的潜在的可能发生竞争和数据同步的地方.
87.几种安全代码:
88.在并发开发中:我们实际上要保护的是数据而不是代码.
89.线程中的局部数据不需要加锁,因为线程上的局部数据是存储在线程的栈空间的中的,特定于每一个线程的栈空间中都有一份自己的线程局部数据,它们互不影响.一条原则时,如果公有数据可能同时被多个线程访问,那么这些公有数据需要被访问.
90.避免死锁的方法:
91.原子操作:执行过程不会被打断的操作.
92.读写内存中的一个字的操作是原子操作,也就是说,在对这个内存字的读的过程中,不会出现对该字的写的过程,在对该字的写的过程中,不会出现对该字的读的过程.
93.原子性:确保指令执行期间不会被其它的操作打断,也就是说这个指令一次性完成,在这条指令完成的过程中,不会有其它的指令执行.
94.顺序性:两条或多条指令出现在独立的执行线程中,甚至独立的处理器上,但它们本该执行的顺序依然要得到保持.
95.自旋锁为多处理器机器上提供了防止并发访问的数据所需要的锁保护机制.
96.Linux下的自旋锁是不可递归的.
97.当一个线程在试图获取自旋锁时,如果这个锁已被其它的线程所获取,那么这个等待的线程不会因为等待这个自旋锁而休眠,相反,这个等待的线程会一直尝试去获取这个自旋锁。
98. 自旋锁可以使用在中断处理程序中(此处不能使用信号量,因为它们会导致睡眠),在中断程序中调用自旋锁时,一定要在获取锁之前首先禁用本地中断(在当前的处理器上的中断请求),否则,中断处理程序可能会打断正持有锁的内核代码,有可能会试图争用这个已经被持有的自旋锁,这样一来,中断处理程序就会自旋,等待该锁重新可用,但是锁的持有者在这个中断处理过程处理完成之前不可能运行,这正是我们在前一章节中提到的“双重请求死锁”。注意,需要关闭的只是当前的处理器上的中断,如果中断发生在不同的机器上,即使中断处理程序在同一锁上自旋,也不会妨碍锁的持有者(在不同的处理器上)最终释放锁.
99.要记住一点:在任何时候,我们加锁是为了锁住数据,而不是为了锁住代码.
100.对于自旋锁和下半部:
101. 信号量:信号量支持两个原子操作P和V,前者叫做测试操作,后者叫做增加操作,后来系统把这两个操作分别叫做down和up。down通过对信号量计数减 1来请示获得一个信号量,如果结果是0或者大于0,那么获取锁成功,进入到临界区中.如果结果是负数,那么任务就会被放入到等待队列,对应的进程也会进入休眠.处理器此时可以执行其它的操作.相反,在临界区中的操作完成之后,通过up操作来释放信号量,该操作也被称作是提升信号量的值,因为它会增加信号量的计数,如果在该信号量上的等待队列不为空,那么处于队列中的等待的任务就会被唤醒同时获得该信号量.
102.内核中的 barrier的作用是保证:一个操作必须在另一个操作之前完成这一点不会被编译器或者是处理器改变.
chapter 10
103.系统定时器:一种可编程的硬件芯片,它能以固定频率产生中断,这种中断就是所谓的“定时器中断”.它所对应的中断处理程序负责更新系统时间,还负责执行需要周期性执行的任务.系统定时器和时钟中断处理程序是Linux系统内核管理机制中的中枢.
104.动态定时器: 一种用来推迟执行程序的工具,内核可以动态创建或销毁动态定时器.
105. 系统定时器以某种固定的频率自动触发时钟中断,这种频率可以通过编程预定,称作节拍率,当时钟中断发生时,内核就通过一种特殊的中断处理程序对其进行处理.因为预编的节拍率对内核来说是已知的,所以内核知道两次连接的时钟中断间隔的时间,这个间隔时间就被称作:节拍(tick)。它等于节拍频率分之一,内核就是靠这种时钟间隔来计算墙上时间和系统时间.
106.jiffies:记录自系统启动以来的时钟节拍数.
107.实时时钟:RTC,是用来持久存放系统时间的设备,即使系统关闭后,它也可以靠主板上的微型电池提供的电力保持系统的计时,在PC体系结构中,RTC和CMOS集成在一起,而且RTC的运行和BIOS的保存设置都是通过同一个电池供电的.
108.实时时钟的最大作用是在启动时初始化xtime变量.
109.系统定时器:通过对电子晶振进行分频来实现系统定时器.
110.在X86系统中,主要采用可编程中断时钟(PIT)。在X86系统中还包括本地APIC时钟和时间戳计数(TSC)等时钟资源.
111.时钟处理程序:时钟处理程序可以分为两部分,体系结构相关的部分和体系结构无关的部分。
112.动态定时器:它并不周期性地运行,它在超时后就自行销毁,这也是这种定时器被称为动态定时器的原因,动态定时器不断创建和销毁,而且它的运行次数也不受限制。
113.定时器会在指定的定时值到达之后开始运行.在运行完成之后,这个定时器会被删除,所以如果你想要一个定时函数周期性地运行下去,那么你需要在定时器超时后重新设定定时器,也就是在定时器处理函数的最后重新设定这个定时器.
114.volatile变量可以强制使得编译器在每次访问变量时都重新从主内存中获取而不是通过寄存器中的变量的别名来获取,这样就可以保证变量的值永远都是最新的.
115.经验表明,不要使用udelay来处理超过1毫秒的延迟,在延迟超过1毫秒的情况下,使用mdelay更为安全.这些函数的实现使用基于循环的忙等待.
116.MIPS:Million Instruction Per Second。处理器每秒执行的百万条指令数.
chapter 11
117.内存页:内核把物理内存页作内存管理的基本单位,尽管处理器的最小可寻址单位通常是字(甚至字节),但是,内存管理单元(MMU:管理内存并把虚拟地址转换为物理地址的硬件)通常以页为单位来进行处理,正因为如此,MMU大小为单位来管理系统中的页表(这也是页表名的由来),从物理内存的CPU角度来看,内存的最小单位是字节,但是从虚拟内存的角度来看,页就是最小单位.
118.在32位机上,一个内存页的大小是4K,而在64位机上,内存页的大小为8K。所以在32位机上,1G的内存会被分成262144个页.
119.内存区:内核并不是对所有的内存页都一致对待,内核使用区对具有相似特性的页进行分组。
120.内核slab层:slab层把不同的对象划分为所谓的“调整缓存”组,其中每个高速缓存都存放不同类型的对象,每种对象类型对应于一个高速缓存,例如,一个高速缓存用于存放进程描述符,另一个高速缓存用于存放索引节点.slab由一个或多个物理上连续的页组成.
chapter 12
121.虚拟文件系统:有时也称作虚拟文件交换(VFS),采用面向对象的设计思路,作为内核子系统,为用户空间提供了系统相关的接口,系统中所有的文件系统不但依赖于VFS共存,而且也依靠VFS系统协同工作。
122.UNIX文件系统:UNIX使用了4种与文件系统相关的传统抽象概念:
123.文件系统:从本质上讲,文件系统是特殊的数据分层存储结构,包含文件,目录和相关的控制信息。在UNIX系统中,文件系统被安装在一个特定的安装点上,该安装点在全局层次结构中被称作命名空间,所有已安装的文件系统都作为根文件系统树的枝叶出现在系统中.
124.UNIX是面向流的文件系统,其它的操作系统中有面向记录的文件系统.UNIX将相关信息和文件本身这两个概念加以区分.
125.VFS中的4种主要对象:
126.目录项对象的状态:被使用,未被使用,负状态.
127.如果系统中有大量的进程都要打开超过32个文件,为了优化性能,管理员可以适当增大NR_OPEN_DEFAULT的值.
chapter 13
128.Linux系统中的设置类型分为“块设备”和“字符设备”
块设备:能够随机的访问固定大小数据片的设备,如果磁盘,软盘驱动器,CD-ROM。它们都是以安装文件系统的方式使用。
字符设备:字符设备按照字符流的方式被访问,像串口和键盘就属于字符设备。
这两种设备的本质区别在于是否可以进行随机访问.
129. 块设备的最小的可寻址单元是扇区,扇区大小一般是2的倍数,而最常见的大小是512字节,扇区的大小是设备的物理属性,扇区是所有的块设备的基本单元,块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区,虽然大多数设备的扇区的大小都是512字节,不过其它大小的扇区也是很常见的,比如,CD-ROM的扇区大小就是2K.
130.虽然物理磁盘都是按扇区级进行寻址的,但是内核却是基于块的方式来操作磁盘的,所以块必须是扇区大小的整数倍,而且要小于页面的大小,所以通常块的大小是512字节或是4K.
131.为了优化高度程序,内核会在提交I/O请求到磁盘之前所将这些请求进行“合并与排序”,从而每次I/O请求所消耗的时间.
chapter 14
132.进程的地址空间包括:
a.代码段:可执行文件代码的内存映射
b.数据段:可执行文件的已初始化全局变量的内存映射.
c.BSS的零页:包含未初始化全局变量的内存映射.
d.进程用户栈:不要和进程的内核栈混淆,进程的内核栈独立存在并由内核维护。
e.每一个诸如C库或动态连接程序等共享库的代码段,数据段和bss也会被载入进程的地址空间。
f.任何内存映射文件.
g.任何共享内存段.
h.任何匿名的内存映射,比如由malloc分配的内存.
进程地址空间中的任何有效地址都只能位于唯一一个区域,这些内存区域并不能相互覆盖,可以看到,在执行的地址中,每个不同的内存片段都对应一个独立的内存区域:栈,对象代码,全局变量,被映射的文件等等.
133.Linux中线程与进程的唯一区别几乎是:是否共享地址空间.
134.内核线程没有进程地址空间,也没有相关的内存描述符,所以内核线程对应的进程描述符中的MM域为空,事实上,这也正是内核线程的真实含义--它们没有用户上下文。
135.平坦地址空间:描述的是地址空间范围是一个独立的连续空间(比如从0扩展到429496729的32位地址空间)。
136.进程的地址空间之间互不相干.两个不同的进程可以在相同的地址空间上存放相同的数据,但是进程之间也可以共享地址空间,我们称这样的进程为线程.
137.VMA:虚拟内存区域.
138. 页表:虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但是处理器直接操作的却是物理内存,所以,当程序访问一个虚拟地址时,首先必须将虚拟地址转化为物理地址,然后处理器才能解析地址访问请求,地址的转换工作需要通过查询页表来完成,概括地讲,地址转换需要将虚拟地址分段,使每段虚拟地址都作为一个索引指向页表,而页表则指向下一级别的页表或者指向最终的物理页面.
139.Linux中使用三级页表完成地址转换。得用多级页表能够节约地址转换所需要占用的空间,但如果利用三级页表转换地址,即使是64位机器,占用的地址空间也是很有限的,但是如果使用静态数组来实现页表,那么即使是在 32位机器上,该数组也将占用巨大的存放空间。Linux对所有的体系结构,所括对那些不支持三级页表的体系结构都使用三级页表进行管理.
140.三级页表结构:
chapter 15
141.页高度缓存:是Linux内核实现的一种主要的磁盘缓存,这主要用来减少对磁盘的IO操作,具体地讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问。
142.磁盘高速缓存的价值主要存在于两个方面:
a. 访问磁盘的速度要远远低于访问内存的速度,因此,从内存访问数据比从磁盘访问速度更快。
b. 数据一旦被访问,就很有可能在短期内再次被访问到。这种短时期内集中访问同一片数据的原理被称作“临时局部原理(temporal locality)”,临时局部原理能够保证,如果在第一次访问数据时缓存它,那就极有可能在短期内再次被高速缓存命中(访问高速缓存中的数据).
143.页高速缓存是由RAM中的物理页组成的,缓存中的每一页对应着磁盘中的多个块,每当执行一次磁盘操作时,会首先检查需要的数据是否在高速缓存中,如果在,那么内核就直接使用高速缓存中的数据,从而避免了磁盘访问.
144. 缓冲区高速缓存:通过I/O缓冲区把独立的磁盘块与页高速缓存联系在一起,一个缓冲就是一个单独物理磁盘块在内存中的表示,缓冲就是内存到磁盘块的映射描述符,因此通过缓存磁盘块以及缓冲I/O操作,页高速缓存也可以减少对磁盘的访问量.缓冲区高速缓存实际上并不是一个独立的缓存,而是页高速缓存的一部分.
145.当页高速缓存中的数据比后台磁盘中的对就数据更新时,那么调整缓存中的这些缓存数据被称作“脏数据”,需要在后面写回到磁盘.
chapter 16
146.Linux是“单块内核”(monolithic)的操作系统--也就是说,整个系统都运行于一个单独的保护域中,但是Linux内核是模块化的,它允许在运行时动态地向其中插入或是从中删除代码.这些代码--包括子例程,数据,函数入口和函数出口被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块,或被简称为“模块”。支持模块的好处是基本内核镜像可以尽可能小,因为可选的功能和驱动程序可以利用模块的形式再提供,模块允许我们方便地删除和重新载入内核代码,也方便了调试。
147.模块被载入后,就会动态连接到内核,注意,它与用户空间的动态连接库类似,只有当显示被导出后的外部函数,才可以被动态调用。在内核中,导出内核函数需要使用特殊的命令:EXPORT_SYMBOL和
EXPORT_SYMBOL_GPL。导出的内核函数可以被模块调用,而未导出的函数模块则无法被调用,模块代码的链接和调用规则相比核心内核镜像中的代码而言,要更加严格,核心代码在内核中可以调用任意非静态接口,因为所有的核心代码文件被链接成了同一个镜像,当然,被导出的符号表所含的函数必然也是非静态的.导出的符号表被看作是导出的内核接口,甚至称为“内核API”.
chapter 17
148.设备模型:设备模型专门提供了一种独立的机制来专门表示设备,并描述其在系统中的拓扑结构。保证能以正确的顺序关闭各设备的电源是设备模型的最初动机.
149.内核事件层:实现了内核到用户的消息通知系统,就是建立在上文一直讨论的kobjects基础之上的.
chapter 18
150.内核提供了printk这个函数用于显示调试信息.在任何时候,任何地方都可以调用它,它可以在中断上下文中调用,可以在进程上下文中调用,可以在持有锁时调用,可以在多处理器上同时调用,而且调用者连锁都不必使用.
151.神奇的SysRq
在i386和PPC上,它可以通过: ALT + PrintScreen 来访问:该功能可以通过CONFIG_MAGIX_SYSRQ配置选项来启用。
SysRq-b 重新启动机器
SysRq-e 向init之外的所有的进程发送SIGTERM信号
SysRq-h 在控制台显示SysRq
SysRq-i 向init之外的所有的进程发送SIGKILL信号
SysRq-k 安全访问键,杀死这个控制台上的所有程序
SysRq-l 向包括init的所有的进程发送SIGKILL信号
SysRq-m 所内核信息输出到控制台
SysRq-o 关闭机器
SysRq-p 所寄存器的信息输出到控制台
SysRq-r 关闭键盘原始模式
SysRq-s 把所有已安装文件系统刷新到磁盘
SysRq-t 所任务信息输出到控制台
SysRq-u 卸载所有已安装文件系统.
152.内核调试的利器: kdb
chapter 19
153.人们通常所说的机器是多少位,它们其实说的是机器的字长是多少位,也就是一个字的bit数.
154.处理器的通用寄存器的大小和它的字长是相同的。对于一般的体系结构来说,它的各个部件的宽度,比如,内存总线--最少要和它的字长一样大,地址空间的大小也等于字长.
155.Linux类型总对应于机器的字长.所以,我们可以通过 sizeof( long ) 为4还是8来判断是32位机还是64位机.一个指针变量的大小与寄存器的字节一致。32位机上是4字节,64位机上是8字节.
156.一个char的长度恒为 8 bit。在Linux支持的所有的系统上,int 为 32 bit
157.数据对齐:
158.避免对齐引发的问题
通常编译器会通过让所有的数据自然对齐来避免引发对齐问题,实际上,内核开发者不用在对齐上花费太多心思,只有搞GCC的那些老兄才应该为此犯愁。可是当程序员使用指针太多时,数据的访问方式走出编译器的预期时,就会引发问题了。
一个数据类型长度较小,它本来是对齐的,如果你用一个指针进行类型转换,并且转换后的类型长度较大,那么通过解引用指针进行数据访问时就会引发对齐问题(无论如何,对于某些体系结构确实存在这种问题),也就是说,下面的代码是错误的:
char dog[10];
char *p = &dog[ 1 ];
unsigned long l = *( unsigned long *)p;
这个盒子将一个指向char型的指针当作指向unsigned long型的指针来用,这会引起问题,因为此时试图从一个不能被4整除的内存地址上载入32位的unsigned long 型的数据.
159.非标准类型的对齐
前面说到了,对于标准数据类型来说,它的地址只要是长度的整数倍就对齐了,而非标准类型的C结构体按照下列规则对齐:
160.为了保证结构体中的每一个成员都能够自然对齐,结构体要进行“对齐填补”.
161.ANSI C 标准明确规定:不允许编译器改变结构体成员的顺序.
162.内核开发者需要注意结构体填补问题,特别是在整体使用时,这是指当需要通过网络发送它们或是需要将它们写入文件的时候,因为不同的体系结构之间需要的填补也不尽相同,这也是为什么C没有提供一个内建的结构体比较操作符的原因之一,结构体内的填充字节可能会包含垃圾信息,所以,在结构体之间进行一字节一字节的比较就不大可能了。
163.因为结构体可能有填充对齐的问题,所以,对于不同的相同类型的结构体对象,不能直接使用
memcmp来比较,而要直接成员之间的比较.
164.字节序:
165.编写可移植的代码
a. 编码尽量取最大公因子:假定任何事情都可能发生,任何潜在的约束也都存在
b. 编码尽量选取最小公约数:不要假定给定的内核特性是可用的,且仅仅需要最小的体系结构功能.
chapter 20
166.内核开发的优秀站点: