1、与内核通信
系统调用在用户空间进程和硬件设备之间添加了一个中间层。
- 该中间层的作用是什么?
- 为用户空间提供了一种硬件的抽象接口,应用程序不用去管硬件底层的东西。
- 系统调用保证了系统的稳定和安全,避免应用程序不正确地使用硬件资源或窃取资源等危害系统的事情。
- 为了实现多任务和虚拟内存,内核需要对应用程序进行管理。
- 内核合法的入口有哪些?
- 对于用户空间访问来说,系统调用是访问内核的唯一手段
- 异常和陷入
2、API、POSIX、C库
-
应用程序如何实现?
通过在用户空间实现的应用编程接口(API)实现,而不是直接通过系统调用来实现。 -
API和系统调用之间的关系是什么?
API可以通过0个、1个或者多个系统调用来实现,即API与系统调用之间无直接联系。 -
应用程序、C库和内核之间的关系是什么?
- 从图中我们还能看出什么?
C库提供了Linux的系统调用接口和POSIX的绝大部分API
- 从图中我们还能看出什么?
-
什么是POSIX?
POSIX 是 可移植操作系统接口(Portable Operating System Interface) 的缩写。POSIX 标准定义了一套操作系统必须实现的API,这样的话,当你写的代码只使用了POSIX 标准定义的接口时,那么你的代码相对于所有支持POSIX 标准的操作系统来说,都是可移植的,最多重新编译一下就可以使用。
3、系统调用
- 如何访问系统调用?
通常通过C库中定义的函数调用来进行。 - 系统调用的返回值是什么?
系统调用还会通过一个long
类型(为了与64位的硬件体系结构保持兼容)的返回值表示成功或者错误。通常负值
代表失败,0值
代表成功。 - 系统调用在出错时会发生什么?
C库会把错误码写入errno
全局变量。通过调用perror()
库函数,可以把该变量翻译成用户可以理解的错误字符串。 - 如何定义系统调用?
- 需要用到
asmlinkage
限定词,通知编译器仅从栈中提取该函数的参数。 - 为保证32位和64位系统的兼容,系统调用在用户空间和内核空间有着不同的返回值。用户空间为
int
,内核空间为long
- 系统调用
xxx()
在内核中被定义为sys_xxx()
函数
- 需要用到
- 什么是系统调用号?
在Linux中, 每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。 - 系统调用号是否可以变更?
不可以,否则编译好的应用程序就会崩溃。 - 什么是系统调用表?
内核记录了系统调用表中的所有已注册过的系统调用的列表,存储在sys_call_table
中。每一种体系结构都明确定义了这个表。
4、系统调用处理程序
-
应用程序如何通知系统,告诉内核自己需要执行一个系统调用?
通知内核的机制是靠软中断实现:通过引发一个一场来促使系统切换到内核态去执行一场处理程序。 -
如何执行恰当的系统调用?
- 陷入内核空间
- 把系统调用号传给内核(在x86中,系统调用通过
eax
寄存器传递给内核) system_call()
函数通过给定的系统调用号与NR_syscalls
作比较来检查其有效性。if( 它 >= NR_syscalls ) return -ENOSYS; else 执行系统调用:call *sys_call_table(,%rax,8); //最后一个参数8的意义:系统调用表的表项目以64位(8字节)类型存放,所以内核需要将给定的系统调用号乘以8,然后用得到的结果在该表中查询其位置。
-
系统调用时如何传递参数?
像传递系统调用号一样,把这些参数也存放在寄存器里。例如在x86-32系统中,ebx
、ecx
、edx
、esi
和edi
按照顺序存放前五个参数(eax
用于存放系统调用号) -
接上问,如果传递的参数>=6个呢?
用一个单独的寄存器存放指向所有这些参数在用户空间地址的指针 -
内核给用户的返回值放在哪里呢?
给用户空间的返回值通过寄存器传递,在x86系统上,他存放在eax
寄存器中
5、系统调用的实现
- 实现系统调用
- 如何实现系统调用,实现系统调用重要的点有哪些?
- 第一步,决定系统调用的用途
- 系统调用的接口应该力求简洁,参数尽可能少
- 系统调用的语义和行为非常关键,它们力求稳定,不做改动
- 写系统调用的时候,要时刻注意可移植性和健壮性
- 如何实现系统调用,实现系统调用重要的点有哪些?
- 参数验证
- 系统调用时为何要实现参数验证?
系统调用在内核空间执行,系统调用必须仔细检查它们的所有参数是否合法有效,否则可能危害系统的安全和稳定。 - 最重要的一种检查是什么?
检查用户提供的指针是否有效。 - 在接收一个用户空间的指针之前,内核必须保证什么?
- 指针指向的内核趋于属于用户空间。 进程决不能哄骗内核去读内核空间的数据
- 指针指向的内存区域在进程的地址空间里。 进程不能哄骗内存去读其他进程的数据。
- 进程不能突破内存的访问限制。 如果是读,该内存应该被标记为读;如果是写,该内存应该被标记为写;如果是可执行,该内存应该被标记为可执行;
- 系统调用时为何要实现参数验证?
6、系统调用上下文
- 绑定一个系统调用的最后步骤是什么?
- 在系统调用表的最后加入一个表项
- 添加系统调用号
- 将系统调用编译进内核映像
- 如何从用户空间访问系统调用?
用户程序通过包含标准头文件并和C库链接,就可以使用系统调用(或者调用库函数,再由库函数实际调用)