Shell、TTY子系统、终端的概念和作用
Linux系统的交互式命令行的组成
Linux系统的交互式命令行由Shell和TTY子系统构成实现。
Shell和TTY子系统的概念和作用
Shell是一个运行于用户空间的应用程序,它负责命令的解析、执行和执行结果的输出,它需要有设备为它提供命令内容的输入和接收它的输出,这个设备就由TTY子系统来提供。TTY子系统是一个分层的框架结构,我们可以把TTY子系统看成是面向对象编层中的一个类,这个类的一个实例就是一个TTY设备,一个TTY设备对应于一个设备文件。Shell如果要进入输入输出操作,那么去操作这个TTY设备文件就可以了。
上面这段话大家可以结合下面这幅TTY子系统的框架结构图来理解:

终端的概念和作用
终端有两个含义,我们这里先说它在TTY框架中的含义,另一个含义放到下一个目录中讲。
终端在TTY框架中的含义如下:
终端是TTY子系统中的概念,它位于“TTY I/O”层和“行规程(line discipline)”层之间,它实际上可以看成是“TTY I/O”层的输入/输出接口。
从图中我们可以看到,终端层根据底层的设备数不同,具体的实现结构也不一样,对应的终端类型也不一样。
情况一:底层设备为1个的情况:
比如运行在开发板上的Linux系统,键盘和显示器借用的是PC的,开发板与PC之间通过串口来进行通信,所以对于运行在开发板上的Linux系统,完成一条命令的输入和输出显示需要的只是1个串口设备而已,此时终端层的具体实现为空,即终端层不需要额外的代码(无实际功能),此时串口的输入和输出经行规程(line discipline)处理后,直接就送给了“TTY I/O”层。这种情况下的终端被称为叫“串口终端”。
情况二:底层设备为2个的情况:
比如在PC上运行的Ubuntu中的交互式命令行,则需要键盘和显示器一起,才能实现一条命令的输入和输出显示,此时终端对应的底层实现设备有两个,即键盘和显示器,此时就需要终端层提供一个有实际功能的终端,把这两个底层设备在逻辑上合并在一起后提供给“TTY I/O”层。这种情况下的终端被称为叫“虚拟终端”,又称为虚拟控制台(virtual console)。
细说Shell的作用
然后再来细说下Shell。Shell负责命令的解析和执行,它是一个 用户空间的应用程序,它的任务是:
- 读取 TTY 设备传来的输入(命令)。
- 解析命令并执行(如
ls、cd、echo)。 - 将执行结果传递给TTY设备。
- 处理管道、重定向、环境变量等(如
ls | grep txt)。 - 提供交互功能(如命令补全、历史记录)。
两个实际的命令行交互例子
例子1:在PC上的Ubuntu上进行命令行交互
假设你在PC上的Ubuntu的命令行输入下面的命令:
ls -l
则内部发生的流程如下:
- 键盘输入
ls -l,TTY 结构中的底层设备驱动(如keyboard.c)捕获输入数据,并传递给TTY结构中的行规程。 - TTY结构中的行规程层→虚拟终端→“TTY I/O”层依次处理并传递数据,“TTY I/O”层最终把数据传递给用户空间的 Shell程序。
- Shell 解析命令
ls -l,查找/bin/ls并执行该程序。 ls进程运行,产生文件列表输出,将数据写入标准输出(stdout)。- TTY结构中的“TTY I/O”层→虚拟终端→行规程层依次处理并传递数据,将数据传递给底层显示设备(如屏幕)。
- 用户看到
ls -l结果,完成了一次命令行交互。
例子2:运行于开发板上的Linux系统利用PC上的SecureCRT完成命令行交互
假设你的嵌入式开发板通过串口与PC相连,并在SecureCRT中输入以下命令:
ls -l
则内部发生的流程如下:
- PC端SecureCRT输入
ls -l,SecureCRT 通过串口将数据发送到嵌入式开发板。 - 嵌入式开发板的串口驱动(如
imx-serial.c)接收数据,并传递给TTY子系统结构中的行规则层,由于没有实际的终端层,所以行规则层处理完后,直接将数据传递给“TTY I/O”层,TTY I/O”层最终把数据传递给用户空间的 Shell程序。说明一下:其实这个串口驱动也是基于TTY子系框架来进行编写和注册的。 - Shell 解析命令
ls -l,查找/bin/ls并执行该程序。 ls进程运行,产生文件列表输出,将数据写入标准输出(stdout)。- TTY结构中的“TTY I/O”层将数据接收到后,由于没有实际的终端层,所以“TTY I/O”层直接将数据发给行规则层,行规则层处理完后,将数据传给串口。
- 串口收到待发送数据后,再把数据发送到PC端,PC端收到数据后再把数据传递给运行于PC上的SecureCRT 。
- SecureCRT 接收到数据后进行显示,用户在PC上看到
ls -l结果,完成了一次命令行交互。
特别要注意:“终端”的另一个含义:表示Linux中的 “交互式命令行”
上面的介绍中,在TTY框架中有终端的概念,这个时候的终端是指TTY子系统中位于“TTY I/O”层和“行规程(line discipline)”层之间的层。
但“终端”这个词还有另一含义:即表示Linux中的 “交互式命令行”。
在下文的叙述中,你要需要根据实际情况去分区到底在具体环境中“终端”是什么含义。
为什么大家喜欢把“交互式命令行”称为叫终端(Terminal)?
我们在Ubuntu中如果要打开一个交互式命令行,通常是搜索Terminal,然后打开相应的程序:


