笔记:深入理解Linux内核(一)

笔记:深入理解Linux内核(一)

第一章:绪论

由题而问:

  1. Linux的特点,相较于其他unix内核而言。
  2. 硬件依赖是什么?
  3. 操作系统是什么?
  4. Unix是什么,其文件系统和内核又是什么?

Linux与其他类Unix比较

从技术角度来说,Linux是一个真正的Unix内核,但它不是一个完全的Unix操作系统,这是因为它不包含全部的Unix应用程序,诸如文件系统、窗口系统及图形化桌面、系统管理员命令、文本编辑程序、编译程序等等。不过以上大部分应用程序都可以在GUN许可证下免费获得,因此可以把它们安装在任何一个基于Linux内核的系统中。

Linux包括了现代Unix操作系统的全部特点,如虚拟存储、虚拟文件系统、轻量级进程、Unix信号量、SVR4进程间通信、支持对称多处理器(Symmetric Multiprocessor,SMP)系统等。

Linux与一些商用Unix内核如何竞争:

  • 单块结构的内核(Monolithic kernel):

    它是一个庞大、复杂的自我完善(do-it-yourself)程序,有几个逻辑上独立的成分构成。大多数商用Unix也是单块结构。

  • 编译并静态连接的转筒Unix内核:

    大部分现代操作系统内核可以动态地装载和卸载部分内核代码(典型例子如设备驱动程序),通常把这部分代码称作模块。Linux对模块地支持是很好的,因为他能自动按需装在或卸载模块。

  • 内核线程

    内核线程是一个能被独立调度的执行环境;也许它与用户程序有关,也许仅仅执行一些内核函数。线程之间的上下文切换比普通进程之间的上下文切换花费的代价要少得多,因为前者通常在同一个地址空间执行。Linux以一种十分有限的方式使用内核线程来周期性地执行几个内核函数;但是,它们并不代表基本地执行上下文的抽象。

  • 多线程应用程序支持

    用户程序是根据很多相对独立的执行流来设计的,而这些执行流之间共享应用程序的大部分数据结构。一个多线程的用户程序由很多轻量级进程(lightweight process,LWP)组成,这些进程可能对共同的地址空间、共同的物理内存页、共同的打开文件等进行操作。Linux定义了自己的LWP版本。

  • 抢占式(preemptive)内核

    当采用“抢占式内核”选项来编译内核时,Linux可以随意交错执行处于特权模式的执行流。

  • 多处理器支持

    Linux支持不同存储模式的对称多处理(SMP),包括NUMA:系统不仅可以使用多处理器,而且每个处理器可以毫无区别地处理任何一个任务。尽管通过一个单独的“大内核锁”使得内核中少数代码依然串行执行,但公平的说,Linux以几乎最优化的方式使用SMP。

  • 文件系统

    普通的Ext2文件系统;可以避免系统崩溃后冗长的文件系统检查的Ext3文件系统;如果你不得不处理很多小文件,Reiser FS很适合;还有一些日志文件;还有强大的面向对象虚拟文件系统技术。

  • STREAMS

    大部分Unix内核包含了SVR4引入的STREAMS I/O子系统,但Linux并没有类似的子系统。

Linux的优势:

  1. Linux是免费的
  2. Linux所有成分都可以充分的定制
  3. Linux可以运行在低档、便宜的平台上。可以在4MB内存的80386上构建网络服务器。
  4. Linux是强大的,充分挖掘硬件部分的特点,十分高效,内核非常小,且紧凑
  5. Linux与很多操作系统高度兼容
  6. Linux的开发者都是非常出色的程序员,有很好的技术支持

稳定的Linux版本内核由Linux的发布者和内核黑客彻底检查过。好牛

硬件依赖

Linux试图在硬件无关的代码和相关的代码之间保持清晰的界限。在arch和include目录下包含的子目录来应对Linux所支持的不同硬件平台。

那么硬件依赖是什么?

操作系统基本概念

任何计算机系统都包含一个名为操作系统的基本程序集合,集合中最重要的程序称为内核,术语“操作系统”常常作为“内核”的同义词。

操作系统必须要完成的两个主要任务:

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

一些操作系统允许所有的用户程序都直接与硬件部分进行交互(如MS-DOS)。但是,类Unix系统把与计算机物理组织相关的所有低层细节都对用户运行的程序隐藏。程序想要使用硬件资源,必须向操作系统发出请求,内核进行评估,如果允许,则由内核代表程序与相关硬件部分进行交互。

