内核实现环形日志并测试多线程读写

文章描述了一位开发者在Linux内核中实现环形日志管理程序的过程,包括使用内核模块创建proc接口来写入和读取日志。遇到的问题包括kmalloc可能导致的加载失败、多线程写入日志时的顺序混乱以及写入日志数量与设定不符。解决方案涉及使用栈内存代替kmalloc,确保线程安全以及调整日志长度以避免写入错误。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

要求

内核模块的使用

遇到的问题

问题一:

问题二:

问题三:写入日志数量与设定不符合

logPROC目录

ringlog.c文件

ringlog.h文件

Makefile文件

write_read_data目录

main.c文件

Makefile文件

test目录:

test.c文件

test.h文件

Makefile文件


要求

  1. 在内核中实现一个环形日志的管理程序,并提供一个写入和读取日志的proc接口。日志缓存6M
  2. 提供一个命令行工具,写入一句日志和日志等级
  3. 日志管理程序 将收到的日志内容 写入环形日志缓存中,并追加日志时间。
  4. 提供一个命令行工具,提供日志遍历的功能。
    1. 写入日志和读取日志 为同一个命令行工具。
  • 驱动模块代码是没有main函数的,这段代码是一个Linux内核模块,它被编译成一个动态链接库,然后加载到内核中运行。

内核模块的使用

加载内核模块:sudo insmod ringlog.ko
在设备文件/dev/bjflog中写入日志信息,例如:
        echo "test log" > /proc/bjflog
在proc文件系统中查看日志信息:
        cat /proc/bjflog
卸载内核模块:sudo rmmod ringlog
删除设备文件:sudo rm /proc/bjflog

遇到的问题

问题一:

  1. 这是内核模块代码,其中可能会使用到kmalloc开辟内核空间,开辟空间太多会导致模块加载失败,内核内存不足。
    1. 可以在使用kmalloc的地方,使用栈内存代替,即不开辟空间,而是直接定义数组并且给大小。
  2. 因为 VS Code 连接的是本地的 Windows 系统,而不是 Ubuntu 系统,导致编译环境不匹配。会导致下列头文件找不到,但是编译的时候没有问题。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/types.h>

问题二:

多线程向proc文件写入日志时出现问题

  • 写入日志顺序有可能会混乱,但是时间信息是顺序的
  • 使用了5个线程向proc文件写入数据,但是有时只会有两三个线程成功写入数据,其他线程没有写入成功。

问题原因:

  • 创建线程传递参数j时,使用的是取地址的形式,然后在RunCmd函数中取地址中的数字
  • 这个 j 是用来写入日志等级的
    • 但是这是多线程,RunCmd函数中取地址中的数据,这里是使用的公共资源,也就是说可能会出现这种情况:
      • 上一个线程在解引用读取数字时,主进程中又在修改这个数字。这样在多线程写入日志等级时,可能会出现两个线程写入相同的日志等级,所以会出现问题二,只有两三个线程写入数据了,但实际是5个线程都写入数据了,只是写入的数据是重复的,导致无法区分哪个数据是哪个线程写的,并且导致日志信息顺序混乱。
  • 修改为下面的代码,使用值传递的方式,多线程函数中就没有使用公共资源了,问题成功解决

问题三:写入日志数量与设定不符合

  • 原因:在使用接口函数中,sprintf时,我已经添加 \n 了。并两个 %s 之间有空格又占了一个字节。系统会默认补齐最后的 /0

  • 在将数据写入文件时,也会默认补齐 /0 ,所以fwrite时只需要写入前面的字符而不需要写入最后的 /0

  • 这里判断字符串长度不能大于128字节,因为日志信息总长度为128字节,但是最后还有 /n/0两个字节,所以日志信息长度应为126字节,再加上日志等级 0-9的数字,也占一个字节,所以写入proc文件的字符串最多只能是127字节。

logPROC目录

  • 驱动程序,用于创建一个 /proc/bjflog 文件,并重写该文件的 write 和 read。

ringlog.c文件

#include "ringlog.h"

struct log_entry arraybuf[LOG_BUF_SIZE / sizeof(struct log_entry)] = {0};
static struct log_queue logq = {0};

DEFINE_RWLOCK(rwlock);


