Вы находитесь на странице: 1из 7

1 из 7

Основы UNIX и разработки под


UNIX. Базовое описание
стандарта POSIX.
Вводная часть, можно пропустить ;)
UNIX это стандарт индустрии разработки операционных систем (ОС)
де-факто уже с 1960-ых годов. К его разработке приложили руку такие
гиганты General Electric, MIT (Массачусетский Институт Технологий) и
Bell Labs (изначально Bell Telephone Laboratories, а сейчас AT&T -
крупнейшая в индустрии телефонии и стандартов протоколов связи
компания, сотовый оператор в США). Про историю UNIX можно сказать
много, но остановимся на том, что это просто стандарт, который важно
знать даже сейчас.
К UNIX системам относятся, если не перечислять "кости мамонтов",
такие как SunOS, хоть и используемые и поныне в узких
промышленных системах, следующие свободные ОС - FreeBSD,
OpenBSD, GNU/Linux и сотни других форков (от "fork" - "вилка") и
клонов, таких как современный macOS (и все его ответвления - iOS,
tvOS, iPadOS, watchOS и прочие).
К проприетарным, т.е. закрытым, без доступа к исходным кодам
широкой аудитории - BSD (Berkeley Software Distribution - на самом деле
уже тоже "умерший мамонт", разрабатывался с нуля в Университете
Калифорнии, Беркли), HP-UX (Hewlett Packard Enterprise), Solaris, SCO
UnixWare, macOS (частично открыт из-за использования большой части
ядра FreeBSD, открыто, например, базовое ядро XNU, некоторые
драйвера и часть пользовательских приложений), IBM AIX и многие
другие.
BSD интересен тем, что дал жизнь новым ОС - FreeBSD и OpenBSD, а
также мотивировал Торвальдса и Столмана разработать открытую ОС
- скрещивание которых и породило GNU/Linux. Кстати GNU, Linux и
XNU любят утверждать, что они не UNIX - GNU=GNU is NOT UNIX или
Linux - Linux Is Not UNUX (т.е. уже в названии это подчеркнули - такие
названия это рекурсивные акронимы - акронимы в которых есть
косвенное или прямое указание на самого себя). Они относятся к UNIX-
like ОС - "похожи на UNIX". Ближе всего к POSIX является XNU, хотя
официальную сертификацию не получал, имеет в своей истории
разработки двух прямых предков из мира "чистого" UNIX.
Именно в процессе разработки UNIX и появился язык C. На нем были
созданы компиляторы (включая свой собственный - исходные коды
компилятора C, написаны тоже на C), ядра ОС (за исключением
нескольких ассемблерных частей), пользовательские и системные
утилиты.
2 из 7
Что из этого всего следует? Разработчик, системный администратор
или простой пользователь UNIX-подобной ОС всегда разберется с
другой подобной ОС, именно поэтому это и стало стандартом. Есть и
десятки тысяч других ОС, самая популярная из которых Windows -
совершенно иная концепция как построения ядра, так и свой терминал,
свое представление о графическом пользовательском интерфейсе,
различные RTOS, закрытые ОС военной или промышленной отрасли.
Подготовка специалиста самая дорогая часть оплаты его работы.
Поэтому UNIX так и набрал обороты. Но тут надо добавить, что
Microsoft тоже пошел путем стандартизации и человек который когда-
либо программировал что-то кросс-платформенное это знает, если это
был, например, сетевой проект. Сокеты UNIX очень похожи на те, что
используются в Windows, хотя есть и важные отличия, включая
способы обработки сигналов, инициализация подсистемы WinSock
(ws2_32.dll и winsock.dll). Также есть проекты MinGW (Minimalist GNU for
Windows) и Cygwin, предназначенные для облегчения автоматизации и
работы в Windows, человека из мира UNIX, а также для сборки из
исходных кодов (если, конечно, получится, иногда требуется очень
много времени и опыт разработки в обеих системах) проектов из UNIX.
В тоже время, пока токсичное сообщество UNIX ругало проекты
Microsoft, такие как COM (Component Object Model), ActiveX/OLE (Object
linking and embedding), .NET Framework, они постепенно начали
кочевать и в разработку UNIX. Кроссплатформенность .NET вызывала
смех, потому что эта самая кроссплатформенность была только внутри
разных ОС самой Microsoft. Сейчас это не так, сообщество
программистов по всему миру постаралось, да и Microsoft не мало
приложили усилий, чтобы сейчас почти на каждом компьютере стояло
что-то, написанное на C#, VB.NET (сильно реже) или Borland Delphi
(который тоже стал делать компиляторы под .NET). Проекты mono,
Xamarin (выросший из mono), NetBox и другие. Т.е. ОС друг на друга
безусловно влияют, причем в обе стороны и это хорошо ощущается. В
любом случае чтобы увидеть всю картину целиком, надо глубоко
анализировать рынок продуктов под эти ОС, учесть аспекты мобильной
разработки и встраиваемых устройств (Embedded Development). Сюда
же относится и Java (шутки про то, что C# это Microsoft JAVA в сторону).
Она наоборот из Oracle, который всеми силами голосует за UNIX,
пришла в Microsoft и другие ОС.
Но единственным языком программирования, на котором пишут ядра
ОС, основные компоненты низкоуровневых интерфейсов этих ОС -
был, есть и остается язык Си. Про компиляторы так уже сказать
нельзя, но когда-то это тоже имело место быть. В интернете можно
прочитать огромное количество негативных мнений про это язык и его
старшего брата C++, однако это незрелые мнения юных
"революционеров" ("Кто в молодости не был революционером, у того
нет сердца, но тот, кто остался им и в старости, у того нет разума", У.
3 из 7
Черчилль), равно как и мнения, что все и всегда надо писать только на
C/C++. Многие языки хороши и каждому есть свое место (даже
Brainfuck подобным). Знание тройки C/C++ и python считается
отличным качеством современного разработчика - его легко
подготовить к специфике работы в конкретной области - на другом
языке, или на этих, но с огромным количеством научных материалов
или инженерной специфики работы со сложными механизмами
(промышленный сектор, embedded-разработка). Python в этом списке
по причине популярности его использования в рамках внутренного
скриптового языка крупного программного проекта (lua остается
уделом игровой индустрии и неудобно расширяется), использования
его в качестве построения сложных билдовых систем (build system), а
также, и это основная причина - это пример функционального языка.
Императивные и декларативные парадигмы программирования
являются двумя основными. Строго говоря, python лишь частично
функциональный язык (еще более строго - все языки не относятся
100% к одной парадигме, но какая-то преобладает). В нем есть
элементы императивного, процедурного, структурного,
метапрограммирования и даже объектно-ориентированного стилей
разработки (ООП). Как функциональный язык он не самый удачный
пример, но общее понимание у человека сложится, а там и Haskell
быстро освоит, и OCaml если потребуется.
POSIX
Выше обсуждались C/C++ и UNIX и их важность в современном IT
мире. Что же такое POSIX и причем тут он? Мы уже приближаемся к
технической части задания, но сначала освежим или получим знания,
или убедимся в понимании следующих постулатов и основ системы
POSIX.
POSIX расшифровывается как Portable Operating System Interface, из
чего мы сразу понимаем, что речь о таких вещах, например, как
сетевой обмен между разными устройствами, интерфейс командной
строки, принципы работы в ней и наименования основных команд.
Погружаясь более глубоко, становится понятно, что для этих двух
концепций мы должны еще определить работу самих программ - IPC
(Inter Process Communication, все что касается создания процессов,
связи между ними, общая память, обработка сообщений, сигналы,
семафоры и мьютексы, пайпы), порты ввода/вывода (I/O Ports),
расширение библиотеки языка C (язык C включает свой набор
библиотек, но крайне ограниченный, POSIX же расширяет работу в ОС,
но ломает кроссплатформенность стандарта языка C). Сразу о
терминах.
"Пайп" (pipe). Простой пример FIFO канала (First In First Out). Пайпы
бывают именованные и неименованные. Неименованные нужны для
одноразовых связей между двумя процессами. Простой пример,
команда получения строк содержащих слово "world" - cat example.txt |
4 из 7
grep "world" - здесь вертикальная черта сообщает нашей командной
строке (shell, "оболочка") о том что надо вывод STDOUT перенаправить
через пайп в следующую команду - в ее STDIN. Именованные же имеют
больший срок жизни, они продолжают существовать до тех пор, пока
существует каталог, в котором они находятся, или пока не был явно
удален соответствующий файл. Если каталог не относится к
временным или подключаемым из внешнего устройства или сетевого
диска - после рестарта компьютера пайп тоже будет еще
существовать. Расширим предыдущий пример на основе применения
пайпов в shell - если нам нужен обмен между двумя процессами в обе
стороны, это уже менее тривиально сделать в командной строке без
именованного канала. Допустим мы хотим передавать текстовые
сообщения своему другу из другого города, не используя Telegram (а
еще Telgeram или Whatsapp сложно поставить на пылесос или чайник -
анекдоты и шутливые комментарии про "звоню с утюга" перестали
быть шуткой - сейчас компьютер, хоть и примитивный есть очень много
где - это один из подвидов Embedded - IoT - "интернет вещей"). Для
этого воспользуемся утилитой netcat - сокращенно nc. Мы или наш друг
запускаем nc -p 4000 после чего IP адрес сервера сообщается второму
абоненту - он вводит nc <ip> 4000 (здесь 4000 это номер порта,
который выбрали когда создавали сервер). Если у нас нет друга с
белым IP, опытом настройки VPN или иного другого способа проброса
соединения через NAT (все никак не дождемся IPv6), или если хотим
убедиться в работоспособности технике у себя на компьютере, мы
открываем два терминала и вводим в одном nc -p 4000, а в другом nc
127.0.0.1 400. Все что вводится в один, появляется в другом, если
нажать Enter. Если бы мы захотели на другой стороне иметь
возможность редактирования файлов, это уже задача посложнее.
Создаем сервер открывающий файл example.txt и подключаемся к нему
уже известным способом - две команды - vi example.txt | nc -l 4000 и nc
127.0.0.1 4000. Первая выдаст предупреждение, что вывод происходит
с перенаправлением, а не в терминал. А вторая не дает нам
возможности редактирования. Почему так произошло? Неименованный
канал здесь создал связь STDOUT -> STDIN - т.е. вывод на терминал
программы vi произошел в netcat, ввод netcat был направлен по
сетевому сокету в другой netcat - и мы увидели окно программы vi в
клиенте. При этом все, что мы пишем и отправляем в клиентский netcat
просто выводится на экран на сервер. Требуется перенаправить вывод
из netcat на ввод vi.
Но как? Команда vi example.txt |< nc -p 4000 синтаксически неверна в
UNIX shell. Для этого надо создать еще один пайп, на этот раз
именованный. Команда будет выглядеть уже как 2 команды, потому что
пайп потребуется еще предварительно создать. Для этого есть
команда mk fo. В паре на стороне сервера это будет выглядеть так -
mk fo /tmp/ fo и vi example.txt < /tmp/ fo | nc -l 4000 > /tmp/ fo (тут
fi
fi
fi
fi
fi
5 из 7
добавляется перенаправление ввода для vi из пайпа /tmp/ fo - в его
STDIN, а STDOUT у netcat попадает на вход /tmp/ fo). Во втором
терминале все также набираем nc 127.0.0.1 4000, переводим vi в режим
ввода печатаем "i", а затем "hello". Далее ESC и ":wq" - пишем и
сохраняем. Оба netcat остановятся по необработанному сигналу
SIGPIPE (сигнал закрытия пайпа), а если в том же каталоге где мы
запускали сервер сделаем команду cat example.txt - увидим hello.
Пример, конечно, не самый удачный, для выполнения удаленных
команд обычно используют не трансляцию shell или shell-команд в пайп
по сети, а другие специальные инструменты - ssh, scp, putty/getty, VNC,
RDP и еще сотни RAT утилит (TeamViewer, AnyDesk). Вся магия в том, что
пайп не позволяет передавать количество строк и столбцов, обработка
сигналов введенных с клавиатуры частично транслируется в
служебные коды в текстовом формате, а частично передается
процессам только локально - CTRL+C и CTRL+D, например, передать
не выйдет, без предварительно настроенного терминала. Более
сложные графические команды, работа мыши - это не текст, который
можно туда-сюда отправлять. Пайп это просто канал данных,
буфферизованный, причем этот буффер только в оперативной памяти.
Если мы сейчас сделаем вывод в наш именованный канал echo
"example" > /tmp/ fo, можно его получить обратно через cat /tmp/ fo - и
мы выполним эту команду позже - т.е. данные не потерялись. Но если
между ними будет перезагрузка ОС - данные потеряются. Размер
пайпа всегда показан как 0. Терминал же это более сложный
инструмент, у него тоже есть буферы ввода/вывода, но кроме этого он
управляет сигналами, отрисовкой графических окон. Если мы видели
цветные символы с трюком с пайпированием vi в сетевой сокет - это
сделал за нас тот терминал, где эти команды мы вводили. Так работать
с терминалом или командами с User-Interaction (интерактивные
команды) крайне неудобно. Зато данный пример хорошо
демонстрирует эти проблемы и появляется понимание, зачем тот же
SSH протокол имеет несколько типов передаваемых данных - сигналы,
команды, протокол X11 (графическая подсистема), авторизация. В тоже
время какое-то подобие удаленного управления таким простым
способом можно получить. Для полноценного управления нужен TTY и
что характерно, если пайп это односторонний канал - нам требуется 2
пайпа чтобы связать ввод/вывод двух программ, то TTY - может сразу
управлять и вводом и выводом.
"Сигнал" (signal). Выше мы столкнулись с концепцией сигналов, тем,
что они могут иметь свое текстовое представление (хоть и не относятся
к printable - печатаемых символам). Сигналы часть IPC в POSIX - они
позволяют контролировать процессы, сообщать им важную
информацию. Основные сигналы которые часто используются:
fi
fi
fi
fi
6 из 7
SIGPIPE - сигнал закрытия дескриптора пайпа, не обработка данного
сигнала иногда ведет к завершению процесса (если закрыт пайп или
сокет на чтение, попытка записи генерирует этот сигнал)
SIGSEGV - очень плохой сигнал - программа аварийно завершается,
сигнал нельзя игнорировать, произошло исключение segmentation fault
- как правило следствие адресной арифметики в C и ошибке алгоритма
обращения с памятью
SIGINT - interrupt, прерывание, можно игнорировать если поставить
свой обработчик или флаг SIGIGN, сигнал порождается, например,
если зависшую или долго работящую команду мы прерываем через
CTRL+C
SIGTERM - terminate, завершение, можно игнорировать, если
поставить свой обработчик или флаг SIGIGN
SIGKILL - kill, убийство процесса, сигнал, который нельзя пропустить
- программа завершится в любом случае (kill -9 <PID> посылает этот
сигнал по номеру процессора, killall -9 <NAME> - по имени завершает
группу процессов)
SIGUSR1, SIGUSR2 - определяемые пользователем сигналы (USER1 и
USER2), в данном случае под пользователем имеется ввиду
программист, который определил этим сигналам смысл (например,
общая память готова - ее можно писать или читать)
SIGCHLD - дочерний процесс, созданный с помощью системного
вызова fork, завершился
К редко используемым сигналам относятся - SIGABRT, SIGALRM,
SIGFPE, SIGQUIT, SIGHUP, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN,
SIGTTOU, SIGBUS, SIGPOLL, SIGPROF, SIGSYS, SIGTRAP, SIGURG,
SIGVTALRM, SIGXCPU, SIGXFSZ
Выше упомянутый CTRL+D сигналом не является, а шлет
специальный служебный символ EOF - end of le, в некоторых случаях
он обрабатывается прозрачно - без возможности его физически
считать (не попадает во внутренний буффер STDIN, перехватывается с
выполнением особых действий с STDIN или игнорируется).
Задача
Для улучшения понимания концепций POSIX в университетах часто
дают задачу разработки собственного интерпретатора команд - shell.
Для реализации требуется использование следующих функций языка
C, являющихся исключительно частью POSIX и не входящих в стандарт
ANSI C - pipe(), fork(), wait(), close(), dup2(), execvp(), getenv(), setenv(), а
также функций ввода/вывода (их можно брать как из стандарта ANSI C,
так и из POSIX - по желанию и как нравится). Само создание шелла
подразумевает реализацию встраиваемых команд - тех, которые не
могут реализованы внешними программами или таких, которые в виде
внешних программ делать бессмысленно или неудобно - echo, cd, fc, fg,
pwd, let, exit (fc или fg приведены в качестве примером встраиваемых
команд, а не тех, которые дают в процессе обучения - они слишком
fi
7 из 7
сложны, fc предполагает иметь историю команд, fg/bg требуют
сложного управления процессами и их статусом). Дополнительно к
задаче реализации шелла проводится жеребьевка и предлагается
реализация невстраиваемых команд - таких как ls, cat, nd, grep -
упрощенные, с неполным набором опций. Задача разработки
текстового редактора может быть поставлена вообще отдельно.
Для конкретизации задачи предлагается реализовать чуть более
интересный набор - echo, cd, pwd, exit, netcat (nc). Это сразу
контролирует знания и опыт в понимании работы /bin/sh и
закрепляет сетевой стек (работа с сокетами Беркли). Сама задача
описана в прилагаемой методичке - включена в программу
обучения на ВМК МГУ для студентов 2-ого курса.
Ссылки
* https://en.wikipedia.org/wiki/History_of_Unix
* https://docs.altlinux.org/ru-RU/archive/2.3/html-single/junior/alt-docs-
extras-linuxnovice/ch02s10.html
* https://unix.stackexchange.com/questions/110240/why-does-ctrl-d-eof-
exit-the-shell
* https://manpages.ubuntu.com/manpages/impish/man7/bash-
builtins.7.html
* https://tldp.org/LDP/abs/html/internal.html
* https://ru.wikipedia.org/wiki/
%D0%A1%D0%BE%D0%BA%D0%B5%D1%82%D1%8B_%D0%91%D
0%B5%D1%80%D0%BA%D0%BB%D0%B8
Литература для выполнения задания и само
задание
* https://cmcmsu.info/2course/
* https://cmcmsu.info/download/avst.unix.pdf
* https://al.cs.msu.ru/ les/kuzina_prac.pdf (ЗАДАНИЕ НОМЕР 6)
* https://cmcmsu.info/2course/2005.task.01.215-216.htm
(АЛЬТЕРНАТИВНАЯ ФОРМУЛИРОВКА)
fi
fi

Вам также может понравиться