树莓派开始,玩转Linux18:完整架构
Linux系统可以分为内核和应用程序两个主要部分,但如果细分,内核和应用程序之间,还可以有更精细的模块划分。完整的Linux系统架构,如图所示,下面分别来看Linux架构中的不同部分。
1.内核模式与系统调用:
计算机启动之后,Linux的内核程序启动成为一个单一的内核进程。这个单一进程将执行内核的相关功能。内核进程有权调用所有的计算机资源。当应用程序运行时,内核会分配给该应用程序一定的计算机资源。应用程序与硬件之间的互动,也必须经由内核进行。因此,即使是一个应用程序,它的运作也离不开内核。我们把内核程序的活动称为内核模式(Kernel Mode),而把应用程序的活动称为用户模式(UserMode)。
应用程序可以通过特定的接口来调用内核功能。用户单次的内核调用,可以称为一次系统调用(System Call)。接口中的系统调用有大约两百种,每种系统调用都有特定的名称和调用方式。在Shell中输入下面的命令就可以查看Linux下所有的系统调用。
在Linux最常见的开发语言C中,系统调用都制作成了有特定函数名
的函数。你可以通过:
来查看系统调用read这一系统调用的说明。命令中的2,对应了系统调用类相关的查询。
命令man定义的几个查询类可以通过下面的命令查看:
常见的系统调用如下:
· read,文件读取。
· write,文件写入。
· fork,复制当前进程。
· wait,等待某个进程完成。
· chdir,改变工作目录。
每次系统调用,用户程序就触发了内核的特定动作。以bash中的内置函数cd为例。
bash实际上执行的就是进行chdir这个系统调用。系统调用发生后,作为用户进程的bash暂停,操作系统转入内核模式。当内核完成chdir的系统调用后,即更改了进程的工作目录,bash进程将恢复执
行,程序又重回用户模式。
对于用户程序来说,系统调用是内核的最小功能单位。用户程序不可能调用超越系统调用的内核动作。一个系统调用就像是汉字的一个笔画。任何一个汉字都要由基本的笔画构成,没有人可以臆造笔画。通过系统调用这个接口,Linux实现了内核封装,隐藏了底层的复杂性,也提高了上层应用的可移植性。
2.库函数:
系统调用提供的功能非常基础,使用起来很麻烦。一个给变量分配内存空间的简单操作,就需要动用多个系统调用。为了方便,程序员还可以使用上层的库函数(Library Function),来实现特定的"组合拳"。
函数是面向过程语言中复用代码功能的一种方式。所谓的"库"是一个文件,它包含了多个常用函数。在C语言中,我们可以跨文件地调用库中的函数。C语言本身就规定了C标准库(C Standard Library)。之前使用的printf函数,就属于C标准库:
如果只是使用系统调用来实现上面的程序,那么我们编程的工作量
就会增加:
在这个程序中,我们需要手动为文本分配内存空间,并计算文本的长度。而在之前的程序中,这些工作都由printf代劳。此外,printf还可以改变文本输出的格式,并且优化输入和输出的效率,这些都是write系统调用所不具备的。
我们可以用man来查阅库函数的帮助文档。库函数的查询类是3:
除了C标准库,Linux系统中还有很多其他的库,比如POSIX标准库。UNIX操作系统都会安装POSIX标准库。函数malloc就是POSIX标准库中的库函数,这个函数常用于内存分配。目录/lib和/lib64下存放着Linux系统自带的库。用户也可以在目录/usr/lib下安装额外的库。这些库函数把程序员从细节中解救出来,极大提高了编程效率。
3.shell:
系统调用和库函数是为程序员准备的,普通用户使用的都是编译好的应用程序。在所有应用程序中,Shell的地位相当特殊。在历史上,Shell曾经是Linux用户使用计算机的唯一界面。用户必须在Shell中用命令的方式来运行程序。当然,随着图形化桌面的发展,让用户有了一个新的运行程序的界面。但图形化桌面并不能完全取代Shell的地位。很多程序只能通过Shell的方式启动,比如用于下载的wget。
Shell把Linux系统的很多功能开放给用户。对于有编程能力的高级用户来说,他们当然可以通过系统调用、库函数和C语言编程来完整地发挥Linux系统的能力。在另一个极端,大多数应用程序有其专攻的方向,比如文字编辑、收发邮件、显示网页等。一个应用程序只发挥了操作系统很小的一部分能力。Shell介于两者之间,把相对底层的功能用简单而统一的接口呈现给用户,比如用简单的文本符号"|"来使用管道,又如用内置命令cd来改变Shell的工作目录等。对于编程经验有限的普通用户来说,Shell开放出来的系统功能是福音。由于Shell很简单,又容易和其他应用程序互动,它的开放接口也深受资深程序员的喜爱。
我们也已经看到Shell的可编程性。借着这种可编程性,Shell可以充当不同命令之间的"胶水",把功能专一的应用程序组合成功能多样的脚本。此外,脚本还可以预设一连串的操作。用户可以把耗时的手工操作编写成Shell脚本,让Linux系统按照脚本的指挥自动运行。很多Linux计算机就是凭着Shell脚本,在无人工干预的情况下长期工作。
4.用户程序:
整个架构的最上层就是应用程序。我们已经知道,应用程序是二进制的可执行文件。在/bin和/usr/bin中,我们可以看到不少这样的可执行文件。可执行文件还可以出现在文件系统的其他位置。按照惯例,应用程序所在的目录都被命名为bin。这里的bin是指binary,即二进制。
Linux中大多数可执行文件都是由C语言编写的。当然,你还可以用其他的语言来编写程序,如C++和Fortran。C语言写成的程序是可读的文本,必须经过编译才能生成可执行文件。我们可以用gcc命令把C程序直接编译成可执行文件。但在幕后,gcc实际上要做好几件事。它必须对源代码进行一定的文本处理,把人类可读的文本翻译成机器可读的二进制序列,最后还要找到程序依赖的库文件。
编译成功后,就可以用Shell运行应用程序。两种运行程序的方式如下。
· 直接输入程序名,如ls、man、wget。
· 在应用程序所在的目录中用"./可执行文件名"的方式,比如./a.out。
这两种方式的区别在于,前一类的命令包含在Shell的默认路径中。
输入这些命令时,Shell会搜索默认路径,直到找到同名的程序并运行。
默认路径在Shell中保存为一个变量,你可以打印出该默认路径:
可以看到,/bin包含在PATH变量中。由于ls程序位于/bin中,因此我们可以不加路径地运行该程序。
如果应用程序所在位置在默认路径之外,那么我们就必须切换工作目录,或者使用完整路径,如:
Linux系统提供了一个多层次的互动平台。从应用程序到Shell,再到库函数和系统调用,用户可以一层层地接近底层。越往底层,用户受到的限制越少,能发挥的功能越丰富。
当然,学习难度也越来越大。对于爱好挑战的计算机爱好者来说,这样的"学习升级"过程也充满了趣味。