Windows 命令行:深入 Windows 控制台(Windows Command-Line: Inside the Windows Console)

服务器 William 315浏览 0评论

Welcome to the third post in the Windows Command-Line series. In this post, we’ll start to dig into the internals of the Windows Console and Command-Line, what it is, what it does … and what it doesn’t do!

Posts in this series:

  1. Command-Line Backgrounder

  2. The Evolution of the Windows Command-Line

  3. Inside the Windows Console (this post)

During the initial development of Windows NT, circa 1989, there was no GUI, there was no desktop, there was ONLY a full-screen command-line, that visually resembled MS-DOS more than it did the future! When the Windows GUI’s implementation started to arrive, the team needed a Console GUI app and thus, the Windows Console was born! Windows Console is one of the first Windows NT GUI apps, and is certainly one of the oldest Windows apps still in general use!

The Windows Console code-base is currently (July 2018) almost 30 years old … older, in fact, than the developers who now work on it! 

欢迎来阅读第三篇Windows 命令行系列文章。在这篇,我们开始深入Windows 控制台和命令行,它是什么,你可以用它可以做什么……和它不能做什么!

系列文章:

  1.     命令行产生的背景

  2.     Windows 命令行的发展

  3.     深入Windows命令行(本篇)

在开始开发Windows NT操作系统的那时候,大概是1989年,那时候还没有GUI(图形化用户界面),也没有桌面操作系统,只有最原始的全屏的命令行界面,类似于MS-DOS的可视化界面越来越重要!Windows GUI 开始开发的时候是在开发团队需要开发一个基于控制台的应用的背景下诞生的!Windows 控制台是第一个Windows NT的GUI应用,并且可以保证兼容运行继续使用已有的Windows应用。

Windows 控制台最初的代码到现在(2018年)已经有30年的历史……古老的东西,事实上,今天还有很多开发者在使用它!

What does the Console do?

As we learned in our previous posts, a Terminal’s job is relatively simple:

  • Handle User Input

    • Accept input from devices including keyboard, mouse, touch, pen, etc.

    • Translate input into relevant characters and/or ANSI/VT sequences

    • Send characters to the connected app/tool/shell

  • Handle App Output:

    • Accept text output from a connected Command-Line app/tool

    • Update the display as required, based on the received app output (e.g. output text, move the cursor, set text color, etc.)

  • Handle System Interactions:

    • Launch when requested

    • Manage resources

    • Resize/maximize/minimize, etc.

    • Terminate when required, or when the communications channel is closed/terminated

However, the Windows Console does things a little differently:

控制台程序能做什么?

就像之前的文章说的,终端的工作其实很简单:

  • 处理用户输入:

    • 可以支持的输入设备包括键盘、鼠标、触摸板、笔等等。

    • 转换输入的数据到中间字符的或者ANSI/VT编码格式

    • 发送字符数据到已连接的应用程序或设备

  • 处理应用程序输出:

    • 允许从已连接的应用程序输出文本

    • 更新屏幕上面的显示,基于应用程序接受显示(比如显示文本,移动光标,设置字体颜色等)

  • 系统协调处理:

    • 运行处理作业请求

    • 管理设备和资源

    • 支持调整窗口尺寸、最大化窗口、最小化窗口等

    • 中断请求或当信道关闭或结束处理

但是,Windows 控制台能做的事情有些不同:

Inside the Windows Console

Windows Console is a traditional Win32 executable and, though it was originally written in ‘C’, much of the code is being migrated to modern C++ as the team modernizes and modularizes Console’s codebase.

For those who care about such things: Many have asked whether Windows is written in C or C++. The answer is that – despite NT’s Object-Based design – like most OS’, Windows is almost entirely written in C! Why? C++ introduces a cost in terms of memory footprint, and code execution overhead. Even today, the hidden costs of code written in C++ can be surprising, but back in the late 1990’s, when memory cost ~$60/MB (yes … $60 per MEGABYTE!), the hidden memory cost of vtables etc. was significant. In addition, the cost of virtual-method call indirection and object-dereferencing could result in very significant performance & scale penalties for C++ code at that time. While one still needs to be careful, the performance overhead of modern C++ on modern computers is much less of a concern, and is often an acceptable trade-off considering its security, readability, and maintainability benefits … which is why we’re steadily upgrading the Console’s code to modern C++! 

