Четверг, 2025-01-23, 1:02 AM
Статьи - HI - TECH
Приветствую Вас Гость | RSS
Главная страница Каталог статей Регистрация Вход
Меню сайта

Категории каталога
BIOS [9]
Статьи по BIOS
CD/DVD [6]
Статьи по CD/DVD
HDD - Жёсткие диски [8]
Статьи по HDD - жестким дискам
Модем [4]
Статьи по модемам
Блоки питания [5]
Статьи о блоках питания
Windows и общие вопросы [41]
Статьи по Windows и общим вопросам
Windows VISTA [8]
Статьи по Windos Vista
Linux [20]
Статьи по Linux

Наш опрос
Выбираем ОС
Всего ответов: 192

Начало » Статьи » HI - TECH » Linux

The Linux Kernel Module Programming Guide
Глава 9. Замена printk
9.1. Замена printk
Ранее я уже говорил о том, что XWindow и разработка модулей ядра есть вещи несовместимые. Это все так, но иногда возникает необходимость выдачи сообщений от модуля на любой tty. [13]

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

Пример 9-1. print_string.c

/*
* print_string.c - отправляет вывод на
* tty терминала, независимо от того
* X11 это, или telnet, или что-то еще.
* Делается это путем вывода строки на tty,
* ассоциированный с текущим процессом.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h> /* определение current */
#include <linux/tty.h> /* определение tty */
#include <linux/version.h> /* макрос LINUX_VERSION_CODE */

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter Jay Salzman");

static void print_string(char *str)
{
struct tty_struct *my_tty;

/*
* Начиная с версии 2.6.6, структура tty перекочевала в структуру signal
*/
#if ( LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,5) )
/*
* tty текущего процесса
*/
my_tty = current->tty;
#else
/*
* tty текущего процесса, для ядер 2.6.6 и выше
*/
my_tty = current->signal->tty;
#endif

/*
* если my_tty == NULL, то текущий процесс не имеет tty на который можно
* было бы что нибудь вывести (например, демон).
* В этом случае нам ничего не нужно делать.
*/
if (my_tty != NULL) {

/*
* my_tty->driver -- структура, которая
* хранит указатели на функции-обработчики,
* одна из которых (write) используется
* для вывода строк на tty.
*
* Первый параметр функции -- tty, на
* который осуществляется вывод,
* поскольку эта функция обычно
* используется для вывода на все
* tty одного и того же типа.
* Второй параметр -- флаг расположения строки
* если строка находится в пространстве ядра,
* флаг равен false (0)
* если в пространстве пользователя, то true (не ноль).
* Третий параметр -- указатель на строку.
* Четвертый параметр -- длина строки.
*/
((my_tty->driver)->write) (my_tty, /* Собственно tty */
0, /* Строка в пространстве ядра */
str, /* Сама строка */
strlen(str)); /* Длина строки */

/*
* tty изначально был аппаратным устройством, который (обычно)
* ограничивался стандартом ASCII, в котором перевод строки
* включал в себя два символа -- "возврат каретки" и "перевод строки".
* В Unix, символ ASCII -- "перевод строки" заменял оба этих символа,
* поэтому нам придется использовать для перевода строки
* оба символа.
*
* Это одна из причин различий между текстовыми файлами Unix и
* MS Windows. CP/M и ее "наследницы", например MS-DOS и
* MS Windows, строго придерживались стандарта ASCII.
*/
((my_tty->driver)->write) (my_tty, 0, "\015\012", 2);
}
}

static int __init print_string_init(void)
{
print_string("The module has been inserted. Hello world!");
return 0;
}

static void __exit print_string_exit(void)
{
print_string("The module has been removed. Farewell world!");
}

module_init(print_string_init);
module_exit(print_string_exit);

9.2. Управление индикаторами на клавиатуре
При определенных условиях у вас может возникнуть желание дать вашему модулю более простой и более прямолинейный способ взаимодействия с внешним миром. Изменение состояния светодиодных индикаторов клавиатуры может быть одним из вариантов привлечения внимания пользователя или отображения некоторого состояния. Светодиодные индикаторы присутствуют на любой клавиатуре, они всегда находятся в поле зрения, они не нуждаются в установке, и их "подмаргивание" достаточно ненавязчиво, по сравнению с выводом на tty или в файл.

