Linux内核简介

1. 前言

本文主要简要介绍Linux内核的功能,以及Linux内核分别由哪些部分组成,并对各个部分完成的主要功能做简要说明。

本文主要是对三本Linux传世之作(LKD, PLKA, ULK)绪论部分的综述。

2. Unix的发展历程

若要了解Linux,首先要先了解Unix的发展历史

1969年 Unix诞生,由Dennis Ritchie和Ken Thompson共同完成,运行在PDP-7上

1971年 被移植到PDP-11上

1973年 整个UNIX系统被用C语言重写,第一个被广泛使用的版本是UNIX V6,之后产生各种变体

1977年 贝尔实验室整合各种变体,发布Unix System 3(期间加州大学伯克利推出1BSD)

1978年 加州大学伯克利分校推出了2BSD,加入csh,vi

1979年 加州大学伯克利分校推出了3BSD,支持虚拟内存

1983年 AT&T推出Unix System 5

1994年 加州大学伯克利分校推出了4.4BSD

注:linus在1991年推出了Linux的初始版本

 

3. 传统Unix和Linux的不同

 

特性 Linux内核 传统Unix
是否支持模块加载 支持 不支持
是否支持SMP  支持   不支持
是否支持抢占 支持 不支持
是否区分进程和线程 不区分 区分
是否支持某些拙劣特性,如STREAMS 不支持 支持
是否具有面向对象的特性
是否体现自由

表 Linux内核与传统Unix的区别

 

4. 操作系统和内核

. 操作系统是在整个系统中完成最基本功能和系统管理的部分,包括内核、设备驱动程序、启动引导程序、命令行shell或其它种类的用户界面、基本的文件管理工具和系统工     具

. 内核是操作系统的核心,通常内核包含进程管理、中断相应、内存管理、网络与进程间通信等系统服务组成

5.内核的功能

内核是硬件与软件的一个中间层,其作用是将应用程序的请求传递给硬件。

  • 从应用程序的角度

应用程序发出的任何请求,都将由内核进行抽象处理,屏蔽了操作的细节,因此对应用来讲内核是对硬件的增强

  • 从多进程并发执行的角度

当多个进程在执行时,内核需要对资源如内存等进行分配与管理,因此内核是一个资源管理者

  • 从系统调用的角度

应用程序可以通过系统调用来请求内核完成某些功能,内核好像是应用程序调用的库

6.内核的实现策略

内核有两种实现策略:

微内核:内核只实现最基本的功能,非基本功能如文件系统、内存管理等委托给一些独立进程处理

宏内核:内核除了实现最基本的功能,其它功能包括文件系统、内存管理、驱动程序也都打包在同一个文件中

Linux kernel属于宏内核,但是可以通过模块插入到内核代码,也可以从内核代码中移除

7.内核的组成部分

7.1 进程管理

  • 进程实现

. 每个进程有一个进程描述符表示,这个描述符包含有关进程当前状态的信息

  • 可重入内核

. Linux内核是可重入的,若干个进程可以同时在内核态下执行。表现在多个内核控制路径可以交替执行

注:内核控制路径是内核处理系统调用、异常和中断所执行的指令序列

  • 进程地址空间

. 各个进程的地址空间是彼此独立的,表现在用户态时进程有自己私有的地址空间,包括私有栈、数据区和代码区;内核态时进程访问内核的数据区和代码区,但使用另外的私有栈;

. 进程之间也可以共享地址空间,如代码的共享、部分地址空间的共享

. 线程共享主线程的地址空间(资源和数据),多线程并发访问相同的内存资源时需要做互斥.Linux采用clone方法创建线程

  • 进程切换与调度

. 进程切换是撤销一个进程的执行,保存当前进程的状态,换一个进程执行,待重新激活进程时再将保存的进程状态恢复;

. 进程调度决定现存进程如何共享CPU时间,即每个进程运行多长时间

  • 进程的分层结构

