您的位置:首页 > 运维架构 > Linux

linux-3.18内核系统调用

2015-12-02 16:19 411 查看


1.系统调用基本概念

系统调用是指就是函数调用,只是调用的函数是系统函数,处于内核态而已。用户在调用系统调用时会向内核传递一个系统调用号,然后系统调用处理程序通过此号从系统调用表中找到相应的内核函数执行(也就是系统调用服务例程),

2.系统调用信息定义初始化

在arch/x86/kernel/syscall_64.c文件中定义并初始化了sys_call_table

#define __SYSCALL_COMMON(nr, sym, compat) __SYSCALL_64(nr, sym, compat)
...
#define __SYSCALL_64(nr, sym, compat) extern asmlinkage void sym(void) ;
#include <asm/syscalls_64.h>
#undef __SYSCALL_64
 
#define __SYSCALL_64(nr, sym, compat) [nr] = sym,
extern void sys_ni_syscall(void);
asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
/*
* Smells like a compiler bug -- it doesn't work
* when the & below is removed.
*/
[0 ... __NR_syscall_max] = &sys_ni_syscall,
#include <asm/syscalls_64.h>
}

sys_call_table的初始化使用了GCC的扩展语法, [0 ... __NR_syscall_max] = &sys_ni_syscall将数组内容全部初始化为

未实现(服务例程没有实际内容,下边介绍);然后包含syscalls_64.h的内容逐项初始化,这个文件预先不存在,是在内核编译期间生成的(请看2.2)。

2.1 sys_ni_syscall

并不是所有的服务例程都有实际内容,sys_ni_syscall服务例程除了返回-ENOSYS外不做任何其他工作,在kernel/sys_ni.c文件中定义。

asmlinkage long sys_ni_syscall(void)
{
return -ENOSYS;
}

sys_ni_syscall的确是最简单的系统调用服务例程,表面上看,它可能并没有什么用处,但是,它在sys_call_table中占据了很多位置。多数位置上的sys_ni_syscal都代表了那些已经被内核中淘汰或者没有实际用处的系统调用。

如果一个系统调用被淘汰,它所对应的服务例程就要被指定为sys_ni_syscall。我们并不能将它们的位置分配给其他的系统调用,因为一些老的代码可能还会使用到它们。否则,如果某个用户应用试图调用这些已经被淘汰的系统调用,所得到的结果,比如打开了一个文件,就会与预期完全不同,这将令人感到非常奇怪。其实,sys_ni_syscall中的"ni"即表示"not implemented(没有实现)"。

2.2 系统调用号

在linux-3.18.24/arch/x86/syscalls/目录中存放有系统调用号相关的文件,unistd_64.h、syscalls_64.h等文件都是在内核编译时根据syscalls目录中的脚本和系统调用号定义文件syscall_64.tbl生成的。

2.2.1 syscall_64.tbl

系统调用号定义在syscall_64.tbl(32位的为syscall_32.tbl)文件中。

64-bit system call numbers and entry vectors

The format is:

The abi is "common", "64" or "x32" for this file.

abi就是应用程序二进制接口

A、应用程序 <-> 操作系统;

B、应用程序 <-> (应用程序所用到的)库

C 、应用程序各个组件之间

类似于API的作用是使得程序的代码间的兼容,ABI目的是使得程序的二进制(级别)的兼容。

common:64 bit mode和32 bit mode程序都能兼容的

64: 64 bit mode的程序

x32:运行在64 bit mode 下的32 bit的程序(x32 abi系统调用号从512开始)

syscall_64.tbl内容如下:

0       common  read                    sys_read
1       common  write                   sys_write
2       common  open                    sys_open
3       common  close                   sys_close
4       common  stat                    sys_newstat

2.2.2 如何生成syscalls_64.h、unistd_64.h等文件

vi syscalltbl.sh     //生成syscalls_64.h
#!/bin/sh
 
in="$1"
out="$2"
 
grep '^[0-9]' "$in" | sort -n | (            //将syscall_64.tbl中以数字开头的行排序
    while read nr abi name entry compat; do
        abi=`echo "$abi" | tr '[a-z]' '[A-Z]'`        //将abi字段置为大写
if [ -n "$compat" ]; then                         //判断compat字段是否为空
            echo "__SYSCALL_${abi}($nr, $entry, $compat)"
        elif [ -n "$entry" ]; then            //entry字段非空
echo "__SYSCALL_${abi}($nr, $entry, $entry)"
fi
done
) > "$out

syscalls_64.h内容如下

