Доступ до ядра Linux через файлову систему / proc (исходники), Вільне ПЗ, Програмні керівництва, статті

Спочатку файлова система / 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 <linux/module.h>
/ * Задається тип ліцензії завантажуваного модуля * /
MODULE_LICENSE(“GPL”);
/ * Init-функція, що викликається при завантаженні модуля * /
int my_module_init( void )
{
printk(KERN_INFO “my_module_init called. Module is now loaded.
“);
return 0;
}
/ * Cleanup-функція, що викликається при вивантаженні модуля * /
void my_module_cleanup( void )
{
printk(KERN_INFO “my_module_cleanup called. Module is now unloaded.
“);
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 <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
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
“);
} 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.
“);
}
}
return ret;
}
void cleanup_fortune_module( void )
{
remove_proc_entry(“fortune”, &proc_root);
vfree(cookie_pot);
printk(KERN_INFO “fortune: Module unloaded.
“);
}
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!
“);
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
“, &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 широко використовується як засіб збору інформації про стан ядра і для його динамічного конфігурування. Вона незамінна для розробки драйверів та модулів ядра, в ніж нескладно переконатися.


Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*