Навигация
Главная »  Linux 

Доступ к ядру Linux через файловую систему /proc (исходники)


Источник: IBM developerWorks Россия
М. Тим Джонс
Изначально файловая система /proc разрабатывалась как средство предоставления информации о выполняющихся в системе процессах. Но из-за ее удобства многие подсистемы ядра стали использовать эту файловую систему как средство предоставления информации и динамического конфигурирования.

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

В короткой статье нельзя описать файловую систему /proc во всех деталях, но вполне возможно продемонстрировать несколько вариантов ее использования, дающих представление о ее возможностях. В листинге 1 показан интерактивный обзор некоторых элементов /proc. Мы видим корневой каталог файловой системы /proc. Обратите внимание на файлы с номерными именами в левой части листинга. Это - каталоги, содержащие информацию о выполняющихся в системе процессах. Process-id равный 1 присвоен процессу init, который в системе GNU/Linux запускается первым. Если выполнить команду ls для такого каталога, будет отображен список находящихся в нем файлов. В каждом файле содержатся те или иные сведения о процессе. Например, для того, чтобы посмотреть сведения о параметрах командной строки, с которыми был запущен процесс init, достаточно просмотреть содержимое файла cmdline с помощью команды cat.

В /proc есть и другие интересные файлы. Например, cpuinfo, содержащий сведения о типе и производительности центрального процессора, pci, из которого можно получить информацию об устройствах на шине PCI и modules, в котором находится список загруженных в ядро модулей.

Листинг 1. Интерактивный обзор файловой системы /proc
  [root@plato]# ls /proc  1     2040  2347  2874  474          fb           mdstat      sys  104   2061  2356  2930  9            filesystems  meminfo     sysrq-trigger  113   2073  2375  2933  acpi         fs           misc        sysvipc  1375  21    2409  2934  buddyinfo    ide          modules     tty  1395  2189  2445  2935  bus          interrupts   mounts      uptime  1706  2201  2514  2938  cmdline      iomem        mtrr        version  179   2211  2515  2947  cpuinfo      ioports      net         vmstat  180   2223  2607  3     crypto       irq          partitions  181   2278  2608  3004  devices      kallsyms     pci  182   2291  2609  3008  diskstats    kcore        self  2     2301  263   3056  dma          kmsg         slabinfo  2015  2311  2805  394   driver       loadavg      stat  2019  2337  2821  4     execdomains  locks        swaps  [root@plato 1]# ls /proc/1  auxv     cwd      exe  loginuid  mem     oom_adj    root  statm   task  cmdline  environ  fd   maps      mounts  oom_score  stat  status  wchan  [root@plato]# cat /proc/1/cmdline  init [5]  [root@plato]#  

В листинге 2 показаны чтение и запись параметров ядра в виртуальный файл, находящийся в /proc. Приведенный пример кода отображает значение параметра, управляющего режимом "IP forwarding" стека TCP/IP ядра и затем включает его.

Листинг 2. Чтение и запись /proc (конфигурирование ядра)
  [root@plato]# cat /proc/sys/net/ipv4/ip_forward  0  [root@plato]# echo "1" > /proc/sys/net/ipv4/ip_forward  [root@plato]# cat /proc/sys/net/ipv4/ip_forward  1  [root@plato]#  

Другим способом изменения параметров конфигурации ядра является использование команды sysctl.

На самом деле, /proc - не единственная виртуальная файловая система в ОС GNU/Linux. Аналогичная файловая система sysfs имеет сходные функциональные возможности и немного более удачную структуру (при ее разработке был учтен опыт /proc). Тем не менее /proc является де-факто стандартом и, несмотря на то, что sysfs имеет некоторые преимущества, будет и впредь оставаться таковым. Можно упомянуть еще одну виртуальную файловую систему - debugfs , которая (как следует из ее названия), представляет собой скорее отладочный интерфейс. Ее преимуществом является простота, с которой происходит экспорт значения из ядра в пользовательское пространство (фактически, это требует единственного системного вызова).

Знакомство с модулями ядра

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

