Как добавить сообщение, которое будет прочитано с помощью dmesg?

Вопрос или проблема

Я пытаюсь написать несколько пользовательских сообщений в вывод dmesg. Я попробовал:

logger "Hello"

но это не работает. Он завершает работу без ошибок, но “Hello” не появляется в выводе:

dmesg

Я использую Fedora 9, и, похоже, что демон syslogd/klogd не запущен. Однако все мои сообщения ядра успешно записываются в буфер dmesg.

Есть идеи?

dmesg отображает содержимое буфера ядра, в то время как logger предназначен для syslogd. Если вы хотите записать что-то в буфер ядра, вам нужно создать драйвер, который использует функцию printk(). Если же вам нужно просто в /var/log/messages, то в “нормальной” конфигурации то, что вы сделали с logger, уже вполне нормально.

Самый простой пример драйвера с printk() будет следующим:

hello.c:

#include <linux/module.h>
#include <linux/kernel.h>

int init_module(void)
{
    printk(KERN_INFO "Hello world\n");
    return 0;
}

void cleanup_module(void)
{
    printk(KERN_INFO "Goodbye world\n");

}

Makefile:

obj-m += hello.o

all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

Затем:

$ make
$ sudo insmod hello.ko
$ dmesg | tail -n1
 [7089996.746366] Hello world

http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN121 для получения дополнительной информации…

Вы можете, будучи root, записывать в /dev/kmsg, чтобы выводить в буфер сообщений ядра:

 fixnum:~# echo Some message > /dev/kmsg
 fixnum:~# dmesg | tail -n1
 [28078118.692242] Some message

Я протестировал это на своем сервере и встроенном Linux-устройстве, и это работает на обоих, так что я просто предполагаю, что это работает практически везде.

Основываясь на модуле Кайла:


#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

static int pk_write(struct file *file, const char *buffer, unsigned long count, void *data)
{
        char string[256];
        count = count < 255 ? count : 255;

        if(copy_from_user(string, buffer, count))
                return -EFAULT;

        string[count] = '\0';        
        printk(string);
        return count;
}

static int __init printk_init(void)
{
        struct proc_dir_entry *pk_file;

        pk_file = create_proc_entry("printk", 0222, NULL);
        if(pk_file == NULL)
                return -ENOMEM;

        pk_file->write_proc = pk_write;
        pk_file->owner = THIS_MODULE;

        return 0;
}

static void __exit printk_cleanup(void)
{
        remove_proc_entry("printk", NULL);
}

module_init(printk_init);
module_exit(printk_cleanup);
MODULE_LICENSE("GPL");

Чтобы сделать printk из пользовательского пространства:

echo "Hello" > /proc/printk

Ответ @Calandoa больше не работает для ядра +3.10. Объединил его код и пример кода, который я нашел здесь. Затем улучшил качество кода…

Код сохранен в printk_user.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>

static ssize_t write_proc(struct file *filep, const char *buffer, size_t count, loff_t *offsetp)
{
    char string[256];
    count = count < 255 ? count : 255;

    if(copy_from_user(string, buffer, count) != 0) {
        return -EFAULT;
    }

    string[count] = '\0';
    printk(string);
    return count;
}

static const struct file_operations proc_fops = {
    .owner = THIS_MODULE,
    .write = write_proc,
};

static int proc_init(void) {
    struct proc_dir_entry *proc_file;
    proc_file = proc_create("printk_user", 0, NULL, &proc_fops);

    if(proc_file == NULL) {
        return -ENOMEM;
    }

    return 0;
}

static void proc_cleanup(void) {
    remove_proc_entry("printk_user", NULL);
}

MODULE_LICENSE("GPL"); 
module_init(proc_init);
module_exit(proc_cleanup);

Сделайте это с помощью следующего Makefile

TARGET = printk_user
obj-m := $(TARGET).o

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

printk:
$(MAKE) -C $(KDIR) M=$(PWD) modules

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

Вам нужно установить исходный код текущего работающего ядра, чтобы сделать это.
После того, как вы выполните make, вам нужно будет sudo insmod printk_user.ko, чтобы загрузить модуль ядра.
Модуль создает /proc/printk_user, по умолчанию с правами 444.
Если вам нужно, чтобы все пользователи имели доступ, измените с помощью sudo chmod 666 /proc/printk_user

Чтобы сделать printk из пользовательского пространства:

echo "Hello" > /proc/printk_user

Пример вывода:

$ dmesg | tail
[13291030.939275] printk_user: загружается модуль, не входящий в дерево, который загрязняет ядро.
[13291030.939862] printk_user: проверка модуля не удалась: отсутствует подпись и/или необходимый ключ - загрязняющее ядро
[13291103.728306] Hello

редактировать: /proc/printk ранее использовался для ядра 3/4, но, похоже, что последнее ядро поддерживает /proc/printk_user

Основываясь на ответе Кайла, здесь есть краткое руководство, показывающее, как это сделать.

Я подумал, что включу полный пример чего-то, что люди могут просто скомпилировать и запустить для тех, кто не так хорошо разбирается в C, исходя из ответа @BuvinJ

#include <stdio.h>
#include <string.h>
#include <fcntl.h> // функция open
#include <unistd.h> // функция close
#include "sys/syscall.h"

int main(); // Не будем об этом сейчас беспокоиться

void dmesg( const char *tag, const char *msg, const int len )
{
    const int TAG_LEN=3;
    char buffer[128]={0};
    memcpy( &buffer[0], tag, TAG_LEN );
    memcpy( &buffer[TAG_LEN], msg, len );
    int fd_kmsg = open( "/dev/kmsg", O_WRONLY );
    write( fd_kmsg, &buffer, TAG_LEN+len );
    close( fd_kmsg );
}
void dmesgWarn(  const char *msg, const int len ){ dmesg( "<4>", msg, len ); }
void dmesgInfo(  const char *msg, const int len ){ dmesg( "<6>", msg, len ); }
void dmesgDebug( const char *msg, const int len ){ dmesg( "<7>", msg, len ); }

int main(int argc, char **argv)
{
    int getmysize = strlen(argv[1]);
    printf("%d\n", getmysize);

    printf("To be written: %s\nSize of argument: %d\n", argv[1], getmysize);
    // dmesgWarn dmesgInfo или dmesgDebug
    dmesgDebug(argv[1], getmysize);
};

Чтобы запустить, сохраните приведенное выше в виде kmsg.c, затем выполните gcc kmsg.c -o kmsg и запустите с sudo ./kmsg "строка, которую вы хотите добавить в /dev/kmsg"

Я просто хотел несколько быстрых сообщений для отладки в демоне, написанном кем-то другим, в кросс-компилированном ядре. Я столкнулся с ошибкой компиляции, пытаясь использовать printk, так как <linux/module.h> не мог быть включен. Вместо того чтобы чрезмерно бороться с этим (чтобы сделать это правильно), я обошел это с помощью следующего ленивого, но функционального обходного пути:

void dmesg( const char *tag, const char *msg, const int len )
{
    size_t taglen = strlen(tag);
    char buffer[taglen + len];
    memcpy(&buffer[0], tag, taglen);
    memcpy(&buffer[taglen], msg, len);
    int fd_kmsg = open("/dev/kmsg", O_WRONLY);
    write(fd_kmsg, &buffer, TAG_LEN + len);
    close(fd_kmsg);
}
void dmesgWarn(const char *msg, const int len) { dmesg("<4>", msg, len); }
void dmesgInfo(const char *msg, const int len) { dmesg("<6>", msg, len); }
void dmesgDebug(const char *msg, const int len) { dmesg("<7>", msg, len); }

ОБНОВЛЕНИЕ (Спасибо @glglgl!)

Куда более простая версия может выглядеть так:

void dmesg( const unsigned int tag, const char *msg)
{
    size_t msglen = sprintf(NULL, "<%u>%s", tag, msg);
    char buffer[msglen + 1];
    sprintf(buffer, "<%u>%s", tag, msg);
    // snprintf(buffer, sizeof(buffer), "<%u>%s", tag, msg);
    // было бы безопаснее, но здесь мы убеждаемся, что все работает как должно.
    int fd_kmsg = open("/dev/kmsg", O_WRONLY);
    write(fd_kmsg, &buffer, msglen);
    close(fd_kmsg);
}
void dmesgWarn(const char *msg) { dmesg(4, msg); }
void dmesgInfo(const char *msg) { dmesg(6, msg); }
void dmesgDebug(const char *msg) { dmesg(7, msg); }

Теперь он просто принимает строки, интегрирует их в сообщение, которое будет записано в этот файл, и пишет его.

Теперь, когда мы об этом говорим, это может быть еще легче:

void dmesg( const unsigned int tag, const char *msg)
{
    int fd_kmsg = open("/dev/kmsg", O_WRONLY);
    FILE * f_kmsg = fdopen(fd_kmsg, "w");
    fprintf(f_kmsg, "<%u>%s", tag, msg);
    fclose(f_kmsg); // также закрывает fd_kmsg
}
void dmesgWarn(const char *msg) { dmesg(4, msg); }
void dmesgInfo(const char *msg) { dmesg(6, msg); }
void dmesgDebug(const char *msg) { dmesg(7, msg); }

В случае, если это может помочь кому-то, кто имеет дело с этой темой dmesg / журналы ядра из пользовательского пространства, но с взаимодействием с инструментами более высокого уровня, такими как journald/journalctl и loki/promtail.

В дополнение к предыдущему комментарию Как добавить сообщение, которое будет прочитано с помощью dmesg?,

Я бы рекомендовал использовать такую форму:

echo "<2>kernel: EXT4-fs (sda2): I/O error while writing superblock" > /dev/kmsg

С <i> как уровень серьезности и kernel: как специфическая метка, перед любой дополнительной меткой “подсистемы” ядра.

Используя метку ядра, journalctl не будет сообщать журнал как выданный службой “неизвестно”.
И в некоторых случаях это также может помочь правильно интегрировать ваш журнал с такими инструментами, как Loki/Promtail.

Ответ или решение

Чтобы добавить сообщения, которые будут отображаться в выводе команды dmesg, необходимо взаимодействовать с буфером сообщений ядра Linux. Из пользовательского пространства можно использовать различные методы для записи сообщений в этот буфер.

Способы добавления сообщений в dmesg

  1. Использование /dev/kmsg:
    Вы можете записывать сообщения непосредственно в специальный файл устройства /dev/kmsg. Это самый простой способ добавления сообщений в dmesg без необходимости писать ядро:

    echo "Ваше сообщение" > /dev/kmsg

    Пример:

    echo "Hello from user space" > /dev/kmsg
    dmesg | tail -n 10

    Вывод команды dmesg должен включать ваше сообщение.

  2. Создание модуля ядра:
    Если у вас есть необходимость добавлять сообщения в dmesg через модуль ядра, вы можете использовать функцию printk(). Вот пример простого модуля:

    #include <linux/module.h>
    #include <linux/kernel.h>
    
    int init_module(void)
    {
       printk(KERN_INFO "Hello from kernel module\n");
       return 0;
    }
    
    void cleanup_module(void)
    {
       printk(KERN_INFO "Goodbye from kernel module\n");
    }
    
    MODULE_LICENSE("GPL");

    Сохраните этот код в файл (например, hello.c) и создайте файл Makefile:

    obj-m += hello.o
    
    all:
       make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

    После этого выполните команды:

    make
    sudo insmod hello.ko
    dmesg | tail -n 10

    Не забудьте удалить модуль после завершения:

    sudo rmmod hello
  3. Использование пользовательского приложения:
    Вы также можете написать простую программу на C, записывающую сообщения в dmesg. Вот пример:

    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <unistd.h>
    
    void log_to_dmesg(const char *msg) {
       int fd = open("/dev/kmsg", O_WRONLY);
       if (fd < 0) {
           perror("open");
           return;
       }
       write(fd, msg, strlen(msg));
       close(fd);
    }
    
    int main(int argc, char **argv) {
       if (argc > 1) {
           log_to_dmesg(argv[1]);
       } else {
           printf("Usage: %s <message>\n", argv[0]);
       }
       return 0;
    }

    Скомпилируйте программу:

    gcc your_program.c -o your_program

    Запустите с параметром:

    sudo ./your_program "Hello from my application"

Заключение

Используя указанные методы, вы можете эффективно добавлять сообщения в dmesg. Для простоты записи в буфер сообщений ядра рекомендуется использовать /dev/kmsg. Если же вам нужно более специализированное решение, рассмотрите возможность написания собственного модуля ядра.

Оцените материал
Добавить комментарий

Капча загружается...