The first question: how does data get from the kernel to userspace safely?
Camera pipelines are producer-consumer systems. A sensor produces frames; an application consumes them. Before I could understand GStreamer or V4L2, I needed to understand the most fundamental version of this: how does a kernel-space producer hand data to a userspace consumer reliably?
This driver is that question in code — a 16-slot ring buffer exposed through a character device.
Writers push 128-byte messages into the buffer via write(). Readers call read() and block if the buffer is empty. The kernel wait queue handles the sleeping and waking.
| Parameter | Value |
|---|---|
| Device node | /dev/sanath_queue |
| Buffer | 16 slots × 128 bytes |
| Sync | mutex |
| Full-buffer policy | Drop oldest (overwrite) |
typedef struct kernel_logger {
uint8_t kernel_buffer[ROW_SIZE][MEM_SIZE];
uint8_t read_indexer;
uint8_t write_indexer;
struct mutex etx_mutex;
uint8_t count;
} kernel_logger_t;if (kernel_logger.count < ROW_SIZE)
kernel_logger.count++;
else
kernel_logger.read_indexer =
(kernel_logger.read_indexer + 1) % ROW_SIZE;while (kernel_logger.count == 0) {
mutex_unlock(&kernel_logger.etx_mutex);
if (wait_event_interruptible_exclusive(etx_wait_queue,
kernel_logger.count > 0))
return -ERESTARTSYS;
mutex_lock(&kernel_logger.etx_mutex);
}The exclusive flag means only one waiter wakes per write event. Without it every reader wakes up, races to grab the mutex, and all but one find nothing to read — the thundering herd problem.
| Concept | What I learned |
|---|---|
alloc_chrdev_region | Dynamic major number — the kernel assigns it, you don't hardcode it |
cdev_init / cdev_add | Binds your file_operations struct to the character device |
class_create / device_create | Creates /dev/sanath_queue automatically via udev |
copy_to/from_user | The kernel can't trust user pointers — these functions handle faults safely |
| mutex vs spinlock | Read/write run in process context so they can sleep — mutex is correct here |