Жизненный цикл процесса

Создание процесса

Вызов newpid=fork() создает новый процесс, являющейся точной копией текущего и отличающийся лишь возвращаемым значением newpid. В родительском процессе newpid равно PID дочернего процесса, в дочернем процессе newpid равно 0. Свой PID можно узнать вызовом mypid=getpid(), родительский – вызовом parentpid=getppid().

Типичный пример

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{
    perror("Страшная ошибка:");
}

Запуск программы

В оперативной памяти процесса находятся код и данные, загруженные из файла. При запуске программы из командной строки, обычно создается новый процесс и в его память загружается файл с программой. Загрузка файла делается вызовом одной из функций семейства exec (см. man 3 exec). Функции отличаются способом передачи параметров, а также тем, используется ли переменная окружения PATH для поиска исполняемого файла. Например execl в качестве первого параметра принимает имя исполняемого файла, вторым и последующими – строки аргументы, передаваемые в argv[], и, наконец, последний параметр должен быть NULL, он дает процедуре возможность определить, что параметров больше нет.

int pid=fork();
if( pid > 0 ){
     waitpid(NULL);
}else if( pid == 0 ) {
     if(-1 == execl("/bin/ls","ls","-l",NULL) ) {
          exit(1);
      }
}

Пример exec с двумя ошибками:

if( 0 == execl("/bin/ls","-l",NULL) ){
    printf("Программа ls запущена успешно\n");
}else{
    printf("Программа ls не запущена\n");
}

Ошибка 1: Первый аргумент передаваемый программе это имя самой программы. В данном примере в списке процессов будет видна программа с именем -l, запущенная без параметров.

Ошибка 2: Поскольку код из файла /bin/ls будет загружен в текущий процесс, то старый код и данные, в том числе printf("Программа ls запущена успешно\n"), будет затерты. Первый printf не сработает никогда.

Завершение процесса

Процесс может завершиться, получив сигнал или через системный вызов _exit(int status). status может принимать значения от 0 до 255. По соглашению, status==0 означает успешное завершение программы, а ненулевое значение - означает ошибку. Некоторые программы (например kaspersky для Linux) используют статус для возврата некоторой информации о результатах работы программы.

_exit() может быть вызван несколькими путями.

  • return status; в функции main(). В этом случае _exit() выполнит некая служебная функция, вызывающая main()
  • через библиотечную функцию exit(status), которая завершает работу библиотеки libc и вызывает _exit()
  • явным вызовом _exit()

Удаление завершенного процесса из таблицы процессов

После завершения процесса его pid остается занят - это состояние процесса называется "зомби". Чтобы освободить pid родительский процесс должен дождаться завершения дочернего и очистить таблицу процессов. Это достигается вызовом:

pid_t cpid=waitpid(pid_t pid, int *status, int options)
//или
pid_t cpid=wait(int *status)

Вызов wait(&status); эквивалентен waitpid(-1, &status, 0);

waitpid ждет завершения дочернего процесса и возвращает его PID. Код завершения и обстоятельства завершения заносятся в переменную status. Дополнительно, поведением waitpid можно управлять через параметр options.

  • pid < -1 - ожидание завершения дочернего процесса из группы с pgid==-pid
  • pid == -1 - ожидание завершения любого дочернего процесса
  • pid == 0 - ожидание завершения дочернего процесса из группы, pgid которой совпадает с pgid текущего процесса
  • pid > 0 - ожидание завершения любого дочернего процесса с указанным pid

Опция WNOHANG - означает неблокирующую проверку завершившихся дочерних процессов.

Статус завершения проверяется макросами:

  • WIFEXITED(status) - истина если дочерний процесс завершился вызовом _exit(st)
  • WEXITSTATUS(status) - код завершения st переданный в _exit(st)
  • WIFSIGNALED(status) - истина если дочерний процесс завершился по сигналу
  • WTERMSIG(status) - номер завершившего сигнала
  • WCOREDUMP(status)истина если дочерний процесс завершился с дампом памяти
  • WIFSTOPPED(status) истина если дочерний процесс остановлен
  • WSTOPSIG(status) - номер остановившего сигнала
  • WIFCONTINUED(status) истина если дочерний процесс перезапущен