天天看点

一个日志输出系统的设计

本文介绍一个基于linux进程调度策略来调度的信息输出系统,多条信息可以显示多次,每次的显示时间也可以设置:

以下是头文件定义loglet.h:

#include <pthread.h>

#define MAX_PRIO 8

#define MAX_LENGTH 512

static inline void __set_bit(int nr, volatile unsigned long * addr)

{

    ...

}

static inline void clear_bit(int nr, volatile unsigned long * addr)

static inline void __clear_bit(int nr, volatile unsigned long * addr)

struct list_head {

};

#define list_entry(ptr, type, member) /

        ...

static inline void INIT_LIST_HEAD(struct list_head *list)

static inline void __list_add(struct list_head *new,

                  struct list_head *prev,

                  struct list_head *next)

static inline void list_add(struct list_head *new, struct list_head *head)

static inline void list_add_tail(struct list_head *new, struct list_head *head)

static inline void __list_del(struct list_head * prev, struct list_head * next)

static inline void list_del(struct list_head *entry)

static inline int list_empty(const struct list_head *head)

struct prio_array {

//log_buff定义一条日志任务

struct log_buff {

    char    buf[MAX_LENGTH]; //实际需要展示的数据

    int     prio; //这个日志任务的优先级

    int    times; //这个任务目前为止显示的次数

    int    sed_slice; //持续多久被调度出去

    int    need_go; //是否被抢占

    int    remove_flag; //是否应该被删除

    int    alive_times; //这个任务需要显示的次数

    int    blink; //每次显示持续的时间

    struct list_head tasks;    //全局链表

    struct prio_array *array; //目前属于哪个数组,活跃还是过期?

    struct list_head run_list; //加入链表的锚

//log_queue定义一个全局的队列

struct log_queue {

    unsigned long    nr_running; //一共有多少任务

    unsigned long long last_tick; //上次调度的时间

    struct log_buff    *idle; //它的idle任务,没事干的时候显示

    struct prio_array *active;, *expired, arrays[2]; //活跃数组/过期数组

    void   (*handler_init)(void *argv); //输出处理初始化回调函数,比如初始化网络,LCD屏幕等等

    void   (*handler_process)(void *argv, int blink); //输出处理函数,比如写LCD屏幕等

    int   (*prio2times)(int prio); //优先级转化为时间片的函数

    pthread_mutex_t queue_mutex;

//以下定义一系列导出函数

//全局初始化

int global_init();

//以下三个设置log_queue的回调函数

void set_handle_process(void   (*handler_process)(void *argv, int blink));

void set_handle_init(void   (*handler_init)(void *argv), void *arg);

void set_prio2times(int   (*prio2times)(int prio));

//设置调度间隔

int set_interval(int val);

void set_idle(const char *info);

int insert_log(const char *info, int prio, int alives, int blink);    

--END--

以下是实现文件代码:

#include "log_let.h"

#include <stdlib.h>

#include <string.h>

#include <sys/time.h>

#include <signal.h>

#include <unistd.h>

#include <stdio.h>

struct log_buff *current = NULL;

struct log_buff *prev = NULL;

unsigned long long tick = 0;

struct log_queue  *global_rq;

//从一个链表中将一个日志退出

static void dequeue_log(struct log_buff *p, struct prio_array *array)

    array->nr_active--;

    list_del(&p->run_list);

    if (list_empty(array->queue + p->prio))

        __clear_bit(p->prio, array->bitmap);

//将一条日志加入一个链表

static void enqueue_log(struct log_buff *p, struct prio_array *array)

    struct list_head *lh = &global_rq->active->queue[p->prio];

    list_add_tail(&p->run_list, &array->queue[p->prio]);

    __set_bit(p->prio, array->bitmap);

    array->nr_active ++;

    p->array = array;

//得到默认队列

struct log_queue *get_queue()

    return global_rq;

void remove_log(struct log_buff *buf)

    buf->remove_flag = 1;

//信号处理函数

void time_tick(void)

    unsigned long long now = tick ++;

