tty子系统(pty)
前言:
之前有一篇博文,不过略显肤浅,不过可以结合着看,今天更深入的介绍一下
https://www.cnblogs.com/aozhejin/p/16065019.html
实验环境:
[root@aozhejin2 /sys/dev]#cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [root@aozhejin2 /sys/dev]#uname -r 3.10.0-1160.62.1.el7.x86_64
[root@aozhejin2 /root]#cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"
CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"
开启三个终端 pts/6 pts/7 pts/8
一、TTY虚拟终端(也称为虚拟控制台,英文pseudo-tty )
首先要说明的是TTY 子系统是驻留在内核的
termios 、line discipline(ldisc)和 tty drivces 组成了TTY 设备(/dev/tty[0-N])
1. 当用户按下 enter 键时,termios控制数据行为比如:
2. line discipline 将字符写入缓冲区
3. ldisc负责发送到 TTY 驱动程序,tty驱动程序负责跟硬件的交互,后者将这些字符传递到 TTY 的前台进程.
如果你采用的是pty驱动(master-slave模型,则pty实际是融入到了tty驱动当中,对外展现就是 /dev/pts/N和/dev/ptmx)
tty驱动有三种
console, serial port, 和 pty(伪终端模型master/slave) , 他们都已经被安装到系统当中,你可以查看 /proc/tty/drivers
TTY 子系统使终端交互变得不灵活。解决方案是将终端仿真移至用户区,在内核中保留 line discipline(ldisc)和 tty 驱动程序。
pty 由两部分组成:
1)pty master (ptmx一个) - 连接到终端仿真器(终端仿真器的包括 xterm 和 gnome-terminal等)
2)pty master (pts多个) - 为进程提供与 pty master的接口
终端成为内核中的一个计算机程序,它将字符直接发送到 TTY 驱动程序,从中读取并打印到屏幕上。
也就是说,内核程序将模拟物理终端设备,因此称为终端模拟器。终端仿真器与过去的物理终端一样笨,它侦听来自键盘的事件并将其发送给驱动程序。不同之处在于没有物理设备或电缆连接到 TTY 驱动程序
不同的用户可以在其中并行登录是过程是(交互式shell):
1)首先,一个名为“agetty”的程序运行。
2)进程“agetty”获得一个 TTY;然后这个过程被另一个叫做“登录”的过程所取代,给用户一个登录提示。
3)登录后,用户将获得代表 TTY ( /dev/tty<tty_number>) 的文件的写入权限。
4)将读取该文件/etc/passwd以决定为该特定用户运行什么 shell。然后 shell 将替换登录过程。
5)shell 会提示用户键入命令。
查一下我们自己的终端仿真器是什么?
[root@aozhejin2 /sys/class/tty/tty1]#echo $TERM xterm
您可以使用以下命令之一显示每个进程的控制终端
[root@aozhejin2 /sys/class/tty/tty1]#tty /dev/pts/6 who来显示所有登录控制终端的用户。 [root@aozhejin2 /sys/class/tty/tty1]#who root pts/6 2023-03-24 22:12 (192.168.1.2) root pts/7 2023-03-24 22:51 (192.168.1.2) ps -eF(使用 GNU ps),ps aux(使用 BSD ps)- 显示所有进程的控制终端(如果有的话);查看“TTY”列。
二、关于agetty
这里有个程序agetty在运行,agetty 是 Linux 版本的 getty 。
agetty负责 TTY 维护(TTY 的父进程),用于新建tty终端,提示登录名并调用 /bin/login 命令。agetty 一般由 init 调用,当然我们可以杀死agetty本身,但这通常只会产生一个相同的新实例,因为内核负责恢复tty终端管理服务。它的配置文件 /etc/login.defs
agetty调用和配置tty子系统的termios
pts6终端上的进程 [root@aozhejin2 /usr/local/src/shell]#ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 43912 4108 ? Ss 2022 152:06 /usr/lib/systemd/systemd --system --deserialize 20 root 870 0.0 0.0 110208 864 tty1 Ss+ 2022 0:00 /sbin/agetty --noclear tty1 linux root 92319 0.0 0.0 118980 5632 pts/6 Ss 22:12 0:00 -bash root 94969 0.0 0.0 157616 6000 ? Ss 22:51 0:00 sshd: root@pts/7
root 94971 0.0 0.0 118980 5648 pts/7 Ss+ 22:51 0:00 -bash root 98385 0.0 0.0 157536 1900 pts/6 R+ 23:43 0:00 ps aux //正在运行或可运行(在运行队列上) root 2153 0.0 0.0 11824 1928 pts/5 Ss+ 2022 0:00 -bash
root 1894 0.0 0.0 91252 2700 ? Ss 2022 0:00 login -- root
root 1144 0.0 0.0 43184 3404 ? Ss 2022 1:20 /sbin/init
....
1. tty可以看到agetty
2. stat 列,s+代表是前台进程组的部分(请参看 man ps 说明)
man ps 中有说明
进程 STATE(STAT) CODES https://man7.org/linux/man-pages/man1/ps.1.html
以下是s、stat和状态输出说明符(标头“stat”或“s”)指定的不同值将显示以描述进程的状态:
D 不间断的睡眠 (一般是指IO,进程正在等待一个不能被打扰的事件) R 正在运行或可运行 (在运行的队列上,这个进程可能会被分配cpu) S 可中断的睡眠 (进程正在等待一个事件) T stopped, either by a job control signal or because it is being traced. W 分页 (不是有效的, 2.6.xx 内核开始) X 死了 (should never be seen) Z 这是一个已经完成的进程,但是,它的退出代码还没有被它的父进程获取。这意味着进程不能“死”,它仍然是“不死的”. For BSD formats and when the stat keyword is used, additional characters may be displayed: < 高优线级 (not nice to other users) N 低优先级 (nice to other users) L 将页面锁定在内存中 (for real-time and custom IO) s 是会话负责人 l 是多线程 (using CLONE_THREAD, like NPTL pthreads do) + 在前台进程组中.
做个小实验:
现在有两个终端(pts)
查看目前登录的终端 [root@aozhejin2 /sys/dev]#who root pts/6 2023-03-24 22:12 (192.168.1.2) root pts/7 2023-03-24 22:51 (192.168.1.2) [root@aozhejin2 /sys/dev]#echo "aozhejin"> /dev/pts/6 xshell进入另一个终端,接收到消息 [root@aozhejin2 /usr/local/src/shell]#aozhejin ^C
注意:
/dev/ptmx (pty master ,内核会用ioctl函数来创建一个slave的pts,属于系统调用)
/dev/pts/[0-N] (pty slave是多个,比如pts1即一个slave)
https://man7.org/linux/man-pages/man7/pty.7.html
解释为
pseudoterminal有时缩写为pty,是一对虚拟的字符设备,是一对提供双向的虚拟字符设备沟通渠道。通道的一端称为master; 另一端称为slave
一个伪tty即pty,一个节点包含着master(ptmx)和N个slave(pts)
我在pts/6终端上:
[root@aozhejin2 /sys/class/tty/tty1]#ps l $$ F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 4 0 92319 92317 20 0 118980 5664 do_wai Ss pts/6 0:00 -bash
“ $$ ”是一个 shell 变量,显示当前 shell 的 PID。
在上面的示例中,UID表示进程所有者的数字“ id ”。PPID是父进程 ID。PRI是进程的优先级。数字越高,进程越低。VSZ是内存中的进程大小 (KiB)。RSS是 RAM 中的进程当前大小 (KiB)。所有进程信息都存储在 /proc 文件系统中。包括“ps”在内的许多程序都使用这里的信息来显示。
WCHAN列表示的是内核函数名称,进程正在休眠
我这里把92319干掉之后,linux直接退出,下面是过程
[root@aozhejin2 /sys/class/tty/tty1]#ps l $$ F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 4 0 92319 92317 20 0 118980 5676 do_wai Ss pts/6 0:00 -bash [root@aozhejin2 /sys/class/tty/tty1]#kill -9 92319 Connection to 10.119.15.11 closed.
我在进入另一个开着的终端pts/7看下
[root@aozhejin2 /sys/devices/virtual/tty/tty1]#ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 4 0 870 1 20 0 110208 864 n_tty_ Ss+ tty1 0:00 /sbin/agetty --noclear tty1 linux 4 0 2153 1894 20 0 11824 1928 n_tty_ Ss+ pts/5 0:00 -bash 4 0 94971 94969 20 0 118980 5664 do_wai Ss pts/7 0:00 -bash 4 0 104003 1 20 0 110208 860 n_tty_ Ss+ tty6 0:00 /sbin/agetty --noclear tty6 linux 0 0 116615 94971 20 0 153328 1524 - R+ pts/7 0:00 ps l
我们使用ps来显示给定 TTY ( -t tty1 ) 周围的层次结构( -H ) 。请注意它是如何从登录 shell 开始的,
但作为子进程过渡到传统的bash shell
2. pts/7处于登录
3. tty6 是我自己干掉之后出现的
查看当前的tty这个并不是很容易
https://www.kernel.org/doc/html/latest/admin-guide/devices.html 里面有一段
4 char TTY devices 0 = /dev/tty0 Current virtual console 1 = /dev/tty1 First virtual console (agetty默认) ... 63 = /dev/tty63 63rd virtual console 64 = /dev/ttyS0 First UART serial port ... 255 = /dev/ttyS191 192nd UART serial port UART serial ports refer to 8250/16450/16550 series devices. Older versions of the Linux kernel used this major number for BSD PTY devices. As of Linux 2.1.115, this is no longer supported. Use major numbers 2 and 3. 4 block Aliases for dynamically allocated major devices to be used when its not possible to create the real device nodes because the root filesystem is mounted read-only. 0 = /dev/root 5 char Alternate TTY devices 0 = /dev/tty Current TTY device 1 = /dev/console System console 2 = /dev/ptmx PTY master multiplex 3 = /dev/ttyprintk User messages via printk TTY device 64 = /dev/cua0 Callout device for ttyS0 ... 255 = /dev/cua191 Callout device for ttyS191 (5,1) is /dev/console starting with Linux 2.1.71. See the section on terminal devices for more information on /dev/console. 6 char Parallel printer devices 0 = /dev/lp0 Parallel printer on parport0 1 = /dev/lp1 Parallel printer on parport1 ... Current Linux kernels no longer have a fixed mapping between parallel ports and I/O addresses. Instead, they are redirected through the parport multiplex layer. 7 char Virtual console capture devices 0 = /dev/vcs Current vc text (glyph) contents 1 = /dev/vcs1 tty1 text (glyph) contents ... 63 = /dev/vcs63 tty63 text (glyph) contents 64 = /dev/vcsu Current vc text (unicode) contents 65 = /dev/vcsu1 tty1 text (unicode) contents ... 127 = /dev/vcsu63 tty63 text (unicode) contents 128 = /dev/vcsa Current vc text/attribute (glyph) contents 129 = /dev/vcsa1 tty1 text/attribute (glyph) contents ... 191 = /dev/vcsa63 tty63 text/attribute (glyph) contents 注意,这些设备允许读写访问
我们看下
[root@ht6.node ~]$ls -l /dev/tty0 /dev/tty1 crw--w---- 1 root tty 4, 0 Mar 25 03:44 /dev/tty0 crw--w---- 1 root tty 4, 1 Apr 21 2022 /dev/tty1
/dev/console和/dev/tty0代表是同样的,代表当前显示,通常/dev/console代表/dev/ttyS0,不去讨论它
你可以通过 https://github.com/torvalds/linux/blob/master/Documentation/admin-guide/serial-console.rst 了解它
tty0代表当前,你可以通过读取 /sys/devices/virtual/tty/tty0/active 得到当前的tty
具体可以参看看 https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-tty
你可以看到系统内的信号的实现
[root@aozhejin2 /usr/local/src/shell]#kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
信号从1开始的
下面agetty是由systemd服务启动,即getty@.service 启动,pts是系统保留的终端
[root@aozhejin2 /usr/local/src/shell]#ps s UID PID PENDING BLOCKED IGNORED CAUGHT STAT TTY TIME COMMAND 0 870 0000000000000000 0000000000000000 0000000000000006 0000000000000000 Ss+ tty1 0:00 /sbin/agetty --noclear tty1 linux 0 2153 0000000000000000 0000000000000000 0000000000384004 000000004b813efb Ss+ pts/5 0:00 -bash 0 92319 0000000000000000 0000000000010000 0000000000384004 000000004b813efb Ss pts/6 0:00 -bash 0 94971 0000000000000000 0000000000000000 0000000000384004 000000004b813efb Ss+ pts/7 0:00 -bash 0 101604 0000000000000000 0000000000000000 0000000000000000 00000001f3d1fef9 R+ pts/6 0:00 ps s
这里的列,最好查看man ps了解其中的含义
[root@aozhejin2 /usr/local/src/shell]#ps l F UID PID PPID PRI NI VSZ RSS WCHAN STAT TTY TIME COMMAND 4 0 870 1 20 0 110208 864 n_tty_ Ss+ tty1 0:00 /sbin/agetty --noclear tty1 linux 4 0 2153 1894 20 0 11824 1928 n_tty_ Ss+ pts/5 0:00 -bash 4 0 92319 92317 20 0 118980 5656 do_wai Ss pts/6 0:00 -bash 4 0 94971 94969 20 0 118980 5648 n_tty_ Ss+ pts/7 0:00 -bash 0 0 102003 92319 20 0 153328 1524 - R+ pts/6 0:00 ps l
tty 驱动程序
共有三种不同类型的 tty 驱动程序:控制台(console)、串行端口(serial port)和 pty(也叫伪终端)。控制台和 pty 驱动程序已经安装到了系统中。
Linux tty 驱动程序核心位于标准字符驱动程序级别之下,并提供一系列功能,这些功能专注于为终端样式的设备提供使用接口。用户层暴漏的接口termio 负责控制和传递到 tty子系统底层的数据流和数据格式的形式。这允许 tty 驱动程序专注于处理进出硬件的数据,而不是担心如何以一致的方式控制与用户空间的交互。为了控制数据流,有许多不同的 line discipline(ldisc) 可以虚拟地“插入”到任何 tty 子系统中。这是由不同的 tty line discipline 驱动程序完成的。
以本机为例:
所以模型就是 用户层-->termio(tty上层暴漏的接口)-->ldisc--->tty(pty)
1、/proc/tty/drivers
tty驱动类型: console, serial port, 和 pty , 他们都已经被安装到系统当中,你可以查看 /proc/tty/drivers
要确定内核中当前加载了哪种 tty 驱动程序以及当前存在哪些 tty 设备,请查看/proc/tty/drivers文件。该文件包含当前存在的不同 tty 驱动程序的列表,显示驱动程序的名称、默认节点名称、驱动程序的主编号、驱动程序使用的次要驱动程序的范围以及 tty 驱动程序的类型。以下是此文件的示例:
[root@aozhejin2 /usr/local/src/shell]#cat /proc/tty/drivers /dev/tty /dev/tty 5 0 system:/dev/tty /dev/console /dev/console 5 1 system:console /dev/ptmx /dev/ptmx 5 2 system /dev/vc/0 /dev/vc/0 4 0 system:vtmaster usbserial /dev/ttyUSB 188 0-511 serial serial /dev/ttyS 4 64-95 serial pty_slave /dev/pts 136 0-1048575 pty:slave pty_master /dev/ptm 128 0-1048575 pty:master unknown /dev/tty 4 1-63 console
2、/sys/class/tty
[root@aozhejin2 /sys/class/tty/tty1]#tree . ├── dev ├── power │ ├── async │ ├── autosuspend_delay_ms │ ├── control │ ├── runtime_active_kids │ ├── runtime_active_time │ ├── runtime_enabled │ ├── runtime_status │ ├── runtime_suspended_time │ └── runtime_usage ├── subsystem -> ../../../../class/tty └── uevent 2 directories, 11 files ################################################################### [root@aozhejin2 /sys/class/tty]#tree . ├── console -> ../../devices/virtual/tty/console ├── ptmx -> ../../devices/virtual/tty/ptmx ├── tty -> ../../devices/virtual/tty/tty ├── tty0 -> ../../devices/virtual/tty/tty0 ├── tty1 -> ../../devices/virtual/tty/tty1
字符设备号块设备
[root@aozhejin2 /sys/dev]#ls block char
查看tty主设备号相关情况
我们看下主设备号是多少? 这里是4,既然是主设备,那么它就有对应的tty driver [root@aozhejin2 /sys/dev]#cat /proc/devices Character devices:4 /dev/vc/0 4 tty 4 ttyS #串行端口设备 5 /dev/tty 5 /dev/console 5 /dev/ptmx99 ppdev 128 ptm 136 pts .. 其它部分都剔除掉了
我们看下/dev 次设备号情况
[root@aozhejin2 /usr/local/src/shell]#ll /dev
total 0
crw-rw-rw- 1 root tty 5, 2 Mar 25 00:06 ptmx drwxr-xr-x 2 root root 0 Apr 21 2022 pts lrwxrwxrwx 1 root root 15 Apr 21 2022 stderr -> /proc/self/fd/2 lrwxrwxrwx 1 root root 15 Apr 21 2022 stdin -> /proc/self/fd/0 lrwxrwxrwx 1 root root 15 Apr 21 2022 stdout -> /proc/self/fd/1 crw-rw-rw- 1 root tty 5, 0 Apr 27 2022 tty crw--w---- 1 root tty 4, 0 Apr 21 2022 tty0 crw--w---- 1 root tty 4, 1 Apr 21 2022 tty1 crw--w---- 1 root tty 4, 10 Apr 21 2022 tty10
....这里忽略很多.... #串行端口终端 crw-rw---- 1 root dialout 4, 64 Apr 21 2022 ttyS0 crw-rw---- 1 root dialout 4, 65 Apr 21 2022 ttyS1 crw-rw---- 1 root dialout 4, 66 Apr 21 2022 ttyS2 crw-rw---- 1 root dialout 4, 67 Apr 21 2022 ttyS3
顺便提一嘴,注意字符设备/dev/mem 这种设备你不能用cat打开,它存放物理内存地址,是通过内核系统调用方式来可以读写的。所以要
了解设备的性质.
pts主设备号是136,次设备号为[1-8]
[root@aozhejin2 /dev/pts]#ll total 0 crw--w---- 1 root tty 136, 1 Apr 27 2022 1 crw--w---- 1 root tty 136, 2 Apr 27 2022 2 crw--w---- 1 root tty 136, 3 Apr 27 2022 3 crw--w---- 1 root tty 136, 4 Apr 27 2022 4 crw--w---- 1 root tty 136, 5 Apr 27 2022 5 crw--w---- 1 root tty 136, 6 Mar 26 17:48 6 crw--w---- 1 root tty 136, 7 Mar 26 19:53 7 crw--w---- 1 root tty 136, 8 Mar 26 11:55 8 c--------- 1 root root 5, 2 Apr 21 2022 ptmx
我们看下ptmx的主设备号和次设备号
[root@aozhejin2 /dev/pts]# ls -l /dev/ptmx /dev/pts/ptmx crw-rw-rw- 1 root tty 5, 2 Mar 26 20:04 /dev/ptmx c--------- 1 root root 5, 2 Apr 21 2022 /dev/pts/ptmx
//这里/dev/ptmx是标准的
// /dev/pts/ptmx是它的实例,也是同样指向的同样的内核驱动程序
[root@aozhejin2 /dev/pts]#stat -L /dev/fd/3 3<> /dev/ptmx File: ‘/dev/fd/3’ Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 5h/5d Inode: 1134 Links: 1 Device type: 5,2 Access: (0666/crw-rw-rw-) Uid: ( 0/ root) Gid: ( 5/ tty) Access: 2023-03-26 20:19:44.088999692 +0800 Modify: 2023-03-26 20:19:44.088999692 +0800 Change: 2022-04-21 08:57:42.088999692 +0800 Birth: - [root@aozhejin2 /dev/pts]#stat -L /dev/fd/3 3<> /dev/pts/ptmx File: ‘/dev/fd/3’ Size: 0 Blocks: 0 IO Block: 1024 character special file Device: ch/12d Inode: 2 Links: 1 Device type: 5,2 Access: (0000/c---------) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2022-04-21 08:57:34.724018647 +0800 Modify: 2022-04-21 08:57:34.724018647 +0800 Change: 2022-04-21 08:57:34.724018647 +0800 Birth: -
/dev/fd 是一个symlnk ,指向 /proc/self/fd
文件
[root@aozhejin2 /dev/fd]#ll
total 0
lrwx------ 1 root root 64 Mar 24 22:51 0 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 24 22:51 1 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 24 22:51 2 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 26 20:30 255 -> /dev/pts/7 //内部连接到tty的
lrwx------ 1 root root 64 Mar 24 22:51 6 -> /dev/pts/7
我们上两个终端分别看下self标识了当前进程bash
[root@aozhejin2 /dev/fd]#ls -l /proc/self/fd
total 0
lrwx------ 1 root root 64 Mar 26 20:32 0 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 26 20:32 1 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 26 20:32 2 -> /dev/pts/7
lr-x------ 1 root root 64 Mar 26 20:32 3 -> /proc/7193/fd
lrwx------ 1 root root 64 Mar 26 20:32 6 -> /dev/pts/7
[root@aozhejin2 /proc/self]#ls -l /proc/self/fd
total 0
lrwx------ 1 root root 64 Mar 26 21:53 0 -> /dev/pts/6
lrwx------ 1 root root 64 Mar 26 21:53 1 -> /dev/pts/6
lrwx------ 1 root root 64 Mar 26 21:53 2 -> /dev/pts/6
lr-x------ 1 root root 64 Mar 26 21:53 3 -> /proc/12309/fd
lrwx------ 1 root root 64 Mar 26 21:53 6 -> /dev/pts/6
[root@aozhejin2 /proc/self]#ps a
PID TTY STAT TIME COMMAND
870 tty1 Ss+ 0:00 /sbin/agetty --noclear tty1 linux
2153 pts/5 Ss+ 0:00 -bash
12417 pts/6 R+ 0:00 ps a
22191 pts/6 Ss 0:00 -bash
94971 pts/7 Ss+ 0:00 -bash
97785 pts/8 Ss 0:00 -bash
103366 pts/8 S+ 0:00 bash -v
104003 tty6 Ss+ 0:00 /sbin/agetty --noclear tty6 linux
[root@aozhejin2 /dev]#ll stdin stdout stderr (每个进程都会包括标准输入、标准输出、标准错误)
lrwxrwxrwx 1 root root 15 Apr 21 2022 stderr -> /proc/self/fd/2 2代表标准错误
lrwxrwxrwx 1 root root 15 Apr 21 2022 stdin -> /proc/self/fd/0 0代表标准输入
lrwxrwxrwx 1 root root 15 Apr 21 2022 stdout -> /proc/self/fd/1 1代表标准输出
self代表当前进程,fd即文件描述符。
文件描述符(fd)是文件或其他输入/输出资源(例如管道或网络套接字)的进程唯一标识符(句柄)
找个进程看下
[root@aozhejin2 /dev/fd]#ll /proc/1188/fd/
total 0
lr-x------ 1 root root 64 Apr 28 2022 0 -> /dev/null
lrwx------ 1 root root 64 Apr 28 2022 1 -> socket:[11028009]
lrwx------ 1 root root 64 Apr 28 2022 2 -> socket:[11028009]
lrwx------ 1 root root 64 Apr 28 2022 3 -> /run/crond.pid
lrwx------ 1 root root 64 Apr 28 2022 4 -> socket:[11030106]
lr-x------ 1 root root 64 Apr 28 2022 5 -> anon_inode:inotify
再可以比较下pts/6和pts/7,注意登录后的self指的是bash这个执行环境即一个进程
[root@aozhejin2 /proc/self]#lsof -p $$ COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 94971 root cwd DIR 0,3 0 630855481 /proc/94971 bash 94971 root rtd DIR 253,0 4096 128 / bash 94971 root txt REG 253,0 964536 134217876 /usr/bin/bash bash 94971 root mem REG 253,0 61560 269462479 /usr/lib64/libnss_files-2.17.so bash 94971 root mem REG 253,0 106176928 268633366 /usr/lib/locale/locale-archive bash 94971 root mem REG 253,0 2156592 268633378 /usr/lib64/libc-2.17.so bash 94971 root mem REG 253,0 19248 268820392 /usr/lib64/libdl-2.17.so bash 94971 root mem REG 253,0 174576 270478653 /usr/lib64/libtinfo.so.5.9 bash 94971 root mem REG 253,0 163312 281915295 /usr/lib64/ld-2.17.so bash 94971 root mem REG 253,0 26970 402655573 /usr/lib64/gconv/gconv-modules.cache bash 94971 root 0u CHR 136,7 0t0 10 /dev/pts/7 bash 94971 root 1u CHR 136,7 0t0 10 /dev/pts/7 bash 94971 root 2u CHR 136,7 0t0 10 /dev/pts/7 bash 94971 root 6u CHR 136,7 0t0 10 /dev/pts/7 bash 94971 root 255u CHR 136,7 0t0 10 /dev/pts/7 在另一个终端pts/7 [root@aozhejin2 /proc/self]#lsof -p $$ COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME bash 22191 root cwd DIR 0,3 0 632063806 /proc/22191 #指向/proc/self bash 22191 root rtd DIR 253,0 4096 128 / bash 22191 root txt REG 253,0 964536 134217876 /usr/bin/bash bash 22191 root mem REG 253,0 61560 269462479 /usr/lib64/libnss_files-2.17.so bash 22191 root mem REG 253,0 106176928 268633366 /usr/lib/locale/locale-archive bash 22191 root mem REG 253,0 2156592 268633378 /usr/lib64/libc-2.17.so bash 22191 root mem REG 253,0 19248 268820392 /usr/lib64/libdl-2.17.so bash 22191 root mem REG 253,0 174576 270478653 /usr/lib64/libtinfo.so.5.9 bash 22191 root mem REG 253,0 163312 281915295 /usr/lib64/ld-2.17.so bash 22191 root mem REG 253,0 26970 402655573 /usr/lib64/gconv/gconv-modules.cache bash 22191 root 0u CHR 136,6 0t0 9 /dev/pts/6 bash 22191 root 1u CHR 136,6 0t0 9 /dev/pts/6 bash 22191 root 2u CHR 136,6 0t0 9 /dev/pts/6 bash 22191 root 6u CHR 136,6 0t0 9 /dev/pts/6 bash 22191 root 255u CHR 136,6 0t0 9 /dev/pts/6
字符设备各自有各自的标准输入与标准输出以及标准错误....
[root@aozhejin2 /proc/self]#ll /dev/fd/
total 0
lrwx------ 1 root root 64 Mar 26 22:15 0 -> /dev/pts/6
lrwx------ 1 root root 64 Mar 26 22:15 1 -> /dev/pts/6
lrwx------ 1 root root 64 Mar 26 22:15 2 -> /dev/pts/6
lr-x------ 1 root root 64 Mar 26 22:15 3 -> /proc/13749/fd
lrwx------ 1 root root 64 Mar 26 22:15 6 -> /dev/pts/6
[root@aozhejin2 /proc/self]#ll /dev/fd/
total 0
lrwx------ 1 root root 64 Mar 26 22:16 0 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 26 22:16 1 -> /dev/pts/7
lrwx------ 1 root root 64 Mar 26 22:16 2 -> /dev/pts/7
lr-x------ 1 root root 64 Mar 26 22:16 3 -> /proc/13802/fd
lrwx------ 1 root root 64 Mar 26 22:16 6 -> /dev/pts/7
字符设备的主设备号和次设备号
[root@aozhejin2 /sys/dev]#ls /sys/dev/char/ 10:144 10:231 10:60 1:12 13:64 1:5 202:3 203:3 21:1 248:0 29:0 4:13 4:19 4:24 4:3 4:35 4:40 4:46 4:51 4:57 4:62 4:7 7:0 7:132 7:5 10:175 10:235 10:61 1:3 13:65 162:0 202:4 203:4 21:2 248:1 4:0 4:14 4:2 4:25 4:30 4:36 4:41 4:47 4:52 4:58 4:63 4:8 7:1 7:133 7:6 10:183 10:236 10:62 13:0 13:66 1:7 202:5 203:5 226:0 248:2 4:1 4:15 4:20 4:26 4:31 4:37 4:42 4:48 4:53 4:59 4:64 4:9 7:128 7:134 10:200 10:57 10:63 13:32 13:67 1:8 202:6 203:6 226:128 248:3 4:10 4:16 4:21 4:27 4:32 4:38 4:43 4:49 4:54 4:6 4:65 5:0 7:129 7:2 10:227 10:58 1:1 13:33 13:68 1:9 202:7 203:7 244:0 248:4 4:11 4:17 4:22 4:28 4:33 4:39 4:44 4:5 4:55 4:60 4:66 5:1 7:130 7:3 10:228 10:59 1:11 13:63 1:4 202:2 203:2 21:0 247:0 252:0 4:12 4:18 4:23 4:29 4:34 4:4 4:45 4:50 4:56 4:61 4:67 5:2 7:131 7:4
三、伪终端 (PTY)
什么是伪终端 (PTY)?
实际也叫伪tty终端设备
它是由在用户领域运行的计算机程序模拟的电传打字机。
将其与 TTY 进行比较:区别在于程序运行的位置;它不是内核程序,而是在用户空间运行的程序。
用户空间中的程序只与内核交互,而不是直接与硬件交互。
PTY 存在的主要原因是为了方便将终端仿真移动到用户区,同时仍然保持 TTY 子系统(会话管理和线路规程)不变
它是如何工作的呢?
终端仿真器(或任何其他程序)可以向内核请求一对字符文件(称为 PTY master 和 PTY slave)。
在master端你有终端仿真器,而在slave端你有一个 Shell
pty本质是完成read和write的操作在成对的设备文件上(pty master和 pty slave)
注意: pty master即ptmx,pty slave即pts
用户区的终端模拟器中输入一些东西时候,比如 XTerm 或你用来获取终端的任何其他应用程序。
启动 bash 作为子进程
bash进程的标准输入、标准输出和标准错误(进程的三要素)将被设置为 pty slave。
XTerm 监听键盘事件并将字符发送给 pty master
ldisc(tty的一部分)获取字符并缓冲它们。只有当您按下回车键时,它才会将它们复制到slave。它还将其输入写回到主机(回显)。
记住终端是哑的,它只会在屏幕上显示来自 pty master 的东西。因此,ldisc回显字符,以便终端绘制它,让您看到您刚刚输入的内容。
这个动作是非常快的,你可能感觉不到,当系统卡的时候可能你体会比较明显。
当您按下回车键时,TTY 驱动程序(内核模块)负责将ldisc写入缓冲区的数据复制到 pty slave(pts)
bash(正在等待标准输入的输入)最终读取字符(例如“ls”)。同样,请记住 bash 标准输入设置为 pty slave(pts)。
此时 bash 解释运行“ls”所需的字符和数字
它fork进程并在其中运行“ls”。fork的进程将具有 bash 使用的相同标准输入、标准输出和标准错误,它是 pty slave。
ls 运行并打印到标准输出(再一次,这是 pty slave)
tty 驱动程序将字符复制到主机
XTerm 从 pty master 循环读取字节并绘制
从代码层面看pty:
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\drivers\tty\pty.c
要使用伪tty,即pty(tty子系统) ,必须安装master驱动程序/dev/ptmx 的节点和N 个slave pts驱动程序( N个slave在安装时已确定,即/dev/pts下)。slave设备的名称是/dev/pts/M,其中M的值是 0 到 N-1。用户通过master(称为ptm)访问pty设备(伪tty),而master设备又通过克隆驱动程序访问。master设备设置为克隆设备,其中master设备号是克隆设备的master设备号(实例)
pts
https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c 908行: pts_driver->driver_name = "pty_slave"; //pty slave即指向的pts驱动
ptmx
首先确定
https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c#L908
783行:
Allocate a unix98 pty master device from the ptmx driver
即pty master驱动来自 ptmx驱动
多路复用器由内核通过位于 /dev/ptmx 的设备文件寻址
ptmx, pts - 伪终端master和slave
/dev/ptmx是一个字符设备文件,主设备号为5,次设备号为2,通常模式为0666,owner.group 为root.root。它用于创建伪终端maste/slave对。
当一个进程打开/dev/ptmx时,它会获得一个伪终端master (ptm) 的文件描述符,并在/dev/pts目录中创建一个伪终端slave (pts) 设备。
通过打开/dev/ptmx获得的每个文件描述符都是一个独立的 PTM,具有自己关联的多个pts,
其路径可以通过将描述符传递给ptsname (3)来找到。
注意由于他们都是设备文件,所以ptmx驱动和pts驱动,可以参考:
所有实例中 pty 对的总数受 sysctls 限制:
kernel.pty.max = 4096 - global limit
kernel.pty.reserve = 1024 - reserved for filesystems mounted from the initial mount namespace
kernel.pty.nr - current count of ptys
我们查看一下ptmx(pty master)-->pts(pty slave)设备在系统里面的呈现
[root@aozhejin2 /sys/dev]#ls -l /dev/ptmx crw-rw-rw- 1 root tty 5, 2 Mar 25 02:17 /dev/ptmx
[root@aozhejin2 /sys/dev]# ls -l /dev/pts/ total 0 crw--w---- 1 root tty 136, 1 Apr 27 2022 1 crw--w---- 1 root tty 136, 2 Apr 27 2022 2 crw--w---- 1 root tty 136, 3 Apr 27 2022 3 crw--w---- 1 root tty 136, 4 Apr 27 2022 4 crw--w---- 1 root tty 136, 5 Apr 27 2022 5 crw--w---- 1 root tty 136, 6 Mar 25 01:57 6 crw--w---- 1 root tty 136, 7 Mar 25 02:16 7 c--------- 1 root root 5, 2 Apr 21 2022 ptmx (master)
//这个master克隆自/dev/ptmx,从设备pts多个 ,ptmx和多个pts组成pty
四、stty
stty命令用于检查和修改当前注册的tty的通信参数。 stty是用户程序来修改数据与pty(pty是tty的一种驱动形式)交互的行为, stty给了我们直接操作 tty 的接口,termios是tty在内核编程层面,对外暴漏的接口。本机采用的是pty驱动模式。
UNIX系统为键盘的输入和终端的输出提供了重要的控制手段,可以通过stty命令对特定终端或通信线路设置选项。 stty以信号的方式工作
可以在stty命令中使用-a查看当前注册终端的pty设置情况。
[root@aozhejin2 /usr/local/src/shell]#stty -a speed 38400 baud; rows 34; columns 166; line = 0; intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0; -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8 opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
解释一下第一行:
speed | UART | The baud rate. Ignored for pseudo terminals. |
rows, columns | TTY driver | Somebody's idea of the size, in characters, of the terminal attached to this TTY device. Basically, it's just a pair of variables within kernel space, that you may freely set and get. Setting them will cause the TTY driver to dispatch a SIGWINCH to the foreground job. |
line | Line discipline | The line discipline attached to the TTY device. 0 is N_TTY. All valid numbers are listed in /proc/tty/ldiscs. Unlisted numbers appear to be aliases for N_TTY, but don't rely on it. |
stty
获取或设置终端设备上的选项。默认情况下,它在连接到其标准输出(stdout)的设备上运行,但您可以使用 -F 选项将其传递给任意设备 ,
stty
的-F
选项对于查看正在执行的终端操作非常有用。如果 tty
在 shell 中运行,它将打印到该 shell 的终端设备的路径(通常是 /dev/pts/N 的形式,至少在我的Linux 下是这样)
现在我们设置不显示输入,意思是你只能盲打键盘,否则你看不到输入的命令
[root@aozhejin2 /]#stty -echo
注意echo前面有一个 -符号 设置之后,机器像死了一样,一会你就可以输入了,但是命令你看不到了,即关闭终端回显 然后你凭着自己的习惯,再输入下面的命令即可 stty echo 打开回显 [root@aozhejin2 /]#stty echo //操作这个之前最好打开两个终端,这样防止错误。
//这个操作只会影响当前的pts
设置之前
[root@aozhejin2 /usr/local/src/teststty]#stty -a
speed 38400 baud; rows 34; columns 166; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
....
设置之后
[root@aozhejin2 /usr/local/src/teststty]#stty erase ^?
[root@aozhejin2 /usr/local/src/teststty]#sdfj^H^H^H^H^H^H^H^H^H^H
查看设置
[root@aozhejin2 /usr/local/src/teststty]#stty -a
speed 38400 baud; rows 34; columns 166; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
.....
恢复
[root@aozhejin2 /usr/local/src/teststty]#stty erase ^H
关闭大小写,输入不了大写字母,并且上下键也不会显示历史命令
[root@aozhejin2 /usr/local/src/teststty]#stty iuclc #关闭 [root@aozhejin2 /usr/local/src/teststty]#ls test [root@aozhejin2 /usr/local/src/teststty]#stty -iuclc #打开
其它测试如:
#在命令行下禁止输出小写: stty olcuc #开启 stty -olcuc #恢复 #改变Ctrl+D的方法: stty eof "string" #系统默认是Ctrl+D来表示文件的结束,而通过这种方法,可以改变! #屏蔽显示: stty -echo #禁止回显 stty echo #打开回显 #测试方法: stty -echo;read;stty echo;read #忽略回车符: stty igncr #开启 stty -igncr #恢复
#打印出终端的行数和列数:
stty size
下面是一段可以改变终端数据行为的c代码,c代码对终端支持是比较丰富的.那么这里的termios就是tty的接口层
#include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <termios.h> /* Use this variable to remember original terminal attributes. */ struct termios saved_attributes; void reset_input_mode (void) {
//tcsetattr函数根据以下请求的TCSANOW操作设置与 termios结构中指定文件描述符引用的终端关联的参数.
tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes); } void set_input_mode (void) { struct termios tattr; //声明一个termios结构体 char *name; /* 确认stdin一个终端. */ if (!isatty (STDIN_FILENO)) { fprintf (stderr, "Not a terminal.\n"); exit (EXIT_FAILURE); }else{
printf("this is terminal");
} /* Save the terminal attributes so we can restore them later. */ tcgetattr (STDIN_FILENO, &saved_attributes); atexit (reset_input_mode); //程序退出时调用 reset_input_mode /* Set the funny terminal modes. */ tcgetattr (STDIN_FILENO, &tattr);
//c_lflag本地模式标识 tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0;
term.c_cc[VEOF]=(cc_t)0x07;
tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
} int main (void) { char c; set_input_mode (); while (1) { read (STDIN_FILENO, &c, 1); if (c == '\004') /* C-d */ break; else putchar (c); } return EXIT_SUCCESS;
}
参看:
[root@aozhejin2 /usr/local/src/teststty]#cat /usr/include/bits/termios.h | grep ECHO
获得屏幕大小的
#!/bin/bash stty_size=$(stty size) nofCols=${stty_size##* } nofRows=${stty_size%% *} echo "Number of columns: $nofCols" echo "Number of rows: $nofRows" 保存为 testreturnsize [root@aozhejin2 /usr/local/src/teststty]#sh returnsize Number of columns: 166 Number of rows: 34 [root@aozhejin2 /usr/local/src/teststty]#stty size 34 166
五、/dev/console
/dev/console is the system console, it points to /dev/tty0 as a default It can be ttyn, ttySn, ttyUSBn, lpn, and so on
这个设备在linux内核启动的时候初始化。 我们可以在启动的时候在 grub.conf中设置别的控制台
/dev/console -> /dev/{tty?,ttyS?}
深入具体分析
linux 启动时候调用
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\init\main.c asmlinkage void __init start_kernel(void){ .... console_init(); //会调用这个函数 ... }
我们对比一下
以前的: console_init 定义在 ./drivers/char/tty_io.c void __init console_init(void) { ..... /* 根据控制设备不同,来启用不同的控制台 */ #ifdef CONFIG_VT con_init(); #endif #ifdef CONFIG_SERIAL_CONSOLE #if (defined(CONFIG_8xx) || defined(CONFIG_8260)) console_8xx_init(); #elif defined(CONFIG_SERIAL) serial_console_init(); //串口控制台 #endif /* CONFIG_8xx */ #ifdef CONFIG_SGI_SERIAL sgi_serial_console_init(); //Linux Terminal I/O via SPI Device #endif #if defined(CONFIG_MVME162_SCC) || defined(CONFIG_BVME6000_SCC) || defined(CONFIG_MVME147_SCC) vme_scc_console_init(); //MVME147, MVME162, BVME6000 SCC serial ports 等外部输出设备 #endif #if defined(CONFIG_SERIAL167) serial167_console_init(); //uclinux内核的console #endif #if defined(CONFIG_SH_SCI) sci_console_init(); //Serial communication interface (SCI) #endif #endif #ifdef CONFIG_3215 con3215_init(); // S390平台终端 #endif #ifdef CONFIG_HWC hwc_console_init(); //HWC line mode terminal #endif #ifdef CONFIG_SERIAL_21285_CONSOLE rs285_console_init(); //rs285协议标准的终端 #endif #ifdef CONFIG_SERIAL_SA1100_CONSOLE sa1100_rs_console_init(); #endif #ifdef CONFIG_SERIAL_AMBA_CONSOLE ambauart_console_init(); #endif } 现在的定义在: E:\linux内核\linux-2.6.38.5\drivers\tty\tty_io.c /* * Initialize the console device. This is called *early*, so * we can't necessarily depend on lots of kernel help here. * Just do some early initializations, and do the complex setup * later. */ void __init console_init(void) { initcall_t *call; /* Setup the default TTY line discipline. 安装 tty ldisc n_tty_init 主要调用 tty_register_ldisc(N_TTY, &n_tty_ops) 注册 tty ldisc */ tty_ldisc_begin(); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ call = __con_initcall_start; while (call < __con_initcall_end) { (*call)(); call++; } } 在 console_init 函数中,它做的两件事 1.注册 tty ldisc,注册 tty 驱动,tty 核心是包含在内核当中的。 tty 线路规程和 tty 驱动可以有很多个。 2.对于 tty 来说,输入设备和输出设备不是同一个设备,输入设备是键盘,输出设备是屏幕 现在的定义在: E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\drivers\tty\tty_io.c /* * Initialize the console device. This is called *early*, so * we can't necessarily depend on lots of kernel help here. * Just do some early initializations, and do the complex setup * later. */ void __init console_init(void) { initcall_t *call; /* Setup the default TTY line discipline. 安装 tty ldisc n_tty_init 主要调用 tty_register_ldisc(N_TTY, &n_tty_ops) 注册 tty ldisc https://cloud.tencent.com/developer/article/1887793 */ tty_ldisc_begin(); /* * set up the console device so that later boot sequences can * inform about problems etc.. */ call = __con_initcall_start; while (call < __con_initcall_end) { (*call)(); call++; } } 在 console_init 函数中,它做的两件事 1.注册 tty ldisc,注册 tty 驱动,tty 核心是包含在内核当中的。 tty 线路规程和 tty 驱动可以有很多个。 2.对于 tty 来说,输入设备和输出设备不是同一个设备,输入设备是键盘,输出设备是屏幕
三层模型
tty子系统 它包括四个重要部分: tty 用户层
termios对外接口 termios调用接口和line discipline(ldisc)和tty驱动程序。 tty 驱动程序负责处理后端硬件;ldisc就像输入/输出上的过滤器。 注意:这只是模型,但是他们内部交叉进行。 模型 termio-->ldisc--->tty(设备文件) pty(master和slave)插入(注册到)tty底层驱动 pty master(ptmx) pty slave(pts) 三层模型: tty termio 负责对用户层提供接口,处理数据格式和数据流程等 可参考https://man7.org/linux/man-pages/man3/termios.3.html 有的人说是 用户层是tty core, 实际说的就是tty termio tty driver 聚焦处理数据在硬件之间 tty ldisc,它可以有多个不同的ldisc, 插入到tty driver 中 数据流转为: core--->ldisc--->driver tty驱动有三种 console, serial port, 和 pty , 他们都已经被安装到系统当中,你可以查看 /proc/tty/drivers
tty子系统
它包括四个重要部分:
tty 用户层
termios接口
line discipline(ldisc)。ldisc(默认是N_TTY,它尝试按照 POSIX 处理字符)。
tty 驱动程序(pty)
本机采用的pty类型的tty驱动程序模型
termio-->ldisc--->tty(设备文件即/dev/tty[0-N])
tty-->对应--->(pty驱动)
master(ptmx)
slave(pts)
三层模型职责:
tty termio 负责对用户层提供接口,处理数据格式和数据流程等
可参考https://man7.org/linux/man-pages/man3/termios.3.html
有的人说是 用户层是tty core, 实际说的就是tty termio
tty driver 聚焦处理数据在硬件之间
tty ldisc,它可以有多个不同的ldisc, 插入到tty driver 中
数据流转为: termio--->ldisc--->driver
一、用户层接口层termio
termios 描述了一个通用的终端接口,它用于控制异步通信端口,tty 层是用户视角是终端 I/O。这是一个复杂的主题,因为它用于控制许多不同的设备(终端、调制解调器、串行线路……)。在由设备驱动程序调解的用户输入/输出和读/写进程之间,有 tty 层,它在两个队列中缓冲数据。两个队列都有最大大小。
当输出队列已满时,进程将等待。当输入队列已满时,字符将丢失。如果启用回显,输入字符将在输出队列中回显。
如果你看了 agetty源码,就知道它调用的是termio,有一个初始化函数 termio_init ,头文件引入的是用户区的 termio.h(/usr/include/bits/termios.h)
termios是在Posix规范中定义的标准接口,表示终端设备(包括虚拟终端、串口等)。
因为串口是一种终端设备,所以通过终端编程接口对其进行配置和控制。
termios 结构是在POSIX规范中定义的标准接口,它类似于system V中的termio接口,
通过设置termios类型的数据结构中的值和使用一小组函数调用,你就可以对终端接口进行控制。
https://man7.org/linux/man-pages/man3/termios.3.html
tty 的内核特性包含在 struct termio 中(在 include/asm/termios.h 中定义)。
定义了 termio 结构体
struct termio { unsigned short c_iflag; /* input mode flags */ unsigned short c_oflag; /* output mode flags */ unsigned short c_cflag; /* control mode flags */ unsigned short c_lflag; /* local mode flags */ unsigned char c_line; /* line discipline */ unsigned char c_cc[NCC]; /* control characters */ };
本文件包括
1.从用户层转换到内核层 /* * Translate a "termio" structure into a "termios". Ugh. */ static inline int user_termio_to_kernel_termios(struct ktermios *termios, const struct termio __user *termio) { ... } 2.从内核层转换到用户层 /* * Translate a "termios" structure into a "termio". Ugh. */ static inline int kernel_termios_to_user_termio(struct termio __user *termio, struct ktermios *termios) { ... }
内核源码
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\include\linux\termios.h
/******************************/ #ifndef _LINUX_TERMIOS_H #define _LINUX_TERMIOS_H #include <linux/types.h> #include <asm/termios.h> #define NFF 5 struct termiox { __u16 x_hflag; __u16 x_cflag; __u16 x_rflag[NFF]; __u16 x_sflag; }; #define RTSXOFF 0x0001 /* RTS flow control on input */ #define CTSXON 0x0002 /* CTS flow control on output */ #define DTRXOFF 0x0004 /* DTR flow control on input */ #define DSRXON 0x0008 /* DCD flow control on output */ #endif /******************************/
类似的,termios被定义在用户区 /usr/include/bits/termios.h
[root@aozhejin2 /root]#cat /usr/include/bits/termios.h /* termios type and macro definitions. Linux version.*/ #ifndef _TERMIOS_H # error "Never include <bits/termios.h> directly; use <termios.h> instead." #endif typedef unsigned char cc_t; typedef unsigned int speed_t; typedef unsigned int tcflag_t; #define NCCS 32 struct termios { tcflag_t c_iflag; /* input mode flags */ tcflag_t c_oflag; /* output mode flags */ tcflag_t c_cflag; /* control mode flags */ tcflag_t c_lflag; /* local mode flags */ cc_t c_line; /* line discipline */ cc_t c_cc[NCCS]; /* control characters */ speed_t c_ispeed; /* input speed */ speed_t c_ospeed; /* output speed */ #define _HAVE_STRUCT_TERMIOS_C_ISPEED 1 #define _HAVE_STRUCT_TERMIOS_C_OSPEED 1 }; /* c_cc characters */ ... /* c_iflag bits */ ... /* c_oflag bits */ ... /* c_cflag bit meaning */ ... /* c_lflag bits */ ... /* tcflow() and TCXONC use these */ ... /* tcflush() and TCFLSH use these */ ... /* tcsetattr uses these */ #define _IOT_termios /* Hurd ioctl type field. */ \ _IOT (_IOTS (cflag_t), 4, _IOTS (cc_t), NCCS, _IOTS (speed_t), 2)
我们之前说的stty程序就是和termios一样的,termios是编程用户接口,stty是一个模拟termios的程序,来为我们提供数据的格式比如:utf-8或其他类型格式,以及控制输入的很多行为。
二、中间层 line discipline
line discipline(ldisc) 层承担了数据处理和控台职责,ldisc把 tty termios 和tty driver粘合在一起
用户层是看不到的.
E:\linux内核\linux-2.6.38.5\linux-2.6.38.5\drivers\tty\tty_ldisc.c
/** * tty_ldisc_assign - 设置 ldisc 在 tty 上 (ldisc会有多个实例注册到tty上) * @tty: tty to assign * @ld: line discipline * * 安装 line discipline(ldisc) 到 tty structure 上. The ldisc must have a reference count above zero to ensure it remains. * The tty instance refcount starts at zero. * * Locking: * Caller must hold references */ static void tty_ldisc_assign(struct tty_struct *tty, struct tty_ldisc *ld) { tty->ldisc = ld; } 本文件有很多操作例如: ldisc-->注册到tty ldisc有缓冲区 tty--->关闭ldisc tty--->打开ldisc /** * tty_set_ldisc - set ldisc(line discipline) * @tty: the terminal to set * @ldisc: the line discipline * * Set the discipline of a tty line. Must be called from a process * context. The ldisc change logic has to protect itself against any * overlapping ldisc change (including on the other end of pty pairs), * the close of one side of a tty/pty 对, and eventually hangup. * * Locking: takes tty_ldisc_lock, termios_mutex */ int tty_set_ldisc(struct tty_struct *tty, int ldisc) { ... }
三、底层 tty
1.重要结构体tty_struct
TTY的 tty_struct 它包含一个 tty_driver 和 ldisc。 指向 termios 和 termios_locked。 几个标志和计数器。 alink到另一个 tty_struct 的(对于 ptty)。 它用在tty_driver 的stop/方法中:如果设置了start它的标志,则链接的 read_wait 队列被唤醒 ;packet 一个 tty_flip_buffer flip; 两个 wait_queue_head_t,write_wait和read_wait; 一个 tq_struct tq_hangup; tty_files的列表; N_TTY ldisc 的其他字段。
2.重要结构体tty_driver
注意: tty_driver结构体定义在E:\linux内核\linux-2.6.38.5\include\linux\tty_driver.h
这个文件定义了底层 tty driver 和 tty routines的接口. 里面定义了 tty_driver 结构体
struct tty_driver { int magic; /* magic number for this structure */ struct kref kref; /* Reference management */ struct cdev cdev; struct module *owner; const char *driver_name; const char *name; int name_base; /* offset of printed name */ int major; /* major device number */ int minor_start; /* start of minor device number */ int minor_num; /* number of *possible* devices */ int num; /* number of devices allocated */ short type; /* type of tty driver */ short subtype; /* subtype of tty driver */ struct ktermios init_termios; /* Initial termios */ int flags; /* tty driver flags */ struct proc_dir_entry *proc_entry; /* /proc fs entry */ struct tty_driver *other; /* only used for the PTY driver */ /* * Pointer to the tty data structures */ struct tty_struct **ttys; struct ktermios **termios; struct ktermios **termios_locked; void *driver_state; /* * Driver methods */ const struct tty_operations *ops; struct list_head tty_drivers; }; 设备驱动程序tty_driver结构说明: 它包含附件信息:driver_name(设备数量),和 (tty 驱动程序的类型和子类型)等 指向 tty_struct 的指针table; termios和termios_locked; 双向链表的两个指针,next和 prev. tty_driver 方法: open, 在用户打开后调用。 close, 在用户关闭后调用。 write, 在用户 write.2 之后或内核需要写入 tty 设备(文件)时调用 put_char, 由内核调用以写入单个字符; flush_chars,在使用 put_char 将一些字符写入 tty设备后由内核调用。 write_room, 返回 tty 设备将接受写入的字符数。 ioctl, 驱动程序特定的 ioctl set_termios, 用于通知 tty 驱动程序 termios 发生变化; set_ldisc, 用于通知 tty 驱动程序线路规则的变化; throttle, 告诉 tty 驱动程序线路规程的输入缓冲区接近满; unthrottle, 告诉 tty 驱动程序线路规程的输入缓冲区有足够的空间; stop, 阻止 tty 驱动程序向 tty 设备发送字符; start, 启动 tty 驱动程序向设备发送字符; hangup, 告诉 tty 驱动程序挂断 tty 设备; break_ctl, 切换 RS-232 端口的 BREAK 状态; wait_until_sent, tty 驱动程序等待设备写出其 FIFO 队列中的所有字符; send_xchar, 向设备发送一个高优先级的 XON-XOFF 字符。
对于tty的整体操作核心:
E:\linux内核\linux-2.6.38.5\drivers\tty\tty_io.c 1.tty driver 的注册、注销等都在这里 注册tty驱动就是创建一个/dev下的设备(主设备号和次设备号确定唯一设备) 一旦注册号,就可以对tty层进行操作,内核部分使用 tty_operations 结构体定义了对tty的操作 可参考: https://docs.kernel.org/driver-api/tty/tty_driver.html#c.tty_driver (底层的tty驱动和tty操作) 不能注册上的消息,会定义在 E:\linux内核\linux-2.6.38.5\drivers\char\ttyprintk.c中 2.tty 设备文件的读取(tty_read 函数) tty设备本身就是一个特殊的设备,linux下一切都是文件,该设备文件只是通过内核调用即系统调用方式来读取、写入 3.tty 设备文件的写入(tty_write 函数) 写数据到tty设备文件是通过ldisc 然后tty_write函数会调用do_tty_write函数再写入到临时缓冲区,缓冲区大小是按照chunk为单位,大家都知道 linux是按照page为单位 [root@aozhejin2 /root]#getconf PAGE_SIZE 4096 而这里它按照的是 2048=2k为一个单位来写入缓冲区. 它会把数据从用户空间复制到内核空间(copy_from_user函数) copy_from_user和copy_to_user这两个函数正向和方向操作, 设备驱动程序中的ioctl函数会经常用到这两个函数,他们负责在用户空间和内核空间传递数据 3.tty的启动和停止 4.写入消息到当前的tty(tty_write_message函数) 5.产生次设备号(tty_line_name函数) 6.安装termios (tty_init_termios函数) 7.安装tty driver到driver表里面(tty_driver_install_tty函数) 8.从driver表里面删除tty driver(tty_driver_remove_tty函数) 9.初始化一个tty设备(tty_init_dev 函数) 这里里面有个核心操作 tty_ldisc_setup 函数会调用,也就是设置ldisc于tty的关系.
参考:
http://www.linusakesson.net/programming/tty/
https://thevaluable.dev/mouseless-development-environment/
https://github.com/withoutboats/notty
https://dev.to/napicella/linux-terminals-tty-pty-and-shell-192e
https://dev.to/napicella/linux-terminals-tty-pty-and-shell-part-2-2cb2
https://thevaluable.dev/guide-terminal-shell-console/
http://www.linusakesson.net/programming/tty/ tty的历史
https://www.yabage.me/2016/07/08/tty-under-the-hood/
https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch18.html
https://ishuah.com/2021/02/04/understanding-the-linux-tty-subsystem/
http://www.linusakesson.net/programming/tty/
https://linux.die.net/man/4/ptmx
https://github.com/util-linux/util-linux
https://en.wikipedia.org/wiki/File_descriptor
https://github.com/torvalds/linux/blob/master/drivers/tty/pty.c
https://www.kernel.org/doc/html/latest/admin-guide/devices.html
https://man7.org/linux/man-pages/man1/stty.1.html
https://yakout.io/blog/terminal-under-the-hood/
https://www.baeldung.com/linux/kill-terminal
https://www.ibm.com/docs/en/zos/2.3.0?topic=descriptions-stty-set-display-terminal-options
https://wiki.archlinux.org/title/Getty#Automatic_login_to_virtual_console
https://en.wikipedia.org/wiki/Restricted_shell
https://www.pcmag.com/encyclopedia/term/unix-98
https://unix.stackexchange.com/questions/449315/some-confused-concept-ptmx-and-tty
https://unix.stackexchange.com/questions/117981/what-are-the-responsibilities-of-each-pseudo-terminal-pty-component-software
https://docs.kernel.org/driver-api/tty/tty_ldisc.html#tty-line-discipline
https://docs.kernel.org/driver-api/tty/n_tty.html
https://docs.kernel.org/driver-api/tty/tty_ldisc.html
https://docs.huihoo.com/doxygen/linux/kernel/3.7/include_2linux_2tty_8h.html
https://www.oreilly.com/library/view/linux-device-drivers/0596005903/ch18.html
https://blog.nelhage.com/2009/12/a-brief-introduction-to-termios-termios3-and-stty/
https://www.geeksforgeeks.org/stty-command-in-linux-with-examples/
https://docs.kernel.org/driver-api/tty/n_tty.html
https://docs.kernel.org/driver-api/tty/tty_ldisc.html
https://www.linux.it/~rubini/docs/serial/serial.html
https://www.kernel.org/doc/html/latest/admin-guide/devices.html