Ori Pomerantz

Энциклопедия разработчика модулей ядра Linux

Имена всех изделий и программ здесь используются только для целей идентификации. Торговые марки изготовителя и/или зарегистрированные марки изготовителя принадлежат их владельцам. Я не делаю никакого требования монопольного использования или общей ассоциации с изделиями, программами и компаниями, которые обладают ими.

Введение

Итак, Вы хотите писать модули для ядра. Вы знаете C, вы написали ряд нормальных программ, выполяемых как процессы, и теперь Вы хотите добраться туда, где происходит реальное действие, туда, где один ошибочный указатель может стереть вашу файловую систему или привести к перезагрузке.

Хорошо, добро пожаловать в клуб. Я однажды имел такой указатель, который стер мне важный каталог под DOS, и я не вижу, почему Linux должна быть более безопасной.

Предупреждение: я написал это и проверил программу под версиями 2.0.35 и 2.2.3 ядра, запущенного на Pentium. Главным образом это должно работать на других CPU и на других версиях ядра, по крайней мере версий 2.0.x или 2.2.x, но я не могу обещать что-нибудь. Одна исключительная ситуация: глава 11, которая не должна работать на архитектуре не x86.

Кто должен читать это

Этот документ для тех, кто хочет писать модули ядра. Хотя я буду касаться в нескольких местах того, как многие задачи выполнены в ядре, это не моя цель. Имеется достаточно много хороших источников, авторы которых проделали работу лучшую чем та, которую я мог бы сделать.

Этот документ также для людей, которые знают как писать модули ядра, но еще не адаптировались к версии 2.2. Если Вы такой человек, я предлагаю, Вам прочитать приложение A, чтобы увидеть все различия, с которыми я столкнулся при модифицировании примеров. Список не всесторонний, но я думаю, что он покрывает большинство базисных функциональных возможностей и его будет достаточно для начала.

Ядро имеет большое количество программирования, и я полагаю, что программисты должны читать по крайней мере некоторые его исходные файлы и понимать их. Сказав это, я также верю в значение игры с системой сначала и выяснением вопросов позже. Когда я узнаю новый язык программирования, я не начинаюсь с чтения библиотечного кода, а пишу маленькую программу `hello, world'. Я не вижу, почему начинающий разбираться с ядром должен быть действовать иначе.

Замечания о стиле

Я нахожу приятным поместить так много шуток в мою документацию, насколько возможно. Я пишу это, потому что я наслаждаюсь этим, и я считаю, что Вы читаете это по той же самой причине. Если Вы хотите получить только суть, игнорируйте весь нормальный текст и читайте исходники. Я обещаю помещать все важные детали в замечаниях.

Изменения

Новое в версии 1.0.1

1. Раздел изменений, 0.3.

2. Как найти младший номер устройства, 2.

3. Введено объяснение различия между символом и файлами устройства, 2

4. Makefile'ы для модулей ядра, 1.1.

5. Симметричная многопроцессорность, 12.

6. Глава `Плохие идеи', 13.

Новое в версии 1.1.0

1. Поддержка версии 2.2 ядра во всех главах.

2. Исходный код примеров для разных версий ядра, 2.1.

3. Разница между версиями 2.0 и 2.2, A.

4. Модули ядра в нескольких файлах исходников, 1.2.

5. Предложение, как избежать беспорядка с системными вызовами при выдаче команды rmmod, 7.

Благодарности

Я хотел бы благодарить Weiss за многие полезные идеи и обсуждения, также как и за поиск ошибок внутри этого документа перед публикацией. Конечно, любые оставшиеся ошибки только моя вина.

