深⼊理解Linux内核(完整版)-笔记
第⼀章、绪论
1.Unix⽂件可以是下列类型之⼀:
a.正规⽂件(regular file)
b.⽬录(directroy)
c.符号链(symbolic link)
d.块设备⽂件(block-oriented device file)
e.字符设备⽂件(charactor-oriented device file)
f.管道(pipe)命名管道(named pipe)(即FIFO)
h.套接字(socket)
2.内核分配给进程的虚拟地址空间由以下内存区域组成:
a.程序的可执⾏代码
b.程序的初始化数据
c.程序的未初始化数据
d.初始程序栈(即⽤户态栈)
e.需要共享的库的可执⾏代码和数据
f.堆(由程序动态请求的内存)
3.设备驱动程序
内核通过设备驱动程序(device driver)与I/O设备打交道。设备驱动程序包含在内核中,由控制⼀个或多个设备的数据结构和函数组成。这些设备包括硬盘、键盘、⿏标和监视器等。通过特定的接⼝,每个驱动程序与内核中的其余部分(甚⾄与其他驱动程序)相互作⽤:
优点:
1.可以把特定设备的代码封装在特定的模块中。
2.⼚商可以不懂内核代码,只知道接⼝规范,就能增加新的设备。
3.内核以统⼀的⽅式对待所有的设备,并且通过相同的接⼝访问这些设备。
4.可以把设备驱动程序写成模块,并动态装到内核中,不需要重启系统,不需要的时候可以卸载模块,
以减少存储在RAM中的内核映像⼤⼩。
第三章、进程
1.进程状态:
a.可运⾏状态
b.可中断状态
c.不可中断的等待状态
d.暂停状态
e.僵死状态
第⼗章、进程调度
1.涉及进程调度
a.传统上把进程分类:
I/O范围(I/O-bound):频繁地使⽤I/O设备,并花费很多时间等待I/O操作的完成。
CPU范围(CPU-bound):需要⼤量CPU时间的数值计算应⽤程序。
b.⾮传统分类:
交互式进程:如命令shell、⽂本编辑程序及图像应⽤程序。
批处理进程:如编译程序、数据库搜索引擎及科学计算。
实时进程:视频和⾳频应⽤程序、机器⼈控制程序及从物理传感器上收集数据的程序。
第⼗⼀章、内核同步
1.内核同步,内核态进程的⾮抢占性:
a.在内核态中运⾏的进程不会被其他进程取代,除⾮这个进程主动放弃CPU的控制权
b.中断或异常处理可以中断在内核态中运⾏的进程。但是,在中断处理程序结束时,该进程的内核控制路径被恢复
c.执⾏中断或异常处理的内核控制路径只能被执⾏中断或异常处理的其他内核控制路径所中断
2.SMP原⼦操作从Intel 80286开始引⼊lock指令解决这中问题。
lock只是⼀个⽤于⼀条汇编指令之前的特殊字节。当控制单元检测到⼀个lock字节时,就锁定内存总线
这样其他处理器就不能存取下⼀条汇编语⾔指令的⽬的操作数所指定的内存单元。只有在这条指令执⾏完成时总线锁才会被释放。
因此,带有lock前缀的读-修改-写指令即使在多处理器环境中也是原⼦的。
3.内核数据结构进⾏同步访问的常⽤⽅法是使⽤信号量和⾃旋锁。
第⼗⼆章、虚拟⽂件系统
第⼗⼆章、虚拟⽂件系统
1.通⽤⽂件模型由下列对象类型组成:
a.超级块对象(superblock object):存放已安装⽂件系统的有关信息。对于基于磁盘的⽂件系统,
这类对象通常对应于存放在磁盘上的⽂件系统控制块(filesystem control block)。
b.索引节点对象(inode object):存放关于具体⽂件的⼀般信息。对于基于磁盘的⽂件系统,
这类对象通常对应于存放在磁盘上的⽂件控制块(file control block)。
每个索引节点对象都有⼀个索引节点号,这个号唯⼀地标识⽂件系统中的指定⽂件。
c.⽂件对象(file object):存放打开⽂件与进程之间交互的相关信息。这类信息仅当进程访问⽂件期间存在于内核内存中。
d.⽬录项对象(dentry object):存放⽬录项与对应⽂件进⾏连接的信息。每个基于磁盘的⽂件系统都以⾃⼰特有的⽅式将该类信息存在磁盘上。
linux内核设计与实现 pdf
slab分配⾼速缓存中。
e.进程->⽂件对象->⽬录项对象->索引节点->(超级块对象)->磁盘⽂件。
2.虚拟⽂件系统,⽬录项对象属于以下四种状态之⼀:
a.空闲状态:  还没有被VFS使⽤,对于德内存区由slab分配器进⾏管理。
b.未使⽤状态:  该⽬录项对象当前还没有被内核使⽤。该对象的引⽤计数器d_count为NULL。
但其d_indoe域只想相关索引节点。为了必要时回收内存,⽬录项包含有效信息,它的内存可能被丢失。
c.正在使⽤状态: 该对象被内核使⽤。d_count的值为正数。⽬录项包含有效的信息,并且不能被丢弃。
d.负状态:  与⽬录项相关的索引节点不复存在,那是因为相应的磁盘索引节点已被删除,d_count域被置NULL。
第⼗三章、管理I/O设备
1.三类内存地址:
a.逻辑地址(CPU内部使⽤)
b.线性地址(CPU内部使⽤)
c.物理地址(CPU从物理上驱动数据总线所⽤的内存地址)
d.总线地址(bus address):除CPU之外的硬件设备驱动数据总线所⽤的内存地址。
2.设备⽂件(mknod()系统调⽤来创建设备⽂件):⽤来表⽰Linux所⽀持的⼤部分I/O设备的,除了⽂件名,每个设备⽂件都还有三个主要属性。
a.类型(type):块设备或字符设备
b.主号(major number):从1到255之间的⼀个数,⽤以标识设备的类型,通常,具有相同主号和相同类型的所有设备⽂件共享相同的⽂件操作集合。
因为他们是由同⼀设备驱动程序处理的。
c.次号(minor number):在⼀组主号相同的设备之间唯⼀标识特定设备所使⽤的⼀个数字。
3.没有对应设备⽂件的I/O设备
⽐如⽹卡:⽹卡把向外发送的数据放⼊通往远程计算机系统的⼀条线上,把从远程系统中接受到的报⽂
装⼊内核内存。
由于没有使⽤⽂件系统,所以系统管理员必须建⽴设备名和⽹络地址之间的联系。
应⽤程序和⽹络接⼝之间的数据通信不是基于标准的有关⽂件的系统调⽤。
⽽是基于socket()、bind()、listen()、accept()和connect()系统调⽤。
这些系统调⽤对⽹络地址进⾏操作。这组系统调⽤由Unix BSD中⾸先引⼊,现在已经成为⽹络设备的标准变成模型。
4.VFS对设备⽂件的处理:设备⽂件也在系统的⽬录数中。但它们和正规⽂件及⽬录有根本的不同。当进程访问正规⽂件时,
它会通过⽂件系统访问磁盘分区的⼀些
数据块;⽽在进程访问设备⽂件时,它只要驱动硬件设备就可以了。VFS的责任是为应⽤程序隐藏设备⽂件与正规⽂件之间的差异。
VFS改变打开的设备⽂件的缺省⽂件操作。可以把对设备⽂件的任⼀系统调⽤转换成对设备相关的函
数的调⽤,⽽不是对主⽂件系统对于函数的调⽤。设备相关的函数对硬件设备进⾏操作以完成进程所请求的操作。
驱动程序:控制I/O设备的⼀组相关的函数称为设备驱动程序(device driver)。由于每个设备都有⼀个唯⼀的I/O控制器,因此也就有唯⼀的
命令和唯⼀的状态信息,所以⼤部分I/O设备类型都有⾃⼰的驱动程序。
5.设备⽂件调⽤open()函数执⾏的操作:
a.如果设备驱动程序被包含在⼀个内核模块中,那么把引⽤计数器的值加1,以便只有把设备⽂件关闭之后才能卸载这个模块。
b.如果设备驱动程序要处理多个同类型的设备,那么就是⽤次号来选择合适的驱动程序,如果需要,还要使⽤专门的⽂件操作表选择驱动程序。
c.检查该设备是否真正存在,现在是否正在⼯作。
d.如果必要,向硬件设备发送⼀个初始化命令序列。
e.初始化设备驱动程序的数据结构。
6.内核⽀持的级别
a.根本不⽀持:应⽤程序使⽤设当的in和out汇编语⾔指令直接与设备的I/O端⼝进⾏交互。
b.最⼩⽀持:内核不能设别硬件设备,但能识别I/O接⼝。⽤户程序把I/O接⼝是为能够读写字符流的顺序设备。
c.扩展⽀持:内核设备硬件设备,并处理I/O设备本⾝,事实上,这种设备可能就没有对应的设备⽂件。
7.访问I/O设备的地址,可以从/proc/ioports⽂件中获得。
8.内核对于块设备的⽀持特点:
a.通过VFS提供统⼀接⼝
b.对磁盘数据进⾏有效的链接
c.为数据提供磁盘⾼速缓存
c.为数据提供磁盘⾼速缓存
9.内核基本上把I/O数据传送划分成两类:
a.缓冲区I/O操作:所传送的数据保存在缓冲区中,缓冲区是磁盘数据在内核中的普通内存容器。每个缓冲区都和⼀个特定的块相关联
⽽这个块由⼀个设备号和⼀个块号来标识
b.页I/O操作:所传输的数据保存在页框中,每个页框包含的数据都属于正规⽂件。因为没有必要把这种数据存放在相邻的磁盘中。
所以就是⽤⽂件的索引节点和⽂件内的偏移量来标识这种数据。主要⽤于读取正规⽂件、⽂件内存映射和交换。
10.所谓块(block):就是块设备驱动程序在⼀次单独操作中所传送的⼀⼤块相邻字节。
扇区(sector):扇区是硬件设备传送数据的基本单元。
11.块设备驱动程序的两部分:
a.⾼级驱动程序:处理VFS层。
b.低级驱动程序:处理硬件设备。
第⼗五章、访问正规⽂件
1.从正规⽂件读取数据:generic_file_read()函数实现了⼤部分⽂件系统的正规⽂件的read⽅法。
2.对正规⽂件进⾏预读:正规⽂件的预读需要的算法⽐物理块的预读需要的算法更复杂:
a.由于数据是逐页进⾏读取的,因此预读算法不必考虑页内偏移量,只考虑所访问的页在⽂件内部的位置就可以了。
b.当前访问与上⼀次访问不是顺序的时,预读就必须从头开始重新执⾏。
c.当进程⼀直反复地访问同⼀页时(该⽂件只有很少的⼀部分被使⽤),应该减慢预读的速度甚⾄停⽌执⾏。
d.如果需要,预读算法必须激活低级I/O设备驱动程序来确保新页会被读取。
e.内核通过多次调⽤⼀个名为:try_to_read_ahead()的函数来执⾏预读操作(read ahead operation),⼀次预读⼀页。
f.对于每个请求页,内核都要调⽤generic_file_readhead()函数,该函数确定是否要执⾏预读操作。
3.写正规⽂件:write()系统调⽤会涉及把数据从调⽤进程的⽤户态地址空间中移动到内核数据结构中,然后再移动到磁盘上。
⽂件对象的write⽅法允许每种⽂件类型都定义⼀个专⽤的写操作。
a.写操作发⽣时,有效数据是在缓冲区⾼速缓存中,⽽不是在页⾼速缓存中,更确切地说,当write⽅法修改了⽂件的任何部分时。
与这些部分对应的页⾼速缓存中的所有页都不再包含有效数据。⼀个进程可能认为⾃⼰在读取正确数据,
但是却没看到其他进程对这些数据所做的修改。
b.所有基于磁盘的⽂件系统的write⽅法都要调⽤update_vm_cache()函数来修改读操作所使⽤的页⾼速缓存。
c.通过页⾼速缓存对正规⽂件执⾏的写操作,只能⽤于⽹络⽂件系统。⽂件的write⽅法是使⽤generic_file_write()函数实现。
4.内存映射:⼀个线性区可以和基于磁盘的⽂件系统中的⼀个⽂件(或者⽂件的⼀部分)相关联。这就是说,内核会把线性区中对⼀个页中字节的访问转换成
对正规⽂件中相对于字节的操作,这种技术成为内存映射。
a.共享的:对线性区中的任何写操作都会修改磁盘上的⽂件。⽽且,如果进程对共享内存映射中的⼀个页进⾏写,
那么这种修改对于其他映射了相同⽂件的所有
进程来说都是可见的。
b.私有的:当进程创建的映射只是为读⽂件,⽽不是写⽂件时才会使⽤。处于这种⽬的,私有映射的效率要⽐共享映射的效率更⾼。
但是私有映射页的认识写操作都会使内核不再映射该⽂件中的页。⼀个写操作即不会改磁盘上的⽂件,
对访问相同⽂件的其他进程来说这种改变也是不可见的。
c.使⽤mmap()系统调⽤创建⼀个新的内存映射。必须指定MAP_SHARED或MAP_PRIVATE标志。
第⼗六章、磁盘数据结构
1.任何Ext2分区中的第⼀个块从不受Ext2⽂件系统的管理,因为这⼀块是为启动扇区所保留的。Ext2分区的其余部分被分成块组(block group)。
2.块组中的每个块包含下列信息之⼀:
a.⼀个Ext2⽂件系统超级块的拷贝
b.⼀组块组描述符的拷贝
c.⼀个数据块位图:标识在⼀组中块的使⽤和空闲状况。
d.⼀个索引节点位图
e.⼀组索引节点
f.属于⽂件的⼀块数据;即⼀个数据块。
3.如果⼀个块中不包含任何有意义的信息,就说这个块是空的。
4.在Ext2⽂件系统中的所有块组⼤⼩相同并被顺序存放,因此,内核可以从块组的整数索引很容易地得到磁盘中⼀个块组的位置。
5.超级块与组描述符被复制到每个块组中。只有块组0中所包含的超级块和描述符才由内核使⽤,⽽其余的超级块和组描述符保持不变
事实上,内核甚⾄不考虑它们。
6./sbin/e2fsck程序对Ext2⽂件系统的状态执⾏⼀致性检查时,就引⽤存放在块组0中的超级块和组描述符,然后把它们拷贝到其他所有的块组中。
7.每组之多有8 x b块,b是以字节单位的块⼤⼩。
8.⽰例:
8GB的Ext2分区
块的⼤⼩为4KB
块位图的⼤⼩4KB(b=4KB)
最多描述32KB的数据块
每个块组的容量:4KB x 32KB = 128MB
最多需要的块组数: n = 8GB/128MB = 64
9.磁盘数据结构:
9.磁盘数据结构:
a.超级块的域:
索引节点的总数
以块位单位的⽂件系统的⼤⼩
保留的块数
空闲块计数器
空闲索引节点计数器
第⼀次使⽤的块号(总为1)
块的⼤⼩
⽚的⼤⼩
每组中的块数
每组中的⽚数
.....
b.组描述符:每个块组有⾃⼰的组描述符,为ext2_group_desc结构。
Ext2组描述符的域
块位图的块号
索引节点位图的块号
第⼀个索引节点表块的块号
组中空闲块的个数
组中索引节点的个数
组中⽬录的个数
.....
c.位图:是位的序列,0表⽰相应的索引节点块或数据块是空闲的,1表⽰占⽤。
d.索引节点表:所有索引节点的⼤⼩相同,即128字节。索引节点表第⼀个块的块号存放在组描述符的bg_inode_table域中。
Ext2磁盘索引节点的域:
⽂件类型和访问权限
拥有者的标识符
以字节为单位的⽂件长度
最后⼀次⽂件访问的时间
.......
⽂件的索引节点号没有必要再磁盘上存放,因为它的值可以从块组号和它在索引节点表中的相对于位置⽽得出。
10.Ext2的⽂件操作:
VFS⽅法的read和mmap是由很多⽂件系统共⽤的通⽤函数实现的。这些⽅法存放在
ext2_file_operations表中:
lseek -> ext2_file_lseek()
read  -> generic_file_read()
write -> ext2_file_write()
.......
11.各种⽂件类型如何使⽤磁盘块
正规⽂件:正规⽂件只有在开始有数据时才需要数据块。
⽬录:Ext2以⼀种特殊的⽂件实现了⽬录,这种⽂件的数据块存放了⽂件名和相应的所有节点号。
数据块中包含了类型为ext2_dir_entry_2的结构
名字域最⼤为EXT2_NAME_LEN(通常是255)个字符的边长数组。
⽬录项的长度是4的倍数
符号链:符号链的路径名达到60个字符,就把它存放在索引节点的i_blocks域,该域是由15个4字节整数组成的数组,因此⽆需数据块如果路径名⼤于60个字符,就需要⼀个单独的数据块。
设备⽂件、管道和套接字:这些类型的⽂件不需要数据块,所有必须的信息都存放在索引节点中。
12.Ext2⽂件的类型
0 未知
1 正规⽂件
2 ⽬录
3 字符设备
4 块设备
5 命名管道
6 套接字
7 符号链
13.⽂件的洞是正规⽂件的⼀部分,它是⼀些空字符但没有存放在磁盘的任何数据块中。
引⼊⽂件的洞是为了避免磁盘空间的浪费。它们被⼴泛地⽤在数据库引⽤中,更⼀般的说,⽤于⽂件上散列法的所有应⽤。
第⼗⼋章进程通信
1.进程间通信的机制:
管道和FIFO(管道):最适合在进程之间实现⽣产者/消费者的交互。有些进程往管道中写⼊数据,⽽另外⼀些进程则从管道中读取数据。⽆名管道:⽤户⽆法打开⼀个现有的管道。除⾮管道是由⼀个公共的祖先进程创建的。
有名管道:有磁盘索引节点,因此任何进程都可以访问FIFO,没有使⽤数据块,使⽤内核缓冲区。
有名管道:有磁盘索引节点,因此任何进程都可以访问FIFO,没有使⽤数据块,使⽤内核缓冲区。
信号量IPC(Interprocess Communicatoin),表⽰⼀组系统调⽤,这组系统调⽤
允许⽤户态进程:
a.通过信号量和其他进程进⾏同步
b.向其他进程发送消息或者从其他进程处接受消息
c.和其他进程共享⼀个线性区
使⽤IPC资源:
信号量:semget()
消息队列:msgget()
共享内存:shmget()
消息:允许进程异步地交换信息(⼩块数据)。可以认为消息是传递附加信息的信号。
共享内存:当进程之间在⾼效地共享⼤量数据时,这是⼀种最合适的交互⽅式。
套接字(socket):涉及到⽹络相关。
第⼗九章、程序的执⾏
1.进程概念:在Unix中是⽤来表⽰正在运⾏的⼀组程序竞争系统资源的⾏为。
2.内核需要处理的问题(把⼀组指令装⼊内存并让CPU执⾏):
不同的可执⾏⽂件格式: Linux的⼀个著名之处就是能执⾏其他操作系统所编译的⼆进制⽂件。
共享库: 很多可执⾏⽂件并不包含运⾏程序所需要的所有代码,⽽是希望在运⾏时由内核从函数库装⼊函数。
执⾏上下⽂中的其他信息: 包括程序员熟悉的命令⾏参数与环境变量。
3.进程的信任状和能⼒:Unix系统与每个进程的⼀些信任相关,信任状把进程与⼀个特定的⽤户或⽤户组捆绑在⼀起。
信任状在多⽤户系统上尤为重要,因为信任状可以决定每个进程能做什么,不能做什么,这样既保证了每个⽤户个⼈数据的完整性
也保证了系统整体上的稳定性。
a.进程的能⼒:⼀种能⼒仅仅是⼀个标志,它表明是否允许进程执⾏⼀个特定的操作或⼀组特定的操作。
4.库:所有的全局外部符号名的线性地址。这些地址的分配或解析是由连接程序完成的,链接程序把程序所有的⽬标⽂件收集起来并构造可执⾏⽂件。
5.静态库/共享库
动态库的优缺点:进程不需要拷贝⽬标代码、仅仅执⾏⼀个内存映射,把库⽂件的相关部分映射到进程的地址空间中。
缺点也很明显,动态的启动时间较长,移植性也不如静链接的好,系统中所包含的库版本发⽣变化时,
动态库连接的程序可能不适合本地运⾏。
静态库的优缺点:占⽤⼤量的磁盘空间。每个静态连接的可执⾏⽂件都复制库代码的⼀部分。 gcc编译器提供-static选项告诉链接程序使⽤静态库。
6.程序段和进程的线性区:
逻辑上,Unix程序的线性地址空间传统上被划分为⼏个叫段(segment)的区间:
a.正⽂段:包含可执⾏代码。
b.数据段:包含初始化的数据,也就是说,初始值存放在可执⾏⽂件中的所有静态变量和全局变量(因为程序在启动时必须知道它们的值)
c.bss段:包含未初始化的数据,也就是说,初值没有存放在可执⾏⽂件中的所有全局变量(因为程序在引⽤它们之前才赋值)
d.堆栈段:包含程序的堆栈,堆栈中有返回地址、参数和被执⾏函数的局部变量。
堆:线性区包含动态分配给进程的内存区。
/sbin/init程序,它创建和监视在操作系统外层实现的所有进程的活动。init进程对应的线性区可以从(/proc/1/maps)⽂件得到这样的信息。
7.执⾏跟踪(execution tracing):是⼀个程序监视另⼀个程序执⾏的⼀种技术。被跟踪的程序⼀步⼀步地执⾏,直到接受到⼀个信号或调⽤⼀个系统调⽤。执⾏跟踪由调试程序(debugger)⼴泛应⽤,当然还使⽤其他技术(包括在被调试程序中插⼊断点及运⾏时访问它的变量)。
8.ptrace()系统调⽤进程执⾏跟踪。设置了CAP_SYS_PTRACE能⼒的进程可以跟踪系统中的任何进程(除了init)。
相反,没有CAP_SYS_PTRACE能⼒的进程P只能跟踪
与P有相同属主的进程。此外,两个进程不能同时跟踪⼀个进程。
a.ptrace()系统调⽤修改被跟踪进程描述符的p_pptr域以使它指向跟踪进程,因此,跟踪进程变为被跟踪进程的有效⽗进程
跟踪结束时,以PTRAC_DETACH命令调⽤ptrace()时,这个系统调⽤把p_pptr设置为p_oppter的值,恢复被跟踪进程原来的⽗进程。
b.被跟踪进程相关的⼏个监控事件为:
⼀条单独汇编指令执⾏的结束
进⼊⼀个系统调⽤
从⼀个系统调⽤退出
接收到⼀个信号
c.当⼀个监控的事件发⽣时,被跟踪的程序停⽌,并将SIGCHID信号发送给它的⽗进程。当⽗进程希望恢复⼦进程的执⾏时,
就是⽤PTRACE_CONT、PTRACE_SINGLESTEP和
PTRACE_SYSCALL命令中的⼀条,这取决于⽗进程要监控那种事件。
9.可执⾏格式:Linux正式的可执⾏格式是ELF(Execuable and Linking Format)。
类型为linux_binfmt的对象所描述的可执⾏格式实质上提供以下三种⽅法:
a.loadbinary:通读存放在可执⾏⽂件中的信息为当前进程建⽴⼀个新的执⾏环境。
b.load_shlib:⽤于动态地把⼀个共享库捆绑到⼀个已经在运⾏的进程,这个是由uselib()系统调⽤激活的。
<_dump:在名为core的⽂件中存放当前进程的执⾏上下⽂。这个⽂件通常是在进程接收到⼀个缺省操作为"dump"的信号时被创建的
其格式取决于被执⾏程序的可执⾏类型。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。