. Linux进程采用层次结构,每个进程都依赖于一个父进程,内核会启动init进程作为第一个进程

  • fork、__exit与exec

. fork是创建进程的唯一方式,fork后父子进程有不同的PID,采用写时复制技术;

. exec是将新程序加载当前进程的内存空间并运行,并未创建新进程

. exit终止一个进程,它会释放进程的资源,并向父进程发送SIGCHILD信号

注:写时复制是一种可以推迟甚至避免拷贝数据的技术。内核在fork时并不复制整个进程的地址空间,而是让父子进程在页根本不会被写入的情况下共享同一个地址空间(例如,fork()后立即执行exec(),地址空间就无需被复制了)。只有在需要写入的时候才会复制地址空间,从而使各个进程拥有各自的地址空间

  • 进程命名空间

. 命名空间将全局资源分组,不同的资源组属于不同的命名空间,每个命名空间包含不同的PID集合,这样使得不同的进程看到不同的资源视   图。挂载在不同命名空间的文件系统对其它命名空间不可见。命名空间的典型应用是虚拟机。

  • 信号与进程通信

. Linux信号提供了一种将系统事件报告给进程的一种机制,包括两种系统事件:异步通告、同步错误或异常

. 进程对接收到的信号可以做出两种反应:忽略该信号、异步的执行一个指定的过程(信号处理程序)

. 如果进程不指定何种处理方式,内核会根据信号的编号执行一个默认操作,五种可能的默认操作:

  终止进程、core dump并终止进程、忽略信号、挂起进程、如果进程曾被暂停则恢复其执行

. 用户空间其它种类的进程通信机制包括:信号量、消息队列、共享内存

注:SIGKILL和SIGSTOP不能由进程处理也不能由进程忽略

  • 僵死进程

. wait4()系统调用允许进程等待,直到其中一个子进程结束,返回已结束的子进程的PID

. waitpid()允许进程等待一个特殊的子进程

. 如果父进程在子进程之前就已经结束,那么在结束前它会修改所有子进程的进程描述符指针将其所有的子进程改为init的孩子,由init继续监控

  • 进程组和登录会话

. 进程组:多个进程可以组成一个组,每个进程的描述符都包含进程组的字段,每个进程组有一个领头进程,新创建进程最初插入到其父进程的进程组中

. 登录会话:终端会话进程的所有后代进程

 

7.2 内存管理

  • 虚拟内存

.  Linux将地址空间分为虚拟地址空间和物理地址空间,地址空间的最大长度取决于CPU字长;

.  虚拟内存处在用户的内存请求与MMU之间,通过MMU和页表协同定位虚拟内存对应的物理内存位置  

.  虚拟地址空间和物理地址空间都划分成大小相同的页(物理内存称为页帧),通过页表可以将虚拟地址转换为物理地址;

.  两个进程的不同虚拟地址页可以映射到同一物理页帧来实现内存共享;

.  由于虚拟页没有全部使用,因此并非虚拟地址空间中所有的页都映射到某个页帧;

注1:CPU字长是CPU可以处理的二进制的位数,也就是数据总线的宽度

注2:用户层仅指代应用程序,用户空间不仅指代应用程序还包括应用程序运行的虚拟空间的一部分,内核空间指代系统态及受保护的内存空间

  • 用户态和内核态

.  Linux将虚拟地址空间划分为虚拟内核空间和虚拟用户空间,各个进程的用户空间彼此隔离,而所有进程对应同样一个内核空间;

.  进程运行在用户空间称为用户态,进程运行在内核空间称为内核态,用户进程无法访问内核空间的数据;

.  通过系统调用使得用户态切换到内核态,内核态代表用户进程执行所需要的操作(此时内核可以访问用户虚拟地址空间),执行完毕后返回用户态;

.   硬件中断也可以激活内核态执行,由于中断与当前用户进程无关,内核在中断上下文无权访问当前用户虚拟地址空间;

