零碎调用
零碎调用就是一组用于用户过程与内核交互的接口。实现:
- 应用程序 受限 地拜访硬件设施(硬件的形象接口),基于权限进行拜访裁决。
- 创立过程间通信机制
- 申请操作系统其它资源的能力。
零碎调用是用户控件拜访内核的惟一形式,除了异样和陷入之外。
API、POSIX 和 C 库
API:应用程序通过在用户控件实现的利用编程接口。通过 API 进行零碎调用,而不间接进行零碎调用,不须要和零碎调用一一对应。
POSIX 指标是提供基于 Unix 的可移植操作系统规范。依据 POSIX 定义的 API 函数和零碎调用都有间接的关系。
linux 的零碎调用能够作为 C 库的一部分进行提供,C 库实现了 Unix 零碎次要 API,提供了绝大部分 POSIX API。
程序员只跟 API 打交道,Unix 的接口设计准则是只提供机制而不提供策略。
应用 Linux 零碎调用
程序员通过 API 调用进行零碎调用,关注输出,输入,全局错误码 errno 变量。通过 perror()库函数,把 errno 变量进行打印。定义零碎调用:
asmlinkage long sys_getpid(void)
限定词 asmlinkage 是一个编译指令,告诉编译器仅从栈中提取该函数的参数,所有零碎调用都须要这个限定词。
函数返回值 long,兼顾 32 位和 64 位,零碎调用在用户空间和内核空间有不同的返回值类型,在用户空间为 int,内核空间 long。
零碎调用号
- 每个零碎调用对应一个零碎调用号。
- 过程不会提及零碎调用的名称。
- 一旦调配就不能扭转,否则零碎解体,不兼容。
- 零碎被删除或者废除后,Linux 有一个未实现零碎调用 sys_ni_syscall(),除了返回 -ENOSYS 外不做任何工作。
- sys_call_table 零碎调用表中记录了所有注册过的零碎调用列表。
- 不同体系结构,零碎调用表不同。x86 在 arch/i386/kernel/syscall_64.c
零碎调用性能
Linux 零碎调用性能比其余操作系统要好,因为他有更短的上下文切换,而且零碎调用处理程序和每个零碎调用自身都比拟简洁。
零碎调用处理程序
用户态过程通过软中断的形式告诉内核须要执行一个零碎调用:引发一个异样,促使零碎切换到内核态去执行异样处理程序,该程序就是零碎调用处理程序,中断号 128,int $0x80 指令触发中断,system_call(),与硬件体系结构严密相干
,x86-64 用汇编写的 entry_64.S,后减少了 sysenter 指令。
陷入内核的同时,须要把零碎调用号一并传给内核,x86 上通过寄存器 eax.system_call()将零碎调用号与 NR_syscalls 作比拟,大于或等于 NR_syscalls,函数返回 -ENOSYS,否则就执行相应的零碎调用:
call *sys_call_table(,%rax,8)
因为零碎调用表中的表项是以 64 位(8 字节)类型寄存的,所以内核须要将给定的零碎调用号 *4, 而后将所得后果在该表中查问地位(????),x86-32 零碎上,代码很相似,只是用 4 代替 8。
零碎调用的参数传递通过寄存器进行传递 ebx, ecx, edx, esi 和 edi 寄存前 5 个参数,6 个或以上,用一个独自寄存器寄存指向这些参数在用户空间地址的指针。
返回值也是通过寄存器进行传送,eax 寄存器中。
零碎调用实现
在 linux 设计和实现一个零碎调用是难题,退出内核不麻烦。
- 决定用处,调用参数,返回值,错误码。力求简洁,稳固,向后兼容,可移植性。许多零碎调用通过提供标记参数以确保向前兼容,标记并不是用来让单个零碎调用具备多个不同的行为,而是为了即便减少新的性能和选项,也不毁坏向后兼容或不须要减少新的零碎调用。别对机器的字节长度和字节序做假如。
- 参数验证,用户输出,用户指针(拜访权限问题, 指向区域必须是用户控件,指针指向的内存区域在过程的地址空间内,可读可写可执行的权限分清)
copy_from_user()
,copy_to_user()
: 目标地址,源地址,内存长度。可能引起阻塞,当蕴含用户数据的页被换到硬盘上而不是物理内存上时,过程休眠,晓得缺页处理程序将该页从硬盘从新换回物理内存。权限验证:通过capable()
函数查看是否有权对特定资源进行操作,非 0 有权操作。 -
零碎调用上下文,如果执行零碎调用,内核就处于过程上下文,current 指针指向当前任务。
- 过程上下文中,内核能够休眠(零碎调用阻塞或者 schedule()调用)
- 能够被抢占,新过程能够应用雷同的零碎调用,所以要留神该零碎调用是否可重入。
- 零碎调用返回时,控制权依然在
system_call()
中,他最终会负责切换回用户空间,并让用户过程继续执行上来。
-
绑定零碎调用
- 零碎调用表
entry.s
中减少一个表项,0 开始 -end - 与体系结构强相干,
<asm/unistd.h>
- 零碎调用必须被编译进内核映像,不能被编译成模块,放在
kernel/
下一个相干文件就可了,如sys.c
ENTRY(sys_call_table) .long sys_restart_syscall ....
- 没有明确指向编号,默认从 0 -
- 须要对每个体系结构减少该零碎调用
- 零碎调用号减少到
<asm/unistd.h>
中,
#define __NR_restart_systemcall 0 ...
- 零碎调用表
-
从用户空间拜访零碎调用
- 通过 c 库进行调用,新增的零碎调用可能不反对
- 能够应用宏对系统调用进行拜访,他会设置好寄存器并调用陷入指令。
_syscalln()//0-6, 代表传递给零碎调用的参数个数 long open(const char *filename, int flags, int mode) #define NR_open 5 //<asm/unistd.h> 中定义,是零碎调用号 _syscall3(long, open, const char, *filename, int, flags, int, mode)
- 该宏会被扩大为内嵌汇编的 c 函数。将零碎调用号和参数压入寄存器并触发软中断来陷入内核。把下面的宏放在应用程序中就行。
-
不举荐通过零碎调用的形式实现
零碎调用在 linux 中容易创立和使用方便以及高性能。
问题是- 须要一个零碎调用号,官网调配
- 退出稳固内核后就被固化,不能做扭转
- 须要将他别离注册到不同体系结构中,
- 脚本中不容易应用零碎调用,也不能从文件系统中拜访零碎调用
代替办法 - 实现一个设施节点,对此实现
read
和write
,应用ioctl
对特定的设置进行操作或者对特定的信息进行检索。 - 像信号量这样的某些接口,能够用文件描述符来示意,因而也就能够依照上述办法对他进行操作
- 把减少的信息作为一个文件放在 sysfs 的适合地位。