TEX скелет для этой книги был бесстыдно захвачен из руководства `Linux Installation and Getting Started', где работа в TEX была выполнена Matt Welsh.

Моя благодарность Linus Torvalds, Richard Stallman и всем другим людям, кто сделали возможным для меня выполнить операционную систему высокого качества на моем компьютере и получить ее исходный текст, как нечто само собой разумеющееся (да, право, зачем я говорю это?).

Благодарности к версии 1.0.1

Я не могу внести в список каждого, кто послал по e-mail мне сообщение, и если я не вписал именно Вас, я приношу извинения заранее. Следующие люди были особенно полезны:

• Frodo Looijaard из Нидерландов За сервер с кучей информации и полезных советов по ядрам версий 2.1.x.

• Stephen Judd из Новой Зеландии За правку орфографии.

• Magnus Ahltorp из Швеции За исправления, касательно разницы между символьными и блочными устройствами.

Благодарности к версии 1.1.0

• Emmanuel Papirakis из Квебека, Канада за перенос всех примеров в версию 2.2 ядра.

• Frodo Looijaard из Нидерландов за сообщение как создать многофайловый модуль ядра (1.2).

Конечно, любые оставшиеся ошибки мои собственные, и если Вы думаете, что они делают книгу непригодной, требуйте полного возврата денег, которые Вы заплатили за книгу.

Hello, world

Традиционно все учебники программирования начинаются с программы "Hello, world!". Я не знаю, что случается с людьми, которые порывают с этой традицией, и думаю, что безопаснее не выяснять.

Модуль ядра (в дальнейшем просто модуль для краткости) должен иметь по крайней мере две функции: init_module, которая вызывается, когда модуль вставляется в ядро и cleanup_module, которая вызывается, когда он удаляется. Обычно init_module регистрирует драйвер для каких-либо действий с ядром или заменяет одну из ядерных функций собственным кодом (обычно код делает что-то и затем вызывает первоначальную функцию). Функция cleanup_module, как предполагается, отменяет все, что сделано init_module, так что модуль может быть выгружен безопасно.

hello.c

/* hello.c

* Copyright (C) 1998 by Ori Pomerantz

*

* "Hello, world" - версия для модуля ядра.

*/

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Initialize the module */

int init_module() {

printk("Hello, world - this is the kernel speaking\n");

/* If we return a non zero value, it means that

* init_module failed and the kernel module

* can't be loaded */

return 0;

}

/* Cleanup - undid whatever init_module did */

void cleanup_module() {

printk("Short is the life of a kernel module\n");

}

Makefile'ы для модулей ядра

Модуль не яявляется независимой программой, а представляет собой объектный файл, который будет прилинкован к ядру во время выполнения. В результате, они должны компилироваться с опцией -c. Все модули должны компилироваться с некоторыми определенными символами.

• __KERNEL__ — Этот символ сообщает файлам заголовка, что этот код будет выполнен в ядерном режиме (нравится мне такое определение), а не как часть процесса пользователя.

• MODULE — Этот символ сообщает файлам заголовка, что надо дать соответствующие определения для модуля.

• LINUX — Технически это не необходимо. Однако, если Вы когда-либо захотите написать серьезный модуль, который компилируется на больше чем одной операционной системе, вы будете счастливы, что Вы сделали данное определение. Это позволит Вам делать условную трансляцию частей, которые являются OS-зависимыми.

Имеются другие символы, которые должны быть включены или наоборот выключены в зависимости от параметров с которыми компилировалось ядро. Если вы не уверены, как ядро компилировалось, посмотрите в /usr/include/linux/config.h

• __SMP__ — Симметричная многопроцессорная обработка. Этот символ должен быть определен, если Вы компилируете модуль для ядра, которое было скомпилировано с опцией «Поддержка SMP» (даже если работать оно будет на однопроцессорной машине). Если Вы используете симметричную многопроцессорную обработку, имеются другие хитрости, которые Вы должны предусмотреть (см. главу 12).

• CONFIG_MODVERSIONS — Если CONFIG_MODVERSIONS разрешен, Вы должны иметь определить его при компиляции модуля и включить /usr/include/linux/modversions.h. Это может быть также выполнено кодом непосредственно.

Makefile

# Makefile для базисного ядерного модуля

CC=gcc

MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX

hello.o:        hello.c /usr/include/linux/version.h

$(CC) $(MODCFLAGS) -c hello.c

echo insmod hello.o to turn it on

echo rmmod hello to turn if off

echo

echo X and kernel programming do not mix.

echo Do the insmod and rmmod from outside X

Так, теперь единственное, что надо сделать, это выполнить su, чтобы зайти как root (Вы не компилировали модуль как root, не так ли?[1]) Теперь скомандуйте insmod hello и rmmod hello. Когда Вы даете эти команды, обратите внимание на Ваш новый модуль в /proc/modules.

Между прочим, причина, почему Makefile предостерегает против выполнения из X в том, что когда ядро имеет сообщение, чтобы печатать его с помощью printk, оно посылает его на консоль. Когда Вы не используете X, оно придет на терминал, который вы используете (тот, который Вы выбрали Alt-F<n>) и Вы его увидите. Когда Вы используете X, имеются две возможности. Или Вы имеете консоль открытой с xterm -C, тогда вывод будет послан туда, или Вы консоль не видите, тогда вывод будет идти на терминал 7 — тот, который «захвачен» X.

Если в ядре происходит ошибка, у Вас больше шансов получить из ядра отладочные сообщения, если Вы работаете в текстовой консоли, чем если Вы работаете в X. Вне X вывод printk идет непосредственно с ядра на консоль. В X printk идет на процесс режима пользователя (xterm -C). Когда этот процесс получает время CPU, предполагается послать дааные X процессу. Затем, когда X сервер получает время, сообщение отобразится, но нестабильное ядро обычно означает, что система собирается разрушиться или перезагружаться, так что Вы не успеете получить сообщения об ошибках, которые могли бы объяснить Вам, что именно пошло неправильно. Так что, никаких иксов!

Модули ядра из нескольких файлов

Иногда имеет смысл разделить модуль на несколько файлов. В этом случае Вы должны делать следующее:

1. Во всех исходных файлах добавьте строку #define __NO_VERSION__. Это важно, потому что module.h обычно включает определение kernel_version, глобальная переменная версии ядра для которой компилируется модуль. Если Вы нуждаетесь в version.h, Вы должны включить его непосредственно, потому что module.h не будет делать этого после указания __NO_VERSION__.

2. Скомпилируйте все исходные файлы как обычно.

3. Объедините все объектные файлы в один. Под x86 это делается командой:

ld -m elf_i386 -r -o <имя_модуля>.o <1-ый исходный файл>.o <2-ой исходный файл>.o.

Пример такого модуля:

start.c

/* start.c

* Copyright (C) 1999 by Ori Pomerantz

*

* "Hello, world" - the kernel module version.

* This file includes just the start routine

*/

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h>   /* We're doing kernel work */

#include <linux/module.h>   /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Initialize the module */

int init_module() {

printk("Hello, world - this is the kernel speaking\n");

/* If we return a non zero value, it means that

* init_module failed and the kernel module

* can't be loaded */

return 0;

}

stop.c

/* stop.c

* Copyright (C) 1999 by Ori Pomerantz

*

* "Hello, world" - the kernel module version. This

* file includes just the stop routine.

*/

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#define __NO_VERSION__ /* This isn't "the" file of the kernel module */

#include <linux/module.h> /* Specifically, a module */

#include <linux/version.h> /* Not included by module.h because of the __NO_VERSION__ */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Cleanup - undid whatever init_module did */

void cleanup_module(){

printk("Short is the life of a kernel module\n");

}

Makefile

# Makefile for a multifile kernel module

CC=gcc

MODCFLAGS := -Wall -DMODULE -D__KERNEL__ -DLINUX

hello.o:        start.o stop.o

ld -m elf_i386 -r -o hello.o start.o stop.o

start.o:        start.c /usr/include/linux/version.h

$(CC) $(MODCFLAGS) -c start.c

stop.o:         stop.c /usr/include/linux/version.h

$(CC) $(MODCFLAGS) -c stop.c

Файлы символьных устройств

Имеются два главных пути для общения модуля разговаривать с процессами. Первый идет через файлы устройства (подобно файлам в каталоге /dev), другой должен использовать файловую систему proc. Поскольку одной из главных причин написания модуля ядра, является поддержка некоего аппаратного устройства, мы начнем с файлов устройства.

Первоначальная цель файлов устройства состоит в том, чтобы позволить процессам связываться с драйверами устройства в ядре, и через них с физическими устройствами (модемы, терминалы, и т.д.).

Каждый драйвер устройства, который является ответственным за некоторый тип аппаратных средств, имеет собственный главный номер. Список драйверов и их главных номеров доступен в /proc/devices. Каждое физическое устройство, управляемое драйвером устройства имеет малый номер. Каталог /dev включает специальный файл, названный файлом устройства, для каждого из тех устройств, которые реально установлены в системе.

Например, если Вы даете команду ls -l /dev/hd[ab]*, вы увидите все IDE разделы жесткого диска, которые могли бы быть связаны с машиной. Обратите внимание, что все из них используют тот же самый главный номер, 3, но малые номера у каждого свои! Оговорка: Считается, что вы используете архитектуру PC. Я не знаю ничего относительно файлов устройств Linux на других архитектурах.

Когда система была установлена, все файлы устройств были созданы командой mknod. Не имеется никакой технической причины, по которой они должны быть в каталоге /dev, это только полезное соглашение. При создании файла устройства для целей тестирования, как с упражнением здесь, вероятно имело бы смысл поместить его в каталог, где Вы компилируете модуль.

Устройства разделены на два типа: символьные и блочные. Различие в том, что блочные имеют буфер для запросов, так что они могут выбирать в каком порядке им отвечать. Это важно в случае устройств памяти, где скорее понадобится читать или писать сектора, которые ближе друг к другу, чем те, которые находятся далеко. Другое различие: блочные устройства могут принимать ввод и возвращать вывод только в блоках (чей размер может измениться согласно устройству), в то время как символьные устройства могут использовать столько байтов, сколько нужно. Большинство устройств в мире символьно, потому что они не нуждаются в этом типе буферизации и не работают с фиксированным размером блока. Вы можете узнать, является ли устройство блочным или символьным, рассматривая первый символ в выводе ls -l. Если это "b", значит устройство блочное, а если "c", то символьное.

Этот модуль разделен на две отдельных части: часть модуля, которая регистрирует устройство и часть драйвера устройства. init_module вызывает module_register_chrdev, чтобы добавить драйвер устройства к символьной таблице драйверов устройств ядра. Этот вызов также возвращает главный номер, который нужно использовать для драйвера. Функция cleanup_module вычеркивает из списка устройство.

Это (регистрация и отмена регистрации) основные функциональные возможности этих двух функций. Действия в ядре не выполняются по собственной инициативе, подобно процессам, а вызываются процессами через системные вызовы или аппаратными устройствами через прерывания или другими частями ядра (просто вызывая специфические функции). В результате, когда Вы добавляете код к ядру, вы регистрируете его как драйвер для некоторого типа события, и когда Вы удаляете его, вы отменяете регистрацию.

Драйвер устройства выполняет четыре действия (функции), которые вызываются, когда кто-то пробует делать что-либо с файлом устройства, который имеет наш главный номер. Ядро знает, что вызвать их надо через структуру file_operations, Fops, который был дан, когда устройство было зарегистрировано, включает указатели на те четыре функции, которые данное устройство выполняет.

Еще мы должны помнить, что мы не можем позволять модулю выгружаться командой rmmod всякий раз, когда root захочет его выгрузить. Причина в том что, если файл устройства открыт процессом, и мы удаляем модуль, то использование файла вызвало бы обращение к точке памяти где располагалась соответствующая функция. Если мы удачливы, никакой другой код не был загружен туда, и мы получим уродливое сообщение об ошибках. Если мы неудачливы (обычно так и бывает), другой модуль был загружен в то же самое место, что означает переход в середину другой функции внутри ядра. Результаты этого невозможно предсказывать, но они не могут быть положительны.

Обычно, когда Вы не хотите выполнять что-либо, Вы возвращаете код ошибки (отрицательное число) из функции, которая делает данное действие. С cleanup_module такой фокус не пройдет: если cleanup_module вызван, модуль завершился. Однако, имеется счетчик использований, который считает, сколько других модулей используют этот модуль, названный номером ссылки (последний номер строки в /proc/modules). Если это число не нулевое, rmmod будет терпеть неудачу. Счетчик модульных ссылок доступен в переменной mod_use_count_. Так как имеются макрокоманды, определенные для обработки этой переменной (MOD_INC_USE_COUNT и MOD_DEC_USE_COUNT), мы предпочитаем использовать их, а не mod_use_count_ непосредственно, так что мы будем в безопасности, если реализация изменится в будущем.

chardev.c

/* chardev.c

* Copyright (C) 1998-1999 by Ori Pomerantz

*

* Create a character device (read only)

*/

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* For character devices */

#include <linux/fs.h> /* The character device definitions are here */

#include <linux/wrapper.h> /* A wrapper which does next to nothing at present, but may help for compatibility with future versions of Linux */

/* In 2.2.3 /usr/include/linux/version.h includes a macro for this, but 2.0.35 doesn't - so I add it here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c)

((a)*65536+(b)*256+(c))

#endif

/* Conditional compilation. LINUX_VERSION_CODE is the code (as per KERNEL_VERSION) of this version. */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h> /* for put_user */

#endif

#define SUCCESS 0

/* Device Declarations **************************** */

/* The name for our device, as it will appear in /proc/devices */

#define DEVICE_NAME "char_dev"

/* The maximum length of the message from the device */

#define BUF_LEN 80

/* Is the device open right now? Used to prevent concurent access into the same device */

static int Device_Open = 0;

/* The message the device will give when asked */

static char Message[BUF_LEN];

/* How far did the process reading the message get? Useful if the message is larger than the size of the buffer we get to fill in device_read. */

static char *Message_Ptr;

/* This function is called whenever a process attempts to open the device file */

static int device_open(struct inode *inode, struct file *file) {

static int counter = 0;

#ifdef DEBUG

printk("device_open(%p,%p)\n", inode, file);

#endif

/* This is how you get the minor device number in case you have more than one physical device using the driver. */

printk("Device: %d.%d\n", inode->i_rdev >> 8, inode->i_rdev & 0xFF);

/* We don't want to talk to two processes at the same time */

if (Device_Open) return -EBUSY;

/* If this was a process, we would have had to be

* more careful here.

*

* In the case of processes, the danger would be

* that one process might have check Device_Open

* and then be replaced by the schedualer by another

* process which runs this function. Then, when the

* first process was back on the CPU, it would assume

* the device is still not open.

*

* However, Linux guarantees that a process won't be

* replaced while it is running in kernel context.

*

* In the case of SMP, one CPU might increment

* Device_Open while another CPU is here, right after

* the check. However, in version 2.0 of the

* kernel this is not a problem because there's a lock

* to guarantee only one CPU will be kernel module at

* the same time. This is bad in terms of

* performance, so version 2.2 changed it.

* Unfortunately, I don't have access to an SMP box

* to check how it works with SMP. */

Device_Open++;

/* Initialize the message. */

sprintf(Message, "If I told you once, I told you %d times - %s", counter++, "Hello, world\n");

/* The only reason we're allowed to do this sprintf

* is because the maximum length of the message

* (assuming 32 bit integers - up to 10 digits

* with the minus sign) is less than BUF_LEN, which

* is 80. BE CAREFUL NOT TO OVERFLOW BUFFERS,

* ESPECIALLY IN THE KERNEL!!! */

Message_Ptr = Message;

/* Make sure that the module isn't removed while

* the file is open by incrementing the usage count

* (the number of opened references to the module, if

* it's not zero rmmod will fail)

*/

MOD_INC_USE_COUNT;

return SUCCESS;

}

/* This function is called when a process closes the

* device file. It doesn't have a return value in

* version 2.0.x because it can't fail (you must ALWAYS

* be able to close a device). In version 2.2.x it is

* allowed to fail - but we won't let it. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static int device_release(struct inode *inode, struct file *file)

#else

static void device_release(struct inode *inode, struct file *file)

#endif

{

#ifdef DEBUG

printk("device_release(%p,%p)\n", inode, file);

#endif

/* We're now ready for our next caller */

Device_Open--;

/* Decrement the usage count, otherwise once you opened the file you'll never get rid of the module. */

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0;

#endif

}

/* This function is called whenever a process which

* have already opened the device file attempts to

* read from it. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_read(struct file *file,

char *buffer, /* The buffer to fill with data */

size_t length, /* The length of the buffer */

loff_t *offset) /* Our offset in the file */

#else

static int device_read(struct inode *inode, struct file *file,

char *buffer, /* The buffer to fill with the data */

int length) /* The length of the buffer (mustn't write beyond that!) */

#endif

{

/* Number of bytes actually written to the buffer */

int bytes_read = 0;

/* If we're at the end of the message, return 0 (which signifies end of file) */

if (*Message_Ptr == 0) return 0;

/* Actually put the data into the buffer */

while (length && *Message_Ptr) {

/* Because the buffer is in the user data segment,

* not the kernel data segment, assignment wouldn't

* work. Instead, we have to use put_user which

* copies data from the kernel data segment to the

* user data segment. */

put_user(*(Message_Ptr++), buffer++);

length--;

bytes_read++;

}

#ifdef DEBUG

printk("Read %d bytes, %d left\n", bytes_read, length);

#endif

/* Read functions are supposed to return the number of bytes actually inserted into the buffer */

return bytes_read;

}

/* This function is called when somebody tries to write

* into our device file - unsupported in this example. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_write(struct file *file,

const char *buffer, /* The buffer */

size_t length, /* The length of the buffer */

loff_t *offset) /* Our offset in the file */

#else

static int device_write(struct inode *inode, struct file *file, const char *buffer, int length)

#endif

{

return -EINVAL;

}

/* Module Declarations ***************************** */

/* The major device number for the device. This is

* global (well, static, which in this context is global

* within this file) because it has to be accessible * both for registration and for release. */

static int Major;

/* This structure will hold the functions to be

* called when a process does something to the device

* we created. Since a pointer to this structure is

* kept in the devices table, it can't be local to

* init_module. NULL is for unimplemented functions. */

struct file_operations Fops = {

NULL, /* seek */

device_read, device_write,

NULL, /* readdir */

NULL, /* select */

NULL, /* ioctl */

NULL, /* mmap */

device_open,

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* flush */

#endif

device_release /* a.k.a. close */

};

/* Initialize the module - Register the character device */

int init_module() {

/* Register the character device (atleast try) */

Major = module_register_chrdev(0, DEVICE_NAME, &Fops);

/* Negative values signify an error */

if (Major < 0) {

printk("%s device failed with %d\n", "Sorry, registering the character", Major);

return Major;

}

printk("%s The major device number is %d.\n", "Registeration is a success.", Major);

printk("If you want to talk to the device driver,\n");

printk("you'll have to create a device file. \n");

printk("We suggest you use:\n");

printk("mknod <name> c %d <minor>\n", Major);

printk("You can try different minor numbers %s", "and see what happens.\n");

return 0;

}

/* Cleanup - unregister the appropriate file from /proc */

void cleanup_module() {

int ret;

/* Unregister the device */

ret = module_unregister_chrdev(Major, DEVICE_NAME);

/* If there's an error, report it */

if (ret < 0) printk("Error in unregister_chrdev: %d\n", ret);

}

Исходники для разных версий ядра Files

Системные вызовы, которые являются главным интерфейсом ядра, для процессов выглядят одинаково, независимо от версии. Новый системный вызов может быть добавлен, но старые обычно будут вести себя точно так, как и раньше. Это необходимо для обратной совместимости новая версия ядра, как предполагается, не разрывает регулярные процессы. В большинстве случаев, файлы устройства также останутся теми же самыми. С другой стороны, внутренние интерфейсы ядра могут изменяться между версиями.

Версии ядра Linux разделены между устойчивыми версиями (n.<Четное число>.m) и версии разработки (n.<Нечетное число>.m). Версии разработки включают все новые идеи, включая те, которые будут считаться ошибкой или повторно выполнены в следующей версии. В результате Вы не можете доверять интерфейсу в том плане, что он останется тем же самым в версиях разработки. В устойчивых версиях мы можем ожидать, что интерфейс останется тем же самым независимо от версии исправления ошибок (число m).

Эта версия MPG включает поддержку для версии 2.0.x и версии ядра Linux. Так как имеются различия между ними, требуется условная трансляция в зависимости от версии. Способ сделать это сводится к тому, чтобы использовать макрокоманду LINUX_VERSION_CODE. В версии a.b.c ядра значение этой макрокоманды было бы 2^16a+2^8b+c. Чтобы получать значение для конкретной версии, мы можем использовать макрокоманду KERNEL_VERSION. Так как этот макрос не определен в 2.0.35, мы определяем его сами в случае необходимости.

Файловая система /proc

В Linux имеется дополнительный механизм для ядра и ядерных модулей, чтобы они могли послать информацию процессам: файловая система /proc. Первоначально разработанная для свободного доступа к информации относительно процессов, она теперь используется каждым кусочком ядра, который может что-либо сообщить, например, /proc/modules, который имеет список модулей и /proc/meminfo, который имеет статистику использования памяти.

Метод использования файловой системы /proc очень похож на работу с драйверами устройства: Вы создаете структуру со всей информацией, необходимой для /proc файла, включая указатели на любые функции драйвера (в нашем случае имеется только один, вызываемый когда кто-то пытается читать из /proc файла). Затем init_module регистрирует структуру и cleanup_module отменяет регистрацию.

Причина по которой мы используем proc_register_dynamic[2] в том, что мы не хотим определять inode номер, используемый для нашего файла заранее, но позволяем ядру определять его, чтобы предотвратить столкновения. В нормальных файловых системах размещенных на диске, а не только в памяти (как /proc) inode является указателем на то место, в котором на диске размещен индексный узел файла (кратко, inode). Inode содержит информацию относительно файла, например разрешения файла, вместе с указателем на то место, где могут быть найдены данные файла.

Поскольку никаких наших функций не вызывается когда файл открывается или закрывается, некуда поместить MOD_INC_USE_COUNT и MOD_DEC_USE_COUNT в этом модуле, и если файл открыт и затем модуль удален, не имеется никакого способа избежать проблем. В следующей главе мы рассмотрим более тяжелый в реализации, но более гибкий путь имеющий дело с файлами из /proc, который позволит нам защититься от этой проблемы.

procfs.c

/* procfs.c - create a "file" in /proc

* Copyright (C) 1998-1999 by Ori Pomerantz

*/

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Necessary because we use the proc fs */

#include <linux/proc_fs.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

/* Put data into the proc fs file.

Arguments

=========

1. The buffer where the data is to be inserted, if you decide to use it.

2. A pointer to a pointer to characters. This is useful if you don't want to use the buffer allocated by the kernel.

3. The current position in the file.

4. The size of the buffer in the first argument.

5. Zero (for future use?).

Usage and Return Value

======================

If you use your own buffer, like I do, put its location in the second argument and return the number of bytes used in the buffer.

A return value of zero means you have no further information at this time (end of file). A negative return value is an error condition.

For More Information

====================

The way I discovered what to do with this function wasn't by reading documentation, but by reading the code which used it. I just looked to see what uses the get_info field of proc_dir_entry struct (I used a combination of find and grep, if you're interested), and I saw that it is used in <kernel source directory>/fs/proc/array.c.

If something is unknown about the kernel, this is usually the way to go. In Linux we have the great advantage of having the kernel source code for free - use it.

*/

int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int zero) {

int len; /* The number of bytes actually used */

/* This is static so it will still be in memory when we leave this function */

static char my_buffer[80];

static int count = 1;

/* We give all of our information in one go, so if the

* user asks us if we have more information the

* answer should always be no.

*

* This is important because the standard read

* function from the library would continue to issue

* the read system call until the kernel replies

* that it has no more information, or until its * buffer is filled. */

if (offset > 0) return 0;

/* Fill the buffer and get its length */

len = sprintf(my_buffer, "For the %d%s time, go away!\n", count,

(count % 100 > 10 && count % 100 < 14) ? "th" :

(count % 10 == 1) ? "st" : (count % 10 == 2) ? "nd" :

(count % 10 == 3) ? "rd" : "th" );

count++;

/* Tell the function which called us where the buffer is */

*buffer_location = my_buffer;

/* Return the length */

return len;

}

struct proc_dir_entry Our_Proc_File = {

0, /* Inode number - ignore, it will be filled by proc_register[_dynamic] */

4, /* Length of the file name */

"test", /* The file name */

S_IFREG | S_IRUGO, /* File mode - this is a regular file which can be read by its owner, its group, and everybody else */

1, /* Number of links (directories where the file is referenced) */

0, 0, /* The uid and gid for the file - we give it * to root */

80, /* The size of the file reported by ls. */

NULL, /* functions which can be done on the inode (linking, removing, etc.) - we don't support any. */

procfile_read, /* The read function for this file, the function called when somebody tries to read something from it. */

NULL /* We could have here a function to fill the file's inode, to enable us to play with permissions, ownership, etc. */

};

/* Initialize the module - register the proc file */

int init_module() {

/* Success if proc_register[_dynamic] is a success, failure otherwise. */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

/* In version 2.2, proc_register assign a dynamic

* inode number automatically if it is zero in the

* structure, so there's no more need for

* proc_register_dynamic */

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

/* proc_root is the root directory for the proc fs (/proc). This is where we want our file to be located. */

}

/* Cleanup - unregister our file from /proc */

void cleanup_module() {

proc_unregister(&proc_root, Our_Proc_File.low_ino);

}

Использование /proc для ввода

Пока мы имеем два способа генерировать вывод из модулей: мы можем зарегистрировать драйвер устройства и создать mknod файл устройства, или мы можем создать /proc файл. Это позволяет модулю сообщать нам что-нибудь. Единственная проблема в том, что не имеется никакого пути для нас, чтобы возразить. Первый путем, которым мы пошлем ввод модулям, будет запись обратно в файл в системе /proc.

Поскольку файловая система /proc была написана главным образом, чтобы позволить ядру сообщать ситуацию процессам, нет никаких специальных условий для ввода. Структура proc_dir_entry не включает указатель на функцию ввода. Вместо этого, чтобы писать в /proc, мы должны использовать стандартный механизм файловой системы.

В Linux имеется стандартный механизм для регистрации файловой системы. Так как каждая файловая система должна иметь собственные функции, чтобы обработать inode и выполнять файловые операции[3], имеется специальная структура, чтобы хранить указатели на все нужные функции, struct inode_operations, который включает указатель на struct file_operations. В /proc всякий раз, когда мы регистрируем новый файл, нам позволяют определить, который struct inode_operations будет использоваться для доступа к этому файлу. Это механизм, который мы используем, struct inode_operations, который включает указатель на struct file_operations, который включает указатели на наши функции module_input и module_output.

Важно обратить внимание что стандартные роли чтения и записи, обращены в ядре. Функции чтения используются для вывода, в то время как функции записи используются для ввода. Причина для этого в том, что чтение и запись относятся к точке зрения пользователя: если процесс читает что-то из ядра, ядро должно вывести это, а если процесс пишет что-то в ядро, оно получает это как ввод.

Другая интересная точка здесь это функция module_permission. Эта функция вызывается всякий раз, когда процесс пробует делать что-либо с файлами системы /proc, и может решить позволить доступ или нет. Сейчас это только основано на операции и uid текущиего пользователя, но в принципе такое разрешение может выдаваться исходя из других параметров, например, что другие процессы делают с тем же самым файлом, времени дня или последнего ввода, который мы получили.

Причина для put_user и get_user в том, что память в Linux (под архитектурой Intel, это может быть различно под некоторыми другими процессорами) сегментирована. Это означает, что указатель, отдельно не ссылается на уникальное место в памяти, а только на место в сегменте памяти, и Вы должны знать, который сегмент памяти нужен. Имеется один сегмент памяти для ядра и по одному на каждый из процессов.

Единственный сегмент памяти, доступный для процесса, это его собственный, так для нормальных программ, нет никакой потребности волноваться относительно сегментов. Когда Вы пишете модуль, обычно Вы хотите обращаться к сегменту памяти ядра, который обрабатывается системой автоматически. Однако, когда содержание буфера памяти должно быть передано между процессом в настоящее время имеющим управление и ядром, функция получает указатель на буфер памяти, который находится в своем сегменте процесса. Макрокоманды put_user и get_user позволяют Вам обращаться к нужной Вам памяти.

procfs.c

/* procfs.c - create a "file" in /proc, which allows

* both input and output.

*/

/* Copyright (C) 1998-1999 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Necessary because we use proc fs */

#include <linux/proc_fs.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h> /* for get_user and put_user */

#endif

/* The module's file functions ********************** */

/* Here we keep the last message received, to prove

* that we can process our input */

#define MESSAGE_LENGTH 80

static char Message[MESSAGE_LENGTH];

/* Since we use the file operations struct, we can't

* use the special proc output provisions - we have to

* use a standard read function, which is this function */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_output(

struct file *file, /* The file read */

char *buf, /* The buffer to put data to (in the user segment) */

size_t len, /* The length of the buffer */

loff_t *offset) /* Offset in the file - ignore */

#else

static int module_output(

struct inode *inode, /* The inode read */

struct file *file, /* The file read */

char *buf, /* The buffer to put data to (in the user segment) */

int len) /* The length of the buffer */

#endif

{

static int finished = 0;

int i;

char message[MESSAGE_LENGTH+30];

/* We return 0 to indicate end of file, that we have

* no more information. Otherwise, processes will

* continue to read from us in an endless loop. */

if (finished) {

finished = 0;

return 0;

}

/* We use put_user to copy the string from the kernel's

* memory segment to the memory segment of the process

* that called us. get_user, BTW, is

* used for the reverse. */

sprintf(message, "Last input:%s", Message);

for(i=0; i<len && message[i]; i++) put_user(message[i], buf+i);

/* Notice, we assume here that the size of the message

* is below len, or it will be received cut. In a real

* life situation, if the size of the message is less

* than len then we'd return len and on the second call

* start filling the buffer with the len+1'th byte of the message. */

finished = 1;

return i; /* Return the number of bytes "read" */

}

/* This function receives input from the user when the

* user writes to the /proc file. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_input(

struct file *file, /* The file itself */

const char *buf, /* The buffer with input */

size_t length, /* The buffer's length */

loff_t *offset) /* offset to file - ignore */

#else

static int module_input(

struct inode *inode, /* The file's inode */

struct file *file, /* The file itself */

const char *buf, /* The buffer with the input */

int length) /* The buffer's length */

#endif

{

int i;

/* Put the input into Message, where module_output will later be able to use it */

for (i=0; i<MESSAGE_LENGTH-1 && i<length; i++)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(Message[i], buf+i);

/* In version 2.2 the semantics of get_user changed,

* it not longer returns a character, but expects a

* variable to fill up as its first argument and a

* user segment pointer to fill it from as the its  second.

*

* The reason for this change is that the version 2.2

* get_user can also read an short or an int. The way

* it knows the type of the variable it should read

* is by using sizeof, and for that it needs the

* variable itself. */

#else

Message[i] = get_user(buf+i);

#endif

Message[i] = '\0'; /* we want a standard, zero terminated string */

/* We need to return the number of input characters used */

return i;

}

/* This function decides whether to allow an operation

* (return zero) or not allow it (return a non-zero

* which indicates why it is not allowed).

*

* The operation can be one of the following values:

* 0 - Execute (run the "file" - meaningless in our case)

* 2 - Write (input to the kernel module)

* 4 - Read (output from the kernel module)

*

* This is the real function that checks file

* permissions. The permissions returned by ls -l are

* for reference only, and can be overridden here. */

static int module_permission(struct inode *inode, int op) {

/* We allow everybody to read from our module, but only root (uid 0) may write to it */

if (op == 4 || (op == 2 && current->euid == 0)) return 0;

/* If it's anything else, access is denied */

return -EACCES;

}

/* The file is opened - we don't really care about

* that, but it does mean we need to increment the

* module's reference count. */

int module_open(struct inode *inode, struct file *file) {

MOD_INC_USE_COUNT;

return 0;

}

/* The file is closed - again, interesting only because of the reference count. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

int module_close(struct inode *inode, struct file *file)

#else

void module_close(struct inode *inode, struct file *file)

#endif

{

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0; /* success */

#endif

}

/* Structures to register as the /proc file, with

* pointers to all the relevant functions. ********** */

/* File operations for our proc file. This is where we

* place pointers to all the functions called when

* somebody tries to do something to our file. NULL

* means we don't want to deal with something. */

static struct file_operations File_Ops_4_Our_Proc_File = {

NULL, /* lseek */

module_output, /* "read" from the file */

module_input, /* "write" to the file */

NULL, /* readdir */

NULL, /* select */

NULL, /* ioctl */

NULL, /* mmap */

module_open, /* Somebody opened the file */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* flush, added here in version 2.2 */

#endif

module_close, /* Somebody closed the file */

/* etc. etc. etc. (they are all given in

* /usr/include/linux/fs.h). Since we don't put

* anything here, the system will keep the default

* data, which in Unix is zeros (NULLs when taken as pointers). */

};

/* Inode operations for our proc file. We need it so

* we'll have some place to specify the file operations

* structure we want to use, and the function we use for

* permissions. It's also possible to specify functions

* to be called for anything else which could be done to

* an inode (although we don't bother, we just put NULL). */

static struct inode_operations Inode_Ops_4_Our_Proc_File = {

&File_Ops_4_Our_Proc_File,

NULL, /* create */

NULL, /* lookup */

NULL, /* link */

NULL, /* unlink */

NULL, /* symlink */

NULL, /* mkdir */

NULL, /* rmdir */

NULL, /* mknod */

NULL, /* rename */

NULL, /* readlink */

NULL, /* follow_link */

NULL, /* readpage */

NULL, /* writepage */

NULL, /* bmap */

NULL, /* truncate */

module_permission /* check for permissions */

};

/* Directory entry */

static struct proc_dir_entry Our_Proc_File = {

0, /* Inode number - ignore, it will be filled by proc_register[_dynamic] */

7, /* Length of the file name */

"rw_test", /* The file name */

S_IFREG | S_IRUGO | S_IWUSR,

/* File mode - this is a regular file which

* can be read by its owner, its group, and everybody

* else. Also, its owner can write to it.

*

* Actually, this field is just for reference, it's

* module_permission that does the actual check. It

* could use this field, but in our implementation it

* doesn't, for simplicity. */

1, /* Number of links (directories where the file is referenced) */

0, 0, /* The uid and gid for the file - we give it to root */

80, /* The size of the file reported by ls. */

&Inode_Ops_4_Our_Proc_File,

/* A pointer to the inode structure for

* the file, if we need it. In our case we

* do, because we need a write function. */

NULL

/* The read function for the file. Irrelevant,

* because we put it in the inode structure above */

};

/* Module initialization and cleanup ******************* */

/* Initialize the module - register the proc file */

int init_module() {

/* Success if proc_register[_dynamic] is a success, failure otherwise */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

/* In version 2.2, proc_register assign a dynamic

* inode number automatically if it is zero in the

* structure, so there's no more need for

* proc_register_dynamic */

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

}

/* Cleanup - unregister our file from /proc */

void cleanup_module() {

proc_unregister(&proc_root, Our_Proc_File.low_ino);

}

Работа с файлами устройств (запись и IOCTL)

Файлы устройства представляют физические устройства. Многие физические устройства используются для вывода и для ввода, так должен иметься некоторый механизм для драйверов устройства в ядре, чтобы послать вывод устройству из процесса. Это выполняется, открывая файл устройства для вывода и записывая в него точно так же, как в обычный файл. В следующем примере, это выполнено функцией device_write.

Этого не всегда достаточно. Вообразите, что Вы имеете последовательный порт, связанный с модемом (даже если Вы имеете внутренний модем, он выглядит с точки зрения CPU как последовательный порт, связанный с модемом, так что Вы не должны слишком сдерживать Ваше воображение). Естественное решение использовать файл устройства, чтобы передавать модему команды модема или данные, которые будут посланы через телефонную линию и читать из модема ответы для команд или данные, полученные через телефонную линию. Однако, это оставляет открытым вопрос о том что делать, когда Вы должны работать с последовательным портом непосредственно, например настроить скорость обмена данными с портом.

Ответ в Unix должен использовать специальную функцию, названную ioctl (сокращение от input output control). Каждое устройство может иметь собственные команды ioctl, которые могут читать ioctl (для передачи данных от процесса ядру), записывать ioctl (чтобы возвратить информацию процессу)[4], выполнять оба действия или ни одно из них. Функция ioctl вызывается с тремя параметрами: описатель файла соответствующий файлу устройства, ioctl номер, и параметра, который имеет тип long, так что Вы можете использовать приведение, чтобы передать что-нибудь.[5]

Ioctl номер кодирует главный номер устройства, тип ioctl команды и тип параметра. Этот ioctl номер обычно создается макрообращением (_IO, _IOR, _IOW или _IOWR: в зависимости от типа) в файле заголовка. Этот файл заголовка должен быть присоединен командой #include программой, которая использует ioctl и модулем (так что они могут генерировать соответствующие ioctl). В примере ниже, файл заголовка chardev.h и программа, которая использует это ioctl.c.

Если Вы хотите использовать ioctl в ваших собственных модулях, самое лучшее получить официальное ioctl назначение, так, если Вы случайно получаете ioctl кого-то другого вы будете знать, что что-то неправильно. Для большего количества информации, проконсультируйтесь в файле `Documentation/ioctl-number.txt' дерева исходников ядра.

