Главная страница » Что такое процесс в linux

Что такое процесс в linux

  • автор:

Processes¶

A process is an operating system abstraction that groups together multiple resources:

  • An address space
  • One or more threads
  • Opened files
  • Sockets
  • Semaphores
  • Shared memory regions
  • Timers
  • Signal handlers
  • Many other resources and status information

All this information is grouped in the Process Control Group (PCB). In Linux this is struct task_struct .

Overview of process resources¶

A summary of the resources a process has can be obtain from the /proc/<pid> directory, where <pid> is the process id for the process we want to look at.

struct task_struct ¶

Lets take a close look at struct task_struct . For that we could just look at the source code, but here we will use a tool called pahole (part of the dwarves install package) in order to get some insights about this structure:

As you can see it is a pretty large data structure: almost 8KB in size and 155 fields.

Inspecting task_struct¶

The following screencast is going to demonstrate how we can inspect the process control block ( struct task_struct ) by connecting the debugger to the running virtual machine. We are going to use a helper gdb command lx-ps to list the processes and the address of the task_struct for each process.

Quiz: Inspect a task to determine opened files¶

Use the debugger to inspect the process named syslogd.

  • What command should we use to list the opened file descriptors?
  • How many file descriptors are opened?
  • What command should we use the determine the file name for opened file descriptor 3?
  • What is the filename for file descriptor 3?

Threads¶

A thread is the basic unit that the kernel process scheduler uses to allow applications to run the CPU. A thread has the following characteristics:

  • Each thread has its own stack and together with the register values it determines the thread execution state
  • A thread runs in the context of a process and all threads in the same process share the resources
  • The kernel schedules threads not processes and user-level threads (e.g. fibers, coroutines, etc.) are not visible at the kernel level

The typical thread implementation is one where the threads is implemented as a separate data structure which is then linked to the process data structure. For example, the Windows kernel uses such an implementation:

../_images/ditaa-4b5c1874d3924d9716f26d4893a3e4f313bf1c43.png

Linux uses a different implementation for threads. The basic unit is called a task (hence the struct task_struct ) and it is used for both tasks and processes. Instead of embedding resources in the task structure it has pointers to these resources.

Thus, if two threads are the same process will point to the same resource structure instance. If two threads are in different processes they will point to different resource structure instances.

../_images/ditaa-fd771038e88b95def30ae9bd4df0b7bd6b7b3503.png

The clone system call¶

In Linux a new thread or process is create with the clone() system call. Both the fork() system call and the pthread_create() function uses the clone() implementation.

It allows the caller to decide what resources should be shared with the parent and which should be copied or isolated:

  • CLONE_FILES — shares the file descriptor table with the parent
  • CLONE_VM — shares the address space with the parent
  • CLONE_FS — shares the filesystem information (root directory, current directory) with the parent
  • CLONE_NEWNS — does not share the mount namespace with the parent
  • CLONE_NEWIPC — does not share the IPC namespace (System V IPC objects, POSIX message queues) with the parent
  • CLONE_NEWNET — does not share the networking namespaces (network interfaces, routing table) with the parent

For example, if CLONE_FILES | CLONE_VM | CLONE_FS is used by the caller than effectively a new thread is created. If these flags are not used than a new process is created.

Namespaces and "containers"¶

"Containers" are a form of lightweight virtual machines that share the same kernel instance, as opposed to normal virtualization where a hypervisor runs multiple VMs, each with its one kernel instance.

Examples of container technologies are LXC — that allows running lightweight "VM" and docker — a specialized container for running a single application.

Containers are built on top of a few kernel features, one of which is namespaces. They allow isolation of different resources that would otherwise be globally visible. For example, without containers, all processes would be visible in /proc. With containers, processes in one container will not be visible (in /proc or be killable) to other containers.

To achieve this partitioning, the struct nsproxy structure is used to group types of resources that we want to partition. It currently supports IPC, networking, cgroup, mount, networking, PID, time namespaces. For example, instead of having a global list for networking interfaces, the list is part of a struct net . The system initializes with a default namespace ( init_net ) and by default all processes will share this namespace. When a new namespace is created a new net namespace is created and then new processes can point to that new namespace instead of the default one.

Accessing the current process¶

Accessing the current process is a frequent operation:

  • opening a file needs access to struct task_struct ‘s file field
  • mapping a new file needs access to struct task_struct ‘s mm field
  • Over 90% of the system calls needs to access the current process structure so it needs to be fast
  • The current macro is available to access to current process’s struct task_struct

In order to support fast access in multi processor configurations a per CPU variable is used to store and retrieve the pointer to the current struct task_struct :

../_images/ditaa-019489e686a2f60f1594e37458cfcb10320eae0f.png

Previously the following sequence was used as the implementation for the current macro:

Quiz: previous implementation for current (x86)¶

What is the size of struct thread_info ?

Which of the following are potential valid sizes for struct thread_info : 4095, 4096, 4097?

Context switching¶

The following diagram shows an overview of the Linux kernel context switch process:

../_images/ditaa-f6b228332baf165f498d8a1bb0bc0bdb91ae50c5.png

