了解Linux系统架构、内核、基本原理,才是进入Linux平台下编程学习的前提。不了解这些,调用C/C++库来开发,可能表面上会学用一些库函数接口,实则可能是代码处处缺陷,一旦遇到有点深度的故障,程序开发寸步难行。
1 Linux的分层架构
不管是Linux、Windows,还是其它的操作系统,采用分层结构模型,这几乎是一个通行做法了。不仅是大系统,小的嵌入式系统,往往程序员也会不由自主地采用分层模型,就像上面的图上所示,通常的操作系统会分为这样几层:硬件层、系统层和应用层。系统层内部一般情况下,大概再细分为驱动层、操作系统内核和系统扩展层。
分层结构是为了模块化、分工协作、降低代码耦合度的需要,也是功能优化、易维护、高效率低成本的一个自然选择。
Linux也类似,分硬件层hardware、内核层Linux-kernal和用户层user space。如上图所示,Linux系统的内核采用的是大包大揽的宏内核结构:核中的所有模块,进程管理、任务调度、内存管理、任务调度、设备驱动和文件系统,统统运行在相同的地址空间中,通过系统调用system calls来响应用户层服务。
2用户空间与内核空间
从硬件层往上,是软件层。总体上方面讲,Linux软件体系结构可以分为两块:
(1)用户空间:用户空间中又包含了,用户的应用程序,C库
(2)内核空间:内核空间包括,系统调用,内核,以及与平台架构相关的代码
内核具有很高的权限,可以控制 cpu、内存、硬盘等硬件,而应用程序具有的权限很小。
操作系统相应的,也会把内存分成两个区域,一个是内核区,另一个是用户区。
我们也可以近似地把上面所说的用户空间等同于用户内存区,内核空间等于内核内存区。
用户空间的代码只能访问一个局部的内存空间,而内核空间的代码可以访问所有内存空间。用户应用程序不可直接访问内核空间的任何资源,包括内核内存区在内。一旦试图直接访问,就是越界,会引发Linux系统错误。
3用户态和内核态
为什么要分成用户态和内核态呢?这是因为安全的需要。
CPU一般会设计多种工作模式,ARM实现了7种工作模式,不同模式下CPU可以执行的指令或者访问的寄存器不同。X86实现了4个不同级别的权限:Ring0—Ring3。Ring0下可以执行特权指令,可以访问IO设备,Ring3则有很多的限制。
所以,从CPU的角度出发,为了保护内核的安全,把系统分成了2部分。内核运行在CPU的特权级别下,称之为内核态。用户程序运行在CPU一般权限模式下,称之为用户态。所以,程序的运行状态,用户态和内核态,主要是针对CPU当前工作模式来说的。
用户态:用户程序运行在用户内存空间,也可以说是用户空间(user space),且不能越界。应用运行在CPU普通权限模式下。
内核态:内核模块运行在内核空间(kernel space),能完全访问所有资源,处于运行在CPU特权模式下。
综合上面所述,空间是指代资源而言的,而状态是指代CPU权限模式而言的。所以,一个普通应用,只能处于用户空间。但指令可以引发内核服务,从而由用户态进入内核态。
4Linux一个应用是怎么工作的?
运行在用户内存空间的一个应用程序,其指令一般运行CPU的普通权限模式下。但必要时,应用要请求内核调用。然后内核开始响应,在内核调用已经开始,尙未返回的期间,就处于内核态。
用户程序的指令如果需要内核响应,就必须就需要通过「系统调用」,才进入内核空间,下面来看看系统调用的过程:
内核程序执行在内核态,用户程序执行在用户态。用户态和内核态是程序执行的两种不同状态,我们可以通过“系统调用”和“硬件中断“来完成用户态到内核态间的转移,转移的同时,程序工作空间,也由用户空间(用户内存区)转为内核空间(内核资源区)。
当应用程序使用系统调用时,会产生一个中断。发生中断后, CPU 会中断当前在执行的用户程序,转而跳转到中断处理程序,也就是开始执行内核程序。内核处理完后,主动触发中断,把 CPU 执行权限交回给用户程序,回到用户态继续工作。
5以进程为视角
什么是程序?完成特定任务的一系列指令集合就是程序。用更俗更简单的话来说,你使用gcc编译成的那个可执行文件,就是程序。
什么是进程?程序在操作系统执行后的过程,就是进程。更简单的说法,你用gcc编译成的可执行文件,真的运行起来了,操作系统给其分配了资源,这就是进程。
程序放在磁盘上,包含的是指令(代码段+数据段),是静态概念。进程运行在内存中,操作系统给其分配了运行时环境,是动态概念。
普通用户只知找到可执行文件并执行起来,为已所用。但程序员则要用进程的视角,来创建、调试、编译代码。
6内核视角
一个进程在Linux系统上运行了,然而普通进程在Linux操作系统上,它的占用的资源极其有限,能力极其低下。可以说是“低三下四”。《Linux系统编程手册》上有段这样的话:
你看看,进程的不能,都是内核的能耐。内核对系统所有的资源,无所不知、无所不能,掌管着进程的生死特权。进程想干点啥事,稍微有所越轨,必须通过系统调用向内核申请。
所以,必须站在内核的视角,才能在liunx上深度玩转编程。那种面向进程的方式,在Linux平台上的深度开发,是不可取的。
7Linux系统编程学什么?
在进行Linux系统编程学习的过程中,我们大多数情况下,如果不涉及硬件细节,撑握200多个System calls(系统调用)API函数的使用方法,来验证和领悟Linux系统的内核各个部分的设计原理,这样就可以了。
这一点要提一下windows,相比LInux而言,windows平台的系统级API高达数千个,我看到一个2009年汇总的WIN API大全,已经2248个了,此后还在不断增加之中。(传言现在已经高达上万了,不过我懒得考证。)这背后的原因就是windows没有像Linux那样抽象而统一的设计思路和模型,各模块、各组件、各层次均为易用而生。
在Ubuntu20上,
/usr/src/linux-headers-5.4.0-48/arch/sh/include/uapi/asm/unistd_64.h中有系统API调用号定义,现在也不过394个,常用的也只会有200多个。如果你局限在某一行业进行LInux应用开发,可能面临常用的也就30到50个左右。
更何况Linux平台是开源的,个人可以学无止境。而在windows,个人几乎无法深度进阶。所以拥抱Linux平台,在Linux平台下训练开发,才是系统级开发的光明大道。
再说一点,微软公司经常放弃自已的架构、工具、及各项技术,除了不敢放弃windows界面之外,我看微软都没有啥它不敢放弃的了。别的不敢说,在国内如此内卷的情况下,做程序,跟着微软混,年纪大了,超过35岁,很有可能日子过的一天不如一天。