在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即可,參數根據函數定義,提供相應的參數。