Следующий исходный код иллюстрирует модуль ядра, который после загрузки начинает мигать индикаторами клавиатуры.

Пример 9-2. kbleds.c

/*
* kbleds.c - Мигание индикаторами на клавиатуре.
*/

#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/tty.h> /* определение fg_console, MAX_NR_CONSOLES */
#include <linux/kd.h> /* определение KDSETLED */
#include <linux/console_struct.h> /* определение vc_cons */

MODULE_DESCRIPTION("Пример module illustrating the use of Keyboard LEDs.");
MODULE_AUTHOR("Daniele Paolo Scarpazza");
MODULE_LICENSE("GPL");

struct timer_list my_timer;
struct tty_driver *my_driver;
char kbledstatus = 0;

#define BLINK_DELAY HZ/5
#define ALL_LEDS_ON 0x07
#define RESTORE_LEDS 0xFF

/*
* Функция my_timer_func мигает индикаторами
* на клавиатуре периодически вызывая
* ioctl() драйвера клавиатуры с командой KDSETLED.
* Дополнительную информацию,
* по командам ioctl виртуального терминала, вы найдете в:
* /usr/src/linux/drivers/char/vt_ioctl.c, function vt_ioctl().
*
* Дополнительный аргумент команды KDSETLED -- значение 7
* (перевод в режим LED_SHOW_IOCTL -- управление
* индикаторами через ioctl), значение 0xFF --
* (любое значение, большее 7, перевод в режим
* LED_SHOW_FLAGS --
* отображение фактического состояния клавиатуры).
* Дополнительная информация:
* /usr/src/linux/drivers/char/keyboard.c,
* function setledstate().
*
*/

static void my_timer_func(unsigned long ptr)
{
int *pstatus = (int *)ptr;

if (*pstatus == ALL_LEDS_ON)
*pstatus = RESTORE_LEDS;
else
*pstatus = ALL_LEDS_ON;

(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
*pstatus);

my_timer.expires = jiffies + BLINK_DELAY;
add_timer(&my_timer);
}

static int __init kbleds_init(void)
{
int i;

printk(KERN_INFO "kbleds: loading\n");
printk(KERN_INFO "kbleds: fgconsole is %x\n", fg_console);
for (i = 0; i < MAX_NR_CONSOLES; i++) {
if (!vc_cons[i].d)
break;
printk(KERN_INFO "poet_atkm: console[%i/%i] #%i, tty %lx\n", i,
MAX_NR_CONSOLES, vc_cons[i].d->vc_num,
(unsigned long)vc_cons[i].d->vc_tty);
}
printk(KERN_INFO "kbleds: finished scanning consoles\n");

my_driver = vc_cons[fg_console].d->vc_tty->driver;
printk(KERN_INFO "kbleds: tty driver magic %x\n", my_driver->magic);

/*
* Инициировать таймер
*/
init_timer(&my_timer);
my_timer.function = my_timer_func;
my_timer.data = (unsigned long)&kbledstatus;
my_timer.expires = jiffies + BLINK_DELAY;
add_timer(&my_timer);

return 0;
}

static void __exit kbleds_cleanup(void)
{
printk(KERN_INFO "kbleds: unloading...\n");
del_timer(&my_timer);
(my_driver->ioctl) (vc_cons[fg_console].d->vc_tty, NULL, KDSETLED,
RESTORE_LEDS);
}

module_init(kbleds_init);
module_exit(kbleds_cleanup);


Если ни один из примеров данной главы вас не устраивает, можно попробовать другие хитрости, скрытые в ядре. Может быть вам подойдет опция CONFIG_LL_DEBUG в make menuconfig? Включив ее, вы получите низкоуровневый доступ к последовательному порту. Как бы страшно это ни прозвучало, но можете попробовать изменить реализацию kernel/printk.c или какого нибудь другого системного вызова для вывода ascii-строк, чтобы иметь возможность отслеживать действия вашего модуля через последовательную линию связи.