static ssize_t log_write_proc(struct file *filp,
                         const char __user *buf,
                         size_t count,
                         loff_t *useless)
{
    
    char logmsg[LOG_LEN] = {0};
    int loglevl = 0;

    if (strlen(buf) >= LOG_LEN) goto EXIT;
    if(sscanf(buf, "%d %s", &loglevl, logmsg) != 2) goto EXIT;
    if(loglevl > 9 || loglevl < 1) goto EXIT;
    
    logmsg[strlen(logmsg)+1] = '\0';
    logmsg[strlen(logmsg)] = '\n';

    write_lock(&rwlock); 
    
    if (logq.count == logq.size)
    {
        logq.tail = (logq.tail + 1) % logq.size;
        logq.count--;
    }

    ktime_get_real_ts(&((logq.buf[logq.head]).timestamp));
    (logq.buf[logq.head]).level = loglevl;
    strncpy((logq.buf[logq.head]).msg,logmsg,strlen(logmsg));
    (logq.buf[logq.head]).msg[strlen(logmsg)] = '\0';
    logq.head = (logq.head + 1) % logq.size; 
    logq.count++;
    

    write_unlock(&rwlock); 
    EXIT:
    return count;
}



static int my_proc_show(struct seq_file *m, void *v)
{
    char linedata[sizeof(struct log_entry)*2] = {0};
    int i = 0;
    int tailtemp = 0;
    struct log_entry tempbuf;
    memset(&tempbuf, 0, sizeof(tempbuf));
    struct tm calendar;
    memset(&calendar,0,sizeof(calendar));

    read_lock(&rwlock); 
    
    tailtemp = logq.tail;
    for(i = 0;i<logq.count;i++)  
    {
        tempbuf = logq.buf[(tailtemp++)%logq.size];
        time64_to_tm(tempbuf.timestamp.tv_sec, 0, &calendar); 
        sprintf(linedata, "[%04d-%02d-%02d %02d:%02d:%02d]  [%d]  %s", calendar.tm_year + 1900, calendar.tm_mon + 1, calendar.tm_mday,
                calendar.tm_hour, calendar.tm_min, calendar.tm_sec,tempbuf.level,tempbuf.msg);
        seq_printf(m, linedata);
    }

    read_unlock(&rwlock); 

    return 0;
}


static int log_open_proc (struct inode *node, struct file *file)
{
     return single_open(file, my_proc_show, NULL);
}

static const struct file_operations log_proc_fops = {
    .owner = THIS_MODULE,
    .open = log_open_proc,
    .read = seq_read,
    .write = log_write_proc
};



static int __init log_init(void)
{
    memset(arraybuf, 0, sizeof(arraybuf));

    logq.buf = arraybuf;
    logq.size = LOG_BUF_SIZE / sizeof(struct log_entry);
    logq.head = 0;
    logq.tail = 0;
    logq.count = 0;


    if (proc_create(PROC_NAME, 0666, NULL, &log_proc_fops) == NULL) return -ENOMEM; 
    return 0;
}

static void __exit log_exit(void)
{
    remove_proc_entry(PROC_NAME, NULL); 
    return;
}

MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("YFWL"); 
MODULE_DESCRIPTION("A circular log management program"); 

module_init(log_init); 
module_exit(log_exit); 

ringlog.h文件

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/types.h>
#include <linux/rwlock.h>
#include <linux/ktime.h>


#define LOG_BUF_SIZE (6 * 1024 * 1024)
#define LOG_LEN 128
#define PROC_NAME "bjflog"


struct log_entry {
    struct timespec timestamp; 
    int level; 
    char msg[LOG_LEN];
};

struct log_queue {
    struct log_entry *buf;
    int size; 
    int head; 
    int tail; 
    int count; 
};

Makefile文件


obj-m := ringlog.o

KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean

write_read_data目录

  • 功能:实现一个应用层接口,通过这个接口可以向 /proc/bjflog 文件中写入数据和读取数据

main.c文件

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>

#define LOGLEN 128
#define LOGSUM 256
#define LOGFILE "/proc/bjflog"



void Help()
{
    printf("Note: Log information can only be written in 126 bytes at most.\n\n");
    printf("ProcLog help: This interface must have options.\n");
    printf("    -h: Display help information.\n");
    printf("    -H: Display help information.\n");
    printf("    \tmethod of application one: ./ProcLog -h/-H\n");
    printf("    \tmethod of application two: ./ProcLog -help/-Help\n");

    printf("    -w: Write a log message.\n");
    printf("    \tmethod of application: ./ProcLog -w [Log level] [Log information]\n");

    printf("    -r: Read all log information.\n");
    printf("    -r: method of application: ./ProcLog -r [level]\n");
    printf("    \t Show all logs: ./ProcLog -r 0\n");
    printf("    \t Show one logs: ./ProcLog -r 0/1/2/3/...\n");
    return;
}

