LevelDB 源码阅读二:操作系统接口封装

本文继续介绍 LevelDB 源码,重点是 LevelDB 中对操作系统相关接口的封装,也即 Env 接口及相关实现类。本文涉及的相关源码路径如下:

  • include/leveldb/env.h
  • util/env.cc
  • util/posix_logger.h
  • util/env_posix.cc

1 env.h

include/leveldb/env.h 源文件中,定义了若干接口,用于对操作系统提供的功能进行接口的统一。这一点从源代码的注释可以看出:

1
2
3
4
5
6
7
// An Env is an interface used by the leveldb implementation to access
// operating system functionality like the filesystem etc. Callers
// may wish to provide a custom Env object when opening a database to
// get fine gain control; e.g., to rate limit file system operations.
//
// All Env implementations are safe for concurrent access from
// multiple threads without any external synchronization.

在源文件中,最重要的类是虚基类 Env ,它声明了提供以下功能的函数:

  • 创建不同类型的文件
  • 判断文件是否存在
  • 获取目录的所有文件名
  • 删除文件
  • 创建/删除目录
  • 获取文件大小
  • 重命名文件
  • 获取/释放文件锁
  • 线程/调度相关操作
  • 创建日志文件
  • ……

除了 Env 类,该源文件实际上还声明了一些表示不同类型文件的类,如:

  • SequentialFile:用于顺序读取文件的文件抽象。
  • RandomAccessFile:用于随机读取文件内容的文件抽象。
  • WritableFile:用于顺序写入的文件抽象。实现必须提供缓冲,因为调用者可以一次将小片段附加到文件中。
  • Logger:用于写入日志消息的接口。

不同类型的文件提供了不同的接口,这里不详细解释,后面会再次提及。

另外,还有用于多进程并发访问的文件锁 FileLock

1
2
3
4
5
6
7
8
9
10
// Identifies a locked file.
class LEVELDB_EXPORT FileLock {
public:
FileLock() = default;

FileLock(const FileLock&) = delete;
FileLock& operator=(const FileLock&) = delete;

virtual ~FileLock();
};

此外,该头文件中还声明了一些实用函数,包括:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Log the specified data to *info_log if info_log is non-null.
void Log(Logger* info_log, const char* format, ...)
#if defined(__GNUC__) || defined(__clang__)
__attribute__((__format__(__printf__, 2, 3)))
#endif
;

// A utility routine: write "data" to the named file.
LEVELDB_EXPORT Status WriteStringToFile(Env* env, const Slice& data,
const std::string& fname);

// A utility routine: read contents of named file into *data
LEVELDB_EXPORT Status ReadFileToString(Env* env, const std::string& fname,
std::string* data);

它们分别用于写日志、写数据到文件和读文件到字符串。

最后,还有一个对 Env 类进行包装的 EnvWrapper 类,提供给仅需要重写 Env 部分接口的用户使用。其数据成员只包括一个执行某个 Env 实现类的指针,部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// An implementation of Env that forwards all calls to another Env.
// May be useful to clients who wish to override just part of the
// functionality of another Env.
class LEVELDB_EXPORT EnvWrapper : public Env {
public:
// Initialize an EnvWrapper that delegates all calls to *t.
explicit EnvWrapper(Env* t) : target_(t) {}
virtual ~EnvWrapper();

// Return the target to which this Env forwards all calls.
Env* target() const { return target_; }

// The following text is boilerplate that forwards all methods to target().
Status NewSequentialFile(const std::string& f, SequentialFile** r) override {
return target_->NewSequentialFile(f, r);
}
// 其他接口都是类似的逻辑,调用 target_ 成员的对应函数

private:
Env* target_;
};

2. env.cc

env.cc 具体路径为 util/env.cc,其中对 env.h 源文件中部分声明(一些类的构造函数、析构函数、实用函数等)提供了定义。例如,前文提到的用于写日志、写数据到文件和读文件到字符串的函数,在此源文件中进行了实现,部分代码如下:

1
2
3
4
5
6
7
8
void Log(Logger* info_log, const char* format, ...) {
if (info_log != nullptr) {
std::va_list ap;
va_start(ap, format);
info_log->Logv(format, ap);
va_end(ap);
}
}

3. posix_logger.h

此源文件位于 util/posix_logger.h,用于实现 Posix 环境下的 Logger 接口。具体而言,该源文件中声明并实现了继承自 Logger 接口的 PosixLogger 类。文件头的注释如下:

1
2
// Logger implementation that can be shared by all environments
// where enough posix functionality is available.

PosixLogger 类的数据成员为一个 std::FILE 类型的指针,通过调用 std::FILE 相关函数,实现了 Logger 接口的 Logv 函数。日志记录的信息包括:

  • 时间戳
  • 线程 id
  • 日志信息