Если вам приходилось вручную собирать ядро Linux, вы, вероятно, обращали внимание на то, что многие драйверы устройств и другие компоненты ядра компилируются в виде модулей. Если драйвер скомпилирован как часть ядра, его код и статические данные занимают память даже тогда, когда он не используется. Но если скомпилировать драйвер как модуль, он будет занимать память только если он действительно необходим и загружен в ядро. Удивительно, но заметной потери производительности при использовании LKM не происходит. Это делает загружаемые модули незаменимым средством при сборке ядра с низкими требованиями к объему памяти и возможностью использования не только штатного набора оборудования, но и подключаемых устройств.

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

Код в листинге 3 начинается с обязательного заголовка (описывающего интерфейс модуля, типы и макросы). Затем, с помощью макроса MODULE_LICENSE, указывается тип лицензии, под которой распространяется модуль. В данном примере мы используем лицензию GPL , чтобы не получать предупреждений о "заражении" ядра проприетарным кодом.

Далее в листинге 3 следует определение функций модуля init и cleanup. Функция my_module_init вызывается при загрузке модуля и поэтому может использоваться для инициализации. Другая функция, my_module_cleanup, вызывается в момент выгрузки модуля. В ней происходит освобождение памяти и ликвидация следов пребывания модуля в ядре. Обратите внимание на то, что мы используем функцию printk: это аналог printf для ядра. С помощью макроса KERN_INFO можно записать в кольцевой буфер ядра произвольную строку (аналогично функции syslog).

Функции, вызываемые при загрузке и выгрузке модуля, задаются в заключительных строках листинга с помощью макросов module_init and module_exit. Такой способ определения вспомогательных функций init and cleanup позволяет называть их как угодно. Достаточно лишь сообщить их имена ядру.

Листинг 3. Простой, но работоспособный загружаемый модуль (simple-lkm.c)
  #include     /* Задается тип лицензии загружаемого модуля */  MODULE_LICENSE("GPL");    /* Init-функция, вызываемая при загрузке модуля */  int my_module_init( void )  {  printk(KERN_INFO "my_module_init called.  Module is now loaded.\n");    return 0;  }    /* Cleanup-функция, вызываемая при выгрузке модуля */  void my_module_cleanup( void )  {  printk(KERN_INFO "my_module_cleanup called.  Module is now unloaded.\n");    return;  }    /*  Ядру сообщаются названия функций, вызываемых при загрузке и выгрузке модуля */  module_init( my_module_init );  module_exit( my_module_cleanup );  

В листинге 3 приведен код простого, но работоспособного загружаемого модуля. Теперь мы соберем его и протестируем на ядре версии 2.6. Начиная с этой версии, в ядре появилась поддержка нового метода сборки модулей ядра, который, на мой взгляд, проще, чем методы, использовавшиеся ранее. Чтобы им воспользоваться, необходимо, помимо файла simple-lkm.c, создать make-файл, содержащий единственную строку, приведенную ниже:

obj-m += simple-lkm.o  

Для сборки модуля, выполните команду make, как показано в листинге 4.

Листинг 4. Сборка модуля
  [root@plato]# make -C /usr/src/linux-`uname -r` SUBDIRS=$PWD modules  make: Entering directory `/usr/src/linux-2.6.11'  CC [M]  /root/projects/misc/module2.6/simple/simple-lkm.o  Building modules, stage 2.  MODPOST  CC      /root/projects/misc/module2.6/simple/simple-lkm.mod.o  LD [M]  /root/projects/misc/module2.6/simple/simple-lkm.ko  make: Leaving directory `/usr/src/linux-2.6.11'  [root@plato]#  

После сборки должен появиться файл simple-lkm.ko. Новый способ именования позволяет легко отличать модули ядра от обыкновенных объектов. Теперь, когда модуль готов, можно его загрузить, затем выгрузить и посмотреть на выведенные при этом сообщения. Чтобы загрузить модуль, выполните команду insmod; для выгрузки выполните команду rmmod. Команда lsmod выводит список модулей, загруженных в данный момент (см. листинг 5).

Листинг 5. Загрузка, проверка загрузки и выгрузка модуля
  [root@plato]# insmod simple-lkm.ko  [root@plato]# lsmod  Module                  Size  Used by  simple_lkm              1536  0  autofs4                26244  0  video                  13956  0  button                  5264  0  battery                 7684  0  ac                      3716  0  yenta_socket           18952  3  rsrc_nonstatic          9472  1 yenta_socket  uhci_hcd               32144  0  i2c_piix4               7824  0  dm_mod                 56468  3  [root@plato]# rmmod simple-lkm  [root@plato]#  

