Linux - статьи

         

Блокировка процессов


Что вы делаете, когда кто-то просит вас о чем-то, а вы не можете сделать это немедленно? Пожалуй единственное, что вы можете ответить: "Пожалуйста, не сейчас, я пока занят.". А что должен делать модуль ядра? У него есть другая возможность. Он можете приостановить работу процесса до тех пор, пока не сможет обслужить его. В конечном итоге, ядро постоянно то приостанавливает, то вновь возобновляет работу процессов. Именно так обеспечивается возможность одновременного исполнения нескольких процессов на единственном процессоре.

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

Когда процесс закрывает файл, это приводит к вызову module_close. Она запускает все процессы, которые "сидят" в очереди WaitQ (к сожалению нет механизма, который позволил бы "разбудить" только один процесс). Затем управление возвращается процессу, который только что закрыл файл и он продолжает свою работу. После того, как данный процесс исчерпает свой квант времени, планировщик передаст управление другому процессу. Таким образом, один из процессов, ожидавших своей очереди доступа к файлу, в конечном итоге получит управление и продолжит исполнение с точки, следующей за вызовом wait_event_interruptible. [11] Он установит глобальную переменную, извещающую остальные процессы о том, что файл открыт и займется обработкой открытого файла. Когда другие процессы получат свой квант времени, они обнаружат, что файл все еще открыт и опять приостановят свою работу.


Чтобы как- то оживить повествование замечу, что module_close не обладает монопольным правом на возобновление работы ожидающих процессов. Сигнал Ctrl-C (SIGINT) также может "разбудить" процесс. [12] В этом случае процессу немедленно возвращается -EINTR. Таким образом пользователи могут, например, прервать процесс прежде, чем он получит доступ к файлу.
Тут есть еще один момент, о котором хотелось бы упомянуть. Некоторые процессы не желают быть заблокированными, такие процессы должны либо получить в свое распоряжение открытый файл немедленно, либо извещение о том, что их запрос не может быть удовлетворен в настоящий момент. Такие процессы используют флаг O_NONBLOCK при открытии файла. Если ядро не в состоянии немедленно удовлетворить запрос, оно отвечает кодом ошибки -EAGAIN.
Пример 8-1. sleep.c
/* * sleep.c - Создает файл в /proc, доступ к * которому может получить только один процесс, * все остальные будут приостановлены. */
#include <linux/kernel.h> /* Все-таки мы работаем с ядром! */ #include <linux/module.h> /* Необходимо для любого модуля */ #include <linux/proc_fs.h> /* Необходимо для работы с /proc */ #include <linux/sched.h> /* Взаимодействие с планировщиком */ #include <asm/uaccess.h> /* определение функций get_user и put_user */
/* * Место хранения последнего принятого сообщения, * которое будет выводиться в файл, чтобы показать, что * модуль действительно может получать ввод от пользователя */ #define MESSAGE_LENGTH 80 static char Message[MESSAGE_LENGTH];
static struct proc_dir_entry *Our_Proc_File; #define PROC_ENTRY_FILENAME "sleep"
/* см. include/linux/fs.h */ static ssize_t module_output(struct file *file, /* буфер с данными (в пространстве пользователя) */ char *buf, /* размер буфера */ size_t len, loff_t * offset) { static int finished = 0; int i; char message[MESSAGE_LENGTH + 30];
/* * Для индикации признака конца файла возвращается 0. * В противном случае процесс будет продолжать читать из файла * угодив в бесконечный цикл. */ if (finished) { finished = 0; return 0; }


/* * Для передачи данных из пространства ядра в пространство пользователя * следует использовать put_user. * В обратном направлении -- get_user. */ 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; /* Вернуть количество "прочитанных" байт */ }
/* * Эта функция принимает введенное пользователем сообщение */ static ssize_t module_input(struct file *file, /* Собственно файл */ const char *buf, /* Буфер с сообщением */ size_t length, /* размер буфера */ loff_t * offset) { /* смещение в файле - игнорируется */ int i;
/* * Переместить данные, полученные от пользователя в буфер, * который позднее будет выведен йункцией module_output. */ for (i = 0; i < MESSAGE_LENGTH - 1 && i < length; i++) get_user(Message[i], buf + i);
/* Обычная строка, завершающаяся символом \0 */ Message[i] = '\0';
/* * Вернуть число принятых байт */ return i; }
/* * 1 -- если файл открыт */ int Already_Open = 0;
/* * Очередь ожидания */ DECLARE_WAIT_QUEUE_HEAD(WaitQ); /* * Вызывается при открытии файла в /proc */ static int module_open(struct inode *inode, struct file *file) { /* * Если установлен флаг O_NONBLOCK, * то процесс не должен приостанавливаться * В этом случае, если файл уже открыт, * необходимо вернуть код ошибки * -EAGAIN, что означает "попробуйте в другой раз" */ if ((file->f_flags & O_NONBLOCK) && Already_Open) return -EAGAIN;
/* * Нарастить счетчик обращений, * чтобы невозможно было выгрузить модуль */ try_module_get(THIS_MODULE);
/* * Если файл уже открыт -- приостановить процесс */
while (Already_Open) { int i, is_sig = 0;
/* * Эта функция приостановит процесс и поместит его в очередь ожидания. * Исполнение процесса будет продолжено с точки, следующей за вызовом * этой функции, когда кто нибудь сделает вызов * wake_up(&WaitQ) (это возможно только внутри module_close, когда * файл будет закрыт) или когда процессу поступит сигнал Ctrl-C */ wait_event_interruptible(WaitQ, !Already_Open);


for (i = 0; i < _NSIG_WORDS && !is_sig; i++) is_sig = current->pending.signal.sig[i] & ~current-> blocked.sig[i];
if (is_sig) { /* * Не забыть вызвать здесь module_put(THIS_MODULE), * поскольку процесс был прерван * и никогда не вызовет функцию close. * Если не уменьшить счетчик обращений, то он навсегда останется * больше нуля, в результате модуль можно будет * уничтожить только при перезагрузке системы */ module_put(THIS_MODULE); return -EINTR; } }
/* * В этой точке переменная Already_Open должна быть равна нулю */
/* * Открыть файл */ Already_Open = 1; return 0; }
/* * Вызывается при закрытии файла */ int module_close(struct inode *inode, struct file *file) { /* * Записать ноль в Already_Open, тогда один * из процессов из WaitQ * сможет записать туда единицу и открыть файл. * Все остальные процессы, ожидающие доступа * к файлу опять будут приостановлены */ Already_Open = 0;
/* * Возобновить работу процессов из WaitQ. */ wake_up(&WaitQ);
module_put(THIS_MODULE);
return 0; }
/* * Эта функция принимает решение о праве на выполнение операций с файлом * 0 -- разрешено, ненулеое значение -- запрещено. * * Операции с файлом могут быть: * 0 - Исполнениеe (не имеет смысла в нашей ситуации) * 2 - Запись (передача от пользователя к модулю ядра) * 4 - Чтение (передача от модуля ядра к пользователю) * * Эта функция проверяет права доступа к файлу * Права, выводимые командой ls -l * могут быть проигнорированы здесь. */ static int module_permission(struct inode *inode, int op, struct nameidata *nd) { /* * Позволим любому читать файл, но * писать -- только root-у (uid 0) */ if (op == 4 (op == 2 && current->euid == 0)) return 0;
/* * Если что-то иное -- запретить доступ */ return -EACCES; }
/* * Указатели на функции-обработчики для нашего файла. */ static struct file_operations File_Ops_4_Our_Proc_File = { .read = module_output, /* чтение из файла */ .write = module_input, /* запись в файл */ .open = module_open, /* открытие файла */ .release = module_close, /* закрытие файла */ };