为了实现这种机制,现代操作系统依靠特殊的硬件特性来禁止用户程序直接与低层硬件部分进行交互,或者禁止直接访问任意的物理地址。特别的,硬件为CPU引入了至少两种执行模式:用户程序的非特权模式和内核的特权模式。Unix把它们称为用户态(User Mode)和内核态(Kernel Mode),实际上一些CPU可以有两种以上的执行状态。

多用户系统

多用户系统(multiuser system)就是一台能并发和独立地执行分别属于两个或多个用户地若干应用程序地计算机。“并发”(concurrently)意味着几个应用程序能同时处于活动状态并竞争各种资源,如CPU、内存等。“独立”(independently)意味着每个应用程序能执行自己地任务,无需考虑其他用户地应用程序在干什么。从一个应用程序切换到另一个会使每一个应用程序的速度有所减慢,现代操作系统内核提供许多复杂特性减少强加在每个程序上的延迟时间。

多用户操作系统的特点:

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

为了确保实现这些安全保护机制,操作系统必须利用与CPU特权模式相关的硬件保护机制,否则,用户程序便可以直接访问系统电路并克服这些限制。Unix实施系统资源硬件保护。

用户和组

在多用户系统中,每一个用户在机器上都有私用空间;典型地,他拥有部分磁盘空间来存储。操作系统必须保证用户空间的私有部分仅对拥有者可见,且没有用户能够开发出用于侵犯他人用户私有空间的程序。

所有用户有一个唯一的数字来标识,即用户标识符(User ID,UID)

组也有唯一的数字来标识,即用户组标识符(user group ID),用来组内共享文件。

任何类Unix系统都有一个特殊的用户——root,超级用户。系统管理员的登陆身份,他几乎无所不能。

进程

所有操作系统都使用一种基本的抽象:进程(process)。一个进程可以定义为“程序执行时的一个实例“或者一个运行程序的”执行上下文“。

在传统的操作系统中,一个进程在地址空间(address space)中执行一个单独的指令序列。地址空间是允许进程引用的内存地址集合。现代操作系统允许具有多个执行流的进程。即在相同的地址空间执行多个指令序列。

区分程序和进程是非常重要的:几个进程能够并发执行同一程序,同一个进程能够顺序地执行几个程序。

多用户系统必须实施多道程序系统(multi programming)或多处理器系统(multiprocessing),多用户系统的进程必须是抢占式的(preempt able)。

要注意一些多处理器系统并不是多用户的

单处理器系统只有一个进程能够占用CPU,即在某一个时刻只有一个执行流。操作系统中的调度程序(scheduler)决定哪几个进程能够执行。一些操作系统只允许有非抢占式的(nonpreempt able)进程,意味着,除非进程自动放弃占用CPU,调度程序才被调用。

Unix是具有抢占进程的多处理器操作系统。

类Unix操作系统采用进程/内核模式。每一个进程都自以为它是系统中唯一的进程,可以独占操作系统所提供的服务。只要进程发出系统调用(即对内核提出申请),硬件就会把执行模式由用户态改为内核态,然后进程以非常有限的目的开始一个内核过程的执行。这样操作系统在进程的执行上下文中起作用,以满足进程的请求。一旦这个请求完成,内核过程将迫使硬件返回到用户态,然后进程从系统调用的下一条指令继续执行。

内核体系结构

大部分Unix内核是单块结构:每一个内核层都被集成到整个内核程序中,并代表当前进程在内核态下运行。相反,微内核(microkernel)操作系统只需要内核有一个很小的函数集,通常包括几个同步原语、一个简单的调度程序和进程间通信机制。

微内核操作系统相较单块内核的效率低(不同层级要显式消息传递而花费的代价),不过微内核理论与结构更简单:微内核操作系统采用了模块化的编程方法,易移植;且微内核操作系统充分利用了ram:暂且不需要执行的系统进程可以被调出或者撤销。

为了做到微内核的优点,Linux内核提供了模块(module)。

模块是一个目标文件,其代码可以运行时链接到内核或者从内核解除链接。这种目标代码通常由一组函数组成,用来实现文件系统、驱动程序或者其他内核上层功能。与微内核操作系统的外层不同,模块不是作为一个特殊的进程执行的。相反,与任何其他静态链接的内核函数一样,它代表当前进程在内核态下执行。使用模块的主要优点如下:

  1. 模块化方法:任何模块都可以在运行时链接或者解除链接,明确的软件接口使开发新模块更容易。
  2. 平台无关性。
  3. 节省内存使用:不用时可以被解除。
  4. 无性能损失:无需显式地进行消息传递。

