用户态和内核态

1、概念

  • 进程是资源管理的最小单位;
  • 线程是程序执行的最小单位。
  • OS设计上,从进程演化出线程,最主要的目的就是减小多进程上下文切换开销

 即就是我们常说的:进程作为系统资源分配的基本单位,线程作为任务调度和执行的基本单位。

从四个方面简单说明一下进程与线程的区别:

(1)在开销方面:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小进程切换开销大,线程切换开销小

(2)所处环境:在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(通过CPU调度,在每个时间片中只有一个线程执行);

(3)内存分配方面:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了CPU外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源。即进程有内存空间,线程没有内存空间,使用的资源来自进程

(4)包含关系:没有线程的进程可以看做是单线程的,如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

 

  • 最初的进程定义都包含程序、资源及其执行三部分:
  • (1)程序:通常指代码
  • (2)资源:在操作系统层面上通常包括内存资源、IO资源、信号处理等部分;
  • (3)执行:通常理解为执行上下文,包括对CPU的占用,后来发展为线程。
  • 进程切换只发生在内核态
  • 切换前,用户态进程用的所有寄存器都已保存在内核态堆栈,也包括ssesp这对寄存器的内容(存储用户态堆栈指针的地址)
  • 每个进程被创建时:
  • 在生成task_struct的同时,生成两个栈
  • 用户栈,位于用户地址空间;
  • 内核栈,位于内核空间。
  • 当进程在用户地址空间中执行的时候,使用的是用户栈,CPU堆栈指针寄存器中存的是用户栈的地址
  • 当进程在内核空间执行时,CPU堆栈指针寄存器中放的是内核栈的地址
  • 当位于用户空间的进程系统调用时,它会陷入内核,让内核代其执行。
  • 进程用户栈的地址会被存进内核栈中,CPU堆栈指针寄存器中的内容也会变为内核栈的地址。
  • 系统调用执行完,进程从内核栈找到用户栈地址,继续在用户空间中执行,此时CPU堆栈指针寄存器就变为了用户栈的地址。

 那么,到底什么是内核态和用户态?先别急,看之前,我们先了解一下特级权的概念。

 

2、特权级的概念:

对于任何操作系统来说,创建一个进程是核心功能。创建进程要做很多工作,会消耗很多物理资源。比如分配物理内存,父子进程拷贝信息,拷贝设置页目录页表等等,这些工作得由特定的进程去做,所以就有了特权级别的概念。最关键的工作必须交给特权级最高的进程去执行,这样可以做到集中管理,减少有限资源的访问和使用冲突。inter x86架构的cpu一共有四个级别,0-3级,0级特权级最高,3级特权级最低。

 

 3. 内核态和用户态

   CPU划分出两个权限等级用户态 和 内核态

  • 内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡。CPU也可以将自己从一个程序切换到另一个程序;
  • 用户态: 只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取;

 

【详细解释】

当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。Ring3状态不能访问Ring0的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。

 

用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码。进程切换Ring0,然后进入3G-4G中的内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程地址空间中的数据。

 

 

linux进程有4GB地址空间,如图所示:

 

 

 3G-4G大部分是共享的,是内核态的地址空间。这里存放整个内核的代码和所有的内核模块以及内核所维护的数据。

cpu mode,用户模式只能通过系统调用操作硬件资源,内核模式可以直接操作硬件资源

 

现在我们从特权级的调度来理解用户态和内核态就比较好理解了,当程序运行在3级 特权级上时,就可以称之为运行在用户态,因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;反之,当程序运 行在0级特权级上时,就可以称之为运行在内核态。

虽然用户态下和内核态下工作的程序有很多差别,但最重要的差别就在于特权级的不 同,即权力的不同。

【举个例子】

void testfork(){
    if(0 = = fork()){
        printf(“create new process success!\n”);
    }
    printf(“testfork ok\n”);
}    

