主要内容:
- 什么是系统调用
- Linux系统调用实现原理
- 一个ARM体系架构的系统调用实现
1、什么是系统调用
简单来说,系统调用就是用户程序和硬件设备之间的桥梁。
用户程序在需要的时候,通过系统调用来使用硬件设备。
系统调用的存在,有以下重要的意义:
1)用户程序通过系统调用来使用硬件,而不用关心具体的硬件设备,这样大大简化了用户程序的开发。
比如:用户程序通过write()系统调用就可以将数据写入文件,而不必关心文件是在磁盘上还是软盘上,或者其他存储上。
2)系统调用使得用户程序有更好的可移植性。
只要操作系统提供的系统调用接口相同,用户程序就可在不用修改的情况下,从一个系统迁移到另一个操作系统。
3)系统调用使得内核能更好的管理用户程序,增强了系统的稳定性。
因为系统调用是内核实现的,内核通过系统调用来控制开放什么功能及什么权限给用户程序。
这样可以避免用户程序不正确的使用硬件设备,从而破坏了其他程序。
4)系统调用有效的分离了用户程序和内核的开发。
用户程序只需关心系统调用API,通过这些API来开发自己的应用,不用关心API的具体实现。
内核则只要关心系统调用API的实现,而不必管它们是被如何调用的。
2、Linux系统调用实现原理
当进程执行系统调用函数时,进程跳转到内核态,进入ENTRY(vector_swi)在entry-common.S头文件中。里面涉及到一些EABI和OABI,也就是高版本linux和低版本linux的区别,汇编代码阅读起来可能会繁琐一点。在这个函数中系统检测系统调用号,根据调用号,系统会查看sys_call_table(用户体系下arch/arm/include/asm/unistd.h)查找到内核函数的入口地址ENTRY(sys_call_table)中进入calls.S。接着就在内核态调用该函数,等待返回检查后,返回用户态。
注:仔细的读者可能会想,如果系统调用一旦被删除,怎么办?linux中有一个原则,系统调用号一旦分配不能有任何变更,否则编译好的应用程序就会崩溃,此外一个系统调用号如果被删除,它所占用的系统调用号也不允许被回收。仔细研究发现linux系统中存在一个未被调用的系统调用函数sys_ni_syscall(),它除了返回-ENOSYS外不做任何事情。如果系统调用号错误(大于最大系统调用号或调用函数不存在),这个函数就要填补空缺。
3、一个ARM体系架构的系统调用实现
向内核中添加新的系统调用,需要执行如下3个步骤:
1、 添加新的内核函数
2、 更新头文件unistd.h
3、 针对这个新函数更新系统调用表calls.S
实验内核版本linux3.0.1(注低版本内核可能不适用该方法)
打开arch/arm/kernel/sys_arm.c
添加自己要添加的函数,代码如下:
asmlinkage int sys_add(int a,int b)
{
int c;
c = a + b;
return c;
}
注:asmlinkage是GNU的一个重要标签,具体详见GNU手册。
更新头文件unistd.h
在arch/arm/include/asm/unistd.h
添加如下代码:
#define __NR_add (__NR_SYSCALL_BASE+376)
更新调用表
在arch/arm/kernel/calls.S
添加如下代码:
CALL(sys_add)
添加一个系统调用后必须重新编译内核。
编写应用层实现函数:
#include <stdio.h>
#include <linux/unistd.h>
int main(void)
{
int result;
result = syscall(376,3,2);
printf("result = %d\n", result);
return 0;
}