深入理解LINUX内核(学习笔记)_第一章绪论

Linux与其他类Unix内核的比较:

  • 单块结构的内核:它是一个庞大、复杂的自我完善程序,由几个逻辑上独立的成分构成。
  • 编译并静态链接的传统Unix内核:大部分现代操作系统内核可以动态地装载和卸载部分内核代码(典型的例子如设备驱动程序),通过把这部分代码称做模块(module)。Linux对模块的支持是很好的,因为它能自动按需装载或卸载模块。
  • 内核线程:一些Unix内核,被组织成一组内核线程(kernel thread)。内核线程是一个能被独立调度的执行环境(context);也许它与用户程序有关,也许仅仅执行一些内核函数。线程之间的上下文切换比普通进程之间的上下文切换花费的代价要少很多,因为前者通常在同一个地址空间执行。
  • 多线程应用程序支持:大多数现代操作系统在某种程度上都支持多线程应用程序,也就是说,这些用户程序是根据很多相对独立的执行流来设计的,而这些执行流之间共享应用程序的大部分数据结构。一个多线程用户程序由很多轻量级进程(LWP)组成,这些进程可能对共同的地址空间、共同的物理内存页、共同的打开文件等等进行操作。
  • 抢占式内核:当采用“可抢占式的内核”选项来编译内核时,Linux2.6可以随意交错执行处于特权模式的执行流。
  • 多处理器支持:几种Unix内核变体都利用了多处理器系统。Linux2.6支持不同存储模式的对称多处理(SMP),包括NUMA:系统不仅可以使用多处理器,而且每个处理器可以毫无区别地处理任何一个任务。
  • 文件系统:Linux标准文件系统呈现出多种风格。如果无特殊需要,就可以使用普通的Ext2文件系统,如果想避免系统崩溃后冗长的文件系统检查,就可以切换到Ext3,如果不得不处理很多小文件,ReiserFs文件系统可能是最好的选择;当然还有很多其他文件系统可供选择。
  • STREAMS:尽管现在大部分的Unix内核包含了SVR4引入的STREAMS I/O子系统,并且已变成编写设备驱动程序、终端驱动程序及网络协议的首选接口,但是Linux并没有此类似的子系统;

Linux有如下优势:

  • Linux是免费的;Linux的所有成分都可以充分地定制;Linux可以运行在低档、便宜的硬件平台;Linux是强大的;Linux的开发者都是非常出色的程序员;Linux内核非常小,而且紧凑;Linux与很多通用操作系统高度兼容;Linux有很好的技术支持;

硬件的依赖性:

  • Linux试图在硬件无关的源代码与硬件相关的源代码之间保持清醒的界限。为了做到这点,在arch和include目录下包含了23个子目录,以对应Linux所支持的不同硬件平台。这些平台的标准名字如下:alpha,arm,arm26,cris,frv,h8300,i386,ia64,m-32r,m68k,m68knommu,mips,parisc,ppc,ppc64,s390,sh,sh64,sparc,sparc64,um,v850,x86_64。

操作系统基本概念:操作系统必须完成两个主要目标

  • 与硬件部分交互,为包含在硬件平台上的所有底层可编程部件提供服务。
  • 为运行在计算机系统上的应用程序(即所谓用户程序)提供执行环境。

多用户系统:多用户系统就是一台能并发和独立地执行分别属于两个或多个用户的若干应用程序的计算机。“并发”意味着几个应用程序能同时处于活动状态竞争各种资源。“独立”意味着每个应用程序能执行自己的任务,而无需考虑其它用户的应用程序在干些什么。多用户操作系统必须包含以下几个特点

  • 核实用户身份的认证机制。
  • 防止有错误的用户程序妨碍其它应用程序在系统中运行的保护机制。
  • 防止有恶意的用户程序干涉或窥视其它用户的活动的保护机制。
  • 限制分配给每个用户的资源数的计帐机制。

