引言:最近在看Java内置锁的实现时看到重量级锁的性能开销较大,主要因为使用重量级锁需要用到一个pthread_mutex_lock系统调用,导致Java程序需要在用户态和内核态之间切换,由于不太了解用户态和内核态到底是什么,所有今天来研究一下。
是什么?
Linux体系中内核态与用户态是什么?
在Linux操作系统体系中,进程被分为2种类型,一种是操作系统自身运行的内核类进程,也被称为操作系统进程;另一种非操作系统进程运行在操作系统提供的能力之上的一种用户自定义的程序,我们将其称为用户类进程。
如上图所示,操作系统的工作是管理CPU、内存、硬盘、网络设备、输入输出等设备。
内核态中运行的代码可以调度CPU、分配内存回收内存、接受键鼠的中断信号等。
用户态,是用户程序所运行的模式,运行在该模式的代码被限制,不能进行某些操作,比如写入其他进程的存储空间,不能调度CPU,只能等待CPU调度。
为什么?
为什么会有内核态与用户态这两种空间的存在呢?
举个例子:
其实无论是不是Linux,对于任何操作系统来说,创建一个新的进程都是属于核心功能,因为它要做很多底层细致地工作,消耗系统的物理资源,比如分配物理内存,从父进程拷贝相关信息,拷贝设置页目录页表等等。
这些显然不能随便让哪个程序就能去做,于是自然引出特权级别的概念,最关键的权利必须由高特权级的程序来执行,这样做的好处:
- 既可以保证资源的集中管理,减少资源的使用冲突;
- 也可以降低其他程序的开发门槛,降低出错的概率,减少程序的开发运维成本。
用户进程工作在用户态,它是受限的,很多涉及到硬件的操作都无法执行,但是它们又想要取得结果,就只能请求工作在内核态的操作系统帮助完成这些操作,并将操作结果交给用户进程。
怎么用?
用户态如何切换到内核态?有三种方式:
一、中断
要想在任何需要的时候回到操作系统,这相当于是改变了CPU的正常执行流程,所以一个非常熟悉的字眼——中断(Interrupt)就出现了。通过中断,可以保证回到操作系统,从而将CPU的控制权交给操作系统。
中断的字面意思就是打断正常执行流程,但是注意,它表示的是打断流程而不是终止流程,这是不同的概念。中断是操作系统中非常重要的机制,正如上面所描述的:中断用于保证CPU控制权交给操作系统,从而让操作系统可以执行某些操作。
比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
二、系统调用
系统调用(system call)就是操作系统提供给用户进程请求操作系统做一些特权操作的接口,即为用户进程提供服务的窗口。在Linux下可以通过man syscalls命令查看Linux提供的所有系统调用。
理解系统调用其实很简单,比如有一个程序想要读取a.log文件(例如head -n 1 a.log),读取之前必须先打开文件,但是用户进程是没有权限打开文件的,所以用户进程只能发送一个open()的系统调用请求操作系统去帮忙打开这个文件,操作系统打开这个文件后会将打开的结果——文件描述符交给用户进程,用户进程通过这个文件描述符就能去操作这个文件。再进一步,用户进程想要从这个打开的文件中读取一行数据,用户进程是没有权限读取文件的,只能发送一个read()系统调用请求操作系统去读取这一行数据,操作系统读取这行数据后就能交给用户进程。
不难发现,系统调用open()和read()都像是函数。其实它们确实都是函数,只不过是比较特殊的由操作系统提供的,一般是由汇编语言编写或参杂了部分汇编代码,因为它们要和硬件交互。
发起系统调用后的主要过程:
1.发起系统调用,请求操作系统帮忙执行某些操作,这会产生软中断; 2.软中断导致陷入内核,CPU控制权交给操作系统,操作系统处理中断,即执行被请求的操作; 3.如果一切正常,操作系统在完成操作后会恢复到断点处继续向下执行,这会回到用户态; 4.用户进程取得操作系统操作的成果,继续向下执行。
Java语言中的重量级锁便是使用pthread_mutex_lock系统调用来实现。
三、异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
小结:
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
|