取消引用映射内存时出现分段错误

Segmentation Fault on dereferencing mapped memory

我 运行 遇到了一个恼人的问题,我无法以任何方式访问从 mmap 获得的内存而不会出现分段错误。

我用来获取映射内存的函数如下所示。

   /**
   * Preconditions: filename must be verified as referencing a valid file.
   */
  char *IOUtils::memory_map_file(string const& filename, size_t length, int open_flags){
    int fd = open(filename.c_str(), open_flags | O_CREAT, 
                  S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
    int prot;
    if (open_flags == O_RDONLY)
      prot = PROT_READ;
    else
      prot = PROT_READ | PROT_WRITE;
    void *output = mmap(NULL, length, prot, MAP_SHARED, fd, 0);
    if (output == (void *) -1){
      cerr << filename << ": " << strerror(errno) << '\n';
      _exit(2);
    }
    close(fd);
    return (char *)output;
  }

我的主要功能是这样的。

int main(int argc, char *argv[]){
  size_t input_length = IOUtils::file_size(argv[1]); //This works fine
  char *input_buffer = IOUtils::memory_map_file(argv[1], input_length,
    O_RDONLY); //This succeeds
  char *output_buffer = IOUtils::memory_map_file(argv[2], 2*input_length,
    O_RDWR); //This succeeds
  DomainParser parser(input_length, input_buffer, output_buffer);
  while(!parser.finished()){
    parser.write_entry();
  }
  mremap(output_buffer, 2*input_length, MREMAP_MAYMOVE, 
         parser.bytes_written());
  munmap(output_buffer, parser.bytes_written());
}

解析器的相关代码如下所示

void DomainParser::write_entry(void){
    char const *in = input(); //Gets position in input file
    char const *copy_up_to = end(); //Gets position at input EOF
    for(char const *it = in; it < copy_up_to; ++it){
        cerr << *it;   //SIGSEGV! 
        if(*it == '\n') break;
    }
    cerr << '\n';
    /* Do writes */
}

程序在 cerr << *it 后立即出现段错误。我不知道为什么会发生这种情况,考虑到所有映射内存都配备了读取权限并已成功分配。

编辑:如果有人怀疑 class 有问题,这里是完整的源代码。

using std::stringstream;
using std::string;

class DomainParser{
  size_t _input_offset;
  const size_t _input_length;
  size_t _output_offset;
  char const *input_buffer;
  char *output_buffer;

  char const *input(void){
    return input_buffer + _input_offset;
  }

  char *output(void){
    return output_buffer + _output_offset;
  }

  char const* end(void){
    return input_buffer + _input_length;
  }

  char const *find(char const *begin, char const *max, char c){
    while (*begin != c){
      cerr << *begin++;
    }
    cerr << c;
    return begin;
  }

public: 
  DomainParser(size_t length, char const *input, char *output) :
    _input_length(length), input_buffer(input), output_buffer(output)
  {}

  bool finished(void){
    return _input_offset == _input_length;
  }

  size_t bytes_written(void){
    return _output_offset;
  }

  size_t write_entry(void){
    if (finished()){
      return 0;  
    }
    char const *in = input();
    char const *copy_up_to = find(in, end(), '\n');
    size_t input_entry_length = copy_up_to - in;

    string s(in, copy_up_to);
    stringstream ss(s);
    string name, type, host;
    ss >> name >> type >> host;
    if (!ss){
      cerr << s << '\n';
      _input_offset += input_entry_length;
      return 0;
    }
    ss.str(""); ss.clear();
    ss << "{\"name\":\"" << name << "\"," <<
      "\"host\":\"" << host << "\"," <<
      "\"type\":\"" << type << "\"}\n";
    string entry = ss.str();
    std::memcpy(output(), entry.c_str(), entry.size());

    _input_offset += input_entry_length;
    _output_offset += entry.size();
    return entry.size();
  }
};

我没有看到 _input_offset 的任何初始化。

如果修复该问题,您将 运行 遇到输出文件为空的问题,因此访问任何页面都会触发 SIGBUS 信号。您需要使用 ftruncate 将其调整为预期大小(可能与映射的大小相匹配,但这取决于您要执行的操作)。

也不是说 munmap 可能非常昂贵(尤其是在大型系统上),因此内存映射 I/O 仅在文件非常大时才有效。