深入Windows控制台内部

Windows控制台是一种传统的Win32可执行文件,虽然它最初是用“C”编写的,但随着团队现代化和模块化控制台的代码库,大部分代码都已正在迁移到现代C++了。

对于那些关心此类事物的人:许多人都在询问Windows是用C还是C++编写的。答案是 – 尽管NT是基于对象的设计 – 像大多数操作系统一样,Windows几乎完全用C语言编写!为什么? C++在内存占用和代码执行开销方面引入了开销。即使在今天,使用C++编写的代码的其所隐藏的开销也会令人大吃一惊,但早在1990年代后期,此时内存价格约为60$/MB(是的……每个MEGABYTE为60美元!)时,vtable等隐藏机制的内存开销非常高。此外,虚方法间接调用和对象解引用的开销可能导致当时的C++代码存在非常显着的性能和规模损耗。虽然你仍然需要当心,现代C++在现代计算机上的性能开销并不是一个值得关注的问题,同时考虑到其安全性、可读性和可维护性方面的优势,这通常是一种可接受的折衷…这就是为什么我们将Console的代码稳步升级到现代C++这样做的原因!

So, what’s inside the Windows Console?

Before Windows 7, Windows Console instances were hosted in the crucial Client Server Runtime Subsystem (CSRSS)! In Windows 7, however, for security and reliability reasons, Console was extracted from CSRSS, and given a new home in the following binaries:

  • conhost.exe – the user-mode Windows Console UX & command-line plumbing

  • condrv.sys – a Windows kernel driver providing communication infrastructure between conhost and one or more Command-Line shells/tools/apps

A high-level view of Console’s current internal architecture looks like this:

The core components of the Console consist of the following (from the bottom-up):

  • ConDrv.sys – Kernel-Mode driver

    • Data representing requests to execute API calls against the Console instance

    • Text sent from the Console to the Command-Line app

    • Provides a high-performance communications channel between Console and any connected Command-Line apps

    • Ferries IO Control (IOCTL) messages back and forth between Command-Line apps and the Console they’re “attached” to

    • Console IOCTL messages contain

  • ConHost.exe – Win32 GUI app:

    • Manages the layout, size, position, etc. of the Console window on-screen

    • Displays and handles settings UI, etc.

    • Pumps the Windows message queue, handles Windows messages, and translates user input into key and mouse event records, storing them in the Input Buffer

    • API Server: Converts IOCTL messages received from Command-Line app(s) into API calls, and sends text records from Console to Command-Line app

    • API: Implements the Win32 Console API & logic behind all the operations that the Console can be asked to perform

    • Input BufferStores keyboard and mouse event records generated by user input

    • VT Parser: If enabled, parses VT sequences from text, extracts any found from text, and generates equivalent API calls instead

    • Output BufferStores the text displayed on the Console’s display. Essentially a 2D array of CHAR_INFO structs which contain each cell’s character data & attributes (more on the buffer below)

    • Other: Not included in the diagram above include settings infrastructure storing/retrieving values from registry and/or shortcut files etc.

    • ConHost Core – the Console’s internals and plumbing

    • Console UX App Services – the Console UX & UI layer

那么,Windows 控制台内部是什么样?

在 Windows 7 之前,Windows 控制台实例托管于核心的客户-服务器运行子系统(Client Server Runtime Subsystem,CSRSS)!然而,在 Windows 7 中,考虑到安全性和可靠性因素,控制台从CSRSS 中剥离出来,组件了一个包含如下二进制文件的新家庭:

  • conhost.exe – 用户模式的 Windows 控制台 UX 和命令行管道

  • condrv.sys – 一个提供基础通信结构的核心驱动,连接 conhost 和命令行 Shell/工具/应用之间的通信

控制台当前的内部结构总体结构图就像这样:

控制台的核心组件包含如下内容(自下而上):

  • ConDrv.sys – 核心模式驱动

    • 请求执行 API 调用控制台实例的数据呈现

    • 从控制台发送到命令行应用的文本

    • 为控制台及其连接的命令行应用提供高性能通信通道

    • 在控制台及附着于其上的命令行应用这间反复传递 IO 控制 (IOCTL) 消息

    • 管理控制台 IOCTL 消息

  • ConHost.exe – Win32 图形界面(GUI)应用:

    • 管理控制台容器在屏幕上的布局、大小、位置等。

    • 显示并处理设置界面等。

    • 调用 Windows 消息队列,处理 Windows 消息并将用户输入转换为键盘和鼠标事件,并将之存储于输入缓冲区。

    • API Server: 转换 API 调用时从命令行应用收到的 IOCTL 消息,并将文本记录从控制台发给命令行应用。

    • API: 实现 Win32 控制台 API,以及所有要求控制台执行的操作背后的逻辑。

    • Input Buffer: 保存由用户输入产生的键盘和鼠标事件记录

    • VT Parser: 如果启动,则从文本中解析 VT 序列,根据找到的信息产生等效的 API 调 I用

    • Output Buffer保存控制台呈现的文本。本质上是一个二维的  CHAR_INFO 结构数组,其每个元素都包含了字符数据及其属性(缓存区之下的更多信息)

    • Other: 未包含在上层呈现,包含从注册表或快捷文件中存储/检索基础设置值。

    • ConHost Core – 控制台的内部控制和管道

    • Console UX App Services – 控制台的 UX 和 UI 层

The Windows Console API

As can be seen in the Console architecture above, unlike NIX terminals, the Console sends/receives API calls and/or data serialized into IO Control (IOCTL) messages, not serialized text! Even ANSI/VT sequences embedded in text received from (primarily Linux) Command-Line apps is extracted, parsed and converted into API calls!

This difference exposes the key fundamental philosophical difference between *NIX and Windows: In *NIX, “everything is a file”, whereas, in Windows, “everything is an object“!

There are pros and cons to both approaches, which we’ll outline, but avoid debating at length here. Just remember that this key difference in philosophy is fundamental to many of the differences between Windows and *NIX!

Windows控制台API

从上述的控制台架构图中可以看出,与NIX终端不同的是,控制台发送/接收API调用和/或数据序列化为IO控制(IOCTL)消息,而不是序列化后的文本! 甚至从(主要是Linux)命令行应用程序接收的文本中所嵌入的ANSI/VT序列也被提取、解析并转换为API调用!

这种差异揭示了*NIX和Windows之间关键的基本哲学差异:在*NIX中,“一切都是文件”,然而在Windows中,“一切都是对象”!

两种方法都有利有弊,我们将概括之,但避免在这里进行长篇大论。请记住,哲学中的这一关键差异是Windows和* NIX之间诸多差异的基础!

In *NIX, Everything is a File

When Unix was first implemented in the late 1960’s and early 1970’s, one of the core tenets was that (wherever possible) everything should be abstracted as a file stream. One of the key goals was to simplify the code required to access devices and peripherals: If all devices presented themselves to the OS as file-systems, then existing code could access those devices more easily.

This philosophy runs deep: One can even navigate and interrogate a great deal of a *NIX-based OS & machine configuration by navigating pseudo/virtual file-systems which expose what appear to be “files” and folders, but actually represent machine configuration, and hardware.

For example, in Linux, one can explore a machine’s processors’ properties by examining the contents of the /proc/cpuinfo pseudo-file:

The simplicity and consistency of this model can, however, come at a cost: Extracting/interrogating specific information from text in pseudo files, and returned from executing commands often requires tools, e.g. sed, awk, perl, python, etc. These tools are used to write commands and scripts to parse the text content, looking for specific patterns, fields, and values. Some of these scripts can get quite complex, are often difficult to maintain, and can be fragile – if the structure, layout, and/or format of the text changes, many scripts will likely need to be updated.

在 *NIX系统中,一切都是文件

在60年代末和70年代初Unix被第一次实现的时候,其中一个核心原则就是任何东西都可以被抽象成文件流,一个关键目标是简化对设备和外设的访问处理:如果所有的设备都在系统中以文件系统的形式存在,那么现存的代码就可以不做修改地直接访问这些设备。

这个原则影响深远:你可以通过伪文件系统或虚拟文件系统来浏览和查询大量的基于*NIX的系统和机器配置,它们仅仅是”表现得“像是“文件”或“文件夹”,实际可能是机器配置或硬件。

