kernel中實現的循環buffer及使用方式

在kernel中輪詢設備數據,用戶空間通過阻塞式讀取數據,難免在kernel中需要buffer存在,因此實現可配置的循環buffer,供大家參考,待優化部分是buffer加上信號量(如果數據量不是特別大,也沒有必要加上信號量,加上保護在性能上或許得不償失)。代碼如下:

data_queue.h

#ifndef __DATA_QUEUE_H__
#define __DATA_QUEUE_H__

#include <linux/types.h>

typedef struct data_queue
{
	int buf_len;
	char* buf;
	int head;  // idle buffer head
	int tail;  // data buffer head
	bool full;
}data_queue;

typedef struct queue
{
	struct data_queue common;
	int (*enqueue)(struct data_queue*,const char* , int , int /* ms */);
	int (*dequeue)(struct data_queue*, char __user *, int );
	bool (*exist_data)(struct data_queue*);
}queue_t;

int enqueue_data(struct data_queue* queue, const char* data, int size, int timeout/* ms */);
int dequeue_data(struct data_queue* queue, char __user *buff, int size);
bool check_exist_data(struct data_queue* queue);

#define DECLARE_DATA_QUEUE(name, size) \
	static char buf_##name[size] = "\0"; \
	static struct queue name = { \
		.common = { \
			.buf_len = size, \
			.buf = buf_##name, \
			.head = 0, \
			.tail = 0, \
			.full = false, \
		}, \
		.enqueue = enqueue_data, \
		.dequeue = dequeue_data, \
		.exist_data = check_exist_data, \
	};


#endif

data_queue.c

#include <linux/spinlock.h>
#include <asm/uaccess.h>
#include <linux/spinlock_types.h>
#include <linux/delay.h>

#include "data_queue.h"

#define LOG_TAG	 "data_queue:"
#define print_dbg(fmt, ...) printk(KERN_DEBUG LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)
#define print_err(fmt, ...) printk(KERN_ERR LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)
#define print_info(fmt, ...) printk(KERN_NOTICE LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)

DEFINE_SPINLOCK(data_queue_lock);

#define USE_LOCK 0
#define lock_share()  do {if(USE_LOCK) spin_lock(&data_queue_lock);} while(0)
#define unlock_share()  do {if(USE_LOCK) spin_unlock(&data_queue_lock);} while(0)

static int get_idle(struct data_queue* queue)
{
	int idle = 0;
	lock_share();
	print_dbg("---------- data queue debug -----------");
	print_dbg("buf_len = %d", queue->buf_len);
	print_dbg("full = %d", queue->full);
	print_dbg("head = %d", queue->head);
	print_dbg("tail = %d", queue->tail);
	print_dbg("---------------------------------------");
	if(!queue->full && queue->tail == queue->head) idle = queue->buf_len;
	if(queue->head > queue->tail) idle = queue->buf_len - (queue->head-queue->tail);
	if(queue->head < queue->tail) idle = queue->tail - queue->head;
	unlock_share();
	return idle;
}

static void push_data(struct data_queue* queue, const char* data, int size)
{
	memcpy(&queue->buf[queue->head], data, size);
	lock_share();
	queue->head = (queue->head + size)%queue->buf_len;
	if(queue->head == queue->tail) queue->full = true;
	unlock_share();
}

static int pull_data(struct data_queue* queue, char __user *buff, int size)
{
	int buf_len = queue->buf_len;
	if(buf_len - queue->tail >= size) {
		if(copy_to_user((void*)buff, (const void*)&queue->buf[queue->tail], (unsigned long)size) != 0) {
			print_err("copy_to_user failed.");
			return -1;
		}
	} else {
		int first_half = buf_len-queue->tail;
		if(copy_to_user((void*)buff, (const void*)&queue->buf[queue->tail],
			(unsigned long)(first_half))) {
			print_err("copy_to_user failed.");
			return -1;
		}
		if(copy_to_user((void*)(buff+first_half), (const void*)&queue->buf[0],
			(unsigned long)(size-first_half))) {
			print_err("copy_to_user failed.");
			return -1;
		}
	}
	lock_share();
	queue->tail = (queue->tail + size) % buf_len;
	if(queue->head == queue->tail) queue->full = false;
	unlock_share();
	return size;
}

int enqueue_data(struct data_queue* queue, const char* data, int size, int timeout/* ms */)
{
	int buf_len = queue->buf_len;
	int idle = 0;
	if(data == NULL || size > buf_len) {
		print_err("params are not valid.");
		return -1;
	}
	idle = get_idle(queue);

	if(idle >= size) {
		push_data(queue, data, size);
		return size;
	}

	while(timeout > 0) {
		msleep(10);
		timeout -= 10;
		if(get_idle(queue) > size) {
			push_data(queue, data, size);
			return size;
		}
	}
	return 0;
}

int dequeue_data(struct data_queue* queue, char __user *buff, int size)
{
	int valid_size = 0;
	int idle = 0;
	int buf_len = queue->buf_len;
	if(buff == NULL || size <= 0) {
		print_err("params are not valid.");
		return -1;
	}

	if(get_idle(queue) == buf_len) {
		print_info("no data exist.");
		return 0;
	}

	idle = get_idle(queue);
	valid_size = buf_len - idle;
	if(size >= valid_size) return pull_data(queue, buff, valid_size);
	return pull_data(queue, buff, size);
}

bool check_exist_data(struct data_queue* queue)
{
	return (queue->buf_len - get_idle(queue) > 0);
}

使用循環buffer的代碼

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mod_devicetable.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/sched.h>
#include <linux/poll.h>

#include "fm17550_drv.h"
#include "data_queue.h"

#define LOG_TAG	 "rfid:"
#define print_dbg(fmt, ...) printk(KERN_DEBUG LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)
#define print_err(fmt, ...) printk(KERN_ERR LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)
#define print_info(fmt, ...) printk(KERN_INFO LOG_TAG "%s:%d->" fmt "\n", \
					__func__, __LINE__, ##__VA_ARGS__)

static rfid_dev_init	g_dev_init = fm17550_init;
static rfid_dev_ops		g_dev_ops;
static struct mutex 	g_mutex;

// 定義全局循環buffer變量,有100個字節
DECLARE_DATA_QUEUE(rfid_data, 100);

...

static ssize_t _read (struct file *file, char __user * buff, size_t length, loff_t *offset)
{
	print_dbg("enter.");
	// 判斷是否存在可讀數據
	if(!rfid_data.exist_data(&rfid_data.common)) return 0;
	// 從buffer中獲取數據
	// 此處讀取數據的長度沒有特別要求,我的代碼中讀取數據是以MAX_VALID_UUID_LEN爲整數倍的
	return rfid_data.dequeue(&rfid_data.common, buff,  (length/MAX_VALID_UUID_LEN)*MAX_VALID_UUID_LEN);
}

...

存儲數據和獲取數據方式相同,函數換位queue即可,參數根據函數定義,提供相應的參數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章