注:TASK_SIZE是特定于计算机体系结构的常数,把地址空间划分为两部分,内核空间(>TASK_SIZE)和用户空间(0~TASK_SIZE)  

  • 进程虚拟地址空间

内核分配给进程的虚拟地址空间包括如下几个部分:

程序的可执行代码

程序的初始化数据

程序未初始化的数据

初始程序栈

共享库

  • 页表

 . 页表是用来将虚拟地址空间映射到物理地址空间的数据结构;

 . 为减少页表大小一般采用多级分页,对三级分页虚拟地址一般划分为PGD(全局页目录),PMD(页中间目录),PTE(页表项),offset(页内偏移),归根结底,每个虚      .  拟地址都指向物理内存的一个字节;内核与体系结构无关部分使用四级页表,如果只支持两级或三级页表的CPU,内核体系结构相关代码必须使用空页表;

 . MMU(内存管理单元)实现将虚拟地址转换为物理地址;TLB存储了地址转换中出现最频繁的那些虚拟地址,页表内容变化时需要无效对应的TLB项;

  • RAM的使用

. RAM被分成两部分来使用: 存储内核镜像(内核代码和内核静态数据结构),其它部分由虚拟内存来处理;

. 虚拟内存满足3种需求:

满足内核对缓冲区、描述符及其它动态内核数据结构的请求

满足进程对一般内存区的请求及文件内存映射的请求

借助高速缓存从磁盘或其他缓冲设备获取较好的性能

注1:  内存映射是将任意来源的数据(如内存、磁盘等)映射到进程的虚拟地址空间中,这样对虚拟地址空间的操作会反映到源数据;

注2: 高速缓存是作为磁盘的一个快照,当需要访问磁盘中的数据时可以直接从高速缓存中读取。而 sync()系统调用会强制将高速缓存中的数据写入磁盘来同步数据

  • 内存分配

. 伙伴系统:宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块 200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个 256K的内存块分配给内存,这样就是一个分配的过程。

. slab缓存:基于伙伴系统,定义了额外的内存管理层,将伙伴系统提供的页划分为更小的部分。slab实现包含两方面功能:通用的slab缓存分配小内存块;给频繁使用的对象分配一组专用缓存。

  • 页面交互与页面回收

. 页面交换:不使用的页写入硬盘,标记为换出,需要时通过缺页异常从硬盘读入内存

. 页面回收:也称为数据回写,将内存映射被修改的内容与块设备同步

 

7.3 同步和临界区

  • 临界区

. 如果内核控制路径对内核某个数据结构操作时挂起,其他内核控制路径不能再对该数据结构进行操作,除非该数据结构被重新设置成一致性状态

. 进入临界区的进程必须完成后,另一个进程才能够进入

  • 同步

. 非抢占式内核:当进程在内核执行时,不能被任意挂起,也不能被其它高优先级进程抢占(对于抢占式内核要确保进入临界区前禁止抢占,退出临界区后开启抢占)

. 禁止中断:在进程进入一个临界区前禁止所有硬件中断,离开临界区在重新开启硬件中断。

. 信号量:进程在要访问临界资源前先要检查信号量的计数值,如果计数值大于0,才可以使用并将技术支持减1,离开临界区则要将技术支持加1

. 自旋锁:一个进程在访问临界区时,另一个进程如果想访问,只能自旋等待,不像信号量一样会挂起进程

注:对于SMP,禁止中断还无法保证同步,还需要采用其它机制,如信号量或自旋锁。信号量和自旋锁无论对于单核或多核的同步都是有效的

7.3 计时

. 内核需要测量时间点及不同时间点之间的时差

. 对于计时粒度较粗的情况下内核可以使用jiffies来完成此功能,通过底层体系结构的定时器中断,对jiffies执行递增操作,递增的频率取决于HZ

. 如果需要更加细小的计时粒度可以通过高分辨率的定时器来实现,一般以24MHZ晶振频率来实现

注:HZ是一秒内发生定时器中断的次数

