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 |
|
在源文件中,最重要的类是虚基类 Env
,它声明了提供以下功能的函数:
- 创建不同类型的文件
- 判断文件是否存在
- 获取目录的所有文件名
- 删除文件
- 创建/删除目录
- 获取文件大小
- 重命名文件
- 获取/释放文件锁
- 线程/调度相关操作
- 创建日志文件
- ……
除了 Env
类,该源文件实际上还声明了一些表示不同类型文件的类,如:
SequentialFile
:用于顺序读取文件的文件抽象。RandomAccessFile
:用于随机读取文件内容的文件抽象。WritableFile
:用于顺序写入的文件抽象。实现必须提供缓冲,因为调用者可以一次将小片段附加到文件中。Logger
:用于写入日志消息的接口。
不同类型的文件提供了不同的接口,这里不详细解释,后面会再次提及。
另外,还有用于多进程并发访问的文件锁 FileLock
:
1 |
|
此外,该头文件中还声明了一些实用函数,包括:
1 |
|
它们分别用于写日志、写数据到文件和读文件到字符串。
最后,还有一个对 Env
类进行包装的
EnvWrapper
类,提供给仅需要重写 Env
部分接口的用户使用。其数据成员只包括一个执行某个 Env
实现类的指针,部分代码如下:
1 |
|
2. env.cc
env.cc
具体路径为 util/env.cc
,其中对
env.h
源文件中部分声明(一些类的构造函数、析构函数、实用函数等)提供了定义。例如,前文提到的用于写日志、写数据到文件和读文件到字符串的函数,在此源文件中进行了实现,部分代码如下:
1 |
|
3. posix_logger.h
此源文件位于 util/posix_logger.h
,用于实现 Posix
环境下的 Logger
接口。具体而言,该源文件中声明并实现了继承自 Logger
接口的
PosixLogger
类。文件头的注释如下:
1 |
|
PosixLogger
类的数据成员为一个 std::FILE
类型的指针,通过调用 std::FILE
相关函数,实现了
Logger
接口的 Logv
函数。日志记录的信息包括:
- 时间戳
- 线程
id
- 日志信息
一个示例输出如下:
1 |
|
4. env_posix.cc
该源文件路径为 util/env_posix.cc
,是 Posix 环境下对
Env
相关接口的实现。下面我们来看其涉及到的相关类或函数。
4.1 Limiter 类
Limiter
是用于控制资源(只读文件描述符和
mmap
文件)使用数量的辅助类,以防止文件描述符或虚拟内存用尽,如注释所示:
1 |
|
该类有一个成员变量,表示允许的最大资源数。有两个返回值类型为
bool
的成员函数 Acquire
和
Release
分别表示获取和释放资源。
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
27class 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
31class 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
39class 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 的
read
、write
等系统调用,实现了可写文件接口。
4.3 PosixFileLock 类
这一类实现了 FileLock
接口,其代码比较简单:
1 |
|
4.4 PosixLockTable 类
PosixLockTable
类也非常简单:使用一个 set
追踪文件释放被 LockFile
加锁,代码如下:
1 |
|
4.5 PosixEnv 类
这个类实现了 Env
接口。部分代码如下:
1 |
|
在 PosixEnv
类中的 NewxxxFile
函数中,调用前文介绍的 PosixxxxFile
创建相应类型的文件。其他的文件/目录相关操作,通过调用 Linux
系统提供的接口实现。
需要注意的是 LockFile
和 UnlockFile
函数中,调用了 LockOrUnlock
函数进行加锁或解锁,函数定义如下:
1 |
|
其中,通过 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 |
|
利用这个单例模板类,实现了 env.h
头文件中的静态接口,获取 LevelDB 默认的环境对象:
1 |
|
5. 总结
本文介绍了 LevelDB 对操作系统接口提供封装涉及的相关代码。通过
env.h
头文件定义了相关接口,在 env_posix.cc
源文件中,实现类对 Posix 系统的 API 进行封装,转换为 env.h
中定义的接口,供 LevelDB
其余部分使用。为了包装系统环境的一致性,使用单例模式,保证运行时仅有一个
Env
依赖。