例如,在Linux中,你可以通过访问 /proc/cpuinfo 虚拟文件节点来查看CPU的一些信息:

这个模型是如此简单和一致,但它也存在一些额外开销:从这些伪文件中提取或查询特殊的文本信息并从执行命令中返回,经常需要一些工具的辅助,比如:sed,awk,perl,python等。这些工具经常被用来写脚本和命令来解析文本内容、查找特殊模式、区域和值。这些脚本可以变得非常复杂,难以维护和碎片化。如果文本的结构、布局或格式发生变更,那么许多脚本也需要随之更新。

In Windows, Everything is an Object

When Windows NT was being designed & built, “Objects” were seen as the future of software design: “Object Oriented” languages were emerging faster than rabbits from a burrow – Simula and Smalltalk were already established, and C++ was becoming popular. Other Object-Oriented languages like Python, Eiffel, Objective-C, ObjectPascal/Delphi, Java, C#, and many others followed in rapid succession.

Inevitably, having been forged during those heady, Object-Oriented days (circa 1989), Windows NT was designed with a philosophy that “everything is an object”. In fact, one of the most important parts of the NT Kernel is the “Object Manager“!

Windows NT exposes a rich set of Win32 API’s that can be called to obtain and/or manipulate objects from the Operating System. Developers use the Win32 API to gather and present similar information provided by *NIX pseudo files and tools, but via objects and structures. And because parsers, compilers, and analyzers understand the structure of objects, many coding errors can often be caught earlier, helping verify that the programmer’s intent is syntactically and logically correct. This can also result in less breakage, volatility, and “churn” over time.

So, coming back to our central discussion about Windows Console: The NT team decided to build a “Console” which differentiated itself from a traditional *NIX terminal in a couple of key areas:

  1. Console API: Rather than relying on programmers’ ability to generate “difficult to verify” ANSI/VT-sequences, Windows Console can be manipulated and controlled via a rich  Console API

  2. Common services: To avoid having every Command-Line shell re-implement the same services time and again (e.g. Command History, Command Aliasing), the Console itself provides some of these services, accessible via the Console API

在Windows中,任何事物都是对象

Windows NT被设计和构建时,“对象”被视为软件设计的未来:“面向对象”的语言比洞穴里的兔子更快出现 – Simula和Smalltalk已经建立起来,而C ++正变得越来越流行。其他面向对象的语言,如Python,Eiffel,Objective-C,ObjectPascal / Delphi,Java,C#等许多其他语言都在快速发展紧随其后。

不可避免的是,它成型于面向对象大好时期(大约1989年)中,Windows NT的设计理念是“一切都是对象”。事实上,NT内核最重要的部分之一是“对象管理器”!

Windows NT公开了一组丰富的Win32 API,可以调用这些API来从操作系统获取和/或操作对象。开发人员使用Win32 API来收集和呈现* NIX伪文件和工具提供的类似信息,但是通过对象和结构。并且因为解析器,编译器和分析器理解对象的结构,所以通常可以更早地捕获许多编码错误,从而帮助验证程序员的意图在语法和逻辑上是否正确。随着时间的推移,这也可以减少系统破损,波动和“搅动”。

所以,回到我们关于Windows控制台的中心讨论:NT团队决定构建一个“控制台”,它在几个关键领域区别于传统的* NIX终端:

  1. 控制台API:Windows Console可以通过丰富的Console API进行操作和控制,而不是依赖程序员生成“难以验证”的ANSI / VT序列的能力。

  2. 公共服务:为避免每个命令行shell一次又一次地重新实现相同的服务(例如命令历史记录,命令别名),控制台本身提供了一些这些服务,可通过Console API访问

Problems with the Windows Console

While the Console’s API has proven very popular in the world of Windows Command-Line tools and services, the API-centric model presents some challenges for Command-Line scenarios:

Only Windows implements the Console API

Many Windows Command-Line tools and apps make extensive use of the Console API.

The problem? These APIs only work on Windows.

Thus, combined with other differentiating factors (e.g. process lifecycle differences, etc.), Windows Command-Line apps are not always easily-portable to *NIX, and vice-versa.