Unix文件系统概述

Unix操作系统地设计集中反映在其文件系统上,文件系统有几个重要且有趣的特点。

文件

Unix文件是以字节序列组成的信息载体(container),内核不解释文件的内容。

从用户的观点来看,文件被组织在一个树结构的命名空间中。除了叶节点,其它都是目录名,目录节点包含它下面文件及目录的所有信息;树根称为“根目录”(root directory)名字为“/”。

文件名与目录名由除“/”和空字符“\0”之外的任意ASCII字符序列组成,一些操作系统允许以多种字符来表示文件名。同目录中,不可以有相同名字目录或文件,不同目录可以。

Unix的每一个进程都有当前工作目录,它属于进程执行上下文(execution context),标识出进程所用的当前目录。

为了标识一个特定文件,进程使用路径名,由“/”和目录名与末端文件名组成。

绝对路径:路径起始为“/” 相对路径:路径起始为文件或目录名

“.”表示当前工作目录。“…”表示父目录,如果当前目录是“/”,则“.”“…”的指向相同,为根目录。

硬链接与软链接

包含在目录中的文件名就是一个文件的硬链接(hard link),简称链接(link)。在同一个目录或不同目录中,同一文件可以有几个链接,因此对应几个文件名。

Unix命令:ln P1 P2

用来创建一个新链接,即有路径P1标识的文件创建一个路径名为P2的硬链接。

硬链接有两个方面的限制:

  • 不允许用户给目录。因为这样可能把目录树变成一个环状,从而无法通过名字定位文件。
  • 只有在同一文件系统中的文件之间才能创建链接。现代Unix系统会包含多种文件系统。

为了克服这些限制,引入软链接(soft link)(或者叫符号链接symbolic link)符号链接是短文件,这些文件包含有另一个文件的任意一个路径名。路径名可以指向位于任意一个文件系统的任意文件或目录,甚至是一个不存在的文件。

Unix命令:ln -s P1 P2

创建一个路径名为P2的新软链接,P2指向P1。当执行这个命令时,文件系统抽出P2的目录部分,并在那个目录下创建一个名为P2的符号连接类型的新项,这个新文件包含路径名P1。这样对P2的引用可以被自动转换成指向P1的引用。

文件类型

符号文件描述
-普通文件regular file
d目录
l符号链接,链接文件
b面向块的设备文件(block-oriented device file),块设备文件,
存取是以一个字块(block)为单位。磁盘硬盘等
s面向字符的设备文件(character-oriented device file),字符设备文件
存取数据时是以单个字符为单位的。
p管道(pipe)和命名管道(named pipe)(也叫FIFO)负责将一个进
程的信息传递给另一个进程,从而使该进程的输出成为另一个进程的输入。
s套接字(socket)

前三种是基本类型,设备文件与I/O设备以及集成到内核中的设备驱动程序相关。管道和套接字设计用于进程通信的特殊文件

套接字文件系统是一个用户不可见的,高度简化的,用于汇集网络套接字的内存文件系统,它没有块设备, 没有子目录,没有文件缓冲,它借用虚拟文件系统的框架来使套接字与文件描述字具有相同的用户接口。当用户用socket(family,type,protocol)创建一个网络协议族为family, 类型为type,协议为protocol的套接字时, 系统就在套接字文件系统中为其创建了一个名称为其索引节点编号的套接字文件。

文件描述符和索引节点

Unix对文件内容和描述文件的信息给出了清楚的区分,除了设备文件和特殊文件系统文件外,每一个文件都是有字符序列组成。文件内容不包含任何控制信息,如文件长度或文件结束符(end-of-file,EOF)。

文件系统处理文件所需要的所有信息包含在了一个名为索引节点(inode)的数据结构中,每一个文件都有自己的索引节点,文件系统通过索引节点来标识文件。

访问权限和文件模式

文件的潜在用户类型分为三类:文件所有者的用户;同组用户、不包含所有者;其他用户。

有三种访问权限,读、写和执行。文件的访问允许权限共有9种,分别是:rwxrwxrwx,分别对应用户、组、其他的读写执行权限。