从这个过程来看,在Ubuntu中,实际上是把“交互式命令行”的名字命名为“终端(Terminal)",为什么呢?
其实不为什么,就是大家习惯这样叫了,毕竟终端在Linux系统的“交互式命令行”实现中也是个关键因素嘛。
即然Ubuntu中把“交互式命令行”命名为“终端(Terminal)",那我们也就把其它情况下的“交互式命令行”也可以称为终端吧,比如SecureCRT打开的“交互式命令行”。事实上,大家也习惯把“交互式命令行”称为叫终端了。
TTY子系统的结构和框架详解
TTY子系统的结构示意图及讲解

Linux TTY(Teletype)子系统是Linux内核中的一个核心部分,主要负责负责处理用户与系统之间的交互。结合这幅框架图,我们可以从以下几个层次来介绍TTY子系统的架构及工作机制。
1. 用户空间与TTY设备
在用户空间,shell(如 shell0、shell1)是典型的命令行交互程序,用户可以在其中输入命令并获得输出。它们通过设备文件(如 /dev/ttyS0、/dev/tty3、/dev/tty4)与内核中的TTY子系统进行交互。
这些设备文件可以代表不同类型的TTY设备:
- 串口终端(Serial Terminal):如
/dev/ttyS0,通常对应物理串口设备(UART)。 - 虚拟终端(Virtual Console, VC):如
/dev/tty3,是Linux内核提供的多终端切换机制。
2. TTY I/O层
TTY I/O层(drivers/tty/tty_io.c)是TTY子系统的核心部分,负责管理TTY设备,处理应用程序的读写请求,并与底层硬件驱动进行交互。它的主要功能包括:
- 维护
struct tty_struct结构体,一个struct tty_struct表示一个TTY设备实例。 - 处理
read()、write()、ioctl()等系统调用。 - 调度数据流动,避免不同的终端之间相互干扰。
3. 终端层
图中的“终端层”对于底层为串口设备来说,没有实际的终端层实现。对于底层为键盘和显示器来说,实际了虚拟终端(Virtual Console,VC)。
虚拟终端的作用如下:
- 允许用户在多个虚拟终端之间进行切换,而不需要多套键盘和显示器。
- 提供命令行环境,即用户在不同的TTY设备上运行不同的shell进程。
- 把显示器(Display)和键盘(Keyboard)合并为一个逻辑设备,让它们作为一个整体来处理输入和输出。这样,用户空间的shell 只需要和 /dev/ttyX 交互,而不需要关心具体的输入和输出设备。
4. 行规程(Line Discipline)
行规程(drivers/tty/n_tty.c)是TTY子系统中一个重要的中间层,它决定了如何处理输入数据。它的主要作用是:
- 处理回显(echo)、退格(backspace)、行编辑等功能。
- 解析特定的终端控制字符(如
Ctrl+C发送SIGINT信号终止进程)。 - 规范化用户输入,使应用程序可以使用标准输入方式读取数据。
5. 驱动层
TTY子系统与硬件交互主要依赖驱动程序,分为:
-
串口驱动(UART Driver):
- 典型实现文件:
drivers/tty/serial/imx.c(i.MX平台的串口驱动)、stm32-usart.c(STM32平台的USART驱动)。 - 负责UART控制器的配置、数据发送和接收。
- 通过底层UART硬件将数据发送至远程终端或接收来自串口设备的数据。
- 典型实现文件:
-
显示驱动(Display Driver):
- 典型实现文件:
drivers/video/console/vgacon.c。 - 负责将TTY的输出数据呈现在屏幕上(如VGA文本模式的终端)。
- 在虚拟终端切换时更新屏幕显示内容。
- 典型实现文件:
-
键盘驱动(Keyboard Driver):
- 典型实现文件:
drivers/tty/vt/keyboard.c。 - 负责读取键盘输入,并将数据传输到TTY子系统中。
- 可以处理特殊按键(如
Alt+F1切换虚拟终端)。
- 典型实现文件:
6. 硬件层
最底层是实际的硬件设备:
- UART(串口):提供远程终端通信能力,比如嵌入式设备上的串口调试终端。
- 显示设备(Display):如VGA显示器或LCD屏幕,显示终端输出内容。
- 键盘(Keyboard):用户通过键盘输入数据,由键盘驱动处理后送往TTY子系统。
小结
Linux的TTY子系统提供了终端交互的核心机制:
- 用户层 通过
/dev/ttyX设备文件访问TTY子系统。 - TTY I/O层 负责管理TTY实例,调度数据流动。
- 终端层 负责虚拟终端的管理,实现多终端切换、实现把显示器(Display)和键盘(Keyboard)合并为一个逻辑设备,让它们作为一个整体来处理输入和输出
- 行规程层 处理终端输入,提供用户友好的交互体验。
- 驱动层 连接TTY子系统与实际的硬件设备,如UART、显示设备、键盘。
TTY框架中涉汲到的关键数据结构
在 Linux 内核中,TTY 设备通常使用以下关键结构体:
struct tty_driver:TTY 驱动的主要结构,负责管理 TTY 设备。struct tty_port:表示一个 TTY 端口,负责维护设备状态。struct tty_operations:TTY 设备的操作函数集合,类似于file_operations,包括open、close、write等。
如何开发一个 TTY框架下的设备驱动
如果你要开发一个 TTY 框架下的终端设备驱动,一般需要:
- 注册一个 TTY 驱动(
tty_register_driver)。 - 实现
struct tty_operations操作函数(如open、write)。 - 管理 TTY 端口(
struct tty_port)。 - 处理数据收发(如果是串口设备,需要对接 UART 驱动)。
- 具体的实现教程和例子请百度网盘搜索“1-13_13_编写虚拟UART驱动程序_框架”。
可以通过TTY框架使用串口
如果TTY框架的底层设备是串口,那么从TTY的框架可以看出,当把TTY框架中的行规程层的相关数据处理功能关闭,那么别的非Shell的应用程序其实是可以通过TTY框架使用串口的。相关的例子见博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/146175153
TTY设备的设备文件中的tty三个字母的来源
在Linux中,以tty开头的设备文件通常用于TTY设备。tty是“Teletypewriter”的缩写,它本意指的是电传打字机,由于Linux的TTY子系统就是用来管理用户与系统之间的交互,而电传打字机也是人机交互的设备,所以把Linux的的这个子系统称为叫TTY子系统。
如何查看当前Shell所使用的TTY设备文件
输入下面这个命令就可以看查看当前Shell所使用的TTY设备文件
tty
示例截图如下:




常见终端(交互式命令行)的分类
注意:这里的终端是指交互式命令行,分类的依据是交互式命令行使用的TTY设备中的终端类型来划分。这句话中前面的“终端”和后面的“终端”是不同的含义,也就是说“终端”这个词在本文中有两个含义,具体的我在前面已经叙述了,详情请在本文中搜索“另一个含义”。
1-串口终端
比如我与开发板之间通过串口和SecureCRT形成的交互式命令行就是一个典型的串口终端,在串口终端上运行tty命令可得到这个终端所使用的TTY设备文件的名字,结果如下:

TTY设备文件名称中的mxc是 Freescale/NXP i.MX 系列处理器的串行控制器名称。
不过一般的串口终端对应的TY设备文件名通常是/dev/ttySX(如/dev/ttyS0、/dev/ttyS1等)。
02-虚拟控制台终端(Virtual Console)
虚拟控制台终端(Virtual Console)【简称“虚拟终端”】是系统启动后由 Linux 内核提供的,通常从系统启动时就开始运行并一直存在,直到系统关机。它们是通过系统的 内核 和 TTY中的虚拟终端驱动(vt 驱动) 来模拟的。
虚拟控制台终端(Virtual Console)能让用户能够在没有图形用户界面的情况下(或在多用户环境中)通过键盘和显示器进行系统管理、登录、运行命令等。每个虚拟终端提供独立的会话,你可以在每个终端上执行不同的任务。
虚拟控制台终端(Virtual Console)所使用的TTY设备文件的路径和名称通常是/dev/ttyX(X 是从 0 到 6,通常 tty1 到 tty6)
当 Linux 系统启动时,内核会自动分配多个虚拟控制台终端(Virtual Console)(通常是 tty1 到 tty6,可以通过 Ctrl + Alt + F1 到 F6 切换,通常来说F2是Ubuntu的图形界面终端)。这些终端一开始就存在,并且在整个系统运行过程中一直保持活跃。
我们的Ubuntu系统启动后,我们按下Ctrl + Alt + F3【注意:不是Ctrl + SHIFT + F3】,得到下面的终端(即命令行交互界面):

