- 系统调用的实现即操作系统接口的具体实现细节,类似于插座背后电路的实现细节概念。
- 操作系统为了保证安全,不混乱,分为两种操作系统状态:内核态和用户态,相应的,内存分为内核段和用户段。
L5 系统调用的实现
内核态和用户态
系统调用的实现即操作系统接口的具体实现细节,类似于插座背后电路的实现细节概念。
操作系统为了保证安全,不混乱,分为两种操作系统状态:内核态和用户态,相应的,内存分为内核段和用户段。
内核态运行的程序可以访问计算机的任何数据和资源,不受限制,包括外围设备,比如网卡、硬盘等。处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况。
用户态运行的程序只能受限地访问内存,,只能直接读取用户程序的数据,并且不允许访问外围设备,用户态下的 CPU 不允许独占,也就是说 CPU 能够被其他程序获取。
DPL(Descriptor Privilege Level):描述符特权,用来描述目标内存段的特权级;数字越小表示特权级越高。
CPL(Current Privilege Level):当前任务特权,用来描述当前内存段的特权级;数字越小表示特权级越高。
★只有当 DPL >= CPL 时,指令才有效。例如:
- DPL=0,CPL=0,即当前在内核态,目标为内核态,都在内核态,指令有效;
- DPL=0,CPL=3,即当前在用户态,目标为内核态,用户态想进入内核态,不被允许,指令无效;
- DPL=3,CPL=0,即当前在内核态,目标为用户态,内核态想进入用户态,允许,指令有效;
- DPL=3,CPL=3,即当前在用户态,目标为用户态,都在用户态,指令有效;
系统调用的实现细节
由上所述,正常情况下是不允许用户态进入内核态的,在某些特殊情况下,为了由用户态进入内核态,只能依靠计算机硬件,对于Intel x86,那就是中断指令int,通过中断指令int将DPL置为3,使得DPL>=CPL,才可以由用户态进入内核态,然后在进入了内核态以后,又将CPL置为0。
★所以便可以得到系统调用,即操作系统接口背后的实现细节:通过操作系统接口中包含的int指令的代码,由用户态进入内核态,操作系统进行中断处理,根据编号执行相应代码,调用计算机硬件,最终实现系统调用。
下面就是一个例子:应用程序调用 printf(…) 函数,在C语言的库中将其转换为库函数 printf(…) ,以适应于库函数 write(…) 所需要的参数格式,然后库函数 printf(…) 调用库函数 write(…) ,而**在库函数write(…)中,则会通过 _syscall3 来调用包含了int 0x80指令的汇编代码,使得能从用户态进入内核态,最终实现系统调用 write(…)**,完成想要实现的功能。
注意:Linux操作系统规定了从用户态进入内核态都是要通过int 0x80中断指令进入的,所以在 _syscall3 汇编代码中,还涉及到系统调用号,以区别不同的系统调用,如 open、write、read等。
下面就是 int 0x80 指令,将DPL置为3,操作系统得以从用户态进入内核态,在进入内核态以后又将CPL置为0。
★总结printf(…)的整个过程,其中有几个关键点:
- int 0x80 将CPL 置为3,使得操作系统能从用户态进入内核态,在进入内核态后又将 CPL 置为0;
- 由于从用户态进入内核态都是要通过int 0x80中断指令进入的,所以为了区分不同的系统调用,还需要系统调用号,这里为_NR_write = 4;
- **在进入内核态以后,通过中断处理、系统调用号、查表等,最终调用 sys_write 函数,实现系统调用。 **