还有三种附加的标记,suid(set user id),sgid(set group id),sticky关于suid的用法说明,还是写下来吧。书中描述:

  • suid:进程执行一个文件时通常保持进程拥有者的UID,然而,如果设置了suid标志位,进程就会获得该文件拥有者的UID。
  • sgid:进程执行一个文件时,通常保持进程组的用户组ID,如果设置了sgid标志位,则进程获得文件用户组ID
  • sticky:设置了sticky,执行完的可执行文件将被保留在内存(已过时)

举一个例子,我们都知道,Linux 系统中所有用户的密码数据都记录在/etc/shadow这个文件中,通过ll /etc/shadow命令可以看到,此文件的权限是0(---------),也就是说,普通用户对此文件没有任何操作权限。

这就会产生一个问题,为什么普通用户可以使用passwd命令修改自己的密码呢?

查看passwd命令的权限配置,可以看到,此命令拥有SUID特殊权限,而且其他人对此文件也有执行权限,这就意味着,任何一个用户都可以用文件所有者,也就是以root的身份去执行passwd命令。

当文件有一个进程创建时,文件拥有者的ID就是该进程的UID,而其用户组ID可以是进程创建者ID,也可以是父目录ID,取决于父目录sigd标志位的值。

文件操作的系统调用

当用户访问一个普通的文件或者目录文件的内容时,他实际上是访问存储在硬件块设备上的一些数据。

所以说,文件系统时硬盘分区物理组织的用户级视图

实际的文件操作都是在内核态下进行的,Unix操作系统定义了几个与文件操作有关的系统调用

打开文件

进程只能访问“打开的”文件。为了打开文件,进程调用系统调用:

​ fd=open(path,flag,mode)

path:表示被打开的文件的(相对或绝对)路径
flag:指定文件打开的方式(例如读写、读/写、追加)。也指定是否创建一个不存在的文件。
mode:指定新创建文件的访问权限。

系统调用创建一个“打开文件”对象,并返回文件描述符(file descriptor)的标识符fd。

打开文件对象包括:

  • 文件操作的一些数据,如指定打开方式的一组标志;表示文件当前位置的offset字段,等
  • 进程可以调用的一i额内核函数指针。这组允许调用的函数集合有flag值决定

文件描述符fd表示进程与打开文件之间的交互。打开文件对象包含了与这种交互有关的数据。同一打开文件对象也许有同一个进程中的几个文件描述符标识。

几个进程也许同时打开同一文件。这种情况下,文件系统给每一个文件都分配一个单独的打开文件对象以及单独的文件描述符;且,Unix文件系统对进程在同一文件上发出的I/O操作之间不提供任何形式的同步机制。(不过,有几个系统调用,如flock(),可用来让进程在整个文件或部分文件上对I/O操作实施同步)

访问打开的文件

对于普通Unix文件,可以顺序访问,也可以随机地访问,而对设备文件和命名管道文件,通常只能顺序访问。访问文件时,内核把文件指针存放在打开文件对象中,也就是说,当前位置的下一次进行读写数据的位置。

顺序访问是文件的默认访问方式。就是说read()和write()系统调用总是从文件指针的当前位置开始读写。为了修改文件指针的值,必须在程序在中显式地调用lseek()系统调用

打开文件时,内核当文件指针指向文件的第一个字节(偏移量为0)。

​ newoffset=lseek(fd,offset,whence);

fd:表示打开文件的文件描述符
offset:指定一个有符号整数值,用来计算文件指针的新位置
whence:指定文件指针新位置的计算方式。offset加0表示文件指针从文件头开始移动;offset加文件指针当前位置表示文件指针从当前位置启动;offset加文件最后一个字节的位置表示文件指针从文件末尾开始移动

​ nread=read(fd,buf,count);

fd:表示打开文件的文件描述符
buf:指定在进程地址空间中缓存区的地址,所读的数据就放在这个缓冲区
count:表示所读的字节数

write()参数与read()相似。

当处理这样的系统调用时,内核会尝试从拥有文件描述符fd的文件中读取count个字节,其起始位置为打开文件的offset字段的当前值。在某种情况下可能遇到文件结束、空管道等等,因此内核无法成功地读出全部count个字节。返回的nread就是实际所读的字节数。给原来的值加上nread就会更新文件指针。

关闭文件

​ res=close(fd);

当进程无需再访问文件的内容时,调用close()系统调用。释放与文件描述符fd相对应的打开文件对象。当进程终止时,内核会关闭其所有仍然打开着的文件。

