|
|||||||
13. Замена макроса Print
Время создания: 20.09.2022 09:54
Текстовые метки: linux, ядро, модуль, программирование, язык, C, Си, пособие, документация
Раздел: Компьютер - Linux - Ядро - Пособие по программированию модулей ядра Linux
Запись: xintrea/mytetra_syncro/master/base/1663656856454uyl27hd/text.html на raw.github.com
|
|||||||
|
|||||||
13. Замена макроса Print ▍ 13.1 ЗаменаВ разделе 2 я говорил, что программировать модули ядра через X Window System не желательно. Это верно относительно именно разработки модулей, но при фактическом использовании нам нужна возможность отправлять сообщения на любой tty, с которого поступила команда на загрузку модуля. Аббревиатура tty означает телетайп, устройство, которое в своем изначальном виде представляло совмещенную с принтером клавиатуру, используемую для взаимодействия с системой Unix. В современном же представлении телетайп является абстракцией текстового потока, используемой программой Unix, будь то физический терминал, xterm на X-сервере, сетевое подключение через ssh или нечто аналогичное. Реализуется это с помощью current, указателя на выполняемую в данный момент задачу, позволяющего получить tty-структуру этой задачи. Затем эта структура просматривается в поиске указателя на строковую функцию write, которая используется для записи строки в данный tty. print_string.c /* * print_string.c – отправляет вывод на tty, с которого мы работаем, * будь то через X11, telnet и т.д. Для этого мы выводим строку на * tty, связанный с текущей задачей. */ #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/sched.h> /* Для current. */ #include <linux/tty.h> /* Для объявлений tty. */
static void print_string(char *str) { /* tty для текущей задачи. */ struct tty_struct *my_tty = get_current_tty();
/* Если my_tty равен NULL, значит у текущей задачи нет tty, куда * можно было бы произвести вывод (например, если это демон). В таком * случае ничего не поделаешь. */ if (my_tty) { const struct tty_operations *ttyops = my_tty->driver->ops; /* my_tty->driver – это структура, где расположены функции tty, * одна из которых (write) используется для записи строк в tty. * С помощью нее можно извлекать строки из сегментов пространства памяти ядра или * пользователя. * * Первый параметр этой функции устанавливает tty, куда нужно * производить запись, потому что одна и та же функция служит * для записи во все tty определенного типа. * Второй параметр – это указатель на строку. * Третий параметр устанавливает длину строки. * * Как вы увидите ниже, иногда необходимо использовать функционал * препроцессора, чтобы получить код, работающий для различных * версий ядра. Реализованный нами здесь наивный подход плохо * масштабируется. Правильный способ решения этой проблемы описан * в разделе 2 документации: linux/Documentation/SubmittingPatches */ (ttyops->write)(my_tty, /* Сам tty. */ str, /* Строка. */ strlen(str)); /* Длина. */
/* Изначально телетайпы были аппаратными и, как правило, строго * следовали стандарту ASCII. В ASCII для перехода на новую строку * необходимо два символа, возврат каретки и перевод строки. В * Unix перевод строки ASCII задействуется для того и другого, * поэтому нельзя просто использовать \n, так как возврата * каретки не произойдет, и следующая строка начнется в столбце, * идущим сразу за переводом строки. * * Именно поэтому в Unix и MS Windows текстовые файлы отличаются. * В CP/M и ее производных вроде MS-DOS и MS Windows текст строго * подчиняется стандарту ASCII, в связи с чем для перехода на * новую строку требуется и LF, и CR. */ (ttyops->write)(my_tty, "\015\012", 2); } }
static int __init print_string_init(void) { print_string("The module has been inserted. Hello world!"); return 0; }
static void __exit print_string_exit(void) { print_string("The module has been removed. Farewell world!"); }
module_init(print_string_init); module_exit(print_string_exit);
MODULE_LICENSE("GPL"); ▍ 13.2 Мигание светодиодами клавиатурыВ определенных условиях вы можете предпочесть более простой и непосредственный способ связи с внешним миром. Решением в таком случае может стать мигание светодиодами клавиатуры. Это прямой способ привлечь внимание или продемонстрировать некое состояние. Светодиоды есть у любой клавиатуры, они всегда на виду, не требуют настройки и очень просты в использовании, если сравнивать с записью в tty или файл. В v4.14 и v.4.15 в API таймера произошел ряд изменений, нацеленных на повышение безопасности памяти. Переполнение буфера в области структуры timer_list может привести к перезаписи полей function и data, предоставив атакующему возможность с помощью возвратно-объектного программирование вызывать произвольные функции в ядре. Кроме того, прототип функции обратного вызова, содержащий аргумент unsigned long, полностью исключит возможность проверки типов. Плюс такой прототип может помешать защитить косвенные переходы и вызовы (forward-edge) с помощью сохранения целостности потока управления (CFI). Поэтому лучше использовать уникальный прототип, чтобы отделиться от кластера, который получает аргумент unsigned long. В обратный вызов таймера необходимо передавать не аргумент unsigned long, а указатель на структуру timer_list. Тогда он объединит всю необходимую ему информацию, включая структуру timer_list, в более обширную структуру и сможет использовать вместо значения unsigned long макрос container_of. Более развернуто эта тема описана в статье Improving the kernel timers API. До Linux v4.14 инициализация таймеров производилась с помощью setup_timer, а структура timer_list выглядела так: struct timer_list { unsigned long expires; void (*function)(unsigned long); unsigned long data; u32 flags; /* ... */ };
void setup_timer(struct timer_list *timer, void (*callback)(unsigned long), unsigned long data); В Linux v4.14 появилась timer_setup, и ядро постепенно перестроилось с setup_timer на timer_setup. Одна из причин изменения API заключалась в потребности сосуществования с интерфейсом старых версий. Более того, по началу timer_setup реализовывалась через setup_timer. void timer_setup(struct timer_list *timer, void (*callback)(struct timer_list *), unsigned int flags); Позднее в v4.15 setup_timer удалили, что также отразилось на облике структуры timer_list: struct timer_list { unsigned long expires; void (*function)(struct timer_list *); u32 flags; /* ... */ }; Приведенный ниже код демонстрирует минимальный модуль ядра, который после загрузки начинает мигать светодиодами до тех пор, пока не будет выгружен. kbleds.c /* * kbleds.c – мигает светодиодами клавиатуры, пока не будет выгружен. */
#include <linux/init.h> #include <linux/kd.h> /* Для KDSETLED. */ #include <linux/module.h> #include <linux/tty.h> /* Для tty_struct. */ #include <linux/vt.h> /* Для MAX_NR_CONSOLES. */ #include <linux/vt_kern.h> /* Для fg_console. */ #include <linux/console_struct.h> /* Для vc_cons. */
MODULE_DESCRIPTION("Example module illustrating the use of Keyboard LEDs.");
static struct timer_list my_timer; static struct tty_driver *my_driver; static unsigned long kbledstatus = 0;
#define BLINK_DELAY HZ / 5 #define ALL_LEDS_ON 0x07 #define RESTORE_LEDS 0xFF
/* Функция my_timer_func периодически мигает светодиодами, * вызывая для драйвера клавиатуры команду управления вводом-выводом * KDSETLED. Дополнительную информацию по командам ввода-вывода * смотрите в функции vt_ioctl() файла drivers/tty/vt/vt_ioctl.c. * * Аргумент KDSETLED попеременно устанавливается то на 7 (что приводит к * активации режима LED_SHOW_IOCTL и загоранию всех светодиодов), то на * 0xFF (любое значение выше 7 переключает режим обратно на * LED_SHOW_FLAGS, в результате чего светодиоды отображают фактический * статус клавиатуры). Подробности смотрите в функции setledstate() файла * drivers/tty/vt/keyboard.c. */ static void my_timer_func(struct timer_list *unused) { struct tty_struct *t = vc_cons[fg_console].d->port.tty;
if (kbledstatus == ALL_LEDS_ON) kbledstatus = RESTORE_LEDS; else kbledstatus = ALL_LEDS_ON;
(my_driver->ops->ioctl)(t, KDSETLED, kbledstatus);
my_timer.expires = jiffies + BLINK_DELAY; add_timer(&my_timer); }
static int __init kbleds_init(void) { int i;
pr_info("kbleds: loading\n"); pr_info("kbleds: fgconsole is %x\n", fg_console); for (i = 0; i < MAX_NR_CONSOLES; i++) { if (!vc_cons[i].d) break; pr_info("poet_atkm: console[%i/%i] #%i, tty %p\n", i, MAX_NR_CONSOLES, vc_cons[i].d->vc_num, (void *)vc_cons[i].d->port.tty); } pr_info("kbleds: finished scanning consoles\n");
my_driver = vc_cons[fg_console].d->port.tty->driver; pr_info("kbleds: tty driver magic %x\n", my_driver->magic);
/* Первая настройка таймера мигания светодиодов. */ timer_setup(&my_timer, my_timer_func, 0); my_timer.expires = jiffies + BLINK_DELAY; add_timer(&my_timer);
return 0; }
static void __exit kbleds_cleanup(void) { pr_info("kbleds: unloading...\n"); del_timer(&my_timer); (my_driver->ops->ioctl)(vc_cons[fg_console].d->port.tty, KDSETLED, RESTORE_LEDS); }
module_init(kbleds_init); module_exit(kbleds_cleanup);
MODULE_LICENSE("GPL"); Если ни один из приведенных в этой главе примеров не подходит под ваши отладочные нужды, то наверняка есть другие решения. Не задумывались, для чего может быть полезна CONFIG_LL_DEBUG из menu menuconfig? В случае ее активации вы получаете низкоуровневый доступ к последовательному порту. И хотя это может не показаться особо полезным, такой прием позволяет пропатчить kernel/printk.c или любой другой важный системный вызов для печати символов ASCII, делая возможным отслеживание практически всех действий кода на последовательном порту. Если вы займетесь портированием ядра на новую, ранее неподдерживаемую архитектуру, то реализация этого решения должна идти одной из первых. Также можно рассмотреть вариант логирования через netconsole. Несмотря на множество рассмотренных здесь отладочных приемов, кое-что нужно иметь ввиду. Отладка практически всегда оказывается интрузивной процедурой. Добавление отладочного кода может привести к тому, что ошибка, на первый взгляд, исчезнет. Поэтому такой код нужно минимизировать и следить, чтобы он не попал в продакшн-код. |
|||||||
Так же в этом разделе:
|
|||||||
|
|||||||
|