这段代码很简单,从功能的角度来看,就是实际执行了一个fork(),生成一个 新的进程,从逻辑的角度看,就是判断了如果fork()返回的是0则打印相关语句,然后函数最后再打印一句表示执行完整个testfork()函数。代码 的执行逻辑和功能上看就是如此简单,一共四行代码,从上到下一句一句执行而已,完全看不出来哪里有体现出用户态和进程态的概念。

如果说前面两种是静态观察的角度看的话,我们还可以从动态的角度来看这段代码,即它被转换成CPU执行的指令后加载执行的过程,这时这段程序就是一个动态执行的指令序列。而究竟加载了哪些代码,如何加载就是和操作系统密切相关了。

 

4. 用户态和内核态的切换

 当在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成一些用户态自己没有特权和能力完成的操作时就会切换到内核态。

  • 用户态切换到内核态的三种途径——中断、异常、陷入(系统调用)
  • 内核态切换到用户态的途径——设置程序状态字

【详细解释】

用户态--->内核态

(1)陷入——又称系统调用

这是用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。例如 fork() 就是执行了一个创建新进程的系统调用。系统调用的机制和新是使用了操作系统为用户特别开放的一个中断来实现,如Linux的int 80h中断。

前面说过,所有用户程序都是运行在用户态的, 但是有时候程序确实需要做一些内核态的事情, 例如从硬盘读取数据, 或者从键盘获取输入等。 而唯一可以做这些事情的就是操作系统, 所以此时程序就需要先操作系统请求以程序的名义来执行这些操作。

这时需要一个这样的机制:用户态程序切换到内核态, 但是不能控制在内核态中执行的指令。

这种机制叫系统调用, 在CPU中的实现称之为陷阱指令(Trap Instruction)

他们的工作流程如下:

  1. 用户态程序将一些数据值放在寄存器中, 或者使用参数创建一个堆栈(stack frame), 以此表明需要操作系统提供的服务;
  2. 用户态程序执行陷阱指令
  3. CPU切换到内核态, 并跳到位于内存指定位置的指令, 这些指令是操作系统的一部分, 他们具有内存保护, 不可被用户态程序访问;
  4. 这些指令称之为陷阱(trap)或者系统调用处理器(system call handler). 他们会读取程序放入内存的数据参数,执行程序请求的服务
  5. 系统调用完成后, 操作系统会重置CPU为用户态并返回系统调用的结果。

运行在用户态下的程序不能直接访问操作系统内核数据结构和程序,比如上面例子中的testfork()就不能直接调用 sys_fork(),因为前者是工作在用户态,属于用户态程序,而sys_fork()是工作在内核态,属于内核态程序。

当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系 统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态,比如testfork()最初运行在用户态进程下,当它调用fork()最终触发 sys_fork()的执行时,就切换到了内核态。

(2)异常

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

(3)中断——外围设备的中断

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

【总结】

这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。

 

具体切换操作:

由用户态切换到内核态 的步骤主要包括:

(1)从当前进程的描述符中提取内核栈的ss0及esp0信息

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

(3)将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。

 

 

内核态--->用户

设置程序状态字PSW

 

5. 进程间通信主要有哪几种方式?

管道:两个进程需要 有共同的祖先,Pipe/popen;

命名管道:两个进程可以无关;

信号

信号量

消息队列

共享内存

套接字

 

 

 

参考:

1、用户态和内核态的理解和区别

2、用户态和内核态的区别(这篇整理的很好)

3、轻量级进程 +SS寄存器和ESP寄存器+怎么理解linux内核栈?+用户态/内核态、用户栈/内核栈

4、深入理解内核态和用户态

5、进程和线程的主要区别(总结)

6、进程间通信有哪几种方式?进程间通信的方法详解

 

 

 

Over......

posted @ 2021-02-16 21:53  额是无名小卒儿  阅读(396)  评论(0编辑  收藏  举报