Перейти к содержимому

Практика 2. Процессы, мониторинг и скрипты


Цель работы

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


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

Задание 1.1. Просмотр процессов

  1. Выведите список всех процессов в системе:

    Окно терминала
    ps aux

    Объясните значение столбцов: USER, PID, %CPU, %MEM, VSZ, RSS, STAT, START, COMMAND.

  2. Найдите процесс текущей оболочки:

    Окно терминала
    ps aux | grep $$
    echo "Мой PID: $$"
    echo "PID родителя: $PPID"
  3. Выведите дерево процессов:

    Окно терминала
    pstree -p

    Найдите в дереве systemd (PID 1). Какие процессы являются его прямыми потомками?

  4. Исследуйте процесс через /proc:

    Окно терминала
    cat /proc/$$/status | head -20
    cat /proc/$$/cmdline
    ls -la /proc/$$/fd/

    Что содержит каталог /proc/<pid>/fd/?

Задание 1.2. Мониторинг в реальном времени

  1. Запустите top и изучите его интерфейс:

    Окно терминала
    top
    • Нажмите P — сортировка по CPU.
    • Нажмите M — сортировка по памяти.
    • Нажмите 1 — показать загрузку по отдельным ядрам.
    • Нажмите q — выход.
  2. Установите и запустите htop (более удобный аналог):

    Окно терминала
    sudo apt install htop
    htop

    Сравните интерфейсы top и htop. Какие преимущества у htop?

  3. Просмотрите информацию о памяти и CPU:

    Окно терминала
    free -h
    uptime
    cat /proc/loadavg

    Что означают три числа load average?


Часть 2. Управление процессами

Задание 2.1. Фоновые процессы

  1. Запустите долгий процесс в фоне:

    Окно терминала
    sleep 300 &
    echo "PID фонового процесса: $!"
  2. Просмотрите список фоновых задач:

    Окно терминала
    jobs -l
  3. Запустите процесс на переднем плане и переведите в фон:

    Окно терминала
    sleep 200
    # Нажмите Ctrl+Z (приостановка)
    bg # Возобновить в фоне
    jobs
    fg # Вернуть на передний план
  4. Запустите процесс, который переживёт закрытие терминала:

    Окно терминала
    nohup sleep 600 &
    cat nohup.out

Задание 2.2. Сигналы

  1. Запустите фоновый процесс и отправьте ему сигналы:

    Окно терминала
    sleep 1000 &
    PID=$!
    echo "Запущен процесс $PID"
    # Отправить SIGTERM (мягкое завершение)
    kill $PID
    # или явно:
    kill -SIGTERM $PID
    # Проверить, жив ли процесс
    ps -p $PID
  2. Создайте «упорный» процесс и убейте его принудительно:

    Окно терминала
    # Запустим процесс, игнорирующий SIGTERM
    bash -c 'trap "" SIGTERM; while true; do sleep 1; done' &
    PID=$!
    kill $PID # Не сработает (SIGTERM перехвачен)
    ps -p $PID # Процесс жив
    kill -9 $PID # SIGKILL — нельзя перехватить
    ps -p $PID # Процесс завершён
  3. Отправьте сигнал SIGUSR1 и перехватите его:

    Окно терминала
    bash -c '
    trap "echo Получен SIGUSR1!" SIGUSR1
    echo "Мой PID: $$"
    while true; do sleep 1; done
    ' &
    PID=$!
    kill -SIGUSR1 $PID
    # В выводе появится "Получен SIGUSR1!"
    kill $PID

Задание 2.3. Приоритеты

  1. Запустите процесс с пониженным приоритетом:

    Окно терминала
    nice -n 10 sleep 300 &
    ps -o pid,ni,comm -p $!
  2. Измените приоритет работающего процесса:

    Окно терминала
    sleep 300 &
    PID=$!
    renice 5 -p $PID
    ps -o pid,ni,comm -p $PID
  3. Попробуйте установить отрицательный nice (потребуется sudo):

    Окно терминала
    sleep 300 &
    PID=$!
    sudo renice -5 -p $PID
    ps -o pid,ni,comm -p $PID