Несмотря на то, что вы уже встретили в этой книге намало наглядных приемов отладки, существует еще ряд моментов, которые вам необходимо знать. Отладка -- это всегда очень утомительный процесс и практически всегда он сопровождается внедрением значительного количества отладочного кода. Может сложиться так, что отладочный код не дает проявляться некоторым ошибкам. Поэтому, при выпуске вашего модуля, старайтесь свести отладочный код к минимуму и "прогнать" модуль еще раз, пытаясь обнаружить какие либо ошибки.

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

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

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

Так как cleanup_module не может вернуть код ошибки (она не имеет возвращаемого значения), то напрашивается решение -- приостановить процедуру завершения работы модуля. Вместо того, чтобы немедленно завершить работу функции cleanup_module, мы можем приостановить работу команды rmmod. Затем, установив глобальную переменную, сообщить функции, вызываемой по прерыванию таймера, чтобы она убрала себя из очереди (точнее -- чтобы она опять не вставляла себя в очередь). На ближайшем прерывании таймера, процесс rmmod будет "разбужен", когда функция удалит себя из очереди таймера и удаление модуля станет безопасным.

Пример 10-1. sched.c

/*
* sched.c - реализация срабатывания по таймеру.
*
* Copyright (C) 2001 by Peter Jay Salzman
*/

/*
* Необходимые заголовочные файлы
*/

/*
* Обычные, для модулей ядра
*/
#include <linux/kernel.h> /* Все-таки мы работаем с ядром! */
#include <linux/module.h> /* Необходимо для любого модуля */
#include <linux/proc_fs.h> /* Необходимо для работы с /proc */
#include <linux/workqueue.h> /* очереди задач */
#include <linux/sched.h> /* Взаимодействие с планировщиком */
#include <linux/init.h> /* макросы __init и __exit */
#include <linux/interrupt.h> /* определение irqreturn_t */

struct proc_dir_entry *Our_Proc_File;
#define PROC_ENTRY_FILENAME "sched"
#define MY_WORK_QUEUE_NAME "WQsched.c"

/*
* Счетчик срабатываний по таймеру
*/
static int TimerIntrpt = 0;

static void intrpt_routine(void *);

static int die = 0; /* 1 -- завершить работу */

/*
* очередь задач, создается для того,
* чтобы поместить в очередь таймера (workqueue.h)
*/
static struct workqueue_struct *my_workqueue;

static struct work_struct Task;
static DECLARE_WORK(Task, intrpt_routine, NULL);

/*
* Функция-обработчик прерывания от таймера.
* Обратите внимание на аргумент типа void*
* функция может получать дополнительные
* аргументы посредством этого указателя.
*/
static void intrpt_routine(void *irrelevant)
{
/*
* Нарастить счетчик
*/
TimerIntrpt++;

/*
* Если признак завершения сброшен,
* то опять вставить себя в очередь таймера
*/
if (die == 0)
queue_delayed_work(my_workqueue, &Task, 100);
}

/*
* Запись данных в файл /proc.
*/
ssize_t
procfile_read(char *buffer,
char **buffer_location,
off_t offset, int buffer_length, int *eof, void *data)
{
int len; /* Фактическое число записанных байт */

/*
* Переменные объявлены как static, поэтому они располагаются не на стеке
* функции, а в памяти модуля
*/
static char my_buffer[80];

static int count = 1;

/*
* Все сведения выдаются за один присест,
* поэтому, если смещение != 0, то значит
* нам нечего больше сказать, поэтому возвращается
* 0, в качестве признака конца файла.
*/
if (offset > 0)
return 0;

/*
* Заполнить буфер и получить его длину
*/
len = sprintf(my_buffer, "Timer called %d times so far\n", TimerIntrpt);
count++;

/*
* Указать адрес буфера
*/
*buffer_location = my_buffer;

/*
* Вернуть длину буфера
*/
return len;
}