/* * Операции над индексной записью нашего файла. Необходима * для того, чтобы указать местоположение структуры * file_operations нашего файла, а так же, чтобы задать * функцию определения прав доступа к файлу. Здесь можно указать адреса * других функций-обработчиков, но нас они не интересуют. */ static struct inode_operations Inode_Ops_4_Our_Proc_File = { .permission = module_permission, /* check for permissions */ };
/* * Начальная и конечная функции модуля */
/* * Инициализация модуля - регистрация файла в /proc */
int init_module() { int rv = 0; Our_Proc_File = create_proc_entry(PROC_ENTRY_FILENAME, 0644, NULL); Our_Proc_File->owner = THIS_MODULE; Our_Proc_File->proc_iops = &Inode_Ops_4_Our_Proc_File; Our_Proc_File->proc_fops = &File_Ops_4_Our_Proc_File; Our_Proc_File->mode = S_IFREG | S_IRUGO | S_IWUSR; 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/test\n"); }
return rv; }
/* * Завершение работы модуля - дерегистрация файла в /proc. * Чревато последствиями * если в WaitQ остаются процессы, ожидающие своей очереди, * поскольку точка их исполнения * практически находится в функции open, которая * будет выгружена при удалении модуля. * Позднее, в 9 главе, я опишу как воспрепятствовать * удалению модуля в таких случаях */ void cleanup_module() { remove_proc_entry(PROC_ENTRY_FILENAME, &proc_root); }

Содержание раздела