转自:https://blog.csdn.net/feixin620/article/details/78416560

引言:分析Android源码的过程中,要想从上至下完全明白一行代码,往往涉及app、framework、native一直到kernel,可能迷失到代码世界,明白了系统调用原理,或许能帮你峰回路转,找到进入kernel函数的入口。本文主要讲解ARM架构相关源码:

  1.  
    /bionic/libc/kernel/uapi/asm-arm/asm/unistd.h
  2.  
    /bionic/libc/arch-arm/syscalls/kill.S
  3.  
     
  4.  
    /kernel/arch/arm/kernel/calls.S
  5.  
    /kernel/arch/arm/include/Uapi/asm/unistd.h
  6.  
    /kernel/include/uapi/asm-generic/unistd.h
  7.  
    /kernel/include/linux/syscalls.h
  8.  
    /kernel/kernel/signal.c
  9.  
    /kernel/arch/arm/kernel/entry-common.S
  10.  
    /kernelarch/arm/kernel/entry-armv.S
  11.  
     

一、Syscall意义

内核提供用户空间程序与内核空间进行交互的一套标准接口,这些接口让用户态程序能受限访问硬件设备,比如申请系统资源,操作设备读写,创建新进程等。用户空间发生请求,内核空间负责执行,这些接口便是用户空间和内核空间共同识别的桥梁,这里提到两个字“受限”,是由于为了保证内核稳定性,而不能让用户空间程序随意更改系统,必须是内核对外开放的且满足权限的程序才能调用相应接口。

在用户空间和内核空间之间,有一个叫做Syscall(系统调用, system call)的中间层,是连接用户态和内核态的桥梁。这样即提高了内核的安全型,也便于移植,只需实现同一套接口即可。Linux系统,用户空间通过向内核空间发出Syscall,产生软中断,从而让程序陷入内核态,执行相应的操作。对于每个系统调用都会有一个对应的系统调用号,比很多操作系统要少很多。

安全性与稳定性:内核驻留在受保护的地址空间,用户空间程序无法直接执行内核代码,也无法访问内核数据,通过系统调用

性能:Linux上下文切换时间很短,以及系统调用处理过程非常精简,内核优化得好,所以性能上往往比很多其他操作系统执行要好。

二、Syscall查找方式

这里以文章理解杀进程的实现原理中的kill()方法为例子,来找一找kill()方法系统调用的过程。

Tips 1: 用户空间的方法xxx,对应系统调用层方法则是sys_xxx; 
Tips 2: unistd.h文件记录着系统调用中断号的信息。

故用户空间kill方法则对应系统调用层便是sys_kill,这个方法去哪里找呢?从/kernel/include/uapi/asm-generic/unistd.h等还有很多unistd.h去慢慢查看,查看关键字sys_kill,便能看到下面几行:

  1.  
    /* kernel/signal.c */
  2.  
    __SYSCALL(__NR_kill, sys_kill)
  3.  
     

根据这个能得到一丝线索,那就是kill对应的方法sys_kill位于/kernel/signal.c文件。

Tips 3: 宏定义SYSCALL_DEFINEx(xxx,…),展开后对应的方法则是sys_xxx; 
Tips 4: 方法参数的个数x,对应于SYSCALL_DEFINEx。

kill(int pid, int sig)方法共两个参数,则对应方法于SYSCALL_DEFINE2(kill,...),进入signal.c文件,再次搜索关键字,便能看到方法:

  1.  
    SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
  2.  
    {
  3.  
    struct siginfo info;
  4.  
    info.si_signo = sig;
  5.  
    info.si_errno = 0;
  6.  
    info.si_code = SI_USER;
  7.  
    info.si_pid = task_tgid_vnr(current);
  8.  
    info.si_uid = from_kuid_munged(current_user_ns(), current_uid());
  9.  
    return kill_something_info(sig, &info, pid);
  10.  
    }
  11.  
     

SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) 基本等价于 asmlinkage long sys_kill(int pid, int sig),这里用的是基本等价,往下看会解释原因。

实用技巧

