Fuchsia源码分析--系统调用流程 以zx_channel_create为例
最近一直在努力阅读Fuchsia的源码,但是说实话,Fuchsia目前以大量C11风格的代码,读起来还是挺费劲的,就好比当初刚开始读JAVA代码或者Python的代码的时候,希望经过坚持一段时间能够适应这个新的c++代码风格。
一直在想以什么样的方式开始切入阅读整个的Fuchsia源码,尝试过比较多的突破点,最后还是选择下从系统调用入手,先把整个系统调用的流程整理清楚。所以暂时先现在zx_channel_create这样一个系统调用入手,从应用层一直追踪到kernel层,完成整个调用流程的梳理。这篇文章将从以下几个方面来梳理下Fuchsia中的系统调用。
- Fuchsia系统调用的定义
- Fuchsia系统调用定义文件的编译
- Fuchsia系统调用用户空间的调用流程
- Fuchsia系统调用Kernel空间的实现
Fuchsia系统调用的定义
我们能看到当前系统所有的系统调用的定义都放在fuchsia/zircon/vdso/目录中,如
可以看到这个文件目录中有大量文件后缀名为.fidl的文件,我们要跟踪的zx_channel_create就是在channel.fidl中定义的,我们先打开这个文件看以下:
library zx;
using ObjType = uint32;
// TODO(scottmg): ZX_OBJ_TYPE_xyz here.
using HandleOp = uint32;
// TODO(scottmg): ZX_HANDLE_OP_xyz here.
struct HandleInfo {
handle handle;
ObjType type;
rights rights;
uint32 unused;
};
struct ChannelCallArgs {
vector<byte> wr_bytes;
vector<handle> wr_handles;
// TODO(scottmg): mutable_vector_void
vector<byte> rd_bytes;
// TODO(scottmg): mutable_vector_handle
vector<handle> rd_handles;
};
struct HandleDisposition {
HandleOp operation;
handle handle;
ObjType type;
rights rights;
status result;
};
[Transport = "Syscall"]
protocol channel {
/// Create a channel.
channel_create(uint32 options) -> (status status, handle out0, handle out1);
/// Read a message from a channel.
/// Rights: handle must be of type ZX_OBJ_TYPE_CHANNEL and have ZX_RIGHT_READ.
[ArgReorder = "handle, options, bytes, handles, num_bytes, num_handles, actual_bytes, actual_handles",
HandleUnchecked]
channel_read(handle<channel> handle,
uint32 options)
-> (status status,
vector_void_u32size bytes,
vector_handle_u32size handles,
optional_uint32 actual_bytes,
optional_uint32 actual_handles);
........
我们可以看到这个fidl文件中有个比较重要的定义是[Transport = “Syscall”],在这个文件里我们有看到一个channel_create名字的定义。这个貌似跟我们前面说的zx_channel_create还有点区别,先别急,我们再找找。
Fuchsia系统调用定义文件的编译
我们从https://fuchsia.dev/fuchsia-src/reference/syscalls页面能够看到相关的介绍:
Syscall support is generated from //zircon/syscalls. The FIDL files in that directory are first run through fidlc which produces an intermediate format. That intermediate format is consumed by kazoo which produces output for both the kernel and userspace in a variety of languages. This output includes C or C++ headers for both the kernel and userspace, syscall entry points, other language bindings, and so on.
This tool is invoked as a part of the build, rather than checking in its output.
这段话的意思zircon/syscalls目录中的FIDL 文件首先会被fidlc生产一些中间格式,因为这些FIDL 文件中带有[Transport = “Syscall”],是被定义成系统调用的,所以会让kazoo工具会将这些中间格式生产kernel and userspace使用的c/c++头文件,在这里我们先不去计较哪些文件是kazoo工具生产的,也不区计较FIDL 文件是怎么被fidlc工具所使用。这些我们后面再来一一关注。
Fuchsia系统调用用户空间的调用流程
正常在userspace中,我们随便找一个文件,例如src/lib/fsl/io/device_watcher.cc,从这里我们开始追踪。
zx::channel client, server;
if (zx::channel::create(0, &client, &server) != ZX_OK) {
return nullptr;
}
我们看到是通过zx::channel::create来调用的,channel是一个继承自object的类,这个也符合fuchsia是一个以C++实现的kernel的特征,一切都是对象。
我们再找到channel类的实现zircon/system/ulib/zx/channel.cc:
#include <lib/zx/channel.h>
#include <zircon/syscalls.h>
namespace zx {
zx_status_t channel::create(uint32_t flags, channel* endpoint0, channel* endpoint1) {
// Ensure aliasing of both out parameters to the same container
// has a well-defined result, and does not leak.
channel h0;
channel h1;
zx_status_t status =
zx_channel_create(flags, h0.reset_and_get_address(), h1.reset_and_get_address());
endpoint0->reset(h0.release());
endpoint1->reset(h1.release());
return status;
}
} // namespace zx
在这里我们终于第一次看到zx_channel_create这个函数了,惊不惊喜?接下来我们就来看看几个方面的内容:
1)zx_channel_create这个函数是在哪里定义的。
2)zx_channel_create这个函数是在哪里实现的。
2)zx_channel_create这个函数与前面channel.fidl中所定义的channel_create有什么样的关系。
我们先来关注第一个方面:
zx_channel_create函数的定义
我们注意到zircon/system/ulib/zx/channel.cc有引用一个头文件:
#include <zircon/syscalls.h>
这个头文件的位置在于:./zircon/system/public/zircon/syscalls.h
这个头文件是这样写的:
#ifndef SYSROOT_ZIRCON_SYSCALLS_H_
#define SYSROOT_ZIRCON_SYSCALLS_H_
#include <zircon/string_view.h>
#include <zircon/syscalls/object.h>
#include <zircon/syscalls/pci.h>
#include <zircon/syscalls/profile.h>
#include <zircon/syscalls/types.h>
#include <zircon/types.h>
__BEGIN_CDECLS
#define _ZX_SYSCALL_DECL(name, type, attrs, nargs, arglist, prototype) \
extern attrs type zx_##name prototype; \
extern attrs type _zx_##name prototype;
#ifdef __clang__
#define _ZX_SYSCALL_ANNO(attr) __attribute__((attr))
#else
#define _ZX_SYSCALL_ANNO(attr) // Nothing for compilers without the support.
#endif
#include <zircon/syscalls/internal/cdecls.inc>
#undef _ZX_SYSCALL_ANNO
#undef _ZX_SYSCALL_DECL
// Compatibility wrappers for deprecated syscalls also go here, when
// there are any.
// This DEPRECATED interface is replaced by zx_system_get_version_string.
zx_status_t zx_system_get_version(char* version, size_t version_size) __LEAF_FN;
zx_status_t _zx_system_get_version(char* version, size_t version_size) __LEAF_FN;
__END_CDECLS
#endif // SYSROOT_ZIRCON_SYSCALLS_H_
这个里面定义了一个_ZX_SYSCALL_DECL的宏,也引用了一个inc头文件:
#include <zircon/syscalls/internal/cdecls.inc>
这个头文件位置就在于:./out/default.zircon/gen/include/zircon/syscalls/internal/cdecls.inc
一看就是一个编译过程中所生成的文件。
打开这个文件看看,我们有找到跟channel_create相关的内容:
_ZX_SYSCALL_DECL(channel_create, zx_status_t, /* no attributes */, 3,
(options, out0, out1), (
uint32_t options,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out0,
_ZX_SYSCALL_ANNO(acquire_handle("Fuchsia")) zx_handle_t* out1))
_ZX_SYSCALL_DECL这个宏定义在./zircon/system/public/zircon/syscalls.h中,也就在引用#include <zircon/syscalls/internal/cdecls.inc>的前面。所以在编译过程中cdecls.inc文件中的内容会被引用进syscalls.h中,而且通过宏展开,展开之后会形成一个zx_channel_create的函数定义。我们看下_ZX_SYSCALL_DECL的宏定义
#define _ZX_SYSCALL_DECL(name, type, attrs, nargs, arglist, prototype) \
extern attrs type zx_##name prototype; \
extern attrs type _zx_##name prototype;
对着cdecls.inc中的内容,我们看看是不是这里定义了zx_channel_create函数。
我们再来看看zx_channel_create函数是在哪里实现的:
zx_channel_create函数的实现
找这个函数的实现还真的费了一番手脚,主要是这个实现方式比较巧妙,让人拍案叫绝,真的可以仔细把玩,慢慢品味,没想到代码还是这么玩。
首先我们找到./zircon/system/ulib/zircon/syscalls-x86.S这个文件,因为是在x86的环境上跟踪fuchsia的源码,如果是在arm环境上下,可以去看这个目录下的arm版本的实现。
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* define and implement the zircon syscall wrappers for x86-64 */
#include "syscall-entry.h"
#include "zircon-syscall-x86.S"
.text
.cfi_sections .eh_frame, .debug_frame
// The following assembly code converts arguments from the x86-64 SysV
// ABI's function calling conventions to the conventions