Note that before a context switch can occur we must do a kernel transition, either with a system call or with an interrupt. At that point the user space registers are saved on the kernel stack. At some point the schedule() function will be called which can decide that a context switch must occur from T0 to T1 (e.g. because the current thread is blocking waiting for an I/O operation to complete or because it’s allocated time slice has expired).

At that point context_switch() will perform architecture specific operations and will switch the address space if needed:

Then it will call the architecture specific switch_to implementation to switch the registers state and kernel stack. Note that registers are saved on stack and that the stack pointer is saved in the task structure:

You can notice that the instruction pointer is not explicitly saved. It is not needed because:

  • a task will always resume in this function
  • the schedule() ( context_switch() is always inlined) caller’s return address is saved on the kernel stack
  • a jmp is used to execute __switch_to() which is a function and when it returns it will pop the original (next task) return address from the stack

The following screencast uses the debugger to setup a breaking in __switch_to_asm and examine the stack during the context switch:

Quiz: context switch¶

We are executing a context switch. Select all of the statements that are true.

  • the ESP register is saved in the task structure
  • the EIP register is saved in the task structure
  • general registers are saved in the task structure
  • the ESP register is saved on the stack
  • the EIP register is saved on the stack
  • general registers are saved on the stack

Blocking and waking up tasks¶

Task states¶

The following diagram shows to the task (threads) states and the possible transitions between them:

../_images/ditaa-50c298a7368c337533bf64c3a8b8787d7dd5231e.png

Blocking the current thread¶

Blocking the current thread is an important operation we need to perform to implement efficient task scheduling — we want to run other threads while I/O operations complete.

In order to accomplish this the following operations take place:

  • Set the current thread state to TASK_UINTERRUPTIBLE or TASK_INTERRUPTIBLE
  • Add the task to a waiting queue
  • Call the scheduler which will pick up a new task from the READY queue
  • Do the context switch to the new task

Below are some snippets for the wait_event implementation. Note that the waiting queue is a list with some extra information like a pointer to the task struct.

Also note that a lot of effort is put into making sure no deadlock can occur between wait_event and wake_up : the task is added to the list before checking condition , signals are checked before calling schedule() .

Waking up a task¶

We can wake-up tasks by using the wake_up primitive. The following high level operations are performed to wake up a task:

  • Select a task from the waiting queue
  • Set the task state to TASK_READY
  • Insert the task into the scheduler’s READY queue
  • On SMP system this is a complex operation: each processor has its own queue, queues need to be balanced, CPUs needs to be signaled

Preempting tasks¶

Up until this point we look at how context switches occurs voluntary between threads. Next we will look at how preemption is handled. We will start wight the simpler case where the kernel is configured as non preemptive and then we will move to the preemptive kernel case.

Non preemptive kernel¶

  • At every tick the kernel checks to see if the current process has its time slice consumed
  • If that happens a flag is set in interrupt context
  • Before returning to userspace the kernel checks this flag and calls schedule() if needed
  • In this case tasks are not preempted while running in kernel mode (e.g. system call) so there are no synchronization issues

Preemptive kernel¶

In this case the current task can be preempted even if we are running in kernel mode and executing a system call. This requires using a special synchronization primitives: preempt_disable and preempt_enable .

In order to simplify handling for preemptive kernels and since synchronization primitives are needed for the SMP case anyway, preemption is disabled automatically when a spinlock is used.

As before, if we run into a condition that requires the preemption of the current task (its time slices has expired) a flag is set. This flag is checked whenever the preemption is reactivated, e.g. when exiting a critical section through a spin_unlock() and if needed the scheduler is called to select a new task.

Process context¶

Now that we have examined the implementation of processes and threads (tasks), how context switching occurs, how we can block, wake-up and preempt tasks, we can finally define what the process context is what are its properties:

The kernel is executing in process context when it is running a system call.

In process context there is a well defined context and we can access the current process data with current

In process context we can sleep (wait on a condition).

In process context we can access the user-space (unless we are running in a kernel thread context).

Kernel threads¶

Sometimes the kernel core or device drivers need to perform blocking operations and thus they need to run in process context.

Kernel threads are used exactly for this and are a special class of tasks that don’t "userspace" resources (e.g. no address space or opened files).

The following screencast takes a closer look at kernel threads:

Using gdb scripts for kernel inspection¶

The Linux kernel comes with a predefined set of gdb extra commands we can use to inspect the kernel during debugging. They will automatically be loaded as long gdbinit is properly setup

All of the kernel specific commands are prefixed with lx-. You can use TAB in gdb to list all of them:

The implementation of the commands can be found at script/gdb/linux . Lets take a closer look at the lx-ps implementation:

Управление процессами в Linux

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

После этого в памяти выделяется пространство под хранение переменных и других вещей, необходимых для запуска программы.

Запущенный экземпляр программы называется процессом. Процессами можно управлять.

Как узнать, какие процессы запущены сейчас

Linux, как и большинство современных ОС, — мультизадачен. Это значит, что в Linux одновременно может работать множество процессов.

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

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