Because of this, the Windows ecosystem has developed its own, often similar, but usually different Command-Line tools and apps. This means that users have to learn one set of Command-Line apps and tools, shells, scripting languages, etc. when using Windows, and another when using *NIX.

There is no simple quick-fix for this issue: The Windows Console and Command-Line cannot simply be thrown away and replaced by bash and iTerm2 – there are hundreds of millions of apps, and scripts that depend upon the Windows Console and Cmd/PowerShell shells.

3rd party tools like Cygwin do a great job of porting many of the core GNU tools and compatibility libs to Windows, but they’re unable to run un-ported, unmodified Linux binaries. This is pretty essential since many Ruby, Python, Node packages and modules depend upon or wrap Linux binaries, and/or depend upon *NIX behaviors.

These reasons led Microsoft to expand Windows’ compatibilities by enabling genuine, unmodified Linux binaries and tools to run natively on Windows’ Subsystem for Linux (WSL). Using WSL, users can now download and install one or more Linux distros side-by-side on the same machine, and use apt/zypper/npm/gem/etc. to install and run the vast majority of Linux Command-Line tools alongside their favorite Windows apps and tools.

However, there are still some things that Console offers that haven’t been adopted by non-Microsoft terminals: Specifically, the Windows Console provides command-history and command-alias services, eliminating the need for every command-line shells (in particular) to re-re-re-implement the same functionality.

Windows控制台的问题

虽然Console的API已经证明在Windows命令行工具和服务领域非常流行,但以API为中心的模型对命令行方案提出了一些挑战:

只有Windows实现了Console API

许多Windows命令行工具和应用程序广泛使用Console API

问题呢?这些API仅适用于Windows。

因此,结合其他差异化因素(例如过程生命周期差异等),Windows命令行应用程序并不总是易于移植到* NIX,反之亦然。

因此,Windows生态系统开发了自己的,通常类似但通常不同的命令行工具和应用程序。这意味着用户在使用Windows时必须学习一组命令行应用程序和工具,shell,脚本语言等,而在使用* NIX时则需要学习另一组。

这个问题没有简单的快速解决方案:Windows控制台和命令行不能简单地丢弃并被bash和iTerm2取代 – 有数以亿计的应用程序和脚本依赖于Windows控制台和Cmd / PowerShell shells。

Cygwin这样的第三方工具可以很好地将许多核心GNU工具和兼容性库移植到Windows,但是它们无法运行未移植的,未经修改的Linux二进制文件。这非常重要,因为许多Ruby,Python,Node包和模块依赖于或包装Linux二进制文件,或者依赖于* NIX运转状态。

这些原因促使微软通过在 Windows的子系统Linux(WSL)上本地运行真正的,未经修改的Linux二进制文件和工具来扩展Windows的兼容性。使用WSL的用户现在可以在同一台机器上并行下载和安装一个或多个Linux发行版,并使用apt / zypper / npm / gem / etc.安装和运行绝大多数Linux命令行工具以及他们喜欢的Windows应用程序和工具。

但是,还有一些控制台提供的东西尚未被非Microsoft终端采用:具体来说,Windows控制台提供命令历史记录和命令别名服务,从而无需每个命令行shell(特别是)重新实现相同的功能。

Remoting Windows’ Command-Line is difficult

As we discussed in the Command-Line Backgrounder post, Terminals were originally separate from the computer to which they were attached. Fast-forward to today, this design remains: Most modern terminals and Command-Line apps/shells/etc. are separated by processes and/or machine boundaries.

On *NIX-based platforms, the notion that terminals and command-line applications are separate and simply exchange characters, has resulted in *NIX Command-Lines being easy to access and operate from a remote computer/device: As long as a terminal and a Command-Line application can exchange streams of characters via a some type of ordered serial communications infrastructure (TTY/PTY/etc.), it is pretty trivial to remotely operate a *NIX machine’s Command-Line.

On Windows however, many Command-Line applications depend on calling Console API’s, and assume that they’re running on the same machine as the Console itself. This makes it difficult to remotely operate Windows Command-Line shells/tools/etc.: How does a Command-Line application running on a remote machine call API’s on the user’s local machine’s Console? And worse, how does the remote Command-Line app call Console API’s if its being accessed via a terminal on a Mac or Linux box?!