一个示例输出如下:

1
2024/06/03-17:03:49.672877 140472843178944 Just for test log

4. env_posix.cc

该源文件路径为 util/env_posix.cc,是 Posix 环境下对 Env 相关接口的实现。下面我们来看其涉及到的相关类或函数。

4.1 Limiter 类

Limiter 是用于控制资源(只读文件描述符和 mmap 文件)使用数量的辅助类,以防止文件描述符或虚拟内存用尽,如注释所示:

1
2
3
4
// Helper class to limit resource usage to avoid exhaustion.
// Currently used to limit read-only file descriptors and mmap file usage
// so that we do not run out of file descriptors or virtual memory, or run into
// kernel performance problems for very large databases.

该类有一个成员变量,表示允许的最大资源数。有两个返回值类型为 bool 的成员函数 AcquireRelease 分别表示获取和释放资源。

4.2 相关文件实现

前文提到,env.h 头文件中声明了若干表示不同类型文件的接口。这一小节介绍 Posix 环境下对这些接口的实现类。涉及到的具体实现类有:

  • PosixSequentialFile:使用 read() 实现对文件的顺序读访问。其数据成员包括文件描述符和文件名。其使用 Linux 下的系统调用 ::read(fd_, scratch, n) 实现 SequentialFile 定义的接口。

  • PosixRandomAccessFile:使用 pread() 实现对文件的随机读访问。其包含的数据成员和实现的接口如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class PosixRandomAccessFile final : public RandomAccessFile {
    public:
    // The new instance takes ownership of |fd|. |fd_limiter| must outlive this
    // instance, and will be used to determine if .
    PosixRandomAccessFile(std::string filename, int fd, Limiter* fd_limiter)
    : has_permanent_fd_(fd_limiter->Acquire()),
    fd_(has_permanent_fd_ ? fd : -1),
    fd_limiter_(fd_limiter),
    filename_(std::move(filename)) {
    // 省略
    }

    ~PosixRandomAccessFile() override {
    // 省略
    }

    Status Read(uint64_t offset, size_t n, Slice* result,
    char* scratch) const override {
    // 省略
    }

    private:
    const bool has_permanent_fd_; // If false, the file is opened on every read.
    const int fd_; // -1 if has_permanent_fd_ is false.
    Limiter* const fd_limiter_;
    const std::string filename_;
    };

    Read 接口中,调用 Linux 系统调用 ::pread(fd, scratch, n, static_cast<off_t>(offset)) 完成对数据的随机读取。

  • PosixMmapReadableFile:使用 mmap() 实现对文件的随机读访问。该类也是对 RandomAccessFile 接口的实现。其数据成员和函数如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class PosixMmapReadableFile final : public RandomAccessFile {
    public:
    // mmap_base[0, length-1] points to the memory-mapped contents of the file. It
    // must be the result of a successful call to mmap(). This instances takes
    // over the ownership of the region.
    //
    // |mmap_limiter| must outlive this instance. The caller must have already
    // acquired the right to use one mmap region, which will be released when this
    // instance is destroyed.
    PosixMmapReadableFile(std::string filename, char* mmap_base, size_t length,
    Limiter* mmap_limiter)
    : mmap_base_(mmap_base),
    length_(length),
    mmap_limiter_(mmap_limiter),
    filename_(std::move(filename)) {}

    ~PosixMmapReadableFile() override {
    // 省略 需释放 mmap 映射的内存
    }

    Status Read(uint64_t offset, size_t n, Slice* result,
    char* scratch) const override {
    // 省略
    }

    private:
    char* const mmap_base_; // 指向通过 mmap() 映射的文件内容的基地址
    const size_t length_; // 映射区域的长度
    Limiter* const mmap_limiter_;
    const std::string filename_;
    };
  • PosixWritableFile:实现了 WritableFile 可写文件接口。其相关数据成员和函数定义如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class PosixWritableFile final : public WritableFile {
    public:
    PosixWritableFile(std::string filename, int fd)
    : pos_(0),
    fd_(fd),
    is_manifest_(IsManifest(filename)),
    filename_(std::move(filename)),
    dirname_(Dirname(filename_)) {}

    ~PosixWritableFile() override {
    // 省略
    }

    Status Append(const Slice& data) override {
    // 省略
    }

    Status Close() override {
    // 省略
    }

    Status Flush() override { return FlushBuffer(); }

    Status Sync() override {
    // 省略
    }

    private:
    // 省略部分成员函数

    // buf_[0, pos_ - 1] contains data to be written to fd_.
    char buf_[kWritableFileBufferSize];
    size_t pos_;
    int fd_;

    const bool is_manifest_; // True if the file's name starts with MANIFEST.
    const std::string filename_;
    const std::string dirname_; // The directory of filename_.
    }

    这个类在内部调用 Linux 的 readwrite 等系统调用,实现了可写文件接口。

