自学内容网 自学内容网

Kraken代码阅读(一)

源码DerrickWood/kraken: Kraken taxonomic sequence classification system (github.com)

文件quickfile.hpp

#ifndef QUICKFILE_HPP
#define QUICKFILE_HPP

#include "kraken_headers.hpp"

namespace kraken {
  class QuickFile {
    public:

    QuickFile();
    QuickFile(std::string filename, std::string mode="r", size_t size=0);
    ~QuickFile();
    void open_file(std::string filename, std::string mode="r", size_t size=0);
    char *ptr();
    size_t size();
    void load_file();
    void sync_file();
    void close_file();

    protected:

    bool valid;
    int fd;
    char *fptr;
    size_t filesize;
  };
}

#endif

文件quickfile.cpp

1.构造函数QuickFile()

#include "kraken_headers.hpp"
#include "quickfile.hpp"

using std::string;

namespace kraken {

QuickFile::QuickFile() {
  valid = false;
  fptr = NULL;
  filesize = 0;
  fd = -1;
}

有效位valid置无效,fptr指向文件内容的指针置空,filesize文件大小置0,fd文件描述符,用于文件操作,置-1。

QuickFile::QuickFile(string filename_str, string mode, size_t size) {
  open_file(filename_str, mode, size);
}

string filename_str: 要打开的文件的名称。
string mode: 打开文件的模式,可以是"r"(只读)、“w”(读写,如果文件不存在则创建,如果存在则截断)、“rw”(读写,如果文件不存在则创建)。
size_t size: 文件的大小,当以写入模式打开文件并且文件需要创建时使用。

2.open_file函数

const char *filename = filename_str.c_str();

将filename_str转换为C风格字符串,以便可以使用在后续系统调用中。

int o_flags = mode == "w"
              ? O_RDWR | O_CREAT | O_TRUNC
              : mode == "r" ? O_RDONLY : O_RDWR;

根据mode参数设置打开文件的标志(o_flags)。如果模式是"r",则设置O_RDONLY表示只读;如果模式是"w",则设置O_RDWR | O_CREAT | O_TRUNC表示读写、创建和截断;否则默认为O_RDWR表示读写。

int m_flags = mode == "r" ? MAP_PRIVATE : MAP_SHARED;

根据mode参数设置内存映射的标志(m_flags)。如果模式是"r",则使用MAP_PRIVATE表示私有映射,否则使用MAP_SHARED表示共享映射。

fd = open(filename, o_flags, 0666);

尝试打开文件,0666是文件权限,表示所有用户都有读写权限。

if (fd < 0 && mode == "rw" && errno == ENOENT) {
  o_flags |= O_CREAT;
  fd = open(filename, o_flags, 0666);
}

如果以"rw"模式打开文件并且文件不存在(errno == ENOENT),则再次尝试打开文件,这次包括O_CREAT标志以创建文件。

if (fd < 0)
  err(EX_OSERR, "unable to open %s", filename);

如果文件打开失败,则打印错误消息并退出。

if (o_flags & O_CREAT) {
    if (lseek(fd, size - 1, SEEK_SET) < 0)
      err(EX_OSERR, "unable to lseek (%s)", filename);
    if (write(fd, "", 1) < 0)
      err(EX_OSERR, "write error (%s)", filename);
    filesize = size;
  }
  else {
    struct stat sb;
    if (fstat(fd, &sb) < 0)
      err(EX_OSERR, "unable to fstat %s", filename);
    filesize = sb.st_size;
  }

lseek函数用于设置文件描述符fd的当前位置。这里,它试图将位置设置为文件大小的最后一个字节(size - 1)。
SEEK_SET表示相对于文件的开始位置进行偏移。
如果lseek调用失败(返回值小于0),则打印错误消息并退出。

write函数尝试在当前文件位置写入一个空字符串(只有一个空字符)。
这实际上是在文件中创建一个占位符,以确保文件至少有size个字节。
如果写入失败(返回值小于0),则打印错误消息并退出。
设置filesize变量为用户指定的size,表示文件应该有的大小。

定义一个stat结构体变量sb,用于存储文件的状态信息。

fstat函数获取文件描述符fd引用的文件的状态信息,并将它们存储在sb中。
如果fstat调用失败(返回值小于0),则打印错误消息并退出。

设置filesize变量为sb.st_size,这是通过fstat获得的文件的实际大小。

fptr = (char *)mmap(0, filesize, PROT_READ | PROT_WRITE, m_flags, fd, 0);

使用mmap将文件映射到内存中,以便可以像访问普通内存一样访问文件内容。

madvise(fptr, filesize, MADV_WILLNEED);

使用madvise系统调用,建议内核预读映射的文件区域,因为预期会需要这些数据。

if (fptr == MAP_FAILED)
  err(EX_OSERR, "unable to mmap %s", filename);

如果内存映射失败,则打印错误消息并退出。

valid = true;

设置valid标志,表示文件已成功打开并映射。

open_file函数目的是为了打开一个文件,并根据提供的参数以特定的模式进行操作。

3.load_file函数

它旨在将文件内容加载到内存中,并尝试使用内存锁(mlock)来确保文件内容被锁定在物理内存中。

if(mlock(fptr, filesize) != 0) {

这里,mlock函数尝试将文件指针fptr指向的内存区域(大小为filesize)锁定在物理内存中。如果mlock调用失败(返回非零值),则执行接下来的代码块。

int thread_ct = 1;
int thread = 0;
#ifdef _OPENMP
int old_thread_ct = omp_get_max_threads();
if (old_thread_ct > 4)
  omp_set_num_threads(4);
thread_ct = omp_get_max_threads();
#endif

这部分代码设置了线程的数量。如果没有定义_OPENMP(即OpenMP没有被启用),则默认使用一个线程。如果启用了OpenMP,则尝试获取当前的最大线程数,并将其限制为最多4个线程,然后将thread_ct设置为新的最大线程数。

size_t page_size = getpagesize();

getpagesize函数返回系统的页面大小,这对于内存对齐和操作很有用。

char buf[thread_ct][page_size];

为每个线程分配一个缓冲区,大小等于页面大小。

#ifdef _OPENMP
#pragma omp parallel
#endif
{
  #ifdef _OPENMP
  thread = omp_get_thread_num();
  #endif
  ...
}

如果启用了OpenMP,这段代码将启动一个并行区域,其中每个线程将执行接下来的代码块。

#ifdef _OPENMP
#pragma omp for schedule(dynamic)
#endif
for (size_t pos = 0; pos < filesize; pos += page_size) {
  size_t this_page_size = filesize - pos;
  if (this_page_size > page_size)
    this_page_size = page_size;
  memcpy(buf[thread], fptr + pos, this_page_size);
}

这是一个并行循环,它将文件分割成多个页面大小块,并使用memcpy将每个块复制到相应线程的缓冲区中。schedule(dynamic)表示循环迭代将动态分配给线程。

#ifdef _OPENMP
omp_set_num_threads(old_thread_ct);
#endif

如果之前改变了线程数,现在将其恢复到原始值。

4.ptr函数

char * QuickFile::ptr() {
  return valid ? fptr : NULL;
}

这个函数的作用是,如果QuickFile对象处于有效状态,则提供对文件内容的直接访问;如果对象无效,则返回NULL,表示没有可用的文件内容指针。

5.size函数

size_t QuickFile::size() {
  return valid ? filesize : 0;
}

这个函数的作用是,如果QuickFile对象处于有效状态,则返回文件的实际大小;如果对象无效,则返回0。

6.sync_file函数

void QuickFile::sync_file() {
  msync(fptr, filesize, MS_SYNC);
}

msync(fptr, filesize, MS_SYNC); 是对msync系统调用的调用,它执行以下操作:
fptr:这是一个指向内存映射文件区域的指针。fptr应该是之前通过mmap或类似函数映射文件到内存的指针。
filesize:这是要同步的内存区域的大小,即文件的大小。
MS_SYNC:这个标志告诉msync将所有脏页(即内存中被修改但尚未写回磁盘的页面)同步到磁盘。这意味着调用将阻塞,直到所有数据都被写入磁盘。

msync用于将内存映射的文件区域的数据写回到对应的磁盘文件中。这个调用确保了内存中的数据与磁盘上的数据保持一致。

7.析构函数和close_file函数

QuickFile::~QuickFile() {
  close_file();
}

调用关闭文件的函数。

void QuickFile::close_file() {
  if (! valid)
    return;

这行代码检查 valid 成员变量。

sync_file();

这行代码调用 sync_file 成员函数,其目的是将内存中所有修改过的数据同步到磁盘。这样做是为了确保所有对文件的更改都被保存,即使程序崩溃也不会丢失数据。

munmap(fptr, filesize);

munmap 是一个系统调用,用于解除之前通过 mmap 映射到进程地址空间的内存映射。fptr 是映射区域的起始地址,filesize 是映射区域的大小。解除映射后,内存中的数据将不再与文件内容相关联。

close(fd);

close 是一个系统调用,用于关闭文件描述符 fd。文件描述符是操作系统用来表示打开文件的一个整数。关闭文件描述符后,该描述符不再指向任何文件,并且可以重新用于其他文件。

valid = false;

最后,将 valid 设置为 false,表示 QuickFile 对象不再与任何打开的文件关联。

8.readable_fs函数

char* readable_fs(double size/*in bytes*/, char *buf) {
    int i = 0;

double size:文件的大小,以字节为单位。
char *buf:一个字符数组,用于存储转换后的字符串。

const char* units[] = {"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};

这是一个字符串数组,包含了从字节到尧字节的各种存储单位。

while (size > 1024) {
  size /= 1024;
  i++;
}

这个循环将 size 不断除以 1024,直到它小于或等于 1024。每次除法操作后,size 表示的大小减少一个单位(例如从字节到千字节),同时 i 递增,用于跟踪当前单位在 units 数组中的索引。

sprintf(buf, "%.*f %s", i, size, units[i]);

sprintf 是一个格式化字符串函数,用于将 size 转换为字符串,并附加正确的单位。%.*f 是一个格式说明符,其中 .* 允许指定小数点后的位数,i 的值用于控制小数点后的精度。因此,如果 i 是 2,size 将被格式化为两位小数的字符串。

return buf;

函数返回 buf 的指针,它指向包含人类可读文件大小的字符串。

总之,readable_fs 函数通过循环除以 1024 来减少文件大小的数值,同时递增单位索引,最后使用 sprintf 将结果格式化为易于理解的字符串。

9.slurp_file函数

std::vector<char> slurp_file(std::string filename, size_t lSize) {
  FILE * pFile;
  size_t result;

std::string filename:要读取的文件的名称。
size_t lSize:文件的大小。如果传递的是 0 或负值,函数将计算文件的实际大小。
FILE * pFile:一个指向 FILE 结构的指针,用于文件操作。
size_t result:用于存储 fread 的返回值,表示实际读取的字节数。

pFile = fopen ( filename.c_str() , "rb" );

使用 fopen 函数尝试以二进制读模式打开文件。filename.c_str() 将 std::string 对象转换为 C 风格的字符串,因为 fopen 需要一个 C 字符串作为参数。

if (pFile==NULL) {fputs ("File error",stderr); exit (1);}

如果 fopen 返回 NULL,则表示文件打开失败。函数将错误信息写入标准错误输出,并使用 exit 函数退出程序

if (lSize > 0) {
  fseek (pFile , 0 , SEEK_END);
  lSize = ftell (pFile);
  rewind (pFile);
}

fseek 将文件指针移动到文件末尾,ftell 返回文件指针的当前位置,即文件的大小。然后 rewind 将文件指针重置到文件开始。

char buf[50];
readable_fs(lSize, buf);
std::cerr << "Getting " << filename << " into memory (" << buf << ") ...";

这部分代码使用之前定义的 readable_fs 函数将文件大小转换为可读的格式,并打印出一条信息,告知用户正在将文件加载到内存中。

std::vector<char> buffer(lSize);
result = fread (buffer.data(), 1, lSize, pFile);

创建一个大小为 lSize 的 std::vector<char> 容器 buffer,然后使用 fread 函数从文件中读取 lSize 字节到 buffer 中。buffer.data() 返回指向向量内部数据的指针。

if (result != lSize) {fputs ("Reading error",stderr); exit (3);}

如果 fread 读取的字节数不等于 lSize,表示读取过程中可能发生了错误。函数将错误信息写入标准错误输出,并退出程序。

fclose (pFile);

使用 fclose 函数关闭之前打开的文件。

std::cerr << " Done" << std::endl;

打印一条消息,告知用户文件读取完成。

return(buffer);

函数返回包含文件内容的 std::vector<char> 容器。

总的来说,slurp_file 函数用于将整个文件内容一次性读取到内存中,并以 std::vector<char> 的形式返回,便于后续处理。

总结

这个程序的主要目的是提供一个文件操作库,该库包含一个名为 QuickFile 的类,用于高效地读写文件,以及一个名为 slurp_file 的函数,用于快速加载文件内容到内存中。


原文地址:https://blog.csdn.net/2302_77272988/article/details/140561293

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!