Следует учесть, что сообщения, генерируемые кодом ядра, выводятся в кольцевой буфер ядра, а не в stdout, поскольку последний привязан к конкретному процессу. Для просмотра сообщений в кольцевом буфере ядра, вы можете воспользоваться командой dmesg (или просмотреть сообщения в самой файловой системе /proc с помощью команды cat /proc/kmsg). В листинге 6 приведены несколько последних сообщений, выведенных по команде dmesg.

Листинг 6. Сообщения, сгенерированные тестовым модулем
  [root@plato]# dmesg / tail -5  cs: IO port probe 0xa00-0xaff: clean.  eth0: Link is down  eth0: Link is up, running at 100Mbit half-duplex  my_module_init called.  Module is now loaded.  my_module_cleanup called.  Module is now unloaded.  [root@plato]#  

Сообщения, выведенные тестовым модулем, видны в общем потоке сообщений ядра. А теперь предлагаю перейти от нашего простого примера к интерфейсам ядра, позволяющим разрабатывать загружаемые модули.

Интеграция с файловой системой /proc

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

Создание и удаление виртуального файла в /proc

Для того, чтобы создать виртуальный файл в файловой системе /proc, используется функция create_proc_entry. Эта функция принимает в качестве параметров имя создаваемого файла, режим доступа к нему и один из подкаталогов /proc для его размещения. Функция create_proc_entry возвращает указатель на структуру proc_dir_entry (или NULL в случае возникновения ошибки). Полученный указатель можно использовать для настройки остальных параметров виртуального файла, таких, как функция, вызываемая при чтении из файла. Прототип функции create_proc_entry и фрагмент структуры proc_dir_entry показаны в листинге 7.

Листинг 7. Элементы интерфейсов управления виртуальными файлами /proc
  struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode,  struct proc_dir_entry *parent );    struct proc_dir_entry {  const char *name;			// имя виртуального файла  mode_t mode;				// режим доступа  uid_t uid;				// уникальный номер пользователя - // владельца файла  uid_t uid;				// уникальный номер группы, которой // принадлежит файл  struct inode_operations *proc_iops;	// функции-обработчики операций с inode  struct inode_operations *proc_iops;	// функции-обработчики операций с файлом  struct proc_dir_entry *parent;		// Родительский каталог  ...  read_proc_t *read_proc;			// функция чтения из /proc  write_proc_t *write_proc;		// функция записи в /proc  void *data;				// Указатель на локальные данные  atomic_t count;				// счетчик ссылок на файл  ...  };    void remove_proc_entry( const char *name, struct proc_dir_entry *parent );  

Чуть позже вы узнаете, как использовать команды read_proc and write_proc для задания функций чтения и записи виртуального файла.

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

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

Таблица 1. Список предопределенных переменных proc_dir_entry
Переменная proc_dir_entry

Каталог

proc_root_fs  /proc 
proc_net  /proc/net 
proc_bus  /proc/bus 
proc_root_driver  /proc/driver 

Callback-функция записи

Вы можете записывать данные в виртуальный файл (из пользовательского пространства в ядро) с помощью функции write_proc. Эта функция имеет прототип следующего вида:

int mod_write( struct file *filp, const char __user *buff,  unsigned long len, void *data );  

Параметр filp представляет собой структуру, соответствующую открытому файлу устройства (нам он не понадобится). Параметр buff соответствует строке, передаваемой в модуль. Поскольку буфер, в котором находится строка находится в пользовательском пространстве, к нему нельзя будет получить непосредственный доступ из модуля. Параметр len содержит количество подлежащих записи байт, находящихся в buff. Параметр data содержит указатель на локальные данные (см. листинг 7). В нашем тестовом модуле сallback-функция записи служит для обработки входящих данных.

В Linux предусмотрен набор API для перемещения данных между пользовательским пространством и пространством ядра. Для операций с данными, находящимися в пользовательском пространстве, в функции write_proc из нашего примера, используется семейство функций copy_from_user.

Callback-функция чтения

Вы можете считать данные из виртуального файла (из ядра в пользовательское пространство) с помощью функции read_proc. Ее прототип выглядит так:

int mod_read( char *page, char **start, off_t off,  int count, int *eof, void *data );  

Параметр page содержит указатель на буфер, в который будут записаны данные, полученные из ядра, при этом параметр count определяет максимальное число символов, которое может быть записано в данный буфер. Если планируется получить более одной страницы данных (обычно, 4KB), следует использовать параметры start и off. После того, как все данные получены, установите признак eof (конец файла). По аналогии с кодом функции write, параметр data соответствует локальным данным. Буфер page, используемый в данной функции располагается в пространстве ядра. Следовательно, для записи в него не требуется вызов функции copy_to_user.

Другие полезные функции

Кроме обыкновенных файлов, в файловой системе /proc можно создавать каталоги, используя функцию proc_mkdir и символьные ссылки (symlinks), используя proc_symlink. Файлы /proc, для которых определена только операция чтения (функция read), можно создать единственным вызовом функции create_proc_read_entry, создающей файл и задающей для него функцию read_proc. Прототипы вышеупомянутых функций показаны в листинге 8.

Листинг 8. Прочие полезные функции для работы с /proc
 /* Создание каталога в файловой системе proc */  struct proc_dir_entry *proc_mkdir( const char *name,  struct proc_dir_entry *parent );    /* Создание символической ссылки в файловой системе proc */  struct proc_dir_entry *proc_symlink( const char *name,  struct proc_dir_entry *parent,  const char *dest );    /* Создание proc_dir_entry с read_proc_t за один вызов */  struct proc_dir_entry *create_proc_read_entry( const char *name,  mode_t mode,  struct proc_dir_entry *base,  read_proc_t *read_proc,  void *data );    /* Копирование буфера из пространства ядра в пользовательское пространство */  unsigned long copy_to_user( void __user *to,  const void *from,  unsigned long n );    /* Копирование буфера из пользовательского пространства в пространство ядра */  unsigned long copy_from_user( void *to,  const void __user *from,  unsigned long n );    /* Выделение 'виртуально' непрерывного блока памяти */  void *vmalloc( unsigned long size );    /* Освобождение блока памяти, выделенного функцией vmalloc */  void vfree( void *addr );    /* Экспорт символа в ядро (после этого ядро сможет его видеть) */  EXPORT_SYMBOL( symbol );    /* Экспорт в ядро всех символов, объявленных в файле  (должен предшествовать подключению файла module.h) */  EXPORT_SYMTAB 

Выдача 'фортунок' с помощью файловой системы /proc

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

Исходный код данного модуля приведен в листинге 9. Init-функция (init_fortune_module) выделяет блок памяти для хранения 'фортунок' вызовом vmalloc и затем заполняет его нулями с помощью memset. После того, как cookie_pot создан и обнулен, в каталоге /proc создается виртуальный файл (типproc_dir_entry) с именем fortune . После того, как файл (proc_entry) успешно создан, происходит инициализация локальных переменных и структуры proc_entry. В соответствующие поля этой структуры записываются указатели на функции модуля read и write (см. листинги 9 и 10, а также информация о владельце модуля. Функция cleanup удаляет файл из файловой системы /proc и освобождает память, занимаемую cookie_pot.

Хранилище 'фортунок' cookie_pot занимает страницу памяти (4KB) и обслуживается двумя индексами. Первый из них, cookie_index, определяет адрес, по которому будет записана следующая 'фортунка'. торой индекс - переменная next_fortune, содержит адрес 'фортунки', которая будет выдана по следующему запросу. После того как выдана последняя 'фортунка', переменной next_fortune присваивается адрес первого элемента и выдача начинается сначала.

Листинг 9. Функции init/cleanup и переменные модуля.
  #include   #include   #include   #include   #include   #include     MODULE_LICENSE("GPL");  MODULE_DESCRIPTION("Fortune Cookie Kernel Module");  MODULE_AUTHOR("M. Tim Jones");    #define MAX_COOKIE_LENGTH       PAGE_SIZE  static struct proc_dir_entry *proc_entry;    static char *cookie_pot;  // Хранилище 'фортунок'  static int cookie_index;  // Индекс первого свободного для записи элемента хранилища  static int cookie_index;  // Индекс элемента хранилища, содержащего // следующую 'фортунку' для вывода по запросу      int init_fortune_module( void )  {  int ret = 0;    cookie_pot = (char *)vmalloc( MAX_COOKIE_LENGTH );    if (!cookie_pot) {  ret = -ENOMEM;  } else {    memset( cookie_pot, 0, MAX_COOKIE_LENGTH );    proc_entry = create_proc_entry( "fortune", 0644, NULL );    if (proc_entry == NULL) {    ret = -ENOMEM;  vfree(cookie_pot);  printk(KERN_INFO "fortune: Couldn't create proc entry\n");    } else {    cookie_index = 0;  next_fortune = 0;    proc_entry->read_proc = fortune_read;  proc_entry->write_proc = fortune_write;  proc_entry->owner = THIS_MODULE;  printk(KERN_INFO "fortune: Module loaded.\n");    }    }    return ret;  }      void cleanup_fortune_module( void )  {  remove_proc_entry("fortune", &proc_root);  vfree(cookie_pot);  printk(KERN_INFO "fortune: Module unloaded.\n");  }      module_init( init_fortune_module );  module_exit( cleanup_fortune_module );  

Записать 'фортунку' в хранилище очень просто (см. листинг 10). Зная длину записываемой 'фортунки', можно определить, достаточно ли места для ее размещения. Если места недостаточно, модуль возвратит пользовательскому процессу код -ENOSPC. В противном случае строка копируется в cookie_pot с помощью функции copy_from_user. После этого происходит увеличение значения переменной cookie_index (на величину, зависящую от длины полученной строки), в конец строки дописывается NULL. Алгоритм завершает свою работу тем, что возвращает пользовательскому процессу количество символов фактически записанных в cookie_pot.

Листинг 10. Функция записи 'фортунки'
  ssize_t fortune_write( struct file *filp, const char __user *buff,  unsigned long len, void *data )  {  int space_available = (MAX_COOKIE_LENGTH-cookie_index)+1;    if (len > space_available) {    printk(KERN_INFO "fortune: cookie pot is full!\n");  return -ENOSPC;    }    if (copy_from_user( &cookie_pot[cookie_index], buff, len )) {  return -EFAULT;  }    cookie_index += len;  cookie_pot[cookie_index-1] = 0;    return len;  }  

Чтение 'фортунки' нисколько не сложнее ее записи. Поскольку буфер, в который нужно произвести запись 'фортунки' (page), уже находится в пользовательском пространстве, для вывода фортунки можно использовать непосредственно функцию sprintf. Если значение индекса next_fortune превышает значение cookie_index (индекс следующего свободного для записи элемента), переменной next_fortune присваивается 0, то есть, индекс первого элемента. После того, как фортунка записана в буфер пользовательского процесса, я увеличиваю индекс next_fortune на ее длину. Теперь этот индекс содержит адрес 'фортунки', которая будет выдана следующей . Длина 'фортунки' также передается пользовательскому процессу в качестве возвращаемого значения.

Листинг 11. Функция чтения 'фортунки'
  int fortune_read( char *page, char **start, off_t off,  int count, int *eof, void *data )  {  int len;    if (off > 0) {  *eof = 1;  return 0;  }    /* Перевод индекса на первый элемент */  if (next_fortune >= cookie_index) next_fortune = 0;    len = sprintf(page, "%s\n", &cookie_pot[next_fortune]);    next_fortune += len;    return len;  }  

Это простой пример показывает что обмен данными между ядром и пользовательским процессом является тривиальной задачей. А сейчас предлагаю посмотреть на модуль выдачи 'фортунок' в действии (листинг 12).

Листинг 12. Загружаемый модуль выдачи 'фортунок' в действии
  [root@plato]# insmod fortune.ko  [root@plato]# echo "Success is an individual proposition.  Thomas Watson" > /proc/fortune  [root@plato]# echo "If a man does his best, what else is there? Gen. Patton" > /proc/fortune  [root@plato]# echo "Cats: All your base are belong to us.  Zero Wing" > /proc/fortune  [root@plato]# cat /proc/fortune  Success is an individual proposition.  Thomas Watson  [root@plato]# cat /proc/fortune  If a man does his best, what else is there?  Gen. Patton  [root@plato]#  

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



 

 Размещение нескольких сайтов с поддержкой SSL на одной сетевой карте с помощью IP-алиасинг (документация).
 Различия между UNIX и Linux.
 "Железный" бизнес Oracle..
 Oracle представила "облачную" систему Exalogic Elastic Cloud.
 Oracle оптимизирует системы Sun Blade для облачных и виртуализованных сред.


Главная »  Linux 

© 2017 Team.Furia.Ru.
Частичное копирование материалов разрешено.