我问this有关如何阅读从偏移pos
到偏移end
到mmap()
的文本文件的问题。特别是文本文件由多个线程读取,代码如下:
void getNextKeyValue() {
key = pos;//value is the actual file offset
char *mmappedData = (char*) mmap(NULL, end-pos+1, PROT_READ, MAP_PRIVATE , fd, pos);
assert(mmappedData != NULL);
value.assign(mmappedData);
assert(munmap(mmappedData, end-pos+1)==0);
morePairs = false;
}
未报告的变量在其他地方声明并初始化。顺便提一下,以下代码读取 整个 文本文件,而不是偏移pos
到end
。
另外,程序突然终止(没有错误输出)多个线程,而只有一个线程读取整个文件才能正确终止。
更新
关注this示例(您可以尝试我的版本,使用cout
write
HERE ./main main.cpp 10 20
{}},我发现我是做错了是我通过cout<<mmappedData<<endl
打印了读取数据。如果我使用write(STDOUT_FILENO, mmappedData+pos-pa_offset, end-pos);
,则会打印出正确的文本部分。
我仍然不理解的是为什么整个文本存储在mmappedData
(或addr
后面的链接示例中):mmap
使用清楚说明读取的字节数是从第4个arg开始的第2个arg。
答案 0 :(得分:1)
更新新信息:
您的问题是您误解了mmap
的工作原理。 mmap
映射内存页面,而不是字节;即使你只要求映射24个字节,它实际上会映射几千字节(在大多数系统上4KB),不能保证数据被NUL
终止(实际上,除非映射到达文件的末尾) ,对于文本输入文件,它可能不会NUL
终止)。仅使用std::string::assign
作为参数的char *
方法使用C风格的字符串逻辑;它只是一直在阅读字符,直到它到达NUL
。如果你很幸运,那么mmap
之后的页面是可读的并且包含NUL
,它只是将NUL
之前的所有内容复制到字符串中(或等效地执行cout << mmappedData
时,将其写出来),如果没有,则在映射后尝试访问未映射的内存地址时会出现段错误。
如果目标是从std::string
为char*
分配特定数量的字节,则需要使用assign
的两个arg形式来创建开始和长度与您对write
一样明确,然后它只会使用您需要的数据:
value.assign(mmappedData+pos-pa_offset, end-pos);
原始投机答案:
所提供的代码不够清晰,无法排除其他问题,但如果您不断重新读取值,并设置了NDEBUG
(禁用assert
s),那么您就可以了解问题。有两个重叠的问题:
mmap
的返回值(即使启用了assert
,也未正确检查; mmap
的错误返回是MAP_FAILED
,而不是NULL
)munmap
- ing; munmap
调用位于assert
内,并且assert
已禁用,预处理器会删除您声明的代码。因此,如果您有大量线程一遍又一遍地调用此函数(特别是如果映射的区域很大),那么您最终会耗尽虚拟内存空间。如果每次调用映射1 MB,即使在64位系统(实际上只有47位用户模式虚拟地址空间)上,在~134M调用之后,您将耗尽虚拟地址空间。如果每次呼叫都映射1 GB,那么在~134K呼叫后你就会用完。当然,在32位系统上,您可以更快地达到更多的限制。
除此之外,我还无法知道是否还有其他问题;如果它是共享数据,value.assign
也很容易成为问题。
答案 1 :(得分:1)
以这种方式从文件中读取数据将是有效的单线程。每个 How should I do this?
var InputValues=React.createClass({
handleClick:function(){
this.props.onUserInput(this.refs.idInputText.value,this.refs.nameInputText.value);
},
render:function(){
return(
<div>
<form>
<label id="id">Id: </label><br/>
<input type="text" placeholder="Enter id" ref="idInputText" />
<br/><br/><label id="name">Name: </label><br/>
<input type="text" placeholder="Enter Name" ref="nameInputText" />
<p>
<button type="button" onClick={this.handleClick}>Submit</button>
</p>
</form>
</div>
);
}
});
var DisplayEmployeeTable=React.createClass({
getInitialState:function(){
return{
id:'',
nameText:''
}
},
handleUserInput:function(idText,nameText)
{
this.setState({id:idText, nameText:nameText});
},
render:function(){
return(
<div>
<InputValues onUserInput = {this.handleUserInput}/>
<EmployeeTable
idText={this.state.id}
nameText={this.state.nameText}
/>
</div>
);
}
});
var EmployeeTable = React.createClass({
render: function() {
return (
<table>
<thead>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<tr>
<td>{this.props.idText}</td> //pass array values
<td>{this.props.nameText}</td>
</tr>
</thead>
</table>
);
}
});
ReactDOM.render(
<DisplayEmployeeTable/>,
document.getElementById('container')
);
/ <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
<body>
<div id="container"></div>
<script type="text/babel" src="src/employee.js"></script>
</body>
</html>
对需要对进程的地址空间内存映射进行两次修改 - 并且只有一个线程可以在任何时候导致地址空间映射的更改。更糟糕的是,更改进程的地址空间映射本身就是一项昂贵的操作。
映射整个文件 - 并保留映射 - 并根据需要从映射中复制数据,或者使用不依赖于任何共享值或状态的低级IO调用,例如{ {3}}。由于您已经有一个文件的打开文件描述符mmap
,如下所示:
munmap
请注意,您可以不使用fd
然后void getNextKeyValue() {
char buffer[end-pos+1];
ssize_t result = pread(fd, buffer, end-pos+1L, pos);
assert(result == ( end-pos+1L ) );
value.assign(buffer);
morePairs = false;
}
- 这不是原子操作 - 另一个线程可以在线程调用后修改文件指针至lseek()
,但在致电read()
之前。
并且不要在每次调用lseek()
时使用read()
/ new
或delete
/ malloc()
作为缓冲区 - 再次有效的单线程读取数据
答案 2 :(得分:0)
我的方法有一些与我有关的事情。首先,我无法想象有问题的文件实际上是一个文本文件,因为如果你只是用一个原始指针调用assign()
并且似乎确定了大小,它似乎有效。这意味着您要么嵌入了NUL,要么总是将所有内容复制到下一个NUL(可能超出映射范围)。在任何情况下,代码都闻起来!这也是cout << ptr
“失败”的原因,而write(stdout, ptr, size)
有效,在第一种情况下strlen()
调用中有缓冲区溢出。
其次,为什么要使用mmap()
?我想您希望获得有关所需尺寸或时间的一些好处。但是,普通的旧C ++ IOStream可能会mmap()
一个滑动窗口进入文件本身,这非常有效。但是,您必须让IOStreams轻松完成此操作:
// turn off some syncing that you shouldn't need
std::ios_base::sync_with_stdio(false);
// disable any character conversions
std::ifstream in;
in.imbue(std::locale::classic());
in.open(filename, std::ios_base::binary);
经典语言环境不包含任何非平凡的字符转换方面。这不应该是必要的,但它也没有明显的伤害。二进制标志用于告诉streambuffer它不应该执行任何CR / LF转换,这尤其会对MS Windows产生影响。
最后一点对C ++ IOStreams来说有点棘手的是定位。除了起始位置(默认构造的streampos
)之外,您只应为从seekp()
或{{1}收到的seekg()
和tellp()
提供值}。但是,有希望:没有任何字符转换,字节偏移可能对您有用。如果没有,你仍然可以构建跟踪周围位置的代码,尽管它可能有点棘手。我可以想象你甚至不得不依赖于实现定义的行为。