Linux内核简介

61 阅读7分钟

1.Linux内核简介

1.1内核和操作系统

正式讲解Linux内核之前,必须先分清操作系统和内核的概念:

  • 操作系统是指在整个系统中负责完成最基本功能和系统管理的那些部分,它包括内核、设备驱动程序、启动引导程序、命令行shell以及用户界面。
  • 而内核指的是操作系统的核心,由“负责响应中断的中断服务程序”、“负责管理多个进程从而分享处理器时间的调度程序”、“负责管理进程地址空间的内存管理程序”、“网络、进程间统信等系统服务程序”共同组成。

1.2内核和应用程序

内核独立于普通应用程序,运行于受保护的内存空间并且拥有访问硬件设备的所有权限。这种内核态的、受保护的内存空间就是内核空间。相对的,普通应用程序运行于用户空间,只能看到允许它们使用的部分系统资源,且不能使用某些特定的系统功能。 

 当内核运行时,就是CPU以内核态进入内核空间执行内核代码,相反,普通应用程序运行时就是CPU以用户态进入用户空间执行应用程序代码。尽管普通应用程序能够使用的系统资源受限,但是普通应用程序可以通过系统调用向内核请求服务。应用程序通常调用库函数,例如C库函数,再由C库函数调用系统调用,此时CPU就会从用户态切换到内核态,并加载用户进程的上下文,在内核空间执行系统调用代码。在这种情况下,内核被称为运行于进程上下文中。(此处补充两个概念,内核态和用户态:本质是用于描述CPU的执行特权级别,当CPU处于用户态时,只能访问用户空间,当CPU处于内核态时,可以访问内核空间和用户空间。) 

 举个例子,当应用程序执行printf()函数时,经过数据处理后,就是通过write()系统调用把处理后的最终数据写在终端上。不过也有一些库函数和系统调用是一一对应的关系,比如库函数open()仅仅只是调用open()系统调用,其他什么也不做。

1.3内核和硬件

内核需要管理系统的硬件设备,主要通过中断机制来完成。当硬件设备想和操作系统通信时,首先要发出一个异步的中断信号去打断内核正在执行的工作,中断对应着一个中断号,内核通过中断号查找相应的中断服务程序,并启动这个程序响应和处理中断。 

 举个例子,当敲击键盘时,键盘控制器给CPU发送一个中断信号,告诉CPU键盘缓冲区有数据到来,CPU会暂停当前正在执行的进程,保存现场,从用户态切换到内核态,让内核通过查找中断号调用相应的中断服务程序,再由中断服务程序处理键盘数据然后通知键盘控制器可以继续输入数据。

 中断服务程序不在进程上下文中执行,而是在一个与所有进程都无关的、专门的中断上下文中运行。之所以存在这样一个执行环境,就是为了保证中断服务程序能够在第一时间响应和处理中断请求,然后快速的退出。

1.4总结

内核永远都运行在内核空间,真正在用户态和内核态的之间切换的是CPU,CPU的三种情况:

  •  CPU运行于内核空间,处于内核态,加载用户进程内核态的上下文(如进程专属内核栈、task_struct指针),执行系统调用,异常处理等内核代码
  • CPU运行于内核空间,处于内核态,加载中断上下文(共享中断栈、硬件状态),执行中断服务程序代码 
  • CPU运行于用户空间,处于用户态,加载用户进程的用户态上下文(用户栈、程序计数器),执行用户进程代码

2.单内核和微内核设计区别

操作系统内核主要可以分为单内核和微内核两种设计。

单内核是较为简单的设计,就是将内核当成一个单独的程序来实现,将内核的所有模块(进程调度、内存管理、文件系统、网络协议栈、设备驱动等)都集成在一起并运行在内核空间,所有的模块共享内核地址空间,模块之间可以直接调用其他模块的函数实现交互。

微内核采用“极简核心+用户态服务”的架构,将操作系统最基础、最核心的功能保留在内核空间的微内核中,如进程调度、内存管理、IPC进程间通信等,其余非核心功能都以独立的服务进程形式运行在用户空间,服务进程之间以及服务进程和微内核之间通过IPC机制通信。这样做的好处是避免一个模块出现bug导致整个内核都崩溃,但是这样做设计内核空间到用户空间的上下文切换,并且IPC机制的开销比函数调用多。

但是为了减少开销,有的微内核系统不会让大部分服务进程运行在用户空间,而是将它们运行在内核空间,以此来消除频繁的上下文切换,例如WindowsNT和MacOSX都是这样。不过这显然违背了微内核的设计初衷。

3.Linux的内核设计

Linux是单内核,即Liunx内核作为一个整体运行在内核空间。但是Linux内核汲取了微内核的设计精华:采用模块化设计、抢占式内核、支持内核线程等。

3.1模块化设计

Linux内核将内核功能拆分为“核心内核”和“可加载模块”,核心内核仅包含最基础的功能,例如进程调度、内存管理、中断处理等,其余功能均以模块形式存在,能够动态加载或卸载,例如设备驱动、文件系统(ext4.ko)、网络协议、加密算法等。

3.2抢占式内核

内核空间的进程/线程在执行过程中,可被更高优先级的进程/线程打断(抢占),CPU转而去执行优先级更高的任务,从而保障系统的响应性,尤其是桌面、实时场景。

传统的非抢占式内核,内核态进程一旦开始执行,只能主动放弃CPU,如调用schedule函数、睡眠,或者执行完毕,低优先级的内核任务可能长期占用CPU,导致高优先级任务卡顿。

关于Linux内核抢占式特性的实现:
2.6内核后默认开启抢占式特性,内核通过preempt_count计数器控制抢占开关,例如持有自旋锁时preempt_count++禁用抢占,避免死锁,释放锁时preempt_count--启用抢占。抢占触发的时机有两个:①时钟中断:每个进程的时间片耗尽,时钟中断触发调度器,检查是否有更高优先级任务就绪,若有则触发抢占。②系统调用/中断返回:用户态切回内核态时,内核会检查preempt_count和高优先级任务的状态,触发抢占。

3.3内核线程

内核线程是运行在内核空间,由内核直接创建或管理的“轻量级执行单元”,没有独立的用户空间,仅共享内核地址空间。核心作用是将内核内部的、耗时的、需要并发执行的后台任务从内核中断、系统调用等同步执行的过程中剥离出来,放到一个独立的、可控的“后台执行单元”中执行。相比于传统单内核采用的“用户态后台进程”,需要运行在用户态,通过系统调用操作内核资源。频繁在用户态和内核态之间切换,性能开销大。或者使用的“内核裸函数循环”,开机后持续运行处理各种后台任务,这样会导致如果有一个后台任务死循环,那么其他所有的后台任务都无法得到处理。