这本书属于学习Linux内核原理必读推荐书目之一!对Linux内核的设计原理进行了细致的说明,也有具体实现部分的介绍,结合源码能很好的理解Linux内核;
在简单翻了一遍之后,带着如下几个疑问,整理了下相关知识点:
1、内核是什么时候加载运行的;
2、应用程序、内核和硬件的关系;
3、进程管理、内存管理和进程地址空间;
其实书上的知识还是那些,可能不同阶段去理解,原来没什么感觉,现在看来,一切都变得那么明朗起来,所以说对操作系统的理解和学习是打通任督二脉的事情,也是更好的理解了原来我们干的事情到底是处在什么样的层次,性能调优也就有了一个从源头开始的思路!
内核是什么时候加载运行的?
-
bootloader复制压缩内核到内存空间;
-
内核自解压;
-
运行内核,流程如下:
1、入口在arch/arm/kernel/head-armv.S
2、查找处理器类型
__lookup_processor_type
__lookup_architecture_type
3、初始化页表:__creat_page_tables
4、初始化C代码空间
5、跳转到C代码中,start_kernel
start_kernel在init/main.c中,完成内核的启动过程;
start_kernel()--->rest_init()--->kernel_init()--->do_basic_setup()--->do_initcalls()
另外BIOS->GRUB->INIT程序启动过程可以参考:https://www.cnblogs.com/dyllove98/p/3235654.html
应用程序、内核和硬件的关系?
-
应用程序使用库提供的open,read,write函数打开我们的设备文件。
-
库根据open,read,write函数传入的参数执行"swi"指令,这条指令引发CPU异常,进入内核。
-
内核的异常处理函数根据这些参数找到对应的驱动程序,返回一个文件句柄给库,进而返回给应用程序。
-
应用程序得到句柄后,使用库提供的write,ioclt函数发出控制命令。
-
库根据write,ioclt函数传入的参数执行swi指令,引起CPU异常,进入内核。
-
内核的异常处理函数根据这些参数调用驱动程序的相关函数。
根据《Linux内核设计与实现》说明,CPU在某个任何特定的时间点上的活动必然概括为下列三种之一:
-
运行于用户空间,执行用户进程;
-
运行于内核空间,处于进程上下文,代表某个特定的进程执行;
-
运行于内核空间,处于中断上下文,与内核进程无关处理某个特定的终端;
驱动程序与应用程序的区别
1、应用程序以main开始,驱动程序没有main,它以一个模块初始化函数作为入口。
2、应用程序从头到尾执行一个任务,驱动程序完成初始化之后不再运行,等待系统调用。
3、应用程序可以使用GLIBC等标准C函数库,驱动程序不能使用标准C库。
Linux设备驱动作为一个linux内核模块存在,模块都有2个接口函数,模块初始化函数和模块退出函数。
上面提到的驱动程序的注册。一般是由模块初始化函数来实现的。模块退出函数则用于取消内核注册,释放资源。 可见只有运行了驱动的这个模块初始化函数之后,驱动程序才能够被注册,内核才能找到设备驱动。 那么什么时候模块初始化函数才获得运行呢?动态加载时,即运行insmode时。静态加载时模块编译进内核系统初始化时会自动调用这个模块初始化函数
用户态与内核态
系统运行时一般情况下,分用户态和内核态,这两种运行态下的数据互不可见的。驱动程序是内核的一部分,工作在内核态,应用程序工作在用户态。这样就存在数据空间访问的问题:无法通过指针直接将二者的数据地址进行传递。问题的解决办法是:系统提供一系列函数帮助完成数据空间转换:例如,get_user 、put_user 、copy_from_user 、copy_to_user 等函数。
linux操作系统为什么分为用户态和内核态,简单以一句话是为了安全, 在CPU的所有指令中,有些指令是非常危险的,如果错用,将导致系统崩溃,比如清内存、设置时钟等。
系统调用:与内核通信的关键,系统调用包括系统调用号,系统调用的实现,系统调用上下文等;
图来源:https://blog.csdn.net/h1791820113/article/details/90545241
进程管理
描述进程的数据结构,进程的创建,fork、clone等方法创建进程,进程的关系,进程结束;进程调度;进程树;
进程就是运行的程序;进程除了可执行的代码段,还包括打开的文件,挂起的信号,内核内部数据,处理器状态,内存地址空间及一个或多个执行线程、全局变量的数据段等等;线程是在进程中活动的对象,线程是内核调度的对象;
进程的创建、运行、和销毁通常使用到的几个方法:fork()、clone()、exec()、exit();
Linux的进程创建时使用到写时拷贝的技术(copy-on-write)页实现。写实拷贝是一种推迟甚至免除拷贝数据的技术,内核此时并不复制整个进程地址空间,而是让父进程和子进程以制度方式共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使个个进程拥有各自的拷贝。
内存管理
内存管理简单理解是操作系统为了让多个应用程序安全、便捷的使用内存单元,实现的一种内存虚拟化技术!解决的是:
1、多进程使用同一个内存硬件资源;
2、内存数据隔离;
3、内存数据安全;
4、内存使用监控;
通过MMU的访存
MMU会先查找TLB中的虚拟地址表
如果TLB中没有虚拟地址的入口,硬件从主存储器中的转换表中获取转换与访问权限。
ARM的MMU页表格式
MMU支持基于节或者页的存储器访问。
节:1MB的存储器块
大页:64KB的存储器块
小页:4KB的存储器块
微页:1KB的存储器块
页表的级别
存在主存储器内的转换页表有两个级别:
第一级表:存储节转换表与指向第二级表的指针
第二级表:
(1)存储大页和小页的转换表。
(2)存储微页的转换表。
MMU/Cache line
CPU L1/L2高速缓存
页高速缓存:Linux内核实现的磁盘缓存,主要用来减少对磁盘的I/O操作。具体来讲,是通过把磁盘中的数据缓存到物理内存中,把对磁盘的访问变为对物理内存的访问。
进程地址空间
进程地址空间由进程可寻址的虚拟内存组成;每个应用程序可见的地址空间是相同的,如下图:
理解了这张图,可能就真正的理解了用户态和内核态是怎么回事了!
一个进程的地址空间和另一个进程的地址空间即使有相同的内存地址,实际上也彼此互不相干。
内存区域可以包括各种内存对象:
1、可执行文件代码的内存映射,成为代码段(text section);
2、可执行文件中的已初始化全局变量的内存映射,称为数据段(data section);
3、包含未初始化全局变量、也就是bss段的零页的内存映射;
4、用于进程用户控件栈的零页的内存映射;
每一个诸如C库或者动态链接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间;
5、任何内存映射文件;
6、任何共享内存段;
7、任何匿名的内存映射,如mallo分配的内存;