Предисловие от автора, Рича Тёрнера из Microsoft. Это статья о командной строке: от её появления и эволюции до планов капительного ремонта Windows Console и командной строки в будущих версиях Windows. Будь вы опытным профессионалом или новичком в IT, надеемся, что вы найдёте статью интересной.
Давным-давно в далёкой-далёкой серверной...
С первых дней развития информатики людям нужен был эффективный способ передавать компьютеру команды и данные и видеть результат выполнения этих команд/вычислений.
Одним из первых по-настоящему эффективных человеко-машинных интерфейсов стал Tele-Typewriter или «телетайп». Это электромеханическая машина с клавиатурой для ввода данных и каким-нибудь устройством вывода — сначала использовался принтер, позже экран.
Вводимые оператором символы локально буферизуются и отправляются с телетайпа на соседний компьютер или мейнфрейм в виде серии сигналов по электрическому кабелю (например, RS-232) со скоростью 10 символов в секунду (110 бод, бит в секунду, bps):
Телетайп Model 33 ASR
Примечание: Дэвид Гессвейн ведёт отличный сайт по PDP-8, где можно найти больше информации об ASR33 (и соответствующей технологии PDP-8), в том числе фотографии, видео и др.
Программа на компьютере получает введённые символы, решает, что с ними делать, и, возможно, асинхронно отправляет ответ на телетайп. Телетайп может напечатать/показать оператору полученные символы.
Затем технология улучшилась, скорость передачи выросла до 19200 bps, а шумные и дорогие принтеры заменили ЭЛТ-дисплеями (широко распространённый тип дисплеев в 80-е и 90-е годы), как на популярном терминале DEC VT100:
Терминал DEC VT100
Хотя технология улучшилась, но эта модель — терминал отправляет символы программе на компьютере, а он выдаёт текст для пользователя — осталась и сегодня как фундаментальная модель взаимодействия всех командных строк и консолей на всех платформах!
Архитектура терминала и командной строки
Модель по-своему элегантна. Одна из причин — в сохранении простоты и цельности каждого компонента: клавиатура выдаёт символы которые буферизуются как электрические сигналы. Устройство вывода просто выдаёт на дисплей (бумагу/экран) символы, полученные с компьютера.
На каждом этапе в системе передаётся только поток символов, так что это относительно простой процесс для внедрения различной инфраструктуры связи. Например, для добавления модемов, чтобы передавать потоки входных и выходных символов на большие расстояния по телефонным линиям.
Кодировка текста
Важно помнить, что терминалы и компьютеры обмениваются данными через потоки символов. При нажатии клавиши на клавиатуре терминала на подключенный компьютер отправляется значение, представляющее введённый символ. Нажмите клавишу ’A’ — и отправляется значение 65 (0x40). Нажмите ’Z’ — и отправляется 90 (0x5a).
7-битная кодировка ASCII
Список символов и их значений определён в стандарте American Standard Code for Information Interchange (ASCII), он же стандарт ISO/IEC 646 / ECMA-6 — «7-битный кодированный набор символов», который определяет:
- 128 значений, представляющих печатные латинские символы A−Z (65-90), a−z (97−122), 0−9 (48−57)
- Много общих знаков препинания
- Несколько непечатаемых управляющих кодов (0−31 и 127):
Стандартные символы 7-битной ASCII
Когда 7 бит недостаточно: кодовые страницы
Однако 7 бит не обеспечивают достаточно места для кодирования многих диакритических знаков, знаков препинания и символов, используемых в других языках и регионах. Так что с добавлением дополнительного бита можно расширить таблицу символов ASCII дополнительными наборами «кодовых страниц» для символов 128−255 (и возможного переопределения нескольких непечатаемых символов ASCII).
Например, IBM ввела кодовую страницу 437 с несколькими графическими символами вроде ╫ (215) и ╣(185) и математическими, включая π (227) и ± (241), а также переопределила печатные символы для обычно непечатаемых символов 1−31:
Кодовая страница 437
Кодовая страница Latin-1 определяет множество символов, используемых языками на основе латиницы:
Кодовая страница Latin-1
Во многих окружениях командной строки и оболочках можно изменять текущую кодовую страницу, чтобы терминал отображал различные символы (в зависимости от доступных шрифтов), особенно для символов со значением 128−255. Но неправильно указанная кодовая страница приведёт к отображению кракозябр. И да, «кракозябры» — это настоящий термин! Кто бы мог подумать? ;)
Когда 8 бит недостаточно: Юникод
Кодовые страницы временно решили проблему, но у них много недостатков, например, они не позволяют отображать текст одновременно из нескольких кодовых страниц/языков. Таким образом, пришлось ввести новую кодировку, которая точно отображает каждый символ и алфавит для всех языков, известных человечеству, оставляя ещё много свободного места! Представляем Юникод.
Юникод — это международный стандарт (ISO/IEC 10646), который в данный момент определяет 137 439 символов из 146 современных и исторических письменностей, а также многие символы и глифы, в том числе многочисленные смайлики, которые широко используются практически в каждом приложении, платформе и устройстве. Юникод регулярно обновляется дополнительными системами письменности, новыми/исправленными смайликами, символами и т. д.
Юникод также определяет «непечатаемые» символы форматирования, которые позволяют, например, соединить символы и/или повлиять на предыдущие или последующие символы! Это особенно полезно в письменностях вроде арабской, где лигатура конкретного символа определяется окружающими. Эмодзи могут использовать соединительный символ нулевой ширины (zero width joiner), чтобы объединить несколько символов в один визуальный глиф. Например, эмодзи кота-ниндзя Microsoft формируются путём соединения кота с другими эмодзи:
Эмодзи кота-ниндзя Microsoft
Когда байтов слишком много: UTF-8!
Для уникального и систематического представления всех символов требуется большое пространство, до нескольких байт на каждый символ.
Поэтому для экономии было разработано несколько новых кодировок Юникода. Среди самых популярных — UTF-32 (4 байта на символ), UTF-16/UCS-2 (2 байта) и UTF-8 (1−4 байта на символ).
Во многом благодаря обратной совместимости с ASCII и экономии места UTF-8 стала самой популярной кодировкой Юникода в интернете. Она демонстрирует взрывной рост с 2008 года, когда обогнала по популярности ASCII и другие популярные кодировки:
Рост популярности кодировки UTF-8 (источник: Википедия)
Таким образом, поначалу терминалы поддерживали 7-битный, а затем 8-битный текст ANSI, но большинство современных терминалов поддерживают текст Unicode/UTF-8.
Итак, что такое командная строка и что такое оболочка?
«Командная строка» или CLI (интерфейс/интерпретатор командной строки) описывает самый фундаментальный механизм, через который человек управляет компьютером: CLI принимает введённый оператором ввод и выполняет требуемые команды.
Например, echo Hello
отправляет текст «Hello» на устройство вывода (например, на экран). dir
(Cmd) или ls
(PowerShell/*NIX) перечисляет содержимое текущего каталога и т.д.
Раньше доступные команды были относительно простыми, но операторы требовали всё более изощрённых команд и возможности писать скрипты для автоматизации повторяющихся или сложных задач. Таким образом, процессоры командной строки стали сложнее и превратились в то, что теперь называют «оболочкой» командной строки (shell).
В Unix/Linux оригинальная оболочка Unix (sh) породила множество оболочек, включая Korn shell (ksh), C shell (csh) и Bourne Shell (sh). В свою очередь, на их основе создан Bourne Again Shell (bash) и т.д.
В мире Microsoft:
- Оригинальный MS-DOS (command.com) был относительно простой оболочкой командной строки
- «Командная строка» Windows NT (cmd.exe) разработана с учётом совместимым с устаревшими скриптами command.com, плюс добавлены несколько команд для новой, более мощной операционной системы
- В 2006 году Microsoft выпустила Windows PowerShell
- PowerShell — это современная объектная оболочка командной строки, которая позаимствовала функции других оболочек и включает в себя возможности .NET CLR и фреймворка .NET
- С помощью PowerShell можно писать скрипты и автоматизировать практически все аспекты одного или нескольких компьютеров под Windows, сети, систем хранения данных, БД и т.д.
- В 2017 году Microsoft открыла исходный код PowerShell, разрешив запуск на macOS, разных вариантах Linux и BSD
- В 2016 году Microsoft представила подсистему Windows для Linux (WSL)
- Позволяет запускать обычные немодифицированные двоичные файлы Linux непосредственно в Windows 10
- Пользователи устанавливают один или несколько обычных дистрибутивов Linux из магазина Windows
- Можно запустить один или несколько экземпляров дистрибутива параллельно с другими, а также параллельно с существующими приложениями и средствами Windows
- WSL позволяет запускать бок о бок все инструменты Windows и инструменты командной строки Linux без использования ресурсоёмких виртуальных машин
Мы ещё вернёмся к оболочкам командной строки Windows, но пока запомним, что существуют разные оболочки, они принимают команды, введённые пользователем/оператором, и выполняют широкий спектр задач по мере необходимости.
Современная командная строка
Современные компьютеры значительно мощнее «тупых терминалов» прошлого и обычно работают под управлением десктопной ОС (например, Windows, Linux, macOS) с графическим пользовательским интерфейсом (GUI). Такое окружение GUI позволяет нескольким приложениям работать одновременно в отдельных окнах на экране и/или невидимо в фоновом режиме.
Cmd, PowerShell и Ubuntu Linux под WSL работают на независимых инстансах консоли
На смену громоздким, неповоротливым электромеханическим телетайпам пришли современные консольные/терминальные приложения, которые работают в окошке на экране компьютера, но по-прежнему выполняют такие же необходимые функции, как аппаратные терминалы из прошлого.
Аналогично и приложения командной строки, к которым подключены терминалы, работают как и раньше: получают входные символы, решают, что делать с этими символами, (необязательно) выполняют работу — и могут выдать текст для отображения пользователю. Только вместо связи по медленным каналам TTY терминальные приложения и приложения командной строки на одной машине общаются по очень скоростным каналам Pseudo Teletype (PTY) в памяти.
Современная командная строка
Современные терминалы в основном взаимодействуют с приложениями командной строки, запущенными локально. Но конечно, они также могут взаимодействовать с приложениями командной строки, запущенными на других машинах в той же сети или даже с удалёнными машинами на другой стороне света через интернет. Это «удалённое» взаимодействие с командной строкой — мощный инструмент, который популярен на каждой платформе, особенно на *NIX.
Эволюция командной строки
Скромное начало: MS-DOS
На заре компьютерной индустрии управление большинством компьютеров осуществлялось путём ввода команд в командной строке. За рыночную долю боролись компьютеры под Unix, CP/M, DR-DOS и других. В итоге система MS-DOS стала стандартом де-факто для IBM PC и всех совместимых компьютеров:
MS-DOS 6.0
Как и большинство основных операционных систем того времени, интерпретатор командной строки или «оболочка» в MS-DOS предоставляла простой, но относительно эффективный набор команд и синтаксис командных скриптов для написания batch-файлов (.bat).
Предприятия крупного и малого бизнеса очень быстро взяли на вооружение MS-DOS и в совокупности создали многие миллионы скриптов, некоторые из которых всё ещё используются сегодня! Batch-скрипты применяются для автоматизации настройки ПК, установки/изменения параметров безопасности, обновления программного обеспечения, сборки кода и т.д.
Вы редко или никогда не увидите реальную работу такого скрипта, потому что многие из них выполняются в фоновом режиме, например, при авторизации на компьютере. Но сотни миллиардов скриптов командной строки и команд выполняются каждый день только на Windows!
Командная строка представляет мощный инструмент в руках человека, который имеет терпение и упорство, чтобы научиться выжимать максимум из доступных команд и инструментов. Но большинству обычных пользователей сложно эффективно управлять компьютером из командной строки. Большинству не нравилось, что нужно изучать и запоминать много словно мистических аббревиатур, чтобы компьютер произвёл хоть какое-нибудь полезное действие.
Требуется более удобный и ориентированный на производительность пользовательский интерфейс.
Графический интерфейс идёт в мейнстрим
Добро пожаловать в графический интерфейс пользователя (GUI), изобретённый в Xerox Alto.
Вскоре после изобретения появилось много конкурирующих GUI на компьютерах Lisa и Macintosh от Apple, Commodore Amiga (Workbench), Atari ST (DRI GEM), Acorn Archimedes (Arthur/RISC OS), Sun Workstation, X11/X Windows и многих других, в том числе Microsoft Windows.
Windows 1.0 вышла в 1985 году и являлась по сути приложением MS-DOS, которое предоставляло простое окружение GUI с плиточным окном, позволяя пользователям запускать несколько приложений бок о бок:
Windows 1.01 на MS-DOS
Windows 2.x, 3.x, 95 и 98 работали на базе MS-DOS. Более поздние версии Windows начали заменять некоторые функции MS-DOS альтернативами Windows (например, операции с файловой системой), но все они полагались на фундамент MS-DOS.
Примечание: Windows ME (Millennium Edition) стала интересным гибридом. В ней наконец-то заменили поддержку MS-DOS и поддержку реального режима из предыдущих версий Windows несколькими новыми функциями (особенно технологии Gaming & Media). Некоторые функции позаимствованы из Windows 2000 (например, новый стек TCP/IP), но настроены для работы на домашних ПК, которым трудно запустить полноценную NT.
Но Microsoft понимала, что не может бесконечно растягивать архитектуру и возможности MS-DOS и Windows. Требовалась новая операционная система с прицелом на будущее.
Microsoft — лидер рынка Unix! Да, серьёзно!
Разрабатывая MS-DOS, Microsoft также занималась поставкой Xenix — фирменного порта Unix версии 7 — для различных процессорных и машинных архитектур, включая Z8000, 8086/80286 и 68000.
К 1984 году Xenix от Microsoft стал самым популярным вариантом Unix в мире!
Тем временем распад Bell Labs — родины Unix — привёл к появлению AT&T, которая начала продавать Unix System V производителям компьютеров и конечным пользователям.
Microsoft понимала, что отсутствие собственной ОС ставит под угрозу её способности для развития. Поэтому было принято решение отказаться от Xenix: в 1987 году Microsoft передала Xenix своему партнёру Santa Cruz Operation (SCO), с которым работала над несколькими проектами по портированию и улучшению Xenix на различных платформах.
Microsoft + IBM == OS/2… ненадолго
В 1985 году Microsoft начала работать с IBM над новой операционной системой OS/2. Она изначально планировалась как «более функциональная DOS» для некоторых современных 32-битных CPU и с учётом других технологий, которые быстро порождались в IBM и у других OEM.
Но история OS/2 оказалась слишком бурной. В 1990 году Microsoft и IBM прекратили сотрудничество. Это было обусловлено рядом факторов, в том числе значительными культурными различиями между разработчиками IBM и Microsoft, проблемами планирования, а также взрывным успехом и ростом внедрения Windows 3.1. IBM продолжала разработку и поддержку OS/2 до конца 2006 года.
К 1988 году Microsoft убедилась, что будущий успех требует более масштабного, смелого и амбициозного подхода. Такой подход потребует новой, современной операционной системы, которая будет поддерживать амбициозные цели компании.
Большая ставка Microsoft: Windows NT
В 1988 году Microsoft пригласила Дэйва Катлера, создателя популярной и уважаемой операционной системы VAX/VMS в компании DEC. Его задача — создать новую, современную, независимую от платформы операционную систему, которой Microsoft будет владеть, контролировать и на которой во многом построит своё будущее.
Этой новой операционной системой стала Windows NT: фундамент, на котором построены Windows 2000, Windows XP, Windows Vista, Windows 7, Windows 8 и Windows 10, а также во все версии Windows Server, Windows Phone 7+, Xbox и HoloLens!
Windows NT изначально спроектирована как кроссплатформенная система. Сначала она поддерживала Intel i860, затем MIPS R3000, Intel 80386+, DEC Alpha и PowerPC. С тех пор семейство ОС Windows NT портировали для поддержки процессорных архитектур IA64 Itanium, x64 и ARM/ARM64, среди прочих.
Windows NT предоставляет интерфейс командной строки через терминальное приложение Windows Console и командную строку Command Prompt (cmd.exe). Cmd разработан на максимальную совместимость с пакетными скриптами MS-DOS, чтобы помочь бизнесу перейти на новую платформу.
Мощь PowerShell
Cmd сохраняется в Windows по сей день (и, вероятно, сохранится в течение многих десятилетий). Поскольку его основная задача — обеспечить максимальную обратную совместимость, Cmd редко улучшается. Даже «исправление ошибок» зачастую затруднено, если эти «баги» существовали в MS-DOS или более ранних версиях Windows!
В начале 2000-х оболочка Cmd уже устарела: Microsoft и её клиенты срочно нуждались в более мощной и гибкой командной строке. Из этой потребности появился PowerShell (который возник из «Манифеста Монады» Джеффри Сновера).
PowerShell — это объектно-ориентированная оболочка, в отличие от оболочек на основе файлов/потоков, которые принято использовать в мире *NIX: вместо потоков текста PowerShell обрабатывает потоки объектов. Он предоставляет авторам скриптов возможность прямого доступа и манипуляций с объектами и их свойствами вместо написания множества скриптов для анализа и обработки текста (как sed/grep/awk/lex/др.).
Созданные на базе .NET Framework и среды Common Language Runtime (CLR), язык и синтаксис PowerShell разработаны для объединения богатства экосистемы .NET со многими распространёнными и полезными функциями из множества других языков сценариев оболочки с акцентом на то, чтобы скрипты обеспечивали максимальную консистентность и исключительную… ну… мощь. :)
Чтобы узнать больше о PowerShell, рекомендую прочитать книгу «PowerShell в действии» (Manning Press), написанную Брюсом Пайеттом — разработчиком синтаксиса и языка PowerShell. В частности, первые несколько глав содержат подробное обоснование структуры языка.
PowerShell был принят для использования многими технологиями на платформе Microsoft, включая Windows, Exchange Server, SQL Server, Azure и многими другими. Он предоставляет очень согласованные команды для администрирования и управления практически всеми аспектами Windows и/или среды.
PowerShell Core — это PowerShell с открытым исходным кодом, доступное для Windows и различных версий Linux, BSD и macOS.
POSIX для NT, Interix и служб UNIX
При проектировании NT компания команда Катлера специально разработала ядро NT и операционную систему для поддержки нескольких подсистем-интерфейсов между кодом пользовательского режима и основным ядром.
Когда в 1993 году вышла первая Windows NT версии 3.1, она поддерживала несколько подсистем: МЅ-DOS, Windows, OS/2 и POSIX v1.2. Эти подсистемы позволяли на одной машине и базовой ОС запускать приложения, нацеленные на несколько платформ операционной системы без виртуализации или эмуляции — это внушительная разработка даже по меркам сегодняшнего дня!
Оригинальная реализация POSIX в Windows NT была приемлемой, но для неё требовались значительных улучшения. Поэтому Microsoft приобрела Softway Systems и её POSIX-совместимую подсистему Interix для NT. Изначально Interix поставлялась как отдельное дополнение, а затем её объединили с несколькими полезными утилитами и инструментами и выпустили в виде Services For Unix (SFU) в Windows Server 2003 R2 и Windows Vista. Однако поддержку SFU пришлось прекратить после Windows 8, в основном, из-за недостаточной популярности.
А потом произошла забавная вещь…
Windows 10 — новая эра для командной строки Windows!
В начале разработки Windows 10 компания открыла страницу UserVoice с вопросом, какие функции люди хотят реализовать в различных областях ОС. Сообщество разработчиков особенно громко требовало от Microsoft две вещи:
- Внести значительные улучшения в консоль Windows
- Предоставить пользователям возможность запускать средства Linux в Windows
На основе этих отзывов Microsoft сформировала две новые группы:
- Группа разработки Windows Console и командной строки, которой поручили провести капитальный ремонт инфраструктуры Windows Console и командной строки
- Группа разработки Windows Subsystem for Linux (WSL)
Остальное, как говорится, история!
Подсистема Windows для Linux (WSL)
Основанные на GNU/Linux «дистрибутивы» (сочетания ядра Linux и коллекций инструментов пользовательского режима) становятся всё популярнее, особенно на серверах и в облаке. Хотя в Windows имелась POSIX-совместимая среда выполнения, но SFU не мог запускать многие инструменты и двоичные файлы Linux из-за дополнительных системных вызовов и различий в поведении по сравнению с традиционной Unix/POSIX.
После анализа обратной связи от разработчиков и технически подкованных пользователей Windows, а также в связи с растущим спросом внутри самой Microsoft, компания изучила несколько вариантов, и в конечном итоге решила позволить на Windows запуск оригинальных немодифицированных бинарных файлов Linux!
В середине 2014 года Microsoft сформировала группу разработки того, что станет подсистемой Windows для Linux (WSL). WSL впервые анонсировали в сборке Build 2016, а вскоре предварительная версия вышла на канале Windows 10 Insider.
С тех пор WSL обновляется в большинстве инсайдерских сборок и в каждом крупном выпуске ОС с момента Anniversary Update осенью 2016 года. В каждой новой версии увеличивается функциональность, совместимость и стабильность WSL: в первой версии это был интересный эксперимент, который мог запускать лишь несколько распространённых программ Linux. При активной помощи сообщества (всем спасибо!) разработчики быстро дорабатывали WSL, так что вскоре она получила много новых возможностей и научилась запускать всё более сложные бинарники Linux.
Сегодня (середина 2018 года) WSL запускает большинство двоичных файлов Linux, программы, компиляторы, компоновщики, отладчикии т.д. Многие разработчики, IT-специалисты, инженеры DevOps и многие другие, кому необходимо запускать или создавать инструменты, приложения, службы Linux и т. д., получили резкое повышение производительности и возможность запускать свои любимые инструменты Linux вместе с любимыми инструментами для Windows на одном компьютере, без загрузки двух операционных систем.
Команда WSL продолжает улучшать WSL в части выполнения задач Linux, повышения производительности и интеграции с Windows.
Перезагрузка и капитальный ремонт Windows Console
В конце 2014 года проект по созданию подсистемы Windows для Linux (WSL) шёл полным ходом, и на фоне взрыва оживлённого интереса пользователей к командной строке стало очевидно, что консоль Windows явно нуждается в некотором апгрейде.
В частности, консоли не хватало многих функций, привычных для современных *NIX-совместимых систем, таких как возможность парсинга и вывода последовательностей ANSI/VT, широко используемых в мире *NIX для вывода насыщенного и подсвеченного текста и текстовых UI.
В чём тогда смысл разработки WSL, если пользователь не сможет корректно использовать инструменты Linux?
Ниже пример того, что отображает консоль Windows 7 и Windows 10: обратите внимание, что Windows 7 (слева) не в состоянии правильно отобразить VT, сгенерированный линуксовыми программами tmux
, htop
, Midnight Commander
и cowsay
, но они корректно выглядят в Windows 10 (справа):
Сравнение консоли Windows 7 и Windows 10
Так, в 2014 году была сформирована небольшая «группа Windows Console». На неё возложили задачу распутать, понять и улучшить кодовую базу Windows Console… которой к этому времени было около 28 лет — больше, чем программистам, которые работают над этим проектом.
Как подтвердит любой разработчик, которому когда-либо приходилось брать старый, грязный, плохо поддерживаемый код, улучшение такого кода представляет собой сложную задачу. Ещё сложнее не нарушить существующее поведение. Для обновления самой часто запускаемой программы в Windows, не нарушив работу миллионов клиентских скриптов, инструментов, скриптов авторизации, систем сборки, производственных систем, систем анализа и прочих, требуется немало «внимания и терпения». ;)
Для разработчиков проблема усугубилась, когда они поняли всю строгость требований к консоли со стороны клиентов. Например, если производительность консоли изменялась на 1−2% от сборки к сборке, то срабатывали сигналы тревоги в группе Windows Build, что приводило… гм… к «быстрой и прямой обратной связи», то есть требованию немедленного исправления.
Итак, когда мы будем обсуждать улучшения консоли и новые функции, помните, что есть несколько незыблемых принципов, которым должно соответствовать каждое изменение, в том числе:
- НЕ допускать новых уязвимостей
- НЕ ломать инструменты, скрипты, команды и т. д. у существующих клиентов (внутренних или внешних)
- НЕ снижать производительность и не увеличивать потребление памяти/IO (без чётких и хорошо доведённых причин)
За последние три года команда Windows Console провела следующую работу:
- Капитальный ремонт внутренних компонентов
- Значительное упрощение и уменьшение кодовой базы
- Замена нескольких внутренних коллекций, списков, стеков и т.д. контейнерами STL
- Разбиение на модули и изоляция логических и функциональных единиц кода, что позволяет улучшать функции (а иногда и заменять их), не «ломая мир»
- Объединение нескольких ранее отдельных и несовместимых консольных движков в один
- МНОЖЕСТВО улучшений безопасности и надёжности
- Возможность парсинга и вывода последовательностей ANSI/VT, что позволяет консоли точно отображать насыщенный текстовый вывод из *NIX и других современных инструментов командной строки и приложений
- Поддержка 24-битного цвета вместо прежних 16 цветов!
- Улучшенная безбарьерность: Narrator и другие приложения безбарьерной среды работают в окне консоли
- Добавлена/улучшена поддержка мыши и сенсорного ввода
И работа продолжается! В настоящее время мы завершаем реализацию нескольких захватывающих новых функций.
К чему был этот урок истории?
Я надеюсь, вы поняли, что командная строка остаётся ключевым компонентом стратегии, платформы и экосистемы Microsoft.
Хотя для конечных пользователей Microsoft продвигала графический интерфейс, сама компания и её технические клиенты/пользователи/партнёры в значительной степени полагались на командную строку для выполнения множества технических задач.
На самом деле Microsoft буквально не смогла бы создать ни Windows, ни любой другой из своих программных продуктов без быстрой, эффективной, стабильной и безопасной консоли!
На протяжении эпох MS-DOS, Unix, OS/2 и Windows командная строка оставалась, пожалуй, самым важным инструментом в наборе инструментов каждого технического пользователя. Даже многие пользователи, которые никогда не вводили команды в окно, в реальности используют консоль каждый день! Даже сборка кода в Visual Studio (VS) происходит в скрытом окне консоли. При использовании Exchange Server или средств администрирования SQL Server многие из этих команд выполняются с помощью PowerShell в скрытой консоли.
Механика Windows Console
Во время начала разработки Windows NT в 1989 году не было ни графического интерфейса, ни рабочего стола. Была только полноэкранная командная строка, которая визуально напоминала MS-DOS. Когда появилась реализация графического интерфейса Windows, потребовалось создать приложение консоли для GUI — и таким образом родилась Windows Console! Это одно из первых приложений Windows NT с графическим интерфейсом и, безусловно, одно из старейших приложений Windows, которое по-прежнему используется повсеместно!
Кодовой базе консоли Windows в настоящее время (июль 2018 года) почти 30 лет… по сути, больше, чем разработчикам, которые сейчас над ней работают!
Что делает консоль?
Как мы узнали ранее, у терминала относительно простой алгоритм работы:
- Обработать пользовательский ввод
- Принять входной сигнал от приборов, включая клавиатуру, мышь, тачскрин и др.
- Перевести ввод в соответствующие символы и/или последовательности ANSI/VT
- Отправить символы в подключенное приложение/инструмент/оболочку
- Обработать вывод приложения:
- Принять вывод текста из покдлюченного приложения/инструмента командной строки
- Обновлять экран по мере необходимости, основываясь на полученных данных от приложения (например, полученный текст, перемещения курсора, изменение цвета текста и т.д.)
- Обработать системные взаимодействия:
- Запуск по запросу
- Управление ресурсами
- Изменение размера/развернуть окно/свернуть окно и т.д.
- Завершение по запросу или после закрытия канала связи
Но консоль Windows работает немного иначе:
Механика Windows Console
Консоль Windows — обычный исполняемый файл Win32. Изначально он написан на C, но большая часть кода сейчас переносится на C++ по мере того, как разработчики модернизируют и разбивают на модули кодовую базу.
Если вам интересны такие вещи: многие спрашивали, Windows написана на C или C++. Ответ такой: несмотря на объектно-ориентированный дизайн NT, как и большинство ОС, Windows почти полностью написана на C! Почему? Потому что C++ увеличивает потребление памяти и привносит накладные расходы на выполнение кода. Даже сегодня скрытые затраты на выполнение кода C++ могут удивить, но ещё в 1990-х, когда память стоила около $60/МБ (да… $60 за МЕГАБАЙТ!), скрытые затраты на vtables и прочее были значительными. Кроме того, затраты на косвенное обращение к виртуальным методам и разыменование объектов в то время могли привести к очень значительным потерям производительности и масштабированию кода C++. В наше время нужно соблюдать осторожность, но издержки производительности C++ на современных компьютерах вызывают намного меньше беспокойства. Часто это приемлемый компромисс, учитывая безопасность, читабельность и лучшую сопровождаемость кода… именно поэтому мы постепенно переписываем код консоли на современном C++!
Так что внутри консоли Windows?
До Windows 7 инстансы консоли Windows размещались в критически важной подсистеме Client Server Runtime Subsystem (CSRSS)! Но в Windows 7 из соображений безопасности и надёжности консоль переместили из CSRSS в следующие бинарники:
- conhost.exe — пользовательский режим консоли Windows UX и механика командной строки
- condrv.sys — драйвер ядра Windows, обеспечивающий коммуникации между conhost и одной или несколькими оболочками командной строки/инструментами/приложениями
Высокоуровневая схема текущей внутренней архитектуры консоли выглядит следующим образом:
Ядро консоли состоит из следующих компонентов (снизу вверх):
- ConDrv.sys — драйвер режима ядра
- Обеспечивает высокопроизводительный канал связи между консолью и любыми подключенными приложениями командной строки
- Переносит туда и обратно сообщения IO Control (IOCTL) между приложениями командной строки и консолью, к которой они «прикреплены»
- Консольные сообщения IOCTL содержат:
- Данные, представляющие запросы на выполнение вызовов API для экземпляра консоли
- Текст, отправляемый из консоли в приложение командной строки
- ConHost.exe — приложение Win32 GUI:
- ConHost Core — внутренности и механика
- Сервер API: преобразует сообщения IOCTL, полученные из приложений командной строки, в вызовы API и отправляет текстовые записи из консоли в приложение командной строки
- API: реализует консольный API Win32 и логику для всех операций, которые консоль может попросить выполнить
- Буфер ввода: хранит записи событий клавиатуры и мыши, генерируемые пользовательским вводом
- VT Parser: если включен, анализирует последовательности VT, извлекает их из текста и генерирует эквивалентные вызовы API
- Буфер вывода: хранит текст, отображаемый на дисплее консоли. По сути, это 2D-массив структур CHAR_INFO, которые содержат символьные данные и атрибуты каждой ячейки (подробнее о буфере ниже)
- Другое: в схему не включены инфраструктура сохранения/извлечения значений из реестра и/или файлов ярлыков и т.д.
- Console UX App Services — слой UX и UI
- Управляет макетом, размером, положением и прочими характеристиками окна консоли на экране
- Отображает и обрабатывает параметры UI и т.д.
- Прокачивает очередь сообщений Windows, обрабатывает их и преобразует введённые пользователем данные в записи событий клавиш и мыши, сохраняя их во входном буфере
- ConHost Core — внутренности и механика
Windows Console API
Как видно из схемы архитектуры, в отличие от терминалов NIX, консоль отправляет/получает вызовы API и/или данные в виде сообщений IO Control (IOCTL), а не текста! Даже встроенные в текст последовательности ANSI/VT из приложений командной строки (в основном Linux), извлекаются, анализируются и преобразуются в вызовы API!
Это различие раскрывает ключевое фундаментальное философское различие между *NIX и Windows: в *NIX «всё является файлом», а в Windows «всё является объектом»!
У обоих подходов есть преимущества и недостатки, которые мы перечислим, но не будем подробно обсуждать. Просто помните, что это ключевое различие в философии объясняет многие фундаментальные различия Windows и *NIX!
В *NIX всё является файлом
Когда Unix впервые появился в конце 1960-х и начале 1970-х годов, одним из основных принципов стало то, что (где это возможно) всё следует абстрагировать как поток файлов. Одна из ключевых целей заключалась в упрощении кода для доступа к устройствам и периферии: если все устройства представляются в ОС как файлы, то коду легче получить к ним доступ.
Эта философия работает на самом глубоком уровне: можно даже перемещаться и опрашивать большую часть конфигурации ОС и компьютера под *NIX, перемещаясь по псевдо/виртуальным файловым системам, которые показывают то, что кажется «файлами» и папками, но на самом деле представляет собой конфигурацию машины и оборудование.
Например, в Linux можно исследовать свойства процессоров, изучая содержимое псевдофайла /proc/cpuinfo
:
Но простота и согласованность этой модели могут дорого стоить: извлечение/анализ конкретной информации в псевдофайлах часто требует специальных инструментов, таких как sed, awk, perl, python и т.д. Эти инструменты используются для написания команд и скриптов парсинга текстового содержимого, поиска определённых шаблонов, полей и значений. Некоторые из скриптов могут быть довольно сложными, часто трудными в обслуживании и хрупкими — если структура, шаблон и/или формат текста изменятся, многие скрипты, вероятно, потребуется обновить.
В Windows всё является объектом
Когда проектировалась Windows NT, «объекты» рассматривались как будущее в разработке программного обеспечения: «объектно-ориентированные» языки программирования появлялись быстрее, чем тараканы: Simula и Smalltalk уже зарекомендовали себя, а C++ набирал популярность. За ними последовали другие объектно-ориентированные языки, в том числе Python, Eiffel, Objective-C, ObjectPascal/Delphi, Java, C# и многие другие.
Результат предсказуем. Созданная в те пьянящие, объектно-ориентированные дни (около 1989 года) Windows NT разработана с философией, что «всё является объектом». На самом деле одной из самых важных частей ядра NT является Менеджер объектов!
Windows NT предоставляет богатый набор Win32 API для получения и/или управления объектами ОС. Разработчики используют Win32 API для сбора и представления информации, похожей на данные из псевдофайлов и инструментов *NIX, только через объекты и структуры. А поскольку парсеры, компиляторы и анализаторы понимают структуру объектов, то многие ошибки кодирования часто проявляются на ранней стадии, что помогает проверить синтаксическую и логическую правильность намерений программиста. Со временем это может привести к уменьшению сбоев, волатильности и лучшему порядку.
Итак, возвращаясь к основной дискуссии о консоли Windows: команда NT решила построить «консоль», отличную от традиционного терминала *NIX в нескольких ключевых областях:
- Console API: вместо того, чтобы полагаться на способность программистов генерировать корректные ANSI/VT-последовательности, которые трудно проверить, консоль Windows управляется через богатые консольные API
- Общие службы: чтобы избежать дублирования служб во всех оболочках командной строки (например, журнал команд, псевдонимы команд), консоль сама предоставляет некоторые из них через Console API
Проблемы с консолью Windows
Хотя консольные API стали очень популярны в инструментах и сервисах командной строки, но ориентированная на API модель имеет определённые недостатки, перечисленные ниже.
Только для Windows
Многие средства командной строки и приложения широко используют Console API.
В чём проблема? Они работают только под Windows.
Таким образом, в сочетании с другими различиями (например, в жизненном цикле и т.д.), приложения командной строки Windows не всегда легко переносятся под *NIX и наоборот.
Из-за этого в экосистеме Windows распространены свои собственные, часто похожие, но обычно отличающиеся средства командной строки и приложения. Это означает, что при использовании Windows пользователям приходится изучать один набор приложений и инструментов командной строки, оболочек, языков сценариев и т.д., а при использовании *NIX — другой набор.
У этой проблемы нет простого решения: консоль Windows и командную строку нельзя просто выбросить и заменить на bash и iTerm2 — существуют сотни миллионов приложений и сценариев, которые зависят от консоли Windows и оболочек Cmd/PowerShell.
Сторонние инструменты, такие как Cygwin, отлично переносят многие основные инструменты GNU и библиотеки совместимости в Windows, но не могут запускать непортированные, неизменённые бинарники Linux. Это очень важно, так как многие пакеты и модули Ruby, Python, Node зависят от бинарных файлов Linux и/или зависят от поведения *NIX.
Эти причины привели к тому, что Microsoft расширила совместимость с Windows, разрешив запуск аутентичных двоичных файлов и средств Linux в подсистеме Windows для Linux (WSL). С помощью WSL пользователи теперь могут загружать и устанавливать один или несколько дистрибутивов Linux бок о бок на одной машине, а также использовать apt/zypper/npm/gem/др. для установки и запуска подавляющего большинства инструментов командной строки Linux вместе с их любимыми приложениями и инструментами Windows.
Тем не менее, у нативной консоли остаётся функциональность, которая отсутствует в сторонних терминалах: в частности, Windows Console предоставляет сервисы command-history и command-alias, чтобы каждой оболочке (в частности) не пришлось повторно реализовать одинаковую функциональность.
Сложности с удалённой работой
Как говорилось в начале статьи, терминалы изначально были отделены от компьютера, к которому подключены. Сегодня эта конструкция сохраняется: большинство современных терминалов и оболочек/приложений/проч. командной строки изолированы в рамках отдельных процессов и/или машин.
На *NIX-платформах парадигма изолированности терминалов и приложений командной строки с простым обменом символами привела к тому, что там легко получить доступ и работать с удалённого компьютера/устройства. Если терминал и приложение командной строки обмениваются потоками символов по какой-то упорядоченной инфруструктуре (TTY, PTY и т.д.), то очень легко работать удалённо.
Но в Windows многие приложения командной строки зависят от вызова API консоли и предполагают выполнение на той же машине, что и сама консоль. Это затрудняет удалённое управление. Как приложению командной строки удалённо обратиться к API на консоли локального компьютера? Хуже того, как удалённое приложение обратится к Console API, если доступ к нему осуществляется через терминал на Mac или Linux?!
Извините, что дразню вас, но более подробно мы вернёмся к этой теме в следующей статье.
Запуск консоли… или нет!
Когда пользователь *NIX хочет запустить инструмент командной строки, то сначала запускает терминал. Тот затем запускает оболочку по умолчанию или может быть настроен для запуска определённого приложения/инструмента. Терминал и приложение командной строки взаимодействуют потоками символов через Pseudo TTY (PTY).
Однако в Windows всё работает иначе: пользователи Windows никогда не запускают консоль (conhost.exe) — они сразу запускают оболочки командной строки и приложения, например, Cmd.exe, PowerShell.exe, wsl.exe и прочее. Windows подключает запущенное приложение к текущей консоли (если оно запущено из командной строки) или к вновь созданному экземпляру консоли.
#SAYWHATNOW?
Да, в Windows пользователи запускают приложение командной строки, а не саму консоль.
Если пользователь запускает приложение командной строки из существующей оболочки командной строки, то Windows обычно присоединяет вновь запущенный .exe к текущей консоли. В противном случае Windows запустит новый экземпляр консоли, к которому будет присоединено только что запущенное приложение.
Немного занудства: многие говорят, что «приложения командной строки запускаются в консоли». Это не так и приводит к большой путанице относительно того, как в реальности работает консоль и приложения командной строки! Приложения командной строки и их консоли запускаются в независимых процессах Win32. Помогите исправить это заблуждение. Всегда говорите, что «средства командной строки/приложения выполняются с подключением к консоли». Спасибо!
Звучит неплохо, правда? На самом деле… нет. Есть некоторые проблемы:
- Консоль и приложение командной строки взаимодействуют сообщениями IOCTL через драйвер, а не через текстовые потоки
- Windows указывает, что ConHost.exe — это консольное приложение, подключенное к приложениям командной строки
- Windows создаёт «каналы» (pipes), по которым взаимодействуют консоль и приложение командной строки
Это существенные ограничения. Что если вы хотите создать альтернативное консольное приложение для Windows? Как отправить события с клавиатуры/мыши и прочие действия пользователя в приложение командной строки, если нельзя получить доступ к каналам, соединяющим консоль с этим приложением?
Увы, ситуация тут не очень хорошая. Есть отличные сторонние консоли и серверные приложения для Windows (например, ConEmu/Cmder, Console2/ConsoleZ, Hyper, Visual Studio Code, OpenSSH и т. д.), но им приходится прибегать к изощрённым трюкам, чтобы работать как обычная консоль!
Например, сторонним консолям приходится запускать приложение командной строки вне экрана, скажем, с координатами (-32000, -32000). Затем отправлять нажатия клавиш на внеэкранную консоль, скрапить с экрана текстовое содержимое внеэкранной консоли — и повторно выводить его в собственном пользовательском интерфейсе!
Какое-то безумие, верно?! Тот факт, что эти приложения вообще работают — лишь доказательство изобретательности и целеустремленности их создателей.
Очевидно, что мы стремимся исправить эту ситуацию. Об этом тоже расскажем в следующих статьях.
Windows Console и VT
Как описано выше, консоль Windows предоставляет богатый API. С помощью Console API приложения и инструменты командной строки пишут текст, изменяют его цвет, перемещают курсор и т.д. И благодаря этим API не нужно было поддерживать последовательности ANSI/VT, которые обеспечивают похожую функциональность на других платформах.
Фактически, до Windows 10 в консоли Windows была реализована только минимальная поддержка последовательностей ANSI/VT:
Всё изменилось в 2014 году, когда Microsoft сформировала новую группу разработки Windows Console. Одним из её главных приоритетов стало реализовать всестороннюю поддержку последовательностей ANSI/VT для визуализации выходных данных приложений *NIX, работающих в подсистеме Windows для Linux (WSL) и на удалённых машинах *NIX.
Группа быстро внедрила в консоль Windows 10 всестороннюю поддержку последовательностей ANSI/VT, что позволило пользователям использовать и наслаждаться огромным набором инструментов и приложений командной строки Windows и Linux.
Команда продолжает улучшать поддержку VT с каждым выпуском ОС и будет благодарна за любые проблемы, которые вы упомянете в нашем трекере на GitHub. ;)
Обработка Юникода
К сожалению, консоль Windows и её API появились до изобретения Юникода!
Консоль Windows хранит текст (который впоследствии выводится на экран) как символы кодировки UCS-2 с двумя байтами на символ. Эта кодировка поддерживает кодирование первых 65536 позиций символов, что известно как плоскость 0 или основная многоязычная плоскость (Basic Multilingual Plane, BMP).
Приложения командной строки выводят текст в консоли с помощью Console API. Обрабатывающие текст интерфейсы, бывают двух видов: функции с суффиксом А
обрабатывают однобайтовые/символьные строки, функции с суффиксом W
обрабатывают двухбайтовые (wchar)/символьные строки.
Например, функция WriteConsoleOutputCharacter компилируется в WriteConsoleOutputCharacterA()
для проектов ASCII или в WriteConsoleOutputCharacterW()
для проектов на Юникоде. Код может напрямую вызвать функцию с суффиксом ...A
или ...W
, если необходима обработка конкретного типа.
Примечание: каждый W API поддерживает UCS-2, потому что это всё, что существовало в момент разделения на A/W, и мы думали, что так будет хорошо. Но многие W API уже обновились для поддержки ещё и UTF-16 на том же канале.
Не все W API понимают UTF-16, но все они знают хотя бы UCS-2.
Кроме того, консоль не поддерживает некоторые новые функции Юникода, включая соединительные символы нулевой ширины (zero width joiner), которые используются для объединения отдельных символов в арабских и индийских письменностях, а также для объединения символов эмодзи в один визуальный глиф.
Как же ввести эмодзи кота-ниндзя или сложные многобайтовые китайские/арабские символы в консоль? К сожалению, никак!
Мало того что консольный API не поддерживает символы Юникод больше двух байт на глиф (эмодзи NinjaCat требует 8 байт!), но внутренний буфер UCS-2 консоли тоже не может хранить дополнительные байты данных. Что ещё хуже, текущий GDI-рендерер консоли не сможет отрисовать глиф, даже если тот поместится в буфер!
Эх… Таковы радости legacy-кода.
Здесь опять я намерен прервать рассказ — вернёмся к этой теме в следующей статье. Оставайтесь с нами!
Итак, на чём мы остановились?
Дорогой читатель, если вы прочитали всё вышенаписанное, спасибо вам, и примите поздравления — теперь вы знаете больше о консоли Windows, чем большинство ваших друзей, и, вероятно, даже больше, чем вы сами хотели узнать! Какая удача!
Мы многое рассмотрели в этой статье:
- Основные строительные блоки консоли Windows:
- Condrv.sys — коммуникационный драйвер
- ConHost.ехе — UX консоли и механика:
- Сервер API — сериализует вызовы API и текстовые данные с помощью сообщений IOCTL, отправляемых в/из драйвера
- API — функциональность консоли
- Буферы — буфер ввода, хранящий пользовательский ввод, и буфер вывода, хранящий выходной/отображаемый текст
- Парсер VT — преобразует последовательности ANSI/VT из текстового потока в вызовы API
- UX консоли — состояние UI консоли, настройки, функции
- Другое — технические данные, безопасность и проч.
- Что делает консоль
- Отправляет пользовательский ввод в подключенное приложение командной строки
- Получает и отображает выходные данные из подключенного приложения командной строки
- Чем консоль отличается от терминалов *NIX
- NIX: «Всё представляет собой файл/текстовый поток»
- Windows: «Все представляет собой объект, доступный через API»
- Проблемы консоли
- Консольные приложения и приложения командной строки взаимодействуют через запросы вызовов API и текст, сериализованный в Сообщения IOCTL
- Консольный API могут вызвать только приложения командной строки Windows
- Сложнее портировать приложения на/из Windows
- Приложения взаимодействуют с консолью через Windows API
- Затрудняет удалённое взаимодействие с приложениями и средствами командной строки Windows
- Зависимость от IOCTL нарушает схему «обмен символами» терминала
- Затрудняет эксплуатацию инструментов командной строки удалённого с не-Windows машин
- Запуск приложений командной строки Windows является «необычным»
- К приложениям командной строки можно присоединить только ConHost.exe
- Сторонние терминалы вынуждены создавать внеэкранную консоль, отправлять туда символы и скрапить экран
- Windows исторически не понимает последовательности ANSI/VT
- В основном исправлено в Windows 10
- У консоли ограниченная поддержка Юникода и в настоящее время проблемы с хранением и рендерингом UTF-8 и глифов, которые нуждаются в соединительных символах нулевой ширины
В следующих нескольких статьях этой серии мы более подробно разберём консоль и обсудим решение этих проблем… и не только!
Как всегда, следите за обновлениями.
Автор: m1rko