目录
要求
- 在内核中实现一个环形日志的管理程序,并提供一个写入和读取日志的proc接口。日志缓存6M
- 提供一个命令行工具,写入一句日志和日志等级
- 日志管理程序 将收到的日志内容 写入环形日志缓存中,并追加日志时间。
- 提供一个命令行工具,提供日志遍历的功能。
- 写入日志和读取日志 为同一个命令行工具。
- 驱动模块代码是没有main函数的,这段代码是一个Linux内核模块,它被编译成一个动态链接库,然后加载到内核中运行。
内核模块的使用
加载内核模块:sudo insmod ringlog.ko
在设备文件/dev/bjflog中写入日志信息,例如:
echo "test log" > /proc/bjflog
在proc文件系统中查看日志信息:
cat /proc/bjflog
卸载内核模块:sudo rmmod ringlog
删除设备文件:sudo rm /proc/bjflog
遇到的问题
问题一:
- 这是内核模块代码,其中可能会使用到kmalloc开辟内核空间,开辟空间太多会导致模块加载失败,内核内存不足。
- 可以在使用kmalloc的地方,使用栈内存代替,即不开辟空间,而是直接定义数组并且给大小。
- 因为 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个线程都写入数据了,只是写入的数据是重复的,导致无法区分哪个数据是哪个线程写的,并且导致日志信息顺序混乱。
- 但是这是多线程,RunCmd函数中取地址中的数据,这里是使用的公共资源,也就是说可能会出现这种情况:
- 修改为下面的代码,使用值传递的方式,多线程函数中就没有使用公共资源了,问题成功解决
问题三:写入日志数量与设定不符合
- 原因:在使用接口函数中,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