7.4 系统调用

. 系统调用时用户进程与内核交互的经典方法,用户进程要从用户态切换为内核态,并将系统关键任务交由内核执行

. 传统系统调用按不同类别分组:进程管理、信号、文件、目录和文件系统、保护机制、定时器函数

7.5 中断机制

. 当硬件设备想要和系统通信的时候,它首先发出异步的中断信号去打断处理器的执行,继而打断内核的执行

. 中断通常对应中断号,内核通过这个中断号查找相应的中断服务程序,并调用这个程序去响应和处理中断

. 中断服务程序与所有进程无关,因此不在进程上下文运行,在专门的中断上下文执行

. 中断上下文有利于第一时间响应和处理中断,然后快速退出

注:每个处理器在指定时间点上的活动无非有三类:

运行于用户空间,执行用户进程;

运行于内核空间,处于进程上下文,代表某个进程执行(当内核空闲时运行idle进程,处于进程上下文);

运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断

7.6 设备驱动程序

. 设备驱动程序用于与系统连接的输入/输出装置通信

. 应用程序可以通过/dev/xxx目录完成与设备的通信

. 外设分为块设备和字符设备和网络设备

7.7 网络

. 网卡驱动属特殊的设备驱动程序,网卡不能通过/dev/xxx方式访问, 应用程序通过套接字完成与文件接口及内核与网络之间的通信

7.8 文件系统

图 虚拟文件系统层、文件系统实现和块设备层之间的互操作

. Linux文件系统采用层次式结构,内核提供了VFS将应用与具体的文件系统进行隔离如上图所示

. Linux支持多种文件系统:Ext2, Ext3, VFat等

. inode是为每个文件构造的单独的管理机构,存储在磁盘上,inode包含了文件所有的元数据,以及指向相关数据块的指针

. 目录可以表示为普通文件,其数据包含了指向目录下所有文件的inode的指针

. 文件系统采用目录结构组织存储数据,并将其它元数据(inode)与实际存储数据联系起来

注:如上inode和目录的描述基于Ext文件系统

7.9 模块与热插拔

. 模块用于在运行时动态的向内核添加功能,如设备驱动程序、文件系统、网络协议等

. 模块也可以在运行时卸载, 针对热插拔设备,可实现运行时连接设备无需重启

. 模块必须提供某些代码段在初始化(终止)阶段执行,以便向内核注册和注销模块

. 模块代码与普通内核代码的权利与义务是相同的,可以像编译到内核中的代码一样,访问内核中的数据结构和接口

. 模块功能使得内核在保持自身精简的情况下又可以支持种类繁多的外设功能

. 可以将只提供二进制的模块加载到内核,但不能访问那些声明为调用者也必须采用GPL许可的函数

7.10 缓存

. 从低速的块设备读取的数据会暂时保存到内存中,应用程序下一次访问该数据时,可以直接从内存中读取

. 页缓存:内核是基于页的内存映射来保存块设备的数据到内存,称为页缓存page cache

. 块缓存:在Linux中块缓存已经被页缓存代替

8. 内核版本说明

图 内核版本说明

如上图所示,说明如下:

  • 第一列,版本性质。主分支(mainline),稳定版(stable),长期维护版(longterm) 
  • 第二列,版本号。-rc表示非正式发布版本,[EOL]表示本分支最后一个版本。

第一个数字表示目前内核发布的主版本;第二个数字表示次版本号,偶数表示稳定版,奇数表示开发版;第三个数字是修订版本号,表示错误修补的次数

  • 第三列,版本发布日期

9. 订阅Linux mail list

见 订阅Linux内核邮件列表 一文

10. 获取linux kernel源码

见 git获取内核源码的方法

11. 参考文档

[1]Linux内核设计与实现,LKD

[2]深入Linux内核架构,PLKA.

[3]深入理解Linux内核,ULK

posted @ 2017-04-07 15:13  jasonactions  阅读(971)  评论(0编辑  收藏  举报