chardev.c

/* chardev.c

*

* Create an input/output character device

*/

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* For character devices */

/* The character device definitions are here */

#include <linux/fs.h>

/* A wrapper which does next to nothing at

* at present, but may help for compatibility

* with future versions of Linux */

#include <linux/wrapper.h>

/* Our own ioctl numbers */

#include "chardev.h"

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h> /* for get_user and put_user */

#endif

#define SUCCESS 0

/* Device Declarations ******************************** */

/* The name for our device, as it will appear in /proc/devices */

#define DEVICE_NAME "char_dev"

/* The maximum length of the message for the device */

#define BUF_LEN 80

/* Is the device open right now? Used to prevent concurent access into the same device */

static int Device_Open = 0;

/* The message the device will give when asked */

static char Message[BUF_LEN];

/* How far did the process reading the message get?

* Useful if the message is larger than the size of the

* buffer we get to fill in device_read. */

static char *Message_Ptr;

/* This function is called whenever a process attempts to open the device file */

static int device_open(struct inode *inode, struct file *file) {

#ifdef DEBUG

printk("device_open(%p)\n", file);

#endif

/* We don't want to talk to two processes at the  same time */

if (Device_Open) return -EBUSY;

/* If this was a process, we would have had to be

* more careful here, because one process might have

* checked Device_Open right before the other one

* tried to increment it. However, we're in the

* kernel, so we're protected against context switches.

*

* This is NOT the right attitude to take, because we

* might be running on an SMP box, but we'll deal with

* SMP in a later chapter. */

Device_Open++;

/* Initialize the message */

Message_Ptr = Message;

MOD_INC_USE_COUNT;

return SUCCESS;

}

