Linux 从入门到项目实践(超值版)
上QQ阅读APP看书,第一时间看更新

3.2 Linux内核

内核属于操作系统的核心部分,它具有操作系统基本的功能,主要负责管理系统的内存、进程、设备驱动程序、文件系统和网络接口,因此,操作系统的性能和稳定性由内核决定,如图3-8所示。

图3-8 内核的结构

3.2.1 内存管理

1.进程对内存的使用

计算机中所有要执行的程序都必须占有一定数量的内存,它主要是用来存放从磁盘中存放的程序代码,也可以是存放来自用户输入的数据等。Linux操作系统采用的是虚拟内存管理技术,这样可以使每个进程都有各自互不干扰的进程地址存储空间。该空间是块大小为4GB的线性虚拟空间,用户所看到和所接触到的都是虚拟的地址,并不能看到实际的物理内存地址。因此利用虚拟地址不仅能保护操作系统,而且方便用户程序使用比实际物理内存较大的地址空间。

一个普通的进程包括代码段、数据段、BSS段、堆和栈5个不同的数据段。

(1)代码段:主要用来存放可执行文件的操作指令。代码段只允许读取操作,不允许修改操作,它是为了防止在运行时被非法修改。

(2)数据段:数据段用来存放可执行文件中已经初始化的全局变量,也就是存放程序静态分配的变量和全局变量。

(3)BSS段:BSS段包含了程序中未初始化的全局变量,在内存中BSS段全部置“0”。

(4)堆:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态地扩张或缩减。当进程调用“malloc”函数分配内存时,新分配的内存就被动态地添加到堆上(堆被扩张);当利用“free”函数释放内存时,被释放的内存从堆中被删除(堆被缩减)。

(5)栈:栈是用户为了存放程序而临时创建的一个局部变量。栈具有先进先出的特点,所以栈可以用来保存或恢复调用现场。因此,可以把堆栈当作一个寄存、交换临时数据的内存区。

注意:数据段、BSS和堆通常是被连续存储的即在内存位置上是连续的。

2.物理内存

物理内存是系统硬件提供的真实的内存大小,除了物理内存之外,在Linux系统中还有一个虚拟内存,虚拟内存是为了满足物理内存的不足而存在的,它是利用磁盘空间虚拟出的一块逻辑内存,用作虚拟内存的磁盘空间被称为交换空间。

对于计算机系统而言,计算机的内存以及其他资源都是固定且有限的。为了让有限的物理内存满足应用程序对内存的大需求量,Linux系统采用了称为“虚拟内存”的内存管理方式。虽然应用程序操作的对象是映射到物理内存之上的虚拟内存,但处理器直接操作的是物理内存。例如:当一个应用程序访问一个虚拟地址时,首先必须将虚拟地址转化成物理地址,然后处理器才能解析地址访问请求。作为物理内存的扩展,Linux系统会在物理内存不足时,使用交换分区的虚拟内存,总的来说,就是内核会将暂时不用的内存块信息存储到交换空间,从而使物理内存得到了释放,这块内存就可以用于其他目的了,当需要用到原始内容时,这些信息会被重新从交换空间读入到物理内存。

Linux系统将内存划分为容易处理的“内存页”。Linux包括了管理可用内存的方式,以及物理和虚拟映射所使用的硬件机制。但内存管理要管理的可不止4KB缓冲区。Linux提供了对4KB缓冲区的抽象,例如slab分配器。这种内存管理模式使用4KB缓冲区为基数,然后从中分配结构,并跟踪内存页的使用情况,例如哪些内存页是满的,哪些页面没有完全使用,哪些页面为空。这样就允许该模式根据系统需要来动态调整内存使用。

注意:Linux系统内核的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块信息自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。

为了支持多个用户使用内存,有时会出现可用内存被完全消耗的情况。由于这个原因,页面可以移出内存并放入磁盘中,这个过程称为交换,就是页面会从内存中被交换到硬盘上。

Linux系统进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,Linux内核根据最近最经常使用的算法,只需要将一些不经常使用的页面文件交换到虚拟内存中。例如,一个占用很大内存的进程运行时,需要耗费很多内存资源,此时就会有一些不常用的页面文件被交换到虚拟内存中,当这个占用很多内存资源的进程结束并释放了很多内存时,刚才被交换出去的页面文件不会自动地交换进物理内存,这时系统物理内存就会有很多的空闲,同时交换空间也被使用。

交换空间的页面在使用时会首先被交换到物理内存,如果此时没有足够的物理内存来容纳这些页面,它们会被马上交换出去,如此一来,虚拟内存中可能没有足够的空间来存储这些交换页面,最终导致Linux系统出现假死机、服务异常问题,虽然它可以在一段时间内自行恢复,但是恢复后的系统已经基本不可以使用了。