比如kill命令, 有两个参数. 则可以直接在kernel目录下搜索 “SYSCALL_DEFINE2(kill”,即可直接找到,所有对应的Syscall方法位于signal.c

三、Syscall流程

Syscall是通过中断方式实现的,ARM平台上通过swi中断来实现系统调用,实现从用户态切换到内核态,发送软中断swi时,从中断向量表中查看跳转代码,其中异常向量表定义在文件/kernelarch/arm/kernel/entry-armv.S(汇编语言文件)。当执行系统调用时会根据系统调用号从系统调用表中来查看目标函数的入口地址,在calls.S文件中声明了入口地址信息。

总体流程:kill() -> kill.S -> swi陷入内核态 -> 从sys_call_table查看到sys_kill -> ret_fast_syscall -> 回到用户态执行kill()下一行代码。 下面介绍部分核心流程:

3.1: 用户程序通过软中断swi指令切入内核态,执行vector_swi处的指令。vector_swi在文件/kenel/arch/arm/kernel/entry-common.S中定义,此处省略。像每一个异常处理程序一样,要做的第一件事当然就是保护现场了。紧接着是获得系统调用的系统调用号

3.2: 仍以kill()函数为例,来详细说说Syscall调用流程,用户空间kill()定义位于文件kill.S

  1.  
    #include <private/bionic_asm.h>
  2.  
    ENTRY(kill)
  3.  
    mov ip, r7
  4.  
    ldr r7, =__NR_kill
  5.  
    swi #0
  6.  
    mov r7, ip
  7.  
    cmn r0, #(MAX_ERRNO + 1)
  8.  
    bxls lr
  9.  
    neg r0, r0
  10.  
    b __set_errno_internal
  11.  
    END(kill)
  12.  
     

当调用kill时, 系统先保存r7内容, 然后将__NR_kill值放入r7, 再执行swi软中断指令切换进内核态。

3.3: Linux内核中,每个Syscall都有唯一的系统调用号对应,kill的系统调用号为__NR_kill,用户空间的系统调用号定义于/bionic/libc/kernel/uapi/asm-generic/unistd.h,如下:

  1.  
    #define __NR_kill (__NR_SYSCALL_BASE + 37)
  2.  
     

其中__NR_SYSCALL_BASE=0,也就是__NR_kill系统调用号=37。

3.4: 在内核中有与系统调用号对应的系统调用表,定义在文件/kernel/arch/arm/kernel/calls.S,如下:

  1.  
    /* 35 */ CALL(sys_ni_syscall) /* was sys_ftime */
  2.  
    CALL(sys_sync)
  3.  
    CALL(sys_kill) //此处为37号
  4.  
    CALL(sys_rename)
  5.  
    CALL(sys_mkdir)
  6.  
     

到这里可知37号系统调用对应sys_kill(),该方法所对应的函数声明在syscalls.h文件

3.5: 文件/kernel/include/linux/syscalls.h中有如下声明:

  1.  
    asmlinkage long sys_kill(int pid, int sig);
  2.  
     

asmlinkage是gcc标签,代表函数读取的参数来自于栈中,而非寄存器。

3.1 SYSCALL_DEFINE

sys_kill()定义在内核源码找不到直接定义,而是通过syscalls.h文件中的SYSCALL_DEFINE宏定义。前面已经讲过sys_kill是通过语句SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)来定义,下面来一层层剖开,这条宏定义的真面目:

等价 1:

syscalls.h中有大量如下宏定义:

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

可得出原语句等价:

  1.  
    SYSCALL_DEFINEx(2, _kill, pid_t, pid, int, sig)
  2.  
     

等价 2:

syscalls.h中有如下宏定义:

  1.  
    #define SYSCALL_DEFINEx(x, sname, ...) \
  2.  
    SYSCALL_METADATA(sname, x, __VA_ARGS__) \
  3.  
    __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
  4.  
     

可得出原语句等价:

  1.  
    SYSCALL_METADATA(_kill, 2, pid_t, pid, int, sig)
  2.  
    __SYSCALL_DEFINEx(2, _kill, pid_t, pid, int, sig)
  3.  
     

