Процессы или потоки linux

Процессы или потоки linux

С помощью процессов можно организовать параллельное выполнение программ. Для этого процессы клонируются вызовами fork () или exec (), а затем между ними организуется взаимодействие средствами IPC. Это довольно дорогостоящий в отношении ресурсов процесс.

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

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

  1. Процесс располагает определенными ресурсами. Он размещен в некотором виртуальном адресном пространстве, содержащем образ этого процесса. Кроме того, процесс управляет другими ресурсами (файлы, устройства ввода / вывода и т.д.).
  2. Процесс подвержен диспетчеризации. Он определяет порядок выполнения одной или нескольких программ, при этом выполнение может перекрываться другими процессами. Каждый процесс имеет состояние выполнения и приоритет диспетчеризации.

Если рассматривать эти характеристики независимо друг от друга (как это принято в современной теории ОС), то:


    владельцу ресурса, обычно называемому процессом или задачей, присущи:

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

    • состояние выполнения (активное, готовность и т.д.);
    • сохранение контекста потока в неактивном состоянии;
    • стек выполнения и некоторая статическая память для локальных переменных;
    • доступ к пространству памяти и ресурсам своего процесса.

  • Все потоки процесса разделяют общие ресурсы. Изменения, вызванные одним потоком, становятся немедленно доступны другим.

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

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

    Источник

    Изучаем процессы в Linux


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

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

    Всё написанное ниже справедливо к Debian Linux с ядром 4.15.0.

    Содержание

    Введение

    Системное программное обеспечение взаимодействует с ядром системы посредством специальных функций — системных вызовов. В редких случаях существует альтернативный API, например, procfs или sysfs, выполненные в виде виртуальных файловых систем.

    Атрибуты процесса

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

    • Идентификатор процесса (pid)
    • Открытые файловые дескрипторы (fd)
    • Обработчики сигналов (signal handler)
    • Текущий рабочий каталог (cwd)
    • Переменные окружения (environ)
    • Код возврата

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

    Рождение процесса

    Только один процесс в системе рождается особенным способом — init — он порождается непосредственно ядром. Все остальные процессы появляются путём дублирования текущего процесса с помощью системного вызова fork(2) . После выполнения fork(2) получаем два практически идентичных процесса за исключением следующих пунктов:

    1. fork(2) возвращает родителю PID ребёнка, ребёнку возвращается 0;
    2. У ребёнка меняется PPID (Parent Process Id) на PID родителя.

    После выполнения fork(2) все ресурсы дочернего процесса — это копия ресурсов родителя. Копировать процесс со всеми выделенными страницами памяти — дело дорогое, поэтому в ядре Linux используется технология Copy-On-Write.
    Все страницы памяти родителя помечаются как read-only и становятся доступны и родителю, и ребёнку. Как только один из процессов изменяет данные на определённой странице, эта страница не изменяется, а копируется и изменяется уже копия. Оригинал при этом «отвязывается» от данного процесса. Как только read-only оригинал остаётся «привязанным» к одному процессу, странице вновь назначается статус read-write.

    Состояние «готов»

    Сразу после выполнения fork(2) переходит в состояние «готов».
    Фактически, процесс стоит в очереди и ждёт, когда планировщик (scheduler) в ядре даст процессу выполняться на процессоре.

    Состояние «выполняется»

    Как только планировщик поставил процесс на выполнение, началось состояние «выполняется». Процесс может выполняться весь предложенный промежуток (квант) времени, а может уступить место другим процессам, воспользовавшись системным вывозом sched_yield .

    Перерождение в другую программу

    В некоторых программах реализована логика, в которой родительский процесс создает дочерний для решения какой-либо задачи. Ребёнок в данном случае решает какую-то конкретную проблему, а родитель лишь делегирует своим детям задачи. Например, веб-сервер при входящем подключении создаёт ребёнка и передаёт обработку подключения ему.
    Однако, если нужно запустить другую программу, то необходимо прибегнуть к системному вызову execve(2) :

    или библиотечным вызовам execl(3), execlp(3), execle(3), execv(3), execvp(3), execvpe(3) :

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

    Как не путаться во всех этих вызовах и выбирать нужный? Достаточно постичь логику именования:

    • Все вызовы начинаются с exec
    • Пятая буква определяет вид передачи аргументов:
      • l обозначает list, все параметры передаются как arg1, arg2, . NULL
      • v обозначает vector, все параметры передаются в нуль-терминированном массиве;
    • Далее может следовать буква p, которая обозначает path. Если аргумент file начинается с символа, отличного от «/», то указанный file ищется в каталогах, перечисленных в переменной окружения PATH
    • Последней может быть буква e, обозначающая environ. В таких вызовах последним аргументом идёт нуль-терминированный массив нуль-терминированных строк вида key=value — переменные окружения, которые будут переданы новой программе.

    Семейство вызовов exec* позволяет запускать скрипты с правами на исполнение и начинающиеся с последовательности шебанг (#!).

    Есть соглашение, которое подразумевает, что argv[0] совпадает с нулевым аргументов для функций семейства exec*. Однако, это можно нарушить.

    Любопытный читатель может заметить, что в сигнатуре функции int main(int argc, char* argv[]) есть число — количество аргументов, но в семействе функций exec* ничего такого не передаётся. Почему? Потому что при запуске программы управление передаётся не сразу в main. Перед этим выполняются некоторые действия, определённые glibc, в том числе подсчёт argc.

    Состояние «ожидает»

    Некоторые системные вызовы могут выполняться долго, например, ввод-вывод. В таких случаях процесс переходит в состояние «ожидает». Как только системный вызов будет выполнен, ядро переведёт процесс в состояние «готов».
    В Linux так же существует состояние «ожидает», в котором процесс не реагирует на сигналы прерывания. В этом состоянии процесс становится «неубиваемым», а все пришедшие сигналы встают в очередь до тех пор, пока процесс не выйдет из этого состояния.
    Ядро само выбирает, в какое из состояний перевести процесс. Чаще всего в состояние «ожидает (без прерываний)» попадают процессы, которые запрашивают ввод-вывод. Особенно заметно это при использовании удалённого диска (NFS) с не очень быстрым интернетом.

    Состояние «остановлен»

    В любой момент можно приостановить выполнение процесса, отправив ему сигнал SIGSTOP. Процесс перейдёт в состояние «остановлен» и будет находиться там до тех пор, пока ему не придёт сигнал продолжать работу (SIGCONT) или умереть (SIGKILL). Остальные сигналы будут поставлены в очередь.

    Завершение процесса

    Ни одна программа не умеет завершаться сама. Они могут лишь попросить систему об этом с помощью системного вызова _exit или быть завершенными системой из-за ошибки. Даже когда возвращаешь число из main() , всё равно неявно вызывается _exit .
    Хотя аргумент системного вызова принимает значение типа int, в качестве кода возврата берется лишь младший байт числа.

    Состояние «зомби»

    Сразу после того, как процесс завершился (неважно, корректно или нет), ядро записывает информацию о том, как завершился процесс и переводит его в состояние «зомби». Иными словами, зомби — это завершившийся процесс, но память о нём всё ещё хранится в ядре.
    Более того, это второе состояние, в котором процесс может смело игнорировать сигнал SIGKILL, ведь что мертво не может умереть ещё раз.

    Забытье

    Код возврата и причина завершения процесса всё ещё хранится в ядре и её нужно оттуда забрать. Для этого можно воспользоваться соответствующими системными вызовами:

    Вся информация о завершении процесса влезает в тип данных int. Для получения кода возврата и причины завершения программы используются макросы, описанные в man-странице waitpid(2) .

    Передача argv[0] как NULL приводит к падению.

    Бывают случаи, при которых родитель завершается раньше, чем ребёнок. В таких случаях родителем ребёнка станет init и он применит вызов wait(2) , когда придёт время.

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

    Благодарности

    Спасибо Саше «Al» за редактуру и помощь в оформлении;

    Спасибо Саше «Reisse» за понятные ответы на сложные вопросы.

    Они стойко перенесли напавшее на меня вдохновение и напавший на них шквал моих вопросов.

    Источник

    процессы и потоки

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

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

    2. если в системе существуют несколько процессов, то они не могут взаимодействовать.

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

    4. при этом если процесс браузера породит дочерний поток(например в виде отдельного окна для диспетчера задач браузера), то они могут взаимодействовать потому что связаны отношением «родитель-наследник».

    5. потоки создаются средствами языка программирования и существуют в пределах процесса.

    6. при этом они могут взаимодействовать в пределах своего процесса.

    7. например иметь доступ к переменной: менять её, удалять и т.д.

    8. а также получать друг от друга сигналы типа mutex, semafor и т.д.

    9. при этом потоки, которые живут в разных процессах не могут взаимодействовать.

    10. если завершается процесс, то завершаются все его потоки тоже.

    11. процессы выполняются по очереди. ОС выделяет каждому определённое время. в это время он выполняется, а остальные процессы не активны. далее по очереди каждый процесс становится активным, а остальные неактивными

    12. ОС сама решает по какому принципу усыплять и оживлять процессы. в зависимости от типа ОС этот принцип бывает разный

    если я что-то неправильно понимаю, то пожалуйста укажите на это и, если можно, посоветуйте что почитать чтобы вополнить пробел.

    Если забанили в гугле , то восполняем пробел . Их много у него. 🙂 https://m.youtube.com/watch?v=Ni8udz5bRmU

    Таненбаум в помощь — у него все это подробно описано

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

    1.И тот и другой регистрируются в ОС, процесс более сложен — память, образ на диске, переменные окружения, командная строка и проч. 2. нет

    4.просто могут, не потому что

    5.создаются вызовом ОС, это объект для ОС, не языка

    11.Потоки по очереди

    Поток содержит код и данные. Процесс выполняет код выбранный из потока

    Не путай человека, и сам не путайся

    Тема обширная, чтобы тут расписывать, но пожалуй стоит сказать, что на уровне железа и процесс, и поток существуют. Для проца оба являются одним и тем же «задачей» (task), и разница только в том, какие области памяти и ресурсы ядра «видят» две/три/15 разных задач. Могут иметь общую память, а могут не иметь, смотря кому что отмапит ядро ос. На уровне ос разница уже существеннее, т.к. процесс это еще и учетная единица, где есть главный поток, есть ресурсы ядра вроде дескрипторов или примитивов синхронизации, есть состояние навроде cwd, есть ipc — shm, mmap, etc. Это ос создает различие на базе одного общего механизма многозадачности, которому самому абсолютно похрен на это все. Можешь рассматривать процесс как некую структуру в ядре, у которой есть не менее одного потока (на самом деле не менее нуля, если считать zombie за процессы). И потоки внутри процесса шарят как эту «структуру», так и память и прочее.

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

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

    Сфига-ли? Хэндлы окон совершенно не против, чтобы их перехватывали и творили там что угодно, хоть член нарисуй в натуральную величину.

    Ну, если лезть в дебри программирования. То есть два класса: Process и Thread. Чем они отличаются? Свойствами, вестимо. Я не знаю как ты, а я с первого раза в гугле нашел, чем отличается поток от процесса. Процесс — не лезет в другие участки памяти, кроме выделенного своего. Иначе- BSoD. Поток — лезет в участки памяти, но только своих приложений, которых может быть несколько. Ну, это грубо говоря.

    Всё равно что инкапсуляцию и полиморфизм обьяснять на пальцах. Один хрен, не поймут.

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

    Процесс создаётся через системный вызов clone() (можно через fork(), но glibc’шный враппер fork() использует clone()), поток создаётся через clone(). Почитай ман этого системного вызова, там описано, какие части контекста процесса можно шарить.

    Источник

    Читайте также:  Tail command in windows
    Оцените статью