描述Linux内核的文章早已有上亿字了
并且对于初学者,还是应当多学习多看,虽然上亿字不能一下子就明白的。
虽然看了所有的Linux内核文章,恐怕也还不是很明白,这时侯,还是须要fuckingthecode.
28年前(1991年8月26日)Linus公开Linux的代码,开启了一个伟大的时代。这篇文章从进程调度,显存管理,设备驱动,文件,网路等方面讲解Linux内核系统构架。Linux的系统构架是一个精典的设计,它优秀的分层和模块化,融合了数目繁杂的设备和不同的化学构架,让世界各地的内核开发者才能高效并行工作。先来瞧瞧Linus在多年前公开Linux的电邮。
"Helloeverybodyoutthereusingminix-I’mdoinga(free)operatingsystem(justahobby,won’tbebigandprofessionallikegnu)for386(486)ATclones.Thishasbeenbrewingsinceapril,andisstartingtogetready.I’dlikeanyfeedbackonthingspeoplelike/dislikeinminix,asmyOSresemblesitsomewhat(samephysicallayoutofthefile-system(duetopracticalreasons)amongotherthings).
I’vecurrentlyportedbash(1.08)andgcc(1.40),andthingsseemtowork.ThisimpliesthatI’llgetsomethingpracticalwithinafewmonths,andI’dliketoknowwhatfeaturesmostpeoplewouldwant.Anysuggestionsarewelcome,butIwon’tpromiseI’llimplementthem:-)
Linus(torv...@kruuna.helsinki[1].fi)"
事实上,从那三天开始,Linux便是博采众长,融合了十分多的优秀设计。在了解操作系统的时侯,我们起码须要晓得:
1.操作系统是怎样管理各类资源的?
2.软硬件怎么协同工作?3.怎样通过具象化屏蔽差别4.软硬件怎么分工?
这篇文章通过对内核主要模块的介绍,希望能为你们找寻这种问题的答案起一个抛砖引玉的作用。实际上,建议每一个希望成为技术专家的人都读一遍Linux的源代码。
先来瞧瞧Linux内核一个高阶构架图:
Linux系统构架图
构架十分清晰,从硬件层,硬件具象层,内核基础模块(进程调度,显存管理,网路合同栈等)到应用层,这个基本上也是各种软硬件结合的系统构架的基础设计,比如物联网系统(从单片机,MCU等大型嵌入式系统,到智能家饰,智慧社区甚至智慧城市)在接入端设备的可参考构架模型。
Linux最初是运行在PC机上的,使用的x86构架处理器相对来说比较强悍,各种指令和模式也比较齐全。比如我们看见的用户态和内核态,在通常的大型嵌入式处理器上是没有的,它的用处是通过将代码和数据段(segment)给与不同的权限,保护内核态的代码和数据(包括硬件资源)必须通过类似系统调用(SysCall)的方法能够访问,确保内核的稳定。
想像一下,假如须要你写一个操作系统,有什么诱因须要考虑?
进程管理:怎样在多任务系统中根据调度算法分配CPU的时间片。
显存管理:怎样实现虚拟显存和化学显存的映射,分配和回收显存。
文件系统:怎样将硬碟的磁道组织成文件系统,实现文件的读写等操作。
设备管理:怎么主存,访问,读,写设备配置信息和数据。
这种概念是操作系统的核心概念,因为篇幅缘由,本文章主要从高阶的角度来讲,更多细节不在本文覆盖。
进程管理
进程在不同的操作系统中有些称为process,有些称为task。操作系统中进程数据结构包含了好多元素,常常用数组联接。
进程相关的内容主要包括:虚拟地址空间,优先级,生命周期(阻塞,就绪,运行等),占有的资源(比如讯号量linux内核信号量,文件等)。
CPU在每位系统滴答(Tick)中断形成的时侯检测就绪队列上面的进程(遍历数组中的进程结构体),如有符合调度算法的新进程须要切换,保存当前运行的进程的信息(包括栈信息等)后挂起当前进程,选择新的进程运行,这就是进程调度。
进程的优先级差别是CPU调度的基本根据,调度的终极目标是让高优先级的活动才能即时得到CPU的估算资源(即时响应),低优先级的任务也能公正分配到CPU资源。由于须要保存进程运行的上下文(processcontext)等,进程的切换本身是有成本的,调度算法在进程切换频度上也须要考虑效率。
在初期的Linux操作系统中,主要采用的是时间片轮转算法(Round-Robin),内核在就绪的进程队列中选择高优先级的进程运行,每次运行相等的时间。该算法简单直观,但依然会造成个别低优先级的进程长时间未能得到调度。为了提升调度的公正性,在Linux2.6.23然后,引入了称为完全公正调度器CFS(CompletelyFairScheduler)。
CPU在任何时间点只能运行一个程序,用户在使用优酷APP看视频时,同时在陌陌中打字聊天,优酷和陌陌是两个不同的程序,为何看上去像是在同时运行?CFS的目标就是让所有的程序看上去都是以相同的速率在多个并行的CPU上运行,即nr_running个运行的进程,每位进程以1/nr_running的速率并发执行,比如如有2个可运行的任务,这么每位以50%的CPU化学能力并发执行。
CFS引入了"虚拟运行时间"的概念,虚拟运行时间用p->se.vruntime(nanosec-unit)表示,通过它记录和测度任务应当获得的"CPU时间"。在理想的调度情况下,任何时侯所有的任务都应当有相同的p->se.vruntime值(前面提及的以相同的速率运行)。由于每位任务都是并发执行的,没有任务会超过理想状态下应当占有的CPU时间。CFS选择须要运行的任务的逻辑基于p->se.vruntime值,十分简单:它总是选购p->se.vruntime值最小的任务运行(最少被调度到的任务)。
CFS使用了基于时间排序的黑红树来为将来任务的执行排时间线。所有的任务按p->se.vruntime关键字排序。CFS从树中选择最右边的任务执行。随着系统运行,执行过的任务会被放在树的一侧,逐渐地地让每位任务都有机会成为最右边的任务,因而在一个可确定的时间内获得CPU资源。
总结来说,CFS首先运行一个任务,当任务切换(或则Tick中断发生的时侯)时,该任务使用的CPU时间会加到p->se.vruntime里,当p->se.vruntime的值渐渐减小到别的任务弄成了黑红树最右边的任务时(同时在该任务和最右边任务间降低一个小的细度距离,避免过度切换任务,影响性能),最右边的任务被选中执行,当前的任务被占据。
CFS黑红树
通常来说,调度器处理单个任务,且尽可能为每位任务提供公正的CPU时间。个别时侯,可能须要将任务分组,并为每位组提供公正的CPU时间。诸如,系统可以为每位用户分配平均的CPU时间后,再为每位用户的每位任务分配平均的CPU时间。
显存管理
显存本身是一个外部储存设备,系统须要对显存区域轮询,找到对应的显存单元(memorycell),读写其中的数据。
显存区域通过表针轮询,CPU的字节宽度(32bit机器,64bit机器)决定了最大的可轮询地址空间。在32位机器上最大的轮询空间是4GBtyes。在64位机器上理论上有2^64Bytes。
最大的地址空间和实际系统有多少化学显存无关,所以称为虚拟地址空间。对系统中所有的进程来说,看上去每位进程都独立占有这个地址空间,且它难以感知其它进程的显存空间。事实上操作系统让应用程序无需关注其它应用程序,看上去每位任务都是这个笔记本上运行的惟一进程。
Linux将虚拟地址空间分为内核空间和用户空间。每位用户进程的虚拟空间范围从0到TASK_SIZE。从TASK_SIZE到2^32或2^64的区域保留给内核,不能被用户进程访问。TASK_SIZE可以配置,Linux系统默认配置3:1,应用程序使用3GB的空间,内核使用1GB的空间,这个界定并不依赖实际RAM的大小。在64位机器上,虚拟地址空间的范围可以十分大,但实际上只使用其中42位或47位(2^42或2^47)。
虚拟地址空间
绝大多数情况下,虚拟地址空间比实际系统可用的化学显存(RAM)大,内核和CPU必须考虑怎样将实际可用的化学显存映射到虚拟地址空间。
一个方式是通过页表(PageTable)将虚拟地址映射到化学地址。虚拟地址与进程使用的用户&内核地址相关,化学地址拿来轮询实际使用的RAM。
如右图所示,进程A和B的虚拟地址空间被分为大小相等的部份,称为页(page)。化学显存同样被分割为大小相等的页(pageframe)。
虚拟和化学地址空间映射
进程A第1个显存页映射到化学显存(RAM)的第4页;进程B第1个显存页映射到化学显存第5页。进程A第5个显存页和进程B第1个显存页都映射到化学显存的第5页(内核可决定什么显存空间被不同进程共享)。
如图所示,并不是每位虚拟地址空间的页都与某个pageframe关联,该页可能并未使用或则数据还没有被加载到化学显存(暂时不须要),也可能由于数学显存页被置换到了硬碟上,后续实际再须要的时侯再被置换回显存。
页表(pagetable)将虚拟地址空间映射到化学地址空间。最简单的做法是用一个链表将虚拟页和化学页一一对应,并且这样做可能须要消耗整个RAM本身来保存这个页表,假定每位页大小为4KB,虚拟地址空间大小为4GB,须要一个1百万个元素的链表来保存页表。
由于虚拟地址空间的绝大多数区域实际并没有使用,这种页实际并没有和pageframe关联,引入多级页表(multilevelpaging)能极大减少页表使用的显存,提升查询效率。关于多级也表的细节描述可以参考xxx。
显存映射(memorymapping)是一个重要的具象方式,被运用在内核和用户应用程序等多个地方。映射是将来自某个数据源的数据(也可以是某个设备的I/O端口等)转移到某个进程的虚拟显存空间。对映射的地址空间的操作可以使用处理普通显存的方式(对地址内容直接进行读写)。任何对显存的改动会手动转移到原数据源,比如将某个文件的内容映射到显存中,只须要通过读该显存来获取文件的内容,通过将改动讲到该显存来更改文件的内容,内核确保任何改动就会手动彰显到文件里。
另外,在内核中linux视频教程,实现设备驱动时,外设(外部设备)的输入和输出区域可以被映射到虚拟地址空间,读写这种空间会被系统重定向到设备,因而对设备进行操作,极大地简化了驱动的实现。
内核必须跟踪什么数学页早已被分配了,什么还是空闲的,防止两个进程使用RAM中的同一个区域。显存的分配和释放是十分频繁的任务,内核必须确保完成的速率尽量快,内核只能分配整个pageframe,它将显存分为更小的部份的任务交给了用户空间,用户空间的程序库可以将从内核收到的pageframe分成更小的区域后分配给进程。
虚拟文件系统
Unix系统是构建在一些有见地的理念上的,一个十分重要的意象是:
Everythingisafile.
即系统几乎所有的资源都可以看成是文件。为了支持不同的本地文件系统,内核在用户进程和文件系统实现间包含了一层虚拟文件系统(VirtualFileSystem)。大多数的内核提供的函数都能通过VFS(VirtualFileSystem)定义的文件插口访问。诸如内核子系统:字符和块设备,管线,网路Socket,交互输入输出终端等。
另外用于操作字符和块设备的设备文件是在/dev目录下的真实文件,当读写操作执行的时侯,其的内容会被对应的设备驱动动态创建。
VFS系统
在虚拟文件系统中,inode拿来表示文件和文件目录(对于系统来说,目录是一种特殊的文件)。inode的元素包含两类:1.Metadata用于描述文件的状态,比如读写权限。2.用于保存文件内容的数据段。
每位inode都有一个非常的号码用于惟一辨识,文件名和inode的关联构建在该编号基础上。以内核查找/usr/bin/emacs为例,讲解inodes怎样组成文件系统的目录结构。从根inode开始查找(即根目录‘/’),该目录使用一个inode表示,inode的数据段没有普通的数据,只包含了根目录存的一些文件/目录项,这种项可以表示文件或其它目录,每项包含两个部份:1.下一个数据项所在的inode编号2.文件或目录名
首先扫描根inode的数据区域直至找到一个名为‘usr’的项,查找子目录usr的inode。通过‘usr’inode编号找到关联的inode。重复以上步骤,查找名为‘bin’的数据项,之后在其数据项的‘bin’对应的inode中搜索名子‘emacs’的数据项,最后返回的inode表示一个文件而不是一个目录。最后一个inode的文件内容不同于之前,前三个每位都表示了一个目录,包含了它的子目录和文件清单,和emacs文件关联的inode在它的数据段保存了文件的实际内容。
虽然在VFS查找某个文件的步骤和前面的描述一样,但细节上还是有些差异。比如由于频繁打开文件是一个很慢的操作,引入缓存加速查找。
通过inode机制查找某个文件
设备驱动
与外设通讯常常指的是输入(input)和输出(output)操作,简称I/O。实现外设的I/O内核必须处理三个任务:第一,必须针对不同的设备类型采用不同的方式来轮询硬件。第二,内核必须为用户应用程序和系统工具提供操作不同设备的方式,且须要使用一个统一的机制来确保尽量有限的编程工作,和保证虽然硬件方式不同应用程序也能相互交互。第三,用户空间须要晓得在内核中有什么设备。
与外设通讯的层级关系如下:
设备通讯层级图
外部设备大多通过总线与CPU联接,系统常常不止一个总线,而是总线的集合。在好多PC设计中包含两个通过一个bridge相连的PCI总线。个别总线诸如USB不能当成主总线使用,须要通过一个系统总线将数据传递给处理器。右图显示不同的总线是怎样联接到系统的。
系统总线拓扑图
系统与外设交互主要有以下形式:
I/O端口:使用I/O端口通讯的情况下,内核通过一个I/O控制器发送数据,每位接收设备有惟一的端标语,且将数据转发给系统附着的硬件。有一个由处理器管理的单独的虚拟地址空间拿来管理所有的I/O地址。
I/O地址空间并不总是和普通的系统显存关联,考虑到端口才能映射到显存中,这常常不好理解。
端口有不同的类型。一些是只读的,一些是只写的,通常情况下它们是可以单向操作的,数据才能在处理器和外设间单向交换。
在IA-32构架体系中,端口的地址空间包含了2^16个不同的8位地址,这种地址可以通过从0x0到0xFFFFH间的数惟一辨识。每位端口都有一个设备分配给它,或则空闲没有使用,多个外设不能共享一个端口。好多情况下,交换数据使用8位是不够用的,基于这个缘由,可以将两个连续的8位端口绑定为一个16位的端口。两个连续的16位端口才能被当成一个32位的端口,处理器可以通过组装语句来做输入输出操作。
不同处理器类型在实现操作端口时有所不同,内核必须提供一个合适的具象层linux内核信号量,比如outb(写一个字节),outw(写一个字)和inb(读一个字节)这种可以拿来操作端口。
I/O显存映射:必须才能像访问RAM显存一样轮询许多设备。因此处理器提供了将外设对应的I/O端口映射到显存中,这样才能像操作普通显存一样操作设备了。诸如主板使用这样的机制,PCI也常常通过映射的I/O地址轮询。
为了实现显存映射,I/O端口必须首先被映射到普通系统显存中(使用处理器特有的函数)。由于平台间的实现方法差别比较大,所以内核提供了一个具象层来映射和去映射I/O区域。
不仅怎么访问外设,哪些时侯系统会晓得是否外设有数据可以访问?主要通过两种方法:协程和中断。
协程周期性地访问查询设备是否有打算好的数据,假如有,便获取数据。这些技巧须要处理器在设备没有数据的情况下也不断去访问设备,浪费了CPU时间片。
另一种形式是中断,它的理念是外设把某件事情做完了后,主动通知CPU,中断的优先级最高,会中断CPU的当前进程运行。每位CPU都提供了中断线(可被不同的设备共享),每位中断由惟一的中断号辨识,内核为每位使用的中断提供一个服务方式(ISR,InterruptServiceRoutine,即中断发生后,CPU调用的处理函数)linux cp,中断本身也可以设置优先级。
中断会挂起普通的系统工作。当有数据已打算好可以给内核或则间接被一个应用程序使用的时侯,外设出发一个中断。使用中断确保系统只有在外设须要处理器介入的时侯就会通知处理器,有效提升了效率。
通过总线控制设备:不是所有的设备都是直接通过I/O句子轮询操作的,好多情况下是通过某个总线系统。
不是所有的设备类型都能直接挂接在所有的总线系统上,比如硬碟挂到SCSI插口上,但显存不可以(主板可以挂到PCI总线上)。硬碟必须通过IDE间接挂到PCI总线上。
总线类型可分为系统总线和扩充总线。硬件上的实现差异对内核来说并不重要,只有总线和它附着的外设怎样被轮询才相关。对于系统总线来说,比如PCI总线,I/O句子和显存映射拿来与总线通讯,也用于和它附着的设备通讯。内核还提供了一些供设备驱动来调用总线函数,比如访问可用的设备列表,使用统一的格式读写配置信息。
扩充总线诸如USB,SCSI通过清晰定义的总线合同与附着的设备来交换数据和。内核通过I/O句子或显存映射来与总线通讯,通过平台无关的函数来使总线与附着的设备通讯。
与总线附着的设备通讯不一定须要通过在内核空间的驱动进行,在个别情况下也可以通过用户空间实现。一个主要的事例是SCSIWriter,通过cdrecord工具来轮询。这个工具形成所须要的SCSI命令,在内核的帮助下通过SCSI总线将命令发送到对应的设备,处理和回复设备形成或返回的信息。
块设备(block)和字符设备(character)在3个方面明显不同:
块设备中的数据才能在任何点操作,而字符设备不能也没这个要求。
块设备数据传输的时侯总是使用固定大小的块。虽然只恳求一个字节的情况下,设备驱动也总是从设备获取一个完整的块。相反,字符设备才能返回单个字节。
读写块设备会使用缓存。读操作方面,数据缓存在显存中,才能在须要的时侯重新访问。写操作方面,也会被缓存,延时写入设备。使用缓存对于字符设备(比如鼠标)来说不合理,每位读恳求都必须被可靠地交互到设备。
块和磁道的概念:块是一个指定大小的字节序列,用于保存在内核和设备间传输的数据,块的大小可以被设置。磁道是固定大小的,能被设备传输的最小的数据量。块是一段连续的磁道,块大小是磁道的整数倍。
网路
Linux的网路子系统为互联网的发展提供了坚实的基础。网路模型基于ISO的OSI模型,如右图右半部份。但在具体应用中,常常会把相应层级结合以简化模型,右图左半部份为Linux运用的TCP/IP参考模型。(因为介绍Linux网路部份的资料比较多,在本文中只对大的层级简单介绍,不展开说明。)
网路模型
Host-to-host层(PhysicalLayer和Datalinklayer,即化学层和数据链路层)负责将数据从一个计算机传输到另一台计算机。这一层处理数学传输介质的电气和编解码属性,也将数据流拆分成固定大小的数据帧用于传输。如多个笔记本共享一个传输路线,网路适配器(网卡等)必须有一个惟一的ID(即MAC地址)来分辨。从内核的角度,这一层是通过网卡的设备驱动实现的。
OSI模型的网路层在TCP/IP模型中称为网路层,网路层使网路中的计算机之间能交换数据,而这种计算机不一定是直接相连的。
如右图,A和B之间化学上并没有直接相连,所以也没有直接的数据交换。网路层的任务是为网路中各机器之间通讯找到路由。
网路联接的笔记本
网路层也负责即将传输的包分成指定的大小,由于包在传输路径上每位笔记本支持的最大的数据包大小可能不一样,在传输时,数据流被分割成不同的包,在接收端再被组合。
网路层为网路中的笔记本分配了惟一的网路地址便于她们能互相通信(不同于硬件的MAC地址,由于网路常常由子网路组成)。在互联网中,网路层由IP网路组成,有V4和V6版本。
传输层的任务是规范在两个联接的笔记本上运行的应用程序之间的数据传输。比如两台笔记本上的顾客端和服务端程序,包括TCP或UDP联接,通过端标语来辨识通讯的应用程序。诸如端标语80用于webserver,浏览器的顾客端必须将恳求发送到这个端口来获取须要的数据。而顾客端也须要有一个惟一的端标语便于webserver能将回恶变献给它。
这一层还负责为数据的传输提供一个可靠的联接(TCP情况下)。
TCP/IP模型中的应用层在OSI模型中包含(session层,彰显层,应用层)。当通讯联接在两个应用之间构建上去后,这一层负责实际内容的传输。诸如webserver与它的顾客端传输时的合同和数据,不同与mailserver与它的顾客端之间。
大多数的网路合同在RFC(RequestforComments)中定义。
网路实现分层模型:内核对网路层的实现类似TCP/IP参考模型。它是通过C代码实现的,每位层只能和它的上下层通讯,这样的用处是可以将不同的合同和传输机制结合。如右图所示:
网路实现分层图
本文先介绍到这,对技术感兴趣的同学可以关注"从零开始学构架",后续也会继续推出对各种构架设计的介绍,希望和你们多多交流,也欢迎你们留言。(TheEnd)
参考资料:
《ProfessionalLinuxKernelArchitecture》
《UnderstandingLinuxKernel》
《ArchitectureoftheLinuxKernel》
References
[1]torv...@kruuna.helsinki:mailto:torv...@kruuna.helsinki
本文原创地址://q13zd.cn/lgklnhxtjgrs.html编辑:刘遄,审核员:暂无