专栏首页Linux内核及编程语言底层相关技术研究Linux内核源码分析 - 系统调用 . 续

Linux内核源码分析 - 系统调用 . 续

上一篇文章 Linux内核源码分析 - 系统调用 中分析了linux下的系统调用在kernel space层是如何实现的,现在我们来分析下user space层的实现。

上篇结尾讲到我们可以使用syscall机器指令来调用系统调用,那如何指定系统调用的编号及参数,以及如何获取返回值呢?

详细介绍可以参考这篇文章:

http://man7.org/linux/man-pages/man2/syscall.2.html

简而言之就是通过一定的约定来实现指定系统调用编号和传递参数及返回值。

比如x86_64平台,在执行syscall机器码之前,系统调用的编号要先放到rax寄存器,参数要分别放到rdi、rsi、rdx、r10、r8、r9寄存器中,这样kernel中的代码就会从这些地方取值,然后继续执行逻辑,当kernel部分的逻辑完成之后,结果会再放到rax寄存器中,这样user space的部分就可以从rax寄存器中拿到返回值。

下面我们再来看下上篇文章最后的例子:

# ----------------------------------------------------------------------------------------
# Writes "Hello, World" to the console using only system calls. Runs on 64-bit Linux only.
# To assemble and run:
#
#     gcc -c hello.s && ld hello.o && ./a.out
#
# or
#
#     gcc -nostdlib hello.s && ./a.out
# ----------------------------------------------------------------------------------------

        .global _start

        .text
_start:
        # write(1, message, 13)
        mov     $1, %rax                # system call 1 is write
        mov     $1, %rdi                # file handle 1 is stdout
        mov     $message, %rsi          # address of string to output
        mov     $13, %rdx               # number of bytes
        syscall                         # invoke operating system to do the write

        # exit(0)
        mov     $60, %rax               # system call 60 is exit
        xor     %rdi, %rdi              # we want return code 0
        syscall                         # invoke operating system to exit
message:
        .ascii  "Hello, world\n"

现在就非常明白了吧,比如第一个write系统调用,因为其编号为1,所以先将1放到rax里,之后将标准输出文件描述符到到rdi里,再之后将message地址放到rsi里,再之后将message的长度13放到rdx里,最后调用syscall机器码,这样就会转到对应kernel space部分的代码。

从汇编角度我们已经讲明白了,那在c语言中我们又是如何调用呢?总不能在c中嵌入汇编代码吧?

其实本质上就是在c中嵌入汇编代码,只是不是我们来做,而是glibc来帮我做。

再来看个例子:

#include <unistd.h>

int main(int argc, char *argv[]) {
  write(STDOUT_FILENO, "Hello, World\n", 13);
  return 60;
}

这个例子就是上面汇编代码对应的c实现,编译执行之后也是会输出同样的内容。

注意,这里的write并不是kernel内部的系统调用write,而是glibc中的一个wrapper,这个wrapper里面再帮我们调用真正的系统调用write。

我们再来看下对应的glibc的代码:

// sysdeps/unix/sysv/linux/write.c
/* Write NBYTES of BUF to FD.  Return the number written, or -1.  */
ssize_t
__libc_write (int fd, const void *buf, size_t nbytes)
{
  return SYSCALL_CANCEL (write, fd, buf, nbytes);
}
...
weak_alias (__libc_write, write)
...

这里需要注意的是,write方法其实是__lib_write的一个weak alias,当我们调用write时,其实相当于我们在调用__lib_write。

继续看下SYSCALL_CANCEL宏:

// sysdeps/unix/sysdep.h
#define SYSCALL_CANCEL(...) \
  ({                                                                         \
    long int sc_ret;                                                         \
    if (SINGLE_THREAD_P)                                                     \
      sc_ret = INLINE_SYSCALL_CALL (__VA_ARGS__);                            \
    else                                                                     \
      {
        ...                                                                  \
      }                                                                      \
    sc_ret;                                                                  \
  })

这个宏里面又调用了INLINE_SYSCALL_CALL,INLINE_SYSCALL_CALL里又调用了很多其他的宏,这里就不一一展开了,有兴趣的朋友可以留言,我们再一起交流。

