Wiki

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

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

Пример ниже демонстрирует такую возможность. Модуль создает файл /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"
 
static ssize_t module_output(struct file *file, /* см. include/linux/fs.h   */
                      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] = &#39;\0&#39;;
 
  /* 
   * Вернуть число принятых байт
   */
  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);
}