内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性。
Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等。如图:
其中,进程管理为Linux内核最重要的部分,因为在操作系统中,进程是程序运行的动态实体,所有进程的工作开展都要依托于Linux内核的进程调度,其相互关系如下图所示。
最终程序以进程的形式得到CPU资源而运行,当其需要调动下层资源时,会向Linux操作系统内核发出系统调用请求,运行环境从用户态转向内核态,在某个时间节点,完成预期功能,再返回用户态继续运行。
- 用户态:即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。在?户态,代码能够掌控的范围会受到限制。
- 内核态:所有的指令包括特权指令都可以执?。
在这里,我们可以把CPU抽象成?个for循环,因为它总是在执?next instruction(下?条指令),然后从内存?取下?条指令来执?。从这个?度来看,内存保存指令和数据,CPU负责解释和执?这些指令,它们通过总线连接起来。这?揭示了计算机可以?动化执?程序的原理。
计算机的存储体系中最关键的是内存(Memory),是存储程序计算机模型中两个关键部件之?。每个内存单元有?个地址(Address),内存地址是从0开始编号的整数,CPU通过地址找到相应的内存单元,取其中的指令或者读写其中的数据。?个地址所对应的内存单元不能存很多东?,只能存?个字节,指令或int、?oat等多字节的数据保存在内存中要占?连续的多个字节的地址,这种情况下指令或数据的地址是它所占内存单元的起始字节的地址。
堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间, 在这个堆栈上保存了函数调用框架, 传递的参数, 保存的返回地址, 函数内部的局部变量等等参数.
为了与Linux内核的汇编形式一致,这里采用AT&T汇编。
movl %eax, %edx
把eax的内容放到edx中
movl $0x123, %edx
把0x123的十六进制数值放到edx中,开头有$符号表示这是一个立即数
movl 0x123, %edx
把十六进制0x123内存地址指向的内存中存储的数据放到edx中,开头无$符号表示这是一个地址
movl (%ebx), %edx
把ebx寄存器中存储的数据当作内存地址,把这个内存地址存储的数据放到edx中
movl 4(%ebx), %edx
在上一条内存地址的基础上加立即数4,取出对应地址中存储的数据放入edx中
pushl、popl、subl等。
call指令实际做的就是将eip寄存器的当前值压栈并将要调用函数的地址赋给eip。
出于安全方面的原因,eip寄存器不能被程序员直接使用。
ret指令实际做的事就是出栈并将值赋给eip。
一个函数调用动作是通过call
指令来完成的.一个完成的函数调用过程主要分为以下三步:
中断最初?于避免CPU轮询I/O设备,就绪状态发?时让I/O设备主动通过中断信号通知CPU,??提?了CPU在输?输出上的?作效率,这就是硬件中断(外部中断)。后来随着中断适?范围扩?,?如解决机器运?过程出现的异常情况以及系统调?的实现等,这就产?了软件中断(内部中断),也称为异常,软件中断?分为故障(fault)和陷阱(trap)。
简而言之,在没有中断机制之前,计算机只能?个程序?个程序地执?,也就是批处理,??法多个程序并发?作。有了中断机制,CPU帮我们做了?件事情,就是当?个中断信号发?时,CPU把当前正在执?的进程X的CS:RIP寄存器和RSP寄存器等都压栈到了?个叫内核堆栈的地?,然后把CS:RIP指向?个中断处理程序的??,做保存现场的?作,然后去执?其他进程?如Y,等重新回来时再恢复现场,即恢复CS:RIP寄存器和RSP寄存器等到CPU上继续执?原进程X。显然中断机制在计算机系统中发挥着关键作?。
中断在定义上可以分为传统意义上的中断(硬件中断)和异常, 而中断又分为:
异常又分为:
处理器探测异常
由CPU执行指令时探测到一个反常条件时产生,如溢出、除0错等.根据发生异常时保存在内核堆栈中的eip的值可以进一步分为:
故障(fault)
eip=引起故障的指令的地址. 这种异常通常可以纠正,处理完异常时,该指令被重新执行. 例如缺页异常
陷阱(trap)
eip=随后要执行的指令的地址
异常终止
eip=???.发生严重的错误。eip值无效,只有强制终止受影响的进程
编程异常
这类异常通常由编程者发出的特定请求产生,通常由int类指令触发, 例如系统调用
关于中断和异常的处理流程, 又可以分为硬件处理流程以及软件处理流程
存放Linux内核启动时的初始化代码放在init?录中。init?录中的main.c源?件负责整个Linux内核启动,?main.c源?件中的start_kernel函数是Linux内核启动过程的起点。此函数会初始化高层内核功能,安装根文件系统,以及启动 init 进程
Init 进程:启动各种应用程序(根据相关启动脚本设置)。Init 进程首先进行一系列的硬件初始化,并挂载根文件系统。最后 init 进程会执行用户传递过来的“init=”启动参数执行用户指定的命令,或者执行以下几个进程之一,由内核态变为用户态: execve("/sbin/init",argv_init,envp_init);
1、正在运行的用户态进程X
2、发生中断,保存当前的CPU上下文到进程X的内核堆栈。加载当前进程内核堆栈的相关信息,跳转到中断处理程序。
3、保存现场,此时完成了中断上下文的切换。进程X从用户态进入了内核态。
4、中断处理过程中调用了schedule函数,其中的switch_to做了关键的进程上下文切换,将当前进程X的内核堆栈切换到进程调度算法选出的Y进程的内核堆栈。并完成EIP等寄存器状态的切换。
5、从进程Y继续执行。
6、恢复现场,要注意这里现在是进程Y的中断处理过程中。
7、从Y的进程内核堆栈中弹出之前完成的压栈内容,此时完成了中断上下文的切换。从进程Y的内核态返回了用户态。
8、继续运行用户态进程Y。
进程是进程实体 的运行过程,是系统进行资源分配和调度的一 个独立单位
特征:
为了对给定类型的进程进行有效的搜索,内核维护了几个进程链表。一般进程链表是双向链表。
进程切换本质上由两步完成:
linux提供了几个系统调用来创建和终止进程:
fork,vfork,clone来创建新进程
exec执行一个新程序
exit来终止进程
撤销过程分为:
在讲进程地址空间时涉及到了内存管理。Linux把进程地址空间分成内核区和用户区两部分。当在内核态申请内存时直接给分配,而进程在用户态申请内存时,只是给了一个新的线性地址空间的一个使用权(打白条),真的要用的时候会产生缺页异常,然后再真的分配。请求调页是一种动态内存分配技术,它把页框的分配推迟到不能再推迟为止。与之相关的分配页框的方式为写时复制:父子进程是共享页框的,当读的时候,不分配新的页框。当写的时候,谁写就给谁分配,两者各自过运行一段时间都就都有了自己的空间。
与进程地址空间有关的全部信息都包含在一个叫做内存描述符的数据结构中,进程只能访问某个有效的线性区,进程的地址空间是线性区链表上所有线性区描述符所规定的集合(通过链表来管理,头部由mmap指向)
在LINUX系统中有一个重要的概念:一切都是文件。 其实这是UNIX哲学的一个体现,而Linux是重写UNIX而来,所以这个概念也就传承了下来。在UNIX系统中,把一切资源都看作是文件,包括硬件设备。UNIX系统把每个硬件都看成是一个文件,通常称为设备文件,这样用户就可以用读写文件的方式实现对硬件的访问。这样带来优势也是显而易见的:
UNIX 权限模型也是围绕文件的概念来建立的,所以对设备也就可以同样处理了。
? 虚拟文件系统(VirtualFileSystem,VFS):隐藏了各种硬件的具体细节,把文件系统操作和不同文件系统的具体实现细节分离了开来,为所有的设备提供了统一的接口,VFS提供了多达数十种不同的文件系统。虚拟文件系统可以分为逻辑文件系统和设备驱动程序。逻辑文件系统指Linux所支持的文件系统,如ext2,fat等,设备驱动程序指为每一种硬件控制器所编写的设备驱动程序模块。
? 虚拟文件系统(VFS)是 Linux 内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。VFS 在 SCI 和内核所支持的文件系统之间提供了一个交换层。即VFS 在用户和文件系统之间提供了一个交换层。
通过学习《Linux操作系统分析》课程,自底向上,深入浅出,并通过结合具体例子进行原理分析,系统的了解了Linux操作系统的基本原理,包括Linux的初始化过程、零号进程的创建、系统调用的原理、进程的创建与切换、中断的原理、设备驱动等等。几乎涵盖了Linux操作系统最主要的功能,但各个部分并非完全独立工作而是互有交织,通过整理归纳所学知识构造概念模型,可以更加系统的认识Linux系统。
原文:https://www.cnblogs.com/xuzhihao/p/13275116.html