然后输入用户名book和密码就能登陆了。注意:如果密码中有数字不能用数字键盘区域输入数字,而要用字母区上方的数字输入,否则会提示不正确的登陆。登陆成功后的截图如下:

然后输入exit即可退出这个终端(即命令行交互界面):

退出之后如下图所示:

然后我们可以按是Ctrl + Alt + F2切换回图形终端:

03-伪终端(Pseudo Terminal)
Ubuntu图形界面下搜索Terminal打开的终端(命令行界面)就是一个典型的伪终端,它在TTY框架中对应的终端层类型为伪终端类型。
另外通过网络远程连接(如 SSH)连接Linux系统时得到的终端,其类弄也为伪终端。所以其实目前来说这种类型的终端是最常用的。
这种终端(即交互式命令行)也可以打开很多个,所以其实它和虚拟控制台终端(Virtual Console)也很像。不过伪终端不像虚拟控制台终端那样系统启动后就存在并且一直运行,伪终端则需要人为打开才行,比如在Ubuntu图形界面下搜索Terminal打开,或者通过SSH远程连接打开。
伪终端所使用的TYY设备文件的路径和名字通常如下:
/dev/pts/X(X 是一个数字,表示不同的伪终端设备)
相关截图如下:




多个终端之间的通信示例
我们在Ubuntu中通过右键打开两个终端,如下图所示:


然后分别用命令tty查看它们的TTY设备文件:

我们不妨把使用/dev/pts/0TTY设备文件的终端称为叫终端0,把使用/dev/pts/1TTY设备文件的终端称为叫终端1,
接下来,我们在终端0中运行下面这条命令通过终端0向终端1发送hello信息:
echo hello > /dev/pts/1

从截图中可以看到终端0向终端1发送了字符hello,而终端1收到并打印了出来。
重要说明:我们认为一个TTY设备文件代表某一个终端(交互式命令行)
由于一个终端(交互式命令行)必定会使用一个唯一的TTY设备文件,所以在下面的叙述中,我们就认为一个TTY设备文件代表某一个终端(交互式命令行)。
几个特殊的TTY设备文件
前提说明:我们认为一个TTY设备文件代表某一个终端(交互式命令行)
由于一个终端(交互式命令行)必定会使用一个唯一的TTY设备文件,所以在下面的叙述中,我们就认为一个TTY设备文件代表某一个终端(交互式命令行)。
01-/dev/tty:代表进程所在终端
在Linux中,/dev/tty 代表当前进程所关联的控制终端(controlling terminal)。它是一个特殊的设备文件,指向当前会话的终端。这样,如果某个进程想使用与自己关联的终端,那么就可以直接使用/dev/tty ,而不用再去写清楚具体的终端对应的TTY设备文件名。
具体含义:
/dev/tty始终指向当前进程的控制终端的TTY设备。- 如果一个进程运行在某个终端(如
/dev/pts/2),那么/dev/tty对于该进程来说等价于/dev/pts/2。
示例:
我在终端/dev/pts/1中运行下面这条命令:
echo "Hello" > /dev/tty
等效于:
echo "Hello" > /dev/pts/1
运行结果如下:

02-特殊的虚拟控制台终端:/dev/tty0
/dev/tty0是一个特殊的设备文件,它代表当前活动的虚拟控制台终端,注意:它只对虚拟控制台终端有效,它不适用于伪终端。
当你在一个特定的虚拟控制台上工作时,系统会将其作为当前控制台,并将输出发送到该控制台。如果你在 tty1 上登录并工作,/dev/tty0 则指向 tty1,因为它是当前活动的虚拟控制台终端。
我们启动Ubuntu系统,然后用Ctrl + Alt + F3和Ctrl + Alt + F4打开两个虚拟控制台终端,如下图所示:


然后我们在tty3中手动输入下面的命令(注意:tty3这个虚拟控制台终端不支持复制粘贴功能),然后运行:
while [ 1 ]; do echo msg_from_tty3_to_tty0 > /dev/tty0; sleep 3; done
这条命令表示每隔3秒向当前活动的虚拟控制台终端输入字符串信息“msg_from_tty3_to_tty0”,相应的当前活动终端会在收到信息后会打印出来。

然后运行结果却告诉我command not found:

…这就没办法了。
理想情况应该是不管切换到哪个虚拟控制台终端,它都会打印出信息“msg_from_tty3_to_tty0”。
被作为控制台的终端与一般终端的区别
在u-boot和Linux的系统设置中,有一个参数是console。
在博文 https://blog.youkuaiyun.com/wenhao_ir/article/details/145902134 中我们看到u-boot中有个名叫console的环境变量,如下图所示:

这个参数是作为u-boot传递给内核的启动参数bootargs里使用的,如下图所示:

那么这个参数是何意义呢?它表示u-boot使用该串口终端ttymxc0作为控制台。另外这个值也通过参数bootargs传递给内核,也就是说内核也用终端ttymxc0作为控制台。
那么什么叫控制台呢?简单的理解就是被作为控制台的终端会打印输出各种启动信息与内核信息。
详细的理解如下:
在 Linux 和嵌入式系统中,“控制台(console)” 和 “终端(terminal)” 都涉及用户与系统的交互,但它们有不同的概念和作用。
1. 控制台(Console)
定义:
控制台 是一个特殊的终端,它通常是 系统启动时的主要输入输出设备,用于显示 系统消息(如内核日志) 和 接收用户输入(如命令行登录)。
特点:
- 系统的默认交互终端,可以接收 Linux 内核的
printk()输出。 - 在系统崩溃或发生错误时,调试信息一般会输出到 控制台。
- 可以是:
- 物理设备终端(如 串口 UART、VGA 显示器 + 键盘)。
- 虚拟终端(如
/dev/tty1)。 - 远程终端(如
ssh连接后打开的pts/0)。
示例:
在 U-Boot 或 Linux 内核的启动参数中,console= 选项指定 控制台设备:
console=ttyS0,115200
这表示:
- 系统的主要日志输出 会通过
ttyS0这个串口。 - 用户可以在
ttyS0上直接与系统交互。
可以用 dmesg | grep console 查看当前的控制台:
[ 0.000000] console [ttyS0] enabled
表示 ttyS0 被作为控制台。
2. 终端(Terminal)
略,前面已经说得很清楚了。
3. 控制台 vs. 一般终端的主要区别
| 特性 | 控制台(Console) | 一般终端(Terminal) |
|---|---|---|
| 作用 | 主要用于 系统启动和核心日志输出,管理低级交互 | 主要用于 用户输入和程序执行 |
| 设备类型 | 物理设备(ttyS0)或虚拟设备(tty1) | 可以是伪终端(pts/0)或虚拟终端(tty2) |
| 内核消息 | 可以接收 printk() 等日志 | 通常不能 直接接收内核日志 |
| 开机交互 | 默认可用,即使系统崩溃也可能仍然可用 | 需要用户登录 才能访问 |
| 是否可修改 | 通过 console= 选项指定 | 可通过 tty 命令动态获取 |
4. 什么时候需要“控制台”而不是一般终端?
在以下情况下,必须使用 控制台 而不是普通终端:
-
系统启动调试
- 例如,嵌入式系统使用
console=ttyS0,115200以便在串口终端上调试 Linux 启动过程。
- 例如,嵌入式系统使用
-
系统崩溃时的日志
- 如果 Linux 内核崩溃,普通终端(
pts/0)可能无法使用,但控制台(如ttyS0)仍可输出panic信息。
- 如果 Linux 内核崩溃,普通终端(
-
无图形界面的系统(如嵌入式系统)
- 在没有 X 窗口的系统上,
tty1(或ttyS0)通常被用作默认控制台。
- 在没有 X 窗口的系统上,
-
U-Boot 的交互
- U-Boot 启动时,会选择
console=xxx指定的设备作为控制台(如ttyS0),用于输入 U-Boot 命令。
- U-Boot 启动时,会选择
5. 如何修改 Linux 的控制台?
如果想修改 Linux 启动日志输出到某个终端,可以:
-
在 U-Boot 设置
bootargs:setenv bootargs console=ttyS1,115200 saveenv这会让 Linux 启动时,把
ttyS1作为控制台。 -
在 Linux 内核命令行修改(如
grub):console=tty1这样 Linux 会把
/dev/tty1(虚拟终端1)作为控制台。
6. 结论
核心区别
| 概念 | 控制台(Console) | 一般终端(Terminal) |
|---|---|---|
| 主要作用 | 处理 系统日志、核心调试、低级交互 | 提供用户交互、运行程序 |
printk() 日志 | 可以接收 | 通常不能 直接接收 |
| 系统启动日志 | 默认显示 | 不会显示 |
| 崩溃时是否可用 | 可能仍然可用 | 可能无法使用 |
| 设备文件 | /dev/ttyS0, /dev/tty1 | /dev/pts/0, /dev/tty2 |
| 指定方法 | console= 内核参数 | tty 命令动态获取 |
如果你需要查看 Linux 内核启动日志,或在 系统崩溃时仍能访问调试信息,应该 使用“控制台”(console=xxx)。
如果你只是想使用一个 shell 来运行命令,那么 普通终端(如 xterm, ssh)就足够了。

976

被折叠的 条评论
为什么被折叠?