/*
* Функция инициализации - зарегистрировать файл в /proc
*/
int __init init_module()
{
int rv = 0;
/*
* Создать очередь задач с нашей задачей и поместить ее в очередь таймера
*/
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
queue_delayed_work(my_workqueue, &Task, 100);

Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL);
Our_Proc_File->read_proc = procfile_read;
Our_Proc_File->owner = THIS_MODULE;
Our_Proc_File->mode = S_IFREG | S_IRUGO;
Our_Proc_File->uid = 0;
Our_Proc_File->gid = 0;
Our_Proc_File->size = 80;

if (Our_Proc_File == NULL) {
rv = -ENOMEM;
remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
printk(KERN_INFO "Error: Could not initialize /proc/%s\n",
PROC_ENTRY_FILENAME);
}

return rv;
}

/*
* Завершение работы
*/
void __exit cleanup_module()
{
/*
* Удалить файл из /proc
*/
remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root);
printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_FILENAME);
/* Известить функцию обработки прерываний о завершении работы */
die = 1;
cancel_delayed_work(&Task);
flush_workqueue(my_workqueue); /* ждать пока отработает таймер */
destroy_workqueue(my_workqueue);

/*
* Приостановить работу, пока intrpt_routine не
* отработает в последний раз.
* Это необходимо, поскольку мы освобождаем память,
* занимаемую этой функцией.
*/

}

/*
* некоторые функции, относящиеся к work_queue
* доступны только если модуль лицензирован под GPL
*/
MODULE_LICENSE("GPL");


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

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

В Linux аппаратные прерывания называются IRQ (сокращенно от Interrupt ReQuests -- Запросы на Прерывание). [14] Имеется два типа IRQ: "короткие" и "длинные". "Короткие" IRQ занимают очень короткий период времени, в течение которого работа операционной системы будет заблокирована, а так же будет невозможна обработка других прерываний. "Длинные" IRQ могут занять довольно продолжительное время, в течение которого могут обрабатываться и другие прерывания (но не прерывания из того же самого устройства). Поэтому, иногда бывает благоразумным разбить выполнение работы на исполняемую внутри обработчика прерываний (т.е. подтверждение прерывания, изменение состояния и пр.) и работу, которая может быть отложена на некоторое время (например постобработка данных, активизация процессов, ожидающих эти данные и т.п.). Если это возможно, лучше объявлять обработчики прерывания "длинными".

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

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

Во время обработки прерывания, из функции-обработчика прерывания, мы можем получить данные от устройства и затем, с помощью queue_task_irq, tq_immediate и mark_bh(BH_IMMEDIATE), запланировать "нижнюю половину". В ранних версиях Linux имелся массив только из 32 "нижних половин", теперь же, одна из них (а именно BH_IMMEDIATE) используется для обслуживания целого списка "нижних половин" драйверов. Вызов mark_bh(BH_IMMEDIATE) как раз и вставляет "нижнюю половину" драйвера в этот список, планируя таким образом ее исполнение.

11.2. Клавиатура на архитектуре Intel
Материал, рассматриваемый в оставшейся части этой главы, может быть применим исключительно к архитектуре Intel. На других платформах код примера работать не будет.

Было очень трудно выбрать тип драйвера, который можно было бы использовать в качестве примера в этой главе. С одной стороны пример должен быть достаточно полезным, он должен работать на любом компьютере и быть достаточно выразительным. С другой стороны, в ядро уже включено огромное количество драйверов практически для всех общеизвестных и широкораспространенных устройств. Эти драйверы не смогли бы совместно работать с тем, что я собирался написать. Наконец я принял решение представить в качестве примера -- обработчик прерываний от клавиатуры, но для демонстрации работоспособности кода сначала придется отключить стандартный обработчик прерываний от клавиатуры, а так как этот символ объявлен как static (в файле drivers/char/keyboard.c), то нет никакого способа восстановить обработчик. Поэтому, прежде чем вы дадите команду insmod, перейдите в другую консоль и дайте команду sleep 120; reboot, если ваша файловая система представляет для вас хоть какую-нибудь ценность.

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

Пример 11-1. intrpt.c

/*
* intrpt.c - Обработчик прерываний.
*
* Copyright (C) 2001 by Peter Jay Salzman
*/

/*
* Standard in kernel modules
*/
#include <linux/kernel.h> /* Все-таки мы работаем с ядром! */
#include <linux/module.h> /* Необходимо для любого модуля */
#include <linux/workqueue.h> /* очереди задач */
#include <linux/sched.h> /* Взаимодействие с планировщиком */
#include <linux/interrupt.h> /* определение irqreturn_t */
#include <asm/io.h>

#define MY_WORK_QUEUE_NAME "WQsched.c"

static struct workqueue_struct *my_workqueue;

/*
* Эта функция вызывается ядром, поэтому в ней будут безопасны все действия
* которые допустимы в модулях ядра.
*/
static void got_char(void *scancode)
{
printk("Scan Code %x %s.\n",
(int)*((char *)scancode) & 0x7F,
*((char *)scancode) & 0x80 ? "Released" : "Pressed");
}

/*
* Обработчик прерываний от клавиатуры.
* Он считывает информацию с клавиатуры
* и передает ее менее критичной по
* времени исполнения части,
* которая будет запущена сразу же,
* как только ядро сочтет это возможным.
*/
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* Эти переменные объявлены статическими, чтобы имелась возможность
* доступа к ним (посредством указателей) из "нижней половины".
*/
static int initialised = 0;
static unsigned char scancode;
static struct work_struct task;
unsigned char status;

/*
* Прочитать состояние клавиатуры
*/
status = inb(0x64);
scancode = inb(0x60);

if (initialised == 0) {
INIT_WORK(&task, got_char, &scancode);
initialised = 1;
} else {
PREPARE_WORK(&task, got_char, &scancode);
}

queue_work(my_workqueue, &task);

return IRQ_HANDLED;
}

/*
* Инициализация модуля - регистрация обработчика прерывания
*/
int init_module()
{
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);

/*
* Поскольку стандартный обработчик прерываний от клавиатуры не может
* сосуществовать с таким как наш, то придется запретить его
* (освободить IRQ) прежде, чем что либо сделать.
* Но поскольку мы не знаем где он находится в ядре, то мы лишены
* возможности переустановить его - поэтому
* компьютер придется перезагрузить
* после опробования этого примера.
*/
free_irq(1, NULL);

/*
* Подставить свой обработчик (irq_handler) на IRQ 1.
* SA_SHIRQ означает, что мы допускаем возможность совместного
* обслуживания этого IRQ другими обработчиками.
*/
return request_irq(1, /* Номер IRQ */
irq_handler, /* наш обработчик */
SA_SHIRQ,
"test_keyboard_irq_handler",
(void *)(irq_handler));
}

/*
* Завершение работы
*/
void cleanup_module()
{
/*
* Эта функция добавлена лишь для полноты изложения.
* Она вообще бессмысленна, поскольку я не вижу способа
* восстановить стандартный обработчик прерываний от клавиатуры
* поэтому необходимо выполнить перезагрузку системы.
*/
free_irq(1, NULL);
}

/*
* некоторые функции, относящиеся к work_queue
* доступны только если модуль лицензирован под GPL
*/
MODULE_LICENSE("GPL");

Глава 12. Симметричная многопроцессорность
Один из самых простых и самых дешевых способов увеличения производительности -- это разместить на материнской плате несколько микропроцессоров. Каждый из процессоров может играть свою собственную роль (асимметричная многопроцессорная обработка -- ASMP) или же они все работают параллельно, организуя вычисления таким образом, при котором и операционная система, и приложения могут использовать любой доступный процессор (симметричная многопроцессорная обработка -- SMP). Примером асимметричной многопроцессорной ОС может служить NetWare SFT III, использующая зеркальное отображение серверов, в двухпроцессорном сервере первый процессор занимается предоставлением услуг, а второй - операциями ввода/вывода. Асимметричная многопроцессорноя обработка более эффективна, но она требует точного распределения ролей между процессорами, что практически невозможно на универсальных ОС, подобных Linux. С другой стороны, симметричная многопроцессорная обработка менее эффективна, но относительно проста в реализации (здесь, под словами "относительно проста", вовсе не подразумевается, что это действительно просто).

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

