- Давайте напишем Linux терминал
- Приветствие
- Что должен уметь наш терминал
- Как устроена работа терминала
- Немного про системный вызов fork()
- Подробнее про exec()
- Перейдем к полноценной реализации
- Часть 1. Чтение строки с консоли.
- Часть 2. Разбиение строки на токены.
- Часть 3. Выполнение процессов.
- Структура хранения списка запущенных процессов.
- Вспомогательные функции добавления процессов.
- Вспомогательные функции для завершения процессов.
- Непосредственно запуск процессов.
- Вспомогательные функции для терминала.
- Реализация вспомогательных функций.
- Часть 4. Главный цикл терминала.
- Часть 5. Итоговый результат.
- Краткое введение в терминалы и консоль
- Linux=Terminal?
- Тестовое окружение
- Создаем рабочий стол
- Вместо заключения
Давайте напишем Linux терминал
Приветствие
Всем привет! Хочу поделиться своим опытом написания собственного терминала Linux используя Posix API, усаживайтесь поудобнее.
Итоговый результат
Что должен уметь наш терминал
Запуск процессов в foreground и background режиме
Завершение background процессов из терминала
Поддержка перемещения по директориям
Как устроена работа терминала
Считывание строки из стандартного потока ввода
Разбиение строки на токены
Создание дочернего процесса с помощью системного вызова fork
Замена дочернего процесса на необходимый с помощью системного вызова exec
Ожидание завершения дочернего процесса (в случае foreground процесса)
Немного про системный вызов fork()
Простыми словами системный вызов fork создает полный клон текущего процесса, отличаются они лишь своим идентификатором, т. е. pid .
Что выведет данная программа:
I’m parent process!
I’m child process!
Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс.
Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 — мы имеем дело с дочерним процессом, если нет — с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0 .
Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL) , которая дожидается окончания дочернего процесса.
Подробнее про exec()
В документации есть различные вариации системного вызова exec , но одни отличаются только способом передачи токенов в параметры функции, смысл от этого не изменяется.
Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.
Что выведет данная программа
total 16
-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main
-rw-r—r— 1 runner runner 267 Jan 13 07:33 main.c
Родительский процесс как обычно ожидает завершения дочернего процесса. В это время после системного вызова exec происходит замена дочернего процесса на консольную утилиту ls , она была взята для примера.
Можно сказать мы реализовали простой терминал, вся логика заключается именно в этом.
Перейдем к полноценной реализации
Часть 1. Чтение строки с консоли.
Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей.
В данной функции происходит чтение строки с применением функции getline . После чего, если в конце строки имеется символ переноса строки, удаляем его.
Обратите внимание, что при чтении могут возникнуть ошибки, одна из них — нажатие сочетания клавиш ctrl-D . Однако это штатный случай завершения работы терминала, следовательно в данном случае не должна выводиться ошибка.
Часть 2. Разбиение строки на токены.
В данной части будет представлена реализация разбиения строки на массив токенов.
Определения начальной длины массива и разделителей строки.
Код выглядит довольно громоздким, однако в нем нет ничего сложного.
Очередной токен получается с использованием функции strtok . После чего данный токен копируется в массив токенов. Если в массиве токенов не достаточно места, массив увеличивается в 2 раза.
Завершается всё добавлением завершающего токена равного NULL , т. к. функция exec() ожидает наличие данного завершающего токена.
Часть 3. Выполнение процессов.
Структура хранения списка запущенных процессов.
Напишем определения структур для foreground и background процессов, fg_task и bg_task . А также определение структуры для хранения всех процессов tasks .
Создадим в коде глобальную переменную типа tasks , которая и будет хранить все наши запущенные процессы.
Вспомогательные функции добавления процессов.
Установка foreground процесса выглядит банально и не нуждается в комментировании.
Добавление background процесса выглядит посложнее.
На деле же все проще. Смотрим есть ли место в массиве для хранения информации о процессе. Если его недостаточно — увеличиваем длину массива в 2 раза. После чего происходит добавление структуры bg_task в массив и последующее заполнение полей структуры информацией о процессе.
Данная функция возвращает -1 в случае неудачи.
Вспомогательные функции для завершения процессов.
Добавим функцию экстренного завершения foreground процесса. Данная функция с помощью системного вызова kill с параметром SIGTERM завершает процесс по id процесса.
Также добавим функцию для завершения background процесса.
Данная функция принимает в себя массив токенов вида <"term", " ", NULL>. После чего преобразует токен индекса background задачи в число. Убивает background задачу посредством системного вызова kill .
Непосредственно запуск процессов.
Для удобства введем функцию is_background , определяющую является ли задача фоновым процессом. Данная функция просто проверяет наличие & в конце.
Введем функцию launch которая будет запускать background процесс если в конце присутствует токен & , иначе будет запускаться foreground процесс.
То, что происходит в этой функции уже должно быть все понятно.
Создается дубликат процесса с помощью системного вызова fork
Заменяем дочерний процесс на требуемый с помощью системного вызова exec
Определяем является ли процесс фоновым
Если процесс фоновый — просто добавляем его в список bacground задач
Если процесс не фоновый — дожидаемся окончания выполнения процесса
В функции присутствует неизвестная функция quit . Ее мы разберем в следующем блоке.
Вспомогательные функции для терминала.
Введем функцию execute , которая в зависимости от первого токена выбирает нужное действие.
Данная функция пропускает действие, если первый токен NULL . Переходит в директорию, если первый токен cd . Выводит справку о пользовании, если первый токен help . Завершает работу терминала, если первый токен quit . Выводит список background задач, если первый токен bg . Убивает процесс по индексу, если первый токен term .
Во всех других случаях запускается процесс.
Реализация вспомогательных функций.
Значение CONTINUE означает дальнейшее исполнение главного цикла терминала. Значение EXIT прерывает выполнение главного цикла программы.
Функция quit отключает все callback функции по событию SIGCHLD — т. е. функции, выполняющиеся когда дочерний элемент был завершен. После этого завершает все активные процессы.
Основные цвета терминала.
Часть 4. Главный цикл терминала.
Здесь и происходит вся магия. Взгляните на следующие строки. С помощью функции signal задаются callback функции на заданные события.
Событие SIGINT — срабатывает при нажатии комбинации ctrl-C , которое в дефолтном поведении завершает работу программы. В нашем же случае мы переназначаем его на завершение foreground процесса.
Событие SIGCHLD — срабатывает при завершении дочернего процесса созданyого с помощью системного вызова fork . В нашем случае мы переопределяем его на пометку фоновой задачи как выполненной с помощью функции mark_ended_task .
Все что описано в главном цикле терминала можно описать словами:
Вывод информации о пользователе и текущей директории с помощью функции display
Чтение строки из стандартного потока ввода
Разбиение строки на токены
Выполнение ранее описанной функции execute , которая в зависимости от массива токенов выполняет нужное нам действие.
Нам осталось реализовать одну оставшуюся функцию display . Которая получает информацию о текущей директории с помощью функции getcwd и имя пользователя с помощью функции getpwuid .
Часть 5. Итоговый результат.
Надеюсь, данный материал полезен. Если вам есть чем дополнить данный материал, буду рад услышать ваши мысли в комментариях.
С исходным кодом проекта вы можете ознакомиться по данной ссылке.
Источник
Краткое введение в терминалы и консоль
Тут живут драконы и нет ментейнеров (вместо эпиграфа)
Очень сжатое боевое описание «что есть псевдо-терминал» и «консоль».
Итак, во-первых, определимся с тем, что такое консоль: консоль это программа, которая принимает от пользователя кнопки, а от соединения с компьютером — буквы на экран и спец. команды по рисованию картинки на экране.
Терминал — обобщённое название как программы, так и специальной железки (таких больше нет в природе). Особое исключение — когда вы запускаете путти или ещё кого-то для подключения к последовательному порту коммутатора/рутера/модема (или даже сервера), то вы выступаете (точнее компьютер с путти выступает) в роли терминала (название «терминал» — потому что терминирует кабель (то есть висит на самом конце) ).
Как легко понять по кабелю, есть два конца: терминал и то, что ему посылает байтики в кабель.
Когда запускается шелл, то он получает в своё распоряжение виртуальное устройство /dev/tty, которое у каждого процесса своё — это «обратная» часть терминала. Передняя часть в случае кабеля реализуется путти+компьютер, в случае консоли компьютера, специализированным ядерным драйвером (виртуальными консолями). Важно: драйвер эмулирует наличие терминала.
Существует интерфейс unix98 (/dev/ptmx), который позволяет создать парочку из «входа и выхода» для всех обратившихся программ. В этом случае одна половинка изображает из себя «обратную» половинку, а вторая — консоль. Используется, в частности, openssh’ем и x’овыми терминалками.
В этом месте много путаницы, но правило простое: с одной стороны тот, кто рисует, с другой — тот, кто говорит, что рисовать. С той стороны, с которой рисуют, с той же находится и пользователь с клавиатурой. Его данные поступают на противоположную сторону к программе.
В качестве самой частой программы со стороны «программы» выступает getty разных видов (mgetty, agetty и т.д.), задачей которой является выставление скорости работы канала и запуск команды login для ввода пароля.
В качестве передней части (рисующей байтики) выступает либо виртуальная консоль линукса, либо программа-терминалка (почти любая), либо находящийся за последовательным шнурком терминал (например, другой компьютер).
Каждое устройство по рисованию (терминал, виртуальная консоль и т.д.) имеют свой набор возможностей (поддерживаемых символов и команд). Тип терминала задаётся переменной TERM, но только в смысле, что он говорит программам, с каким терминалом они работают. Невозможно переменной TERM заставить терминал эмулировать другие терминалы — он про эту переменную ничего не знает.
Внезапное применение: KVM, XEN (и, я не уверен, но, кажется, OpenVZ) используют этот механизм для доступа к консоли виртуальных машин: в виртуальной машине создаётся специализированное устройство, взаимодействующее с демоном в dom0. С точки зрения domU — у него есть последовательное устройство для терминала. Чтобы на этом устройстве увидеть строчку login, нужно прописать в inittab строчку с вызовом getty и разрешить логин (/etc/securetty).
В dom0 демон видит последовательное устройство от domU, создаёт соответствующий псевдотерминал с помощью /dev/ptmx). Созданные устройства для подключения терминалов появляются в /dev/pts/*. Помимо этого consoled пишет в xenstore кому какой домен соответствует, специализированные терминалки (xenconsole) умеют это читать и выполнять. Когда даётся команда xl console (xm console), то она всего лишь вызывает xenconsole с заданным параметром.
Вместо xenconsoled можно использовать любую другую терминалку — хоть minicom, хоть cu, хоть socat, хоть putty. После подключения к консоли правильным является задать тип используемой консоли (set TERM=linux или set TERM=xterm, установка в vt100 даст чёрно-белый экран).
Ровно так же можно организовать подключение к машине по модему (если кто их ещё помнит) — модемы просто «удлиняют» последовательный порт на N километров. С одной стороны нужно настроить модем на автоответ и повесить mgetty (и не забыть про /etc/securetty), с другой стороны нужно набрать номер и получить обычное сообщение login.
Это было очень сжатое и которое описание псевдотерминалов. Полное почти невозможно, ибо для полного понимания как это работает нужно начинать с телетайпов и устройства механических печатных машинок.
Основным моментом, который нужно осознать, является то, что в tty всегда есть два участника, один из которых умеет рисовать (сам, или выбрасывая содержимое на экран так, чтобы терминалка пользователя это нарисовала), а другой — даёт команды что рисовать и принимает последовательности нажатия кнопок.
Источник
Linux=Terminal?
Несколько десятилетий прошло с момента релиза первого рабочего стола пользователя, где с помощью манипулятора-мыши появилась возможность выбирать различные части интерфейса, взаимодействовать с ними, вводить данные. Это был прорыв, определивший, как будет выглядеть пользовательское взаимодействие с современной операционной системой еще долгие годы. Сегодня без этой фичи сложно представить пользовательскую операционную систему. Визуальный рабочий стол стал для большинства пользователей основным способом знакомства с компьютерными технологиями и операционной системой.
Для более продвинутого администрирования системы пользователю так или иначе придется сделать шаг назад и обратиться к специализированному и менее визуализированному интерфейсу — консоли. В разных операционных системах этот интерфейс может называться по разному. В текущей статье обратимся к Terminal-у в ОС Linux. Операционная система Linux до сегодняшнего дня сохраняет особое отношение к Terminal-у, без него не получится установить приложение, настроить часы, осуществить поиск машин в сети и так далее. Начинающему пользователю или администратору может быть сложно ориентироваться в мире Terminal без окон, поэтому цель данной статьи — рассказать, как можно заполучить от операционной системы хотя бы примитивный рабочий стол и даже запустить приложение, которое требует наличия окон, в терминале.
Тестовое окружение
Для демонстрации настроек и работы с системами операционной системы Linux будем использовать Ubuntu 20.04 Server версию. Необходимо установить базовую систему и создать обычного пользователя. В результате должно получиться нечто похожее на снимок ниже:
Для подготовки лабораторной операционной системы также потребуется установить дополнительное ПО на сервер. Это будет Xvfb и Xpra (их описание будет в статье далее). Установить их можно соответствующими командами:
apt-get update && apt-get -y install xvfb imagemagick (последний нужен для корректного преобразования памяти в картинку)
apt-get update && apt-get -y install xpra
Проверка и отчасти демонстрация результатов может производиться на любом хосте в сети с сервером. Для тестов в качестве второй машины использовалась операционная система Kali Linux, включенная в локальную сеть вместе с Ubuntu сервером.
Создаем рабочий стол
Инструменты xpra и xvfb были созданы в первую очередь для тестирования ОС и её окружения. Однако, с появлением целого сообщества этих инструментов, их использование значительно расширилось.
xvfb — приложение, которое создано для работы с графикой на тех системах, где даже на уровне железа может и не быть видеокарты или другого устройства вывода. Все операции инструмент проводит в оперативной памяти. Применение инструмента нашлось в рендеринге, фоновых операциях с графикой. Стоит заметить, что это ПО даёт только лишь возможность взглянуть на экран, но не имеет инструментов для взаимодействия с его интерфейсами. Поэтому, чтобы картинка менялась, придется немного покодить для автоматизации. К примеру, для браузера можно использовать скрипты для Selenium.
Продемонстрируем, как можно выстроить работу с помощью xvfb . Главной единицей взаимодействия для данной тулзы является устройство Screen или просто экран. По умолчанию существует только один экран с разрешением 1280x1024x8 (ШиринаХВысотаХГлубина). Выполним следующие команды на сервере:
1. Установим X11 приложения — apt install x11-apps . Нам понадобится калькулятор для демонстрации интерфейса.
2. Откроем экран для отрисовки калькулятора:
3. Запустим калькулятор на созданном экране — DISPLAY=$DISPLAY /usr/bin/xcalc &
4. Создадим из экрана картинку, которую можно будет просмотреть на клиенте —
Выполним команду на клиенте, чтобы увидеть результат:
Выглядит не слишком современно, но это уже больше, чем просто консоль. На снимке слева — калькулятор, который был захвачен из xvfb буфера, а справа — оригинальный калькулятор, который был запущен в клиентской системе. Вообщем-то ничего важного при визуализации потеряно не было, как будто просто черно-белый экран. Проверим, что можно сделать с помощью других инструментов.
Xpra в противовес инструменту xvfb имеет полный функционал по взаимодействию с системой. Можно сказать, это своеобразный аналог VNC, который может использовать в качестве транспорта протокол ssh, tcp и несколько других, достаточно лишь настроить на целевой системе сервер для приема соединения, а на клиенте использовать верный протокол для взаимодействия. Оставим эту настройку для читателя (тестирование на виртуальных машинах не рекомендуется, так как у ПО есть проблемы с поддержкой, виртуальные машины просто зависают). А в качестве второго эксперимента проведем печать интерфейса приложения прямо из запущенного контейнера Docker.
Для проведения этого теста нам понадобится установить Docker для операционной системы Ubuntu. Инструкции к его установке можно найти вот здесь. В инструкции достаточно подробное описание, потому перейдем сразу к этапу создания Image, который будет использоваться для контейнера целевой машины. Для создания контейнера нам нужно сделать файл “Dockerfile” и наполнить его следующими данными:
Для завершения эксперимента нужно снова запустить команды для создания файла png и скопировать его на клиентскую машину. В результате получаем такой же скриншот:
Вместо заключения
В очередной раз операционная система Linux доказала свою многогранность и возможность применять различные неожиданные решения. Новичкам нужно только найти правильный набор ПО, и с ним можно будет решить любую задачу — даже просмотр пользовательского интерфейса на сервере, в котором нет видеокарты.
Данная статья была написана в преддверии старта курса Administrator Linux. Basic от OTUS. Узнать подробнее о курсе и посетить бесплатный урок можно по этой ссылке.
Источник