    struct log_buff *p = current;

    struct log_queue *rq = get_queue();//global_rq;

    rq->last_tick = now;

    //如果是idle日志,不处理

    //如果还没有任何日志,不处理

    //如果和前一次处理的一样,不重复处理

    if (p == rq->idle || !p || p == prev) {

        return;

    }

    prev = p;

    //如果时间片耗尽,则要么加入到“过期数组”,要么被删除

    if (!--p->sed_slice) {

        p->need_go = 1;

        //如果此条日志需要显示的次数到了,那么就要删除它了

        if (p->alive_times > 0 && !--p->alive_times) {

            remove_log(p); //仅仅设置一个删除标志

            return;

        }

        //将一个“过期”的日志加入到一个“过期数组”中,注意需要锁操作

        pthread_mutex_lock(&rq->queue_mutex);    

        dequeue_log(p, rq->active);

        pthread_mutex_unlock(&rq->queue_mutex);    

        //重新设置显示“时间片”信息

        p->sed_slice = rq->prio2times(p->prio);

        pthread_mutex_lock(&rq->queue_mutex);

        enqueue_log(p, rq->expired);

        pthread_mutex_unlock(&rq->queue_mutex);

    } 

//激活一条日志任务

static void __activate_log(struct log_buff *p, struct log_queue *rq)

    struct prio_array *target = rq->active;

    pthread_mutex_lock(&rq->queue_mutex);

    enqueue_log(p, target);

    pthread_mutex_unlock(&rq->queue_mutex);

    rq->nr_running++;

//实际的插入动作参数依次是(内容,优先级别,显示次数,每次显示滞留的时间,是否是idle)

int insert_log_real(const char *content, int prio, int alive_times, int blink, int idle)

    int ret = 0;

    struct log_queue *rq;

    struct log_buff *lb;

