Некоторые системные вызовы Unix Основное средство для организации многозадочности - это вызов безаргументной функции fork() из <unistd.h>, который создает копию вызвавшего ее процесса. Копия может быть отличена от оригинала по возвращаемому значению fork(), которое --- ноль в копии и номер процесса-копии в исходном, порождающем процессе. Если вызов не удался, например, из-за нехватки памяти, то fork() возвращает -1. Вызовы процессов образуют бинарное дерево. Код возврата процесса возвращается порождающему процессу. Если порождающий процесс завершается раньше порождённого, то порожденный процесс превращается в зомби-процесс. Он не может полностью исчезнуть, так как его код возврата может быть востребован порождающим процессом. Начальный процесс в системе (корень в дереве процессов) называется init или systemd --- его номер обычно 1. Собственно вызов новой задачи или, другими словами, установка нового содержимого для процесса осуществляется функциями семейства exec из <unistd.h>: execl(), execlp(), execle(), execv(), execvp(). Функции, заканчивающиеся на p, не требуют точного указания местоположения загружаемого файла --- они могут найти его по адресам, перечисленным в переменной среды PATH. Функции, содержащие l, получают параметры вызова через список аргументов неопределенной длины, а функции, содержащие v, получают такой список через массив, подобно main(). Последним элементом как списка, так и массива должен быть 0, а первым --- имя программы. Функция execle() позволяет задавать среду исполнения процесса. Пример вызова программы date. #include<iostream> #include<unistd.h> main() { execlp("date", "date", 0); std::cerr << "Can't execute program 'date'\n"; } Программа date вытесняет текущий процесс, поэтому строка с сообщением об ошибке будет выполнена только в случае, если загрузка date не удалась. Сохранить текущий процесс при вызове нового можно только с помощью fork(). #include<iostream> #include<unistd.h> main() { if (fork() == 0) { std::cout << "This is child\n"; execlp("date", "date", 0); std::cerr << "Can't execute program 'date'\n"; } else std::cout << "This is parent\n"; } Функция wait() из <sys/wait.h> позволяет дождаться окончания выполнения какого-нибудь порождаемого процесса. Вызовы wait() будут успешны до тех пор, пока остаются порожденные процессы. #include<iostream> #include<unistd.h> #include<sys/wait.h> main() { if (fork() == 0) { std::cout << "This is child\n"; execlp("sleep", "sleep", "10", 0); std::cerr << "Can't execute program 'sleep'\n"; } else { int status; std::cout << "This is parent\n"; unsigned cid = wait(&status); std::cout << "process #" << cid << " is finished with " << (status&255) << " exit code\n"; //this message appears after 10 sec } } Значение status, устанавливаемое wait(), содержит, в частности, код возврата процесса-потомка в младшем байте. Результат wait() --- это номер завершившегося процесса или -1 в случае ошибки. Можно вызывать wait() с аргументом 0, если возвращаемое значение неважно. Ввод-вывод системного уровня обеспечивается функциями из <unistd.h> read() --- читать из файла, write() --- писать в файл, dup() --- создать копию дескриптора файла, close() --- закрыть файл, unlink() --- отсоединить, уничтожить жесткий соединитель (файл), lseek() --- искать позицию в файле. И функциями из <fcntl.h> creat() --- создать файл, open() --- открыть файл. Некоторые стандартные константы для некоторых из этих функций определены в <sys/types.h> и <sys/stat.h>. Файл на низком уровне задается дескриптором --- целым числом, оно используется, в частности, при перенаправлении потоков ввода-вывода. Рассмотрим программу, которая печатает два раза все, что будет введено с клавиатуры. #include<unistd.h> main() { char buf[8], n, cp1; cp1 = dup(1); //copy of std output while ((n = read(0, buf, 8)) > 0) { write(1, buf, n); write(cp1, buf, n); } } Создание и работа с файлом. #include<unistd.h> //#include<sys/types.h> //#include<sys/stat.h> #include<fcntl.h> main() { char fn[] = "testio.txt", buf[11] = "123456\nok\n"; int fd = creat(fn, 0664); //0664 - mode for(int i = 0; i < 3; i++) write(fd, buf, 7); close(fd); fd = open(fn, 1); //0=O_RDONLY - read, 1=O_WRONLY - write, 2=O_RDWR - read & write; //можно устанавливать и другие флаги, см. man 2 open lseek(fd, 14, 0); //3rd arg is the same as at fseek buf[0] = '*'; write(fd, buf, 1); close(fd); write(1, buf+7, 3); //prints "ok" } //creates text file 'testio.txt' with 3 lines: 123456 // 123456 // *23456 Для обмена данными между процессами можно использовать трубопроводы, создаваемые функцией pipe() из <unistd.h>, у которой один аргумент --- массив из двух целых чисел. Первый элемент массива --- это выход из трубопровода, для чтения данных, а второй --- это вход. #include <sys/wait.h> #include <cstdio> #include <unistd.h> #include <cstring> using namespace std; int main() { int pipefd[2]; pid_t cpid; char buf; if (pipe(pipefd) == -1) { fputs("pipe\n", stderr); return 1; } cpid = fork(); if (cpid == -1) { fputs("fork\n", stderr); return 2; } puts("ok"); //напечатается 2 раза if (cpid == 0) { //Порожденный процесс читает из трубопровода close(pipefd[1]); //Закрытие ненужного входа в трубопровод while (read(pipefd[0], &buf, 1) > 0) write(STDOUT_FILENO, &buf, 1); write(STDOUT_FILENO, "\n", 1); close(pipefd[0]); return 0; } else { //Базовый процесс пишет строку в трубопровод char string[] = "Hello from the parent to the child"; close(pipefd[0]); //Закрытие ненужного выхода из трубопровода write(pipefd[1], string, strlen(string)); close(pipefd[1]); //получатель данных получит EOF wait(0); //ждём завершения работы получателя return 0; } } Вызов dup в подобных программах позволяет связать концы трубопровода со стандартными потоками ввода-вывода, что соответствует | в оболочке ОС. Процессы могут посылать друг другу сигналы. В частности, если порождённый процесс заканчивается или приостанавливается, то автоматически генерируется сигнал SIGCHLD. Функция signal() из <signal.h> устанавливает обработчик сигнала с заданным номером, а функция kill() из этого же заголовка используется для генерации заданного сигнала. #include <cstdio> #include <sys/wait.h> #include <signal.h> #include <unistd.h> using namespace std; void signalHandler(int signal) { printf("Cought signal %d!\n", signal); if (signal == SIGCHLD) { puts("Child ended"); wait(0); //для совместимости } } int main() { signal(SIGALRM, signalHandler); signal(SIGUSR1, signalHandler); signal(SIGCHLD, signalHandler); signal(SIGINT, signalHandler); //Control-C if (!fork()) { puts("Child running..."); sleep(2); puts("Child sending SIGALRM..."); kill(getppid(), SIGALRM); //послать сигнал родителю sleep(10); puts("Child exitting..."); return 0; } printf("Parent running, PID=%d. Press ENTER to exit.\n", getpid()); fgetc(stdin); puts("Parent exitting..."); return 0; } Функции getpid() и getppid() из <unistd.h> возвращают соответственно номера текущего и порождающего процессов. Функция sleep() из <unistd.h> --- это задержка на заданное число секунд. При исполнении заданной программы ей можно посылать сигнал SIGUSR1 для перехвата, например, с командной строки, запуская kill -SIGUSR1 НОМЕР-ПРОЦЕССА. Средства взаимодействия процессов (IPC --- Inter-process communication) помимо сигналов и трубопроводов (с именем и без) включают семафоры, разделяемую память (shared memory), файлы (обычные и отображаемые в память), сокеты и очереди сообщений. Отображаемый в память файл (Memory-mapped file) --- весь или частично доступен для прямого доступа по заданным адресам оперативной памяти. Сложности работы с процессами можно проиллюстрировать следующим примером. #include<iostream> #include<unistd.h> main() { for (int i = 0; i < 2; ++i) { fork(); std::cout << '.'; //std::cout.flush(); //std::cerr << '.'; } } //8/6 точек с cout/cerr Функция clone() в Linux служит для создания сопроцессов или нитей (threads) --- процессов, разделяющих общую память и другие ресурсы. Управление сопроцессами проводится при помощи средств, похожих на те, что используются для управления процессами: семафоры, мьютексы, .... Библиотека NPTL (New Posix Thread Library) содержит необходимые средства для работы с сопроцессами. Они вводятся заголовком <pthread.h> си/си++. В стандарте си++ 2011 года для этого определяются заголовки <thread>, <mutex>, <condition_variable> и <future>.