/* This function is called when a process closes the

* device file. It doesn't have a return value because

* it cannot fail. Regardless of what else happens, you

* should always be able to close a device (in 2.0, a 2.2

* device file could be impossible to close). */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static int device_release(struct inode *inode, struct file *file)

#else

static void device_release(struct inode *inode, struct file *file)

#endif

{

#ifdef DEBUG

printk("device_release(%p,%p)\n", inode, file);

#endif

/* We're now ready for our next caller */

Device_Open--;

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0;

#endif

}

/* This function is called whenever a process which

* has already opened the device file attempts to read from it. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_read(

struct file *file,

char *buffer, /* The buffer to fill with the data */

size_t length, /* The length of the buffer */

loff_t *offset) /* offset to the file */

#else

static int device_read(

struct inode *inode, struct file *file,

char *buffer, /* The buffer to fill with the data */

int length) /* The length of the buffer (mustn't write beyond that!) */

#endif

{

/* Number of bytes actually written to the buffer */

int bytes_read = 0;

#ifdef DEBUG

printk("device_read(%p,%p,%d)\n", file, buffer, length);

#endif

/* If we're at the end of the message, return 0 (which signifies end of file) */

if (*Message_Ptr == 0) return 0;

/* Actually put the data into the buffer */

while (length && *Message_Ptr) {

/* Because the buffer is in the user data segment,

* not the kernel data segment, assignment wouldn't

* work. Instead, we have to use put_user which

* copies data from the kernel data segment to the

* user data segment. */

put_user(*(Message_Ptr++), buffer++);

length--;

bytes_read++;

}

#ifdef DEBUG

printk("Read %d bytes, %d left\n", bytes_read, length);

#endif

/* Read functions are supposed to return the number

* of bytes actually inserted into the buffer */

return bytes_read;

}

/* This function is called when somebody tries to write into our device file. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t device_write(struct file *file, const char *buffer, size_t length, loff_t *offset)

#else

static int device_write(struct inode *inode, struct file *file, const char *buffer, int length)

#endif

{

int i;

#ifdef DEBUG

printk("device_write(%p,%s,%d)", file, buffer, length);

#endif

for(i=0; i<length && i<BUF_LEN; i++)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(Message[i], buffer+i);

#else

Message[i] = get_user(buffer+i);

#endif

Message_Ptr = Message;

/* Again, return the number of input characters used */

return i;

}

/* This function is called whenever a process tries to

* do an ioctl on our device file. We get two extra

* parameters (additional to the inode and file

* structures, which all device functions get): the number

* of the ioctl called and the parameter given to the ioctl function.

*

* If the ioctl is write or read/write (meaning output

* is returned to the calling process), the ioctl call

* returns the output of this function. */

int device_ioctl(struct inode *inode, struct file *file,

unsigned int ioctl_num, /* The number of the ioctl */

unsigned long ioctl_param) /* The parameter to it */

{

int i;

char *temp;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

char ch;

#endif

/* Switch according to the ioctl called */

switch (ioctl_num) {

case IOCTL_SET_MSG:

/* Receive a pointer to a message (in user space)

* and set that to be the device's message. */

/* Get the parameter given to ioctl by the process */

temp = (char*)ioctl_param;

/* Find the length of the message */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(ch, temp);

for (i=0; ch && i<BUF_LEN; i++, temp++) get_user(ch, temp);

#else

for (i=0; get_user(temp) && i<BUF_LEN; i++, temp++) ;

#endif

/* Don't reinvent the wheel - call device_write */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

device_write(file, (char*)ioctl_param, i, 0);

#else

device_write(inode, file, (char*)ioctl_param, i);

#endif

break;

case IOCTL_GET_MSG:

/* Give the current message to the calling

* process - the parameter we got is a pointer, fill it. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

i = device_read(file, (char*)ioctl_param, 99, 0);

#else

i = device_read(inode, file, (char*)ioctl_param, 99);

#endif

/* Warning - we assume here the buffer length is

* 100. If it's less than that we might overflow

* the buffer, causing the process to core dump.

*

* The reason we only allow up to 99 characters is

* that the NULL which terminates the string also needs room. */

/* Put a zero at the end of the buffer, so it will be properly terminated */

put_user('\0', (char*)ioctl_param+i);

break;

case IOCTL_GET_NTH_BYTE:

/* This ioctl is both input (ioctl_param) and

* output (the return value of this function) */

return Message[ioctl_param];

break;

}

return SUCCESS;

}

/* Module Declarations *************************** */

/* This structure will hold the functions to be called

* when a process does something to the device we

* created. Since a pointer to this structure is kept in

* the devices table, it can't be local to

* init_module. NULL is for unimplemented functions. */

struct file_operations Fops = {

NULL, /* seek */

device_read,

device_write,

NULL, /* readdir */

NULL, /* select */

device_ioctl, /* ioctl */

NULL, /* mmap */

device_open,

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* flush */

#endif

device_release /* a.k.a. close */

};

/* Initialize the module - Register the character device */

int init_module() {

int ret_val;

/* Register the character device (atleast try) */

ret_val = module_register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

/* Negative values signify an error */

if (ret_val < 0) {

printk("%s failed with %d\n", "Sorry, registering the character device ", ret_val);

return ret_val;

}

printk("%s The major device number is %d.\n", "Registeration is a success", MAJOR_NUM);

printk("If you want to talk to the device driver,\n");

printk ("you'll have to create a device file. \n");

printk ("We suggest you use:\n");

printk ("mknod %s c %d 0\n", DEVICE_FILE_NAME, MAJOR_NUM);

printk ("The device file name is important, because\n");

printk ("the ioctl program assumes that's the\n");

printk ("file you'll use.\n");

return 0;

}

/* Cleanup - unregister the appropriate file from /proc */

void cleanup_module() {

int ret;

/* Unregister the device */

ret = module_unregister_chrdev(MAJOR_NUM, DEVICE_NAME);

/* If there's an error, report it */

if (ret < 0) printk("Error in module_unregister_chrdev: %d\n", ret);

}

chardev.h

/* chardev.h - the header file with the ioctl definitions.

*

* The declarations here have to be in a header file,

* because they need to be known both to the kernel

* module (in chardev.c) and the process calling ioctl (ioctl.c)

*/

#ifndef CHARDEV_H

#define CHARDEV_H #

include <linux/ioctl.h>

/* The major device number. We can't rely on dynamic

* registration any more, because ioctls need to know it. */

#define MAJOR_NUM 100

/* Set the message of the device driver */

#define IOCTL_SET_MSG _IOR(MAJOR_NUM, 0, char *)

/* _IOR means that we're creating an ioctl command

* number for passing information from a user process

* to the kernel module.

*

* The first arguments, MAJOR_NUM, is the major device

* number we're using.

*

* The second argument is the number of the command

* (there could be several with different meanings).

*

* The third argument is the type we want to get from

* the process to the kernel. */

/* Get the message of the device driver */

#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)

/* This IOCTL is used for output, to get the message

* of the device driver. However, we still need the

* buffer to place the message in to be input,

* as it is allocated by the process. */

/* Get the n'th byte of the message */

#define IOCTL_GET_NTH_BYTE _IOWR(MAJOR_NUM, 2, int)

/* The IOCTL is used for both input and output. It

* receives from the user a number, n, and returns Message[n]. */

/* The name of the device file */

#define DEVICE_FILE_NAME "char_dev"

#endif

ioctl.c

/* ioctl.c - the process to use ioctl's to control the

* kernel module

*

* Until now we could have used cat for input and

* output. But now we need to do ioctl's, which require

* writing our own process. */

/* Copyright (C) 1998 by Ori Pomerantz */

/* device specifics, such as ioctl numbers and the major device file. */

#include "chardev.h"

#include <fcntl.h> /* open */

#include <unistd.h> /* exit */

#include <sys/ioctl.h> /* ioctl */

/* Functions for the ioctl calls */

ioctl_set_msg(int file_desc, char *message) {

int ret_val;

ret_val = ioctl(file_desc, IOCTL_SET_MSG, message);

if (ret_val < 0) {

printf("ioctl_set_msg failed:%d\n", ret_val);

exit(-1);

}

}

ioctl_get_msg(int file_desc) {

int ret_val;

char message[100];

/* Warning - this is dangerous because we don't tell

* the kernel how far it's allowed to write, so it

* might overflow the buffer. In a real production

* program, we would have used two ioctls - one to tell

* the kernel the buffer length and another to give

* it the buffer to fill */

ret_val = ioctl(file_desc, IOCTL_GET_MSG, message);

if (ret_val < 0) {

printf("ioctl_get_msg failed:%d\n", ret_val);

exit(-1);

}

printf("get_msg message:%s\n", message);

}

ioctl_get_nth_byte(int file_desc) {

int i;

char c;

printf("get_nth_byte message:");

i = 0;

while (c != 0) {

c = ioctl(file_desc, IOCTL_GET_NTH_BYTE, i++);

if (c < 0) {

printf("ioctl_get_nth_byte failed at the %d'th byte:\n", i);

exit(-1);

}

putchar(c);

}

putchar('\n');

}

/* Main - Call the ioctl functions */

