233 lines
7.0 KiB
C
233 lines
7.0 KiB
C
// Multi-threaded file-cache/VAD fault regression test.
|
|
// Several threads map the same file mapping and fault/read pages concurrently.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define THREAD_COUNT 6u
|
|
#define ITERATIONS 16u
|
|
#define TEST_SIZE 32768u
|
|
|
|
static uint8_t gPattern[TEST_SIZE];
|
|
|
|
typedef struct THREAD_CTX {
|
|
uint32_t Index;
|
|
uint32_t Mapping;
|
|
volatile uint32_t Status;
|
|
volatile uint32_t IterationsDone;
|
|
} THREAD_CTX;
|
|
|
|
static THREAD_CTX gThreads[THREAD_COUNT];
|
|
|
|
static void print(const char* s) {
|
|
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
|
|
SysWriteConsole(&req);
|
|
}
|
|
|
|
static void print_u64(uint64_t v) {
|
|
char buf[32];
|
|
int i = 0;
|
|
if (v == 0) { print("0"); return; }
|
|
while (v && i < (int)sizeof(buf)) {
|
|
buf[i++] = (char)('0' + (v % 10));
|
|
v /= 10;
|
|
}
|
|
while (i--) {
|
|
char c[2] = { buf[i], 0 };
|
|
print(c);
|
|
}
|
|
}
|
|
|
|
static void exit_now(int code) {
|
|
for (;;) SysExitProcess(code);
|
|
}
|
|
|
|
static void fail(const char* msg) {
|
|
print("pcache_multithread: ");
|
|
print(msg);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
static void fail_status(const char* what, uint32_t status) {
|
|
print("pcache_multithread: ");
|
|
print(what);
|
|
print(" failed status=");
|
|
print_u64(status);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
|
|
static uint32_t query_cache(SYS_FILE_CACHE_STATS* out) {
|
|
memset(out, 0, sizeof(*out));
|
|
return SysQueryKernelStats(SYS_KERNEL_STATS_FILE_CACHE, out, sizeof(*out), 0);
|
|
}
|
|
|
|
static void delete_path(const char* path) {
|
|
SYS_PATH_REQUEST req = { SysSvFromCstr(path) };
|
|
(void)SysDeleteFile(&req);
|
|
}
|
|
|
|
static uint32_t create_temp(uint32_t* fileOut, const char** pathOut) {
|
|
static const char* paths[] = {
|
|
"pcache_multithread.tmp",
|
|
"/pcache_multithread.tmp",
|
|
"1:/pcache_multithread.tmp",
|
|
"0:/pcache_multithread.tmp",
|
|
};
|
|
|
|
for (size_t i = 0; i < sizeof(paths) / sizeof(paths[0]); ++i) {
|
|
uint32_t h = 0;
|
|
delete_path(paths[i]);
|
|
SYS_OPEN_FILE_REQUEST req = SysMakeOpenFileRequest(SysSvFromCstr(paths[i]),
|
|
SYS_FILE_ACCESS_READ | SYS_FILE_ACCESS_WRITE | SYS_FILE_ACCESS_QUERY,
|
|
SYS_FILE_CREATE_NEW,
|
|
SYS_FILE_FLAG_NONE,
|
|
&h);
|
|
uint32_t s = SysOpenFile(&req);
|
|
if (s == NTLSTATUS_SUCCESS) {
|
|
*fileOut = h;
|
|
if (pathOut) *pathOut = paths[i];
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
}
|
|
|
|
return NTLSTATUS_NOT_FOUND;
|
|
}
|
|
|
|
static void fill_pattern(void) {
|
|
for (size_t i = 0; i < TEST_SIZE; ++i)
|
|
gPattern[i] = (uint8_t)(0x5Au ^ (uint8_t)(i * 13u) ^ (uint8_t)(i >> 3));
|
|
}
|
|
|
|
static uint32_t validate_mapping(const uint8_t* base) {
|
|
for (size_t i = 0; i < TEST_SIZE; ++i) {
|
|
if (base[i] != gPattern[i])
|
|
return 0x10000000u | (uint32_t)(i & 0x0FFFFFFFu);
|
|
}
|
|
return NTLSTATUS_SUCCESS;
|
|
}
|
|
|
|
static void thread_main(uintptr_t arg) {
|
|
THREAD_CTX* ctx = (THREAD_CTX*)arg;
|
|
if (!ctx) SysExitThread(1);
|
|
|
|
for (uint32_t iter = 0; iter < ITERATIONS; ++iter) {
|
|
void* base = 0;
|
|
uint32_t s = SysMapFile(ctx->Mapping, 0, TEST_SIZE,
|
|
SYS_MAP_ACCESS_READ, SYS_MAP_FLAG_NONE, &base);
|
|
if (s != NTLSTATUS_SUCCESS || !base) {
|
|
ctx->Status = s ? s : NTLSTATUS_INVALID_PARAMETER;
|
|
SysExitThread(1);
|
|
}
|
|
|
|
volatile uint8_t* touch = (volatile uint8_t*)base;
|
|
for (size_t off = 0; off < TEST_SIZE; off += 4096)
|
|
(void)touch[off];
|
|
(void)touch[TEST_SIZE - 1];
|
|
|
|
s = validate_mapping((const uint8_t*)base);
|
|
uint32_t fs = SysFreeVm(base);
|
|
if (s != NTLSTATUS_SUCCESS) {
|
|
ctx->Status = s;
|
|
SysExitThread(1);
|
|
}
|
|
if (fs != NTLSTATUS_SUCCESS) {
|
|
ctx->Status = fs;
|
|
SysExitThread(1);
|
|
}
|
|
ctx->IterationsDone++;
|
|
}
|
|
|
|
ctx->Status = NTLSTATUS_SUCCESS;
|
|
SysExitThread(0);
|
|
}
|
|
|
|
int main(void) {
|
|
print("pcache_multithread: start\n");
|
|
fill_pattern();
|
|
|
|
uint32_t file = 0;
|
|
const char* path = 0;
|
|
uint32_t s = create_temp(&file, &path);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create temp", s);
|
|
|
|
size_t written = 0;
|
|
SYS_FILE_IO_REQUEST writeReq = SysMakeFileIoRequest(file, gPattern, sizeof(gPattern), &written);
|
|
s = SysWriteFile(&writeReq);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("write pattern", s);
|
|
if (written != sizeof(gPattern)) fail("short write");
|
|
|
|
uint32_t mapping = 0;
|
|
s = SysCreateFileMapping(file, SYS_VM_READ, SYS_MAP_FLAG_NONE, TEST_SIZE, &mapping);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create mapping", s);
|
|
|
|
SYS_FILE_CACHE_STATS before;
|
|
SYS_FILE_CACHE_STATS after;
|
|
if (query_cache(&before) != NTLSTATUS_SUCCESS) fail("initial stats query failed");
|
|
|
|
uint32_t handles[THREAD_COUNT];
|
|
uint32_t tids[THREAD_COUNT];
|
|
memset(handles, 0, sizeof(handles));
|
|
memset(tids, 0, sizeof(tids));
|
|
memset(gThreads, 0, sizeof(gThreads));
|
|
|
|
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
|
gThreads[i].Index = i;
|
|
gThreads[i].Mapping = mapping;
|
|
gThreads[i].Status = NTLSTATUS_PENDING;
|
|
SYS_CREATE_THREAD_REQUEST req = {
|
|
(uintptr_t)thread_main,
|
|
(uintptr_t)&gThreads[i],
|
|
1,
|
|
0,
|
|
&handles[i],
|
|
&tids[i]
|
|
};
|
|
s = SysCreateThread(&req);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("create thread", s);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
|
s = SysWait(handles[i], SYS_WAIT_FLAG_NONE);
|
|
if (s != NTLSTATUS_SUCCESS) fail_status("wait thread", s);
|
|
SysCloseHandle(handles[i]);
|
|
}
|
|
|
|
for (uint32_t i = 0; i < THREAD_COUNT; ++i) {
|
|
if (gThreads[i].Status != NTLSTATUS_SUCCESS) {
|
|
print("pcache_multithread: worker ");
|
|
print_u64(i);
|
|
print(" status=");
|
|
print_u64(gThreads[i].Status);
|
|
print(" iterations=");
|
|
print_u64(gThreads[i].IterationsDone);
|
|
print("\n");
|
|
exit_now(1);
|
|
}
|
|
if (gThreads[i].IterationsDone != ITERATIONS) fail("worker iteration count mismatch");
|
|
}
|
|
|
|
if (query_cache(&after) != NTLSTATUS_SUCCESS) fail("final stats query failed");
|
|
if (after.Loads <= before.Loads && after.Hits <= before.Hits)
|
|
fail("concurrent mmap did not touch cache pages");
|
|
if (after.Hits <= before.Hits) fail("concurrent mmap did not reuse cache pages");
|
|
|
|
SysCloseHandle(mapping);
|
|
SysCloseHandle(file);
|
|
delete_path(path);
|
|
|
|
print("pcache_multithread: loads+=");
|
|
print_u64(after.Loads - before.Loads);
|
|
print(" hits+=");
|
|
print_u64(after.Hits - before.Hits);
|
|
print(" races+=");
|
|
print_u64(after.RaceDiscards - before.RaceDiscards);
|
|
print("\n");
|
|
print("pcache_multithread: PASS\n");
|
|
exit_now(0);
|
|
return 0;
|
|
}
|