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