main() {

int file_desc, ret_val;

char *msg = "Message passed by ioctl\n";

file_desc = open(DEVICE_FILE_NAME, 0);

if (file_desc < 0) {

printf("Can't open device file: %s\n", DEVICE_FILE_NAME);

exit(-1);

}

ioctl_get_nth_byte(file_desc);

ioctl_get_msg(file_desc);

ioctl_set_msg(file_desc, msg);

close(file_desc);

}

Загрузочные параметры

Во многих из предыдущих примеров, мы жестко задавали какие-либо параметры в модуле. Это идет против правил Unix и Linux, философия которых такова, что программа должна быть гибкой, чтобы пользователь мог ее настраивать.

Способ сообщить программе или модулю что-либо до запуска это параметры командной строки. В случае ядерных модулей, мы не получаем argc и argv. Вместо этого, мы получаем кое-что лучше. Мы можем определять глобальные переменные в модуле, и insmod заполнит их для нас.

В этом ядерном модуле мы определяем две таких переменных: str1 и str2. Все, что Вы должны делать это скомпилировать модуль и затем выполнить insmod str1=xxx str2=yyy. При вызове init_module str1 укажет на строку `xxx' и str2 на строку `yyy'.

В версии 2.0 не имеется никакого контроля соответствия типов аргументов[6]. Если первый символ str1 или str2 является цифрой, ядро заполнит переменную значением целого числа, а не указателем на строку. В реальной ситуации Вы должны проверять это.

С другой стороны, в версии 2.2 Вы используете макрокоманду MACRO_PARM , чтобы сообщить insmod, что Вы ожидаете параметры, их имена и их типы. Это решает проблему типа и позволяет модулям получать строки, которые начинаются с цифры.

param.c

/* param.c

*

* Receive command line parameters at module installation

*/

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

#include <stdio.h> /* I need NULL */

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

/* Emmanuel Papirakis:

*

* Parameter names are now (2.2) handled in a macro.

* The kernel doesn't resolve the symbol names

* like it seems to have once did.

*

* To pass parameters to a module, you have to use a macro

* defined in include/linux/modules.h (line 176).

* The macro takes two parameters. The parameter's name and

* it's type. The type is a letter in double quotes.

* For example, "i" should be an integer and "s" should

* be a string. */

char *str1, *str2;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

MODULE_PARM(str1, "s");

MODULE_PARM(str2, "s");

#endif

/* Initialize the module - show the parameters */

int init_module() {

if (str1 == NULL || str2 == NULL) {

printk("Next time, do insmod param str1=<something>");

printk("str2=<something>\n");

} else printk("Strings:%s and %s\n", str1, str2);

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

printk("If you try to insmod this module twice,");

printk("(without rmmod'ing\n");

printk("it first), you might get the wrong");

printk("error message:\n");

printk("'symbol for parameters str1 not found'.\n");

#endif

return 0;

}

/* Cleanup */

void cleanup_module() { }

Системные вызовы

Пока что все программы, которые мы сделали должны были использовать хорошо определенные механизмы ядра, чтобы регистрировать /proc файлы и драйверы устройства. Это прекрасно, если Вы хотите делать что-то уже предусмотренное программистами ядра, например писать драйвер устройства. Но что, если Вы хотите сделать что-то необычное, изменить поведение системы некоторым способом?

Это как раз то место, где программирование ядра становится опасным. При написании примера ниже, я уничтожил системный вызов open. Это подразумевало, что я не могу открывать любые файлы, я не могу выполнять любые программы, и я не могу закрыть систему командой shutdown. Я должен выключить питание, чтобы ее остановить. К счастью, никакие файлы не были уничтожены. Чтобы гарантировать, что Вы также не будете терять файлы, пожалуйста выполните sync прежде чем Вы отдадите команды insmod и rmmod.

Забудьте про /proc файлы и файлы устройств. Они только малые детали. Реальный процесс связи с ядром, используемый всеми процессами, это системные вызовы. Когда процесс запрашивает обслуживание из ядра (типа открытия файла, запуска нового процесса или запроса большего количества памяти), используется этот механизм. Если Вы хотите изменить поведение ядра интересными способами, это как раз подходящее место. Между прочим, если Вы хотите видеть какие системные вызовы использованы программой, выполните: strace <command> <arguments>.