Давайте подробнее разберемся, что здесь происходит:

  • Строка 2. Задачи (tasks) — это второе название процессов. В любой момент времени в вашей системе выполняется сразу несколько процессов. Большая часть из них системные. Многие — спят, и это нормально. «Спящие» процессы ожидают какого-то события, чтобы перейти в активное состояние.
  • Строка 3. Это анализ оперативной памяти. Не переживайте, если используется огромное количество памяти: Linux сохраняет недавно открытые программы для повышения быстродействия. Если какому-то процессу понадобится эта память — она очистится.
  • Строка 4. Это анализ виртуальной памяти системы. Если используется большое количество памяти — пора задуматься о ее увеличении. При наличии современного компьютера с достаточным количеством памяти такой проблемы у вас не возникнет.
  • Строки 6-10. Список наиболее ресурсоемких процессов системы в порядке убывания. Он обновляется в реальном времени — наблюдать за этим одно удовольствие. У вас перед глазами все, что происходит в вашей системе. Два важных столбца — используемая память и процент использования ЦП. Если какой-то из них находится на высоком уровне продолжительное время, стоит узнать, почему так. В столбце USER указывается пользователь, который запустил данный процесс, а PID это столбец, в котором указаны ID процессов — их уникальные идентификаторы.

Команда top предоставляет информацию о системе в реальном времени и показывает лишь то число процессов, которые умещаются на экране.

Есть другая команда — ps (processes, процессы). Обычно ее используют для того, чтобы увидеть список процессов в текущем терминале. Но если добавить аргумент aux , выведется полный список процессов, что гораздо полезнее.

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

Как удалить из памяти процессы, потерпевшие сбой

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

Это не страшно: процесс условного Firefox можно завершить и запустить браузер снова. Но сначала нам нужно узнать ID процесса браузера — PID (proccess ID, идентификатор процесса). Вот, как это будет выглядеть:

Рядом с пользователем karpaff находится число — это и есть PID. Его мы будем использовать для обозначения процесса, который мы хотим закрыть. Для завершения процесса существует специальная команда — kill .

Обычно простой запуск kill завершает зависший процесс. В этом случае команда отправляет параметр по умолчанию — -1 . Этот сигнал указывает процессу, ему надо закрыться. Сначала всегда нужно попытаться воспользоваться этим вариантом: он короче.

Однако это не всегда работает. В примере выше мы запускали ps, но процесс все еще работал. Ничего страшного, значит, мы просто подадим параметр -9 — это сигнал для принудительного завершения процесса.

Примечание. Пользователь без root-прав может завершать лишь те процессы, которые он запустил. Root-пользователь может завершить любой.

Что делать, если завис рабочий стол

Иногда зависший процесс тянет за собой и рабочий стол. Давайте разберемся, что делать в ситуации, когда рабочий стол тоже завис.

В Linux работает сразу несколько виртуальных консолей. Большую часть времени мы видим консоль номер 7 — с GUI. Но мы можем легко обратиться к другим.

Если GUI завис, можно закрыть проблемные процессы с другой консоли. Для переключения между консолями используйте последовательность Ctrl + Alt + F<Консоль>. Например, после нажатия Ctrl + Alt + F2 вы подключитесь к консоли, с помощью которой сможете получить id приведших к сбою процессов и отключить их. А Ctrl + Alt + F7 вернет вас в GUI, чтобы проверить, все ли в порядке.

Общая тактика такая: отключать процессы до тех пор, пока рабочий стол не «отвиснет».

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

Фоновые процессы и процессы переднего плана

Возможно, этим вы будете пользоваться этим не часто, но знать об этом довольно полезно: в редких случаях пригодится.

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

В качестве примера приведем команду sleep . Эта команда ждет, когда пройдет заданное количество времени и завершает свою работу.

Для отображения работающих в фоне процессов можно использовать команду jobs .

Если вы запустите эту программу, то заметите, что терминал ждет 5 секунд и только потом предлагает ввод снова. Но если мы сделаем то же самое, но добавим в конец & (амперсанд), мы сообщим терминалу, что программу нужно запустить на фоне.

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

Мы также имеем возможность перемещать процессы между фоном и передним планом. Делается это с помощью комбинации Ctrl + Z. После нажатия этой последовательности текущий фоновый процесс останавливается и перемещается на передний план.

Также мы можем использовать команду fg (foreground, передний план). С ее помощью мы можем перевести фоновый процесс на передний план.

<job number> — номер процесса.

Совет. В Windows Ctrl + Z служит для отмены. Часто пользователи, которые перешли на Linux с Windows, используют эту комбинацию и удивляются — куда же пропала программа и почему ввод появился снова. Если вы допустили эту ошибку, не переживайте. Используйте jobs , чтобы узнать номер задачи, который был назначен программе, и верните ее на передний план с помощью fg .

Что нужно запомнить

Команды

top
Выводит данные о процессах, запущенных в системе, в реальном времени.

ps
Выводит список процессов, запущенных в системе.

kill
Завершает процесс

jobs
Выводит список текущих фоновых процессов.

fg
Переводит фоновый процесс на передний план.

Ctrl + Z
Остановить текущий процесс переднего плана и перевести его в фон.

Практически задания

1. Запустите несколько программ на рабочем столе. Затем с помощью команды ps определите их PID и завершите их.
2. Попробуйте сделать то же самое, но сначала переключитесь на другую виртуальную консоль.
3. Поиграйтесь с командой sleep и перемещением процессов между передним и фоновым планами.

Name already in use

LinuxNotes / Linux Process.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

Процесс — исполняемый экземпляр программы. Процессы могут быть порождены другими процессами, породить ещё процессы, умереть или быть убитыми. Когда процесс создаётся, он почти идентичен своему родителю: получает копию адресного пространства процесса-родителя на момент создания ребёнка. У отца и ребёнка свои собственные данные(стек, куча, структуры данных, код(возможно) — копия), потому изменение ребёнком своих данных не видно родителю и наоборот.

Во время эволюции Unix систем появились пользовательские POSIX потоки — pthread, которые совместно используют большое количество структур данных. Однако первоначальная реализация потоков была не очень удачна, что связано с тем что их планировкой занимались пользователи. Тогда были созданы облегчённые процессы. По сути те же потоки, только их планирование осуществляется ядром и, как следствие, их проще синхронизировать. Сейчас c каждым потоком ассоциирован облегчённый процесс.

В Linux есть такое понятие, как группа потоков — набор облегчённых процессов, которые действуют, как единое целое по отношению к системам вызовам: getpid(), kill(), _exit().

Каждый процесс(облегчённый тоже) описывается с помощью структуры struct task_struct(task_t). Структура полностью описывает всё состояние, атрибуты процесса и многое другое, и она ОЧЕНЬ большая, её описание можно найти в файле include/linux/sched.h.

task_struct
thread_info
state /*-1 unrunnable, 0 runnable, >0 stopped */
*stack
usage
flags
sched_info sched_info
mm_struct mm / Указатели на дескрипторы областей памяти*/
mm_struct *active_mm
pid_t pid /* Идентификатор процесса*/
pid_t tgid /* Идентификатор группы процесса*/
parent / recipient of SIGCHLD, wait4() reports */
real_parent / real parent process */
list_head children /* list of my children */
fs_struct fs / filesystem information */
files_struct files / open file information */
/* signal handlers */
list_head sibling; /* linkage in my parent’s children list */
list_head tasks /* Общий список процессов*/
……………

Рассмотрим некоторые поля:

  1. @parent — это тот task, которому надо отправлять уведомления (например, сигнал SIGCHLD) о данном task_struct.
  2. @real_parent — всегда тот task_struct, который породил данный task. В большинстве случаев parent == real_parent. Но если мы подцепились к task_struct-у отладчиком, то parent-ом будет отладчик.
  3. @children — список из детей данного task_struct-а.
  4. @sibling — list_head-узел в списке (см. описание списков list_head) children твоего родителя
  5. @tasks — list_head-узел в списке всех процессов (голова находится в idle-таске). В ядре иногда возникают задачи, когда нужно перебрать все процессы в системе (например, чтобы найти жертву, когда памяти не хватает, см. mm/oom_kill.c) соответственно, для данного перебора можно написать поиск в глубину (в ширину), зная родственные отношения процессов в ядре, однако в ядре особо не заморачиваются, поэтому проходятся по списку tasks.

Процесс может находится в различных состояниях во время своего существования. Например:

Процесс либо выполняется, либо готов в любой момент выполняться и ждёт своего времени.

Процесс приостановлен, пока не произойдёт определённое событие, которое должно его разбудить (например, пришли данные в pipe, а объект pipe знает, кто его ждет, pipe и занимается пробуждением процесса из состояния INTERRUPTIBLE). Также из этого состояния процесс пробуждается, если приходит сигнал.

Это состояние похоже на предыдущее, но приход сигнала не пробуждает процесс (даже SIGKILL не разбудит), процесс в этом состоянии прервать нельзя. Используется редко, когда ожидаемое событие долго ждать, но оно гарантированно конечно. Например, чтение файлов устройств(IO), времени, взятие mutex.

Процесс переходит в это состояние после получения сигнала SIGSTOP и похожих.

У каждого процесса есть поле — int exit_state, которое содержит состояние, с которым процесс завершил своё выполнение. После того, как процесс завершает работу ядро переводит его в состояние «Зомби»:

В состояние «Зомби» процесс будет находиться до тех пор, пока его родитель не прочитает его состояние с помощью wait4(), waitpid() и т.д.

Последнее состояние жизни процесса, в которое он переходит после того, как кто-нибудьвыполнит к нему wait4, waitpid().

Схема состояний и переходов между ними процесса; о состоянии процесса можно узнать с помощью утилиты ps:

Состояния R (running), r/q (ready, queued), W/S(wait/sleep) — для userspace В ядре состояния R, r/q объединяются в RUNNING.

Максимальное количество процессов обычно 2^15 = 32768. По умолчанию можно получить из макроса #define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000) (include/linux/threads.h). Так же количество доступных процессов может быть изменено системным администратором в /proc/sys/kernel/pid_max/. На 64 разрядных машинах максимальное значение можно увеличить до 4 * 1024 * 1024.

Для повторного использования pid’ов ядро поддерживает pidmap(include/linux/ pid_namespace.h)

task_struct существует для каждого процесса, даже облегчённого, pid у них у всех разный. Однако с потоками надо работать, как с единой сущностью, например, посылая сигнал. Потому у каждого потока в группе есть общий идентификатор — pid лидера группы и хранится он в поле tgid (thread group id), и системный вызов getpid() возвращает именно его.

Для каждого процесса ядро хранила struct thread_info, содержащую указатель на task_struct, и стэк режима ядра. thread_info — платформозависимая структура, потому искать её в <asm/thread_info.h>. Для x86 для ядра 2.6 выглядела вот так:

Раньше размер стэка ядерного режима был 4Кб и task_struct хранился в нём.

Для 32битной системы sizeof(task_struct)

1.7 Kb, что было довольно критично, поэтому ввели thread_info, который указывает на task_struct, также увеличили стэк ядерного режима до 8Kb.

Однако в процессе эволюции thread_info переместилось в окончательно в task_struct, а на структуру указывает регистр gs.

Текущий процесс, запущенный на процессоре можно получить с помощью макроса current() <asm/current.h>:

Так же надо заметить, что thread_info должна быть самой первой в task_struct, чтобы имея адрес одной в регистре fs, иметь адрес двух структур сразу, что можно заметить в макросе:

Списки процессов: Для дальнейшего понимания происходящего необходимо разобраться со списками Linux и технологией RCU. Вы сможете это сделать прочитав, например, соседние файлы: Linux Lists.txt и Linux Synchronization.txt

Все процессы выстроены в двунаправленный список, для этого каждая структура task_struct включает в себя поле — struct list_head tasks. В голове списка находится дескриптор init_task с pid = 0.

Пройтись по списку процессов можно с помощью макроса for_each_process(p) (include/linux/sched.h):

При создании процесса, новый добавляется в конец списка процессов (kernel/fork.c —> copy_procces):

Изучив структуру task_struct, мы можем отыскать ещё списки в её составе:

Данные списки описывают какие отношения связывают наш процесс с другими процессами. Список — children содержит детей нашего процесса, а sibling — братьев — других процессов созданных нашим родителем.

Процесс 0 — предок всех процессов. Его описывает статическая структура: struct task_struct init_task = INIT_TASK(init_task); (/init/init_task.c и макрос в/linux/init_task.h)

Структура инициализируется при старте функцией — start_kernel(void) (/init/main.c). После чего процесс 0 инициализирует подсистемы, создаёт процесс с pid == 1 — init.

Функция kernel_thread(kernel_init, NULL, CLONE_FS) — создаст клона, который будет исполнять функцию kernel_init. После этого процесс 0 вызовет cpu_idle() и будет работать вхолостую, просыпаясь только если другие процессы не могут быть исполнены. Созданный поток ядра завершает инициализацию ядра, а после загружает исполняемую программу init.

Как мы видели раньше у каждого процесса может быть группа потоков исполнения. У каждого потока свой собственный pid, но все потоки должны реагировать на событие, как одна сущность потому есть tgid = pid лидера группы потоков.

Различные процессы объединяются в группы процессов, для того чтобы взаимодействовать с ними, как с единой сущностью есть pgid = pid процесса лидера. В группу процессов входят не только родственники, но и процессы объединённые с помощью конвейера process | process …

Группы процессов в свою очередь объединяются в сессию, сессии связаны с терминалом. И, как можно догадаться, sid = pid лидера сессии.

Для удобства и скорости управления процессами на этапе инициализации ядра создаётся 4 хэш-таблицы: для каждого потока (различные pid), для каждого лидера группы(tgid = pid), для каждого лидера группы процессов(pgid = pid), для каждого лидера сессии(sid = pid).

Хэш таблица эффективнее массива работает с памятью, так как количество процессов в системе зачастую меньше чем их максимальное количество.

Процессы в Linux, взаимодействие процессов

Работа процессов linux, взаимодйствие процессов, управление процессами в linuxДоброго времени, гости моего блога! В сегодняшнем посте расскажу о том, как работают процессы в ОC Linux, а так же как управлять этими самыми процессами, о выполнении процессов в фоне, о повышении/понижении приоритета процессов.

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

В многозадачной системе может быть запущено множество программ. Каждая программа может запустить множество процессов (читай: подпрограмм). При этом в единственный момент на машине, выполняется только 1 процесс. То есть в единственный момент времени ресурсы железа (процессорное время, память, порт ввода/вывода) может использоваться только единственным процессом. Очередью, в которой процессу выделяется определенный ресурс железа, управляет планировщик. При этом, во время прерывания одного процесса и запуска (возобновления) другого процесса, состояние процесса (выполняемые действия, на каком этапе процесс приостановлен) запоминается и записывается в область памяти. Планировщик в Linux — это часть ядра, отвечающая за указанную функциональность. В задачи планировщика так же входит отслеживание и выделение запускаемым процессам определенного приоритета, чтобы процессы «не мешали» друг-другу работать, а так же распределение пространства памяти, чтобы пространство памяти одного процесса не пересекалось с пространством другого.

