Системний виклик Linux у деталях
Системний виклик - це механізм в Linux, який дозволяє додаткам простору користувача з'єднуватися з ядром, яке також є компонентом ядра операційної системи. Додаток простору користувача має надіслати системний виклик ядру, яке використовується для виконання привілейованих функцій від його імені, включаючи запис або читання інформації у файл або початок нового процесу. У цій статті ми детально обговоримо системний виклик Linux, а також різні його типи.
Як працюють системні дзвінки Linux?
Системні виклики виконуються в ядрі, як і програми споживчого простору, до яких можуть отримати доступ через стандартні бібліотечні функції бібліотеки C. Серед них open(), read(), write(), close(), fork(), exec() і безліч інших функцій.
У більшості випадків програма користувальницького простору об'єднує набір суперечок, таких як URL-адреса файлу, що відкривається, і переміщає їх у відповідну типову бібліотеку для виконання системного виклику. Бібліотека працює, а потім організує всі аргументи так, щоб їх можна було передати в ядро, зазвичай шляхом їхньої реплікації в систему, яку називають таблицею, — спеціальну буферизацію в пам'яті.
Бібліотечна функція викликає пастку, коли її параметри знаходяться в таблиці системних викликів, і це переміщає команду з простору користувача в ядро. Потім ядро виконує запитану дію, використовуючи аргументи таблиці системних викликів, і перетворює результат використання користувальницького простору через значення обміну виклику платформи.
Системні виклики є важливими компонентами операційної системи Linux, оскільки вони дозволяють програмам простору користувача виконувати привілейовані дії контрольованим і безпечним чином. Він також надає встановлений інтерфейс для отримання доступу до продуктивності ядра, що полегшує використання, а також забезпечує сумісність системи та безшовну інтеграцію.
Типи системних викликів у Linux
Системні виклики в Linux поділяються на п'ять типів в залежності від операції, яку вони виконують. Це категорії.
- Системні виклики управління процесами. Ці системні виклики використовуються для управління процесами, наприклад запуску нових, зупинки існуючих та очікування їх завершення. Fork(), exec(), wait() і exit() — це приклади системних викликів управління процесами(). fork() — цей системний виклик дублює процес, що викликає, для створення нового процесу. Дочірній процес — це новий процес, який запускає ту саму програму, як і батьківський процес. exec() — системний виклик exec() використовується для заміни поточного способу процесу новим чином процесу. Він завантажує зазначену програму на згадку і виконує її. wait() – батьківський процес використовує цей системний виклик для очікування завершення дочірнього процесу. Батьківський процес зупиняється до завершення дочірнього процесу. exit() — поточний процес завершується, і вихідний процес повертає код стану.
- Виклики системи керування файлами. Ці виклики системи використовуються для відкриття, читання, запису та закриття документів, а також для їх створення, перейменування та видалення. Деякі виклики системи управління файлами () - open (), read (), write (), close (), mkdir () та rmdir (). open() - Розширює файл і повертається до дескриптора файлу (ціло число, що ідентифікує відкритий файл). read() — наведений вище системний виклик перевіряє дані з відкритого файлу буфер пам'яті. write() — це внутрішній дзвінок, який використовується для запису інформації, що зберігається у буфері пам'яті, у відкритий файл. close() - Це системний виклик для закриття файлу, який визначається його описовим терміном.
- Системні виклики керування пристроямиЦі системні дзвінки використовуються для керування пристроями введення-виводу, наприклад читання та запису в них, установки атрибутів пристрою та керування операціями пристрою Системні дзвінки для керування пристроями включають read(), write(), ioctl() та select(). read() — цей дзвінок зчитує дані з пристрою введення в буфер пам'яті. write() — це системний виклик, який використовується для запису даних із буфера пам'яті на пристрій виводу. ioctl() — Цей дзвінок керує поведінкою пристрою, встановлюючи або отримуючи атрибути пристрою. select() – цей дзвінок використовується для очікування завершення операцій введення-виводу на кількох пристроях.
- Системні виклики керування мережею. Ці системні дзвінки використовуються для керування мережними ресурсами, такими як підключення та відключення від мереж, відправлення та отримання даних по мережах, а також дозвіл мережевих адрес. управління мережею(). socket() — він встановлює сокет, і це кінцева точка мережі речей, яку можна використовуватиме зв'язку. connect() – це внутрішній виклик, який використовується для встановлення з'єднання з кінцевою точкою зовнішньої мережі. send() — дзвінок програми, що використовується для передачі даних через з'єднання комп'ютера.
Висновок
Системні виклики – життєво важливий компонент Linux та інших операційних систем, що дозволяє споживчим програмам використовувати функціональні можливості ядра через стандартизований інтерфейс. Системні виклики поділяються на п'ять категорій залежно від типу операцій, що виконуються ними: управління процесами, управління файлами, управління пристроями, управління мережею і системна інформація. Одна група системних викликів містить набір операцій, які дозволяють програмам споживчого простору виконувати певні види операцій в операційній системі, що знаходиться під ними. Системні виклики сприяють сумісності та взаємодії додатків та операційних систем, пропонуючи певний інтерфейс для доступу до використовуваних функцій ядра.
Статті з цієї тематики:
- Пояснення структури файлової системи Linux
- 5 команд Linux для швидкого пошуку необхідної вам системної інформації
- Як зашифрувати існуючу систему Ubuntu 24.04
- Бета-версія Fedora 40 - найшвидша операційна система, яку я тестував, і вона сповнена корисних функцій.
- Як стиснути, перемістити або розширити системний розділ Ubuntu 24.04
- GNOME має новий індикатор завантаження системи (ЦП, ОЗП тощо) на верхній панелі
- Включити елементи керування медіаплеєром у меню на панелі завдань в Ubuntu 18.04
- Stacer — оптимізатор системи/очисник/монітор для робочого столу Ubuntu
- Додайте опцію системного меню «Вимкнути Wi-Fi» в Ubuntu 20.04
- Додайте пропозиції Google до системного пошуку в Ubuntu 20.04 через розширення
- Як зашифрувати весь системний диск під час встановлення Ubuntu 20.04, 21.04
- Як отримати системну панель у стилі Windows 11 в Ubuntu 20.04, 21.04
- Замініть «Дії» меню з логотипом системи у Gnome (Ubuntu, Fedora, CentOS)
- Виберіть "Аудіопристрої" в меню панелі завдань Ubuntu через розширення.
- Випущено Firefox 94.0 з опцією «Вивантажити» для звільнення системного ресурсу
Пояснення системних викликів fork, exec, wait та exit у Linux
Послідовність інструкцій та даних, які можуть виконуватись один раз, кілька разів або одночасно, називаються програмами. І процес є виконання таких програм. Таким чином, ці процеси можуть запускати безліч програм. В тому самому процесі операційна система може завантажувати різні програми. Повторно використовувані стани процесів, такі як поточні каталоги, привілеї, дескриптори файлів тощо. д., успадковуються новими програмами. Такі речі виконуються на тому ж рівні, що й системні виклики, такі як fork(), exec(), wait() та exit().
У цій статті ми збираємося детально обговорити системні виклики Linux fork(), exec(), wait() та exit() з прикладами та варіантами використання.
вилка()
fork() — це один із системних викликів, який дуже специфічний та корисний у системах Linux/Unix. Він використовується процесами створення процесів, які є копіями самих себе. За допомогою таких системних викликів дочірній процес може бути створений батьківським процесом. Поки дочірній процес не буде повністю виконаний, батьківський процес припиняється.
Деякі з важливих моментів fork() полягають в наступному.
- Батьківський процес отримає ідентифікатор дочірнього процесу з ненульовим значенням.
- Нульове значення повертається дочірньому елементу.
- Якщо при створенні дочірнього елемента виникнуть будь-які системні або апаратні помилки, fork() повертається -1.
- Оскільки унікальний ідентифікатор процесу, отриманий дочірнім процесом, відповідає ідентифікатору будь-якої існуючої групи процесів.
Щоб докладніше розповісти про fork(), розглянемо приклад, який пояснює концепцію fork().
Ось код для його копіювання/вставки:
#include #include #include #include int main(int argc, char **argv) < pid_t pid; pid = fork (); if(pid==0) < printf("It is the child process and pid is %d\n",getpid()); exit(0); >else if(pid > 0) < printf("It is the parent process and pid is %d\n",getpid()); >else < printf("Error while forking\n"); exit(EXIT_FAILURE); >return 0; >
Вихід:
Запустивши скрипт, ми отримаємо результат, як показано на скріншоті нижче.
виконавець()
Exec() – це такий системний виклик, який виконується шляхом заміни поточного образу процесу новим чином процесу. Однак вихідний процес залишається новим процесом, але новий процес замінює головні дані, дані стека тощо. буд. Він запускає програму з точки входу, завантажуючи програму у поточний простір процесу.
Щоб уточнити більше, давайте візьмемо приклад, наведений нижче.
#include #include #include #include #include main(void) < pid_t pid = 0; int status; pid = fork (); if (pid == 0) < printf("I am the child."); execl("/bin/ls", "ls", "-l", "/home/ubuntu/", (char *) 0); perror("In exec(): "); >if (pid > 0) < printf("I am the parent, and the child is %d.\n", pid); pid = wait(&status); printf("End of process %d: ", pid); if (WIFEXITED(status)) < printf("The process ended with exit(%d).\n", WEXITSTATUS(status)); >if (WIFSIGNALED(status)) < printf("The process ended with kill -%d.\n", WTERMSIG(status)); >> if (pid < 0) < perror("In fork():"); >exit(0); >
Вихід:
Запустивши скрипт, ми отримаємо результат, як показано на скріншоті нижче.
чекати()
Як і у випадку з розгалуженням, дочірні процеси створюються та виконуються, але батьківський процес припиняється доти, доки не виконається дочірній процес. У цьому випадку системний виклик wait() активується автоматично через призупинення батьківського процесу. Після того, як дочірній процес завершує виконання, батьківський процес знову отримує керування.
Щоб докладніше розповісти про wait(), розглянемо приклад, що пояснює системний виклик wait().
#include // printf() #include // exit() #include // pid_t #include // wait() #include // fork int main(int argc, char **argv) < pid_t pid; pid = fork (); if(pid==0) < printf("It is the child process and pid is %d\n",getpid()); int i=0; for(i=0;i<8;i++) < printf("%d\n",i); >exit(0); > else if(pid > 0) < printf("It is the parent process and pid is %d\n",getpid()); int status; wait(&status); printf("Child is reaped\n"); >else < printf("Error in forking..\n"); exit(EXIT_FAILURE); >return 0; >
Вихід:
Запустивши скрипт, ми отримаємо результат, як показано на скріншоті нижче.
Вихід()
Exit() — це така функція або один із системних викликів, який використовується для завершення процесу. Цей системний виклик визначає, що виконання потоку завершено, особливо у разі багатопотокового середовища. Для подальшого використання фіксується стан процесу.
Після використання системного виклику exit() всі ресурси, що використовуються в процесі, отримуються операційною системою, а потім завершують процес. Системний виклик Exit() еквівалентний виходу().
Короткий опис
#include void _exit(int status); #include void _Exit(int status);
Ви можете побачити використання функції exit() у наведених вище прикладах fork(), wait().Використання системного виклику exit() призначене для завершення процесу.
Висновок
У цій статті ми детально вивчили системні виклики fork(), exec(), wait() та exit() з деякими прикладами. Для отримання більш детальної інформації спробуйте запустити програми за допомогою цих системних дзвінків та перегляньте результат. Дякую!
Статті з цієї тематики:
- 5 причин, чому MacOS і Linux йдуть рука об руку, як ніж та вилка
- Strawberry - музичний плеєр та органайзер вилка Clementine
- SUSE випустить форк Red Hat Enterprise Linux
- Погляд - форк GIMP
- NayuOS: форк Chrome OS для розробників
- LibreWolf: надійна вилка Firefox з відкритим вихідним кодом для Linux
- Представляємо Freenginx, відгалуження найпопулярнішого веб-сервера
- Як встановити Fork CMS на Ubuntu 20.04
- Як встановити Fork CMS на Ubuntu Server 22.04
- Встановіть нову вилку LibreNMS із Observium
- Запобігання форк-бомбам Bash в Linux
- Команди «DNF» (вилка Yum) для керування пакетами RPM у Linux
- Хтось перетворив GIMP на Glimpse, тому що Gimp — образливе слово
- Розуміння вилочної бомби :()< :|:& >;: в Linux
- Розуміння форк-бомби :()< :|:& >;: в Linux
§ 9. Системні виклики
01 Незважаючи на те, що ядро операційної системи та додаток розташовуються в одній і тій же фізичній пам'яті, в програмах використовуються логічні адреси, а значить прямого доступу до пам'яті ядра програма отримати не може. Для того щоб здійснити взаємодію між ядром та додатком використовуються так звані системні виклики - Спеціальні функції, призначені для виконання дій, що вимагають особливих привілеїв.Раніше для здійснення системних викликів використовувалися переривання, в сучасних процесорах з міркувань ефективності для цього використовуються спеціальні інструкції. Виконуючи таку інструкцію, процесор переходить у привілейований режим і віддає керування ядру, яке виконує інструкцію, зчитуючи її номер та аргументи з регістрів.
Як програми взаємодіють із ядром?
02 Сучасні програми не роблять системні виклики безпосередньо, натомість використовуються функції-обгортки мовою Сі. Є й функція-обгортка, яка підходить для будь-якого системного виклику, вона називається syscall. Першим аргументом цієї функції є номер системного виклику, інші аргументи в кожного системного виклику свої. Отримані аргументи передаються до допоміжної функції, написаної на асемблері, в якій здійснюється системний виклик. Праворуч на зображенні показана така функція для архітектури x86. Для інших архітектур функція буде відрізнятися. В даному випадку все зводиться до маніпуляцій з регістрами та виклику однойменної інструкції syscall. Після завершення системного виклику, функція syscall перевіряє вихідне значення, і якщо воно менше нуля, то зберігає це значення у глобальній змінній errno та повертає -1, інакше повертає 0 (тобто завершується успішно).
long
syscall(long
n,
. )
va_list
ap;
// від 0 до 6 аргументів
long
a,b,c,d,e,f;
va_start(ap,
n);
a=va_arg(ap,
long);
.
va_end(ap);
return
__syscall_ret(__syscall(n,a,b,c,d,e,f));
>
long
__syscall_ret(unsigned
long
r)
if
(r
>
-4096UL)
// (-4096,0)
errno
=
-r;
return
-1;
>
return
r;
>
.global
__syscall
.hidden
__syscall
.type
__syscall,@function
__syscall:
movq
%rdi,%rax
movq
%rsi,%rdi
movq
%rdx,%rsi
movq
%rcx,%rdx
movq
%r8,%r10
movq
%r9,%r8
movq
8(%rsp),%r9
syscall
ret
03 Процесори різних архітектур мають різні способи здійснення системних викликів, а також різні способи передачі аргументів для цих системних викликів, через що для кожного системного виклику пишуться функції-обгортки. Бібліотека з такими функціями називається libc. Крім обгорток вона повністю включає стандартну бібліотеку мови Сі, а також деякі додаткові функції, які спрощують роботу з системою. Ця бібліотека підключається до всіх програм, що збираються на Linux, незалежно від мови, якою вони написані.
04 Існує кілька реалізацій бібліотеки libc, які представлені на малюнку нижче. Основною реалізацією вважається glibc — бібліотека, яка підтримується розробниками ядра і яка завжди першою отримує оновлення. Оскільки glibc досить стара і оскільки розробники ядра дуже трепетно ставляться до зворотної сумісності, вона використовує багато пам'яті. Альтернативні бібліотеки позбавлені цього недоліку. Найбільш популярною з альтернатив є бібліотека musl, вона часто використовується в образах для Docker-контейнерів через свої скромні розміри та малого споживання пам'яті. Бібліотеки uclibc та dietlibc використовуються у вбудовуваних системах, вони споживають ще менше пам'яті, але використовують простіші та повільніші алгоритми (наприклад, алгоритм сортування).
musl, glibc, uclibc, dietlibc
Ядро+стандартні бібліотеки+програма.
Як ядро взаємодіє із програмами?
05 Ми з вами поговорили про те, як програми можуть взаємодіяти з ядром. Перефразовуючи програми дивляться на ядро через призму системних викликів.А як ядро дивиться на програми? Для ядра всі програми, будь то системна утиліта, на кшталт команди ls, або Docker-контейнер, є процесами. Процес - це одиниця планування ресурсів в Linux.
06 Процеси об'єднуються в строгу ієрархію, завжди має топологію дерева. Перший процес в ієрархії завжди має номер 1 і створюється ядром після завершення ініціалізації. резидентних процесів, монтування мережевих каталогів та інше. Також, дочірні процеси, батьки яких завершилися раніше (не зачекавши завершення нащадків), стають дочірніми процесами першого процесу.
07 Процеси створюються за допомогою функції-обертки fork . Ця функція в якомусь сенсі унікальна: у неї немає аргументів, зате вона повертається два рази в перший дочірній процес, і в даному випадку повертається значення нулю. процес, і в даному випадку значення, що повертається, дорівнює номеру дочірнього процесу. в тому, що системний виклик, що здійснюється даною функцією, копіює поточний процес разом з усіма структурами ядра і всіма його сторінками пам'яті, включаючи стек, глобальні змінні та код програми, завантажений у пам'ять. . Це призводить до того, що в пам'яті виявляються два ідентичні процеси, розрізнити які можна лише, перевіривши вихідне значення даного системного. виклику.
int
pid
=
fork();
// Копіювання процесу
if
(pid
==
0)
// дочірній процес
>
// Батьківський процес
08 Може здатися, що копіювання всієї пам'яті поточного процесу займає багато часу. Раніше це справді було так, однак у сучасних операційних системах використовується копіювання під час записущо прискорює породження нових процесів. Я написав невеликий тест, який вимірює кількість часу, що витрачається для створення певної кількості процесів. У цьому тесті дочірні процеси одразу завершувалися. На створення 100 процесів система витратила трохи менше дев'яти мілісекунд.
Час породження процесів за допомогою системного виклику fork.
09 Для того, щоб вивчити те, як Linux створює процеси, нам знадобиться команда strace . Ця команда відстежує системні дзвінки, які робить вашу програму. Наприклад, щоб відстежити, які файли відкриває ваша програма, достатньо вказати у фільтрі системний виклик openat . Нижче наведено висновок для програми, яка нічого не робить. Як видно з висновку, програма відкриває тільки бібліотеки: бібліотеку libc і libstdc++.
$ strace -e openat ./empty-main # відстежити відкриття файлу openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC)
=
3 openat(AT_FDCWD, "/lib64/libstdc++.so.6", O_RDONLY|O_CLOEXEC)
=
3 openat(AT_FDCWD, "/lib64/libm.so.6", O_RDONLY|O_CLOEXEC)
=
3 openat(AT_FDCWD, "/lib64/libgcc_s.so.1", O_RDONLY|O_CLOEXEC)
=
3 openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC)
=
3
Відстеження системних дзвінків порожньої програми за допомогою команди strace.
10 Давайте тепер застосуємо strace , щоб відстежити, який системний виклик використовує функція обгортка fork . Це системний виклик clone. У нього передається три аргументи, найважливішим з яких є flags. Перші два прапори є допоміжними, а третій прапор означає сигнал, який буде надіслано батьківському процесу при завершенні дочірнього. Тут вказаний спеціально призначений для цього сигнал SIGCHLD. Системний дзвінок повертає номер створеного процесу.
pid_t
pid
=
fork();
// Створення дочірнього процесу
if
(pid
==
0)
exit(0);
// Завершення дочірнього процесу
>
int
status
=
0;
wait(&status);
// зачекати на завершення дочірнього процесу
clone(
child_stack=NULL, flags=CLONE_CHILD_CLEARTID | CLONE_CHILD_SETTID | SIGCHLD, child_tidptr=0x7f25ede00990 )
=
7296
11 Тепер застосовуємо strace для відстеження системних викликів, які беруть участь у створенні потоку. І знову ми бачимо системний виклик clone, але з набагато більшою кількістю аргументів та прапорів. Крім прапорів, тут є покажчик на стек потоку. Стек – це область пам'яті, яка використовується потоком для локальних змінних (змінних, оголошених усередині функцій та методів). Також передається покажчик на thread local storage - область для зберігання унікальних для потоку змінних (ключове слово thread_local С++). Системний дзвінок також повертає номер дочірнього процесу (потоку).
| Прапор | Пояснення |
|---|
| CLONE_VM |
загальна пам'ять |
| CLONE_FS |
загальні робоча директорія, корінь ФС та маска |
| CLONE_FILES |
загальні файлові дескриптори |
| CLONE_SIGHAND |
загальні обробники сигналів |
| CLONE_SYSVSEM |
загальні примітиви синхронізації |
| CLONE_THREAD |
додавання до групи потоків |
| CLONE_SETTLS |
виділення локальної пам'яті потоку |
12 У таблиці перелічені прапори, що використовуються для створення потоків.Найважливішими є перші п'ять прапорів. Ці прапори фактично роблять так, що дочірній процес успадковує всі ресурси батьківського процесу, тобто може використовувати ту саму пам'ять, ті ж файлові дескриптори, обробники сигналів і примітиви синхронізації (м'ютекси, блокування, семафори). Інші прапори є допоміжними і пояснення не потребують.
std::thread
t
<[]()
<>>;
// створення дочірнього потоку
t.join();
// зачекати на завершення дочірнього потоку
clone(
child_stack=0x7f901f84cfb0, flags=CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID, parent_tidptr=0x7f901f84d9d0, tls=0x7f901f84d700, child_tidptr=0x7f901f84d9d0 )
=
7345
13 Нарешті, викликаємо функцію-обгортку clone безпосередньо. На цей раз виберемо прапори, які використовуються для створення контейнерів, і вкажемо прапор SIGCHLD для того, щоб спрацювала функція wait . Системний виклик знову повернув номер створеного процесу, і у висновку strace з'явилися ті самі прапори, що ми вказали. Тут ми вибираємо розмір стека довільно і передаємо покажчик на кінець стека у функцію clone. Справа в тому, що стек росте вниз на більшості архітектур, тому потрібно передати покажчик саме на кінець стека. Як перший аргумент ми передаємо покажчик на функцію child_main, що виконує роль функції main дочірнього процесу.
| Прапор | Пояснення |
|---|
| CLONE_NEWNS |
точки монтування |
| CLONE_NEWPID |
процеси |
| CLONE_NEWUSER |
користувачі та групи |
| CLONE_NEWNET |
мережеві пристрої |
| CLONE_NEWUTS |
хост та доменне ім'я |
| CLONE_NEWIPC |
примітиви сихронізації |
| CLONE_NEWCGROUP |
контрольні групи |
14 Прапори, які використовуються для створення контейнерів, перелічені у таблиці.Всі ці прапори мають префікс NEW та використовуються для створення нових просторів імен Linux. Саме ця технологія є основою більш високорівневих технологій створення контейнерів. Усього кілька прапорів функції clone здатні забезпечити роботу всіх сучасних технологій створення контейнерів!
15 Створення просторів імен — це складно, і ми не розглядатимемо цю тему в рамках цього курсу. Я поясню лише прапори, які були використані в прикладі. Це прапор CLONE_NEWUTS , за допомогою якого створюється нове місце імен, в якому визначено ім'я машини і доменне ім'я. Доменне ім'я давно застаріло і завжди дорівнює нулю, а ім'я машини - це коротке ім'я адміністраторів. Створення нового простору означає, що дочірній процес, а також дочірні процеси можуть змінювати ім'я машини, і ці зміни не позначаться на імені машини, яке бачать всі інші процеси. Таким чином, за допомогою просторів імен можна ізолювати процеси один від одного так, щоб вони, працюючи на одній і тій же фізичній машині, жили як би в паралельних всесвітах.
16 Прапор CLONE_NEWUSER створює новий простір користувачів та груп, у якому дочірній процес отримує повні права доступу (права суперкористувача!). За допомогою цього прапора дочірній процес отримує право змінювати ім'я машини (зазвичай це право є тільки у суперкористувача). У цьому прикладі це допоміжний прапор.
int
child_main(void*
ptr)
std::string
s
=
"spicy";
sethostname(s.data(),
s.size());
return
0;
>
int
main()
size_t
stack_size
=
1024*10;
std::unique_ptrchar[]>
child_stack(new
char[stack_size]);
pid_t
pid
=
clone(child_main,
child_stack.get()
+
stack_size,
CLONE_NEWUTS
|
CLONE_NEWUSER
|
SIGCHLD,
0);
int
status
=
0;
wait(&status);
return
0;
>
clone(. flags=CLONE_NEWUTS | CLONE_NEWUSER | SIGCHLD)
=
7427
17 Який можна підбити підсумок? Потоки, контейнери та звичайні процеси з погляду ядра Linux розрізняються лише набором прапорів, або набором системних структур та ресурсів, що виділяються процесу та його нащадкам. У разі потоку більшість структур та ресурсів використовуються спільно з батьківським процесом, у разі контейнера майже всі структури та ресурси копіюються. Якщо розташувати ці сутності спектрі, то вийде приблизно така картина. Потоки це процеси-утриманці, що повністю покладаються на ресурси батьків, а контейнери це самостійні процеси, повністю ізольовані від батьків.
Запуск програм у дочірніх процесах
18 У прикладах, використаних раніше всі дочірні процеси виконували той самий код, як і батьківський процес: у тому пам'яті зберігалися самі глобальні змінні, функції й самі бібліотеки. Насправді ж ми часто хочемо запустити зовсім іншу програму в дочірньому процесі. Для цього використовується функція-оберта execvp , в назві якої v позначає вектор, а p - шлях. У функцію передається вектор аргументів, що є масивом рядків, що закінчується нульовим елементом. За згодою нульовий аргумент є назвою програми, яка була запущена. Для дотримання цієї угоди ми передаємо нульовий елемент масиву як перший аргумент функції execvp.
pid_t
pid
=
fork();
if
(pid
==
0)
char*
const
argv[]
=
"ls",
"-l",
0>;
// аргументи
execvp(argv[0],
argv);
// нульовий аргумент - ім'я програми
exit(0);
>
int
status
=
0;
wait(&status);
19 Ця функція замінює поточний бінарний код, завантажений у пам'ять, на код із зазначеного файлу.Суфікс p означає, що система повинна сама знайти файл, що виконується, із зазначеним ім'ям у стандартних директоріях. Після завантаження в пам'ять усі потоки зупиняються і запускається функція main із щойно завантаженої програми. Функція execvp унікальна тим, що під час успішного виклику не повертається. Якщо ж трапилася помилка, то функція повертає -1. У цьому прикладі в цьому випадку дочірній процес завершується за допомогою системного виклику exit.
20 Як бачите, у Linux створення нового процесу та завантаження коду розділені. Це зручно, оскільки після створення дочірнього процесу, батьківський може його налаштувати (обмежити ресурси, змінити робочу директорію тощо), використовуючи ті самі системні виклики, що і для себе самого. Після налаштування викликається функція exec, яка замінює код на новий. При цьому параметри зберігаються (успадковуються).
| Виклик | Опис |
|---|
| getpid |
номер процесу |
| getppid |
номер батьківського процесу |
| getuid |
номер користувача |
| getgid |
номер групи |
| getenv |
змінні середовища |
| getcwd |
робоча директорія |
21 У таблиці показано деякі основні системні дзвінки. Тут є виклики для отримання номерів поточного та батьківського процесів, номерів користувача та групи, змінних середовища та робочої директорії. Для ядра всі користувачі та групи є номерами. Навіть якщо цим номерам не відповідають зареєстровані імена та паролі користувачів, ядро може запустити процес, який працює під номером такого користувача або групи. Таким чином, користувач і група - це лише поля структури, що описує процес (для ядра все є процесом!). Права на ту чи іншу операцію надаються процесу, а не користувачеві. Нині ж перейдемо до обговорення змінних середовища.
Змінні середовища
22 Змінні середовища - це вектор (аналогічний вектору аргументів), кожен елемент якого є парою ключ-значення, розділеними знаком = . Стандартних змінних є досить багато, проте ви не можете бути впевнені в наявності жодної з них. Нижче показано дві змінні, які найчастіше зустрічаються. Це змінна HOME , що містить шлях до домашньої директорії поточного користувача, і змінна LANG , що означає поточну мову та кодування. Ця змінна використовується при виборі перекладів рядків з мов світу.
char**
first
=
environ;
while
(*first)
std::cout
<*first
<'\n';
++first;
>
. HOME=/home/myuser LANG=ru_UA.utf8 .
Завдання
Системні виклики 1 бал
23 Здійсніть системний виклик getpid за допомогою однойменної функції-обгортки із заголовного файлу unistd.h . Зробіть той же виклик за допомогою універсальної функції-обгортки syscall . Перевірте, чи вони повертають однакові значення.
Дочірні процеси 1 бал
24 Створіть дочірній процес за допомогою функції-обгортки fork і запустіть команду expr 2 + 2 * 2 за допомогою системного виклику execlp . Дочекайтеся завершення дочірнього процесу за допомогою функції wait.
Змінні середовища 1 бал
25 Виведіть змінні середовища, ім'я яких містить символ L . Для цього скористайтеся глобальною змінною environ із заголовного файлу environ.h.
Аргументи та простори імен 2 бали
26 Створіть дочірній процес, в якому ви зміните ім'я комп'ютера на ім'я, передане батьківському процесу як перший аргумент. Для цього скористайтесь системним викликом clone та відповідними прапорами цього виклику, а також системним викликом sethostname . Перевірте за допомогою gethostname , що ім'я комп'ютера різне в дочірньому та батьківському процесі.
Відео
- 00:00 Введення
- 02:50 Як програми взаємодіють із ядром?
- 11:33 Дерево процесів
- 15:35 Функція fork
- 17:11 Копіювання під час запису
- 19:29 Команда strace
- 19:29 Висновок strace для функції fork
- 25:11 Виведення strace для об'єкту std::thread
- 28:00 Виведення strace для функції clone
- 34:45 Запуск програм у дочірніх процесах
- 42:29 Деякі системні виклики
- 46:02 Змінні середовища
- 51:10 Сторінки керівництва
- 54:22 Питання про відмінність fork і std::thread