Создание нового системного вызова в ОС Linux

В статье рассмотрена методика добавления в состав ядра ОС Linux (2.4) нового системного вызова. В качестве примера используется функция, переводящая символы латинского алфавита в верхний регистр.

[uncle Bob (ubob@mail.ru)]

Создание нового системного вызова в ОС Linux

Автор: uncle Bob
Дата: 11.01.2004
Раздел: Низкоуровневое программирование в Linux

В статье рассмотрена методика добавления в состав ядра ОС Linux нового системного вызова.

Общий механизм выполнения системных вызовов рассмотрен здесь.

Задача - добавить в состав ядра версии 2.4.23 новый системный вызов, который будет выполнять следующие действия:

  • принимает от приложения пользователя указатель на строку ASCII-символов с кодами 32 - 127 и длину этой строки в байтах;
  • преобразует символы строки, находящиеся в диапазоне 0x61 - 0x7A (a - z) в верхний регистр и возвращает эту строку обратно;
Для решения этой задачи необходимо добавить запись о новом системном вызове в таблицу системных вызовов ядра sys_call_table. Эта таблица находится в файле /usr/src/linux/arch/i386/kernel/entry.S. Новый вызов добавляем в самый конец таблицы:
ENTRY(sys_call_table)
	.long SYMBOL_NAME(sys_ni_syscall)	/* 0  -  old "setup()" system call*/
	.long SYMBOL_NAME(sys_exit)		/* 1 */
	    .
	    .
	    .
 	.long SYMBOL_NAME(sys_upcase)	/* новый системный вызов, 259 */
Новый системный вызов назовем sys_upcase. Его порядковый номер (для ядра версии 2.4.23) равен 259.

Теперь необходимо добавить запись о новом вызове в файл /usr/src/linux/include/asm-i386/unistd.h:

    #define __NR_upcase		259
и в файл /usr/include/bits/syscall.h:
    #define SYS_upcase __NR_upcase
Теперь осталось написать код, реализующий новый системный вызов. Вот как он выглядит:
asmlinkage int sys_upcase(char *src, char *dst, int lenght)
{
    int i = 0;
    char *tmp_buff;

    tmp_buff = (char *)kmalloc(lenght, GFP_KERNEL);
    memset(tmp_buff, 0, lenght);

    copy_from_user(tmp_buff, src, lenght);

    printk(KERN_INFO "%s\n", tmp_buff);
    printk(KERN_INFO "%d\n", lenght);

    for(; i < lenght; i++)
	if((tmp_buff[i] >= 0x61) && (tmp_buff[i] <= 0x7A)) tmp_buff[i] -= 0x20;

    printk(KERN_INFO "%s (after)\n", tmp_buff);

    copy_to_user(dst, tmp_buff, lenght);
    kfree(tmp_buff);

    return lenght;
}
Системный вызов принимает три параметра - указатель на строку, которую необходимо преобразовать, длину этой строки и указатель на область памяти, куда необходимо поместить результат. Функция copy_from_user() копирует исходную строку из адресного пространства пользователя в адресное пространство ядра, а затем в цикле производится смена регистра символов, находящихся в диапазоне 0x61 - 0x7A. Результат (преобразованная строка) копируется из адресного пространства ядра в пространство пользователя при помощи функции copy_to_user().

Эту функцию поместим в файл /usr/src/linux/fs/open.c, хотя место размещения особой роли не играет.

После внесения всех изменений в ядро необходимо перекомпилировать.

Для того, чтобы процесс пользователя мог обращаться к новому системному вызову, создадим в каталоге /usr/include заголовочный файл upcase.h следующего содержания:

/* Заголовочный файл upcase.h */
#ifndef _UPCASE_H
#define _UPCASE_H	1

#include 

static inline _syscall3(int,upcase,char *,src,char *,dst,int,lenght)

#endif

Макрос _syscall3 определен в файле /usr/src/linux/include/asm-i386/unistd.h:

#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \
type name(type1 arg1,type2 arg2,type3 arg3) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
	: "=a" (__res) \
	: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \
		  "d" ((long)(arg3))); \
__syscall_return(type,__res); \
}
Таким образом, системный вызов sys_upcase будет выполняться стандартным для всех системных вызовов способом: сначала в регистры процессора загружаются параметры вызова, а затем следует вызов прерывания int 0x80. __NR_##name будет преобразована в номер системного вызова.

Рассмотрим пример обращения к новому системному вызову из приложения пользователя:

#include #include

int main()
{
    char *src = "ab12cd34ef";	// эту строку будем преобразовывать
    char *dst;			// сюда будет помещен результат
    int lenght = 0, rez = 0;

    lenght = strlen(src);	// определяем длину строки

    dst = (char *)malloc(lenght);
    memset(dst, 0, lenght);

// Выведем для контроля информацию:
    printf("Lenght - %d\n", lenght);
    printf("Source - %s\n", src);
    printf("Destin - %s\n", dst);

// Выполняем обращение к новому системному вызову sys_upcase для преобразования
// символов исходной строки в верхний регистр:
    rez = upcase(src, dst, lenght);

//Отобразим результаты:
    printf("Source - %s\n", src);
    printf("Destin - %s\n", dst);
    printf("rez - %d\n", rez);

    return 0;
}
Для получения исполняемого модуля создадим Makefile:
INCDIR  = /usr/src/linux/include
.PHONY = clean

new_call: new_call.o
	gcc -I$(INCDIR) $^ -o $@

%.o: %.c
	gcc -I$(INCDIR) -c $^

clean:
	rm -f *.o
	rm -f ./new_call

Статья взята с сайта OpenNet.

[ опубликовано 31/05/2004 ]

uncle Bob (ubob@mail.ru) - Создание нового системного вызова в ОС Linux   Версия для печати