__SYSCALL_COMMON(0, sys_read, sys_read)
__SYSCALL_COMMON(1, sys_write, sys_write)
__SYSCALL_COMMON(2, sys_open, sys_open)
__SYSCALL_COMMON(3, sys_close, sys_close)
__SYSCALL_COMMON(4, sys_newstat, sys_newstat)
...

syscalls_64.h中一连串的宏其实完成的就是用文件syscall_64.tbl中声明的系统调用来初始化表sys_call_table中的每一项

syscallhdr.sh脚本用于生成unistd_64.h等文件

#ifndef _ASM_X86_UNISTD_64_H
#define _ASM_X86_UNISTD_64_H 1
 
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4

Makefile中指定输出到哪个目录

out := $(obj)/../include/generated/asm
uapi := $(obj)/../include/generated/uapi/asm
...

3. 系统服务例程

linux-3.18/kernel/sys.c中添加函数定义,即就是自己系统调用的服务例程,可以添加进sys.c文件中,也可以单独在kernel目录下创建一个服务例程文件(必须将此.c文件添加进该目录下的Makefile文件中,即就是以模块形式编译进内核),下边是直接添加进sys.c文件中:

SYSCALL_DEFINE0(tycall)
{
printk("This is mysyscall from kernel. \n");
return current->pid;
}

以上定义宏SYSCALL_DEFINE0实际上会扩展为:

asmlinkage long sys_tycall(void)
{
printf("This is mysyscall from kernel.\n");
return current->pid;
}

asmlinkage修饰符是GCC中一个比较特殊的标志,因为GCC常用的一种编译方法是使用寄存器传递函数的参数,而加了asmlinkage修饰符的函数必须从堆栈中而不是寄存器中获取参数。内核中所有系统调用的实现都是用了这个修饰符。内核在sys.c文件中定义了SYSCALL_DEFINE0~SYSCALL_DEFINE6等七个提供便利的宏,如下所示:

#define SYSCALL_DEFINE0(name)  SYSCALL_METADATA(__##sname, 0);   asmlinkage long sys_##name(void)
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

SYSCALL_DEFINE宏后边的数字说明了系统调用name的输入参数个数,这些宏都已一个基础宏SYSCALL_DEFINEx实现:

#define SYSCALL_DEFINEx(x, sname, ...)                          \
SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
 
#define __SYSCALL_DEFINEx(x, name, ...)                                 \
asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \

宏定义中可以用##运算符把运算符前后两个预处理符号连接成一个预处理符号;

GCC用一个名为[b]VA_ARGS[/b]保留关键字实现了宏定义实现可变数目参数的目的;
SC_DECL和MAP也都是一个宏定义,主要用于系统调用的参数问题:

#define __MAP0(m,...)
#define __MAP1(m,t,a) m(t,a)
#define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
#define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
#define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
#define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
#define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
#define __MAP(n,...) __MAP##n(__VA_ARGS__)
 
#define __SC_DECL(t, a) t a

对于系统调用open定义fs/open.c

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)

结合上边宏的分析展开过程如下:

SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
SYSCALL_DEFINEx(3, _##open, __VA_ARGS__)
__SYSCALL_DEFINEx(3, open, __VA_ARGS__)
asmlinkage long sys_open(__MAP(3,__SC_DECL, __VA_ARGS__))
asmlinkage long sys_open(__MAP3(__SC_DECL, __VA_ARGS__))
asmlinkage long sys_open(const char __user* filename, __MAP2(__SC_DECL,__VA_ARGS__))
asmlinkage long sys_open(const char __user* filename, int flags, __MAP1(__SC_DECL, umode_t, mode))
asmlinkage long sys_open(const char __user* filename, int flags, umode_t mode)

4.添加系统调用号

linux-3.18/arch/x86/syscalls/syscall_64.tbl

322 common tycall sys_tycall

5. 在内核头文件中,添加服务例程的声明

在include/linux/syscalls.h文件的最后,#endif之前加入系统调用服务例程sys_tycall()的声明:

asmlinkage long sys_tycall();

注意: 本操作并非必须,通常将体系结构无关的系统调用的声明放置于此。此文件中的声明并不是给用户程序用的

添加完成,后编译内核,即可测试是否添加成功。

6. 用户态测试系统调用

#include <stdio.h>
#include <sys/syscall.h>
 
#define SYS_tycall 322
main(){
pid_t pid;
pid = syscall(SYS_tycall);
printf("SYS_tycall = %d\n",pid);
}

编译执行,正确输出进程pid即可。

以上就是新版内核系统调用的过程,添加自己的系统调用总共就三部:

a. 定义系统调用服务例程

b. 添加系统调用号

c. 添加系统调用声明

d. 测试
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: