『阿男的Linux内核世界』*14 从User Space到Kernel Space(二)*

『阿男的Linux内核世界』*14 从User Space到Kernel Space(二)*

我们知道了Intel架构的CPU的指令权限是按照Ring的级别来进行区分的:Kernel Space会使用Ring 0获得CPU的全部使用和管理权限,User Space使用Ring 3,获得计算机有限的执行权限。当你在User Space强行使用高权限的CPU指令时,CPU会直接报错的,而Kernel也会把这个错误返回给你。

因此,一些不需要更高权限的指令,比如ADDSUB这些基本的运算指令,是直接在User Space里面执行的。此外,还有一些User Space的内存的数据移动和Copy是直接在User Space执行的,对应的指令比如MOVLEA等等。

当然,如果你超出内存边界范围地去移动一些数据,CPU也会给你报错的。因为我们的Process是运行在Kernel提供给我们的虚拟内存空间里。这种模式对于CPU叫做Protected Mode,也就是保护模式。

所谓保护模式,就是CPU会保护每一个Process在它们各自的虚拟内存地址空间里面执行而不会威胁到Kernel和其它Process的数据安全。这当然也是通过只有Kernel能访问的一些高权限的寄存器来辅助Kernel的代码来完成的。关于具体实现,阿男后续再给大家细讲。

接下来我们可以想一下什么时候Process需要从User Space跳到Kernel Space去执行,也就是说,Process是怎么样从User Space跳到Kernel Space的。

这个问题的答案是:当系统接收到「中断请求」的时候,就会从User Space转到Kernel Space。什么是「中断请求」?「中断请求」大概分为三种情况:

  • CPU异常
  • 硬件中断
  • 软件中断

CPU异常就是指CPU在工作过程中发生的各种错误,比如运算的时候用户写的代码除以0了,或者是Ring 3模式下要执行Ring 0才允许的指令,等等。

硬件中断,就是指计算机的硬件设备给CPU发信号,告诉CPU,我这个硬件设备有信息给你处理。比如我们按键盘,就是一个硬件中断:每次我们按键,键盘都得告诉CPU用户按的是哪个键,这样CPU才知道有用户通过键盘输入的数据要处理。

软件中断就是使用CPU提供的INT指令再加上中断编号,通过指令来生成一个中断。

Intel CPU的架构为以上三种中断支持从0号到255号中断,也就是说,一共256个编号可以用来定义中断,但这里面有一些预留的中断编号是CPU的架构预先约定好的。

比如,前32个编号是固定定义好的,这前32个中断大多和CPU自身的状态相关,我们可以在traps.h里面找到相关的定义^1

126 /* Interrupts/Exceptions */
127 enum {
128         X86_TRAP_DE = 0,        /*  0, Divide-by-zero */
129         X86_TRAP_DB,            /*  1, Debug */
130         X86_TRAP_NMI,           /*  2, Non-maskable Interrupt */
131         X86_TRAP_BP,            /*  3, Breakpoint */
132         X86_TRAP_OF,            /*  4, Overflow */
133         X86_TRAP_BR,            /*  5, Bound Range Exceeded */
134         X86_TRAP_UD,            /*  6, Invalid Opcode */
135         X86_TRAP_NM,            /*  7, Device Not Available */
136         X86_TRAP_DF,            /*  8, Double Fault */
137         X86_TRAP_OLD_MF,        /*  9, Coprocessor Segment Overrun */
138         X86_TRAP_TS,            /* 10, Invalid TSS */
139         X86_TRAP_NP,            /* 11, Segment Not Present */
140         X86_TRAP_SS,            /* 12, Stack Segment Fault */
141         X86_TRAP_GP,            /* 13, General Protection Fault */
142         X86_TRAP_PF,            /* 14, Page Fault */
143         X86_TRAP_SPURIOUS,      /* 15, Spurious Interrupt */
144         X86_TRAP_MF,            /* 16, x87 Floating-Point Exception */
145         X86_TRAP_AC,            /* 17, Alignment Check */
146         X86_TRAP_MC,            /* 18, Machine Check */
147         X86_TRAP_XF,            /* 19, SIMD Floating-Point Exception */
148         X86_TRAP_IRET = 32,     /* 32, IRET Exception */
149 };

此外,剩下的中断编号的定义,我们还可以在irq_vectors.h^2里面看到,这里面也包含了好多硬件中断的约定编号。在这里我们重点要看的就是0x80号中断,其中0x80是16进制,对应十进制的128。下面是0x80号中断在irq_vectors.h中的定义:

 49 #define IA32_SYSCALL_VECTOR             0x80

这个0x80,也就是十进制的128号中断,就是对应所有System calls的入口。所谓System calls,就是Kernel Space给User Space的API接口,用于User Space请求Kernel Space的一些功能。软中断最常用的也就是0x80号中断,所以我们可以在汇编代码中看到大量INT 0x80指令的调用,这也是一种CPU和操作系统中的约定。

当User Space想使用Kernel Space提供的这些System calls的时候,就把需要的System call编号和要传递的参数放到寄存器里,然后执行INT 0x80指令,就会跳进System call的处理函数。

我们可以大概想一下这个处理函数要怎么实现,首先我们要有一张表来保存各个System calls对应的实现函数,然后我们需要有一个入口函数,查表去执行请求的具体函数。关于这个表和这个入口函数的具体函数,阿男就不具体说了,可以看这篇文档^3,如果你不是很关心具体的实现,只要理解这里面的思路就好 。我们这里看一下Linux默认的一些System calls的列表:

#define __NR_open 1024
__SYSCALL(__NR_open, sys_open)
#define __NR_link 1025
__SYSCALL(__NR_link, sys_link)
#define __NR_unlink 1026
__SYSCALL(__NR_unlink, sys_unlink)
#define __NR_mknod 1027
__SYSCALL(__NR_mknod, sys_mknod)
#define __NR_chmod 1028
__SYSCALL(__NR_chmod, sys_chmod)
#define __NR_chown 1029
__SYSCALL(__NR_chown, sys_chown)
#define __NR_mkdir 1030
__SYSCALL(__NR_mkdir, sys_mkdir)
#define __NR_rmdir 1031
__SYSCALL(__NR_rmdir, sys_rmdir)
#define __NR_lchown 1032
__SYSCALL(__NR_lchown, sys_lchown)
#define __NR_access 1033
__SYSCALL(__NR_access, sys_access)
#define __NR_rename 1034
__SYSCALL(__NR_rename, sys_rename)
#define __NR_readlink 1035
__SYSCALL(__NR_readlink, sys_readlink)
#define __NR_symlink 1036
__SYSCALL(__NR_symlink, sys_symlink)
#define __NR_utimes 1037
__SYSCALL(__NR_utimes, sys_utimes)
#define __NR3264_stat 1038
__SC_3264(__NR3264_stat, sys_stat64, sys_newstat)
#define __NR3264_lstat 1039
__SC_3264(__NR3264_lstat, sys_lstat64, sys_newlstat)

最新的Linux内核里面有300多个system calls,涵盖操作系统所提供的方方面面的功能,比如上面阿男列出来的一小部分。上面这些system calls定义在include/uapi/asm-generic/unistd.h里面^4。我们可以看看这些System calls的功能分类,有控制Process生命周期的,比如exitfork等等。还有控制IO设备输入输出的,比如readwrite等等。还有控制各种IO设备资源的打开和关闭的,比如openclose

转载于:https://my.oschina.net/u/3195023/blog/821744

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值