Skip to content

Практическая работа по теме: "Процессы и нити ядра Linux"

Цель работы:
Освоить практические навыки работы с процессами, нитями, механизмами синхронизации, межпроцессным взаимодействием и планированием в Linux на основе пройденной теории.


Подготовка

  1. Установите ПО:
    • Компилятор gcc, библиотека pthread (sudo apt install build-essential).
    • Утилиты: htop, strace, lsof, netcat (sudo apt install htop strace lsof netcat).
  2. Все команды и код выполняются в терминале Linux (рекомендуется использовать Ubuntu/WSL).

Задание 1. Исследование процессов и нитей

Цель: Сравнить процессы и нити, изучить их создание и управление.

Шаги:

  1. Создание процесса через fork()

    c
    // process.c
    #include <unistd.h>
    #include <stdio.h>
    
    int main() {
        pid_t pid = fork();
        if (pid == 0) {
            printf("[Child] PID: %d, PPID: %d\n", getpid(), getppid());
        } else {
            printf("[Parent] PID: %d, Child PID: %d\n", getpid(), pid);
        }
        return 0;
    }
    • Скомпилируйте: gcc process.c -o process.
    • Запустите: ./process.
    • Наблюдайте за процессами:
      bash
      ps aux | grep process  # Найдите PID процесса и его потомка
      top -p $(pgrep -d ',' -f process)  # Отслеживайте использование CPU
  2. Создание нитей через pthread

    c
    // thread.c
    #include <pthread.h>
    #include <stdio.h>
    
    void* thread_func(void* arg) {
        printf("[Thread] TID: %lu\n", (unsigned long)pthread_self());
        return NULL;
    }
    
    int main() {
        pthread_t tid;
        pthread_create(&tid, NULL, thread_func, NULL);
        pthread_join(tid, NULL);
        return 0;
    }
    • Скомпилируйте: gcc thread.c -o thread -lpthread.
    • Запустите: ./thread.
    • Сравните использование ресурсов:
      bash
      htop  # В режиме просмотра нитей (F2 → Other Options → Set Left/Right Columns → добавьте NLWP)

Вопросы для самоконтроля:

  • Чем отличается PID процесса от TID нити?
  • Почему создание нити эффективнее создания процесса?

Задание 2. Race Condition и взаимоисключение

Цель: Продемонстрировать race condition и решить её с помощью мьютекса и семафора.

Шаги:

  1. Race Condition без синхронизации

    c
    // race.c
    #include <pthread.h>
    #include <stdio.h>
    
    int counter = 0;
    
    void* increment(void* arg) {
        for (int i = 0; i < 100000; i++) counter++;
        return NULL;
    }
    
    int main() {
        pthread_t t1, t2;
        pthread_create(&t1, NULL, increment, NULL);
        pthread_create(&t2, NULL, increment, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        printf("Counter: %d (ожидалось 200000)\n", counter);
        return 0;
    }
    • Скомпилируйте и запустите: gcc race.c -o race -lpthread && ./race.
    • Наблюдение: Значение counter будет меньше 200000 из-за race condition.
  2. Решение через мьютекс
    Замените тело increment на:

    c
    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    void* increment(void* arg) {
        for (int i = 0; i < 100000; i++) {
            pthread_mutex_lock(&lock);
            counter++;
            pthread_mutex_unlock(&lock);
        }
        return NULL;
    }
    • Перекомпилируйте и запустите. Убедитесь, что counter = 200000.
  3. Решение через семафор

    c
    #include <semaphore.h>
    sem_t sem;
    void* increment(void* arg) {
        for (int i = 0; i < 100000; i++) {
            sem_wait(&sem);
            counter++;
            sem_post(&sem);
        }
        return NULL;
    }
    int main() {
        sem_init(&sem, 0, 1);  // Инициализация семафора
        // ... создание потоков ...
        sem_destroy(&sem);
    }
    • Скомпилируйте с флагом -lrt: gcc -o sem race.c -lpthread -lrt.

Вопросы для самоконтроля:

  • Почему мьютекс подходит для критической секции, а семафор — для управления доступом к ресурсам?
  • Как аппаратная поддержка (например, инструкция xchg) помогает в реализации мьютекса?

Задание 3. Межпроцессное взаимодействие (IPC)

Цель: Организовать обмен данными через каналы и сокеты.

Шаги:

  1. Неименованный канал (pipe)

    bash
    ls -l | grep ".c"  # Данные из ls передаются в grep через pipe
  2. Именованный канал (FIFO)

    bash
    mkfifo my_pipe      # Создание FIFO
    # В терминале 1:
    cat > my_pipe
    # В терминале 2:
    cat my_pipe         # Введите текст в терминале 1 — он появится в терминале 2
  3. UNIX-сокет (локальный)

    bash
    # Сервер:
    nc -U -l socket.sock
    # Клиент:
    nc -U socket.sock
    • Введите текст в клиенте — он отобразится на сервере.

Вопросы для самоконтроля:

  • В чём разница между неименованным и именованным каналом?
  • Как сообщения в IPC связаны с понятием «монитора»?

Задание 4. Тупики (Deadlock)

Цель: Создать и обнаружить тупик, предложить решение.

Шаги:

  1. Создание тупика

    c
    // deadlock.c
    #include <pthread.h>
    pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER;
    pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER;
    
    void* thread1(void* arg) {
        pthread_mutex_lock(&lock1);
        sleep(1);
        pthread_mutex_lock(&lock2);
        pthread_mutex_unlock(&lock2);
        pthread_mutex_unlock(&lock1);
        return NULL;
    }
    
    void* thread2(void* arg) {
        pthread_mutex_lock(&lock2);
        sleep(1);
        pthread_mutex_lock(&lock1);
        pthread_mutex_unlock(&lock1);
        pthread_mutex_unlock(&lock2);
        return NULL;
    }
    
    int main() {
        pthread_t t1, t2;
        pthread_create(&t1, NULL, thread1, NULL);
        pthread_create(&t2, NULL, thread2, NULL);
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        return 0;
    }
    • Запустите программу: она зависнет из-за deadlock.
    • Найдите PID: ps aux | grep deadlock.
    • Проанализируйте блокировку:
      bash
      strace -p <PID>  # Ищите вызовы futex (системный вызов для мьютексов)
  2. Решение тупика
    Измените порядок захвата мьютексов в thread2:

    c
    void* thread2(void* arg) {
        pthread_mutex_lock(&lock1);  // Сначала lock1, как в thread1
        sleep(1);
        pthread_mutex_lock(&lock2);
        // ...
    }
    • Перекомпилируйте и убедитесь, что программа завершается.

Вопросы для самоконтроля:

  • Какие 4 условия возникновения тупика продемонстрированы в примере?
  • Как метод «иерархии ресурсов» предотвращает тупики?

Задание 5. Планирование процессов

Цель: Изменить приоритеты и политики планирования.

Шаги:

  1. Изменение приоритета через nice

    bash
    nice -n 10 ./process  # Запуск с приоритетом 10
    renice -n 5 -p <PID>  # Изменение приоритета запущенного процесса
    top  # Столбец NI показывает nice-уровень
  2. Real-time планирование через chrt

    bash
    chrt -r 50 ./process  # Запуск с политикой SCHED_FIFO и приоритетом 50
    chrt -m  # Показать диапазон приоритетов для real-time
  3. Анализ политики планирования

    bash
    cat /proc/<PID>/sched  # Политика и приоритет процесса

Вопросы для самоконтроля:

  • В чём разница между вытесняющим и невытесняющим планированием?
  • Почему для real-time процессов используется приоритет выше, чем для обычных?

Отчёт по работе

  1. Для каждого задания предоставьте:
    • Скриншоты терминала с результатами выполнения.
    • Ответы на вопросы для самоконтроля.
  2. Выводы:
    • Как аппаратные механизмы (например, атомарные операции) поддерживают взаимоисключение?
    • Почему игнорирование проблемы тупиков недопустимо в критических системах?

Дополнительные материалы

  • Мануалы: man 2 fork, man 7 pthreads, man 2 sem_overview.
  • Для углубления: Изучите исходный код планировщика CFS в ядре Linux (файл kernel/sched/fair.c).

Преподаватель проверяет:

  • Корректность выполнения всех заданий.
  • Понимание связи между теорией и практикой (через ответы на вопросы).

Успешной работы! 😊

Контакты: bystrovno@basealt.ru