本文是“Linux内核分析”系列文章的第一篇,会以内核的核心功能为出发点,描述Linux内核的整体架构,以及架构之下主要的软件子系统。之后,会介绍Linux内核源文件的目录结构,并和各个软件子系统对应。
注:本文和其它的“Linux内核分析”文章都基于如下约定: a) 内核版本为Linux 3.10.29(该版本是一个long term的版本,会被Linux社区持续维护至少2年),可以从下面的链接获取:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.10.29.tar.xz b) 鉴于嵌入式系统大多使用ARM处理器,因此涉及到体系结构部分的内容,都以ARM为分析对象
如下图所示,Linux内核只是Linux操作系统一部分。对下,它管理系统的所有硬件设备;对上,它通过系统调用,向Library Routine(例如C库)或者其它应用程序提供接口。
因此,其核心功能就是:管理硬件设备,供应用程序使用。而现代计算机(无论是PC还是嵌入式系统)的标准组成,就是CPU、Memory(内存和外存)、输入输出设备、网络设备和其它的外围设备。所以为了管理这些设备,Linux内核提出了如下的架构。
内核之结构
内核主要由以下五大组成部分:
从依赖性的角度分析:
【文章福利】小编推荐自己的Linux内核技术交流群:【865977150】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)
进程调度器是Linux内核中最重要的子系统。其目的是控制对计算机CPU的访问。这不仅包括用户进程的访问,还包括其他内核子系统的访问。
由上图可知,进程调度器可分为四大模块:
进程调度器维护一个数据结构,即任务列表,每个活动进程具有一个条目。此数据结构包含足够的信息来暂停和恢复过程,但还包含其他记帐和状态信息。该数据结构可在整个内核层公开使用。
如前所述,进程调度程序将调用内存管理器子系统。因此,进程调度程序子系统依赖于内存管理器子系统。此外,所有其他内核子系统都依赖进程调度程序来挂起和恢复进程,同时等待硬件请求完成。这些依赖关系通过函数调用和对共享任务列表数据结构的访问来表示。所有内核子系统都读取和写入代表当前任务的数据结构,从而导致整个系统中的双向数据流。
除了内核层中的数据和控制流之外,O / S服务层还为用户进程提供了一个接口,用于注册计时器通知。这对应于[Garlan 1994]中描述的隐式执行体系结构样式。这导致控制从调度程序流向用户进程。恢复休眠过程的通常情况在正常情况下不视为控制流程,因为用户进程无法检测到此操作。最后,调度程序与CPU通信以挂起和恢复进程。这导致数据流和控制流。CPU负责中断当前正在执行的进程,并允许内核调度另一个进程。
内存管理器子系统负责控制对硬件内存资源的进程访问。这是通过硬件内存管理系统完成的,该系统提供了进程内存引用与机器物理内存之间的映射。内存管理器子系统在每个进程的基础上维护此映射,以便两个进程可以访问相同的虚拟内存地址并实际使用不同的物理内存位置。此外,内存管理器子系统还支持交换。它将未使用的内存页面移至持久性存储,以使计算机支持的虚拟内存多于物理内存。
模块结构分析
内存管理器主要由以下三个模块组成:
从数据表征的角度分析
内存管理器存储物理地址到虚拟地址的每个进程的映射。该映射作为参考存储在流程调度程序的任务列表数据结构中。除了此映射之外,数据块中的其他详细信息还告诉内存管理器如何获取和存储页面。例如,可执行代码可以将可执行映像用作后备存储,但是必须将动态分配的数据备份到系统页面文件中。最后,内存管理器在此数据结构中存储权限和记帐信息,以确保系统安全。
数据流,控制流和依赖关系
内存管理器控制内存硬件,并在发生页面错误时从硬件接收通知-这意味着内存管理器模块和内存管理器硬件之间存在双向数据和控制流。另外,内存管理器使用文件系统来支持交换和内存映射的I / O。此要求意味着内存管理器需要对文件系统进行过程调用以存储和从持久性存储中获取内存页面。由于无法立即完成文件系统请求,因此内存管理器需要暂停一个过程,直到将内存换回为止。此要求导致内存管理器对过程调度程序进行过程调用。同样,由于每个进程的内存映射都存储在进程调度程序的数据结构中,因此在内存管理器和进程调度程序之间存在双向数据流。用户进程可以在进程地址空间内设置新的内存映射,并可以注册自己以在新映射的区域内通知页面错误。这引入了从内存管理器到系统调用接口模块再到用户进程的控制流。从传统意义上讲,没有来自用户进程的数据流,但是用户进程可以使用系统调用接口模块中的选择系统调用从内存管理器中检索某些信息。
虚拟文件系统旨在提供存储在硬件设备上的数据的一致视图。计算机中几乎所有的硬件设备都是使用通用设备驱动程序接口表示的。虚拟文件系统进一步发展,并允许系统管理员在任何物理设备上安装一组逻辑文件系统中的任何一个。逻辑文件系统促进与其他操作系统标准的兼容性,并允许开发人员使用不同的策略来实现文件系统。虚拟文件系统抽象了物理设备和逻辑文件系统的详细信息,并允许用户进程使用通用接口访问文件,而不必知道文件驻留在哪个物理或逻辑系统上。
除了传统的文件系统目标之外,虚拟文件系统还负责加载新的可执行程序。该责任由逻辑文件系统模块完成,这使Linux支持多种可执行格式。
故简言之:
模块结构分析
可分为四大模块:
网络子系统允许Linux系统通过网络连接到其他系统。支持许多可能的硬件设备,以及可以使用的许多网络协议。网络子系统抽象了这两个实现细节,因此用户进程和其他内核子系统可以访问网络而不必知道正在使用什么物理设备或协议。
模块结构组成
简言之,
下图示使用ls命令看到的内核源代码的顶层目录结构,具体描述如下:
1.从技术层面讲,内核是硬件与软件之间的一个中间层。作用是将应用层序的请求传递给硬件,并充当底层驱动程序,对系统中的各种设备和组件进行寻址。 2.从应用程序的层面讲,应用程序与硬件没有联系,只与内核有联系,内核是应用程序知道的层次中的最底层。在实际工作中内核抽象了相关细节。 3.内核是一个资源管理程序。负责将可用的共享资源(CPU时间、磁盘空间、网络连接等)分配得到各个系统进程。 4.内核就像一个库,提供了一组面向系统的命令。系统调用对于应用程序来说,就像调用普通函数一样。
1.微内核。最基本的功能由中央内核(微内核)实现。所有其他的功能都委托给一些独立进程,这些进程通过明确定义的通信接口与中心内核通信。 2.宏内核。内核的所有代码,包括子系统(如内存管理、文件管理、设备驱动程序)都打包到一个文件中。内核中的每一个函数都可以访问到内核中所有其他部分。目前支持模块的动态装卸(裁剪)。Linux内核就是基于这个策略实现的。 哪些地方用到了内核机制?
1.进程(在cpu的虚拟内存中分配地址空间,各个进程的地址空间完全独立;同时执行的进程数最多不超过cpu数目)之间进行通 信,需要使用特定的内核机制。 2.进程间切换(同时执行的进程数最多不超过cpu数目),也需要用到内核机制。 进程切换也需要像FreeRTOS任务切换一样保存状态,并将进程置于闲置状态/恢复状态。 3.进程的调度。确认哪个进程运行多长的时间。
(1)方法一:交互式,在内核顶层目录下运行make config
(2)方法二:菜单式,在内核顶层目录下运行make menuconfig
(1)内核编译
(2)内核模块编译:make modules命令编译内核模块
(3)内核模块安装:make modules_install命令将编译号的内核模块copy到当前系统的/lib/modules下的**目录下;
(4)内核模块打包:mkinitrd initrd-$version $version命令对内核模块进行打包,其中initrd-$version为打包后的文件名字,$version为要打包的目录;
(1)拷贝内核:copy内核编译出来的内核映像(步骤1)到启动目录,即cp arch/$cpu/boot/bzImage /boot/vmlinuxz-$version
(2)拷贝内核模块:copy内核模块打包文件(步骤4)到/boot目录下,即cp initrd-$version /boot/
(3)修改启动配置文件:修改/etc/grub.conf文件
为什么要学习写驱动
原来树莓派开发使用厂家提供的wiringPi库,开发简单。但未来做开发时,不一定都是用树莓派,没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。
文件名与设备号
linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?
依靠文件名与设备号
依靠文件名与设备号。在/dev
下ls -l
可以看到
设备号又分为:主设备号用于区别不同种类
的设备;次设备号区别同种类型的多个设备
。驱动插入到链表的位置(顺序)由设备号检索
内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:
open函数打通上层到底层硬件的详细过程:
用户空间调用open(比如open(“/dev/pin4”,O_RDWR))产生一个软中断(中断号是0x80),进入内核空间调用sys_call,这个sys_call在内核里面是汇编的,用Source Insight搜索不到。
sys_calll真正调用的是sys_open(属于VFS层虚拟文件系统,因为磁盘的分区和引脚分区不一样,为了实现上层统一化),根据你的设备名比如pin4去到内核的驱动链表,根据其主设备号与次设备号找到相关驱动函数。
调用驱动函数里面的open,这个open就是对寄存器的操作,从而设置IO口引脚电平。这件事对于单片机来说特变容易,就两句话搞定:
sbit pin4 = P1^4;
pin4=1;
shell(壳)是一个特殊的应用,也经常被称为命令行
。可以理解为是一个命令解释器
例如:当我们输入“ls -l
”的时候,它将此字符串解释为
1.在默认路径找到该文件(/bin/ls), 2.执行该文件,并附带参数"-l"。
一个shell对应一个终端 (terminal)。曾经来说,终端是一个硬件设备,用来输入并显示输出。如今,由于图形化界面的普及,终端往往就像下图一样,是一个图形化的窗口。
你可以通过这个窗口输入或者输出文本,这个文本直接传递给shell进行分析解释,然后执行,本质就是提供和内核交互的程序。
shell脚本
在没有图形界面之前,shell充当了用户的界面,当用户要运行某些应用时,通过shell输入命令,来运行程序。shell是可编程的,它可以执行符合shell语法的文本,这样的文本叫做shell脚本(script)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。