4.3 PosixFileLock 类

这一类实现了 FileLock 接口,其代码比较简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Instances are thread-safe because they are immutable.
class PosixFileLock : public FileLock {
public:
PosixFileLock(int fd, std::string filename)
: fd_(fd), filename_(std::move(filename)) {}

int fd() const { return fd_; }
const std::string& filename() const { return filename_; }

private:
const int fd_;
const std::string filename_;
};

4.4 PosixLockTable 类

PosixLockTable 类也非常简单:使用一个 set 追踪文件释放被 LockFile 加锁,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Tracks the files locked by PosixEnv::LockFile().
//
// We maintain a separate set instead of relying on fcntl(F_SETLK) because
// fcntl(F_SETLK) does not provide any protection against multiple uses from the
// same process.
//
// Instances are thread-safe because all member data is guarded by a mutex.
class PosixLockTable {
public:
bool Insert(const std::string& fname) LOCKS_EXCLUDED(mu_) {
mu_.Lock();
bool succeeded = locked_files_.insert(fname).second;
mu_.Unlock();
return succeeded;
}
void Remove(const std::string& fname) LOCKS_EXCLUDED(mu_) {
mu_.Lock();
locked_files_.erase(fname);
mu_.Unlock();
}

private:
port::Mutex mu_;
std::set<std::string> locked_files_ GUARDED_BY(mu_);
};

4.5 PosixEnv 类

这个类实现了 Env 接口。部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
class PosixEnv : public Env {
public:
PosixEnv();
~PosixEnv() override {
static const char msg[] =
"PosixEnv singleton destroyed. Unsupported behavior!\n";
std::fwrite(msg, 1, sizeof(msg), stderr);
std::abort(); // 析构函数有定义,但是不允许调用
}

Status NewSequentialFile(const std::string& filename,
SequentialFile** result) override {
int fd = ::open(filename.c_str(), O_RDONLY | kOpenBaseFlags); // 打开文件
if (fd < 0) {
*result = nullptr;
return PosixError(filename, errno);
}

*result = new PosixSequentialFile(filename, fd); // 构造 PosixSequentialFile
return Status::OK();
}

Status NewRandomAccessFile(const std::string& filename,
RandomAccessFile** result) override {
// 省略
}

Status NewWritableFile(const std::string& filename,
WritableFile** result) override {
// 省略
}

Status NewAppendableFile(const std::string& filename,
WritableFile** result) override {
// 省略
}

bool FileExists(const std::string& filename) override {
return ::access(filename.c_str(), F_OK) == 0;
}

Status GetChildren(const std::string& directory_path,
std::vector<std::string>* result) override {
// 省略
}

Status RemoveFile(const std::string& filename) override {
// 省略
}

Status CreateDir(const std::string& dirname) override {
// 省略
}

Status RemoveDir(const std::string& dirname) override {
if (::rmdir(dirname.c_str()) != 0) {
return PosixError(dirname, errno);
}
return Status::OK();
}

Status GetFileSize(const std::string& filename, uint64_t* size) override {
// 省略
}

Status RenameFile(const std::string& from, const std::string& to) override {
// 省略
}

Status LockFile(const std::string& filename, FileLock** lock) override {
*lock = nullptr;

int fd = ::open(filename.c_str(), O_RDWR | O_CREAT | kOpenBaseFlags, 0644);
if (fd < 0) {
return PosixError(filename, errno);
}

if (!locks_.Insert(filename)) {
::close(fd);
return Status::IOError("lock " + filename, "already held by process");
}

if (LockOrUnlock(fd, true) == -1) {
int lock_errno = errno;
::close(fd);
locks_.Remove(filename);
return PosixError("lock " + filename, lock_errno);
}

*lock = new PosixFileLock(fd, filename);
return Status::OK();
}

Status UnlockFile(FileLock* lock) override {
// 省略
}

void Schedule(void (*background_work_function)(void* background_work_arg),
void* background_work_arg) override;

void StartThread(void (*thread_main)(void* thread_main_arg),
void* thread_main_arg) override {
std::thread new_thread(thread_main, thread_main_arg);
new_thread.detach();
}

Status GetTestDirectory(std::string* result) override {
// 省略
}

Status NewLogger(const std::string& filename, Logger** result) override {
// 省略
}

uint64_t NowMicros() override {
// 省略
}

void SleepForMicroseconds(int micros) override {
std::this_thread::sleep_for(std::chrono::microseconds(micros));
}

private:
// 省略部分私有成员函数

port::Mutex background_work_mutex_;
port::CondVar background_work_cv_ GUARDED_BY(background_work_mutex_);
bool started_background_thread_ GUARDED_BY(background_work_mutex_);

std::queue<BackgroundWorkItem> background_work_queue_
GUARDED_BY(background_work_mutex_); // 后台任务队列

PosixLockTable locks_; // Thread-safe.
Limiter mmap_limiter_; // Thread-safe.
Limiter fd_limiter_; // Thread-safe.
}

