我问这个问题的原因是,在测试Linux软脏位的行为时,我发现如果我在不接触任何内存的情况下创建一个线程,所有页面的软脏位将被设置为1(脏).
例如,在主线程中设置为malloc(100MB)
,然后清除柔软的脏位,然后创建一个只会Hibernate 的线程.创建线程后,所有malloc(100MB)
MB内存块的软脏位被设置为1.
下面是我正在使用的测试程序:
#include <thread>
#include <iostream>
#include <vector>
#include <cstdint>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#define PAGE_SIZE_4K 0x1000
int GetDirtyBit(uint64_t vaddr) {
int fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0) {
perror("Failed open pagemap");
exit(1);
}
off_t offset = vaddr / 4096 * 8;
if (lseek(fd, offset, SEEK_SET) < 0) {
perror("Failed lseek pagemap");
exit(1);
}
uint64_t pfn = 0;
if (read(fd, &pfn, sizeof(pfn)) != sizeof(pfn)) {
perror("Failed read pagemap");
sleep(1000);
exit(1);
}
close(fd);
return pfn & (1UL << 55) ? 1 : 0;
}
void CleanSoftDirty() {
int fd = open("/proc/self/clear_refs", O_RDWR);
if (fd < 0) {
perror("Failed open clear_refs");
exit(1);
}
char cmd[] = "4";
if (write(fd, cmd, sizeof(cmd)) != sizeof(cmd)) {
perror("Failed write clear_refs");
exit(1);
}
close(fd);
}
int demo(int argc, char *argv[]) {
int x = 1;
// 100 MB
uint64_t size = 1024UL * 1024UL * 100;
void *ptr = malloc(size);
for (uint64_t s = 0; s < size; s += PAGE_SIZE_4K) {
// populate pages
memset(ptr + s, x, PAGE_SIZE_4K);
}
char *cptr = reinterpret_cast<char *>(ptr);
printf("Soft dirty after malloc: %ld, (50MB offset)%ld\n",
GetDirtyBit(reinterpret_cast<uint64_t>(cptr)),
GetDirtyBit(reinterpret_cast<uint64_t>(cptr + 50 * 1024 * 1024)));
printf("ALLOCATE FINISHED\n");
std::string line;
std::vector<std::thread> threads;
while (true) {
sleep(2);
// Set soft dirty of all pages to 0.
CleanSoftDirty();
char *cptr = reinterpret_cast<char *>(ptr);
printf("Soft dirty after reset: %ld, (50MB offset)%ld\n",
GetDirtyBit(reinterpret_cast<uint64_t>(cptr)),
GetDirtyBit(reinterpret_cast<uint64_t>(cptr + 50 * 1024 * 1024)));
// Create thread.
threads.push_back(std::thread([]() { while(true) sleep(1); }));
sleep(2);
printf("Soft dirty after create thread: %ld, (50MB offset)%ld\n",
GetDirtyBit(reinterpret_cast<uint64_t>(cptr)),
GetDirtyBit(reinterpret_cast<uint64_t>(cptr + 50 * 1024 * 1024)));
// memset the first 20MB
memset(cptr, x++, 1024UL * 1024UL * 20);
printf("Soft dirty after memset: %ld, (50MB offset)%ld\n",
GetDirtyBit(reinterpret_cast<uint64_t>(cptr)),
GetDirtyBit(reinterpret_cast<uint64_t>(cptr + 50 * 1024 * 1024)));
}
return 0;
}
int main(int argc, char *argv[]) {
std::string last_arg = argv[argc - 1];
printf("PID: %d\n", getpid());
return demo(argc, argv);
}
我打印第一页的脏部分,然后打印偏移量为50 * 1024 * 1024
的页面.以下是发生的情况:
-
malloc()
之后的软脏位是1,这是预期的. - 在清洗软脏之后,它们变成了0.
- 创建一个只是Hibernate 的线程.
- 判断脏位,100MB区域中的所有页面(我没有打印所有页面的脏位,但我自己进行了判断)现在将软脏位设置为1.
- 重新启动循环,现在行为正确,创建额外线程后,软脏位仍为0.
- 自从我做了
memset()
之后,位于偏移量0的页面的软脏比特是1,而页面50 MB
的软脏比特保持为0.
以下是输出:
Soft dirty after malloc: 1, (50MB offset)1
ALLOCATE FINISHED
Soft dirty after reset: 0, (50MB offset)0
Soft dirty after create thread: 1, (50MB offset)1
Soft dirty after memset: 1, (50MB offset)1
(steps 1-4 above)
(step 5 starts below)
Soft dirty after reset: 0, (50MB offset)0
Soft dirty after create thread: 0, (50MB offset)0
Soft dirty after memset: 1, (50MB offset)0
Soft dirty after reset: 0, (50MB offset)0
Soft dirty after create thread: 0, (50MB offset)0
Soft dirty after memset: 1, (50MB offset)0
Soft dirty after reset: 0, (50MB offset)0
Soft dirty after create thread: 0, (50MB offset)0
Soft dirty after memset: 1, (50MB offset)0
我认为创建线程只会将页面标记为"共享"状态,而不是修改它们,因此软脏部分应该保持不变.显然,他们的行为是不同的.因此,我在想:创建线程是否会在所有页面上触发页面错误?因此,在处理页面错误时,操作系统会将所有页面的软脏位设置为1.
如果不是这样,为什么创建线程会使进程的所有内存页都变得"脏"呢?为什么只有第一线程创建有这样的行为?
我希望我已经很好地解释了这个问题,如果需要更多的细节,或者如果有什么不合理的地方,请告诉我.