Часть 3. Каналы и IPC

Задание 3.1. Именованные каналы (FIFO)

  1. Создайте именованный канал:

    Окно терминала
    mkfifo /tmp/mypipe
    ls -la /tmp/mypipe # Обратите внимание на тип файла (p)
  2. Откройте два терминала. В первом:

    Окно терминала
    echo "Привет из терминала 1!" > /tmp/mypipe

    Во втором:

    Окно терминала
    cat < /tmp/mypipe

    Обратите внимание: echo блокируется, пока кто-то не прочитает из канала.

  3. Удалите канал:

    Окно терминала
    rm /tmp/mypipe

Задание 3.2. Трассировка системных вызовов

  1. Проследите системные вызовы простой команды:

    Окно терминала
    strace ls /tmp 2>&1 | head -30

    Найдите вызовы openat, read, write, close. Что делает каждый?

  2. Проследите системные вызовы при создании процесса:

    Окно терминала
    strace -f bash -c 'echo hello' 2>&1 | grep -E '(clone|exec|write)'

    Найдите вызовы clone (аналог fork) и execve.

  3. Подсчитайте количество каждого типа системных вызовов:

    Окно терминала
    strace -c ls /tmp 2>&1

Часть 4. Bash-скрипты

Задание 4.1. Основы скриптов

  1. Создайте файл ~/project/src/main/hello.sh:

    #!/bin/bash
    # Переменные
    NAME="Студент"
    COURSE="Операционные системы"
    echo "Привет, $NAME!"
    echo "Добро пожаловать на курс: $COURSE"
    echo "Текущая дата: $(date)"
    echo "Вы работаете на: $(hostname)"
    echo "Ваш пользователь: $(whoami)"
  2. Сделайте скрипт исполняемым и запустите:

    Окно терминала
    chmod +x ~/project/src/main/hello.sh
    ~/project/src/main/hello.sh

Задание 4.2. Аргументы и условия

Создайте скрипт ~/project/src/main/check_file.sh:

