ring_log ring log-6M buffer_proc interface

Article directory

    • log_tools.c
    • log.c
    • spin_lock
    • seq_puts
    • seq_read
    • seq_write
    • single_open
    • makefile
    • test.sh
    • test:
      • run ./test.sh
      • read log
      • insert log
      • echo cat test
    • refer to:

log_tools.c

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>


#define MYLOG_PROC_PATH "/proc/mylog"
#define TMP_BUF_SIZE 1024
#define BUF_SIEZ 256
#define PER_LOG_SIZE 128

#define LOG_LEVEL_EMERG 1
#define LOG_LEVEL_ALERT 2
#define LOG_LEVEL_CRIT 3
#define LOG_LEVEL_ERR 4
#define LOG_LEVEL_WARNING 5
#define LOG_LEVEL_NOTICE 6
#define LOG_LEVEL_INFO 7
#define LOG_LEVEL_DEBUG 8

static FILE *fp;

static void print_help(void)
{<!-- -->
printf("-H\t\t--help\t\t\t\tdisplay help\\
");
printf("-h\t\t--help\t\t\t\tdisplay help\\
");
printf("-r\t\t--readall\t\t\t read log\\
");
printf("-R\t\t--readloglevel\t\t\t read logs through log level\\
");
printf("-R\t\t<log_level>\\
");
printf("-w\t\t--write\t\t\t\t write log\\
");
printf("-w\t\t<log_level>\t\t\t<log_message>\\
");

}
void print_log_level(void)
{<!-- -->
    printf("log level must be range 1 to 8\\
");
    printf("LOG_LEVEL_EMERG\t\t\t1\\
");
    printf("LOG_LEVEL_ALERT\t\t\t2\\
");
    printf("LOG_LEVEL_CRIT\t\t\t3\\
");
    printf("LOG_LEVEL_ERR\t\t\t4\\
");
    printf("LOG_LEVEL_WARNING\t\t5\\
");
    printf("LOG_LEVEL_NOTICE\t\t6\\
");
    printf("LOG_LEVEL_INFO\t\t\t7\\
");
    printf("LOG_LEVEL_DEBUG\t\t\t8\\
");
}

int write_log(int argc,char const *argv[])
{<!-- -->
const char *log_message = NULL;
int log_level = -1;
char tmpbuf[TMP_BUF_SIZE] = {<!-- -->0};

if(argc != 4) {<!-- -->
printf("Usage: %s -w <log_level> <log_message>\\
",argv[0]);
goto ERROR;
}

log_level = atoi(argv[2]);
if (log_level < 1 || log_level > LOG_LEVEL_DEBUG) {<!-- -->
    printf("error log_level");
    goto ERROR;
    }

    log_message = argv[3];
    if(strlen(log_message) > PER_LOG_SIZE)
    {<!-- -->
    goto ERROR;
    }

    fseek(fp,0,SEEK_END);
sprintf(tmpbuf,"%d %s\\
",log_level,log_message);
if(fwrite(tmpbuf,strlen(tmpbuf),1,fp) < 0) {<!-- -->
perror("fwrite");
}
printf("insert %d %s\\
",log_level,log_message);

ERROR:
return 0;
}
int read_all_log(void)
{<!-- -->
    char buf[BUF_SIEZ] = {<!-- -->0};
    fseek(fp,0,SEEK_SET);
    while (fgets(buf, sizeof(buf), fp)) {<!-- -->
        printf("%s", buf);
    }
    return 0;
}
int read_log_by_level(int argc, char const *argv[])
{<!-- -->
int level = -1, log_level = -1;
char buf[BUF_SIEZ] = {<!-- -->0};
if(argc != 3) {<!-- -->
printf("Usage: %s -R <log_level>\\
",argv[0]);
goto ERROR;
}

log_level = atoi(argv[2]);
if (log_level < 1 || log_level > LOG_LEVEL_DEBUG) {<!-- -->
    printf("error log_level\\
");
    goto ERROR;
    }

    fseek(fp,0,SEEK_SET);
    while (fgets(buf, sizeof(buf), fp)) {<!-- -->
    sscanf(buf,"%*s %d", &level);
    if(log_level == level)
       printf("%s", buf);
    }
ERROR:
    return 0;
}

int main(int argc, char const *argv[])
{<!-- -->
int res = -1;
int index = -1;

struct option argarr[] = {<!-- -->
{<!-- -->"readall",0,NULL,'r'},
{<!-- -->"readloglevel",1,NULL,'R'},
{<!-- -->"write",1,NULL,'w'},
{<!-- -->"help",0,NULL,'h'},
{<!-- -->"Help",0,NULL,'H'},
{<!-- -->NULL, 0, NULL, 0}
};

if(argc < 2) {<!-- -->
print_help();
goto ERROR;
}

 fp = fopen("/proc/mylog", "r + ");
if (!fp) {<!-- -->
        printf("Failed to open /proc/mylog\\
");
        goto ERROR;
    }

res = getopt_long(argc,(char **)argv,"H:w:R:h:r",argarr, & amp;index);
if(res < 0) {<!-- -->
goto ERROR;
}

switch(res)
{<!-- -->
case 'r':
if (fp != NULL) {<!-- -->
read_all_log();
} else {<!-- -->
printf("Failed to open /proc/mylog\\
");
}
break;
case 'R':
if (optarg) {<!-- -->
read_log_by_level(argc,argv);
} else {<!-- -->
printf("Error: no log_level specified\\
");
print_help();
}
break;
case 'w':
if (optarg) {<!-- -->
write_log(argc,argv);
} else {<!-- -->
printf("Error: no log message specified\\
");
print_help();
}
break;
case 'h':
print_help();
break;
case 'H':
print_help();
print_log_level();
break;
default:
printf("Unknown option: %c\\
", optopt);
print_help();
break;
}
\t
ERROR:
if(fp) {<!-- -->
    fclose(fp);
    fp = NULL;
}
return 0;
}

log.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/seq_file.h>

#define LOG_PROC_NAME "mylog"
#define LOG_BUF_SIZE (6 * 1024 * 1024)
#define PER_LOG_SIZE 128
#define STR_TIME_LEN 32

struct log_entry {<!-- -->
    struct list_head list;
    char *msg;
    size_t len;
};

struct log_buffer {<!-- -->
    struct list_head head;
    size_t size;
    size_t used;
};

static struct log_buffer buffer;
static spinlock_t log_buf_lock;

static ssize_t log_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{<!-- -->
    char *msg = NULL;
    struct log_entry *entry = NULL;
    size_t msg_pos = 0;
    size_t len = 0;
    size_t i = 0;
    struct timespec ts = {<!-- -->
        .tv_sec = 0,
        .tv_nsec = 0
    };

    if(count + STR_TIME_LEN > PER_LOG_SIZE) {<!-- -->
        // count = PER_LOG_SIZE;
        goto ERROR;
    }

    msg = kmalloc(count + STR_TIME_LEN + 1, GFP_KERNEL);
    if (!msg) {<!-- -->
        goto ERROR;
    }

    memset(msg,0,count + STR_TIME_LEN + 1);
    ktime_get_ts( &ts);
    sprintf(msg, "[%ld. ld] ", ts. tv_sec, ts. tv_nsec);
    msg_pos = 0;
    msg_pos += strlen(msg);

    if (copy_from_user(msg + msg_pos, buf, count)) {<!-- -->
        goto COPY_ERROR;
    }

    msg[count + STR_TIME_LEN] = '\0';
    len = 0;
    len = strlen(msg) + 1;

    if (buffer.used + len > buffer.size) {<!-- -->
        i = buffer.used;
        while(buffer.used > i/2) {<!-- -->
            entry = list_first_entry( &buffer.head, struct log_entry, list);
            buffer.used -= entry->len;
            list_del( &entry->list);
            kfree(entry->msg);
            entry->msg = NULL;
            kfree(entry);
            entry = NULL;
        }
    }
    
    entry = NULL;
    entry = kmalloc(sizeof(struct log_entry), GFP_KERNEL);
    if (!entry) {<!-- -->
        goto COPY_ERROR;
    }

    entry->msg = msg;
    entry->len = len;
    list_add_tail( & amp;entry->list, & amp;buffer.head);
    buffer.used += len;

    return count;

COPY_ERROR:
    if(msg != NULL) {<!-- -->
        kfree(msg);
        msg = NULL;
    }
ERROR:
    return -1;

}
static int log_list_show(struct seq_file *m, void *v)
{<!-- -->
    struct log_entry *entry = NULL;
    struct log_entry *n = NULL;

    spin_lock( & log_buf_lock);
    list_for_each_entry_safe(entry,n, &buffer.head,list) {<!-- -->
        seq_printf(m, "%s", entry->msg);
    }
    spin_unlock( & log_buf_lock);

    return 0;
}

static int log_list_open(struct inode *inode, struct file *file)
{<!-- -->
    return single_open(file, log_list_show, NULL);
}


static const struct file_operations log_fops = {<!-- -->
    .owner = THIS_MODULE,
    .open = log_list_open,
    .read = seq_read,
    .write = log_write,
};

static int __init log_init(void)
{<!-- -->
    INIT_LIST_HEAD( &buffer.head);
    buffer.size = LOG_BUF_SIZE;
    buffer.used = 0;

    if (!proc_create(LOG_PROC_NAME, 0666, NULL, &log_fops)) {<!-- -->
        printk(KERN_ERR "Failed to create /proc/%s\\
", LOG_PROC_NAME);
        return -ENOMEM;
    }

    spin_lock_init( &log_buf_lock);
    printk(KERN_INFO "log module loaded\\
");

    return 0;
}

static void __exit log_exit(void)
{<!-- -->
    struct log_entry *entry = NULL;
    struct list_head *pos = NULL, *next = NULL;

    list_for_each_safe(pos, next, & buffer.head) {<!-- -->
        entry = list_entry(pos, struct log_entry, list);
        list_del( &entry->list);
        kfree(entry->msg);
        kfree(entry);
    }

    remove_proc_entry(LOG_PROC_NAME, NULL);

    printk(KERN_INFO "log module unloaded\\
");
}

module_init(log_init);
module_exit(log_exit);
MODULE_LICENSE("GPL");

spin_lock

spin_lock is a spin lock in the Linux kernel, used to protect access to shared resources. Unlike mutex, spin_lock does not enter the sleep state when acquiring the lock, but uses a loop to continuously try to acquire the lock, and will not exit the loop until the lock is acquired, so it is also called a spin lock.

The use of spin_lock is as follows:

  1. Call the spin_lock function to acquire the lock before the code segment that needs to be protected.

  2. Call the spin_unlock function at the end of the code segment to release the lock.

The seq-related header file linux/seq_file.h, and the implementation of seq-related functions are in fs/seq_file.c. The seq function was first introduced in 2001, but it was not used much in the kernel before, and after the 2.6 kernel, many read-only files in /proc have used the seq function extensively.

Since the default operation function of procfs only uses one page of cache, it is a bit troublesome when processing large proc files, and it is not flexible when outputting data in a series of structures. You need to implement iteration in the read_proc function yourself. Prone to bugs. So the kernel hackers did research on some /proc codes, abstracted the commonality, and finally formed the seq_file (Sequence file: sequence file) interface. This interface provides a set of simple functions to solve the problems existing in the above proc interface programming, making programming easier and reducing the chance of bugs.

When it is necessary to create a virtual file composed of a sequence of data or a larger virtual file, it is recommended to use the seq_file interface. But I personally think that not only procfs can use this seq_file interface, because in fact seq_file implements a set of operation functions, this function set is not bound to proc, and can also be used in other places.

seq_puts

is a function in the Linux kernel to write a string to the buffer of a sequence file. Sequence files are special files for sequential access to data streams. The seq_puts function takes two arguments: a pointer to the sequence file (m) and a string to write to the file.

seq_read

is a Linux kernel function that reads data from a sequence file and puts it into a userspace buffer

seq_write

is a function in the Linux kernel that is used to write data to sequentially accessed devices

seq_write is a helper function for creating a file in the proc filesystem that supports sequential writing. Its prototype is defined as follows:

ssize_t seq_write(struct seq_file *, const char *, size_t);

The first parameter is a pointer to seq_file, the second parameter is a pointer to the data buffer, and the third parameter is the length of the data.

The return value of the seq_write function is an integer of type ssize_t, indicating the number of bytes written. If the return value is less than 0, the write failed.

When creating a proc file that supports sequential writing, you can use the seq_write function to write data to the file. Usually need to call the seq_write function in the write function of the file to realize the write operation.

single_open

single_open is a helper function that creates a file in the proc filesystem that only supports sequential reads. Its prototype is defined as follows:

int single_open(struct file , int ()(struct seq_file *, void *), void *);

The first parameter is a pointer to the file, the second parameter is a callback function for writing data to seq_file, and the third parameter is a pointer to private data, which can be used to pass some context information to the callback function .

The return value of the single_open function is an integer indicating the status of the function execution. If the return value is less than 0, it means the file creation failed, otherwise it means the file creation is successful.

When creating a proc file that supports sequential reading, you can usually use the single_open function to create the file, and then pass it the callback function and private data. In the callback function, you can use seq_puts, seq_printf and other functions to write data to seq_file. Finally, use the single_release function to release resources.

makefile

obj -m := log.o # ubuntu16


CC=gcc

LD=ld

KDIR := /usr/src/linux-headers-$(shell uname -r)

PWD := $(shell pwd)



all:

$(MAKE) -C $(KDIR) M=$(PWD) modules CC=$(CC) LD=$(LD)

 

clean:

$(MAKE) -C $(KDIR) M=$(PWD) clean

test.sh

#!/bin/sh


 

sudo make



sudo rmmod log.ko

sudo insmod log.ko

 

echo 1 2 > /proc/mylog

cat /proc/mylog


./test.sh

Test:

Run ./test.sh

Read log

Insert log

echo cat test

Reference:

Linux implements files in /proc – Fan Weisheng – Blog Garden (cnblogs.com)

https://www.cnblogs.com/fanweisheng/p/11141527.html

Linux kernel seq_file interface

https://www.cnblogs.com/embedded-linux/p/9751995.html