Все новые процессы в Linux порождаются клонированием какого-то уже имеющегося процесса, с помощью вызова системных функций clone(2) и fork(2) (от forking — порождение). У нового (порожденного или дочернего) процесса тоже окружение, что и у родителя, отличается только номер ID процесса (т.н. PID). Жизнь типичного процесса в Linux можно представить следующей схемой:

На которой можно описать пошагово следующие этапы:

  • процесс /bin/bash клонирует себя системным вызовом fork()
  • при этом создается клон /bin/bash с новым PID (идентификатор процесса) и PPID — равный PID родителя
  • Клон выполняет системный вызов exec с указанием на исполняемый файл и заменяет свой код — кодом исполняемого файла (родительский процесс при этом ждет завершения потомка — wait)
    • при этом, если по каком-то причинам, потомок завершил свою работу, а родительский процесс не смог получить об этом сигнал, то данный процесс (потомок) не освобождает занятые структуры ядра и состояние процесса становиться — zombie. О состояниях процесса ниже.

    Очень наглядную схему предоставила википедия:

    Состояния процессов Linux

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

    Первый процесс в системе запускается при инициализации ядра. Данный процесс называется — init и имеет PID=1. Это прородитель всех процессов в системе. Подробнее о процессе загрузки ядра и рождении процесса init можно почитать тут.

    В каких же состояниях может находиться процесс в Linux?

    Каждый запущенный процесс в любой момент времени находится в одном из следующих состояний (которое называют еще статусом процесса):

    • Активен (R=Running) – процесс находится в очереди на выполнение, то есть либо выполняется в данный момент, либо ожидает выделения ему очередного кванта времени центрального процессора.
    • «Спит» (S=Sleeping) – процесс находится в состоянии прерываемого ожидания, то есть ожидает какого-то события, сигнала или освобождения нужного ресурса.
    • Находится в состоянии непрерываемого ожидания (D=Direct) – процесс ожидает определенного («прямого») сигнала от аппаратной части и не реагирует на другие сигналы;
    • Приостановлен (T) – процесс находится в режиме трассировки (обычно такое состояние возникает при отладке программ).
    • «Зомби» (Z=Zombie) – это процесс, выполнение которого завершилось, но относящиеся к нему структуры ядра по каким-то причинам не освобождены. Одной из причин их появления в системе может быть следующая ситуация. Обычно освобождение структур ядра, относящихся к процессу, выполняет процесс-родитель после получения от потомка сигнала о завершении. Но бывают случаи, когда родительский процесс завершается раньше дочернего. Процессы, не имеющие родителя, называются «сиротами«. «Сироты» автоматически усыновляются процессом init, который и принимает сигналы об их завершении. Если процесс-родитель или init по каким-то причинам не может принять сигнал о завершении дочернего процесса, то процесс-потомок превращается в «зомби» и получает статус Z. Процессы-зомби не занимают процессорного времени (т. е. их выполнение прекращается), но соответствующие им структуры ядра не освобождаются. В некотором смысле это «мертвые» процессы. Уничтожение таких процессов — одна из обязанностей системного администратора. Хочу отметить, что появление данных процессов говорит о том, что в системе что-то не в порядке, и скорее всего не в порядке с аппаратной частью, так что берем memtest и MHDD и тестим-тестим. Не исключен вариант и кривого кода программы.

    Так же, говоря о процессах в линуксе, можно выделить особый вид процессов — демоны. Данный вид процессов работает в фоне (подобно службам в Windows), без терминала и выполняет задачи для других процессов. Данный вид процессов на серверных системах является основным.

    Т.к. в большинстве случаев, демоны в Linux простаивают и ожидают поступления каких-либо данных, соответственно, нужны относительно редко, так что держать их в памяти постоянно загруженными и расходовать на это ресурсы системы нерационально. Для организации работы демонов придуман демон inetd или его более защищенная модификация xinetd (eXtended InterNET Daemon или расширенный Интернет демон). В функции inetd (Xinetd) можно выделить:

    • установить ограничение на количество запускаемых серверов ( служб , демонов)
    • наблюдение за соединениями на определенных портах и обработка входящих запросов
    • ограничение доступа к сервисам на основе ACL (списков контроля доступа)

    Все процессы в системе, не важно Linux это или другая ОС, обмениваются между собой какой-либо информацией. Отсюда можно задать вопрос, а как же происходит межПРОЦЕССный обмен?

    В ОС LINUX существует несколько видов можпроцессного обмена, а точнее сказать средств межпроцессного взаимодействия (Interprocess Communication — IPC), которые можно разбить на несколько уровней:

    локальный (привязаны к процессору и возможны только в пределах компьютера);

    — каналы

    1. pipe (они же конвейеры, так же неименованные каналы), о них я много рассказывал в прошлом посте, примером можно привести: команда1 | команда2. По сути, pipe использует stdin, stdout и stderr.
    2. Именованные каналы (FIFO: First In First Out). Данный вид канала создаётся с помощью mknod или mkfifo, и два различных процесса могут обратиться к нему по имени. Пример работы с fifo:

    в первом терминале (создаем именованный канал в виде файла pipe и из канала направляем данные с помощью конвейера в архиватор):

    во втором терминале (отправляем в именованный канал данные):

    в результате это приведет к сжатию передаваемых данных gzip-ом

    — сигналы

    • с терминала, нажатием специальных клавиш или комбинаций (например, нажатие Ctrl-C генерирует SIGINT, а Ctrl-Z SIGTSTP);
    • ядром системы:
      • при возникновении аппаратных исключений (недопустимых инструкций, нарушениях при обращении в память, системных сбоях и т. п.);
      • ошибочных системных вызовах;
      • для информирования о событиях ввода-вывода;
      • из шелла, утилитой /bin/kill.

      сигнал — это асинхронное уведомление процесса о каком-либо событии. Когда сигнал послан процессу, операционная система прерывает выполнение процесса. Если процесс установил собственный обработчик сигнала, операционная система запускает этот обработчик, передав ему информацию о сигнале. Если процесс не установил обработчик, то выполняется обработчик по умолчанию.
      Все сигналы начинаются на «SIG…» и имеют числовые соответствия, определяемые в заголовочном файле signal.h. Числовые значения сигналов могут меняться от системы к системе, хотя основная их часть имеет в разных системах одни и те же значения. Утилита kill позволяет задавать сигнал как числом, так и символьным обозначением.
      Сигналы можно послать следующими способами:

      — разделяемая память

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

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

      — очереди сообщений

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

      удаленный;

      — удаленные вызовы процедур (Remote Procedure Calls — RPC)

      RPC — разновидность технологий, которая позволяет компьютерным программам вызывать функции или процедуры в другом адресном пространстве (как правило, на удалённых компьютерах). Обычно, реализация RPC технологии включает в себя два компонента: сетевой протокол (чаще TCP и UDP, реже HTTP) для обмена в режиме клиент-сервер и язык сериализации объектов (или структур, для необъектных RPC).

      — сокеты Unix

      Сокеты UNIX бывают 2х типов: локальные и сетевые. При использовании локального сокета, ему присваивается UNIX-адрес и просто будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём простого чтения/записи из него. Сокеты представляют собой виртуальный объект, который существует, пока на него ссылается хотя бы один из процессов. При использовании сетевого сокета, создается абстрактный объект привязанный к слушающему порту операционной системы и сетевому интерфейсу, ему присваивается INET-адрес, который имеет адрес интерфейса и слушающего порта.

      высокоуровневый

      1. Обычно — пакеты программного обеспечения, которые реализуют промежуточный слой между системной платформой и приложением. Эти пакеты предназначены для переноса уже испытанных протоколов коммуникации приложения на более новую архитектуру. Примером можно привести: DIPC, MPI и др. (мне не знакомы, честно говоря)

      Итак. Подведем маленький итог:

      • В Linux есть процессы,
      • каждый процесс может запускать подпроцессы (нити),
      • создание нового процесса создается клонированием исходного,
      • прородителем всех процессов в системе является процесс init, запускаемый ядром системы при загрузке.
      • процессы взаимодействуют между собой по средствам можпроцессного взаимодействия:
        • каналы
        • сигналы
        • сокеты
        • разделяемая память
        • PID — идентификатор процесса
        • PPID — идентификатор процесса, породившего данный
        • UID и GID — идентификаторы прав процесса (соответствует UID и GID пользователя, от которого запущен процесс)
        • приоритет процесса
        • состояние процесса (выполнение, сон и т.п.)
        • так же у процесса есть таблица открытых (используемых) файлов

        Далее поговорим о том, как посмотреть состояние процессов в Linux и о том, как же ими управлять.

        Управление процессами

        Получение информации о процессе

        Перед тем как управлять процессами, нужно научиться получать о процессах необходимую информацию. В Linux существует псевдофайловая система procfs, которая в большинстве дистрибутивов монтируется в общую ФС в каталог /proc. У данной файловой системы нет физического места размещения, нет блочного устройства, такое как жесткий диск. Вся информация, хранимая в данном каталоге находится в оперативной памяти компьютера, контролируется ядром ОС и она не предназначена для хранения файлов пользователя. О структуре данного каталога я написал в статье о файловой системе Linux. В этой файловой системе дано достаточно много информации, чтобы узнать о процессах и о системе в целом.

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

        Чтобы получить список всех процессов, достаточно ввести команду:

        # ps aux

        Прокомментируем некоторые интересные моменты. Можно заметить, что некоторые процессы указаны в квадратных скобках [ ] – это процессы, которые входят непосредственно в состав ядра и выполняют важные системные задачи, например, такие как управление буферным кэшем [pdflush] и организацией свопинга [kswapd]. С ними лучше не экспериментировать – ничего хорошего из этого не выйдет :). Остальная часть процессов относится к пользовательским.

        Какую информацию можно получить по каждому процессу (комментарии к некоторым полям):

        • PID, PPID – идентификатор процесса и его родителя.
        • %CPU – доля процессорного времени, выделенная процессу.
        • %MEM – процент используемой оперативной памяти.
        • VSZ – виртуальный размер процесса.
        • TTY – управляющий терминал.
        • STAT– статус процесса:
          • R – выполняется;
          • S – спит;
          • Z – зомби;
          • < – Повышенный приоритет;
          • + – Находится в интерактивном режиме.

          Команда ps делает моментальный снимок процессов в текущий момент. В отличии от нее, команда top — динамически выводит состояние процессов и их активность в реальном режиме времени.

          Пример вывода команды top:

          В верхней части вывода отображается астрономическое время, время, прошедшее с момента запуска системы, число пользователей в системе, число запущенных процессов и число процессов, находящихся в разных состояниях, данные об использовании ЦПУ, памяти и свопа. А далее идет таблица, характеризующая отдельные процессы. Число строк, отображаемых в этой таблице, определяется размером окна: сколько строк помещается, столько и выводится.

          Содержимое окна обновляется каждые 5 секунд. Список процессов может быть отсортирован по используемому времени ЦПУ (по умолчанию), по использованию памяти, по PID, по времени исполнения. Переключать режимы отображения можно с помощью следующих клавиатурных команд:

          • <Shift>+<N> — сортировка по PID;
          • <Shift>+<A> — сортировать процессы по возрасту;
          • <Shift>+<P> — сортировать процессы по использованию ЦПУ;
          • <Shift>+<M> — сортировать процессы по использованию памяти;
          • <Shift>+<T> — сортировка по времени выполнения.

          С помощью команды <K> можно завершить некоторый процесс (его PID будет запрошен), а с помощью команды <R> можно переопределить значение nice для некоторого процесса.

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

          Итак, теперь об управлении процессами.

          Управление процессами в Linux

          Каждому процессу при запуске устанавливается определенный приоритет, который имеет значение от -20 до +20, где +20 — самый низкий. Приоритет нового процесса равен приоритету процесса-родителя. Для изменения приоритета запускаемой программы существует утилита nice. Пример ее использования:

          где adnice — значение (от –20 до +19), добавляемое к значению nice процесса-родителя. Отрицательные значения может устанавливать только суперпользователь. Если опция adnice не задана, то по умолчанию для процесса-потомка устанавливается значение nice, увеличенное на 10 по сравнению со значением nice родительского процесса.

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

          Как я уже писал, одним из средств управления процессами являются сигналы. Некоторые сигналы можно сгенерировать с помощью определенных комбинаций клавиш, но такие комбинации существуют не для всех сигналов. Зато имеется команда kill, которая позволяет послать заданному процессу (указав его PID) любой сигнал:

          где SIG — это номер сигнала или наименование сигнала, причем если указание сигнала опущено, то посылается сигнал 15 (SIGTERM — программное завершение процесса). Часто используется сигнал 9 (KILL), с помощью которого суперпользователь может завершить любой процесс. Но сигнал этот очень «грубый», если можно так выразиться, потому что он просто «убивает» процесс, не давая ему времени на корректное сохранение всех обработанных данных. Поэтому в большинстве случаев рекомендуется использовать сигналы TERM или QUIT, которые завершают процесс более «мягко». Если процессу необходимо как-то по-особенному реагировать на сигнал, он может зарегистрировать обработчик, а если обработчика нет, за него отреагирует система.

          Два сигнала – 9 ( KILL ) и 19 ( STOP ) – всегда обрабатывает система. Первый из них нужен для того, чтобы убить процесс наверняка (отсюда и название). Сигнал STOP приостанавливает процесс: в таком состояниипроцесс не удаляется из таблицы процессов, но и не выполняется до тех пор, пока не получит сигнал 18 ( CONT) – после чего продолжит работу. В Linux сигнал STOP можно передать активному процессу с помощью управляющего символа » ^Z «.

          Обычные пользователи могут посылать сигналы только тем процессам, для которых они являются владельцами. Если в команде kill воспользоваться идентификатором процесса (PID), равным -1, то указанный в команде сигнал будет послан всем принадлежащим данному пользователю процессам. Суперпользователь root может посылать сигналы любым процессам. Когда суперпользователь посылает сигнал идентификатору -1, он рассылается всем процессам, за исключением системных. Если этим сигналом будет SIGKILL, то у простых пользователей будут потеряны все открытые ими, но не сохраненные файлы данных.

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

          В оболочке bash имеются две встроенные команды, которые служат для перевода процессов на передний план или возврата их в фоновый режим. Команда fg переводит указанный в аргументе процесс на передний план, а команда bg — переводит процесс в фоновый режим. Одной командой bg можно перевести в фоновый режим сразу несколько процессов, а вот возвращать их на передний план необходимо по одному. Аргументами команд fg и bg могут являться только номера заданий, запущенных из текущего экземпляра shell. Возможные значения заданий можно увидеть, выполнив команду jobs.

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

          Запущенный таким образом процесс будет игнорировать посылаемые ему сигналы (не игнорируются только сигналы SIGHUP и SIGQUIT). Хочу так же выделить команду pstree, которая показывает дерево процессов. Очень наглядно, кстати.

          Ну вот. как-то так. Буду рад любым комментариям и дополнениям.

          Upd 2011.11.19: небольшой рестайлинг и дополнение информации о сигналах.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *