系统调用原理与用户态以及内核态相互切换过程,以linux系统为主

1. 讲系统调用前需要清楚的几个基本概念

1.1 内核态与用户态

  1. 内核态:当CPU执行内核的代码(CPU堆栈指针指向内核堆栈)时,我们就称此时处于内核态,内核态的代码可以使用特权指令,这些指令可以控制计算机,直接操作计算的硬件,如in,out等;
  2. 用户态:CPU执行的是用户写的代码(CPU堆栈指针指向内核堆栈),此时就称处于用户态,用户态不可以使用特权指令,都是一些基础的指令,如mov,add,sub。

1.2 中断

1.3 linux下int 0x80号中断

linux下所有的系统调用都和0x80号中断绑定,当用户或者库函数调用一个系统提供的API,就会产生一个0x80的中断,CPU将陷入内核,去执行内核代码,完成用户或者程序的请求。
在这里插入图片描述

1.4 Linux下的特权级别

X86体系结构支持的所有特权级别:
在这里插入图片描述

Linux内核中实际所采用的特权级别:

  • R0:最高级别,也就是操作系统级别的权限,可以访问所有的资源。即内核态(管理态)
  • R3:最低级别,也就是用户权限,无法直接访问硬件资源,必须通过操作系统提供的接口访问。即用户态。

Linux下用户和内核状态如何转换:

  • 用户态到内核态:我们称之为陷入内核,主要的途径有中断、异常、陷入机制;
  • 内核态到用户态:我们称之为陷出,修改程序状态字PSW即可。

1.5 中断向量表:

存放中断处理程序的入口地址
在这里插入图片描述

2. 系统调用:编程者向操作系统提出服务请求的接口

2.1 作用和意义:使用户态陷入内核态,才能得到资源


  • 是用户与内核之间沟通的桥梁;
  • 我们知道我们计算机几乎所有的硬件等资源都由我们的操作系统管理,不论是我们还是我们自己编写的软件再或者用户的应用软件对计算机硬件或者其他资源进行操作时都需要操作系统的“同意”,这些资源由操作系统给我们统一保管和分配。
    在这里插入图片描述

2.2 一个系统调用基本过程

在这里插入图片描述

2.3 系统调用write()中缓冲区

一般默认将我们目标内容打印到屏幕上,实际上底层调用的是我们的write(),将目标的字符传送到我们的标准输出
实际上,上述的write()并不是每次执行都会进行中断来满足用户,无论是中断还是系统调用,都会产生巨大的开销,无论是时间上的还是空间上的。
write()函数有自己缓冲区,一般来说,要等到缓冲区满或者用户不在写入了,write才切换到内核态执行,这样做大大加快系统的运行效率。

3. 内核态和用户态切换

3.1 堆栈以及堆栈寄存器简单介绍

3.1.1 CPU堆栈寄存器,作用如下:

  • 当cpu的堆栈寄存器指向的是用户的地址空间,说明系统现在处于用户态,正在执行用户空间的代码;
  • 当cpu的堆栈寄存器指向的是内核的地址空间,说明系统正在处于内核态,正在执行内核空间的代码。

3.1.2 用户堆栈以及内核堆栈

  • 内核栈:是内核固有的一块区域,用来保护中断现场,内核中 函数 之间相互调用的参数,返回值等。
  • 用户栈:专门用于用户执行各种各样的函数的内存,随函数调用开始结束共存亡

3.2 用户态——>内核态:

cpu保存用户当前堆栈信息保存到内核的栈中(恢复时用到),然后将cpu指向内核堆栈,去执行内核代码。

3.3 内核态——>用户态:

再切换到内核堆栈前,将用户堆栈信息压入到内核栈中,内核函数执行完回退栈帧,会将用户的堆栈信息POP出栈,然后cpu堆栈寄存器就知道怎么回去了,返回的用户程序中断的地方继续执行。
在这里插入图片描述

4. 补充一个write()调用的大致过程


 - 1.我在我的 应用程序 中执行调用write库函数:

int main()
{
	...
	 write(...);  //应用程序中 调用 库函数 write()
	...
	return 0;
}

 - 2.库函数做的事情:

将参数收集起来  +  启动0x80号中断陷入内核实际是调用system_call()
系统调用号就是system_call()进行推送的

 - 3.陷入内核中做的事情:查系统调用表 + 调响应的系统调用 这里是sys_write() 然后给用户返回

xxxxxxxxxxxx  sys_writen()
{
	...
	return something;
}

整个过程:应用程序->库函数->内核

在这里插入图片描述

再来看看最终的堆栈是什么样的:
在这里插入图片描述
其中,系统调用号之前的东西 由 硬件机制 push入栈,系统调用号由system_call() push入栈

5. 自己设计一个系统调用过程如何设计

  • 利用硬件的支持----中断/异常机制:实现支持系统调用服务

  • 选择一条访管指令(陷入指令)-----用来陷入内核,引发异常完成用户和内核态的切换

  • 为每一个系统调用 进行编号(因为所有系统调用通过同一条陷入指令进行,所以需要编号方便来查找程序);
    参数:编译器选择如何将参数传递(寄存器或者堆栈?都可以)

  • 系统调用表:存放系统调用程序的入口地址

5.1 参数传递问题:用户程序的参数----------->内核系统调用函数

  • 陷入指令自带参数:陷入指令要带系统调用号,所以参数太多的情况带不了怎么办?

通过寄存器传递参数:使用通用寄存器,寄存器的数量通用有限,不过多数情况能满足,如果是在不能满足,在内存中开辟专用堆栈区,用来参数的拷贝。

6. 内核系统调用表长什么样子

//linux-2.6.27.10
#define _ALPHA_UNISTD_H

#define __NR_osf_syscall	  0	/* not implemented */
#define __NR_exit		  1
#define __NR_fork		  2
#define __NR_read		  3
#define __NR_write		  4
#define __NR_osf_old_open	  5	/* not implemented */
#define __NR_close		  6
#define __NR_osf_wait4		  7
#define __NR_osf_old_creat	  8	/* not implemented */
#define __NR_link		  9
#define __NR_unlink		 10
#define __NR_osf_execve		 11	/* not implemented */
#define __NR_chdir		 12
#define __NR_fchdir		 13
#define __NR_mknod		 14
#define __NR_chmod		 15
#define __NR_chown		 16
#define __NR_brk		 17
#define __NR_osf_getfsstat	 18	/* not implemented */
#define __NR_lseek		 19
#define __NR_getxpid		 20
#define __NR_osf_mount		 21
#define __NR_umount		 22
#define __NR_setuid		 23
#define __NR_getxuid		 24
#define __NR_exec_with_loader	 25	/* not implemented */
#define __NR_ptrace		 26

注:以上纯属个人见解,如有不恰当的地方,希望不吝赐教。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值