用户态与内核态

  1. 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序。
  2. 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。
  3. 为什么要有用户态和内核态?
    由于需要限制不同的程序之间的访问能力, 防止他们获取别的程序的内存数据, 或者获取外围设备的数据, 并发送到网络, CPU划分出两个权限等级 – 用户态和内核态。
  4. 指令划分:
    特权指令:只能由操作系统使用、用户程序不能使用的指令。举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机
    非特权指令:用户程序可以使用的指令。举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)
  5. 内核态与用户态区别:
    (1)内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;
    (2)当程序运行在0级特权级上时,就可以称之为运行在内核态
    (3)运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)
    (4)这两种状态的主要差别是:
    处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的
    处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。

用户态和内核态的切换

  1. 用户态切换到内核态的3种方式包括系统调用、异常和外围设备的中断
  2. 系统调用,这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。用户程序通常调用库函数,由库函数再调用系统调用,因此有的库函数会使用户程序进入内核态(只要库函数中某处调用了系统调用),有的则不会。
  3. 异常,当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
  4. 外围设备的中断,当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
  5. 以上3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
  6. 3种不同的类型最终实际完成由用户态到内核态的切换操作上来说,涉及的关键步骤是完全一致的,没有任何区别,都相当于执行了一个中断响应的过程,因为系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本上也是一致的,涉及到由用户态切换到内核态的步骤主要包括:
    (1)从当前进程的描述符中提取其内核栈的ss0及esp0信息。
    (2) 使用ss0和esp0指向的内核栈将当前进程的cs,eip,eflags,ss,esp信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
    (3) 将先前由中断向量检索得到的中断处理程序的cs,eip信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
  7. 内核态切换到用户态的途径——>设置程序状态字PSW
  8. 注意一条特殊的指令——陷入指令(又称为访管指令,因为内核态也被称为管理态,访管就是访问管理态)该指令给用户提供接口,用于调用操作系统的服务。

Linux可执行文件的装载和执行

  1. Linux下可执行文件的格式为ELF格式(Executeable and linkable format),.o文件和可执行文件,都是目标文件,一般使用相同的文件格式
  2. ELF文件里的三种目标文件:
    (1)一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其它的object文件一起来创建一个可执行文件或者是一个共享文件(主要是.o文件)
    (2)一个可执行(executable)文件保存着一个用来执行的程序,该文件指出了exec(BA_OS)如何来创建程序进程映象(操作系统怎么样把可执行文件加载起来并且从哪里开始执行)
    (3)一个共享object文件保存着代码和合适的数据,用来被下面两个链接器链接:(主要是.so文件)第一个是链接编辑器(静态链接),可以和其它的可重定位和共享object文件来创建其它的object,第二个是动态链接器,联合一个可执行文件和其它的共享object文件来创建一个进程映象
  3. 我们可以在linux下的命令行中使用 readelf -h excuteableFile 来查看一个ELF文件的头部信息,运行一个可执行文件时,第一件事就是要将该文件加载到内存中去,从理论上来讲,系统会将该文件拷贝到一个虚拟的内存空间段里面去,在拷贝的过程中,可执行文件的格式和进程的地址空间存在如下的映射关系
  4. ELF可执行文件默认被加载到内存入口这个位置,即从这个位置开始加载。先加载ELF可执行文件的头部信息,再加载代码部分,但因不同文件头部大小不一样,第一行代码(即程序的实际入口地址)的位置也会有所不同。
  5. 当创建或增加一个进程映象的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段,静态链接的时候,会将所有代码放在一个代码段,把所有的链接都链接好了,所以从入口地址开始一行行代码执行,压栈出栈,把整个程序执行完。而实际上如果需要用到共享库,需要动态链接的话,会有多个代码段,情况会更复杂
  6. 装载可执行程序之前的工作最主要的是两大部分:
    (1)可执行程序的文件格式
    (2)可执行程序的执行环境
  7. 在shell环境下运行一个可执行文件(比如 “ls”),实际上就是shell fork出来的子进程执行execve系统调用(ls 作为参数传递),执行完系统调用后,shell的子进程就变为了可执行程序ls。
  8. 可执行程序的执行环境(Shell命令行、main函数的参数与execve的参数):
    (1)int main(int argc, char *argv[])– 愿意接收命令行参数
    (2)int main(int argc, char *argv[], char *envp[])–愿意接收shell的环境变量,前两个参数由用户输入命令的时候设定,后一个是shell环境,shell程序自动加上
    Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数 int execve(const char* filename,char* const argv[],char * const envp[]);
  9. 命令行参数和环境变量是如何保存和传递的?
    先函数调用参数传递,再系统调用参数传递
    Shell程序 -> execve -> sys_execve,然后在初始化新程序堆栈时拷贝进去。命令行参数和环境串都放在用户态堆栈中
  10. 当fork一个子进程的时候,复制父进程,调用execve系统调用的时候,要加载的可执行程序把原来的进程的环境覆盖掉了,覆盖掉之后它的用户态堆栈也被清空了,因为它是个新的程序要执行,那么argv和envp是如何进入新程序的用户态堆栈的?即命令行参数和环境变量是如何进入新程序的堆栈的?
    在创建一个新的用户态堆栈的时候,实际上是把命令行参数的内容和环境变量的内容通过指针的方式传递到execve系统调用的内核处理函数,然后内核处理函数在创建可执行程序新的用户态堆栈的时候,会把参数拷贝到用户态堆栈里,初始化新的可执行程序的上下文环境。所以,新的程序能从main函数开始,把对应的参数接收过来,然后执行。但原先在调用execve时,参数只是压在了shell程序当前进程的堆栈上,而这个堆栈在加载完新的可执行程序之后,已经被清空了,内核又创建了一个新进程的用户态堆栈
  11. 当execve系统调用陷入到内核里的时候,system_call,调用了sys_execve(),sys_execve内部会解析可执行文件格式,后面的调用顺序: do_execve -> do_execve_common -> exec_binprm search_binary_handler根据文件头部信息寻找对应的文件格式处理模块,对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读,在load_elf_binary里有一个很关键的地方,start_thread
  12. 动态链接的过程不是由内核来完成的,主要是由动态链接器来完成的,动态链接器是libc的一部分,是在用户态做的事情
  13. 加载可执行程序两种方式:
    (1)静态链接,直接执行可执行程序的入口 Entry point address
    (2)需要动态链接,由ld动态链接这个可执行程序