PosixEnv 类中的 NewxxxFile 函数中,调用前文介绍的 PosixxxxFile 创建相应类型的文件。其他的文件/目录相关操作,通过调用 Linux 系统提供的接口实现。

需要注意的是 LockFileUnlockFile 函数中,调用了 LockOrUnlock 函数进行加锁或解锁,函数定义如下:

1
2
3
4
5
6
7
8
9
10
int LockOrUnlock(int fd, bool lock) {
errno = 0;
struct ::flock file_lock_info;
std::memset(&file_lock_info, 0, sizeof(file_lock_info));
file_lock_info.l_type = (lock ? F_WRLCK : F_UNLCK); // 加锁 (F_WRLCK) 或解锁 (F_UNLCK)
file_lock_info.l_whence = SEEK_SET;
file_lock_info.l_start = 0;
file_lock_info.l_len = 0; // Lock/unlock entire file.
return ::fcntl(fd, F_SETLK, &file_lock_info);
}

其中,通过 fcntl 系统调用,设置文件锁。struct ::flock 成员的相关设置含义如下:

  • l_whence 设置为 SEEK_SET,表示锁操作的起始位置为文件的开头。
  • l_start 设置为 0,表示从文件开头位置开始加锁或解锁。
  • l_len 设置为 0,表示锁定整个文件。

PosixEnv 类中,还有一些相关成员函数用于执行后台任务,相关函数包括:

  • void PosixEnv::BackgroundThreadMain()
  • void PosixEnv::Schedule(void (*background_work_function)(void* background_work_arg), void* background_work_arg)

具体代码这里不详细介绍。

4.6 SingletonEnv 单例模板类

对操作系统环境的封装在整个项目中仅需要一个对象,因此这里利用单例模式来实现,利用模板类包装 Env 实现类的唯一对象。相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Wraps an Env instance whose destructor is never created.
//
// Intended usage:
// using PlatformSingletonEnv = SingletonEnv<PlatformEnv>;
// void ConfigurePosixEnv(int param) {
// PlatformSingletonEnv::AssertEnvNotInitialized();
// // set global configuration flags.
// }
// Env* Env::Default() {
// static PlatformSingletonEnv default_env;
// return default_env.env();
// }
template <typename EnvType>
class SingletonEnv {
public:
SingletonEnv() {
#if !defined(NDEBUG)
env_initialized_.store(true, std::memory_order_relaxed);
#endif // !defined(NDEBUG)
static_assert(sizeof(env_storage_) >= sizeof(EnvType),
"env_storage_ will not fit the Env");
static_assert(alignof(decltype(env_storage_)) >= alignof(EnvType),
"env_storage_ does not meet the Env's alignment needs");
new (&env_storage_) EnvType(); // inplace new,原地调用构造函数
}
~SingletonEnv() = default;

SingletonEnv(const SingletonEnv&) = delete;
SingletonEnv& operator=(const SingletonEnv&) = delete; // 显式不允许拷贝

Env* env() { return reinterpret_cast<Env*>(&env_storage_); } // 强制类型转换获取 Env 对象

static void AssertEnvNotInitialized() {
#if !defined(NDEBUG)
assert(!env_initialized_.load(std::memory_order_relaxed));
#endif // !defined(NDEBUG)
}

private:
typename std::aligned_storage<sizeof(EnvType), alignof(EnvType)>::type
env_storage_; // 存储 Env 实现类对象的内存空间
#if !defined(NDEBUG)
static std::atomic<bool> env_initialized_;
#endif // !defined(NDEBUG)
};

利用这个单例模板类,实现了 env.h 头文件中的静态接口,获取 LevelDB 默认的环境对象:

1
2
3
4
Env* Env::Default() {
static PosixDefaultEnv env_container;
return env_container.env();
}

5. 总结

本文介绍了 LevelDB 对操作系统接口提供封装涉及的相关代码。通过 env.h 头文件定义了相关接口,在 env_posix.cc 源文件中,实现类对 Posix 系统的 API 进行封装,转换为 env.h 中定义的接口,供 LevelDB 其余部分使用。为了包装系统环境的一致性,使用单例模式,保证运行时仅有一个 Env 依赖。


LevelDB 源码阅读二:操作系统接口封装
https://arcsin2.cloud/2024/06/03/LevelDB-源码阅读二:操作系统接口封装/
作者
arcsin2
发布于
2024年6月3日
许可协议