define __SYSCALL_DEFINEx(x, name, …)

等价 3:

syscalls.h中有如下宏定义:

  1.  
    #define __SYSCALL_DEFINEx(x, name, ...) \
  2.  
    asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) \
  3.  
    __attribute__((alias(__stringify(SyS##name)))); \
  4.  
    static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
  5.  
    asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
  6.  
    asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
  7.  
    { \
  8.  
    long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
  9.  
    __MAP(x,__SC_TEST,__VA_ARGS__); \
  10.  
    __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
  11.  
    return ret; \
  12.  
    } \
  13.  
    static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))
  14.  
     

可得出原语句等价:

  1.  
    asmlinkage long sys_kill(__MAP(2,__SC_DECL,__VA_ARGS__)) \
  2.  
    __attribute__((alias(__stringify(SyS_kill)))); \
  3.  
    static inline long SYSC_kill(__MAP(2,__SC_DECL,__VA_ARGS__)); \
  4.  
    asmlinkage long SyS_kill(__MAP(2,__SC_LONG,__VA_ARGS__)); \
  5.  
    asmlinkage long SyS_kill(__MAP(2,__SC_LONG,__VA_ARGS__)) \
  6.  
    { \
  7.  
    long ret = SYSC_kill(__MAP(2,__SC_CAST,__VA_ARGS__)); \
  8.  
    __MAP(2,__SC_TEST,__VA_ARGS__); \
  9.  
    __PROTECT(2, ret,__MAP(2,__SC_ARGS,__VA_ARGS__)); \
  10.  
    return ret; \
  11.  
    } \
  12.  
    static inline long SYSC_kill(__MAP(2,__SC_DECL,__VA_ARGS__))
  13.  
     

这里__VA_ARGS__等于 pid_t, pid, int, sig

等价 4:

先说说这里涉及的宏定义

__MAP宏定义:

  1.  
    #define __MAP0(m,...)
  2.  
    #define __MAP1(m,t,a) m(t,a)
  3.  
    #define __MAP2(m,t,a,...) m(t,a), __MAP1(m,__VA_ARGS__)
  4.  
    #define __MAP3(m,t,a,...) m(t,a), __MAP2(m,__VA_ARGS__)
  5.  
    #define __MAP4(m,t,a,...) m(t,a), __MAP3(m,__VA_ARGS__)
  6.  
    #define __MAP5(m,t,a,...) m(t,a), __MAP4(m,__VA_ARGS__)
  7.  
    #define __MAP6(m,t,a,...) m(t,a), __MAP5(m,__VA_ARGS__)
  8.  
    #define __MAP(n,...) __MAP##n(__VA_ARGS__)
  9.  
     

相关宏定义:

  1.  
    #define __SC_DECL(t, a) t a
  2.  
    #define __SC_LONG(t, a) __typeof(__builtin_choose_expr(__TYPE_IS_LL(t), 0LL, 0L)) a
  3.  
    #define __SC_CAST(t, a) (t) a
  4.  
    #define __SC_ARGS(t, a) a
  5.  
    #define __SC_TEST(t, a) (void)BUILD_BUG_ON_ZERO(!__TYPE_IS_LL(t) && sizeof(t) > sizeof(long))
  6.  
     

展开:

  1.  
    __MAP(2,__SC_DECL, pid_t, pid, int, sig) //等价于 pid_t pid, int sig
  2.  
    __MAP(2,__SC_LONG,__VA_ARGS__) //等价于 long pid, long sig
  3.  
    __MAP(2,__SC_CAST,__VA_ARGS__) //等价于 (pid_t) pid, (int)sig
  4.  
    __MAP(2,__SC_ARGS,__VA_ARGS__) //等价于 pid, sig
  5.  
     

可得出原语句等价:

  1.  
    //函数声明sys_kill(),并别名指向SyS_kill
  2.  
    asmlinkage long sys_kill(pid_t pid, int sig) __attribute__((alias(__stringify(SyS_kill))));
  3.  
    static inline long SYSC_kill(pid_t pid, int sig);
  4.  
     
  5.  
    //函数声明SyS_kill()
  6.  
    asmlinkage long SyS_kill(long pid, long sig);
  7.  
    asmlinkage long SyS_kill(long pid, long sig)
  8.  
    {
  9.  
    long ret = SYSC_kill((pid_t) pid, (int)sig);
  10.  
    BUILD_BUG_ON_ZERO(sizeof(pid_t) > sizeof(long));
  11.  
    BUILD_BUG_ON_ZERO(sizeof(int) > sizeof(long));
  12.  
    __PROTECT(2, ret, pid, sig);
  13.  
    return ret;
  14.  
    }
  15.  
    static inline long SYSC_kill(pid_t pid, int sig)
  16.  
     

通过以上分析过程:

  • kill添加了sys_前缀,声明sys_kill()函数;
  • 定义SYSC_kill()函数和SyS_kill()函数;
  • sys_kill,通过别名机制等同于SyS_kill().

看到这或许很多人(包括我)会觉得诧异,为何要如此复杂呢,后来查资料,发现这是由于之前64位Linux存在CVE-2009-2009的漏洞,简单说就是32位参数存放在64位寄存器,修改符号扩展可能导致产生一个非法内存地址,从而导致系统崩溃或者提升权限。 为了修复这个问题,把寄存器高位清零即可,但做起来比较困难,为了做尽可能少的修改,将调用参数统一采用使用long型来接收,再强转为相应参数。 窥见一斑,可见Linux大师们精湛的宏定义,已经用得出神入化

如果觉得很复杂,那么可以忽略这个宏定义,只要记住SYSCALL_DEFINE2(kill, pid_t, pid, int, sig) 基本等价于 asmlinkage long sys_kill(int pid, int sig) 就足够了。

四、总结

4.1 内核空间

  1. 系统调用的函数原型的指针:位于文件/kernel/arch/arm/kernel/calls.S,格式为CALL(sys_xxx),指定了目标函数的入口地址。
  2. 系统调用号的宏定义:位于文件/kernel/arch/arm/include/Uapi/asm/unistd.h,记录着内核空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])
  3. 系统调用的函数声明:位于文件/kernel/include/linux/syscalls.h,格式为asmlinkage long sys_xxx(args ...);
  4. 系统调用的函数实现:不同函数位于不同文件,比如kill()位于/kernel/kernel/signal.c文件,格式为SYSCALL_DEFINEx(x, sname, ...)

