forked from mizabrik/acos-notes
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathproc.tex
More file actions
84 lines (71 loc) · 7.81 KB
/
proc.tex
File metadata and controls
84 lines (71 loc) · 7.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
\documentclass[main]{subfiles}
\begin{document}
Какие системные вызовы есть для работы с процессами?
Например, новый процесс порождается с помощью вызова clone, однако,
им практически никто не пользуется, а более распространён fork.
Более того, даже потоки порождаются с помощью clone (есть обёртка
в библиотеке pthread). У каждого процесса есть своя таблица
страниц и каталог страниц. При создании процесса что нужно сделать?
Вот, процесс занимал какую-то оперативную и какую-то внешнюю память.
С точки зрения программ, fork берёт всю оперативную память и копирует
её, создавая процесс-дубликат, который отличается только значением,
возвращённым этим системным вызовом (pid дочернего процесса у отца
и 0 у дочернего процесса). Насколько же дорогой этот вызов?
Давайте разбёмся, что нам вообще нужно сделать. У каждой программы есть
код, данные и стек. Код можно сохранить, а вот данные и стек нужно
скопировать, так как они будут меняться. Но копировать всё сразу --- очень
долго, а потому они будут скопированы только тогда, когда понадобится их
изменить. Изначально все страницы обоих процессов указывают на одну и ту
же физическую память, и они помечаются read-only. Тогда при попытке записи
происходит прерывание page fault, которое обрабатывается ОС. И вот тогда
физическая память копируется, а страница отмечается доступной для записи.
Такой механизм называется copy-on-write.
Помимо этого, конечно же, обновляется контекст процессов: обновляются списки
сыновей, создаётся новый pid и т. д.
\section{Потоки}
Поток --- другой способ парлеллизма, который имеет существенные отличия от
процесса, а потому зачастую таковым и не считается. Обыкновенно есть некоторый
главный поток, который порождает другие потоки, и все они существуют в пределах
одного процесса. Таким образом, вместо одного состояния RIP у нас появляется по
одному на каждый поток. Что примечательно, они всё равно планируются все вместе
(по умолчанию).
Что такое vfork? Идея в том, что мы можем не копировать страницы процесса. Это
можно делать, если вы уверены, что дочерний процесс никогда не будут исполнять
ваш код и изменять секцию данных. Это может быть полезно, если процесс нужен
только для запуска другого процесса через exec. Тогда родительский процесс
блокируется до этого вызова, а дочерний процесс не может изменять данные.
Однако, данная функция считается deprecated.
После порождения процесса мы можем заменить его данные на данные другого
исполняемого файла с помощью вызовов семейства exec. Расммотрим execve:
он принимает три аргумента.
Его первый аргумент --- путь к файлу, которым мы хотим заполнить наш
процесс. Второй аргумент --- char **argv, который получит процесс аргументом
для main. Третий аргумет char **env --- окружение, которое должен получить
наш процесс. Оба списка оканчиваются NULL.
Что происходит после вызова exec* в ОС? Во-первых, нужно избавится от
всей имеющейся памяти. Открытые файлы наследуются, если они не были
открыты с опцией CLOSEXEC (тогда они не будут доступны новому процессу).
Кроме того, происходит много чего ранее не упомянутого.
Самое важное --- это полная смена сегмента кода и данных на то, что
указано в указанном исполнямом файле. За то, что делать с этим файлом
отвечает загрузчик (не путать с загрузчиком ОС!). В UNIX этот загрузчик читает
начало файла. Если там будет встречена последовательность байт shebang
(\#! в ASCII), то будет запущен исполняемый файл указанный в этой строке,
которому будут переданы аргументами имя изначального файла, а потом --- его
аргументы. Обычно же там будет находится заголовок ELF, который будет содержать
описание секций и их расположение в исполнеяемом файле, точку входа и т. д.
Тут, кстати, тоже дейтсвует принцип ленивости: весь файл не будет загружен сразу.
Есть такая штука, как mmap. Благодаря этой функции можно отобразить содержимое файла
без такого радикального метода, как exec.
А что будет с правами на файлы? Во-первых, вызов exec* проверит наличие права
на исполнение у файла. Кроме того, если установлен suid-бит, то euid (или egid)
нового процесса будет установлен в uid или gid владельцев файла.
Лектор замечает, что в современных UNIX-системах это запрещено для файлов,
владелец которых --- не root, из соображений безопасности.
Зачастую, после форка захочется узнать, как завершился наш дочерний
процесс. Для этого используются вызовы wait и waitpid.
Они позволят получить код возврата, указанный системным вызовом \_exit
(не путать с библиотечной функцией exit).
Процесс можно убить, отправив ему соответствующий сигнал с помощью
kill, например.
\end{document}