В одной из лабораторных по организации ЭВМ и систем, от нас требовалось поработать с системой прерываний. Многие делали лабораторные по методичке под DosBox (рассчитаны они на DOS), но такой путь не для меня. Я решил сделать это всё правильно, то есть так, чтобы получить навыки, которые с большей вероятностью мне пригодятся. Короче говоря, я решил сделать её под Linux.
Первое и самое важное, система прерываний в чистом виде там вполне себе живёт и процветает, а доступ к ней получить несравненно легче, нежели в одной (кхе-кхе) известной операционной системе. Однако она всё равно не настолько интуитивна, как под каким-нибудь DOS, так как доступ к прерываниям есть только в пространстве функций ядра.
Тем не менее мы относительно безболезненно можем получить к ней доступ, потому что есть отдельный тип программ, которые работают в пространстве ядра. Я говорю, разумеется, про модули ядра. Из минусов: адекватной документации по ним кот наплакал. Из плюсов: есть неплохая книжка, в которой всё очень хорошо.
Начнём с того, без чего не обходится ни одна серьёзная программа под Linux. Makefile. Это один из примеров стандартного мэйкфайла для сборки модуля ядра. Знаю ли я, что конкретно он делает? Нет. Беспокоит ли меня это? Нет. Понадобится - разберусь.
obj-m += keyb_int.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Совершенно непрофессионально, я знаю. Но сроки поджимали и больший приоритет имел исходный код модуля, а также то, что там использовалось. Начнём с простого. В исходниках ядра есть вот такая прелестная пара макросов.
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);Делают они ровно то, что написано. Инициализируют модуль при его подключении и чистят память при отключении. Рассмотрим реализации соответствующих функций по порядку:
/* Initialize the module - register the IRQ handler */
static int __init InterruptScancode_init(void)
{
printk(KERN_INFO "Init custom keyboard interrupt handler\n");
int result;
/* Request IRQ 1, the keyboard IRQ */
result = request_irq(1,
(irq_handler_t) irq_handler,
IRQF_SHARED,
"keyboard_stats_irq",
(void *)(irq_handler));
if (result)
printk(KERN_INFO "can't get shared interrupt for keyboard\n");
return result;
}Вроде всё простенько и миленько. printk пишет в логи, и как показал опрос, не совсем понятно только одно, что есть request_irq. Это добро как раз уведомляет ядро, что мы тут завели очередной обработчик прерываний на линию 1 (клавиатура), которую мы хотим использовать совместно с другими обработчиками (IRQF_SHARED), что работает только если все обработчики шарят прерывание. Славно.
На выход из модуля всё ещё проще. Даже и пояснять нечего.
static void __exit InterruptScancode_exit(void)
{
printk(KERN_INFO "Exit custom keyboard interrupt handler\n");
/* Shared interrupt, so no passing NULL */
free_irq(1, (void *)(irq_handler));
}free_irq говорит ядру, что больше этот обработчик не юзается, да чистит память. Славно.
Сам же обработчик тоже не отличается особой сложностью
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
unsigned char scancode;
/* Read scancode */
scancode = inb(0x60);
got_char(scancode);
return IRQ_HANDLED;
}got_char - это всего лишь
static void got_char(unsigned char scancode)
{
printk("Scan Code %x %s.\n",
(int) scancode & 0x7F,
scancode & 0x80 ? "Released" : "Pressed");
}
Включение модуля
Отключение модуля
P.S. Сырцы про которые я забыл.