前面这4步都是在内核空间相关的文件定义,有了这些,那么内核就可以使用相应的系统调用号。

4.2 用户空间

  1. 系统调用号的宏定义:位于文件/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h,记录着用户空间的系统调用号,格式为#define__NR_xxx (__NR_SYSCALL_BASE+[num])。这个文件就是由内核空间同名的头文件自动生成的,所以该文件与内核空间的系统调用号是完全一致。

  2. 汇编定义相关函数的中断调用过程:位于文件/bionic/libc/arch-arm/syscalls/xxx.S,比如kill()位于kill.S,格式为:

    1.  
      ENTRY(xxx)
    2.  
      mov ip, r7
    3.  
      ldr r7, =__NR_xxx
    4.  
      swi #0
    5.  
      mov r7, ip
    6.  
      cmn r0, #(MAX_ERRNO + 1)
    7.  
      bxls lr
    8.  
      neg r0, r0
    9.  
      b __set_errno_internal
    10.  
      END(xxx)
    11.  
       

当然kill()方法还有函数声明,有了这些,用户空间也能在程序中使用系统调用。明白了这些过程,那么自己新添加系统调用其实也并不是多困难的一件事,新增系统调用号还需要修改syscalls总个数,但强烈不建议自己新增系统调用号,尽量保持与linux kernel主线一致,兼容性更好,所以就不进一步介绍新增流程了。

转自:http://gityuan.com/2016/05/21/syscall/