因此,合理规划和设计Linux内存使用,是非常重要的。

3.2.2 进程管理

1.进程的概述

进程是在自身的虚拟地址空间运行的一个独立的程序,从操作系统的角度来看,所有在系统上运行的东西,都可以称为一个进程。在Linux系统中,能够同时运行多个进程,Linux通过在短的时间间隔内轮流运行这些进程而实现“多任务”。这一短的时间间隔称为“时间片”,让进程轮流运行的方法称为“进程调度”,完成调度的程序称为调度程序。

注意:程序和进程的区别:进程虽然由程序产生,但是它并不是程序。程序是一个进程指令的集合,它可以启用一个或多个进程,同时,程序只占用磁盘空间,而不占用系统运行资源,而进程仅仅占用系统内存空间,是动态的、可以改变的。如果进程关闭,其所占用的内存资源将随之释放。

进程调度控制进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。运行的进程是指在等待CPU资源的进程,如果某个进程在等待其他资源,则该进程是不可运行进程。Linux使用了比较简单的基于优先级的进程调度算法选择新的进程。

通过多任务机制,每个进程可以单独占用计算机,从而简化了程序的编写。每个进程有自己单独的地址空间,并且只能由这一进程访问,这样,操作系统避免了进程之间的互相干扰以及“恶意攻击”程序对系统可能造成的危害。为了完成某项特定的任务,有时需要综合两个程序的功能,例如一个程序输出文本,而另一个程序对文本进行排序。为此,操作系统还提供进程间的通信机制来帮助完成这样的任务。Linux系统中常见的进程间通信机制有信号、管道、共享内存、信号量和套接字等。

内核通过SCI提供了一个应用程序编程接口(API)来创建一个新进程(fork、exec或Portable Operating System Interface [POSⅨ]函数),停止进程(kill、exit),并在它们之间进行通信和同步(signal或者POSⅨ机制)。

2.进程的分类

按照进程的功能和运行的程序分类,进程可划分为两大类。

(1)系统进程:可以执行内存资源分配和进程切换等管理工作;而且,该进程的运行不受用户的干预,即使是root用户也不能干预系统进程的运行。

(2)用户进程:通过执行用户程序、应用程序或内核之外的系统程序而产生的进程,此类进程可以在用户的控制下运行或关闭。

针对用户进程,又可以分为交互进程、批处理进程和守护进程这3种。

①交互进程:由一个Shell终端启动的进程,在执行过程中,需要与用户进行交互操作,可以运行于前台,也可以运行在后台。

②批处理进程:该进程是一个进程集合,负责按顺序启动其他的进程。

③守护进程:守护进程是一直运行的一种进程,经常在Linux系统启动时启动,在系统关闭时终止。它们独立于控制终端,并且周期性地执行某种任务或等待处理某些发生的事件。

3.进程的状态

进程启动之后,并不是马上开始运行,通常有以下5种状态。

(1)可运行状态:正在运行或者正准备运行。

(2)可中断的等待状态:处于阻塞状态,如果达到某种条件,就会变为运行的状态。同时该状态的进程也会由于接收到信号而被提前唤醒,从而进入到运行的状态。

(3)不中断的等待状态:与“可中断的等待状态”含义类似,不同的地方是处于这个状态的进程对信号不做任何的回应。

(4)僵死状态:又称僵死进程,每个进程在结束后都会处于僵死状态,等待父进程调用进而释放资源,处于该状态的进程已经结束,但是它的父进程还没有释放其系统资源。

(5)暂停状态:表明此时的进程暂时停止,来接收某种特殊处理。

3.2.3 文件系统

Linux操作系统对各种文件系统的支持是通过名为VFS(Virtual File System)的组件实现的,也就是虚拟文件系统。虚拟文件系统隐藏了各种硬件的具体细节,把文件系统操作和不同文件系统的具体实现细节分离了开来,为所有的设备提供了统一的接口,虚拟文件系统提供了数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文件系统是指Linux所支持的文件系统,如ext 3、fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。

虚拟文件系统是Linux内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象,即VFS在用户和文件系统之间提供了一个交换层。

注意:Linux操作系统启动时,第一个必须挂载的是根文件系统;若系统不能从指定设备上挂载根文件系统,则系统会出错而退出启动。之后可以自动或手动挂载其他的文件系统。因此,一个系统中可以同时存在不同的文件系统。

本小节先简单介绍一下文件系统,在3.3小节中我们会进行详细讲解。

3.2.4 设备驱动程序

