前言
作为非科班的小白,对于用户态和内核态很晕,网络的资料人云亦云,不知道真假。
这里细看了《深入理解Linux内核》,整理出来一份概念向的文章。可能有遗漏,但保证知识的正确性。
内核
什么是内核
内核是操作系统的核心部分,负责管理计算机硬件和软件资源,并提供用户和其他应用程序与计算机硬件的接口。内核控制着操作系统中的所有进程和线程,以及文件系统、网络连接和设备驱动程序等系统资源。
什么是用户态、内核态
CPU有两种执行状态,内核态和用户态。当一个程序在用户态下执行时,它不能直接访问内核数据结构或内核的程序。当一个程序在内核态下执行时,这些限制不再有效。在单处理器系统,任何时候只有一个进程在运行,它要么处于用户态,要么处于内核态。如果进程运行在内核态,处理器就执行一些内核例程。需要内核服务的进程使用系统调用来进入内核态。每个系统调用都设置了一组参数来识别进程的要求,然后执行与硬件相关的CPU指令完成从用户态到内核态的转换。
什么时候激活内核态
- 进行系统调用
- 正在执行进程的CPU发出一个异常信号。内核代表产生异常的进程处理异常。
- 外围设备向CPU发出中断信号以通知一个时间的发生。每个中断信号都是由内核中的中断处理程序来处理的。
- 内核线程被执行时。因为内核线程运行在内核态,所以尽管响应的程序被包装成一个进程,但必须认为它是内核的一部分。
内核的静态内存和动态内存
静态内存指在操作系统启动时就被分配好的内存空间,用于存储内核代码、数据和一些固定的数据结构。这些静态内存空间通常在编译操作系统内核时就确定好的,大小和位置都是固定的,不会随着系统变化而变化。
动态内存指在操作系统运行时动态分配的内存空间,用于存储运行时动态产生的数据结构和变量。在操作系统运行时,内核需要动态的分配和管理内存空间来满足进程的需求,比如进程堆栈、动态分配的内存块等,这些都需要动态内存支持。
进程
管理进程,内核需要知道进程的所有信息。进程描述符包含了一个进程相关的所有信息,比如优先级,地址空间,允许访问哪些文件等等。
先分清进程和线程
- 什么是进程:进程是系统中正在运行的一个程序,可以看成程序执行的一个实例。进程是资源分配的基本单位。
- 什么是线程:线程是进程的一个实体,是进程的一条执行路径。一个进程可以有多个线程,线程共享进程资源。线程是CPU调度的基本单位。
上述所说的是用户进程和用户线程。内核线程是一种特殊线程,由操作系统内核创建和管理的,不属于任何用户进程。内核线程常用于操作系统的核心功能,而且所使用的内存空间也是独立的。
进程状态
进程描述符中的状态域描述了进程当前所处的状态。是一组标志。
- 可运行状态
- 可中断的等待状态:进程被挂起,直到被唤醒
- 不可中断的等待状态:和前一个状态类似,但是信号到达并不会唤醒
- 暂停状态:进程的执行被暂停,比如debug的时候,可以暂停程序
- 僵死状态:进程的执行被终止,但是没有wait()
进程标识符PID
用来标识进程,使用32位的无符号整数,为了兼容16位,最大pid位32767
任务数组
内核在自己的地址空间保存一个全局静态数组task,其大小位NR_TASKS,数组的元素就是进程描述符的指针,空指针标识此位置没有进程描述符
进程描述符的存放
task数组只是保存了进程描述符的指针,而进程描述符存在内核的动态内存中(每创建一个进程,就会在内核的动态内存中保存一个进程描述符。操作系统在启动时,内核会预留一部分内存空间用来管理进程描述符)
进程链表
为了对给定类型(比如可运行状态)的进程进行有效的搜索,内核建立了几个进程链表。每个进程链表由指向进程描述符的指针组成。进程描述符的数据结构中包含了两个链表指针,指向前后的进程描述符。
进程间的亲属关系
进程0和进程1是由内核创建的,进程0所有进程的祖先,启动操作系统时第一个运行的进程。进程1是init进程,他是系统的初始化进程,是所有用户级进程的父进程。init进程会在系统启动时被内核创建,然后它会启动其他所有进程,init进程负责监控系统中所有进程的状态,并在进程异常终止时进行相应的处理。进程描述符也包含一些域:祖先,父进程,子进程,兄进程,弟进程
等待队列
运行队列链表把可运行状态的所有进程组织在一起,不过其他状态的进程分组时,不同的状态要求不同的处理。比如终止和僵死状态进程不链接在专门的链表,因为可以通过父进程直接获取;中断和不可中断的进程会分成很多类,每一类对应一个特定的事件。这种情况下,进程状态提供的信息满足不了快速检索进程,因此引入了另外的进程链表,叫做等待队列。
进程切换(自己的整理)
也叫任务切换,上下文切换。进程切换就是切换进程控制块(PCB),进程控制块和进程描述符都是用来描述进程的数据结构,有些操作系统上两者的就是一个概念。进程控制块包含的内容:
- 进程描述信息:进程标识符,用户标识符
- 进程控制和管理信息:进程当前状态,进程优先级
- 资源分配清单:内存地址空间,打开文件的列表,所使用的I/O信息
- CPU相关信息:寄存器的值。
内核线程
系统把一些重要的任务委托给周期性执行的进程,这些任务包括刷新磁盘高速缓存,交换页面,维护网络连接等等。以严格线性的方式执行这些任务效率不高,如果把他们放在后台调度,不管是对他们的函数还是对终端的用户进程都能得到较好的响应。因为一些系统进程只在内核态运行,现代操作系统把他们的函数委托给内核线程,内核线程不受不必要的用户上下文的拖累。
内核线程在以下几方面不同于普通进程
- 每个内核线程执行一个单独指定的内核函数,而普通进程只能通过系统调用执行内核函数
- 内核线程之运行在内核态,而普通进程既可以运行在内核态,也可以运行在用户态
- 内核线程只能使用特定的线性地址空间。而普通进程可以使用4GB的线性地址空间。
用户线程(自己的整理)
用户线程是由用户进程创建和管理的线程,在用户态下运行的,只能访问用户进程的地址和资源。用户进程通过系统调用来创建和撤销,操作系统对用户线程的调度和管理是通过用户进程的代码实现的。
上面是对用户线程的定义,用户线程和内核线程是有对应关系,如1:1,n:1等。
java采用一个用户线程映射到一个内核线程,创建用户线程的时候会创建一个内核线程。java线程的调度和管理由JVM的线程调度器来实现,线程调度器会按照线程的优先级、状态、以及CPU时间片等因素来调度线程的执行。线程之间的切换和同步由操作系统内核(操作系统的线程调度器)负责,jvm只是在内核线程之上提供了一层抽象,将其封装为java线程对象。(HotSpot JVM的实现,其他另说)
Linux的线程模型也是1:1模型,由内核来实现调度。