На самом деле, в случае обычных процессов, такой проблемы не существует. Как правило, в каждый конкретный момент времени, процесс может исполняться только на одном CPU. [15] Но ядро может выполнять одновременно разные процессы на разных CPU.

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

Начиная с ядра версии 2.2.x стал возможным одновременный выход нескольких процессоров в привилегированный режим. Это обстоятельство вы должны знать и помнить.

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

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

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

Не суйте голову в пасть тигру! Вероятно это предупреждение излишне, но тем не менее...

Примечания
[1] В ранних версиях ядра он назывался kerneld.

[2] Если вы предполагаете проводить эксперименты с ядром, то чтобы избежать перезаписи существующих файлов модулей вы можете изменить значение переменной EXTRAVERSION в Makefile ядра. В результате все модули ядра будут записываться в отдельный каталог.

[3] Я не компьютерный гений, я простой физик!

[4] Это - далеко не то же самое, что и "встраивание всех модулей в ядро", хотя идея та же.

[5] Делается это в соответствии с принятыми соглашениями. Однако, при разработке драйвера устройства, на период отладки, размещать файл устройства в своем домашнем каталоге -- наверное не такая уж и плохая идея. Единственное -- не забудьте исправить место для размещения файла устройства после того, как отладка будет закончена.

[6] В версиях ядра 2.0 и 2.2 это делалось автоматически, если в качестве номера inode передавалось нулевое значение.

[7] Различие здесь состоит в том, что файловые операции работают с файлом непосредственно, а функции, работающие с inode, предоставляют способ сослаться на файл, например создание ссылок на файл.

[8] Обратите внимание, здесь смысл терминов "чтение" и "запись" также имеют обратный смысл. Так операция чтения передает данные от процесса ядру, а операция записи -- в обратном направлении, от ядра к процессу.

[9] Не совсем верно. Вы не сможете передать функции ioctl, например структуру, но передать указатель на структуру -- безусловно возможно.

[10] Самый простой способ оставить файл открытым -- это дать команду tail -f

[11] Это означает, что процесс продолжает свою работу в привилегированном режиме -- поскольку процесс был приостановлен во время работы системного вызова, который еще не закончил свою работу. Процесс даже не "подозревает" о том, что кто-то еще, кроме него, использовал процессор в промежутке между обращением к системному вызову и возвратом из него.

[12] Именно по этой причине использовался вызов функции wait_event_interruptible. Можно было бы использовать wait_event, но тогда задачу невозможно будет прервать по Ctrl-C во время ожидания.

[13] Tty (от англ. TeleTYpe - телетайп) -- первоначально обозначал комбинацию клавиатура-печатающее устройство предназначенное для взаимодействия с Unix-системой, на сегодняшний день -- это абстракция текстового потока, используемого программами Unix, независимо от того, является ли он физическим терминалом, окном xterm на дисплее, сетевым подключением или чем то иным.

[14] Это стандартное понятие для архитектуры Intel, на которой начинал разрабатываться Linux.

[15] За исключением многопоточных процессов. В этом случае разные потоки одного процесса могут исполняться одновременно на разных процессорах.

Категория: Linux | Добавил: webmaster (2006-12-15)
Просмотров: 350 | Рейтинг: 0.0 |

Всего комментариев: 0
Имя *:
Email *:
Код *:
Форма входа

Сервисы

Поиск по каталогу

Друзья сайта

| Ссылки 1 | Ссылки 2 | Ссылки 3 |
www.webmaster.clan.su Каталог+поисковая система be number one Bakililar.az Top Sites Сервис авто регистрации в
каталогах, статьи про раскрутку сайтов, web дизайн, flash, 
photoshop, хостинг, рассылки; форум, баннерная сеть, каталог 
сайтов, услуги продвижения и рекламы сайтов Скрипт для определения тиц (Яндекс CY: индекс цитирования). Определение pr (Google Pagerank). Проверить тиц pr сайта.
Copyright WebMaster.Clan © 2006 Бесплатный хостинг uCoz