用户和组:在多用户系统中,每个用户在机器上都有私有空间,典型地,他拥有一定数量的磁盘空间来存储文件、接收私人邮件信息等等。所有的用户由一个唯一的数字来标识,这个数字叫用户标识符(User ID,UID)。为了和其他用户有选择地共享资料,每个用户是一个或多个用户组的一名成员,组由唯一的用户组标识符(user group ID)标识。每个文件也恰好与一个组相对应。任何类Unix操作系统都有一个特殊的用户,叫做root,即超级用户(superuser)。系统管理员必须以root身份登录,以便处理用户账户,完成诸如系统备份、程序升级等维护任务。

进程:所有操作系统都使用一种基本的抽象:进程。一个进程可以定义为:“程序执行时的一个实例”,或者一个运行程序的“执行上下文”。在传统的操作系统中,一个进程在地址空间中执行一个单独的指令序列。地址空间是允许进程引用的内存地址集合。现代操作系统允许具有多个执行流的进程,也就是说,在相同的地址空间可执行多个指令序列。多用户系统必须实施一种执行环境,在这种环境里,几个进程能并发活动,并能竞争系统资源(主要是CPU)。允许进程并发活动的系统称为多道程序系统或多处理系统。区分程序和进程是非常重要的:几个进程能并发地执行同一程序,而同一个进程能顺序地执行几个程序。一般来说,CPU的个数总是有限的,因而只有少数几个进程能执行。操作系统叫做调度程序的部分决定哪个进程能执行。一些操作系统只允许有非抢占式进程,这就意味着,只有当进程自愿放弃CPU时,调度程序才会被调用。但是多用户系统中的进程必须是抢占式的;操作系统记录下每个进程占有的CPU时间,并周期性地激活调度程序。

内核体系结构:大部分Unix内核是单块结构:每一个内核层都被集成到整个内核程序中,并代表当前进程在内核态下运行。相反,微内核操作系统只需要内核有一个很小的函数集,通常包括几个同步原语、一个简单的调度程序和进程间通信机制。运行在微内核之上的几个系统进程实现从前操作系统级实现的而功能,如内存分配程序、设备驱动程序、系统调用处理程序等等。微内核操作系统迫使系统程序员采用模块化的方法。因为任何操作系统层都是一个相对独立的程序,这种程序必须通过定义明确而清晰的软件接口与其他层交互。此外,微内核操作系统可以很容易地移植到其他的体系结构上,因为所有的硬件相关的部分都被封装进微内核代码中。最后,微内核操作系统比单块内核更加充分地利用了RAM,因为暂且不需要执行的系统进程可以被调出或撤销。为了达到微内核理论上的很多优点而又不影响性能,Linux内核提供了模块(module)。模块是一个目标文件,其代码可以在运行时链接到内核或从内核解除链接。这种目标代码通常由一组函数组成,用来实现文件系统、驱动程序或其他内核上层功能。与微内核操作系统的外层不同,模块不是作为一个特殊的进程执行的。相反,与任何其他静态链接的内核函数一样,它代表当前进程在内核态下执行。使用模块的主要优点包括:

  • 模块化方法:因为任何模块都可以在运行时被链接或解除链接,因此,系统程序员必须提出明确的软件接口以访问由模块处理的数据结构。这使得开发新模块变得容易。
  • 平台无关性:即使模块依赖于某些特殊的硬件特点,但它不依赖于某个固定的硬件平台。例如:符合SCSI标准的磁盘驱动程序模块,在IBM兼容PC与HP的Alpha机上都能很好地工作。
  • 节省内存使用:当需要模块功能时,把它链接到正在运行的内核中,否则,将该模块解除链接,这种机制对于小型嵌入式系统是非常有用的。
  • 无性能损失:模块的目标代码一旦被链接到内核,其作用与与静态链接的内核的目标代码完全等价。因此,当模块的函数被调用时,无需显式地进行消息传递。

Unix文件系统概述:Unix操作系统的设计集中反映在文件系统上,文件系统有几个有趣的特点。

  • 文件:Unix文件是以字节序列组成的信息载体,内核不解释文件的内容。Unix的每个进程都有一个当前工作目录,它属于进程执行上下文,标识出进程所用的当前目录。
  • 硬链接和软链接:包含在目录中的文件名就是一个文件的硬链接,或简称链接。$ln P1 P2。硬链接有两方面的限制:(1)不允许用户给目录创建硬链接。因为这可能把目录数变为环形图,从而就可不能通过名字定位一个文件;(2)只有在同一文件系统中的文件之间才能创建链接。为了克服这些限制,引入了软链接(也称符号链接),符号链接是短文件,这些文件包含有另一个文件的任意一个路径名路径名可以指向位于任意文件系统的任意文件或目录,甚至可以指向一个不存在的文件。$ln -s P1 P2。

文件类型:Unix可以是下列类型之一:

  • 普通文件;目录;符号链接;面向块的设备文件;面向字符的设备文件;管道和命名管道(也叫FIFO);嵌套字;

文件描述符与索引节点:Unix对文件的内容和描述文件信息给出了清除的区分。除了设备文件和特殊文件系统文件外,每个文件都由字符序列组成。文件内容不包含任何控制信息,如文件长度或文件结束(EOF)符。虽然文件系统及内核函数对索引节点的处理可能随Unix系统的不同有很大的差异,但它们必须至少提供在POSIX标准中指定的如下属性:文件类型;与文件相关的硬链接个数;以字节为单位的文件长度;设备标识符(即包含文件的设备的标识符);在文件系统中标识文件的索引节点号;文件拥有者的UID;文件用户组ID;几个时间戳;访问权限和文件模式。

访问权限和文件模式:

  • 文件的潜在用户分为三种类型:作用文件所有者的用户;同组用户,不包括所有者;所有剩下的用户;

有三种类型的访问权限——读、写及执行每组用户都有这三种权限,因此文件访问权限组合就用九种不同的二进制来标记。还有三种附加的标记,即suid,sgid,及sticky用来定义文件的模式。

  • suid:进程执行一个文件时通常保持进程拥有者的UID。然而,如果设置了可执行文件suid的标志位进程就获得了该文件拥有者的UID。
  • sgid:进程执行一个文件时保持进程组的用户组ID。然而,如果设置了可执行文件sgid的标志位,进程就获得了该文件用户组的ID。
  • sticky:设置了sticky标志位的可执行文件相当于向内核发出一个请求,当程序执行结束以后,依然将它保留在内存。

文件操作系统调用:

  • 打开文件:fd=open(path,flag,mode)
  • 访问文件:newoffset=lseek(fd,offset,whence);nread=read(fd,buf,count)
  • 关闭文件:res=close(fd)
  • 更名或删除文件:res=rename(oldpath,newpath);res=unlink(pathname)

Unix内核概述:

  • 进程/内核模式:CPU既可以运行在用户态,也可以运行在内核态下。进程是动态的实体,在系统内通常只有有限的生存期。创建、撤销及同步现有进程的任务都委托给内核中的一组例程来完成。内核本身并不是一个进程,而是进程的管理者。进程/内核模式假定:请求内核服务的进程使用所谓系统调用的特殊编程机制。每个系统调用都设置了一组识别进程请求的参数,然后执行与硬件相关的CPU指令完成从用户态到内核态的转换。除用户进程之外,Unix系统还包括几个所谓内核线程的特权进程,它们具有以下特点:(1)它们以内核态运行在内核地址空间;(2)它们不与用户直接交互,因此不需要终端设备;(3)它们通常在系统启动时创建,然后一直处于活跃状态直到系统关闭。Unix内核做的工作远不止处理系统调用,实际上,可以有几种方式激活内核例程:(1)进程调用系统调用;(2)正在执行进程的CPU发出一个异常信号,异常是一些反常情况,内核代表产生异常的进程处理异常;(3)外围设备向CPU发出一个中断信号以通知一个事件的发生,每个中断信号都是有内核中的中断处理程序来处理的;(4)内核线程被执行。
  • 进程实现:为了让内核管理进程,每个进程由一个进程描述符表示,这个描述符包含有关进程当前状态的信息。当内核暂停一个进程的执行时,就把几个相关处理器寄存器的内容保存在进程描述符中。这些寄存器包括:(1)程序计数器(PC)和栈指针(SP)寄存器;(2)通用寄存器;(3)浮点寄存器;(4)包含CPU状态信息的处理器控制寄存器;(5)用来跟踪进程对RAM访问的内存管理寄存器。
  • 可重入内核:所有的Unix内核都是可重入的,这意味着若干个进程可以同时在内核态下执行。当然在单处理器系统上只有一个进程在真正运行,但是有许多进程可能在等待CPU或某一I/O操作完成时在内核态下被阻塞。在最简单的情况下,CPU从第一条指令到最后一条指令顺序地执行内核控制路径。然而,当下述事件之一发生时,CPU交错执行内核控制路径:(1)运行在用户态下的进程调用一个系统调用,而相应的内核控制路径证实这个请求无法立即得到满足;然后,内核控制路径调度程序选择一个新的进程投入运行,结果,进程切换发生。第一个内核控制路径还没完成,而CPU又重新开始执行其他的内核控制路径。在这种情况下,两条控制路径代表两个不同的进程在执行。(2)当运行一个内核控制路径时,CPU检测到一个异常。第一个控制路径被挂起,而CPU开始执行合适的过程。当这个过程结束时,第一个控制路径可以恢复执行。在这种情况下,两个控制路径代表同一个进程在执行。(3)当CPU正在运行一个启动了中断的内核控制路径时,一个硬件中断发生。第一个内核控制路径还没执行完,CPU开始执行另一个内核控制路径来处理这个中断。当这个中断处理程序终止时,第一个内核控制路径恢复。在这种情况下,两个内核控制路径运行在同一个进程的可执行上下文中,所花费的系统CPU时间都算给这个进程。然而,中断处理程序无需代表这个进程运行。(4)在支持抢占式调度的内核中,CPU正在运行,而更高一级的进程加入就绪队列,则中断发生,在这种情况下,第一个内核控制路径还没执行完,CPU代表高优先级进程又开始执行另一个内核控制路路径。只有把内核编译成支持抢占式调度之后,才可能出现这种情况。考虑以下三种不同的CPU状态:(1)在用户态运行一个进程(user);(2)运行一个异常处理程序或系统调用处理程序(Excp);(3)运行一个中断处理程序(Intr)。
  • 进程地址空间:每个进程运行在它的私有地址空间。因为内核是可重入的,因此几个内核控制路径(每个都与不同的进程相关)可以轮流执行。在这种情况下,每个内核控制路径都引用它自己的私有内核栈。进程间也能共享部分地址空间,以实现一种进程间通信。Linux支持mmap()系统调用,该系统调用允许存放在块设备上的文件或信息的一部分映射到进程的部分地址空间。内存映射为正常的读写传送数据方式提供了另一种选择。如果同一文件由几个进程共享,那么共享它的每个进程地址空间都包含有它的内存映射。
  • 同步和临界区:实现可重入内核需要利用同步机制:如果内核控制路径对某个内核数据结构进行操作时被挂起,那么,其他的内核控制路径就不应当再对该数据结构进行操作,除非它已被重新设置成一致性状态。否则,两个控制路径的交互作用将破坏所存储的信息。当某个计算结果取决于如何调度两个或多个进程时,相关代码就是不正确的,我们说存在一种竞争条件。一般来说,对全局变量的安全访问通过原子操作来保证。临界区是这样的一段代码,进入这段代码的进程必须完成,之后另一个进程才能进入。
  • 非抢占式内核:当进程在内核态执行时,它不能被任意挂起,也不能被另一个进程代替。当然,内核态的进程能自愿放弃CPU,但是在这种情况下,它必须确保所有的数据结构都处于一致性状态。此外,当这种进程恢复执行时,它必须重新检查以前访问过的数据结构的值,因为这些数据结构有可能被改变。如果内核支持抢占,那么在应用同步机制时,确保进入临界区前禁止抢占,退出临界区时启用抢占。非抢占能力在多处理器系统上是低效的,因为运行在不同CPU上的两个内核控制路径本可以并发地访问相同的数据结构。
  • 禁止中断:单处理器系统上的另一种同步机制是:在进入一个临界区之前禁止所有硬件中断,离开时再重新启动中断。这种机制尽管简单,但远不是最佳的,如果临界区比较大,那么在一个相对较长的时间内持续禁止中断就可能使所有的硬件活动处于冻结状态。
  • 信号量:广泛使用的一种机制是信号量(semaphore),它在单处理器系统和多处理器系统上都有效。信号量仅仅是与一个数据结构相关的计数器,所有内核线程在试图访问这个数据结构之前,都要检查这个信号量。可以把信号量看作一个对象,其组成如下:(1)一个整数变量;(2)一个等待进程的链表;(3)两个原子方法:down()和up()。
  • 自旋锁:在多处理系统中,信号量并不总是解决同步问题的最佳方案。系统不允许在不同CPU上运行的内核控制路径同时访问某些内核数据结构,在这种情况下,如果修改数据结构所需的时间比较短,那么信号量可能是很低效的。为了检查信号量,内核必须把进程插入到信号量链表中,然后挂起它。因为这两种操作比较耗时,完成这些操作时,其他的内核控制路径可能已经释放了信号量。在这些情况下,多处理器操作系统使用了自旋锁(spin lock)。自旋锁与信号量非常相似,但没有进程链表,当一个进程发现锁被另一个进程锁着时,它就不停地“旋转”,执行一个紧凑的循环指令直到锁打开。当然,自旋锁在单处理器环境下是无效的。当内核控制路径试图访问一个上锁的数据结构时,它开始无休止循环。因此,内核控制路径可能因为正在修改受保护的数据结构而没有机会继续执行,也没有机会释放这个自旋锁,最后的结果可能是系统挂起。
  • 避免死锁:与其他控制路径同步的进程或内核控制路径很容易进入死锁状态。死锁情形会导致受影响的进程或内核控制路径完全处于冻结状态。
  • 信号和进程间通信:Unix信号(signal)提供了把系统事件报告给进程的一种机制。有两种系统事件:(1)异步通告;(2)同步错误或异常。POSIX标准定义了大约20种不同的信号,其中,有两种是用户自定义的,可以当作用户态下进程通信的同步的原语机制。一般来说,进程可以以两种方式对接受到的信号做出反应:(1)忽略信号;(2)异步地执行一个指定的过程。如果进程不指定选择何种方式,内核就根据信合的编号执行一个默认操作,五种可能的默认操作是:(1)终止进程;(2)将执行上下文和进程地址空间的内容写入一个文件(核心转储,core dump),并终止进程;(3)忽略信号;(4)挂起进程;(5)如果进程曾被暂停,则恢复它的执行。内核把它们作为IPC资源来实现:进程要获得一个资源,可以调用shmget()、semget()或msgget()系统调用。共享内存为进程之间交换和共享数据提供了最快的方式。通过调用shmget()系统调用来创建一个新的共享内存,其大小按需设置。在获得IPC资源标识符后,进程调用shmat()系统调用,其返回值是进程的地址空间中新区域的起始地址。当进程希望把共享内存从其他空间分离出去时,就调用shmdt()系统调用。共享内存的实现依赖于内核对进程地址空间的实现。
  • 进程管理:Unix在进程和它所在执行的程序间做出一个清晰的划分。fork()和_exit()系统调用分布用来创建一个新进程和终止一个进程,而调用exec()类系统调用则是装入一个新程序。调用fork()的进程是父进程,而新进程是它的子进程。父子进程能相互找到对方。
  • 僵死进程:父进程如何查询子进程是否终止了呢?wait4()系统调用允许进程等待,直到其中的一个子进程结束;
  • 进程组和登录会话:进程组概念以表示一种“作业”的抽象。登录会话就是shell进程为用户创建的第一条命令。

内存管理:内存管理是迄今为止Unix内核中最复杂的活动

  • 虚拟内存:虚拟内存作为一种逻辑层,处于应用程序的内存请求与硬件内存管理单元(MMU)之间。虚拟内存有很多用途和优点:(1)若干个进程可以并发地执行;(2)应用程序所需内存大小可用物理内存时也可以运行;(3)程序只有部分代码装入内存时进程可以执行它;(4)允许每个进程访问可以物理内存的子集;(5)进程可以共享函数或程序的一个单独内存映像;(6)程序是可重定位的,也就是说,可以把程序放在物理内存的任何地方;(7)程序员可以编写与机器无关的代码,因为它们不必关心有关物理内存的组织结构。进程所用的一组内存地址不同于物理内存地址,当进程使用一个虚拟地址时,内核和MMU协同定位其在内存中的实际物理位置。现在的CPU包含了能自动把虚拟内存地址转换成物理内存地址的硬件电路。为了达到这个目标,把可用RAM划分长度为4KB或8KB的页框,并且引入一组页表来指定虚拟地址与物理地址之间的对应关系。
  • 随机访问存储器(RAM)的使用:RAM毫无疑义地划分为两个部分,其中若干兆字节专门用于存放内核映像(也就是内核代码和内核静态数据结构)。RAM的其余部分通常由虚拟内存系统来处理,并且用在以下三种可能的方面:(1)满足内核缓冲区,描述符及其他动态内核数据结构的请求;(2)满足进程对一般内存区的请求及对文件内存映射的请求;(3)借助于高速缓存从磁盘及其他缓冲设备获得较好的性能。虚拟内存系统必须解决的一个主要问题是内存碎片,理想情况下,只有当空闲页框数太少时,内存请求才失败。然而,通常要求内核使用物理上的连续的内存区域,因此即使有足够的可用内存,但它不能作为一个连续的大块使用时,内存的请求也会失败。
  • 内核内存分配器:以各好的KMA应该具有下列特点:(1)必须快;(2)必须把内存的浪费减到最少;(3)必须努力减轻内存的碎片问题;(4)必须能与其他内存管理子系统合作,以便借用和释放页框。基于各种不同的算法技术,已经提出了几种KMA,包括:(1)资源分配算法;(2)2的幂次方空闲链表;(3)McKusick-Karels分配算法;(4)伙伴(Buddy)系统;(5)Dynix分配算法;(6)Solaris的Slab分配算法。
  • 进程虚拟地址空间处理:进程的虚拟地址空间包括了进程可以引用的所有虚拟内存地址。内核分配给进程的虚拟地址空间由以下内存区组成:(1)程序的可执行代码;(2)程序的初始化数据;(3)程序的未初始化数据;(4)初始程序栈(即用户态栈);(5)所需共享库的可执行代码和数据;(6)堆(由程序动态请求的内存)。
  • 高速缓存:物理内存的一大优势就是用作磁盘和其它块设备的高速缓存。这是因为硬盘非常慢:磁盘的访问需要数毫秒,与RAM的访问时间相比,这太长了。因此,磁盘通常是影响系统性能的瓶颈。sync()系统调用把所有“脏”的缓存区写入磁盘来强制磁盘同步。为了避免数据丢失,所有的操作系统都会注意周期性地把脏缓存区写回磁盘。
  • 设备驱动程序:内核通过设备驱动程序(device driver)与I/O设备交互。通过特点的接口,每个驱动程序与内核中的其余部分(甚至与其他驱动程序)相互作用这种方式具有以下优点:(1)可以把特定设备的代码封装在特定的模块中;(2)厂商可以在不了解内核源代码而只知道接口规范的情况下,就能增加新的设备;(3)内核以统一的方式对待所有的设备,并且通过相同的接口访问这些设备;(4)可以把设备驱动程序写成模块,并动态地把它们封装进内核而不需要重新启动系统,不再需要时,也可以动态地卸载模块,以减少存储在RAM中的内核映像的大小。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值