设备驱动程序是Linux内核的主要部分。设备驱动程序就是应用程序与实际硬件之间的一个软件层,相同的硬件,加载不同的驱动程序就可能提供不同的功能。和操作系统的其他部分类似,设备驱动程序运行在高特权级的处理器环境中,从而可以直接对硬件进行操作,但正因为如此,任何一个设备驱动程序的错误都可能导致操作系统的崩溃。设备驱动程序实际控制操作系统和硬件设备之间的交互。

设备驱动程序提供一组操作系统可理解的抽象接口来完成和操作系统之间的交互,而与硬件相关的具体操作细节由设备驱动程序完成。一般而言,设备驱动程序和设备的控制芯片有关,例如,如果计算机硬盘是SCSI硬盘,则需要使用SCSI驱动程序,而不是IDE驱动程序。

设备的分类如下:

1.字符设备

字符设备是能够像文件一样被访问的设备,由字符设备驱动程序来实现这种特性。字符设备驱动程序通常至少要实现open、close、read、write系统调用。字符设备可以通过文件系统节点来访问,这些设备文件和普通文件之间的唯一差别就在于对普通文件的访问可以前后移动访问位置,而大多数字符设备是一个只能顺序访问的数据通道。一个字符设备是一种字节流设备,对设备的存取只能按顺序、字节的存取进行访问,不能随机访问,字符设备没有请求缓冲区,所有的访问请求都是按顺序执行的。

2.块设备

块设备也是通过设备节点来访问。块设备上能够容纳文件系统。在大多数UNIX系统中,进行I/O操作时块设备每次只能传输一个或多个完整的块,而每块包含513字节。Linux系统内核可以让应用程序向字符设备一样读写块设备,允许一次传递任意多字节的数据。因而,块设备和字符设备的区别仅仅在于内核内部管理数据的方式不同,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。存储设备一般属于块设备,块设备有请求缓冲区,并且支持随机访问而不必按照顺序去存取数据。Linux系统内核下的磁盘设备都是块设备,尽管在Linux系统内核下有块设备节点,但应用程序一般是通过文件系统及其高速缓存来访问块设备的,而不是直接通过设备节点来读写块设备上的数据。

3.网络设备

网络设备不同于字符设备和块设备,它是面向报文的而不是面向流的,它不支持随机访问,也没有请求缓冲区。由于不是面向流的设备,因此将网络接口映射到文件系统中的节点比较困难。内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包传输相关的函数而不是read、write。网络接口没有像字符设备和块设备一样的设备号,只有一个唯一的名字,如eth0、eth1等,而这个名字也不需要与设备文件节点对应。

注意:字符设备与块设备的区别:①字符设备是面向流的,最小访问单位是字节;而块设备是面向块的,最小访问单位是513字节。②字符设备只能顺序按字节访问,而块设备可随机访问。③块设备上可容纳文件系统,访问形式上,字符设备通过设备节点访问,而块设备虽然也可通过设备节点访问,但一般是通过文件系统来访问数据的。

3.2.5 网络接口

网络接口可分为网络协议和网络驱动程序。网络协议负责实现每一种可能的网络传输协议。众所周知,TCP/IP协议是Internet的标准协议,同时也是事实上的工业标准。

1.网络接口的命名

网络接口的命名没有较明确的规范,但网络接口名字的定义一般都是要有意义的。例如:

lo:local的缩写,一般指本地接口。

eth0:ethernet的缩写,一般用于以太网接口。

wifi0:wifi是无线局域网,一般指无线网络接口。

2.网络接口的工作

网络接口是用来发送和接收数据包的基本设备。系统中的所有网络接口组成一个链状结构,应用层的程序使用网络接口时按名称调用。每个网络接口在Linux系统中对应于一个struct net_device结构体,包含name、mac、mask等信息。一个硬件网卡对应一个网络接口,其工作完全由相应的驱动程序控制。

3.虚拟网络接口

虚拟网络接口的应用范围已经非常广泛。“lo”(本地接口)是最常见的接口之一,基本上每个Linux系统都有这个接口。虚拟网络接口并不真实地从外界接收和发送数据包,而是在系统内部接收和发送数据包,因此虚拟网络接口不需要驱动程序。

注意:虚拟网络接口和真实存在的网络接口在使用上是一致的。

4.网络接口的创建

硬件网卡的网络接口由驱动程序创建。而虚拟的网络接口由系统创建或通过应用层程序创建。驱动中创建网络接口的函数有以下两种:

    register_netdev(struct net_device *)
    或者
    register_netdevice(struct net_device *)。

这两个函数的区别是:register_netdev(struct net_device *)会自动生成以“eth”作为打头名称的接口,而register_netdevice(struct net_device *)则需要提前指定接口的名称。

注意:register_netdev(struct net_device *)也是通过调用register_netdevice(struct net_device *)来实现的。