初探Linux的任务调度-内核态与用户态

怎样去理解Linux用户态和内核态?https://zhuanlan.zhihu.com/p/69554144
Linux探秘之用户态与内核 https://www.cnblogs.com/bakari/p/5520860.html

1. Linux的用户态与内核态的概念

1.1定义

初探Linux的任务调度-内核态与用户态

从宏观上看,Linux操作系统的体系结构分为用户态和内核态。

内核态:本身只是是一种软件,控制计算机的硬件资源,如CPU资源,存储资源,I/O资源等,提供上层应用程序运行的环境;

用户态:即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源。

用户态可以通过三种方式访问内核态:

  1. 系统调用:系统调用是操作系统的最小功能单位,系统调用根据不同的应用场景可以进行扩展和裁剪;
  2. 库函数:库函数实现对系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用;比如:C库接口malloc申请动态内存,malloc的实现内部最终还是会sbrk()或者mmap()系统调用来分配内存。以及open(),write(),read()等都是库函数。
  3. Shell脚本:是一个命令解释器,向下连接系统调用,向上连接应用,让不同程序协同工作,从而增强各个程序的功能。

1.2架构

初探Linux的任务调度-内核态与用户态

上图进一步对内核功能进行梳理。主要表现为:

  • 向下控制硬件资源:CPU资源,存储资源,I/O资源等;
  • 向内管理操作系统资源:包括进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理;
  • 向上则向应用程序提供系统调用的接口。

这种分层的架构极大地提高了资源管理的可扩展性和灵活性,而且方便用户对资源的调用和集中式的管理,带来安全性

2 内核态和用户态如何进行切换

2.1背景

操作系统的资源是有限的,例如:内存8G,CPU固定,磁盘2TB,网络接口固定。如果访问资源的操作过多,必然会消耗过多的资源,而且如果不对这些操作加以区分,很可能造成资源访问的冲突。所以,Linux的设计的初衷:对不同的操作赋予不同的执行等级,就是所谓特权的概念。Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。

运行于用户态的进程可以执行的操作和访问的资源都会受到极大的限制,而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制。很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,这就涉及到从用户态切换到内核态的过程。比如C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存,当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换,类似的函数还有printf(),调用的是wirte()系统调用来输出字符串,等等。

2.2 切换方式

从用户态到内核态的切换,一般存在以下三种情况:

a. 系统调用

这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前例中fork()实际上就是执行了一个创建新进程的sys_fork系统调用。

b. 异常

当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。

c. 外围设备的中断

当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。

系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断,这是操作系统为用户特别开放的一种中断,如Linux int 80h中断。所以,从触发方式和效果上来看,这三种切换方式是完全一样的,都相当于是执行了一个中断响应的过程。但是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。

2.3 切换操作

从触发方式上看,可以认为存在前述3种不同的类型,但是从最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,关于它们的具体区别不再赘述。关于中断处理机制的细节和步骤这里也不做过多分析,涉及到由用户态切换到内核态的步骤主要包括:

[1] 从当前进程的描述符中提取其内核栈的ss0(存放栈的段地址)及esp0(栈指针,用于指向栈的栈顶)信息。

[2] 使用ss0和esp0指向的内核栈将当前进程信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。

[3] 将先前由中断向量检索得到的中断处理程序的cs(代码段寄存器),eip(存储着我们cpu要读取指令的地址)信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。