Привіт. У вінді для створення процесу використовується один системний виклик. У ніксах їх два: fork а потім exec. Який сенс спочатку копіювати себе, а потім замінювати образ пам'яті? у вінді те ж саме закамуфлювали під один виклик спочатку копіювати себе, а потім замінювати образ пам'яті? запропонуй спосіб краще, при цьому врахуй COW по-перше, після fork не завжди виконують exec, а по-друге, ніхто там нічого не копіює (без потреби), читай про COW Copy-On-Write COW – це коли для дочірнього процесу створюється свій адресний простір лише при спробі запили, чи я правильно зрозумів? Я не розумію самого сенсу створення копії. Чому не можна відразу створити окремий процес та образ пам'яті для нього? У UNIX існує лише один системний виклик для створення нового процесу – fork. Цей виклик створює точну копію процесу, що викликає. Після виконання системного виклику fork два процеси, батьківський і дочірній, мають єдиний образ пам'яті, єдині рядки опису конфігурації та одні й самі відкриті файли. І більше нічого. Зазвичай після цього дочірній процес змінює образ пам'яті і запускає нову програму, виконуючи системний виклик execve або подібний до нього. Наприклад, коли користувач набирає в оболонці команду sort, оболонка створює дочірній процес, що відгалужується, в якому і виконується команда sort. Сенс цього двоступеневого процесу полягає в тому, щоб дозволити дочірньому процесу керувати його файловими дескрипторами після розгалуження, але перед виконанням execve з метою виконання перенаправлення стандартного введення, стандартного виводу та стандартного виведення повідомлень про помилки. У Windows все відбувається по-іншому: одним викликом функції Win32 CreateProcess створюється процес, і в нього завантажується потрібна програма. Цей виклик має 10 параметрів, включаючи виконувану програму, параметри командного рядка для цієї програми, різні параметри безпеки, біти, що керують успадкуванням відкритих файлів, інформацію про пріоритети, специфікацію вікна, створюваного для процесу (якщо воно використовується), і покажчик на структуру, в якій програмі, що викликає, буде повернуто інформацію про щойно створений процес. На додаток до функції CreateProcess Win32 є близько 100 інших функцій для управління процесами та їх синхронізації, а також виконання всього, що з цим пов'язано. Я зараз читаю 4 видання книги Таненбаума. Переклад, треба сказати, дуже лайновий. Сенс цього двоступеневого процесу полягає в тому, щоб дозволити дочірньому процесу керувати його файловими дескрипторами після розгалуження, але перед виконанням execve з метою виконання перенаправлення стандартного введення, стандартного виводу та стандартного виведення повідомлень про помилки. Я один не зрозумію сенсу цієї пропозиції? Ні, так є vfork(), який нічого не копіює навіть при записі (UB). Але взагалі пайпи, наприклад, дуже активно юзають виведення попередньої програми. І взагалі багато сервісів, що створюють треди-копії, веб-ревер, наприклад. Тож доступ синка до віртуальної пам'яті батька (хочаби навіть рідонлі) це таки вин. Що незрозуміло? Дочка не відкриває дескриптори наново, а юзає батьківські, заощаджує пам'ять та час. Ось тепер зрозуміло. А в книзі чортзна що написано. Фіча в тому, що exec() не завжди потрібен, можна виконувати одну і ту ж програму в декількох процесах, причому дочірні процеси продовжують роботу з повернення з fork(), з вже проініціалізованими файловими дескрипторами. У вінді так не вийде, дочірньому процесу доведеться виконувати програму від початку Іноді роблять fork без exec. Іноді вони виконують без вилки. Це більш гнучке API. Привіт. У вінді для створення процесу використовується один системний виклик. У ніксах їх два: fork а потім exec. Один. exec не створює процесів. ооп, успадкування, поліморфізм, інкапсуляція, уніфікація, KISS, DRY, YAGNI патерни проектування, agile, scrum Якщо точно, то таки два - fork і clone. posix_spawn() спеціально для тебе. execve() is Posix-specific and should not be used in programs intended to be portable Дочка не відкриває дескриптори наново, а юзає батьківські, заощаджує пам'ять та час. Ось тепер зрозуміло. fork-exec не був призначений для економії ресурсів (і в перші роки UNIX жодного COW не було). Створення процесів через fork - це спосіб настроїти середовище виконання нащадка (спрощено кажучи, заповнити дані у пам'яті та відкрити/закрити потрібні дескриптори). tailgunner ★★★★★ «Функція з інтерфейсу, що переноситься, не повинна використовуватися в програмах, які хочуть бути переносимими»? Ти зовсім шизався? fork-exec не був призначений для економії ресурсів (і в перші роки UNIX жодного COW не було). зафіксовано у Plan 9.там rfork має додаткові поля (FreeBSD: теж rfork) - можна вказати які саме ресурси поділяються Створення процесів через fork - це спосіб настроїти середовище виконання нащадка (спрощено кажучи, заповнити дані у пам'яті та відкрити/закрити потрібні дескриптори). ну так. Знову ж таки, в Plan 9 можна вказати які ресурси поділяються (включаючи простори імен файлової системи) тобто, у кожного процесу точка зору на файлову систему налаштовується по-різному, клієнт-сервер файловий виступає об'єктним клієнтом і сервером, об'єкти представляються. як віртуальні псевдофайли, P9 fs як файловий протокол реалізує метаоб'єктний протокол, прозорий по мережі. мультиплексором реальних багатьох об'єктів/ресурсів/файлів в один віртуальний псевдофайл простору імен, клієнт — демультиплексор або користувач віртуального ресурсу. наприклад: у віконній системі rio окремі нероздільні ресурси «вікна» /dev/window/NNN => «відображаються на» псеводфайл, загальний ресурс /dev/window поточного додатка. середовищі/IDDE acme: контрольні файли реалізують інтерфейс доступу до ресурсів acme, вікон програми. точніше, не «інтерфейс API», а протокол управління ресурсами, метаоб'єктний протокол реалізований через файли. в цьому сенсі і REST-like API для управління ресурсами Content-Addressable Memory також можна розглядати не як набір мікросервісів, а як метаоб'єктний протокол як і P9FS. в Erlang взаємодія між легковажними процесами реалізована як пролог, як марківський алгоритм.тобто: замість процесів-процесів-алгоритмів, пов'язаних через пайпи, простим текстовим інтерфейсом (унікальним для кожної пари програм) або «об'єктним інтерфейсом», недоуніверсальним метапротоколом (як у PowerShell: передаються між пайпами не текст, а об'єкти, у яких можна рефлексією опитати властивості, посмикати за стандартні методи) - Erlang ближче до бази ЗНАНЬ, ПРОДУКЦІЙНОЇ МОДЕЛИ ЯКЩО , ТО : якщо є подія-висновок на стандартний висновок постачальника, то запустити дію-обробку події у споживача. при цьому патерн матчінг, підстановки в Erlang, plumbing у Plan9 роблять механізм виклику більш універсальним та зручним. від сильнопов'язаних API до більш пов'язаних компонентів, агентів-об'єктів і протоколів. Процеси – діюче початок. У загальному випадку з процесом пов'язані код та дані у віртуальній оперативній пам'яті, відображення віртуальної пам'яті на фізичну, стан процесора (реєстри, поточна інструкція, що виконується тощо). (у тому числі знижувальний коефіцієнт nice), інформація про відкриті файли та обробники сигналів. виконувана всередині процесу може змінюватися протягом його існування. Нові процеси створюються викликом int pid=fork() , який створює точну копію процесу, що його викликав. Після створення дочірній процес може завантажити в свою пам'ять нову програму (код і дані) з виконуваного файлу викликом execve (const char * filename, char * const argv [], char * const envp []); Дочірній процес пов'язаний із батьківським значенням PPID. У разі завершення батьківського процесу PPID змінюється на особливе значення 1 - PID процесу init. У момент завантаження ядра створюється особливий процес з PID=1, який має існувати до перезавантаження ОС. init тому надалі я називатиму його "процес init". У Linux процес init захищений від втручання інших процесів. procfs, йому не доставляються сигнали, що призводять до завершення процесу. init все ж таки завершиться, то ядро також завершує роботу з відповідним повідомленням. У сучасних дистрибутивах класична програма init замінена на systemd, але сутності процесу з PID=1 це змінює. При завантаженні Linux ядро спочатку монтує кореневу файлову систему на образ диска в оперативній пам'яті initrd, Потім створюється процес з PID=1 і завантажує в нього програму із файлу /init. initrd з дистрибутива CentOS початковий /init - це скрипт для /bin/bash.Скрипт завантажує необхідні драйвери, після чого робить дві речі, необхідні для повноцінного запуску Linux: Для того, щоб виконати ці два пункти через завантажувач у початковому init два параметри: Якщо другий параметр опущено, то шукається ім'я зашите в початковий init за замовчуванням. Якщо ви завантажите замість init
/bin/bashЯк у моєму прикладі, то зможете завершити перший і єдиний процес командою exit та поспостерігати за повідомленням: Цей приклад показує, як отримати права адміністратора при фізичному доступі до комп'ютера. Кожен процес має унікальний на даний момент ідентифікатор PID. Змінити PID процесу неможливо. Значення PID 0 та 1 зарезервовані. Процес з PID==0 не використовується, PID==1 - належить програмі init. Максимальне значення PID у Linux дорівнює PID_MAX-1. Поточне значення PID_MAX можна подивитися командою: За замовчуванням це 2^16 (32768) однак у 64-розрядних Linux його можна збільшити до 222 (4194304): *PID* призначаються послідовно. При створенні нового процесу викликом fork шукається *PID* , більший за значенням, ніж той, який повернули попереднім викликом fork . Якщо під час пошуку досягнуто значення pid_max , то пошук продовжується з PID=2. Така поведінка обрана тому, що деякі програми можуть перевіряти завершення процесу існування його PID. У цій ситуації бажано, щоб PID не використовувався деякий час після завершення процесу. З процесом пов'язане поняття "власника" та "групи", що визначають права доступу процесу до інших процесів та файлів у файловій системі. "Власник" та "група", це числові ідентифікатор UID і GIDє атрибутами процесу. На відміну від файлу, процес може належати кільком групам одночасно. Користувач у діалоговому сеансі має право на доступ до своїх файлів, оскільки діалогова програма (shell), яку він використовує, виконується в процесі з тим же UIDом, що і UIDвказаний в атрибутах файлів. Процес може змінити свого власника та групу у двох випадках: Виклик newpid=fork() створює новий процес, що є точною копією поточного і відрізняється лише значенням newpid, що повертається. У батьківському процесі newpid дорівнює PID дочірнього процесу, у дочірньому процесі newpid дорівнює 0. Свій PID можна дізнатися викликом mypid=getpid() , батьківський викликом parentpid=getppid() . В оперативній пам'яті процесу знаходяться код та дані, завантажені з файлу. При запуску програми з командного рядка зазвичай створюється новий процес і в його пам'ять завантажується файл із програмою. Завантаження файлу робиться викликом однієї з функцій сімейства exec (див. man 3 exec). Функції відрізняються способом передачі параметрів, а також тим, чи використовується змінна оточення PATH для пошуку файлу, що виконується. Наприклад, execl як перший параметр приймає ім'я виконуваного файлу, другим і наступними – рядки аргументи, що передаються в argv[], і, нарешті, останній параметр повинен бути NULL, він дає процедурі можливість визначити, що параметрів більше немає. Приклад exec з двома помилками: Помилка 1: Перший аргумент, що передається програмі, це ім'я самої програми. У цьому прикладі у списку процесів буде видно програму з ім'ям -l, запущену без параметрів. Помилка 2: Оскільки код із файлу /bin/ls буде завантажений у поточний процес, то старий код та дані, у тому числі printf("Програма ls запущена успішно\n"), буде затерто. Перший printf ніколи не спрацює. Процес може завершитись, отримавши сигнал або через системний виклик _exit(int status) . status може приймати значення від 0 до 255. За угодою, status==0 означає успішне завершення програми, а ненульове значення означає помилку. Деякі програми (наприклад kaspersky для Linux) використовують статус повернення певної інформації про результати роботи програми. _exit() може бути викликаний кількома шляхами. Після завершення процесу його pid залишається зайнятий - цей стан процесу називається "зомбі". Щоб звільнити pid батьківський процес повинен дочекатися завершення дочірнього та очистити таблицю процесів. Це досягається викликом: Виклик wait(&status); еквівалентний waitpid(-1, &status, 0); waitpid чекає завершення дочірнього процесу і повертає його PID. Код завершення та обставини завершення заносяться до змінної status. Додатково поведінкою waitpid можна керувати через параметр options. Опція WNOHANG - означає неблокуючу перевірку дочірніх процесів, що завершилися. Статус завершення перевіряється макросами: Для забезпечення багатозадачності кожен процес користувача періодично переривається, його контекст зберігається, а управління передається іншому процесу. Переривання виконання процесу може відбуватися за таймером або під час обробки системного виклику. Залежно від обставин перерваний процес ставиться в чергу процесів на виконання, до списку процесів, що очікують ресурси (наприклад, очікування введення користувача або завершення виведення на фізичний носій) або до списку зупинених процесів. Переривання за таймером відбуваються відповідно до кванту часу, виділеного процесу. У Linux квант часу за умовчанням (DEF_TIMESLICE) дорівнює 0,1 секунди, але може бути перерахований планувальником процесів (sheduler). Системний виклик може завершитися з негайним поверненням в програму користувача, завершитися одночасно з вичерпанням кванта часу або перейти в стан очікування ресурсу . У момент повернення в програму користувача відбувається доставка сигналів - тобто. виклик процедури оброблювача сигналу, зупинка, перезапуск або завершення процесу. Деякі сигнали (SIGSTOP) - призводять до того, що процес включається до списку зупинених процесів, які не надходять у чергу процесів виконання. Сигнал SIGCONT повертає зупинений процес у чергу процесів виконання, сигнал SIGKILL завершує зупинений процес. Після завершення процесу викликом _exit() або сигналом всі його ресурси (пам'ять, відкриті файли) звільняються, але запис у таблиці процесів залишається і займає PID. Такий процес називається "зомбі" і має бути явно очищений з таблиці процесів викликом wait() у батьківському процесі. Якщо батьківський процес завершився раніше за дочірні, то всім його дочірнім процесам приписується значення PPID (parent pid) 1, покладаючи зобов'язання з очищення від них таблиці процесів на особливий процес init з PID=1. На діаграмі показані різні стани процесу У Linux команда ps використовує такі позначення стану процесу: Завданням планувальника процесів є вилучення процесів, готових на виконання, відповідно до деяких правил. Планувальник намагається розподілити процесорні ресурси те щоб жоден із процесів не простоював тривалий час, і щоб процеси, які вважаються пріоритетними, отримували процесорний час насамперед. У багатопроцесорних системах бажано, щоб у послідовних квантах часу процес запускався на тому самому процесорі, щоб максимально використовувати процесорний кеш. При цьому сам планувальник повинен виконувати вибір якнайшвидше. Найпростіша реалізація черги у вигляді FIFO дуже швидка, але не підтримує пріоритети та багатопроцесорності. У Linux 2.6 скористалися простотою FIFO, додавши до неї кілька вдосконалень: Прикладний програміст може додатково знизити пріоритет процесу функцією int nice (int inc); (в Linux nice() - інтерфейс до виклику setpriority()). Більше значення nice означає менший пріоритет. У командному рядку використовується "запуск" з таким же ім'ям: Якщо немає процесів готових до виконання, то планувальник викликає нитку (процес) Idle. У Linux 2.2 однопроцесорна кросплатформова версія Idle виглядала так: В апаратно-залежну реалізацію idle() може бути винесене керування енергозбереженням. У ранніх версіях Linux процес Idle мав PID=0, але, власне кажучи, Idle як самостійний процес немає. Середнє завантаження (Load Average, LA) - усереднений захід використання ресурсів комп'ютера запущеними процесами. Величина LA пропорційна числу процесорів у системі та на ненавантаженій системі коливається від нуля до значення, що дорівнює числу процесорів.Високі значення LA (10 * число ядер і більше) говорять про надмірне навантаження на систему і потенційні проблеми з продуктивністю. У класичному Unix LA має сенс середньої кількості процесів у черзі виконання + кількості виконуваних процесів за одиницю часу. Тобто. LA == 1 означає, що в системі вважається один процес, LA > 1 визначає скільки процесів не змогли стартувати, оскільки їм не вистачило кванта часу, а LA < 1 означає, що в системі є незавантажені ядра. У Linux до кількості процесів додали ще й процеси, які чекають на ресурси. Тепер на зростання LA значно впливають проблеми введення/виводу, такі як недостатня пропускна здатність мережі або повільні диски. LA усереднюється за такою формулою LAt+1=(LAcur+LAt)/2. Де LAt+1 - значення, що відображається в момент t+1, LAcur - поточне виміряне значення, LAt - значення, що відображається в момент t. Таким чином згладжуються піки і після різкого падіння навантаження значення LA повільно знижуватиметься, а короткочасний пік навантаження буде відображений половинною величиною LA. Видача команди top у Linux на комп'ютері з 36 ядрами: На комп'ютері запущено багатопоточне лічильне завдання, яке займає майже всі ядра і не використовує введення/виведення. LA трохи менше 36, що узгоджується з розподілом часу процесора: 93.9 us – користувач, 0.5 sy – ядро, 5.5 id – Idle, 0.0 wa – очікування пристроїв. З кожним процесом Unix пов'язані два атрибути uid і gid - Користувач та основна група.У принципі, вони могли б визначати права доступу процесу до ФС та інших процесів, однак існує кілька ситуацій, коли права процесу відрізняються від прав його власника. Тому крім uid/gid (іноді званих реальними ruid/rgid) з процесом пов'язані атрибути прав доступу - ефективні uid/gid - euid/egid, найчастіше збігаються з ruid/rgid. Крім того, з uid пов'язаний список допоміжних груп, описаних у файлі /etc/group . euid/egid та список груп визначають права доступу процесу до ФС. Нові файли успадковують атрибути uid/gid від euid/egid процесу. Крім того euid визначає права доступу до інших процесів (налагодження, надсилання сигналів тощо). euid рівний нулю використовується для позначення привілейованого процесу, що має особливі права на доступ до ФС та інших процесів, а також на доступ до адміністративних функцій ядра, таким як монтування диска або використання портів TCP з номерами менше 1024. euid=0 завжди має право на читання та запис файлів та каталогів. Право виконання файлів надається привілейованому процесу лише у тому випадку, коли файл виставлений хоча б один атрибут права виконання. Примітка: у сучасних ОС особливі привілеї процесу визначаються через набір особливих прапорів. capabilities і не обов'язково прив'язані до euid=0. (re)uid/(re)gid, і навіть допоміжні групи, успадковуються від батьківського процесу під час виклику fork(). При виклику exec()
ruid/rgid зберігаються, а euid/egid можуть бути змінені якщо у файлу, що виконується, виставлений прапор зміни власника. Для скриптів прапор зміни власника ігнорується. фактично запускається інтерпретатор, а скрипт передається йому як параметр.У момент входу користувача до системи програма login зчитує файли /etc/passwd і /etc/group необхідні величини та встановлює їх перед завантаженням командного інтерпретатора. Список допоміжних груп можна вважати масив функцією int getgroups(int size, gid_t list[]). Чи буде при цьому у списку основна група невідома, це залежить від реалізації конкретної ОС. Максимальну кількість допоміжних груп можна отримати так: long ngroups_max = sysconf(_SC_NGROUPS_MAX); або з командного рядка getconf NGROUPS_MAX . У моєму Linux'і максимальна кількість груп – 65536. Для ініціалізації допоміжних груп у Linux можна скористатися функцією int initgroups(const char *user, gid_t group); ця функція розбирає файл /etc/group, а потім звертається до системного виклику int setgroups(size_t size, const gid_t *list);. Існують кілька функцій для керування атрибутами uid/gid. Для економії місця далі перераховуються лише функції до роботи з uid. Отримати значення атрибутів можна за допомогою опцій getuid(), geteuid() Встановити значення можна за допомогою У Linux, HP-UX та інших ОС додатково підтримуються атрибут збережених прав процесу suid/sgid (Не плутати з однойменними атрибутами файлу). Відповідно є функція для встановлення всіх трьох атрибутів setresuid(rid,eid,sid); Якщо euid=0 або ruid=0 то ruid і euid можуть змінюватися довільно. Тобто. можна зробити euid<>0 або ruid<>0, а потім повернутися в стан euid=ruid=0. Якщо обидва атрибути не дорівнюють нулю, то можлива лише зміна euid в ruid (відмова від додаткових прав). Програма su отримує euid=0 завдяки відповідному атрибуту файлу та використовує можливості привілейованого процесу для запуску програм від імені довільного користувача (у тому числі root). Веб-сервер apache, навпаки, стартує з ruid=euid=0, але потім відбирає у себе зайві права змінюючи ruid і euid на непривілейовані значення.Сенс схеми створення процесів у UNIX
CONFORMING TO clone() є Linux-specific і не може бути використаний в програмах, що надаються до портативного.
(29.10.16 11:35:44 MSK)
Останнє виправлення: tailgunner 29.10.16 11:37:32 MSK (всього виправлень: 1)
Процеси
Створення процесів fork()
Процес init
Kernel panic - не syncing: Attempted to kill init!
PID
cat /proc/sys/kernel/pid_max
echo 4194303 > /proc/sys/kernel/pid_max
UID та GID
Життєвий цикл процесу
Створення процесу
int pid, cpid; int int status; pid=fork() if( pid > 0 ) < cpid=waitpid(&status); if( cpid >0 ) < printf("Я старий процес (pid=%i) створив новий (pid=%i), що завершився з кодом %i\n", getpid(),pid,WEXITSTATUS(status) ); >>else if( pid == 0 )< printf("Я новий процес (pid=%i) створений старим (pid=%i)\n",getpid(),getppid()); >else
Запуск програми
int pid = fork (); if( pid > 0 )< waitpid(NULL); >else if( pid == 0 ) < if(-1 == execl("/bin/ls","ls","-l",NULL) ) < exit(1); >>
if( 0 == execl("/bin/ls","-l",NULL) )< printf("Програма ls запущена успішно\n"); >else
Завершення процесу
Видалення завершеного процесу з таблиці процесів
pid_t cpid=waitpid(pid_t pid, int *status, int options) //або pid_t cpid=wait(int *status)
Основи планування процесів
Планувальник процесів
Процес Idle
int cpu_idle(void *unused)
Обчислення середнього завантаження
Видача команди top
top - 19:43:53 4 дні, 5:54, 1 користувач, load average: 34.07, 33.75, 33.80 Tasks: 550 total, 12 running, 538 sleeping, 0 stopped, 0 zombie %Cpu. , 0.5 sy, 0.0 ni, 5.5 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
Ефективні права процесу
setuid(id); - Встановити ruid і euid в id
seteuid(id); - Встановити euid в id
setreuid(rid,eid); - Встановити ruid і euid в rid і eid. -1 як параметр означає, що значення не змінюється