更名及删除文件

更名或删除文件时,进程不需要打开它。仅对一个或多个目录内容起作用。

​ res=rename(oldpath,newpath);

改变文件链接的名字:

​ res=unlink(pathname);

减少文件链接数,删除相应的目录项,当文件链接数为0时,文件才被真正删除。

Unix内核概述

Unix内核提供了应用程序可以运行的执行环境。因此内核必须实现一组服务及相应的接口。应用程序使用这些接口,通常不会与硬件资源直接交互。

内核不仅仅处理系统调用。有几种方法可以激活内核历程。

  • 进程调用系统调用。
  • 正在执行进程的CPU发出一个异常(exception)信号,内核代表该进程处理异常。
  • 外围设备向CPU发出一个中断(interrupt)信号来通知一个事件的发生。每个中断信号都是由内核的中断处理程序(interrupt handler)来处理的。(中断时间不可预知)
  • 内核线程被执行。内核线程运行在内核态,所以认为其程序也是内核的一部分。

进程/内核模式

Unix系统中包括

  • 用户进程

  • 几个所谓内核线程(kernel thread)的特权进程

    特点:以内核态运行在内核地址空间
    不与用户直接交互,不需要终端设备
    通常在系统启动时创建,一直处于活跃状态直到系统关闭。

用户态下,程序不能直接访问内核数据结构和内核程序,但是内核态可以。每种CPU模型都为两种执行状态的切换转换提供了特殊的指令。

进程是动态的实体,在系统内通常只有有限的生存期。创建、撤销以及同步现有的进程的任务都委托给内核中的一组例程完成。

内核本身并不是一个进程,而是进程的管理者。

进程/内核模式假定:请求内核服务的进程使用的所谓系统调用(system call)的特殊编程机制。

进程实现

为了让内核管理进程,每个进程由一个进程描述符(process descriptor)表示,这个描述符包含了有关进程当前状态的信息。

当内核暂停一个进程的执行时,就把几个相关处理器寄存器的内容保存在进程描述符中,这些寄存器有:

  • 程序计数器(PC)和栈指针(SP)寄存器
  • 通用寄存器
  • 浮点寄存器
  • 包含CPU状态信息的处理器控制寄存器(处理器状态字,Processor Status Word)
  • 用来跟踪进程对RAM访问的内存管理寄存器

当内核决定恢复执行一个进程时,它用进程描述符中合适的字段来装载CPU寄存器。因为程序计数器中所存的值指向下一条将要执行的指令,所以进程从他停止的地方恢复执行。

当一个进程不在CPU上执行时,它在等待某一事件。Unix内核可以区分很多等待状态,这些等待状态通常由进程描述符队列实现。每个队列(可能为空)对应一组等待特定事件的进程。

可重入内核

所有Unix内核都是可重入的(reentrant),指若干个进程可以同时在内核态下执行。当然在单处理器系统上只有一个进程在真正运行,但是有许多进程可能在等待CPU或者某一I/O操作完成时在内核态下阻塞。

提供可重入的一种方式是编写函数,以便这些函数只能修改局部变量,而不能改变全局数据结构,这样的函数叫做可重入函数。

可重入内核并不仅仅局限于可重入函数,相反可重入内核可以包含非重入函数,并且利用锁机制保证一次只有一个进程执行这样的非重入函数。

如果一个硬件中断发生,可重入内核能够挂起当前正在执行的进程,即使这个进程处于内核态。这种能力很重要,能提高发出中断的设备控制器的吞吐量。如果内核能够快速应答,设备控制器就能在CPU处理中断时执行其他任务。

一旦设备发出一个中断,它就一直等待直到CPU应答它为止。

内核控制路径(kernel control path):表示内核处理系统调用、异常或中断所执行的指令序列。

进程地址空间

每一个进程运行在它的私有地址空间。但有时进程之间也共享部分地址空间。在不同情况下,这种共享可以由进程显式地提出,或者由内核自动完成共享以节约内存。

如果同一个程序(如编辑程序)由几个用户同时使用,则这个程序只被装入内存一次,其指令由所有需要它的用户共享。其数据不被共享,因为每个用户都将有独立的数据。这种共享地地址空间由内核完成以节约内存。

进程间共享部分地址空间,以实现进程间通信,这是由System V引入并且被Linux支持的“共享内存”技术。

因为内核时可重入的,因此几个内核控制路径(每一个都与不同的进程相关)可以轮流执行。在这种情况下情况下,每个内核控制路径都引用它自己的私有栈。

Linux支持mmap()系统调用,此系统调用允许存放在块设备上的文件或信息的一部分映射到进程的部分地址空间

内存映射为正常的读写传送数据方式提供了一种选择

如果同一文件由几个进程共享,那么共享它的每个地址空间都包含有它的内存映射

同步和临界区

实现可重入内核需要利用同步机制:如果内核控制路径对某个内核数据结构进行操作时被挂起,那么其他内核控制路径就不应当对该数据结构进行操作,除非它被重新设置成一致性(consistent)状态。否则两个内核控制路径交互作用将破坏所存储的信息。

一般来说,对全局变量的安全访问通过原子操作(atomic operation)来保证

临界区(critical region):一段代码,进入这段代码的进程必须完成,之后另一个进程才能进入。

几种同步技术已经被采用:

  • 非抢占式内核

    大多数传统的Unix内核是非抢占式的,指当进程在内核态执行时,它不能被任意挂起,也不能被另一个进程替代。因此在单处理器系统上,中断或异常处理程序不能修改的所有内核数据结构,内核对他们的访问是安全的。

    如果内核支持支持抢占,那么在应用同步机制时,确保进入临界区前禁止抢占,退出临界时启用抢占。

    非抢占能力在多处理器系统上是低效的,因为运行在不同CPU上的两个内核控制路径本可以并发地访问相同的数据结构。

  • 禁止中断

    单处理器上的另一种同步机制:进入一个临界区之前禁止所有硬件中断,离开时再启用。

    这种机制尽管简单,但远不是最佳。

  • 信号量

    被广泛使用,包括单处理器和多处理器系统。信号量仅仅是一个数据结构相关的计数器。所有内核线程在试图访问这个数据结构之前,都要检查这个信号量,可以把每个信号量看成一个对象,其组成如下:

    • 一个整数变量

    • 一个等待进程的链表

    • 两个原子方法down()和up()

      down()方法对信号量的值减1,如果新值小于零,该方法就把正在运行的进程加入到这个信号量链表,然后阻塞该进程(调用调度程序)。up()方法对信号量的值加1,如果新值大于或等于零,则激活这个信号链表中的一个或多个进程。

  • 自旋锁

    有些多处理器系统采用信号量的方法效果不是最佳。自旋锁与信号量很像,但是没有进程链表;当一个进程发现锁被另一个进程锁着时,它就不停地“旋转”,执行一个紧凑的循环指令直到锁打开。

    在单处理器系统上,自旋锁无效。

  • 避免死锁

    与其他控制路径同步的进程或内核控制路径很容易进入死锁(deadlock)状态。比如进程1在访问数据结构a时,想去访问数据结构b,但是进程2正在访问数据结构b,且进程2正在等待访问数据结构a。导致循环等待。

    有几种操作系统包括Linux通过按规定的顺序请求信号量来避免死锁。

信号和进程间通信

Unix信号(signal)提供了把系统事件报告给进程的一种机制。

每种事件都有自己的信号编号,通常用一个符号常量来表示,例如SIGTERM。

有两种系统事件:
异步通告:例如当用户在终端按下中断键(通常是Ctrl+C),即向前台进程发出中断信号SIGINT
同步错误或异常:如,当进程访问内存非法地址时,内核向这个进程发送SIGSEGV信号

AT&T的Unix System V引入了用户态下其他种类的进程间通信机制。很多Unix内核也采用这些机制:信号量、消息队列和共享内存,统称System V IPC。

此信号量与“同步和临界区”章节中的信号量相似,但这里它们用在用户态下的进程中。

共享内存为进程间交换和共享数据提供了最快的方式。

进程管理

Unix在进程和它正在执行的程序之间有明确的界限。fork()用来创建一个新进程,_exit()来终止进程,exec()类系统调用则是装入一个新程序。

调用fork()的进程是父进程,新进程是它的子进程。

僵死进程(zombie process)

父进程如何知道子进程是否终止?wait4()系统调用允许进程等待,直到其中的一个子进程结束;返回已终止紫禁城的进程标识符(process ID,PID)。

引入僵死进程的特殊状态是为了表示终止的进程:父进程在执行完wait4()之前,进程就一直停留在那种状态。

进程组和登录会话

现代Unix操作系统引入了进程组(process group)的概念,一表示一种“作业(job)”的抽象。

如 ls | sort | more

Shell支持进程组,如上面,bash,为三个相应的进程ls、sort和more创建了一个新的进程组,shell以这种方式作用于这三个进程,将他们当作“作业(job)”。

每个进程描述符包括一个包含进程组ID的字段,进程组可以有一个领头进程,其PID和进程组的ID相同。

新创建的进程最初被插入到父进程的进程组中。

现代Unix内核引入了登录会话(login process),通常情况下,登录会话就是shell进程为用户创建的第一条命令。

进程组中的所有进程必须在同一个登录会话中。一个登录会话可以让几个进程组同时处于活动状态,其中一个进程组一直处于前台,这意味着该进程组可以访问终端,而其他活动着的进程组在后台。

内存管理

虚拟内存(virtual memory)

虚拟内存是一作为一个逻辑层,处于应用程序的内存请求与硬件内存管理单元(memory management unit,MMU)之间。虚拟内存由很多用途和优点:

  • 若干个进程可以并发地执行
  • 应用程序所需内存大于可用物理地址时也可以运行。
  • 程序只有部分代码装入内存时,进程可以执行它。
  • 允许每个进程访问可用物理内存的子集。
  • 进程可以共享库函数或程序的一个单独内存映像。
  • 程序是可重定位的,可以把程序放在物理内存的任何一个地方。
  • 程序员可以编写与机器无关的代码,因为不必关心有关物理内存的组织结构。

虚拟内存子系统的只要成分是虚拟地址空间(virtual address space)的概念。当一个进程使用一个虚拟地址时,内核和MMU协同定位其在内存中的实际物理地址。

进程所用的一组内存地址不同于物理内存地址。

现在的CPU包含了能自动把虚拟地址转换为物理地址的硬件电路。如将可用RAM划分成长度为4KB或者8KB的页框(page frame),并引入一组页表来指定虚拟地址与物理地址之间的对应关系。这些电路使内存分配变得简单,因为一块连续的虚拟地址请求可以通过分配一组非连续的物理地址页框而得到满足。

随机访问存储器(RAM)的使用

所有的Unix系统都将RAM毫无意义地划分为两部分,其中若干兆字节专门用于存放系统内核映像(内核代码和内核静态数据结构)。RAM的其余部分通常由虚拟内存系统来处理。

内核内存分配器

kernel memory allocator,KMA,内核内存分配器是一个子系统,它试图满足系统中所有部分对内存的请求。

一个好的KMA具有如下优点:

  1. 必须快。
  2. 必须把内存的浪费减到最少。
  3. 必须努力减轻内存的碎片(fragmentation)问题。
  4. 必须能与其他内存管理子系统合作,以便借用和释放页框。

Linux在KMA的伙伴系统之上采用了Slab分配算法。

进程虚拟地址空间处理

进程的虚拟地址空间包括了进程可以引用的所有虚拟内存地址。内核通常用一组内存区描述符描述进程虚拟地址空间。

现代Unix系统都采用了所谓的请求调页(demand paging)的内存分配策略。

虚拟地址空间也采用其他更有效的策略。

高速缓存

物理内存的一大优势就是用作磁盘和其他块设备的高速缓存。因为硬盘的访问需要数毫秒,与RAM相比太长了。(磁盘通常影响系统性能)

在最早的Unix系统中,就已经尽可能地推迟写磁盘的时间,即使从磁盘存入RAM的数据长时间不被任何进程使用,也继续留在RAM中。当一个进程请求访问磁盘时,内核首先检查进程请求的数据是否在内存中,如果在,内核就可以为进程请求提供服务而不用访问磁盘。

sync()系统调用把所有“脏”的缓冲区(即缓冲区的内容与对应磁盘块的内容不一样)写入磁盘来强制磁盘同步。

为了避便数据丢失,所有的操作系统都会注意周期性地把脏缓冲区写回磁盘。

设备驱动管理

内核通过设备驱动程序(device driver)与I/O设备交互。设备驱动程序包含在内核中,由控制一个或多个设备的数据结构和函数组成,这些设备包括硬盘、键盘、鼠标、监视器、网络接口及连接到SCIS总线设备上的设备。

通过特定的接口,每一个驱动程序与内核中的其余部分相互作用这种方式,具有如下优点:

  • 可以把特定的设备的代码封装在特定的模块中。
  • 厂商可以在不了解内核源代码而只知道接口规范的情况下,就能增加新的设备。
  • 内核以统一的方式对待所有的设备,并且通过相同的接口访问这些设备。
  • 可以把设备驱动程序写成模块,并动态地把它们装进内核而不需要重新启动系统。不需要时,可以动态地卸载模块,以减少存储在RAM中的内核映像的大小。

设备文件是设备驱动程序接口中用户可见的部分。
先检查进程请求的数据是否在内存中,如果在,内核就可以为进程请求提供服务而不用访问磁盘。

sync()系统调用把所有“脏”的缓冲区(即缓冲区的内容与对应磁盘块的内容不一样)写入磁盘来强制磁盘同步。

为了避便数据丢失,所有的操作系统都会注意周期性地把脏缓冲区写回磁盘。

设备驱动管理

内核通过设备驱动程序(device driver)与I/O设备交互。设备驱动程序包含在内核中,由控制一个或多个设备的数据结构和函数组成,这些设备包括硬盘、键盘、鼠标、监视器、网络接口及连接到SCIS总线设备上的设备。

通过特定的接口,每一个驱动程序与内核中的其余部分相互作用这种方式,具有如下优点:

  • 可以把特定的设备的代码封装在特定的模块中。
  • 厂商可以在不了解内核源代码而只知道接口规范的情况下,就能增加新的设备。
  • 内核以统一的方式对待所有的设备,并且通过相同的接口访问这些设备。
  • 可以把设备驱动程序写成模块,并动态地把它们装进内核而不需要重新启动系统。不需要时,可以动态地卸载模块,以减少存储在RAM中的内核映像的大小。

设备文件是设备驱动程序接口中用户可见的部分。

  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 0
    评论
前言 第一章绪论 Linux与其他类Unix内核的比较 硬件的依赖性 Linux版本 操作系统基本概念 Unix文件系统概述 Unix内核概述 第二章内存寻址 内存地址 硬件中的分段 Linux中的分段 硬件中的分页 Linux中的分页 第三章进程 进程、轻量级进程和线程 进程描述符 进程切换 创建进程 撤消进程 第四章中断和异常 中断信号的作用 中断和异常 中断和异常处理程序的嵌套执行 初始化中断描述符表 异常处理 中断处理 软中断及tasklet 工作队列 从中断和异常返回 第五章内核同步 内核如何为不同的请求提供服务 同步原语 对内核数据结构的同步访问 避免竞争条件的实例 第六章定时测量 时钟和定时器电路 Linux计时体系结构 更新时间和日期 更新系统统计数 软定时器和延迟函数 与定时测量相关的系统调用 第七章进程调度 调度策略 调度算法 调度程序所使用的数据结构 调度程序所使用的函数 多处理器系统中运行队列的平衡 与调度相关的系统调用 第八章内存管理 页框管理 内存区管理 非连续内存区管理 第九章进程地址空间 进程的地址空间 内存描述符 线性区 缺页异常处理程序 创建和删除进程的地址空间 堆的管理 第十章系统调用 POSIXAPI和系统调用 系统调用处理程序及服务例程 进入和退出系统调用 参数传递 内核封装例程 第十一章信号 信号的作用 产生信号 传递信号 与信号处理相关的系统调用 第十二章虚拟文件系统 虚拟文件系统(VFS)的作用 VFS的数据结构 文件系统类型 文件系统处理 路径名查找 VFS系统调用的实现 文件加锁 第十三章I/O体系结构和设备驱动程序 I/O体系结构 设备驱动程序模型 设备文件 设备驱动程序 字符设备驱动程序 第十四章块设备驱动程序 块设备的处理 通用块层 I/O调度程序 块设备驱动程序 打开块设备文件 第十五章页高速缓存 页高速缓存 把块存放在页高速缓存中 把脏页写入磁盘 sync()、fsync()和fdatasync()系统调用 第十六章访问文件 读写文件 内存映射 直接I/O传送 异步I/O 第十七章回收页框 页框回收算法 反向映射 PFRA实现 交换 第十八章Ext2和Ext3文件系统 Ext2的一般特征 Ext2磁盘数据结构 Ext2的内存数据结构 创建Ext2文件系统 Ext2的方法 管理Ext2磁盘空间 Ext3文件系统 第十九章进程通信 管道 FIFO SystemVIPC POSIX消息队列 第二十章程序的执行 可执行文件 可执行格式 执行域 exec函数

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

排着队啊

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值