最终,会调用下面的宏。

// sysdeps/unix/sysv/linux/x86_64/sysdep.h
#define internal_syscall3(number, err, arg1, arg2, arg3)                \
({                                                                      \
    unsigned long int resultvar;                                        \
    TYPEFY (arg3, __arg3) = ARGIFY (arg3);                              \
    TYPEFY (arg2, __arg2) = ARGIFY (arg2);                              \
    TYPEFY (arg1, __arg1) = ARGIFY (arg1);                              \
    register TYPEFY (arg3, _a3) asm ("rdx") = __arg3;                   \
    register TYPEFY (arg2, _a2) asm ("rsi") = __arg2;                   \
    register TYPEFY (arg1, _a1) asm ("rdi") = __arg1;                   \
    asm volatile (                                                      \
    "syscall\n\t"                                                       \
    : "=a" (resultvar)                                                  \
    : "0" (number), "r" (_a1), "r" (_a2), "r" (_a3)                     \
    : "memory", REGISTERS_CLOBBERED_BY_SYSCALL);                        \
    (long int) resultvar;                                               \
})

是不是很熟悉,这就是我们上面手写的汇编代码啊。

到此,整个流程就全部通了。

我们在写c时(其他语言也一样),调用的其实是glibc里的wrapper,glibc里的wrapper再帮我们调用对应的系统调用,之后再将结果从rax中取出,返回给我们,这样我们使用起来就非常方便了。

完。

本文分享自微信公众号 - Linux内核及JVM底层相关技术研究(ytcode),作者:wangyuntao

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • linux内核启动流程分析 - efi_stub_entry

    接上一篇文章 linux内核启动流程分析 - efi_pe_entry,我们继续看efi_stub_entry函数。

    wangyuntao
  • Linux内核源码分析 - 系统调用

    该宏的参数中,x为3,name为_write,...代表的__VA_ARGS__为unsigned int, fd, const char __user *, ...

    wangyuntao
  • ssh技巧之端口转发

    1. 你有台服务器,上面开着个数据库,但该数据库不对外开放,只能通过ssh登录到服务器上才能对其操作,但有时候,你想在本地直接访问该数据库来做些测试,怎么办?

    wangyuntao
  • 用Python调用百度OCR接口实例

    本文主要针对Python开发者,描述百度文字识别接口服务的相关技术内容。OCR接口提供了自然场景下整图文字检测、定位、识别等功能。文字识别的结果可以用于翻译、搜...

    机器学习AI算法工程
  • python pyqt5 QDialog

    QDialog/QMessageBox,QFileDialog,QFontDialog,QInputDialog

    用户5760343
  • VPF:适用于 Python 的开源视频处理框架,加速视频任务、提高 GPU 利用率

    同时,由于 Python 绑定下的 C ++代码,它使开发者可以在数十行代码中实现较高的 GPU 利用率。解码后的视频帧以 NumPy 数组或 CUDA 设备指...

    AI研习社
  • 跨境电子商务( Cross-Border Electronic Commerce )是什么?

    跨境电子商务是基于网络发展起来的,网络空间相对于物理空间来说是一个新空间,是一个由网址和密码组成的虚拟但客观存在的世界。网络空间独特的价值标准和行为模式深刻地影...

    一个会写诗的程序员
  • JavaScript编程精解(二)

    当启用了严格模式(strict mode)后,JS就会在执行代码时变得更为严格。只需在文件或函数顶部放置字符串“use strict”就可以启用严格模式了。

    硬核项目经理
  • 为 hexo 博客添加本地搜索功能

    使用 hexo-generator-search 的 Hexo 插件来做内容搜索,安装命令如下:

    好好学java
  • python中常⽤的excel模块库

    Tablib是MIT许可格式⽆关的表格数据集库,⽤Python编写。它允许您导⼊,导出和操作表格数据集。⾼级功能包括隔离,动态列,标签和过滤以及⽆缝格式导⼊和导...

    小菠萝测试笔记

扫码关注云+社区

领取腾讯云代金券