(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Разработка модулей ядра Linux: Часть 7. Анализ выполнения системного вызова

Источник: ibm
Олег Цилюрик

Введение

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

Пример системного вызова

Все обсуждаемые примеры содержатся в архиве int80.tgz (который можно найти в разделе Материалы для скачивания), и, в отличие от всех остальных примеров, они применимы только к архитектуре Intel x86 , так как в них реализуется прямой системный вызов Linux через команду ассемблера int 80h. Первый пример (файл mp.c) демонстрирует пользовательский процесс, последовательно выполняющий системные вызовы, эквивалентные библиотечным: getpid(), write(), mknod(), причём write() выполняется именно на дескриптор 1, то есть printf().

Листинг 1. Обычный пользовательский процесс

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/stat.h> 
#include <linux/kdev_t.h> 
#include <sys/syscall.h> 

int write_call( int fd, const char* str, int len ) { 
   long __res; 
   __asm__ volatile ( "int $0x80": 
      "=a" (__res):"0"(__NR_write),"b"((long)(fd)),"c"((long)(str)),"d"((long)(len)) ); 
   return (int) __res; 
} 

void do_write( void ) { 
   char *str = "эталонная строка для вывода!\n"; 
   int len = strlen( str ) + 1, n; 
   printf( "string for write length = %d\n", len ); 
   n = write_call( 1, str, len ); 
   printf( "write return : %d\n", n ); 
} 

int mknod_call( const char *pathname, mode_t mode, dev_t dev ) { 
   long __res; 
   __asm__ volatile ( "int $0x80": 
      "=a" (__res): 
      "a"(__NR_mknod),"b"((long)(pathname)),"c"((long)(mode)),"d"((long)(dev)) 
   ); 
   return (int) __res; 
}; 

int mknod_call( const char *pathname, mode_t mode, dev_t dev ) { 
   long __res; 
   __asm__ volatile ( "int $0x80": 
      "=a" (__res): 
      "a"(__NR_mknod),"b"((long)(pathname)),"c"((long)(mode)),"d"((long)(dev)) 
   ); 
   return (int) __res; 
}; 

void do_mknod( void ) { 
   char *nam = "ZZZ"; 
   int n = mknod_call( nam, S_IFCHR / S_IRUSR / S_IWUSR, MKDEV( 247, 0 ) ); 
   printf( "mknod return : %d\n", n ); 
} 

int getpid_call( void ) { 
   long __res; 
   __asm__ volatile ( "int $0x80":"=a" (__res):"a"(__NR_getpid) ); 
   return (int) __res; 
}; 

void do_getpid( void ) { 
   int n = getpid_call(); 
   printf( "getpid return : %d\n", n ); 
} 

int main( int argc, char *argv[] ) { 
   do_getpid(); 
   do_write(); 
   do_mknod(); 
   return EXIT_SUCCESS; 
}; 

Пример написан с использованием ассемблерных inline-вставок компилятора GCC, которые будут рассмотрены в отдельной статье. Пример прост и интуитивно понятен: в каждом случае регистры загружаются значениями из переменных C-кода и вызывается прерывание. Ниже приведен результат его запуска:

$ ./mp 
getpid return : 14180 
string for write length = 54 
эталонная строка для вывода! 
write return : 54 
mknod return : -1 

Всё хорошо, за исключением вызова mknod(), но стоит вспомнить, что одноимённая консольная команда требует прав root:

$ sudo ./mp 
getpid return : 14182 
string for write length = 54 
эталонная строка для вывода! 
write return : 54 
mknod return : 0 
$ ls -l ZZZ 
crw------- 1 root root 247, 0 Дек 20 22:00 ZZZ 
$ rm ZZZ 
rm: удалить защищенный от записи знаковый специальный файл `ZZZ'? y 

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

Рассмотрим следующий вопрос: нельзя ли выполнить эти (а значит и другие) системные вызовы из кода модуля ядра, то есть изнутри ядра? Оформим код, фактически совпадающий с приведенным в листинге 1, в форме модуля ядра. Но так как хотелось бы написать два почти идентичных модуля (mdu.c и mdc.c), то код, общий для них, необходимо поместить в общий включаемый файл (syscall.h):

Листинг 2. Код, общий для двух модулей

#include <linux/module.h> 
#include <linux/fs.h> 
#include <linux/sched.h> 

int write_call( int fd, const char* str, int len ) { 
   long __res; 
   __asm__ volatile ( "int $0x80": 
      "=a" (__res):"0"(__NR_write),"b"((long)(fd)),"c"((long)(str)),"d"((long)(len)) ); 
   return (int) __res; 
} 

void do_write( void ) { 
   char *str = "=== эталонная строка для вывода!\n"; 
   int len = strlen( str ) + 1, n; 
   printk( "=== string for write length = %d\n", len ); 
   n = write_call( 1, str, len ); 
   printk( "=== write return : %d\n", n ); 
} 

int mknod_call( const char *pathname, mode_t mode, dev_t dev ) { 
   long __res; 
   __asm__ volatile ( "int $0x80": 
      "=a" (__res): 
      "a"(__NR_mknod),"b"((long)(pathname)),"c"((long)(mode)),"d"((long)(dev)) 
   ); 
   return (int) __res; 
}; 

void do_mknod( void ) { 
   char *nam = "ZZZ"; 
   int n = mknod_call( nam, S_IFCHR / S_IRUGO, MKDEV( 247, 0 ) ); 
   printk( KERN_INFO "=== mknod return : %d\n", n ); 
} 

int getpid_call( void ) { 
   long __res; 
   __asm__ volatile ( "int $0x80":"=a" (__res):"a"(__NR_getpid) ); 
   return (int) __res; 
}; 

void do_getpid( void ) { 
   int n = getpid_call(); 
   printk( "=== getpid return : %d\n", n ); 
} 

В листинге 3 приведен первый модуль mdu.c, который практически полностью повторяет код выполнявшегося выше процесса.

Листинг 3. Код модуля, выполненного по правилам

#include "syscall.h" 

static int __init x80_init( void ) { 
   do_write(); 
   do_mknod(); 
   do_getpid(); 
   return -1; 
} 
 
module_init( x80_init ); 

Результат его запуска показан ниже:

$ sudo insmod mdu.ko 
insmod: error inserting 'mdu.ko': -1 Operation not permitted 
$ dmesg / tail -n30 / grep === 
=== string for write length = 58 
=== write return : -14 
=== mknod return : -14 
=== getpid return : 14217 
$ ps -A / grep 14217 
$ 

В общем, всё совершенно ожидаемо (ошибки выполнения), кроме вызова getpid(), который вызывает некоторые подозрения, но об этом позже. Цель успешно достигнута: было показано главное различие между пользовательским процессом и ядром. Оно состоит в том, что при выполнении системного вызова из любого процесса, код обработчика системного вызова (в ядре!) должен копировать данные параметров вызова из адресного пространства процесса в пространство ядра, а после выполнения копировать данные результатов обратно в адресное пространства процесса. А при попытке вызвать системный обработчик из контекста ядра (модуля), что только что было сделано, адресного пространства процесса не существует, так как нет самого процесса! Однако getpid() успешно выполнился и показал PID какого-то процесса. Он выполнился, так как этот системный вызов не получает параметров и не копирует результатов (он возвращает значение в регистре). А возвращен был PID того процесса, в контексте которого выполнялся системный вызов, т.е. процесса insmod. Но всё-таки системный вызов был выполнен из модуля!

Далее необходимо переписать модуль mdc.c, незначительно изменив пример из листинга 2.

Листинг 3. Код модуля, выполненного в нарушение правил

#include <linux/uaccess.h> 
#include "syscall.h" 

static int __init x80_init( void ) { 
   mm_segment_t fs = get_fs(); 
   set_fs( get_ds() ); 
   do_write(); 
   do_mknod(); 
   do_getpid(); 
   set_fs( fs ); 
   return -1; 
} 

module_init( x80_init ); 

Примечание. Вызовы set_fs(), get_ds() выполняют смену сегмента данных с пользователя на ядро, но они будут рассматриваться в следующих статьях.

Результат запуска модуля mdc.c, приведенный ниже, может сильно удивить.

$ sudo insmod mdc.ko 
=== эталонная строка для вывода! 
insmod: error inserting 'mdc.ko': -1 Operation not permitted 
$ dmesg / tail -n30 / grep === 
=== string for write length = 58 
=== write return : 58 
=== mknod return : 0 
=== getpid return : 14248 
$ ls -l ZZZ 
cr--r--r-- 1 root root 0, 63232 Дек 20 22:04 ZZZ 

Ранее говорилось, что модуль ядра не сможет выполнить printf() и осуществить вывод на графический терминал. Однако видно, что перед инсталляционным сообщением была выведена текстовая строка! На какой управляющий терминал был тогда произведен вывод? Конечно, на терминал запускающей программы insmod, и сделать подобное можно только из функции инициализации модуля. Но главное не это, а то, что в этом примере все системные вызовы успешно выполнились! А значит, выполнится и любой системный вызов, предназначенный для пользовательского пространства.

Заключение

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

Ссылки по теме


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 22.05.2012 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
IBM DOMINO ENTERPRISE CLIENT ACCESS LICENSE AUTHORIZED USER LICENSE + SW SUBSCRIPTION & SUPPORT 12 MONTHS
IBM Domino Enterprise Server Processor Value Unit (PVU) License + SW Subscription & Support 12 Months
IBM Domino Messaging Client Access License Authorized User License + SW Subscription & Support 12 Months
IBM Domino Enterprise Server Processor Value Unit (PVU) Annual SW Subscription & Support Renewal
IBM Domino Messaging Server Processor Value Unit (PVU) License + SW Subscription & Support 12 Months
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Безопасность компьютерных сетей и защита информации
Новости ITShop.ru - ПО, книги, документация, курсы обучения
CASE-технологии
OS Linux для начинающих. Новости + статьи + обзоры + ссылки
СУБД Oracle "с нуля"
Мир OLAP и Business Intelligence: новости, статьи, обзоры
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
Обсуждения в форумах
Как выводить деньги в лучших казино? (6)
Порой игрок казино из рейтинга 2021 https://casino2021.net/ все сделал точно, заявка на вывод...
 
Отличается ли ДрифтКазино от беттинга? (31)
Друзья, давно заметил, что на Дрифте уже несколько месяцев во всю рекламируется и предлагается...
 
Помощь по MS Access (341)
Доброе время суток. Случайно оказался на этом сайте, искал статьи по OLAP. Вижу, что...
 
Актуальное зеркало БК Марафон на сегодня (1)
На основном сайте БК Марафон https://rabochee-zerkalo-marafon.ru/ зеркало доступно прямо в...
 
Рабочее зеркало Марафонбет (1)
Копии зеркал МарафонБет публикуют на официальных страничках в социальных сетях или на сайте...
 
 
 



    
rambler's top100 Rambler's Top100