Files
nt-lite/code/user/tests/pcache_multithread/pcache_multithread.c
T

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;
}