4

I wish to mock a memory mapped device in C in order to do effective unit testing of a device wrapping library (in Linux).

Now, I know I can mmap a file descriptor into userspace which could in principle represent a mock of said device.

So, AFAICT, my question comes down to this: Is it possible in userspace to create a file descriptor on which mmap can act, with the reading and writing being handled by suitable callbacks?

Alternatively, perhaps this is a solved problem and there is a known kernel driver that can be hooked into?

Henry Gomersall
  • 8,434
  • 3
  • 31
  • 54

1 Answers1

3

Considering it's a Linux system, you can implement a very simple FUSE filesystem with just one file on it. The kernel can handle it from there.

The main issue is that you can expect the kernel to not flush every write. There's a msync() call to flush all outstanding writes, though, but your System Under Test isn't going to call that. However, I think you can get away with opening the file descriptor using O_DIRECT | O_DSYNC

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • I assume in this case the mockery happens in the custom FUSE filesystem? – Henry Gomersall Jul 20 '16 at 11:07
  • Yup, it gets told by the kernel that a page in the page cache is dirty (written to). The FUSE filesystem driver has to calculate a diff to see what changed. When it indicates the write succeeded, the kernel clears the dirty bit in the page cache again, Obvious warning: re-reading the same memory address will give the same results; there's no equivalent to `volatile` for FUSE files. – MSalters Jul 20 '16 at 11:17
  • I don't fully understand your point about `volatile`. Does this mean that the OS will cache what is written even though the accessing code has defined the memory to be `volatile`? Doesn't this make the approach rather useless? – Henry Gomersall Jul 20 '16 at 11:41
  • `volatile` also means that repeated reads aren't cached. This can be an issue for e.g. status registers of a device. Reasonable device drivers may have a `while (! *data_avail) { /*spin*/ }` loop. The `data_avail` pointer would be `volatile`, which means the compiler will emit a real memory read which then gets the value from a memory-mapped device register. But in the mocked situation, only the first read will trigger a full-page FUSE read. The second memory read will find the page already mapped in, so FUSE doesn't call your driver. It thinks the data is still there. – MSalters Jul 20 '16 at 11:46
  • Ah, it seems `direct_io` might be the option that purports to address this issue... – Henry Gomersall Jul 20 '16 at 11:51