(一)系统调用
-
定义
系统调用由操作系统实现提供的应用编程接口(Application Programming Interface,API),是应用程序同系统之间的接口,实则,系统调用是软件上由用户态进入内核态的唯一方式,保证用户用户空间以安全的访问内核。 -
内核为什么要提供系统调用呢?
首先说系统调用的调用流程(后文有更详细的系统调用实现流程),以open(2)和fopen(3)举例, 当用户调用fopen(3)时,C库中的实现,实际上是加上一些的封装的,简化用户的调用的open(2)函数,即fopen(3)调用了open(2)函数,open(2)即时内核提供的系统调用。
系统调用作为内核对外提供的接口,可以间接地使用驱动程序来操纵硬件,如图中红色部分,通过系统调用open(2),最终使用磁盘驱动来操控磁盘(如果是对磁盘写的话,系统并不是立即对磁盘产生写动作,而是由磁盘驱动调度,在一个合适的时间将数据写入,与具体的磁盘驱动算法有关)
从以上的调用层次分析可得:
a) 系统调用可以避免用户直接操控内核中的函数、驱动程序,使得调用者可以安全地调用内核中的驱动。
b) 系统调用实现了类似“多态”的机制。例如,open(2)可以open一个文件,也可以open一个外设,open的对象不同,实现的效果是不一样的。简化了调用者的编程,而不用打开每个设备都单独的调用该设备的open函数。
c) 系统调用是一组定好的编程接口,一般不会再变动,对上层应用来看,只要系统调用接口不变,程序就是兼容的、可移植的。而内核中的函数,随着内核的更新是会变化的,即时不考虑内核安全,出于这个方面的考虑,系统调用也是有存在的必要的,不应该在用户空间直接调用内核函数。所以也就有了内核态的用户态之分。 -
系统调用的实现
实现方式概述:软中断
用户程序首先将系统调用号填充到某个寄存器(ARM v7 r7寄存器可),可查看内核源码—>执行软中断指令—>软中断异常—>跳转到异常向量表中执行—>从异常向量表跳转到真正的软中断异常处理代码执行—>在软中断异常处理代码中找到系统调用号,根据系统调用号调用内核中对应的函数,并把该函数的执行结果返回给用户空间
open(2)函数的实现中(libc.so中),会将系统调用号(open的系统调用号是5)存到对应的寄存器(arm r7),然后调用软中断指令触发软中断(mips中有syscall指令,作用类似),软中断通过系统调用号5,调用内核中sys_open函数,sys_open函数,根据打开文件或者设备的类型不同,去匹配对应的open函数(类似多态机制),如果没有找到该种类型对应的open,会执行一段默认的open代码。
为什么非要走软中断呢?系统调用去调用内核函数不就好了?原因是在于,内核函数的执行需要在内核态,而由用户态切换到内核态,实际上就是由软中断所实现的
内核源码路径如下:
系统调用号:arch/arm/include/asm/unistd.h
系统调用表 位于calls.S
软中断处理代码:entry-common.Svector_swi: 获取系统调用号--->r7 ... adr tbl, sys_call_table //系统调用表 位于calls.S r7 < 系统调用号的最大值 sys_call_table[r7]
(二)为内核增加一个系统调用
- 打开内核源码路径下的arch/arm/kernel/sys_arm.c文件,这个文件中实现了一下linux/arm平台下的一些非标准调用顺序的系统调用,这个文件一定是参加内核编译的(可查看Makefile文件确认,已确认),在这个文件中增加一个函数用来作为系统调用。
- 仿照内核源码系统调用的格式,编写以下代码
asmlinkage int sys_add(int x, int y) { printk("<1>" "enter %s \n", __func__); return x+y; }
- 更新系统调用表vi arch/arm/kernel/calls.S
可以看到,在我没有更新系统调用表时,系统调用号最大为377,对应函数为 sys_process_vm_writev,我在后边加了一个sys_add,所以我添加的这个系统调用的调用号为378
系统调用表的作用是,当有系统调用到来时,来的是375号,就调用sys_setns,如果是378号,就调用sys_add函数,以此类推 - 增加新的系统调用号 vi arch/arm/include/asm/unistd.h
找到最后一个系统调用377号,在其后类比添加一行
#define __NR_add (__NR_SYSCALL_BASE+378)
这个不改也可以通过syscall(378, 1, 2)来调用sys_add(1,2),推测这个文件中的基址+偏移应该是针对共享库的,通过基址+偏移得到各个系统调用在库中的地址,再通过这个地址去访问具体的系统调用代码实现(包括存储中断号,执行软中断指令等) - 重新编译内核 让开发板使用新内核
- 编写测试程序
由于我添加的函数在内核中,正常的系统调用如open在调用过程中,open是在libc.so库中的,然后通过软中断去调用内核函数sys_open,而我只实现了sys_add在内核,libc.so中并没有对应的add函数,所以在我的UC程序中不能直接调用add函数,无法通过用户态调用add进而去调用内核函数sys_add,所以我通过另一个方式来调用内核函数sys_add-------syscall(2)
交叉编译,到下位机执行(注意下位机一定要使用新内核),若是报错缺少库文件,则去上位机交叉编译工具目录下拷贝到下位机/lib即可#include <stdio.h> int main(void){ int res = syscall(378, 10, 20);//sys_add(10, 20) printf("res=%d\n", res);//30 return 0; }