linux syscall 详解【转】的更多相关文章

  1. Linux Ptrace 详解

    转 https://blog.csdn.net/u012417380/article/details/60470075 Linux Ptrace 详解 2017年03月05日 18:59:58 阅读数 ...

  2. Linux命令详解之—tail命令

    tail命令也是一个非常常用的文件查看类的命令,今天就为大家介绍下Linux tail命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux tail命令主要用来从指定点开始将文 ...

  3. Linux命令详解之—less命令

    Linux下还有一个与more命令非常类似的命令--less命令,相比于more命令,less命令更加灵活强大一些,今天就给大家介绍下Linux下的less命令. 更多Linux命令详情请看:Linu ...

  4. Linux命令详解之—more命令

    Linux more命令同cat命令一样,多用来查看文件内容,本文就为大家介绍下Linux more命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux的more命令类似 ca ...

  5. 【转】linux命令详解:md5sum命令

    [转]linux命令详解:md5sum命令 转自:http://blog.itpub.net/29320885/viewspace-1710218/ 前言 在网络传输.设备之间转存.复制大文件等时,可 ...

  6. Linux命令详解之—cat命令

    cat命令的功能是连接文件或标准输入并打印,今天就为大家介绍下Linux中的cat命令. 更多Linux命令详情请看:Linux命令速查手册 Linux 的cat命令通常用来显示文件内容,也可以用来将 ...

  7. Linux命令详解之—pwd命令

    Linux的pwd命令也是一个非常常用的命令,本文为大家介绍下Linux中pwd命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux pwd命令用于显示工作目录. 执行pwd指 ...

  8. Linux命令详解之–cd命令

    cd命令是linux实际使用当中另一个非常重要的命令,本文就为大家介绍下Linux中cd命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux cd命令用于切换当前工作目录至 d ...

  9. Linux命令详解之–ls命令

    今天开始为大家介绍下Linux中常用的命令,首先给大家介绍下Linux中使用频率最高的命令--ls命令. 更多Linux命令详情请看:Linux命令速查手册 linux ls命令用于显示指定工作目录下 ...

随机推荐

  1. collections 模块之Counter

    Counter字典的子类,用于统计哈希对象 from collections import Counter users = ["body1","body11", ...

  2. Centos 6\7下yum安装rstudio-server\shiny-server

    rstudio-server安装 #wget https://download2.rstudio.org/rstudio-server-rhel-1.1.463-x86_64.rpm #yum ins ...

  3. "Error 0162 - Setup data integrity check failure" after updating BIOS via Thinkvantage

    Start the computer and start pressing F1 and get into set up. In setup press F9 for default settings ...

  4. Web前端框架与移动应用开发第七章

    1.练习1:焦点图切换 html: <!doctype html><html><head> <meta charset="utf-8" / ...

  5. js中得计算问题算式结果拼接成字符串怎么解决

    如题:经常遇到类似问题 一种:自定义的弱类型 var savNum=0; var num=$("#numU").val();//jsp页面获得得值 savNum=parseInt( ...

  6. [C#]一个简易的、轻量级的方法并行执行线程辅助类

      一个简易的.轻量级的方法并行执行线程辅助类 在实际应用中,经常要让多个方法并行执行以节约运行时间,线程就是必不可少的了,而多线程的管理经常又是一件头疼的事情,比如方法并行执行异步的返回问题,方法并 ...

  7. idea 快捷键ctrl+shift+f失效的解决方案

    我是使用微软自带的拼音输入法,右下角图标--右键--设置--按键,拉到最下面 ---快捷键(简体繁体切换),取消勾选 即可

  8. Elasticsearch集群节点配置详解

    注意:如果是在局域网中运行elasticsearch集群也是很简单的,只要cluster.name设置一致,并且机器在同一网段下,启动的es会自动发现对方,组成集群. 2.elasticsearch- ...

  9. Hive基础概念、安装部署与基本使用

    1. Hive简介 1.1 什么是Hive Hives是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能. 1.2 为什么使用Hive ① 直接使用 ...

  10. MySQL的随笔

    数据库引擎 MySQL5.0支持的存储引擎包括MyISAM,InnoDB.MEMORY.MERGE.BDB等等,其中InnoDB和BDB提供事务安全表,其他存储引擎都是非事务安全表. MyISAM M ...