#!/bin/bash
# Проверка аргументов
if [ $# -eq 0 ]; then
echo "Использование: $0 <путь_к_файлу>"
exit 1
fi
FILE=$1
if [ -e "$FILE" ]; then
echo "Файл '$FILE' существует."
if [ -f "$FILE" ]; then
echo " Тип: обычный файл"
echo " Размер: $(stat --format=%s "$FILE") байт"
echo " Строк: $(wc -l < "$FILE")"
elif [ -d "$FILE" ]; then
echo " Тип: каталог"
echo " Элементов: $(ls -1 "$FILE" | wc -l)"
elif [ -L "$FILE" ]; then
echo " Тип: символическая ссылка → $(readlink "$FILE")"
fi
echo " Права: $(stat --format=%A "$FILE")"
echo " Владелец: $(stat --format=%U "$FILE")"
else
echo "Файл '$FILE' не существует."
exit 2
fi

Протестируйте скрипт на разных объектах:

Окно терминала
chmod +x ~/project/src/main/check_file.sh
~/project/src/main/check_file.sh /etc/passwd
~/project/src/main/check_file.sh /tmp
~/project/src/main/check_file.sh /nonexistent

Задание 4.3. Циклы и функции

Создайте скрипт ~/project/src/main/process_report.sh:

#!/bin/bash
# Функция вывода разделителя
separator() {
echo "$(printf '=%.0s' {1..50})"
}
# Функция подсчёта процессов пользователя
count_user_processes() {
local user=$1
ps -u "$user" --no-headers | wc -l
}
separator
echo "ОТЧЁТ О ПРОЦЕССАХ"
echo "Дата: $(date)"
separator
echo ""
echo "Всего процессов: $(ps aux --no-headers | wc -l)"
echo "Загрузка CPU: $(uptime | grep -oP 'load average: \K.*')"
echo "Память:"
free -h | grep Mem | awk '{printf " Всего: %s | Использовано: %s | Свободно: %s\n", $2, $3, $4}'
echo ""
echo "Топ-5 процессов по CPU:"
ps aux --sort=-%cpu --no-headers | head -5 | awk '{printf " PID: %-8s CPU: %-6s MEM: %-6s CMD: %s\n", $2, $3, $4, $11}'
echo ""
echo "Топ-5 процессов по памяти:"
ps aux --sort=-%mem --no-headers | head -5 | awk '{printf " PID: %-8s CPU: %-6s MEM: %-6s CMD: %s\n", $2, $3, $4, $11}'
echo ""
echo "Процессы по пользователям:"
for user in $(ps aux --no-headers | awk '{print $1}' | sort -u); do
count=$(count_user_processes "$user")
printf " %-15s %d процессов\n" "$user" "$count"
done
separator

Мини-проект: скрипт мониторинга системы

Напишите скрипт ~/project/src/main/monitor.sh, который:

  1. Принимает аргумент — интервал в секундах (по умолчанию 5).
  2. Каждые N секунд записывает в файл ~/project/logs/monitor.csv строку:
    timestamp,cpu_usage,mem_total,mem_used,mem_percent,proc_count
  3. При первом запуске создаёт заголовок CSV.
  4. Выводит на экран текущие показатели в удобном формате.
  5. По нажатию Ctrl+C (сигнал SIGINT) выводит итоговую статистику:
    • Сколько замеров было сделано.
    • Средняя загрузка CPU.
    • Максимальное использование памяти.
    • Время работы скрипта.
  6. Корректно завершается.

Подсказки:

  • Загрузку CPU можно получить из top -bn1 | grep "Cpu(s)" или из /proc/stat.
  • Информацию о памяти — из free.
  • Для перехвата Ctrl+C используйте trap.
  • Для подсчёта среднего используйте awk.

Шаблон:

#!/bin/bash
INTERVAL=${1:-5}
LOG_FILE=~/project/logs/monitor.csv
COUNT=0
START_TIME=$(date +%s)
# Заголовок CSV
echo "timestamp,cpu_usage,mem_total,mem_used,mem_percent,proc_count" > "$LOG_FILE"
# Обработчик Ctrl+C
cleanup() {
echo ""
echo "=== Итоги мониторинга ==="
echo "Замеров: $COUNT"
# TODO: вычислить среднюю загрузку CPU и макс. память
ELAPSED=$(($(date +%s) - START_TIME))
echo "Время работы: ${ELAPSED} сек"
exit 0
}
trap cleanup SIGINT
# Основной цикл
while true; do
# TODO: собрать метрики
# TODO: записать в CSV
# TODO: вывести на экран
COUNT=$((COUNT + 1))
sleep "$INTERVAL"
done

Контрольные вопросы

  1. Чем процесс отличается от программы? Какую информацию содержит PCB?
  2. Какие состояния может иметь процесс в Linux? Что означают коды в столбце STAT команды ps?
  3. Чем SIGTERM отличается от SIGKILL? Какой из них можно перехватить?
  4. Что такое PID 1? Какую роль он играет в системе?
  5. Как работает механизм fork() + exec() в Unix? Что такое Copy-on-Write?
  6. Чем отличается фоновый процесс (&) от процесса, запущенного через nohup?
  7. Что показывает strace? Для чего он используется?
  8. Объясните разницу между nice и renice.
  9. Что такое именованный канал (FIFO)? Чем он отличается от обычного pipe (|)?
  10. Что такое load average? Как интерпретировать значения 0.5, 1.0, 4.0 на одноядерной и четырёхъядерной системе?