programming

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

  1. fork() - создание процесса
  2. Готов к выполнению (чем дольше процесс в очереди, тем выше приоритет его выполнения)
  3. Выполнение в режиме ядра/пользователя (чем дольше процесс находится в состоянии выполнения, тем ниже его приоритет)
  4. _exit()
  5. wait() - только после вызова wait процесс переходит из состояния зомби в завершённый.

Начальная нагрузка (запуск компьютера)

Начальная загрузка - загрузка ядра системы в оперативную память, запуск ядра.

  • Подаётся питание на схемы компьютера
  • Запуск аппаратного загрузчика. Загрузчик в готовых к работе устройствах выбирает наиболее приоритетное.
    • В BIOS хранится информация об устройствах.
    • Внешнее запоминающее устройство - устройство, на котором может находиться система. Эти устройства ранжированы в приоритетном списке.
  • Чтение нулевого блока системного устройства аппаратным загрузчиком.
  • Запуск программного загрузчика конкретной ОС. (например, grub)
    • Этот загрузчик “знает” структуру системного диска
    • В общем случае, знает устройство файловой системы
    • Программный загрузчик ищет в корне файловой системы файл ядра /unix ()
  • Запуск /unix
    • Инициализация компонентов компьютера (установка часов, инициализация контроллера памяти и т.п.)
    • Инициализация системных структур данных (берётся из системного файла конфигурации с параметрами)
    • Инициализация нулевого процесса (ядра):
      • Не имеет кодового сегмента
      • Существует в течение всего времени работы системы

Инициализация системы

  • Создание ядром первого процесса
    • Копируется процесс 0 (запись таблицы процессов)
  • Создание области кода процесса 1
    • Копирование в область кода процесса 1 программы, реализующей системный вызов exec(), который необходим для выполнения /etc/init
  • Замена команды процесса 1 кодом из файла /etc/init (exec())
  • Подключение интерпретатора команд к системной консоли
  • Создание многопользовательской среды (чтение /etc/passwd)

getty - команда, устанавливающая терминальные соединения и порты и управляющая ими.

  • Схема дальнейшей работы

Легковесные процессы (threads/нити) Unix

  1. Реализация аппарата управления нитями - использование библиотеки функций в рамках полновесного процесса.
  2. Нити, созданные в разных полновесных процессах не пересекаются.
  3. Нити, созданные” внутри” процесса разделяют глобальные переменные и системные ресурсы, выделенные процессу. стек и регистры (включая счётчик команд) локализованы в рамках каждой нити.
  4. Идентификация нити TID (Thread ID) - значение предопределённого типа pthread_t. в различных системах TID может представляться по разному:
    • Long int/unsigned int/pthread

Pthread_create() - Создание нити

#include <pthread.h>
 
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void* (*start)(void*), void *arg)
  • tid - указатель на область памяти, в которой будет размещён идентификатор созданной нити.
  • attr - используется для задания свойств (атрибутов) нити.
  • start - имя функции (указатель на функцию) фиксированного формата, реализующей нить. Параметр типа void* и возвращаемое значение типа void*.
  • arg - аргумент для функции, указанной в start

Аппарат errno здесь не используется. 0 - ошибка, 0 - успех.

Операции с идентификатором нити tid

Сравнение идентификаторов нитей

int pthread_equal(pthread_t tid1, pthread_t tid2)
  • returns 0 if tid1 tid2

Получение tid

pthread_t pthread_self(void)
  • Возвращает tid вызвавшей нити

Pthread_exit() - Завершение нити

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

  • В случае выполнения из одной из нитей _exit() (или библиотечной exit())

Завершение текущей нити

void pthread_exit(void *val_ptr)
  • val_ptr - параметр, через который можно передать другим нитям, ожидающим завершения данной, информацию о завершении. (можно передать структуру и т.п., поэтому используется указатель).
  • Процесс завершается по завершении последней нити.

Завершение заданной нити

int pthread_cancel(pthread_t tid)
  • tid - thread id принудительно завершаемой нити.
  • Возвращает 0 в случае успеха.

Ожидание завершения нити

int pthread_join(pthread_t tid, void **val_ptr)
  • tid - идентификатор ожидаемой нити
  • val_ptr - указатель на переменную, в которой будет размещён код ответа (из pthread_exit(), например)
  • Возвращает 0 в случае успеха.

Блокирует вызвавшую нить до завершения указанной, аналогично wait() в процессах.

Только одна нить может дождаться завершения заданной нити.

Пример программирования нитей

Создаём несколько нитей, перемешиваем порядок их выполнения. Перед завершением каждой нити выводим глобальную и локальную переменные и смотрим разницу.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define NUM_THREADS 3
// общий счётчик
int shared_counter = 0;
 
void* th_fn(void *arg) {
	int local = 0;
	// перемешиваем работу нитей
	sleep(rand() % 4);
	
	// выводим переменные
	printf("Thread #%d with ID %#x: shared value is %d, local value is %d\n", (int)arg, pthread_self(), shared_counter++, local++);
	
	return NULL;
}
 
int main() {
	pthread_t th_id;
	int i, err;
	
	// создаём нити
	for (i = 0; i < NUM_THREADS; i++) {
		err = pthread_create(&th_id, NULL, th_fn, (void*)i);
		printf("Created thread with ID %#x\n", th_id);
	}
	
	sleep(10);
	return 0;
}

Второй пример программирования нитей

Создаём нить, засекаем время. Нить длится случайное время и завершается. Пишем время работы нити.

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <pthread.h>
 
void* th_fn(void *arg) {
	sleep(time(0) % 10);
	pthread_exit(NULL);
}
 
int main() {
	pthread_t th_id;
	void *pret;
	
	pthread_create(&th_id, NULL, th_fn, NULL);
	time_t tm_start = time(0);
	pthread_join(th_id, &pret);
	
	printf("Thread %#x execution time = %u\n", th_id, time(0) - tm_start);
	return 0;
}