int GetLevel(const char* linelog)
{
    
    int count = 0;
    int i = 0;
    char result[11]; 

    while (*linelog != '\0') 
    {
        if (*linelog == '[') {
            count++; 
            if (count == 2) 
            { 
                linelog++; 
                i = 0;
                while (*linelog != ']' && i < 10) 
                {
                    result[i] = *linelog;
                    linelog++;
                    i++;
                }
                result[i] = '\0'; 
                break; 
            }
        }
        linelog++;
    }

    return atoi(result);
}

int WriteFile(int argc,char* argv[]) {
    
    char strlog[LOGSUM] = {0};
    int flags = 0;
    FILE *fp = NULL;
    
    if(argc < 4)
    {
        Help();
        goto EXIT;
    }
    
    fp = fopen(LOGFILE, "w");
    if (fp == NULL) {
        flags = 1;
        goto EXIT;
    }


    sprintf(strlog,"%s%s",argv[2],argv[3]);
    if (fwrite(strlog, strlen(strlog), 1,fp) < 1) 
    {
        flags = 1;
        goto EXIT;
    }

    EXIT:
    if(fp != NULL) fclose(fp);
    return flags;
}


int ReadFile()
{
    int flags = 0;
    char linelog[LOGSUM] = {0};
    int level = 0;
    FILE *fp = NULL;
    
    
    level = atoi(optarg);
    fp = fopen(LOGFILE, "r");
    if (fp == NULL) 
    {
        perror("Failed to fopen file");
        flags = 1;
        goto EXIT;
    }

    while (fgets(linelog, sizeof(linelog), fp) != NULL) 
    {
        if(GetLevel(linelog) == level)  printf("%s", linelog);
        else if(level == 0) printf("%s", linelog);
          
    }

    
    EXIT:
    if(fp != NULL) fclose(fp);
    return flags;
}


int main(int argc,char* argv[])
{    
    
    int opt = -1;
    int longindex = 0;
    int flags = 1;
    char* shortopts = NULL;
    struct option longopts[] = {
        {"help", no_argument, NULL, 'h'},
        {"Help", no_argument, NULL, 'H'},
        {"write", required_argument, NULL, 'w'},
        {"read", required_argument, NULL, 'r'},
    };
    
    shortopts = "hHw:r:";
    while ((opt = getopt_long(argc, argv, shortopts, longopts, &longindex)) != -1) {
        flags = 0;
        switch (opt) {
            case 'h':
                Help();
                break;
            case 'H':
                Help();
                break;
            case 'w':
                if(WriteFile(argc,argv) == 1)
                {
                    perror("WriteFile fail");
                }
                break;
            case 'r':
                if(ReadFile() == 1)
                {
                    perror("ReadFile fail");
                }
                break;
            default:
                Help();
        }
    }
    if(flags == 1) Help();
    
    return 0;
}




Makefile文件

CC=gcc
CFLAGS=-Wall -Wextra -pedantic

all: logdeal

logdeal: main.o
	$(CC) $(CFLAGS) -o $@ $<

main.o: main.c
	$(CC) $(CFLAGS) -c $<

clean:
	rm -f main.o logdeal



test目录:

  • 该目录用于测试write_read_data目录中应用层接口的功能
  • 实现了5个线程同时向创建的 proc/bjflog 文件写入数据

test.c文件

#include "test.h"

void *RunCmd(void *arg) 
{
    int level = (int)arg;
    char command[CMD_LEN] = {0};
    char logdata[LOGDATA] = {0};
    int cycles = BIG_DATA;

    for(int i = 0; i < cycles; i++)
    {
        sprintf(logdata, "%s----%d", "aaaaaaaaaaaa", i);
        sprintf(command, "%s %s %d %s", CMD, OPTION_W, level, logdata);
        system(command);
    }

    pthread_exit(NULL);
}

int main() 
{
    pthread_t thread_id[PTHREAD_NUM] = {0};
    int i = 0;
    int j = 1;
    for(i = 0; i < PTHREAD_NUM; i++)
    {
        pthread_create(&thread_id[i], NULL, RunCmd, j);
        j++;
    }
    
    for(i = 0; i < PTHREAD_NUM; i++)
    {
        pthread_join(thread_id[i], NULL);
    }
    
    return 0;
}


test.h文件

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <time.h>



#define CMD "./../write_data/logdeal"
#define OPTION_W "-w"
#define CMD_LEN 256
#define LOGDATA 128
#define BIG_DATA 1e6
#define PTHREAD_NUM 5

Makefile文件

CC=gcc
CFLAGS=-Wall -Wextra -pedantic -pthread

all: test

test: test.o
	$(CC) $(CFLAGS) -o $@ $<

test.o: test.c
	$(CC) $(CFLAGS) -c $<

clean:
	rm -f test.o test

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值