Вообще, процесс не способен обратиться к ядру. Он не может обращаться к памяти ядра и не может вызывать функции ядра. Аппаратные средства CPU предписывают такое положение дел (недаром это называется `protected mode' (защищенный режим)). Системные вызовы исключение из этого общего правила. Процесс заполняет регистры соответствующими значениями и затем вызывает специальную команду, которая переходит к предварительно определенному месту в ядре (конечно, оно читается процессами пользователя, но не перезаписывается ими). Под Intel CPUs, это выполнено посредством прерывания 0x80. Аппаратные средства знают, что, как только Вы переходите к этому месту, Вы больше не работаете в ограниченном режиме пользователя. Вместо этого Вы работаете как ядро операционной системы, и следовательно вам позволено делать все, что Вы хотите сделать.

Место в ядре, к которому процесс может переходить, названо system_call. Процедура, которая там находится, проверяет номер системного вызова, который сообщает ядру чего именно хочет процесс. Затем, она просматривает таблицу системных вызовов (sys_call_table), чтобы найти адрес функции ядра, которую надо вызвать. Затем вызывается нужная функция, и после того, как она возвращает значение, делается несколько проверок системы. Затем результат возвращается обратно процессу (или другому процессу, если процесс завершился). Если Вы хотите посмотреть код, который все это делает, он находится в исходном файле arch/<architecture> /kernel/entry.S, после строки ENTRY(system_call).

Так, если мы хотим изменить работу некоторого системного вызова, то первое, что мы должны сделать, это написать нашу собственную функцию, чтобы она выполняла соответствующие действия (обычно, добавляя немного нашего собственного кода, и затем вызывая первоначальную функцию), затем изменить указатель в sys_call_table, чтобы указать на нашу функцию. Поскольку мы можем быть удалены позже и не хотим оставлять систему в непостоянном состоянии, это важно для cleanup_module, чтобы восстановить таблицу в ее первоначальном состоянии.

Исходный текст, приводимый здесь, является примером такого модуля. Мы хотим «шпионить» за некоторым пользователем, и посылать через printk сообщение всякий раз, когда данный пользователь открывает файл. Мы заменяем системный вызов, открытия файла нашей собственной функцией, названной our_sys_open. Эта функция проверяет uid (user id) текущего процесса, и если он равен uid, за которым мы шпионим, вызывает printk, чтобы отобразить имя файла, который будет открыт. Затем вызывает оригинал функции open с теми же самыми параметрами, фактически открывает файл.

Функция init_module меняет соответствующее место в sys_call_table и сохраняет первоначальный указатель в переменной. Функция cleanup_module использует эту переменную, чтобы восстановить все назад к норме. Этот подход опасен, из-за возможности существования двух модулей, меняющих один и тот же системный вызов. Вообразите, что мы имеем два модуля, А и B. Системный вызов open модуля А назовем A_open и такой же вызов модуля B назовем B_open. Теперь, когда вставленный в ядро системный вызов заменен на A_open, который вызовет оригинал sys_open, когда сделает все, что ему нужно. Затем, B будет вставлен в ядро, и заменит системный вызов на B_open, который вызовет то, что как он думает, является первоначальным системным вызовом, а на самом деле является A_open.

Теперь, если B удален первым, все будет хорошо: это просто восстановит системный вызов на A_open, который вызывает оригинал. Однако, если удален А, и затем удален B, система разрушится. Удаление А восстановит системный вызов к оригиналу, sys_open, вырезая B из цикла. Затем, когда B удален, он восстановит системный вызов к тому, что он считает оригиналом, На самом деле вызов будет направлен на A_open, который больше не в памяти. На первый взгляд кажется, что мы могли бы решать эту специфическую проблему, проверяя, если системный вызов равен нашей функции open и если так, не менять значение этого вызова (так, чтобы B не изменил системный вызов, когда удаляется), но это вызовет еще худшую проблему. Когда А удаляется, он видит, что системный вызов был изменен на B_open так, чтобы он больше не указывал на A_open, так что он не будет восстанавливать указатель на sys_open прежде, чем будет удалено из памяти. К сожалению, B_open будет все еще пробовать вызывать A_open, который больше не в памяти, так что даже без удаления B система все равно рухнет.

Я вижу два способа предотвратить эту проблему. Первое: восстановить обращение к первоначальному значению sys_open. К сожалению, sys_open не является частью таблицы ядра системы в /proc/ksyms, так что мы не можем обращаться к нему. Другое решение состоит в том, чтобы использовать счетчик ссылки, чтобы предотвратить выгрузку модуля. Это хорошо для обычных модулей, но плохо для «образовательных» модулей.

syscall.c

/* syscall.c

*

* System call "stealing" sample

*/

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

#include <sys/syscall.h> /* The list of system calls */

/* For the current (process) structure, we need

* this to know who the current user is. */

#include <linux/sched.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h>

#endif

/* The system call table (a table of functions). We

* just define this as external, and the kernel will

* fill it up for us when we are insmod'ed */

extern void *sys_call_table[];

/* UID we want to spy on - will be filled from the command line */

int uid;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

MODULE_PARM(uid, "i");

#endif

/* A pointer to the original system call. The reason

* we keep this, rather than call the original function

* (sys_open), is because somebody else might have

* replaced the system call before us. Note that this

* is not 100% safe, because if another module

* replaced sys_open before us, then when we're inserted

* we'll call the function in that module - and it

* might be removed before we are.

*

* Another reason for this is that we can't get sys_open.

* It's a static variable, so it is not exported. */

asmlinkage int (*original_call)(const char *, int, int);

/* For some reason, in 2.2.3 current->uid gave me

* zero, not the real user ID. I tried to find what went

* wrong, but I couldn't do it in a short time, and

* I'm lazy - so I'll just use the system call to get the

* uid, the way a process would.

*

* For some reason, after I recompiled the kernel this

* problem went away.

*/

asmlinkage int (*getuid_call)();

/* The function we'll replace sys_open (the function

* called when you call the open system call) with. To

* find the exact prototype, with the number and type

* of arguments, we find the original function first

* (it's at fs/open.c).

*

* In theory, this means that we're tied to the

* current version of the kernel. In practice, the

* system calls almost never change (it would wreck havoc

* and require programs to be recompiled, since the system

* calls are the interface between the kernel and the processes). */

asmlinkage int our_sys_open(const char *filename, int flags, int mode) {

int i = 0;

char ch;

/* Check if this is the user we're spying on */

if (uid == getuid_call()) {

/* getuid_call is the getuid system call,

* which gives the uid of the user who

* ran the process which called the system

* call we got */

/* Report the file, if relevant */

printk("Opened file by %d: ", uid);

do {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(ch, filename+i);

#else

ch = get_user(filename+i);

#endif

i++;

printk("%c", ch);

} while (ch != 0);

printk("\n");

}

/* Call the original sys_open - otherwise, we lose

* the ability to open files */

return original_call(filename, flags, mode);

}

/* Initialize the module - replace the system call */

int init_module() {

/* Warning - too late for it now, but maybe for next time... */

printk("I'm dangerous. I hope you did a ");

printk("sync before you insmod'ed me.\n");

printk("My counterpart, cleanup_module(), is even");

printk("more dangerous. If\n");

printk("you value your file system, it will ");

printk("be \"sync; rmmod\" \n");

printk("when you remove this module.\n");

/* Keep a pointer to the original function in

* original_call, and then replace the system call

* in the system call table with our_sys_open */

original_call = sys_call_table[__NR_open];

sys_call_table[__NR_open] = our_sys_open;

/* To get the address of the function for system

* call foo, go to sys_call_table[__NR_foo]. */

printk("Spying on UID:%d\n", uid);

/* Get the system call for getuid */

getuid_call = sys_call_table[__NR_getuid];

return 0;

}

/* Cleanup - unregister the appropriate file from /proc */

void cleanup_module() {

/* Return the system call back to normal */

if (sys_call_table[__NR_open] != our_sys_open) {

printk("Somebody else also played with the ");

printk("open system call\n");

printk("The system may be left in ");

printk("an unstable state.\n");

}

sys_call_table[__NR_open] = original_call;

}

Отложенные процессы

Что Вы делаете, когда кто-то просит Вас о чем-то, что Вы не можете сделать сразу же? Если вы человек, и вы обеспокоены, единственное, что Вы можете сказать: «Не сейчас, я занят.». Но если вы модуль, Вы имеете другую возможность. Вы можете поместить процесс в спячку, чтобы он бездействовал, пока Вы не сможете обслужить его. В конце концов, процессы помещаются в спячку и пробуждаются ядром постоянно.

Этот модуль является примером этого подхода. Файл (названный /proc/sleep) может быть открыт только одним процессом сразу. Если файл уже открыт, модуль называет module_interruptible_sleep_on[7]. Эта функция изменяет состояние задачи (задача является структурой данных в ядре, которая хранит информацию относительно процесса и системного вызова) в состояние TASK_INTERRUPTIBLE, что означает, что задача не будет выполняться пока не будет пробуждена так или иначе, и добавляет процесс к WaitQ, очереди задач ждущих, чтобы обратиться к файлу. Затем функция обращается к планировщику за контекстным переключателем другого процесса, который может использовать CPU, то есть управление передается другому процессу.

Когда процесс закончит работу с файлом, тот закрывается, и вызывается module_close. Эта функция пробуждает все процессы в очереди (нет никакого механизма, чтобы пробудить только одни из них). Управление возвращается и процесс, который только закрыл файл, может продолжать выполняться. Планировщик решает, что тот процесс поработал достаточно и передает управление другому процессу. В конечном счете, один из процессов, который был в очереди, получит управление. Он продолжит выполнение с той точки, в которой был вызван module_interruptible_sleep_on[8]. Он может установить глобальную переменную, чтобы сообщить всем другим процессам, что файл является все еще открытым. Когда другие процессы получат часть времени CPU, они увидят глобальную переменную и продолжат спячку.

Чтобы сделать нашу жизнь более интересной, module_close не имеет монополии на пробуждение процессов которые ждут, чтобы обратиться к файлу. Сигнал Ctrl-C (SIGINT) может также пробуждать процесс[9]. В таком случае, мы хотим возвратить -EINTR немедленно. Это важно, так как пользователи могут, например, уничтожить процесс прежде, чем он получит доступ к файлу.

Имеется еще одна хитрость. Некоторые процессы не хотят спать: они хотят или получать то, что они хотят, немедленно или сообщить, что действие не может быть выполнено. Такие процессы используют флажок O_NONBLOCK при открытии файла. Ядро отвечает, возвращая код ошибки -EAGAIN из операций, которые иначе блокировали бы, типа открытия файла в этом примере. Программа cat_noblock, доступная в исходном каталоге для этой главы, может использоваться, чтобы открыть файл с флагом O_NONBLOCK.

sleep.c

/* sleep.c - create a /proc file, and if several

* processes try to open it at the same time, put all

* but one to sleep */

/* Copyright (C) 1998-99 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Necessary because we use proc fs */

#include <linux/proc_fs.h>

/* For putting processes to sleep and waking them up */

#include <linux/sched.h>

#include <linux/wrapper.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

#include <asm/uaccess.h> /* for get_user and put_user */

#endif

/* The module's file functions ********************** */

/* Here we keep the last message received, to prove

* that we can process our input */

#define MESSAGE_LENGTH 80

static char Message[MESSAGE_LENGTH];

/* Since we use the file operations struct, we can't use

* the special proc output provisions - we have to use

* a standard read function, which is this function */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static size_t module_output(

struct file *file, /* The file read */

char *buf, /* The buffer to put data to (in the user segment) */

size_t len, /* The length of the buffer */

loff_t *offset) /* Offset in the file - ignore */

#else

static int module_output(

struct inode *inode, /* The inode read */

struct file *file, /* The file read */

char *buf, /* The buffer to put data to (in the user segment) */

int len) /* The length of the buffer */

#endif

{

static int finished = 0;

int i;

char message[MESSAGE_LENGTH+30];

/* Return 0 to signify end of file - that we have

* nothing more to say at this point. */

if (finished) {

finished = 0;

return 0;

}

/* If you don't understand this by now, you're

* hopeless as a kernel programmer. */

sprintf(message, "Last input:%s\n", Message);

for(i=0; i<len && message[i]; i++) put_user(message[i], buf+i);

finished = 1;

return i; /* Return the number of bytes "read" */

}

/* This function receives input from the user when

* the user writes to the /proc file. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

static ssize_t module_input(

struct file *file, /* The file itself */

const char *buf, /* The buffer with input */

size_t length, /* The buffer's length */

loff_t *offset) /* offset to file - ignore */

#else

static int module_input(

struct inode *inode, /* The file's inode */

struct file *file, /* The file itself */

const char *buf, /* The buffer with the input */

int length) /* The buffer's length */

#endif

{

int i;

/* Put the input into Message, where module_output

* will later be able to use it */

for(i=0; i<MESSAGE_LENGTH-1 && i<length; i++)

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

get_user(Message[i], buf+i);

#else

Message[i] = get_user(buf+i);

#endif

/* we want a standard, zero terminated string */

Message[i] = '\0';

/* We need to return the number of input characters used */

return i;

}

/* 1 if the file is currently open by somebody */

int Already_Open = 0;

/* Queue of processes who want our file */

static struct wait_queue *WaitQ = NULL;

/* Called when the /proc file is opened */

static int module_open(struct inode *inode, struct file *file) {

/* If the file's flags include O_NONBLOCK, it means

* the process doesn't want to wait for the file.

* In this case, if the file is already open, we

* should fail with -EAGAIN, meaning "you'll have to

* try again", instead of blocking a process which

* would rather stay awake. */

if ((file->f_flags & O_NONBLOCK) && Already_Open) return -EAGAIN;

/* This is the correct place for MOD_INC_USE_COUNT

* because if a process is in the loop, which is

* within the kernel module, the kernel module must

* not be removed. */

MOD_INC_USE_COUNT;

/* If the file is already open, wait until it isn't */

while (Already_Open) {

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

int i, is_sig=0;

#endif

/* This function puts the current process,

* including any system calls, such as us, to sleep.

* Execution will be resumed right after the function

* call, either because somebody called

* wake_up(&WaitQ) (only module_close does that,

* when the file is closed) or when a signal, such

* as Ctrl-C, is sent to the process */

module_interruptible_sleep_on(&WaitQ);

/* If we woke up because we got a signal we're not

* blocking, return -EINTR (fail the system call).

* This allows processes to be killed or stopped. */

/*

* Emmanuel Papirakis:

*

* This is a little update to work with 2.2.*. Signals

* now are contained in two words (64 bits) and are

* stored in a structure that contains an array of two

* unsigned longs. We now have to make 2 checks in our if.

*

* Ori Pomerantz:

*

* Nobody promised me they'll never use more than 64

* bits, or that this book won't be used for a version

* of Linux with a word size of 16 bits. This code

* would work in any case. */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

for(i=0; i<_NSIG_WORDS && !is_sig; i++) is_sig = current->signal.sig[i] & current->blocked.sig[i];

if (is_sig) {

#else

if (current->signal & current->blocked) {

#endif

/* It's important to put MOD_DEC_USE_COUNT here,

* because for processes where the open is

* interrupted there will never be a corresponding

* close. If we don't decrement the usage count

* here, we will be left with a positive usage

* count which we'll have no way to bring down to

* zero, giving us an immortal module, which can

* only be killed by rebooting the machine. */

MOD_DEC_USE_COUNT;

return -EINTR;

}

}

/* If we got here, Already_Open must be zero */

/* Open the file */

Already_Open = 1;

return 0;  /* Allow the access */

}

/* Called when the /proc file is closed */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

int module_close(struct inode *inode, struct file *file)

#else

void module_close(struct inode *inode, struct file *file)

#endif

{

/* Set Already_Open to zero, so one of the processes

* in the WaitQ will be able to set Already_Open back

* to one and to open the file. All the other processes

* will be called when Already_Open is back to one, so

* they'll go back to sleep. */

Already_Open = 0;

/* Wake up all the processes in WaitQ, so if anybody

* is waiting for the file, they can have it. */

module_wake_up(&WaitQ);

MOD_DEC_USE_COUNT;

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return 0; /* success */

#endif

}

/* This function decides whether to allow an operation

* (return zero) or not allow it (return a non-zero

* which indicates why it is not allowed).

*

* The operation can be one of the following values:

* 0 - Execute (run the "file" - meaningless in our case)

* 2 - Write (input to the kernel module)

* 4 - Read (output from the kernel module)

*

* This is the real function that checks file

* permissions. The permissions returned by ls -l are

* for referece only, and can be overridden here. */

static int module_permission(struct inode *inode, int op) {

/* We allow everybody to read from our module, but

* only root (uid 0) may write to it */

if (op == 4 || (op == 2 && current->euid == 0)) return 0;

/* If it's anything else, access is denied */

return -EACCES;

}

/* Structures to register as the /proc file, with

* pointers to all the relevant functions. *********** */

/* File operations for our proc file. This is where

* we place pointers to all the functions called when

* somebody tries to do something to our file. NULL

* means we don't want to deal with something. */

static struct file_operations File_Ops_4_Our_Proc_File = {

NULL, /* lseek */

module_output, /* "read" from the file */

module_input, /* "write" to the file */

NULL, /* readdir */

NULL, /* select */

NULL, /* ioctl */

NULL, /* mmap */

module_open,/* called when the /proc file is opened */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

NULL, /* flush */

#endif

module_close /* called when it's classed */

};

/* Inode operations for our proc file. We need it so

* we'll have somewhere to specify the file operations

* structure we want to use, and the function we use for

* permissions. It's also possible to specify functions

* to be called for anything else which could be done to an

* inode (although we don't bother, we just put NULL). */

static struct inode_operations Inode_Ops_4_Our_Proc_File = {

&File_Ops_4_Our_Proc_File,

NULL, /* create */

NULL, /* lookup */

NULL, /* link */

NULL, /* unlink */

NULL, /* symlink */

NULL, /* mkdir */

NULL, /* rmdir */

NULL, /* mknod */

NULL, /* rename */

NULL, /* readlink */

NULL, /* follow_link */

NULL, /* readpage */

NULL, /* writepage */

NULL, /* bmap */

NULL, /* truncate */

module_permission /* check for permissions */

};

/* Directory entry */

static struct proc_dir_entry Our_Proc_File = {

0, /* Inode number - ignore, it will be filled by proc_register[_dynamic] */

5, /* Length of the file name */

"sleep", /* The file name */

S_IFREG | S_IRUGO | S_IWUSR,

/* File mode - this is a regular file which

* can be read by its owner, its group, and everybody

* else. Also, its owner can write to it.

*

* Actually, this field is just for reference, it's

* module_permission that does the actual check. It

* could use this field, but in our implementation it

* doesn't, for simplicity. */

1, /* Number of links (directories where the file is referenced) */

0, 0, /* The uid and gid for the file - we give it to root */

80, /* The size of the file reported by ls. */

&Inode_Ops_4_Our_Proc_File,

/* A pointer to the inode structure for

* the file, if we need it. In our case we

* do, because we need a write function. */

NULL

/* The read function for the file.

* Irrelevant, because we put it

* in the inode structure above */

};

/* Module initialization and cleanup **************** */

/* Initialize the module - register the proc file */

int init_module() {

/* Success if proc_register_dynamic is a success,

* failure otherwise */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,2,0)

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

/* proc_root is the root directory for the proc

* fs (/proc). This is where we want our file to be

* located. */

}

/* Cleanup - unregister our file from /proc. This could

* get dangerous if there are still processes waiting in

* WaitQ, because they are inside our open function,

* which will get unloaded. I'll explain how to avoid

* removal of a kernel module in such a case in chapter 10. */

void cleanup_module() {

proc_unregister(&proc_root, Our_Proc_File.low_ino);

}

Замена для printk

В начале (глава1), я сказал, что X и программирование модулей ядра не совместимы. Это истинно при разработке модуля, но в фактическом использовании, Вы должны быть способны послать сообщениям любому tty[10]. Это важно для идентификации ошибок после того, как модуль выпущен, потому что он будет использоваться через любой из терминалов.

Путем этого достичь: используя текущий указатель на задачу, выполняемую в настоящее время, получить ее структуру tty. Затем мы смотрим внутри этой структура tty, чтобы найти указатель на функцию, пишущую строку на tty. Ее мы и используем для вывода.

printk.c

/* printk.c - send textual output to the tty you're

* running on, regardless of whether it's passed

* through X11, telnet, etc.

*/

/* Copyright (C) 1998 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Necessary here */

#include <linux/sched.h> /* For current */

#include <linux/tty.h> /* For the tty declarations */

/* Print the string to the appropriate tty, the one

* the current task uses */

void print_string(char *str) {

struct tty_struct *my_tty;

/* The tty for the current task */

my_tty = current->tty;

/* If my_tty is NULL, it means that the current task

* has no tty you can print to (this is possible, for

* example, if it's a daemon). In this case, there's

* nothing we can do. */

if (my_tty != NULL) {

/* my_tty->driver is a struct which holds the tty's

* functions, one of which (write) is used to

* write strings to the tty. It can be used to take

* a string either from the user's memory segment

* or the kernel's memory segment.

*

* The function's first parameter is the tty to

* write to, because the same function would

* normally be used for all tty's of a certain type.

* The second parameter controls whether the

* function receives a string from kernel memory

* (false, 0) or from user memory (true, non zero).

* The third parameter is a pointer to a string,

* and the fourth parameter is the length of

* the string. */

(*(my_tty->driver).write)(

my_tty, /* The tty itself */

0, /* We don't take the string from user space */

str, /* String */

strlen(str)); /* Length */

/* ttys were originally hardware devices, which

* (usually) adhered strictly to the ASCII standard.

* According to ASCII, to move to a new line you

* need two characters, a carriage return and a

* line feed. In Unix, on the other hand, the

* ASCII line feed is used for both purposes - so

* we can't just use \n, because it wouldn't have

* a carriage return and the next line will

* start at the column right after the line feed.

*

* BTW, this is the reason why the text file

* is different between Unix and Windows.

* In CP/M and its derivatives, such as MS-DOS and

* Windows, the ASCII standard was strictly

* adhered to, and therefore a new line requires

* both a line feed and a carriage return. */

(*(my_tty->driver).write)(my_tty, 0, "\015\012", 2);

}

}

/* Module initialization and cleanup ****************** */

/* Initialize the module - register the proc file */

int init_module() {

print_string("Module Inserted");

return 0;

}

/* Cleanup - unregister our file from /proc */

void cleanup_module() {

print_string("Module Removed");

}

Планирование задач

Очень часто, мы имеем «вспомогательные» задачи, которые должны быть выполнены в некоторое время. Если задача должна быть выполнена, мы делаем это, помещая ее в файл crontab. Если задача должна быть выполнена модулем, мы имеем две возможности. Первая в том, чтобы поместить процесс в файл crontab, который пробудит модуль системным вызовом когда необходимо, например, открывая файл. Это ужасно неэффективно, однако мы выполняем новый процесс из crontab, читаем новую программу в память, и всем это только чтобы пробудить модуль, который уже находится в памяти.

Вместо того, чтобы делать это, мы можем создавать функцию, которая будет вызвана прерыванием таймера. Таким путем мы создаем запись, хранимую в struct tq_struct, которая хранит указатель на функцию. Затем мы используем queue_task, чтобы поместить эту задачу в список задач, tq_timer, который является списком задач, которые будут выполнены на следующем прерывании таймера. Поскольку мы хотим, чтобы функция продолжила выполнение, мы должны поместить ее обратно в tq_timer всякий раз, когда она вызвана для следующего прерывания таймера.

Имеется еще одна хитрость. Когда модуль удален rmmod, сначала проверяется счетчик ссылок. Если он нулевой, вызывается module_cleanup . Затем модуль удаляется из памяти со всеми функциями. Никто не проверяет(отмечает), чтобы видеть, содержит ли список задач таймера указатель на одну из тех функций, которые больше не будут доступны. Позже (с точки зрения компьютера, с точки зрения человека мгновенно: меньше чем сотая доля секунды, то есть фактически мгновенно), ядро получит прерывание от таймера и попробует вызывать функцию в списке задач. К сожалению, функции больше там нет. В большинстве случаев, страница памяти, где она была, не используется, и Вы получаете сообщение об ошибке. Но если некоторый другой код теперь хранится в том же самом месте памяти, дело плохо. К сожалению, мы не имеем простой способ удалить задачу из списка задачи.

Так как cleanup_module не может вернуть код ошибки (она имеет тип возврата void), возникает решение не позволить возвращаться вообще. Вместо завершения модуля мы вызываем sleep_on или module_sleep_on[11] чтобы отправить в спячку сам rmmod. Перед этим мы сообщаем функции, вызываемой по прерыванию таймера, чтобы она убрала себя из списка, устанавливая глобальную переменную. На следующем прерывании таймера, процесс rmmod будет пробужден, когда наша функция больше не в очереди, и безопасно удалит модуль.

sched.c

/* sched.c - schedule a function to be called on

* every timer interrupt.

*/

/* Copyright (C) 1998 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h> /* We're doing kernel work */

#include <linux/module.h> /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

/* Necessary because we use the proc fs */

#include <linux/proc_fs.h>

/* We scheduale tasks here */

#include <linux/tqueue.h>

/* We also need the ability to put ourselves to sleep

* and wake up later */

#include <linux/sched.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

/* The number of times the timer interrupt has been

* called so far */

static int TimerIntrpt = 0;

/* This is used by cleanup, to prevent the module from

* being unloaded while intrpt_routine is still in

* the task queue */

static struct wait_queue *WaitQ = NULL;

static void intrpt_routine(void *);

/* The task queue structure for this task, from tqueue.h */

static struct tq_struct Task = {

NULL,   /* Next item in list - queue_task will do

* this for us */

0,      /* A flag meaning we haven't been inserted

* into a task queue yet */

intrpt_routine, /* The function to run */

NULL    /* The void* parameter for that function */

};

/* This function will be called on every timer

* interrupt. Notice the void* pointer - task functions

* can be used for more than one purpose, each time

* getting a different parameter. */

static void intrpt_routine(void *irrelevant) {

/* Increment the counter */

TimerIntrpt++;

/* If cleanup wants us to die */

if (WaitQ != NULL) wake_up(&WaitQ);

/* Now cleanup_module can return */

else queue_task(&Task, &tq_timer);

/* Put ourselves back in the task queue */

}

/* Put data into the proc fs file. */

int procfile_read(char *buffer, char **buffer_location, off_t offset, int buffer_length, int zero) {

int len;  /* The number of bytes actually used */

/* This is static so it will still be in memory

* when we leave this function */

static char my_buffer[80];

static int count = 1;

/* We give all of our information in one go, so if

* the anybody asks us if we have more information

* the answer should always be no. */

if (offset > 0) return 0;

/* Fill the buffer and get its length */

len = sprintf(my_buffer, "Timer was called %d times so far\n", TimerIntrpt);

count++;

/* Tell the function which called us where the buffer is */

*buffer_location = my_buffer;

/* Return the length */

return len;

}

struct proc_dir_entry Our_Proc_File = {

0, /* Inode number - ignore, it will be filled by

* proc_register_dynamic */

5, /* Length of the file name */

"sched", /* The file name */

S_IFREG | S_IRUGO,

/* File mode - this is a regular file which can

* be read by its owner, its group, and everybody else */

1,  /* Number of links (directories where

* the file is referenced) */

0, 0,  /* The uid and gid for the file - we give it to root */

80, /* The size of the file reported by ls. */

NULL, /* functions which can be done on the

* inode (linking, removing, etc.) - we don't

* support any. */

procfile_read,

/* The read function for this file, the function called

* when somebody tries to read something from it. */

NULL

/* We could have here a function to fill the

* file's inode, to enable us to play with

* permissions, ownership, etc. */

};

/* Initialize the module - register the proc file */

int init_module() {

/* Put the task in the tq_timer task queue, so it

* will be executed at next timer interrupt */

queue_task(&Task, &tq_timer);

/* Success if proc_register_dynamic is a success,

* failure otherwise */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

return proc_register(&proc_root, &Our_Proc_File);

#else

return proc_register_dynamic(&proc_root, &Our_Proc_File);

#endif

}

/* Cleanup */

void cleanup_module() {

/* Unregister our /proc file */

proc_unregister(&proc_root, Our_Proc_File.low_ino);

/* Sleep until intrpt_routine is called one last

* time. This is necessary, because otherwise we'll

* deallocate the memory holding intrpt_routine and

* Task while tq_timer still references them.

* Notice that here we don't allow signals to

* interrupt us.

*

* Since WaitQ is now not NULL, this automatically

* tells the interrupt routine it's time to die. */

sleep_on(&WaitQ);

}

Обработчики прерываний

Везде, кроме последней главы, все, что мы пока делали в ядре, сводилось к запросам и ответам разным процессам или работали со специальными файлом, посылали ioctl или выдавали системный вызов. Но работа ядра не должна сводится только к обработке запросы. Другая задача, которая является очень важной, сводится к работе с аппаратными средствами, связанными с машиной.

Имеются два типа взаимодействия между CPU и остальной частью аппаратных средств компьютера. Первый тип, когда CPU дает команды аппаратным средствам, другой, когда аппаратные средства должны сообщить что-то CPU. Второй, названный прерываниями, является намного более тяжелым в работе, потому что с ним нужно иметь дело когда удобно аппаратным средствам, а не CPU. Аппаратные устройства обычно имеют очень маленькое количество ОЗУ, и если Вы не читаете их информацию сразу, она теряется.

Под Linux аппаратные прерывания названы IRQ (сокращение от Interrupt Requests)[12]. Имеется два типа IRQ: короткий и длинный. Короткий IRQ тот, который займет очень короткий период времени, в течение которого остальная часть машины будет блокирована, и никакие другие прерывания не будут обработаны. Длинный IRQ тот, который может занять более длительное время, в течение которого другие прерывания могут происходить (но не прерывания из того жесамого устройства). Если возможно, лучше объявить, что программа обработки прерывания будет длинной.

Когда CPU получает прерывание, он останавливает любые процессы (если это не более важное прерывание, тогда обработка пришедшего прерывания будет иметь место только когда более важное будет выполнено), сохраняет параметры в стеке и вызывает программу обработки прерывания (обработчик прерывания). Это означает, что некоторые вещи не позволяются в программе обработки прерывания непосредственно, потому что система находится в неизвестном состоянии. Решение для этой проблемы: программа обработки прерывания, должна разобраться что должно быть выполнено немедленно (обычно чтение чего-то из аппаратных средств или посылка чего-либо аппаратным средствам), затем запланировать обработку новой информации в более позднее время (это названо «нижней половиной») и вернуть управление. Ядро гарантирует вызов нижней половины как можно скорее. Когда оно это сделает, все позволенное в модулях будет доступно нашему обработчику.

Способ выполнять это состоит в том, чтобы вызвать request_irq для получения нашей программы обработки прерывания, вызванную, когда релевантное IRQ получено (их имеется 16 на платформах Intel). Эта функция получает IRQ номер, имя функции, флажки, имя для /proc/interrupts и параметр для для вызова обработчика прерываний. Флажки могут включать SA_SHIRQ, чтобы указать, что вы желаете совместно использовать IRQ с другими программами обработки прерывания (обычно, потому что ряд аппаратных устройств сидит на том же самом IRQ) и SA_INTERRUPT, чтобы указать, что это быстрое прерывание. Эта функция сработает только если еще нет драйвера на этом IRQ, или если вы оба желаете совместно использовать данный IRQ.

Затем, из программы обработки прерывания, мы связываемся с аппаратными средствами и затем используем queue_task_irq с tq_immediate и mark_bh(BH_IMMEDIATE), чтобы запланировать нижнюю половину. Причина по которой мы не можем использовать стандартный вызов queue_task в версии 2.0 в том, что прерывание могло бы случиться в середине какого-то процесса. queue_task[13]. mark_bh нужен потому что более ранние версии Linux имели массив только из 32 нижних частей, и теперь одни из них (а именно BH_IMMEDIATE) используется для связанного списка нижних частей драйверов.

Клавиатура в архитектуре Intel

Предупреждение: Остальная часть этой главы полностью специфическая для Intel. Если вы не запускаете код на платформе Intel, он не будет работать.

Я имел проблему с написанием типового кода для этой главы. С одной стороны, для примера, чтобы быть полезным он должен выполняться на любом компьютере со значимыми результатами. С другой стороны, ядро уже включает драйверы устройства для всех общих устройств, и те драйверы устройства не будут сосуществовать с тем, что я собираюсь писать. Решение, которое я нашел состояло в том, чтобы написать обработчик для прерывания клавиатуры и сначала отключать стандартную программу обработки прерывания клавиатуры. Так как это определено как static в исходных файлах ядра (в файле drivers/char/keyboard.c), нет никакого способа восстановить обработчик.

Этот код связывает себя с IRQ 1, который является IRQ клавиатуры, управляемой в архитектуре Intel. При получении прерывания от клавиатуры, он читает состояние клавиатуры (inb(0x64)) и скэн-кода, который является значением, возвращенным клавиатурой. Затем, как только ядро думает, что это выполнимо, выполняется got_char, который дает код используемой клавиши (первые семь битов скэн-кода) и была ли она нажата (если 8-ой бит нулевой) или отпущена (если он равен единице).

intrpt.c

/* intrpt.c - An interrupt handler. */

/* Copyright (C) 1998 by Ori Pomerantz */

/* The necessary header files */

/* Standard in kernel modules */

#include <linux/kernel.h>   /* We're doing kernel work */

#include <linux/module.h>   /* Specifically, a module */

/* Deal with CONFIG_MODVERSIONS */

#if CONFIG_MODVERSIONS==1

#define MODVERSIONS

#include <linux/modversions.h>

#endif

#include <linux/sched.h>

#include <linux/tqueue.h>

/* We want an interrupt */

#include <linux/interrupt.h>

#include <asm/io.h>

/* In 2.2.3 /usr/include/linux/version.h includes a

* macro for this, but 2.0.35 doesn't - so I add it

* here if necessary. */

#ifndef KERNEL_VERSION

#define KERNEL_VERSION(a,b,c) ((a)*65536+(b)*256+(c))

#endif

/* Bottom Half - this will get called by the kernel

* as soon as it's safe to do everything normally

* allowed by kernel modules. */

static void got_char(void *scancode) {

printk("Scan Code %x %s.\n", (int) *((char *) scancode) & 0x7F, *((char *) scancode) & 0x80 ? "Released" : "Pressed");

}

/* This function services keyboard interrupts. It reads

* the relevant information from the keyboard and then

* scheduales the bottom half to run when the kernel

* considers it safe. */

void irq_handler(int irq, void *dev_id, struct pt_regs *regs) {

/* This variables are static because they need to be

* accessible (through pointers) to the bottom

* half routine. */

static unsigned char scancode;

static struct tq_struct task = {NULL, 0, got_char, &scancode};

unsigned char status;

/* Read keyboard status */

status = inb(0x64);

scancode = inb(0x60);

/* Scheduale bottom half to run */

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,2,0)

queue_task(&task, &tq_immediate);

#else

queue_task_irq(&task, &tq_immediate);

#endif

mark_bh(IMMEDIATE_BH);

}

/* Initialize the module - register the IRQ handler */

int init_module() {

/* Since the keyboard handler won't co-exist with

* another handler, such as us, we have to disable

* it (free its IRQ) before we do anything. Since we

* don't know where it is, there's no way to

* reinstate it later - so the computer will have to

* be rebooted when we're done.  */

free_irq(1, NULL);

/* Request IRQ 1, the keyboard IRQ, to go to our irq_handler. */

return request_irq(

1,  /* The number of the keyboard IRQ on PCs */

irq_handler,  /* our handler */

SA_SHIRQ,

/* SA_SHIRQ means we're willing to have othe

* handlers on this IRQ.

*

* SA_INTERRUPT can be used to make the

* handler into a fast interrupt. */

"test_keyboard_irq_handler", NULL);

}

/* Cleanup */

void cleanup_module() {

/* This is only here for completeness. It's totally

* irrelevant, since we don't have a way to restore

* the normal keyboard interrupt so the computer

* is completely useless and has to be rebooted. */

free_irq(1, NULL);

}

Симметричная многопроцессорность

Один из самых простых (самый дешевый) способ улучшить аппаратную эффективность: поместить больше чем один CPU на плате. Когда такое сделано, могут быть два варианта: либо CPU берут различные работы (асимметричная многопроцессорная обработка) или они все работают параллельно, делая ту же самую работу (симметрическая многопроцессорная обработка, a.k.a. SMP). Выполнение асимметричной многопроцессорной обработки действительно требует специализированного знания относительно задач, которые компьютер должен делать. Такое знание является недоступным в универсальной операционной системе типа Linux. С другой стороны, симметрическая многопроцессорная обработка относительно проста в реализации.

В симметрической многопроцессорной среде CPU совместно используют ту же самую память, и в результате код, работающий на одном CPU может воздействовать на память используемую другим. Вы больше не можете быть уверены, что переменная, которую вы установили в некоторое значение на предыдущей строке все еще имеет то же самое значение: другой CPU может его поменять, в то время как Вы не смотрели.

В случае программирования процесса это обычно не проблема, потому что процесс будет обычно выполняться только на одном CPU сразу[14]. Ядро, с другой стороны, может быть вызвано различными процессами, работающими на различных CPU.

В версии 2.0.x, это не проблема, потому что все ядро находится в одном большом spinlock. Это означает что, если один CPU работает с ядром, и другой CPU хочет обратиться к ядру, например из-за системного вызова, он должно ждать, до тех пор пока первый CPU завершит работу с ядром. Это делает Linux SMP безопасным[15], но неэффективным.

В версии 2.2.x, несколько CPU могут работать с ядром одновременно. Это авторы модуля должны знать. Я надеюсь, что смогу получить доступ к машине с несколькими процессорами, и следующая версия этой книги будет включать большее количество информации.

Общие ловушки

Прежде чем писать ядерные модули, надо учесть несколько важных моментов.

1. Использование стандартных библиотек. Вы не можете делать этого. В модуле, Вы можете использовать только функции, которые Вы можете увидеть в /proc/ksyms.

2. Запрет прерываний. Если Вы на краткое время запретите прерывания, ничего ужасного не произойдет. Но если забудете их разрешить, придется выходить из данной ситуации выключением питания.

Различия между версиями 2.0 и 2.2

Я не знаю, что все ядро достаточно хорошо документирует все изменения. В ходе преобразования примеров (или фактически, адаптации изменений Еммануела Папиракиса) я натолкнулся на следующие различия. Я привожу их все здесь вместе, чтобы помочь программистам, особенно тем, кто обучался на предыдущих версий этой книги и наиболее знакомы с методами, которые я использую, и преобразовываю в новую версию.

Дополнительный ресурс для людей, кто желают преобразоваться в 2.2, находится по адресу: http://www.atnf.csiro.au/~rgooch/linux/docs/porting-to-2.2.htm.

1. asm/uaccess.h: Если Вы нуждаетесь в put_user или get_user: Вы должны #include его.

2. get_user в версии 2.2: get_user получает указатель в памяти пользователя и переменную в памяти ядра, чтобы заполнить информацией. Причина для этого в том, что get_user, может теперь читать два или четыре байта одновременно, если переменная, которую мы читаем длиной два или четыре байта.

3. file_operations: Она теперь выполняет функцию flush между функциями open and close.

4. close в file_operations: В версии 2.2, функция close возвращает целое число, так что она может вернуть код ошибки.

5. read и write в file_operations: Изменились заголовки для этих функций. Они сейчас возвращают ssize_t вместо целого числа, и их список параметра различен. Inode больше не параметр, и с другой стороны смещение в файле.

6. proc_register_dynamic: Функция больше не существует. Вместо нее Вы вызываете proc_register и помещаете ноль в поле inode структуры.

7. Сигналы: Сигналы в структуре task больше не 32 разрядные целые числа, а массив _NSIG_WORDS целых чисел.

8. queue_task_irq: Даже если Вы хотите обратиться к запланированной задаче из внутреннего обработчика прерывания, Вы используете queue_task, а не queue_task_irq.

9. Параметры модуля: Вы объявляете параметры модуля как глобальные переменные. В 2.2 Вы должны также использовать MODULE_PARM, чтобы объявить их тип. Это большое усовершенствование, потому что позволяет модулю получать параметры строкового типа, которые начинаются с цифр.

10. Симметричная многозадачность: Ядро больше не заперто внутри одного огромного spinlock, что означает что два процессора могут одновременно обрабатывать разные части ядра. Модули должны знать о SMP.

Что дальше?

Я мог бы легко добавить еще несколько глав в эту книгу. Я мог бы добавить главу о создании новых файловых систем, или о добавлении новых стеков протоколов (как будто имеется потребность в такой главе: вы должны рыть под землей, чтобы найти стек протоколов, не поддерижаемый Linux). Я мог бы добавить объяснение ядерных механизмов, которых мы не коснулись, типа начальной загрузки или дискового интерфейса.

Однако, я выбрал не это. Моя цель состояла в том, чтобы обеспечить введение в тайны программирования модулей ядра и обучить общим методам для этой цели. Для людей, серьезно заинтересованных программированием ядра, я рекомендую список ресурсов на: http://jungla.dit.upm.es/~jmseyas/linux/kernel/hackers-docs.htm. Также, как сказал Linus, самый лучший путь состоит в том, чтобы прочитать исходный текст самостоятельно.

Если вы заинтересованы в большом количестве примеров коротких модулей, я рекомендую журнал "Phrack". Даже если вы не заинтересованы защитой, модули имеют хорошие примеры того, что Вы можете делать внутри ядра, и они достаточно короткие, чтобы не требовать слишком много усилий для понимания.

Я надеюсь, что я помог Вам в ваших попытках стать лучшим программистом. И, если Вы пишете полезные модули для ядра, я надеюсь, что Вы издаете их под GPL, так что я тоже смогу их использовать.

Goods and Services

Честно говоря, я так и не понял, что именно в данном разделе хотел сказать автор, так что привожу без перевода.

I hope nobody minds the shameless promotions here. They are all things which are likely to be of use to beginning Linux Kernel Module programmers.

Получение книги в бумажном виде

Издательство Coriolis group собиралось печатать эту книгу летом 1999 года. Правда, на английском языке. Попробуйте ее найти, если считаете нужным иметь бумажную копию.

Поблагодарите автора

Это свободно распространяемый документ. Вы не имеете никаких обязательств кроме указанных GNU Public License (GPL) (Приложение   E). Однако, если Вы считаете нужным как-то отблагодарить за получение этой книги, имеется несколько способов, которые Вы могли бы использовать.

• Пошлите мне открытку по адресу:

Ori Pomerantz

Apt. #1032

2355 N Hwy 360

Grand Prairie

TX 75050

USA

Если Вы хотите получить благодарность, включите ваш адрес электронной почты.

• Жертвуйте деньги, или еще лучше, время, на Свободное Программистское Сообщество. Пишите программы или документы и издавайте их по GPL. Обучите другие людей, как использовать свободное программное обеспечение типа Linux или Perl.

• Объясните людям, что нередко эгоизм совместим с проживанием в обществе или с помощью другим людям. Я писал этот документ, веря публикации в то, что он будет опубликован и немало принесет мне в будущем. В то же самое время, я написал книгу, которая помогает Вам. Не забудьте, что счастливые люди обычно более полезны для себя чем несчастные люди, и способные люди лучше, чем люди низкой способности.

• Будьте счастливы. Если я когда-либо встречусь с Вами, это сделает встречу лучше для меня, а Вас более полезными для меня ; -).

Лицензия фонда GNU (The GNU General Public License)

Во избежание искажения смысла лицензии, являющейся юридическим документом, привожу ее в оригинальном виде (скопирована из оригинальной документации).

Printed below is the GNU General Public License (the GPL or copyleft ), under which this book is licensed.

GNU GENERAL PUBLIC LICENSE

Version 2, June 1991

Copyright ©1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.

PREAMBLE

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software-to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

GNU GENERAL PUBLIC LICENSE

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The `Program', below, refers to any such program or work, and a `work based on the Program' means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term `modification'.) Each licensee is addressed as `you'.

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

a. You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

b. You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

c. If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

a. Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

b. Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

c. Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and `any later version', you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM `AS IS' WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

APPENDIX: HOW TO APPLY THESE TERMS TO YOUR NEW PROGRAMS

If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the `copyright' line and a pointer to where the full notice is found.

one line to give the program's name and a brief idea of what it does. Copyright ©19yy name of author

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

The hypothetical commands show w and show c should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than show w and show c; they could even be mouse-clicks or menu items-whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a `copyright disclaimer' for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program Gnomovision (which makes passes at compilers) written by James Hacker.

signature of Ty Coon, 1 April 1989

Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

Примечания

1

Причина, по которой я предпочитаю не компилировать как root в том, что так наиболее безопасно. Я работаю в службе компьютерной безопасности, так что я параноидален.

2

В версии 2.0, в версии 2.2 это выполняется для нас автоматически, если мы устанавливаем inode в ноль.

3

Различие в том, что операции с файлом имеют дело именно с файлом непосредственно, а inode операции имеют дело с путями ссылки на файл, типа создания связей с ним.

4

Обратите внимание, что здесь роли чтения и записи перевернуты снова , так что при чтении ioctl должен послать информацию ядру а при записи должен получить информацию из ядра.

5

Это не точно. Вы не способны передать структуру, например, через ioctl. Но Вы можете передать указатель на структуру.

6

Не может быть с тех пор, как под C объектный файл имеет только расположение глобальных переменных, но не их тип. Именно поэтому файлы заголовков необходимы

7

Самый простой способ держать файл открытым состоит в том, чтобы открыть его tail -f.

8

Это означает, что процесс — все еще в ядерном режиме — насколько процесс затронут, он выдал системный вызов open и системный вызов все же не возвратился. Процесс не знает, что кто-то еще использовал CPU в течение времени между моментом, выдачи обращения и возвратом

9

Это потому что мы использовали module_interruptible_sleep_on. Мы могли бы использовать module_sleep_on вместо него, но это закончится чрезвычайно сердитыми пользователями, чей Ctrl-C игнорируется.

10

Teletype, первоначально комбинация принтера с клавиатурой, используемая, для связи с Unix системой, и сегодня абстракция для текстового потока, используемого для программы Unix, независимо от того, является ли это физическим терминалом, xterm на дисплее X, сетевое подключение, используемое telnet и т.д.

11

Они действительно те же самые.

12

Это стандартная вещь в архитектуре Intel, на которой началась разработка системы Linux.

13

queue_task_irq защищен от этого глобальной блокировкой. В версии 2.2 queue_task_irq и queue_task защищены блокировкой.

14

Исключительная ситуация: threaded-процессы, которые могут выполняться на нескольких CPU сразу.

15

Значение безопасно, чтобы использовать с SMP.

Content

Ori Pomerantz
Энциклопедия разработчика модулей ядра Linux

Введение

Кто должен читать это

Замечания о стиле

Изменения

Новое в версии 1.0.1

Новое в версии 1.1.0

Благодарности

Благодарности к версии 1.0.1

Благодарности к версии 1.1.0

Hello, world

Makefile'ы для модулей ядра

Модули ядра из нескольких файлов

Файлы символьных устройств

Исходники для разных версий ядра Files

Файловая система /proc

Использование /proc для ввода

Работа с файлами устройств (запись и IOCTL)

Загрузочные параметры

Системные вызовы

Отложенные процессы

Замена для printk

Планирование задач

Обработчики прерываний

Клавиатура в архитектуре Intel

Симметричная многопроцессорность

Общие ловушки

Различия между версиями 2.0 и 2.2

Что дальше?

Goods and Services

Получение книги в бумажном виде

Поблагодарите автора

Лицензия фонда GNU (The GNU General Public License)

Примечания