Sorry to tease, but we’ll return to this subject in much more detail in a future post!

把 Windows 命令行远程化是困难的

正如我们在 Command-Line Backgrounder 一文中所讨论的那样,终端最初与它们所连接的计算机是分开的。快进到今天,这种设计仍然存在:大多数现代终端和命令行应用程序/shell 等等是由进程或机器边界分隔的。

在基于 *NIX 的平台上,终端和命令行应用程序的分离并通过简单的字符进行通信的概念导致 *NIX 命令行易于从远程计算机/设备访问和操作:只要终端和命令行应用程序可以通过某种类型的有序串行通信基础架构(TTY/PTY 等)传输字符流,远程操作 *NIX 机器的命令行是非常简单的。

但是在 Windows 上,许多命令行应用程序依赖于调用 Console API,并假设它们与控制台本身在同一台机器上运行。这使得远程操作 Windows 命令行 shell/工具等变得很困难:在远程计算机上运行的命令行应用程序如何调用在用户本地计算机的控制台上的 API 呢?更糟糕的是,如果远程命令行应用程序通过 Mac 或 Linux 机器上的终端访问,它如何调用 Console API 呢?!

很抱歉开个玩笑,但我们将在以后的文章中更详细地阐释这个主题!

Launching the Console … or not!

Generally, on *NIX based systems, when a user wants to launch a Command-Line tool, they first launch a Terminal. The Terminal then starts a default shell, or can be configured to launch a specific app/tool. The Terminal and Command-Line app communicate by exchanging streams of characters via a Pseudo TTY (PTY) until one or both are terminated.

On Windows, however, things work differently: Windows users never launch the Console (conhost.exe) – they launch Command-Line shells and apps e.g. Cmd.exe, PowerShell.exe, wsl.exe, etc. instead. Windows hooks-up the newly launched app to the current Console (if launched from the Command-Line), or to a newly created Console instance.

#SAYWHATNOW?

Yes, in Windows, users launch the Command-Line app, NOT the Console itself.

If a user launches a Command-Line app from an existing Command-Line shell, Windows will usually attach the newly launched .exe to the current Console. Otherwise, Windows will spin up a new Console instance to which it will attach the newly launched app.

Small nit: Many people say “Command-Line apps run in the Console”. This isn’t true and leads to a lot of confusion about how Consoles and Command-Line apps actually work! Command-Line apps and their Consoles eachrun in their own independent Win32 processes. Please help correct this misconception by pointing out that “Command-Line tools/apps run connected to a Console” (or similar). Thanks! 

启动控制台或者不!

通常,在基于 *NIX 的系统上,当用户想要启动一个命令行工具时,他们首先会启动一个终端。然后终端启动一个默认的 shell ,或者可以配置为启动一个特定的应用程序/工具。终端和命令行应用程序通过伪终端(PTY)交换字符流进行通信,直到一个或两个字符终止。

然而,在 Windows 系统上,事情就不一样了:Windows 用户永远不会启动控制台(conhost.exe)——然而他们会启动像是 Cmd.exe,PowerShell.exe,wsl.exe 等等这样的命令行 shell 和应用程序。Windows 系统将新启动的应用程序连接到当前控制台(如果是从命令行启动的话),或者连接到新创建的控制台实例。

# 现在要说的?

是的,在 Windows 系统中,用户启动命令行应用程序,而不是控制台本身。

如果用户从现有的命令行 shell 启动命令行应用程序,Windows 通常会将新启动的 .exe(可执行文件) 附加到当前控制台。否则,Windows 会将一个新的控制台实例与新推出的应用程序绑定在一起。

小白说:很多人说“命令行程序在控制台运行”。这不是真的,而且导致很多关于控制台和命令行应用程序如何工作的困惑!命令行应用程序和它们的控制台都在各自独立的 Win32 进程中运行。请通过指出“命令行工具/应用程序连接到控制台运行”(或类似的)来帮助纠正这种误解。谢谢!


via:oschina

转载请注明:AspxHtml学习分享网 » Windows 命令行:深入 Windows 控制台(Windows Command-Line: Inside the Windows Console)

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址