Предыдущая статья лишь кратко описала основные понятия Linux, проиллюстрировала базовые приложения Linux несколькими примерами и закончила основными конструкциями ядра Linux. Итак, в этой статье мы глубоко разберемся в ядре Linux, чтобы понять основные концепции процессов и потоков Linux. Системные вызовы являются интерфейсами к самой операционной системе и важны для создания процессов и потоков, выделения памяти, общих файлов и ввода-вывода.
Мы начнем с общности каждой версии для обсуждения.
Базовые концепты
Очень важной концепцией Linux является процесс, процесс Linux, и мы находимся в
Обрабатывает и обрабатывает эту статью
Модель процесса, обсуждаемая в, очень похожа. Каждый процесс запускает отдельную программу и имеет отдельный поток управления при инициализации. Другими словами, у каждого процесса будет свой собственный программный счетчик, который используется для записи следующей инструкции, которую необходимо выполнить. Linux позволяет процессам создавать дополнительные потоки во время выполнения.
Linux является мультипрограммной системой, поэтому в системе одновременно выполняются независимые процессы. Также у каждого пользователя будет несколько активных процессов одновременно. Потому что, если это большая система, в ней могут одновременно выполняться сотни или тысячи процессов.
В некотором пользовательском пространстве, даже если пользователь вышел из системы, все еще выполняются некоторые фоновые процессы, эти процессы называются守护进程(daemon)
.
В Linux есть специальный вид демона, который называется计划守护进程(Cron daemon)
, демон планирования может просыпаться каждую минуту, чтобы проверить, есть ли работа, которую нужно выполнить, а затем снова переходить в спящий режим, чтобы дождаться следующего пробуждения.
Cron — это демон, который может делать все, что вы хотите, например, вы можете выполнять регулярное обслуживание системы, делать регулярные резервные копии системы и т. д. Аналогичные программы есть и в других операционных системах, например, демон Cron в Mac OS X, который называется
launchd
процесс демона. можно вызвать в Windows计划任务(Task Scheduler)
.
В системе Linux процесс создания очень прост,fork
Системный вызов создает исходный процесс拷贝(副本)
. Процесс, вызывающий функцию fork, называется父进程(parent process)
, процесс, созданный с помощью функции fork, называется子进程(child process)
. И родительский, и дочерний процессы имеют собственные образы памяти. Если родительский процесс изменяет некоторые переменные после создания дочернего процесса, дочерний процесс не может видеть эти изменения, то есть после fork родительский процесс и дочерний процесс независимы друг от друга.
Хотя родительский процесс и дочерний процесс остаются независимыми друг от друга, они могут совместно использовать один и тот же файл.Если родительский процесс открыл файл до разветвления, то после разветвления родительский процесс и дочерний процесс по-прежнему совместно используют открытый файл. Изменения в общих файлах видны как родительскому, так и дочернему процессам.
Так как же отличить родительский процесс от дочернего? Дочерние процессы — это просто копии родительского процесса, поэтому они практически одинаковы во всем, включая образы памяти, переменные, регистры и т. д. Ключом к различению являетсяfork
Возвращаемое значение после вызова функции, если после fork возвращается ненулевое значение, это ненулевое значение является значением дочернего процесса.进程标识符(Process Identiier, PID)
, но вернет нулевое значение дочернему процессу, что может быть представлено следующим кодом
pid = fork(); // 调用 fork 函数创建进程
if(pid < 0){
error() // pid < 0,创建失败
}
else if(pid > 0){
parent_handle() // 父进程代码
}
else {
child_handle() // 子进程代码
}
Родительский процесс получит PID дочернего процесса после fork, и этот PID может представлять собой уникальный идентификатор дочернего процесса, то есть PID. Если дочерний процесс хочет узнать свой PID, он может вызватьgetpid
метод. Когда дочерний процесс завершит работу, родительский процесс получит PID дочернего процесса, потому что процесс будет разветвлять многие дочерние процессы, а дочерний процесс также будет разветвлять дочерние процессы, поэтому PID очень важен. Мы вызываем процесс после первого вызова fork как原始进程
, примитивный процесс может сгенерировать дерево наследования
Межпроцессное взаимодействие Linux
Механизм связи между процессами Linux часто называютInternel-Process communication,IPC
Поговорим о механизме связи между процессами Linux.Грубо говоря, механизм связи между процессами Linux можно разделить на 6 типов.
Ниже мы даем обзор каждого из них
сигнал
Сигнал — это первый механизм межпроцессного взаимодействия, используемый системами UNIX.Поскольку Linux унаследован от UNIX, Linux также поддерживает сигнальный механизм.Отправляя один или несколько процессов异步事件信号
Для этого сигналы могут генерироваться из таких мест, как клавиатура или доступ к несуществующим местам; сигналы отправляют задачи дочерним процессам через оболочку.
Вы можете войти в систему Linuxkill -l
чтобы перечислить сигналы, используемые системой, вот некоторые из сигналов, которые я предоставил
Процесс может проигнорировать отправленный сигнал, но есть два, которые нельзя игнорировать:SIGSTOP
иSIGKILL
Сигнал. Сигнал SIGSTOP уведомит текущий процесс о необходимости завершения операции, а сигнал SIGKILL уведомит текущий процесс о том, что его следует завершить. В дополнение к этому процесс может выбрать, какие сигналы он хочет обрабатывать, и процесс также может выбрать блокировку сигналов, если нет, он может выбрать обработку их самостоятельно или может выбрать обработку ядра. Если выбор передается ядру для обработки, то выполняется обработка по умолчанию.
Операционная система прервет процесс целевой программы, чтобы послать ей сигнал.В любой неатомарной инструкции выполнение может быть прервано.Если процесс зарегистрировал новый обработчик числа, процесс будет выполнен.Если он не зарегистрирован, будет использована обработка по умолчанию.
Например: когда процесс получаетSIGFPE
После сообщения об исключении с плавающей запятой действие по умолчанию:dump(转储)
и выход. Сигналы не имеют определения приоритета. Если два сигнала генерируются для процесса одновременно, они могут быть представлены процессу или обработаны в любом порядке.
Давайте посмотрим, для чего используются эти сигналы.
- СИГАБРТ и СИГИОТ
Сигналы SIGABRT и SIGIOT отправляются процессу, чтобы сообщить ему о завершении.abort()
функция запускается самим процессом
- СИГАЛРМ, СИГВТАЛРМ, СИГПРОФ
SIGALRM , SIGVTALRM , SIGPROF отправляются процессу, когда истекает время установленной функции часов. SIGALRM отправляется, когда истекает фактическое время или время часов. SIGVTALRM отправляется, когда время ЦП, используемое процессом, истекает. SIGPROF отправляется, когда время ЦП, используемое процессом и системой от имени процесса, истекло.
- SIGBUS
SIGBUS вызовет总线中断
Отправлено в обработку при ошибке
- SIGCHLD
Отправьте SIGCHLD процессу, когда дочерний процесс завершается, прерывается или возобновляется после прерывания. Обычно этот сигнал используется для указания операционной системе очистить ресурсы, используемые дочерним процессом после его завершения.
- SIGCONT
Сигнал SIGCONT указывает операционной системе продолжить выполнение процесса, ранее приостановленного сигналом SIGSTOP или SIGTSTP. Важным использованием этого сигнала является управление заданиями в оболочке Unix.
- SIGFPE
Сигнал SIGFPE отправляется процессу, когда выполняется неправильная арифметическая операция (например, деление на ноль).
- SIGUP
Отправляется процессу, когда терминал, управляемый сигналом SIGUP, закрыт. Многие демоны перезагружают свои файлы конфигурации и повторно открывают свои файлы журналов вместо того, чтобы выйти, когда они получают этот сигнал.
- SIGILL
Сигнал SIGILL выдается при попытке выполнить недопустимую, искаженную, неизвестную или привилегированную инструкцию.
- SIGINT
Когда пользователь хочет прервать процесс, операционная система посылает процессу сигнал SIGINT. Пользователь набирает ctrl-c, чтобы прервать процесс.
- SIGKILL
Сигнал SIGKILL отправляется процессу для его немедленного завершения. По сравнению с SIGTERM и SIGINT, этот сигнал не может быть перехвачен и проигнорирован для выполнения, и процесс не может выполнять какие-либо операции очистки после получения этого сигнала, ниже приведены некоторые исключения.
Процесс-зомби не может быть уничтожен, потому что процесс-зомби мертв и ожидает, пока родительский процесс его захватит.
Заблокированный процесс будет убит только после того, как снова проснется
init
Этот процесс является процессом инициализации Linux, и этот процесс игнорирует любые сигналы.
SIGKILL обычно используется как последний сигнал для уничтожения процесса, обычно он отправляется процессу, когда нет ответа на SIGTERM.
- SIGPIPE
SIGPIPE Отправляется процессу при попытке записи в канал процесса и обнаружении, что канал не подключен и не может быть записан в него.
- SIGPOLL
Сигнал SIGPOLL отправляется, когда происходит событие с явно отслеживаемым дескриптором файла.
- SIGRTMIN в SIGRTMAX
SIGRTMIN в SIGRTMAX да实时信号
- SIGQUIT
Когда пользователь запрашивает выход из процесса и выполнение дампа памяти, управляющий терминал отправляет процессу сигнал SIGQUIT.
- SIGSEGV
Сигнал SIGSEGV отправляется процессу, когда возникает недопустимая ссылка на виртуальную память или ошибка сегментации, т. е. когда выполняется нарушение сегментации.
- SIGSTOP
SIGSTOP при указании операционной системе завершить работу для последующего восстановления
- SIGSYS
Сигнал SIGSYS отправляется процессу, когда системному вызову передается аргумент ошибки.
- SYSTERM
Выше мы кратко упомянули термин SYSTERM, сигнал, посылаемый процессу с запросом на завершение. В отличие от сигнала SIGKILL, этот сигнал может быть перехвачен или проигнорирован процессом. Это позволяет процессу корректно завершать работу, освобождая ресурсы и сохраняя состояние при необходимости. SIGINT почти такой же, как SIGTERM.
- SIGTSIP
Сигнал SIGTSTP посылается процессу его управляющим терминалом, чтобы запросить у терминала остановку.
- СИГТТИН и СИГТТУ
Сигналы отправляются процессу, когда сигналы SIGTTIN и SIGTTOU соответственно пытаются прочитать или записать в tty в фоновом режиме.
- SIGTRAP
Отправлять сигнал SIGTRAP процессу при возникновении исключения или ловушки
- SIGURG
Отправьте сигнал SIGURG процессу, когда сокет имеет срочные или внеполосные данные для чтения.
- SIGUSR1 и SIGUSR2
Сигналы SIGUSR1 и SIGUSR2 отправляются процессам для указания определяемых пользователем условий.
- SIGXCPU
Сигнал SIGXCPU отправляется процессу, когда он исчерпал ресурсы ЦП дольше, чем заданное пользователем заданное значение.
- SIGXFSZ
Сигнал SIGXFSZ отправляется процессу, когда размер файла превышает максимально допустимый.
- SIGWINCH
Сигнал SIGWINCH отправляется процессу, когда его управляющий терминал меняет свой размер (изменяется окно).
трубка
Процессы в системе Linux могут обмениваться данными, устанавливая каналы
Между двумя процессами может быть установлен канал, один процесс записывает поток байтов в этот канал, а другой процесс читает поток байтов из этого канала. Каналы синхронны, и когда процесс пытается прочитать данные из пустого канала, процесс блокируется до тех пор, пока данные не будут доступны. в скорлупе管线 pipelines
Это реализовано с помощью каналов, когда оболочка находит вывод
sort <f | head
Он создаст два процесса, один — sort, другой — head, sort, и между двумя приложениями будет установлен канал, чтобы стандартный вывод процесса sort использовался в качестве стандартного ввода головной программы. Вывод, сгенерированный процессом сортировки, не нужно записывать в файл.Если канал заполнен, система остановит сортировку, чтобы дождаться, пока головка прочитает данные.
труба на самом деле|
, два приложения не знают о существовании каналов, все управляется и контролируется оболочкой.
общая память общая память
Межпроцессное взаимодействие между двумя процессами также возможно через разделяемую память, когда два или более процессов могут обращаться к общему пространству памяти. Совместная работа между двумя процессами выполняется через общую память, и изменения, сделанные одним процессом, могут быть видны другому процессу (во многом подобно обмену данными между потоками).
Прежде чем использовать разделяемую память, необходимо пройти ряд процессов вызова, процесс выглядит следующим образом.
- Создайте сегмент общей памяти или используйте уже созданный сегмент общей памяти
(shmget())
- Прикрепить процесс к уже созданному сегменту памяти
(shmat())
- Отсоединить процесс от присоединенного сегмента разделяемой памяти
(shmdt())
- Выполнение операций управления над сегментами разделяемой памяти
(shmctl())
Очередь «первым пришел – первым вышел» FIFO
FIFO очереди «первым поступил – первым обслужен» часто называют命名管道(Named Pipes)
, именованные каналы работают так же, как обычные каналы, но имеют некоторые заметные отличия. Безымянные каналы не имеют резервных файлов: операционная система отвечает за поддержание буфера в памяти, который передает байты от средств записи к читателям. После завершения записи или вывода буфер будет освобожден, а переданные данные будут потеряны. Напротив, именованные каналы имеют резервный файл и уникальный API, а именованные каналы существуют в файловой системе как специальные файлы для устройств. Когда все коммуникации процесса будут завершены, именованный канал останется в файловой системе для дальнейшего использования. Именованные каналы имеют строгое поведение FIFO.
Первый записанный байт — это первый прочитанный байт, второй записанный байт — второй прочитанный байт и так далее.
очередь сообщений
Вы можете не знать, что это значит, когда слышите термин очередь сообщений, который используется для описания внутреннего связанного списка в адресном пространстве ядра. Сообщения можно последовательно отправлять в очередь и извлекать из нее несколькими различными способами. Каждая очередь сообщений однозначно идентифицируется идентификатором IPC. Существует два режима очереди сообщений, один из них严格模式
, Строгий режим похож на очередь FIFO «первым пришел — первым обслужен», сообщения отправляются последовательно и считываются последовательно. Другой режим非严格模式
, порядок сообщений не очень важен.
разъем
Другой способ управления связью между двумя процессами — использоватьsocket
, сокет обеспечивает сквозную двухфазную связь. Сокет может быть связан с одним или несколькими процессами. Точно так же, как у каналов есть каналы команд и безымянные каналы, у сокетов также есть два режима.Сокеты обычно используются для сетевого взаимодействия между двумя процессами.TCP(传输控制协议)
или более низкий уровеньUDP(用户数据报协议)
и другая поддержка основных протоколов.
Розетки бывают следующих категорий
-
顺序包套接字(Sequential Packet Socket)
: этот тип сокета обеспечивает надежное соединение для дейтаграмм с фиксированной максимальной длиной. Это соединение является двунаправленным и последовательным. -
数据报套接字(Datagram Socket)
: Пакетные сокеты поддерживают двунаправленный поток данных. Пакетные сокеты могут получать сообщения в другом порядке, чем отправитель. -
流式套接字(Stream Socket)
: Потоковые сокеты работают как телефонный разговор, обеспечивая двусторонний надежный поток данных. -
原始套接字(Raw Socket)
: доступ к базовому протоколу связи можно получить с помощью необработанных сокетов.
Системные вызовы управления процессами в Linux
Теперь давайте сосредоточимся на системных вызовах, связанных с управлением процессами в системах Linux. Прежде чем понять, нужно знать, что такое системный вызов.
Операционная система скрывает для нас разницу между аппаратным и программным обеспечением.Ее основная функция состоит в том, чтобы предоставить пользователям абстракцию, скрыть внутреннюю реализацию и позволить пользователям заботиться только о том, как использовать ее в графическом интерфейсе. Операционная система может быть разделена на два режима
- Режим ядра: режим, используемый ядром операционной системы.
- Пользовательский режим: режим, используемый пользовательским приложением.
что мы часто говорим上下文切换
Относится к частому переключению между режимом ядра и режимом пользователя. и系统调用
Относится к способу переключения между режимом ядра и режимом пользователя.Системные вызовы обычно выполняются в фоновом режиме, указывая на то, что компьютерная программа запрашивает услуги у ядра своей операционной системы.
Существует множество инструкций системных вызовов, ниже приведены некоторые из наиболее важных системных вызовов, связанных с управлением процессами.
fork
Вызов fork используется для создания дочернего процесса, который совпадает с родительским процессом.После создания дочерний процесс имеет тот же счетчик программ, те же регистры ЦП и те же открытые файлы, что и родительский процесс.
exec
Системный вызов exec используется для выполнения файла, который находится в активном процессе, после вызова exec новый исполняемый файл заменяет предыдущий исполняемый файл и запускается. То есть, когда вызывается exec, старый файл или программа заменяется новым файлом или exec, а затем выполняется файл или программа. Новый исполнитель загружается в то же пространство выполнения, поэтому процессPID
не будет изменено, потому что мыНовый процесс не создается, просто заменяется старый. Но данные, код и стек процесса были изменены. Если текущий заменяемый процесс содержит несколько потоков, все потоки будут завершены, а новый образ процесса будет загружен и выполнен.
нужно объяснить здесь进程映像(Process image)
Концепция чего-либо
Что такое образ процесса?? Образ процесса — это исполняемый файл, необходимый для выполнения программы, обычно включающий в себя следующее:
- Сегмент кода (сегмент кода/сегмент текста)
Также известный как текстовый сегмент, пространство памяти, используемое для хранения инструкций и выполнения кода.
Размер этого пространства определяется до запуска кода.
Пространство памяти, как правило, доступно только для чтения, а код некоторых архитектур также допускает запись.
В сегменте кода также могут быть некоторые постоянные переменные только для чтения, такие как строковые константы и т. д.
- сегмент данных
прочти и напиши
Сохраняет инициализированные глобальные переменные и инициализированные статические переменные
Время жизни данных в сегменте данных сохраняется в программе (постоянно в процессе). Сохранение процесса: процесс существует, когда процесс создается, и исчезает, когда процесс умирает.
- сегмент bss (бсссегмент):
прочти и напиши
Храните неинициализированные глобальные переменные и неинициализированные статические переменные
Данные в сегменте bss обычно по умолчанию равны 0
- Сегмент данных
доступен для чтения и записи, потому что значение переменной может быть изменено во время выполнения. Размер этого сегмента также фиксирован.
- куча:
прочти и напиши
Хранится локальная переменная (нестатическая переменная) в функции или коде.
Время жизни стека продолжается с кодовым блоком. Кодовый блок выделяет место для вас, когда кодовый блок запускается. Когда кодовый блок заканчивается, пространство будет автоматически высвобождаться.
- куча:
прочти и напиши
Сохраняет пространство malloc/realloc, динамически выделяемое во время выполнения программы.
Время жизни кучи сохраняется вместе с процессом, от malloc/realloc до free.
Ниже представлена схема состава этих областей
Системный вызов exec представляет собой набор функций,
- execl
- execle
- execlp
- execv
- execve
- execvp
Давайте посмотрим, как работает exec
- Текущий образ процесса заменяется новым образом процесса.
- Новый образ процесса — это то, что вы передаете как exec
- Завершить текущий процесс
- Новый образ процесса имеет PID, ту же среду и некоторые файловые дескрипторы (поскольку процесс не заменяется, просто образ процесса)
- Это влияет на состояние ЦП и виртуальную память, а карта виртуальной памяти текущего образа процесса заменяется виртуальной памятью нового образа процесса.
waitpid
Подождите, пока дочерний процесс завершится или завершится
exit
Во многих компьютерных операционных системах компьютерные процессы завершаются выполнениемexit
Выполняется командой системного вызова. 0 означает, что процесс завершился нормально, другие значения означают, что процесс завершился ненормально.
Ниже приведены некоторые другие распространенные системные вызовы.
инструкция системного вызова | описывать |
---|---|
pause | ожидающий сигнал |
nice | Изменить приоритет процесса разделения времени |
ptrace | трассировка процесса |
kill | отправить сигнал процессу |
pipe | Создать конвейер |
mkfifo | Создайте специальный файл для fifo (named pipe) |
sigaction | Установите метод обработки для указанного сигнала |
msgctl | операция управления сообщениями |
semctl | Управление семафором |
Реализация процессов и потоков Linux
Линукс процесс
Процессы Linux похожи на айсберг, то, что вы видите, — это только верхушка айсберга.
В структуре ядра Linux процесс представлен как任务
, через структуруstructure
создавать. В отличие от других операционных систем, в которых различают процессы, упрощенные процессы и потоки, Linux единообразно использует структуры задач для представления контекстов выполнения. Таким образом, для каждого однопоточного процесса однопоточный процесс будет представлен структурой задачи, а для многопоточного процесса каждому потоку пользовательского уровня будет назначена структура задачи. Ядро Linux является многопоточным, и потоки уровня ядра не связаны ни с какими потоками пользовательского уровня.
Для каждого процесса будетtask_struct
Ему соответствует дескриптор процесса. Дескриптор процесса содержит всю полезную информацию о процессе управления ядром, включаяПараметры планирования, дескрипторы открытых файлов и т. д.. Дескрипторы процесса существуют в стеке ядра с момента создания процесса.
Linux, как и Unix, используетPID
Чтобы различать разные процессы, ядро будет составлять структуру задач всех процессов в двусвязный список. PID можно напрямую сопоставить с адресом структуры задачи, называемой процессом, так что нет необходимости просматривать двусвязный список для прямого доступа.
Мы упомянули дескриптор процесса выше, что является очень важным понятием.Мы также упомянули, что дескриптор процесса находится в памяти.Здесь мы опускаем предложение, то есть дескриптор процесса хранится в структуре задачи пользователя.Дескриптор процесса загружается в память только тогда, когда процесс находится в памяти и начинает работать.
进程位于内存
называетсяPIM(Process In Memory)
, что является проявлением архитектуры фон Неймана, программы, которые загружаются в память и выполняются, называются процессами. Проще говоря, процесс — это выполняемая программа.
Дескрипторы процессов можно разделить на следующие категории.
-
调度参数(scheduling parameters)
: Приоритет процесса, самое последнее время использования ЦП и самое последнее время сна вместе определяют следующий процесс для запуска. -
内存映像(memory image)
: Как мы сказали выше, образ процесса — это исполняемый файл, необходимый для выполнения программы, который состоит из данных и кода. -
信号(signals)
: показывает, какие сигналы перехватываются, а какие исполняются. -
寄存器
: при возникновении ловушки ядра содержимое регистров будет сохранено. -
系统调用状态(system call state)
: Информация о текущем системном вызове, включая параметры и результаты. -
文件描述符表(file descriptor table)
: Когда вызывается система в файловом дескрипторе, файловый дескриптор используется в качестве индекса для поиска структуры данных i-узла связанного файла в таблице файловых дескрипторов. -
统计数据(accounting)
: Указатель для записи расписания ЦП, занятого пользователем и процессом, некоторые операционные системы также сохраняют наибольшее время ЦП, занятое процессом, максимальное пространство стека, которое имеет процесс, количество страниц, которые может использовать процесс, и т. д. -
内核堆栈(kernel stack)
: фиксированный стек, который может использоваться ядром процесса. -
其他
: текущее состояние процесса, время ожидания события, тайм-аут для предупреждений о расстоянии, PID, PID родительского процесса, идентификатор пользователя и т. д.
Вооружившись приведенной выше информацией, теперь легко описать, как эти процессы создаются в Linux, а создать новый процесс на самом деле довольно просто.Откройте новый дескриптор процесса пользовательского пространства для дочернего процесса, а затем скопируйте большое количество содержимого из родительского процесса. Назначьте PID этому дочернему процессу, настройте его карту памяти, дайте ему доступ к родительским файлам, зарегистрируйтесь и запустите.
При выполнении системного вызова fork вызывающий процесс попадет в ядро и создаст некоторые структуры данных, связанные с задачей, такие как内核堆栈(kernel stack)
иthread_info
структура.
О структуре thread_info можно обратиться к
Эта структура содержит дескриптор процесса, который находится в фиксированном месте, так что система Linux может найти структуру данных запущенного процесса с небольшими затратами.
Основное содержание дескриптора процесса основано на父进程
дескриптор для заполнения. Операционная система Linux будет искать доступный PID, и этот PID не используется ни одним процессом, просто обновите идентификатор процесса, чтобы он указывал на новую структуру данных. Чтобы уменьшить коллизию хеш-таблицы, будет сформирован дескриптор процесса链表
. Он также устанавливает поля task_struct так, чтобы они указывали на соответствующий предыдущий/следующий процесс в массиве задач.
task_struct : дескриптор процесса Linux, который включает в себя много исходного кода C++, мы объясним его позже.
В принципе, открыть область памяти для дочернего процесса, выделить сегмент данных и сегмент стека для дочернего процесса и скопировать содержимое родительского процесса, но на самом деле, после завершения форка, дочерний процесс и родительский процесс не использует общую память, поэтому для достижения синхронизации необходимо скопировать технологию, но накладные расходы на репликацию относительно велики, поэтому операционная система Linux использует欺骗
Способ. То есть таблица страниц выделяется для дочернего процесса, а затем вновь выделенная таблица страниц указывает на страницы родительского процесса, и эти страницы доступны только для чтения. Когда процесс записывает данные на эти страницы, включается сбой защиты. После того, как ядро обнаружит операцию записи, оно выделит копию процессу, так что данные будут копироваться в эту копию при записи.Эта копия является общей.Этот метод называется写入时复制(copy on write)
, этот метод позволяет избежать необходимости поддерживать две копии в одной и той же области памяти и экономит место в памяти.
После запуска дочернего процесса операционная система вызовет системный вызов exec, ядро выполнит поиск и проверку исполняемого файла, скопирует параметры и переменные среды в ядро и освободит старое адресное пространство.
Теперь необходимо создать и заполнить новые адресные пространства. Если система поддерживает сопоставленные файлы, как в системах Unix, то создаются новые таблицы страниц, указывающие на отсутствие страниц в памяти, если только используемые страницы не являются страницами стека, адресное пространство которых поддерживается исполняемым файлом на диске. Когда новый процесс запускается, он немедленно получает缺页异常(page fault)
, который загружает страницу с кодом в память. Наконец, параметры и переменные среды копируются в новый стек, сигналы сбрасываются, а все регистры очищаются. Новая команда начинает выполняться.
Вот пример: пользователь выводит ls, оболочка вызывает функцию fork для копирования нового процесса, а процесс оболочки вызывает функцию exec для перезаписи своей памяти содержимым исполняемого файла ls.
потоки Linux
Теперь давайте поговорим о потоках в Linux.Потоки — это легковесные процессы.Вы, должно быть, много раз слышали эту фразу.轻量级
Это отражается в том факте, что при переключении процессов необходимо очистить все таблицы и общую информацию между процессами.Вообще говоря, через каналы или общую память, если это процесс родитель-потомок после функции fork, используется общий файл.Однако , переключение потоков не должно быть таким же, как у процессов. Существует два типа потоков: потоки уровня пользователя и потоки уровня ядра.
Поток уровня пользователя
Потоки уровня пользователя избегают использования ядра, как правило, каждый поток отображает переключение вызова, отправляет сигнал или выполняет какую-либо операцию переключения, чтобы освободить ЦП, также таймеры могут принудительно переключать, а пользовательские потоки обычно переключаются намного быстрее. чем потоки ядра. Проблема с реализацией потоков на пользовательском уровне заключается в том, что один поток может монополизировать квант времени процессора, препятствуя выполнению других потоков и, таким образом,饿死
. Если вы выполняете операцию ввода-вывода, блоки ввода-вывода и другие потоки не могут работать.
Одно из решений заключается в том, что эту проблему решает какой-нибудь пакет потоков на уровне пользователя. Эксклюзивным первым временным интервалом можно управлять с помощью монитора тактового цикла. Затем некоторые библиотеки решают проблему блокировки ввода-вывода системных вызовов через специальные обертки или могут писать задачи для неблокирующего ввода-вывода.
потоки уровня ядра
Потоки уровня ядра обычно реализуются в ядре с использованием нескольких таблиц процессов, по одной для каждой задачи. В этом случае ядро планирует каждый поток в пределах временного интервала каждого процесса.
Все вызовы, которые могут быть заблокированы, будут реализованы через системные вызовы.Когда поток заблокирован, ядро может выбрать, запускать ли другой поток в том же процессе (если есть готовый поток) или запускать в другом процессе.
Накладные расходы из пользовательского пространства -> пространство ядра -> пользовательское пространство относительно велики, но потеря времени на инициализацию потока незначительна. Преимущество этой реализации заключается в том, что время переключения потоков определяется часами, поэтому вероятность привязки временного интервала ко времени, затрачиваемому другими потоками в задаче, меньше. Кроме того, блокировка ввода-вывода не является проблемой.
Гибридная реализация
Сочетая преимущества пользовательского пространства и пространства ядра, разработчики приняли内核级线程
метод, а затем мультиплексировать потоки пользовательского уровня с некоторыми или всеми потоками ядра.
В этой модели программисты могут свободно управлять количеством пользовательских потоков и потоков ядра с большой гибкостью. При таком подходе ядро идентифицирует и планирует только потоки уровня ядра. Некоторые из этих потоков уровня ядра мультиплексируются несколькими потоками уровня пользователя.
Linux-расписание
Давайте сосредоточимся на алгоритме планирования системы Linux.Прежде всего, нам нужно понять, что потоки системы Linux являются потоками ядра, поэтому система Linux основана на потоках, а не на процессах.
В целях планирования система Linux делит потоки на три категории.
- ФИФО в реальном времени
- опрос в реальном времени
- совместное времяпровождение
Поток FIFO реального времени имеет наивысший приоритет, и он не будет вытеснен другими потоками, если только это не только что готовая запись с потоком с более высоким приоритетом. Поток ротации в реальном времени в основном такой же, как поток FIFO в реальном времени, за исключением того, что каждый поток ротации в реальном времени имеет количество времени, которое может быть вытеснено по истечении времени. Если готовы несколько потоков реального времени, каждый поток выполняется в течение времени, заданного его количеством времени, а затем вставляется в конец потока циклического перебора в реальном времени.
Обратите внимание, что это реальное время является только относительным и не может быть абсолютно реальным, потому что время выполнения потока не может быть определено. Они больше работают в режиме реального времени, чем системы с разделением времени.
Системы Linux назначают каждому потокуnice
значение, это значение представляет концепцию приоритета. Значение по умолчанию nice value равно 0, но его можно изменить с помощью системного вызова nice value. Диапазон измененных значений от -20 до +19. Значение nice определяет статический приоритет потока. Значение nice общего системного администратора будет выше, чем приоритет общего потока, и его диапазон составляет от -20 до -1.
Ниже мы более подробно обсудим два алгоритма планирования системы Linux, их внутреннюю и调度队列(runqueue)
Дизайн очень похож. Очередь выполнения имеет структуру данных, которая отслеживает все выполняемые задачи в системе и выбирает следующую, которая может быть запущена. Каждая очередь выполнения связана с каждым процессором в системе.
Linux O(1)
Планировщик — исторически популярный планировщик. Название происходит от его способности выполнять планирование задач в постоянное время. В планировщике O(1) очередь планирования организована в два массива, один для задач.активенмассив , одна задачаистекшиймассив . Как показано на рисунке ниже, каждый массив содержит 140 заголовков связанных списков, и каждый заголовок связанного списка имеет различный приоритет.
Общий процесс выглядит следующим образом:
Планировщик выбирает задачу с наивысшим приоритетом из активного массива. Если квант времени этой задачи истекает, он перемещается в массив expired и expired. Если задача заблокирована, скажем, в ожидании события ввода-вывода, то, как только операция ввода-вывода завершится до истечения ее кванта времени, задача будет продолжать выполняться и будет помещена обратно в ранее активное В массиве, поскольку эта задача ранее использовала часть кванта времени ЦП, она будет выполнять оставшуюся часть кванта времени. Когда задача исчерпает свой квант времени, она помещается в массив аннулирования с истекшим сроком действия. Как только в активном массиве задач не будет других задач, планировщик изменит указатели, в результате чего активный массив станет массивом с истекшим сроком действия, а массив с истекшим сроком действия станет активным массивом. Использование этого метода может гарантировать, что каждая приоритетная задача может быть выполнена без остановки потока.
В этом методе планирования задачи с разным приоритетом получают разные кванты времени от ЦП.Процессы с высоким приоритетом часто получают более длительные кванты времени, а задачи с низким приоритетом получают меньшие кванты времени.
Таким образом, чтобы обеспечить лучший сервис, обычно交互式进程
Интерактивный процесс имеет более высокий приоритет.用户进程
.
Система Linux не знает, является ли задача интенсивным вводом-выводом или интенсивным процессором, это просто зависит от интерактивного способа, система Linux отличит, является ли это интенсивным.静态优先级
все еще动态优先级
. Динамический приоритет заключается в использовании системы вознаграждения для достижения. Поощрения двумя способами:Вознаграждайте интерактивные потоки, наказывайте потоки, нагружающие ЦП. В планировщике Linux O(1) награда за наивысший приоритет равна -5.Обратите внимание, что чем ниже приоритет, тем легче его принять планировщику потоков, поэтому наивысший приоритет штрафа равен +5. Конкретный вариант осуществления заключается в том, что операционная система поддерживаетsleep_avg
Когда задача просыпается, она увеличивает значение переменной sleep_avg, а когда задача вытесняется или время истекает, значение этой переменной будет уменьшаться, что отражается в механизме вознаграждения.
Алгоритм планирования O(1) — это планировщик для версии ядра 2.6, который изначально был представлен в нестабильной версии 2.5. Ранние алгоритмы планирования в многопроцессорных средах продемонстрировали, что решения о планировании можно принимать, обращаясь к активным массивам. Позволяет планированию завершиться за постоянное время O(1).
Планировщик O(1) использует启发式
Кстати, что это значит?
В компьютерных науках эвристика — это способ быстро решить проблему, когда традиционные методы медленны, или найти приблизительное решение, когда традиционные методы не могут найти никакого точного решения.
O(1) Использование эвристики таким образом делает приоритизацию задач сложной и несовершенной, что приводит к снижению производительности при работе с интерактивными задачами.
Чтобы исправить этот недостаток, разработчики планировщика O(1) предложили новое решение, а именно公平调度器(Completely Fair Scheduler, CFS)
. Основная идея CFS заключается в использовании红黑树
как очередь отправки.
Структуры данных слишком важны.
CFS упорядочивает задачи в упорядоченном дереве в зависимости от того, как долго они выполнялись на ЦП, вплоть до наносекунд. Ниже приведена модель построения CFS.
Процесс планирования CFS выглядит следующим образом:
Алгоритм CFS всегда отдает приоритет задачам, которые используют наименьшее время процессора. Самые маленькие задачи обычно находятся в крайнем левом положении. Когда необходимо запустить новую задачу, CFS сравнит задачу с крайним левым значением, если задача имеет минимальное значение времени, то она будет запущена, в противном случае она сравнит и найдет подходящую позицию для вставки. Затем ЦП запускает самую левую задачу на красно-черном дереве для текущего сравнения.
Время выбора узла в красно-черном дереве для запуска может быть постоянным, но время вставки задачи равноO(loog(N))
, где N — количество задач в системе. Это приемлемо, учитывая текущий уровень загрузки системы.
Планировщик должен учитывать только выполняемые задачи. Эти задачи помещаются в соответствующую очередь планирования. Невыполнимые задачи и задачи, ожидающие различных операций ввода-вывода или событий ядра, помещаются в等待队列
середина. Голова очереди ожидания содержит указатель на связанный список задач и спин-блокировку. Спин-блокировки очень полезны в сценариях параллельной обработки.
Синхронизация в системах Linux
Поговорим о механизме синхронизации в Linux. Ранние ядра Linux имели только один大内核锁(Big Kernel Lock,BKL)
. Он блокирует возможность одновременной обработки разными процессорами. Следовательно, необходимо ввести некоторые более тонкие механизмы блокировки.
Linux предоставляет несколько различных типов переменных синхронизации, которые можно использовать как в ядре, так и в пользовательских приложениях. В слоях Linux используетatomic_set
иatomic_read
Такие операции обеспечивают инкапсуляцию атомарных инструкций, поддерживаемых аппаратным обеспечением. Аппаратное обеспечение обеспечивает переупорядочивание памяти, что является механизмом барьеров Linux.
Описание с высоким уровнем синхронизации похоже на спин-блокировку: когда два процесса обращаются к ресурсам одновременно, после того, как один процесс получает ресурс, другой процесс не хочет быть заблокированным, поэтому он будет вращаться и ждать некоторое время. , доступ к ресурсам. Linux также предоставляет такие механизмы, как мьютексы или семафоры, а также поддерживает такие вещи, какmutex_tryLock
иmutex_tryWait
Такой неблокирующий звонок. Также поддерживаются транзакции обработки прерываний, в том числе путем динамического отключения и включения соответствующих прерываний.
загрузка Linux
Давайте поговорим о том, как запускается Linux.
Когда компьютер включен,BIOS
будет осуществляться开机自检(Power-On-Self-Test, POST)
, для обнаружения и инициализации оборудования. Потому что при запуске операционной системы будут использоваться диск, экран, клавиатура, мышь и другие устройства. Затем первый раздел на диске, также известный какMBR(Master Boot Record)
Основная загрузочная запись, которая считывается в фиксированную область памяти и выполняется. В этом разделе очень маленькая программа, всего 512 байт. Программа вызывает загрузочную независимую программу с диска, и загрузочная программа копирует себя в память старшего адреса, чтобы освободить память младшего адреса для операционной системы.
После завершения копирования загрузочная программа считывает корневой каталог загрузочного устройства. Программа загрузки понимает файловую систему и формат каталога. Затем загрузочная программа вызывается в ядре, передавая управление ядру. До сих пор загрузка делала свою работу. Ядро системы начинает работать.
Используется код запуска ядра汇编语言
Завершено, в основном включая создание стека ядра, идентификацию типа ЦП, вычисление памяти, отключение прерываний, запуск модуля управления памятью и т. д., а затем вызов основной функции языка C для выполнения части операционной системы.
Эта часть тоже будет делать много чего: во-первых, будет выделен буфер сообщений для хранения отладочных задач, и в буфер будет записываться отладочная информация. При возникновении ошибок отладки эта информация может быть вызвана диагностической программой.
Затем операционная система автоматически настроит, обнаружит устройство и загрузит файл конфигурации.Если обнаруженное устройство ответит, оно будет добавлено в таблицу связанных устройств.Если ответа нет, оно будет классифицировано как неподключенное и напрямую проигнорировано.
Когда все оборудование настроено, следующее, что нужно сделать, — это тщательно вручную создать процесс 0, настроить его стек и запустить его, выполнить инициализацию, настроить часы и смонтировать файловые системы. Создайтеinit 进程(进程 1 )
и守护进程(进程 2)
.
Процесс инициализации проверяет свои флаги, чтобы определить, является ли служба однопользовательской или многопользовательской. В первом случае он вызывает функцию fork для создания процесса оболочки и ожидает завершения процесса. В последнем случае функция fork вызывается для создания процесса, запускающего сценарий оболочки инициализации системы (т. е. /etc/rc), который может выполнять определение согласованности файловой системы, монтировать файловые системы и запускать процессы демона.
Затем процесс /etc/rc считывает данные из /etc/ttys, в которых перечислены все терминалы и атрибуты. Для каждого включенного терминала процесс вызывает функцию fork для создания своей копии, выполняет внутреннюю обработку и запускаетgetty
программа о.
Программа getty наберет на терминале
login:
Ожидание ввода пользователем имени пользователя, после ввода имени пользователя программа getty завершается, и программа входа в систему/bin/login
начать операцию. Программа входа требует пароль и хранится с/etc/passwd
Пароли сравниваются, и, если они введены правильно, программа входа в систему заменяет себя программой оболочки пользователя и ожидает первой команды. Если оно неверно, программа входа запрашивает другое имя пользователя.
Весь процесс запуска системы выглядит следующим образом