Linux探用户态与内核

1、 Unix/Linux的体系架构程序员

  如上图所示,从宏观上来看,Linux操做系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用可以访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。shell

  系统调用是操做系统的最小功能单位,这些系统调用根据不一样的应用场景能够进行扩展和裁剪,如今各类版本的Unix实现都提供了不一样数量的系统调用,如Linux的不一样版本提供了240-260个系统调用,FreeBSD大约提供了320个(reference:UNIX环境高级编程)。咱们能够把系统调用当作是一种不能再化简的操做(相似于原子操做,可是不一样概念),有人把它比做一个汉字的一个“笔画”,而一个“汉字”就表明一个上层应用,我以为这个比喻很是贴切。所以,有时候若是要实现一个完整的汉字(给某个变量分配内存空间),就必须调用不少的系统调用。若是从实现者(程序员)的角度来看,这势必会加剧程序员的负担,良好的程序设计方法是:重视上层的业务逻辑操做,而尽量避免底层复杂的实现细节。库函数正是为了将程序员从复杂的细节中解脱出来而提出的一种有效方法。它实现对系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用,从这个角度上看,库函数就像是组成汉字的“偏旁”。这样的一种组成方式极大加强了程序设计的灵活性,对于简单的操做,咱们能够直接调用系统调用来访问资源,如“人”,对于复杂操做,咱们借助于库函数来实现,如“仁”。显然,这样的库函数依据不一样的标准也能够有不一样的实现版本,如ISO C 标准库,POSIX标准库等。编程

  Shell是一个特殊的应用程序,俗称命令行,本质上是一个命令解释器,它下通系统调用,上通各类应用,一般充当着一种“胶水”的角色,来链接各个小功能程序,让不一样程序可以以一个清晰的接口协同工做,从而加强各个程序的功能。同时,Shell是可编程的,它能够执行符合Shell语法的文本,这样的文本称为Shell脚本,一般短短的几行Shell脚本就能够实现一个很是大的功能,缘由就是这些Shell语句一般都对系统调用作了一层封装。为了方便用户和系统交互,通常,一个Shell对应一个终端,终端是一个硬件设备,呈现给用户的是一个图形化窗口。咱们能够经过这个窗口输入或者输出文本。这个文本直接传递给shell进行分析解释,而后执行。安全


总结一下,用户态的应用程序能够经过三种方式来访问内核态的资源:网络

1)系统调用架构

2)库函数ide

3)Shell脚本函数

  下图是对上图的一个细分结构,从这个图上能够更进一步对内核所作的事有一个“全景式”的印象。主要表现为:向下控制硬件资源,向内管理操做系统资源:包括进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理,向上则向应用程序提供系统调用的接口。从总体上来看,整个操做系统分为两层:用户态和内核态,这种分层的架构极大地提升了资源管理的可扩展性和灵活性,并且方便用户对资源的调用和集中式的管理,带来必定的安全性。性能

2、用户态和内核态的切换spa

  由于操做系统的资源是有限的,若是访问资源的操做过多,必然会消耗过多的资源,并且若是不对这些操做加以区分,极可能形成资源访问的冲突。因此,为了减小有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不一样的操做赋予不一样的执行等级,就是所谓特权的概念。简单说就是有多大能力作多大的事,与系统相关的一些特别关键的操做必须由最高特权的程序来完成。Intel的X86架构的CPU提供了0到3四个特权级,数字越小,特权越高,Linux操做系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。运行于用户态的进程能够执行的操做和访问的资源都会受到极大的限制,而运行在内核态的进程则能够执行任何操做而且在资源的使用上没有限制。不少程序开始时运行于用户态,但在执行的过程当中,一些操做须要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程。好比C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存,当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换,相似的函数还有printf(),调用的是wirte()系统调用来输出字符串,等等。

  到底在什么状况下会发生从用户态到内核态的切换,通常存在如下三种状况:

1)固然就是系统调用:缘由如上的分析。

2)异常事件: 当CPU正在执行运行在用户态的程序时,忽然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如缺页异常。

3)外围设备的中断:当外围设备完成用户的请求操做后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,若是先前执行的指令是在用户态下,则天然就发生从用户态到内核态的转换。

  注意:系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断,这是操做系统为用户特别开放的一种中断,如Linux int 80h中断。因此,从触发方式和效果上来看,这三种切换方式是彻底同样的,都至关因而执行了一个中断响应的过程。可是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。

3、总结

  本文仅是从宏观的角度去理解Linux用户态和内核态的设计,并无去深究它们的具体实现方式。从实现上来看,必需要考虑到的一点我想就是性能问题,由于用户态和内核态之间的切换也会消耗大量资源。关于实现的细节,目前学艺不精不敢乱说,等往后补上。但知道了这一点,我相信对不少问题也就很容易理解了,好比说基于缓冲区的IO和无缓冲的IO,用户进程和内核进程之间的切换,IO复用中的读写内核事件表,等等,这些知识以后会一一补上。