什么是Linux内核
Linux系统的基础包括内核、C库、编译器、工具集和系统的基本工具,比如登陆程序和shell。当我们说到Linux这个词时,一般指的是Linux内核。对于Linux系统来说,操作系统是指在整个系统中负责完成最基本功能和系统管理的那些部分,包括内核、设备驱动程序、启动引导程序、命令行shell或者其他用户界面、基本的文件管理工具和系统工具。
用户界面是操作系统的外在表现,而内核是操作系统的内在核心。操作系统的其他部分必须依靠内核所提供的服务,而内核实质也是一个软件。内核也叫做超级管理者或者操作系统核心。内核一般由负责响应中断的中断服务程序,负责管理多个进程从而分享处理器时间的调度程序,负责管理进程地址空间的内存管理程序和网络、进程间通信等系统服务程序共同组成。
内核态与用户态
对于提供保护机制的现代操作系统来说,内核独立于普通应用程序,它一般处于系统态,拥有受保护的内存空间和访问硬件设备的所有权限。这种系统态和受保护的内存空间统称为内核空间。
与之对应的,应用程序在用户空间执行,它们只能使用权限允许的部分系统资源,并且无法使用特定系统功能,不能直接访问硬件(应用程序想要访问硬件必须通过内核,而使用内核的接口一般称为系统调用,具体在后面有介绍),以及一些其他限制。
当内核运行的时候,系统以内核态进入内核空间,相反,普通用户程序以用户态进入用户空间。应用程序通过系统调用和内核通信来运行。
应用程序通过调用库函数,比如C库函数,再由库函数通过系统调用来让内核完成各种任务。一般来说,库函数提供的功能无法使用单独的系统调用来替代,特别是在一些复杂的库函数中,调用内核的操作往往只是整个函数功能的一个步骤。比如printf()函数,该函数提供了数据缓存和格式化等操作,但它只是在执行末期通过系统调用write()函数把最终数据写在输出流。当然,也有库函数和系统调用一一对应的情况,比如库函数open()和系统调用open()就是一一对应的,库函数open()就只是调用了系统调用open(),没有其它步骤。另外,也有一些库函数不需要系统调用,比如C库函数strcpy()等。
如果一个应用程序请求执行系统调用,我们之称为内核代其执行,此时,应用程序通过系统调用在内核空间运行,而内核此时一般被称为运行于进程上下文中。这种交互关系叫做,应用程序通过系统调用陷入内核,这是应用程序完成工作的基本方式。
内核同时还要负责管理系统的硬件设备,因此,几乎所有的体系结构都会提供中断机制。当硬件想和系统交互时,它会发出一个异步中断信号去打断内核正在执行的工作。中断都会对应一个中断号,内核通过中断号查找相应的中断服务程序,并调用这个中断服务程序来响应和处理中断。为了保证同步,内核可以停用所有的中断或者某个中断。另外,一般来说中断服务程序都不在进程上下文中执行,它们在一个与所有进程都无关的、专门的中断上下文中运行,这么做是为了保证中断服务程序能够在第一时间响应并处理中断请求,然后快速退出。
上下文是指内核活动的范围,一般来说,可以将处理器在任何时间的活动范围总结为下面三种情况:
- 运行于内核空间,处于进程上下文,表示某个特定的进程执行;
- 运行于内核空间,处于中断上下文,与任何进程无关,处理某个特定的中断;
- 运行于用户空间,执行用户进程;
即便是CPU空闲时,也处于这三种情况之一,此时内核运行空进程,处于进程上下文,运行于内核空间。
内核与系统调用
操作系统是一个用来协调、管理和控制计算机硬件和软件资源的系统程序,它位于硬件和应用程序之间。操作系统包括
- 操作系统内核:操作系统的核心代码。操作系统内核是一个管理和控制程序,负责管理计算机的所有物理资源,其中包括文件系统、内存管理、设备管理和进程管理。(应用程序→操作系统→硬件)
- 系统调用接口:向外提供了使用内核的方法。应用程序(用户代码)要想使用或操作计算机硬件(硬盘、显卡、网卡等等),必须要经过操作系统。而操作计算机硬件的接口都是操作系统提供的,比如说我们要查找保存在硬盘上的一个文件,我们需要去向操作系统查询该文件的位置,找到这个文件的代码是操作系统内核中的某一块代码。实际上,内核代码是根据功能划分好一块一块的,比如说某一块是操作硬盘的,某一块是操作网卡的等等,每一个模块都有一个接口,当我们要查找磁盘文件的时候,就会直接通过操作硬盘的这个接口去内核中调用查找硬盘文件的代码,这就是系统调用的概念。
综上,操作系统具有承上启下的作用,向上服务于应用程序,向下可操作硬件,而操作系统是由内核与系统调用接口组成的,内核负责提供操作硬件的功能,系统调用负责与应用软件交互(被应用软件调用)。
操作系统用户界面有图形界面和命令行界面两种,Linux操作系统的发行版本有CentOS(服务器主流)、Ubuntu(主要用于开发)、Red Hat(服务器)等。
C库函数与系统调用
当我们在C语言程序中调用一个库函数的时候,比如调用printf()函数,实际上它是通过文件指针来指向要打印的位置的。并且,printf()函数会调用Linux的系统函数write()函数(它是一个系统接口,也可以人工调用),write()函数再继续调用sys_write()函数(这个函数只能是操作系统去调用),sys_write()继续调用设备驱动,具体调用哪个驱动要看输出的位置,如果是printf()打印到显示器上,那么就调用显示器驱动并打印在屏幕上,如果是写到网络上,就会调用网卡驱动。我们所作的只有在C程序中调用printf()等库函数,其余操作都是操作系统帮我们做的。请看下面这张图。
printf()函数在打印的时候通过一个文件指针来实现打印到某个文件的某个位置。在文件在文件指针中,包含了一个文件描述符,这个文件描述符用于指定目标文件,默认情况下就是STDOUT_FILENO也就是标准输出1号描述符;f_pos指定了读写的位置,比如我们打印的时候他会不停的在上一次打印的末尾位置打印后面的内容,就是通过这个位置去实现的;在最后还有一个缓冲区buffer,那么为什么要有buffer缓冲区呢,其实这是为了提高读写的效率,把读写的内容先放到缓冲区,这样就可以实现一次读写更多的内容。并且,这个缓冲区需要刷新才能得到输入输出。
在Linux下启动一个进程,就会默认打开三个文件描述符:0标准输入、1标准输出、2标准错误。它们分别对应C语言中的stdin、stdout、stderr。当我们每次打开一个文件,就会分配给这个文件一个当前空闲的最小文件描述符,如果此时标准输入0、标准输出1、标准错误2空闲,那么也会把这个文件描述符分配给新打开的文件但是这三个文件描述符0、1、2与stdin、stdout、stderr的对象关系不会变,并且在后续的操作中会把0、1、2指向的新文件当作标准输入输出和标准错误去处理,并将输入输出或错误信息打印到这个文件。
在系统API中,主要包含了这些函数:与文件IO相关的函数接口(比如open(),close(),write(),read()等);与文件属性相关的函数;与目录操作相关的函数;与目录遍历相关的函数;还有dup()、dup2()、fcntl()函数等。这些函数都属于系统调用,可以通过命令 man 2 functionname 查看。这里有个小技巧要注意,如果你直接输入 man functionname 没有显示出函数原型等信息,这就说明该函数也有对应的同名命令,这时候可以通过加章节来查看函数说明,比如 man 2 functionname 表示查看第二章,也就是系统调用API。
单内核与微内核
单内核是指把内核从整体上作为一个单独的大过程来实现,并同时运行在一个单独的地址空间。内核通常以单个静态的二进制文件形式存放在磁盘,所有内核服务都在这样一个大内核空间运行。因为都运行在内核态,且处于同一地址空间,所以内核间的通信变得不再重要,内核可以直接调用函数,与用户空间区别不大。
微内核的功能被划分为独立的过程,每个过程叫做一个服务器。只有强烈请求特权服务的服务器才运行在特权模式下,其他服务器都运行在用户空间,所有服务器都保持独立并运行在各自的地址空间。因此,微内核无法像单模块内核那样直接调用函数,而是通过消息传递处理微内核通信,各种服务器之间通过进程间通信(IPC)机制交互。这样,各个服务器相互独立,一个服务器的失效不会影响其他服务器。
模块化的系统允许一个服务器为了另一个服务器而换出。但是IPC机制的开销要大于函数调用,并且会涉及内核空间到用户空间的上下文切换,因此,消息传递需要一定的周期,而单内核中的函数调用就没有这些开销。微内核系统让大部分或者全部服务器位于内核,这样就可以直接调用函数,省去了频繁的上下文切换。Unix系统大多为单模块,Windows NT内核和Mach内核都是微内核。
Linux内核是一个单内核,它运行在单独的内核地址空间,但是它汲取了微内核的精华,相对于Unix内核,Linux内核有很多新的特性:
- Linux支持动态加载内核模块。虽然Linux内核也是单内核,但是在需要的时候可以动态的卸载和加载部分内核代码;
- Linux支持对称多处理(SMP)机制;
- Linux内核可以抢占,允许在内核运行的任务优先执行;
- Linux内核不区分线程和其他一般的进程,对内核来说,所有进程都一样,只不过有的共享资源;
- Linux提供具有设备类的面向对象的设备模型、热插拔事件,以及用户空间的设备文件系统(sysfs);
- Linux忽略了一些拙劣的Unix特性,并且很好的体现了自由的特性;
内核版本号与开发者社区
Linux内核版本号总共包含三个数字,用 . 隔开,第一个数字为主版本号,第二个数字为从版本号,第三个数字为修订版本号。从版本号可以分辨稳定版本和开发版本,偶数表示稳定版,奇数表示开发版。比如2.6.0表示稳定版。
内核社区邮件列表 Linux kernel mailing list ,可以在http://vger.kernel.org订阅邮件。