亚搏体育官方平台而这种机制是基于cpu提供的不同权限的运行状态来实现

当前位置:亚搏体育官网 > 亚搏体育官方平台 > 亚搏体育官方平台而这种机制是基于cpu提供的不同权限的运行状态来实现
作者: 亚搏体育官网|来源: http://www.daiwagarou.com|栏目:亚搏体育官方平台

文章关键词:亚搏体育官网,内核进程

  当我们说“从用户态切换到内核态”时(例如在进行系统调用read或fork时),是指当前的进程从一种状态进入了另一种状态(并没有进程的切换)?还是指当前…

  在大部分情况下,我们认为内核态是一种CPU的特权态,这个特权态下,CPU可以执行这个特权态才允许执行的指令,访问这个特权态才运行访问的资源。这和当前的进程无关。

  OS通过控制不同的特权态来控制资源分配。一般有两种方法切换特权态,一种是特殊的指令,比如x86的软中断指令,或者大部分RISC系统的系统调用指令(比如SC),第二种是执行异常或者外部中断。发生切换后,现在到底是哪个进程,其实是无所谓的。

  现在说说现代LINUX如何定义线程和进程。我倾向于这样解释线程和进程:

  线程本质就是堆栈,当一段程序在执行,能代表它的是他的过去和现在。过去在堆栈中,现在则是CPU的所有寄存器,如果我们要挂起一个线程,我们把寄存器也保存到堆栈中,我们就具有它的所有状态,可以随时恢复它。这是线程。

  当我们切换线程的时候,同时切换它的地址空间(通过修改MMU即可),则我们认为发生了进程切换。所以进程的本质是地址空间,我们可以认为地址空间决定了进程是否发生切换。

  回到最初的问题,当CPU的特权级刚刚发生切换的时候,显然和进程,线程的切换是无关的,但之后调度器是否切换线程和进程,则和具体的情形相关了。

  简而言之:一个运行态若想成为线程或进程必须在操作系统的数据结构里登记。

  系统调用在Intel x86的机制下有两种原理,传统的原理叫interrupt(中断),然而后来又出现了一种快速系统调用叫sysenter。

  基于interrupt的系统调用:用户端通过int指令产生软件中断(虽说是软件中断,但是这个指令的处理是硬件机制所以内核安全得以保障),中断发生之后原进程会(至少暂时的)被挂起,但是这时候处理请求的内核程序不一定是一个线程,所以也不一定是进程。(interrupt发生后,由硬件临时把原运行状态备份,进入一种特殊的运行状态,运行操作系统指定的代码。而一个运行态若想成为线程必须在操作系统的数据结构里登记)

  只有当这个请求涉及长时间计算时,内核才会决定启动新内核线程来完成请求,目的是避免时间分配不公平(如果都是进程的话是分成时间片公平运行的了,但是一旦interrupt发生如果一直不转换成线程而处于硬件创建的运行态的话,有可能直到请求处理结束都没有别的进程在本cpu运行)

  有时候原用户线程发出的请求标明了是async(异步请求),这时内核也会决定不再挂起原线程。

  interrupt发生时硬件上对当前代码状态的挂起和恢复过程参见Intel 64 and IA-32 Software Developers Manual Vol.3,不过除非是要重新包装最底层的内核代码,应该是不需要写原理相关的代码。

  基于sysenter的进入内核态的方法仍然会短时间挂起原线程,如果内核不做决定也不算启动了新线程。目前sysenter不是很常见,不过效率比interrupt的方法高,sysenter是特殊cpu指令,可以读intel档案vol. 3或者vol 2a+b

  进程对cpu几乎是透明的,在进程切换时,cpu的比较大的变动是切换CR3(指向页表的寄存器)和TLB(用于缓存页表)

  对于操作系统来说进程实际上是一个进程表的一项,当进程切换发生时,进程调度程序保存当前程序的运行现场,然后载入要运行的程序的现场。

  进程正常情况下是不被允许从RPL较大的代码(用户态)跳转到RPL较小的代码(特权更高的用户态或内核态)的,从用户态进入内核态只有三种办法:门(调用门/陷阱门),syscall,sysenter,。系统调用一般使用调用门,而syscall和sysenter的过程和调用门是相似的。

  这就决定了,通过系统调用从用户态进入内核态,必然有CR3切换,必然有栈切换,必然有上下文保存。

  但是如果不触发进程调度,进程调度程序所认为的“当前进程”依然是进入内核态之前的进程。

  但是系统调用处理程序可以随时转到进程调度程序,从而更改“当前进程”,系统调用处理程序甚至可以屏蔽中断以避免转交控制,甚至可以关闭分页和分段进入实模式。

  进程不被允许触发这一类中断,当硬件状态改变或发生其他的事情时,中断控制器(或者别的东西)会向cpu发出中断请求,此时cpu会通过陷阱门执行特定的内核代码(中断处理程序),此时发生cr3切换和栈切换,所以已经相当于进程切换了。

  当然,有些中断是不会交给中断处理程序的,甚至这样的中断是对操作系统透明的——比如SM中断,啊实际上是系统管理中断,这就是连微软感受到被支配的恐惧的中断——cpu接受到系统管理中断时,会立刻转到SMM(系统管理模式),并在一个神秘的地址空间执行bios代码。这个地址空间是os看不到的。

  我个人觉得,进程对于操作系统来说只是数据结构,对于内核代码,比如系统调用处理程序,中断处理程序和进程调度程序,甚至“进程”这个词都是无意义的,它们不属于任何进程。这些跳出三界外,不在五行中的内核态代码,根本就不在乎“进程”。

  如楼上所说,cpu一般有多个权限层级,linux使用了最高和最低两个层级。并且分别在这两个层级中运行内核和用户程序。

  也就是说,内核运行在最高权限下,而普通的进程则运行在最低权限下。当普通进程需要访问其没有权限的资源时就会有两种选择,

  在1中,实质是用户态程序调用了内核所提供的函数而已,并不会造成进程切换。

  你可以将内核态与用户态理解成一种操作系统对用户程序在权限与资源上的限制机制,而这种机制是基于cpu提供的不同权限的运行状态来实现。

  知乎上看到过一句话:OS不是运行着的代码,而是一堆躺在内存里等着被调用的代码。

  放在这里就是“我用你提供的锤子砸了个钉子”,而不是“你去帮我砸个钉子”。

  1.操作系统的任务调度单位是线程,进程的存在只是为了代码和数据隔离,进程内的资源共享。

  2.态的转换,说的是线程的执行流程中当前被CPU执行的代码所处的领空,如果该线程当前被执行的是用户代码,可以说当前线程是处在用户态,如果当前线程正在被执行的是内核里面的代码,可以说当前线.一个进程要能运行并不是只需要你写的exe或者dll或者so里面的代码,而是你写的这些代码+系统提供的运行于用户态的基础设施的代码+系统提供的运行于内核态的代码

  4.用户态和内核态是怎么区分的,是根据代码所处的特权级别区分的,DPL(Descriptor privilege level)描述符特权级,8086架构保护模式编程中基于段来描述代码的不同属性,一个段就是一个内存区块,这个块里面放一些代码和数据,这个段有一个结构体描述,就是段描述符,段描述符里面有一些字段来规定这个段的属性,起始地址,长度,访问属性,当然包括DPL,80x86提供0,1,2,3四个特权级别,但是现代操作系统都只用了0和3两个极端,中间没用,用了也是自找麻烦

  5.态的切换就是程序的执行流程从位于Ring 3的代码跳到了位于Ring0的代码,或者反之。但是其中的过程并不是一个跳就能实现的,而是有不同的实现方式,有中断,也有任务段切换,不能展开讲

  还有我实现的一个基于x86 32位保护模式下的微型操作系统 github 可能需要翻墙

  Windows里面,对于普通进程来说,如果通过系统调用进入内核代码运行,当前的栈指针和EIP之类的context是发生变化的,即会进入内核内存空间,但是当前的进程context并没有变化,进程的用户内存空间还是原来这个进程的,而内核内存空间由于是所有进程所共享的,所以并没有什么区别。

  不过Windows里面确实有一个叫SYSTEM的特殊进程,这个独立的进程里面只运行内核态的线程,并且没有用户态内存空间。这和通过系统调用进入内核的方式不太一样,因为这些线程打一开始就一直在内核态运行。

  还有一些情况下,比如处理中断的时候,并不会有明显的进程上下文,因为在那个高调度级别用户态进程上下文是没有什么用处的,并且那个级别基本也不可能去访问用户内存空间,因为没法正常处理page fault。

  另外,亚搏体育官方平台内核态的代码在某些时刻是可以通过内核提供的API来attach到指定的进程的,当然会有一定限制。

  普通程序的当前特权级CPL=3,为低特权级。通过int 80h指令触发中断,处理器到IDT中访问第128号描述符,此描述符是DPL=3的陷阱门,取出陷阱门描述符中的目标代码段(内核代码system_call函数)选择子以及对应的目标代码段偏移地址,这个选择子和偏移地址分别赋值到cs和eip寄存器。然后再往下面执行的指令就是正式的内核代码(system_call函数)了,在system_call函数中,检测eax寄存器的内容,将之作为索引去查找函数指针数组sys_call_table,取出对应的函数指针,然后call一下就去执行对应的系统调用功能函数了(例如sys_fork)。

  当然,在真正开始执行内核代码前,会将ss和esp寄存器也切换成内核数据段的对应值,这个过程叫做堆栈切换,在堆栈切换到内核态的堆栈后,将程序原来的cs和eip压入堆栈,然后上面过程得到的新cs和eip才覆盖到寄存器中,至此切换内核态完成,内核态代码段特权级为0,即CPL=0,为高特权级。

  在 Understanding Linux Kernel 一书中提到,x86 架构的 CPU 有四种不同的running privilege level(x64下好像是只有2种,谢谢云天明提醒),代表不同的权限,linux 使用最高权限和最低权限两种,分别表示特权模式和非特权模式。这两个模式又被称为内核模式和用户模式。内核模式可能就是题主说的内核态吧,具体可能要根据上下文理解。

  上面这些都只是和 CPU 相关的概念,和进程相关的概念是用户空间和内核空间,每个程序运行都有自己独立的地址空间,32位操作系统的进程地址空间大小是 2^32(64位的是 2^64),也就是 4G。这个地址空间有 1G 保留用于内核代码的执行,剩下 3G 用于用户代码的执行。这种划分方式就把地址空间划分成了内核空间和用户空间。内核代码在内核空间执行而用户代码在用户空间执行。

  内核的代码是在 CPU 的特权模式下运行的(内核模式),而用户的代码是在 CPU 的非特权模式(用户模式)下运行的。你可以把内核想象成一个高级的类库,而这个库的提供给我们的接口就是系统调用,C 标准库在这层接口上又做了另外一层封装提供给我们使用。我们自己写的代码就是用户代码在用户空间运行,CPU 此时处非特权模式,当我们调用系统调用的时候(无论是你直接调用还是通过其他的库间接调用)会通过软件中断自陷到内核空间执行内核的代码,此时 CPU 处于特权模式。

  特权等级(privilege level)是由cpu架构定义的。x86规定了一共有四个特权等级。一般linux只占用0和3两个。

  当前特权等级(current privilege level)是由$cs (code segment)寄存器的最低2bit决定的。如果一段代码运行的时候$cs最低2bit是00那么cpu就认定该代码是内核态,可以执行需要特权的操作。如果是11那么就是用户态。

  一般linux都是monolithic设计,也就是整个内核都运行于内核态。也就是说,这里内核态进程和内核进程是等价的。有些unix,比如JOS,就是micro kernel设计。除了需要特权的部分内核运行在内核态以外,其他内核程序,包括文件系统,都是以用户态运行。在这里,内核态进程是内核进程中的一部分,而内核进程中还包括一部分用户态进程。

  被这个问题困扰了一天,直到看到《深入理解计算机系统》1.7.1进程小节的插图。

  “操作系统的进程切换和CPU的模式切换并没有什么关系,发生模式切换可以不改变正处于运行态的进程状态,这种情况下,保存上下文环境和以后恢复上下文环境只需要很少的开销。但是,如果改变正处于运行态的进程状态到另一个状态(就绪、阻塞等),则操作系统必须使其环境产生实质性的变化”——《操作系统:精髓与设计原理》

  内核代表进程执行某些功能。出于安全的考虑,有些活进程不让碰,你告诉内核(系统调用),内核干完返回结果给你。所以逻辑上看,系统调用只不过就是一个函数调用。于是,不区分内核态和用户态的操作系统也是完全可行的(都在一个cpu运行级)。

  线程还是同一个线程(题主说进程可能不太准确),只是处理器的状态发生了变化,效果就是获得了一些特权,例如可以访问一些用户态不能访问的空间,可以执行一些用户态不能执行的指令等。

  是进程的一种特殊状态,没有进程切换。因此可能会有多个进程陷入内核,所以要有内核锁的概念。

  操作系统向下管理系统硬件资源,向上屏蔽硬件差异,提供资源使用接口,也就是你说的read等api.

  操作系统管理硬件需要超级权限和特权指令,这些指令只能在内核态使用,亚搏体育官方平台针对arm来说,就是supervisor mode。用户调用系统调用,实际上就是调用svc指令进入了arm的 supervisor mode。

  从进程角度来看,进程相当于引用了一个库函数,特殊的地方在于,这个函数会让你进入内核态。进程进入内核态,会切换处理器状态,从而有权限获取系统资源,如内存,文件等。

  打个简单的比方,就是你去公安局办户口。你启动了一个叫办a户口的进程,然后调用派出所的户口api,派出所会检查进程的权限和参数,没问题就会进入公安局的内部流程,这个进程虽然脱离了你用户的掌控,但是还是办户口这个进程。

网友评论

我的2016年度评论盘点
还没有评论,快来抢沙发吧!