    if ((current == NULL && !idle) || (current && idle)) {

        ret = -1;

        goto last;

    rq = get_queue();

    lb = (struct log_buff *)calloc(1, sizeof(struct log_buff));

    strncpy(lb->buf, content, strlen(content)?:MAX_LENGTH);

    INIT_LIST_HEAD(&lb->run_list);

    lb->prio = prio;

    lb->times = 0;

    lb->alive_times = alive_times;

    lb->blink = blink;

    lb->sed_slice = (rq->prio2times)(prio);//show_style_func(prio);  //可设置一个数学函数

    if (!idle) //idle任务并不加入到任何链表当中,方便操作

        __activate_log(lb, rq);

    else {

        current = lb;

        rq->idle = lb;

    //如果插入了一条信息且当前正在运行idle,则抢占它

    //如果当前的任务的优先级别没有新插入的任务的优先级别高,则抢占它

    if (current && ((current == rq->idle&&!idle) || current->prio>prio))

        current->need_go = 1;

last:

    return ret;

//插入idle日志任务

void set_idle(const char *idlebuf)

    insert_log_real(idlebuf, 7, 0, 1, 1);

//插入一般的日志任务

int insert_log(const char *content, int prio, int alive_times, int blink)

    return insert_log_real(content, prio, alive_times, blink, 0);

//以下这些都是直接从linux内核源代码中copy出来的

static inline unsigned long __ffs(unsigned long word)

static inline int sched_find_first_bit(const unsigned long *b)

//设置输出初始化回调函数

void set_handle_init(void (*func)(void *argv), void *arg)

    struct log_queue* rq = get_queue();

    rq->handler_init = func;

    rq->handler_init(arg);

//设置输出处理函数

void set_handle_process(void (*func)(void *argv, int blink))

    rq->handler_process = func;

//设置从优先级到时间片的转换函数

void set_prio2times(int (*func)(int prio))

    rq->prio2times = func;

//全局的初始化

int global_init()

    //分配全局队列

    global_rq = (struct log_queue*)calloc(1, sizeof(struct log_queue));

    struct prio_array *active = (struct prio_array*)malloc(sizeof(struct prio_array));

    struct prio_array *expired = (struct prio_array*)malloc(sizeof(struct prio_array));

    memset(active, 0, sizeof(struct prio_array));

    memset(expired, 0, sizeof(struct prio_array));

    global_rq->active = active;

    global_rq->expired = expired;

    ret = pthread_mutex_init(&global_rq->queue_mutex, NULL);

    int i;

    //初始化活动数组

    for (i = 0; i < MAX_PRIO; i++) {

        INIT_LIST_HEAD(&global_rq->active->queue[i]);

    //初始化过期数组

        INIT_LIST_HEAD(&global_rq->expired->queue[i]);

//设置调度时间间隔并且开始调度

int set_interval(int second)

    static struct itimerval p_realt;

    p_realt.it_interval.tv_sec = second;

    p_realt.it_interval.tv_usec = 0;

    p_realt.it_value.tv_sec = second;

    p_realt.it_value.tv_usec = 0;

        signal(SIGALRM,time_tick);

        if(setitimer(ITIMER_REAL,&p_realt,(struct itimerval *)0) == -1) {

            perror("setitimer error! ");

        return -1;

    return 0;

//全局启动

int start()

    struct log_buff *previous;

    //以下的逻辑完全就是linux内核O(1)进程调度器的逻辑

    //参见linux内核kernel/sched.c中的schedule函数代码

    while (1) {

        struct log_queue *rq = get_queue();

        struct prio_array *array = rq->active;

        previous = current;

        if (current->need_go) {

redo:

            if (!array->nr_active) {

                rq->active = rq->expired;

                rq->expired = array;

                array = rq->active;

            }

            struct log_buff *lb;

                lb = rq->idle;

                goto switch_to;                

            int idx = sched_find_first_bit(array->bitmap);

            struct list_head *queue = array->queue + idx;    

            lb = list_entry(queue->next, struct log_buff, run_list);

            if (lb->remove_flag) {

                int redo_flag = 0;

                if (lb == current) { 

                    redo_flag = 1;

                }

                pthread_mutex_lock(&rq->queue_mutex);    

                dequeue_log(lb, lb->array);

                pthread_mutex_unlock(&rq->queue_mutex);    

                free(lb);

                lb = NULL;

                if(redo_flag) 

                    goto redo;

switch_to:

            lb->need_go = 0;

            current = lb;

        if (current == previous && current == rq->idle) {

            usleep(500);

            continue;

        rq->handler_process(current->buf, current->blink);

下面是一个测试程序

#include "lcd_task.h"

#include <malloc.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <termios.h>

#include <errno.h>

int show_style_func(int prio)

    return 1;//9-prio;

void lcd_init(void *argv)

    //打开并设置屏幕

    ... open( ..., O_RDWR | O_NOCTTY | O_NDELAY);

void lcd_process(void *argv, int blink)

    //argv就是需要输出的信息,blink就是输出的间隔

    char *buf = (char*)argv;

    for (i = 0; i < 需要的屏幕数; i++, ...) {

        //换屏处理,每屏幕都显示blink的间隔,下面的处理为了防止被调度timer信号中断sleep

        while(blink = sleep(blink));

        blink = blk;

void *test_func(void *arg)

    int prio;

    int i, j;

    int total = 20;

    char buf[16];

    while (total--) {

        prio =  random()%8;

        for (i = 0; i < 8; i++) {

            if(prio == i) {

                memset(buf, 0, sizeof(buf));

                for (j = 0; j < 2*i; j++) {

                    strcat(buf, "-");

                if (i == 0)

                    strcat(buf, "0000");

                insert_log(buf, prio, 2, 2);

                break;

        sleep(1);

int main(int argc, char **argv)

    pthread_t id;

    global_init();

    set_handle_process(lcd_process);

    set_handle_init(lcd_init, NULL);

    set_prio2times(show_style_func);

    set_interval(1);

    set_idle("I'm IDLE");

    insert_log("this is one", 4, 3, 5);

    pthread_create(&id,NULL,test_func,NULL);

    start();

 本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog250/1271161

继续阅读