专栏首页嵌入式Linux系统开发Linux 终端初始化 console_init 及 tty 驱动框架

Linux 终端初始化 console_init 及 tty 驱动框架

先前分析了 Linux 入口地址和 Linux 系统启动流程,本文详细分析一下 Linux 启动流程中的 console_init 终端初始化函数。

上两篇文章如下:

Linux 内核入口分析

手把手教你分析 Linux 启动流程

讲解终端初始化之前我们先讲解一个概念:tty

在Linux系统中,终端是一类字符型设备,它包括多种类型,通常使用tty来简称各种类型的终端设备。我们一般分为三类:

串口终端(/dev/ttyS*)

串口终端是使用计算机串口连接的终端设备。Linux 把每个串行端口都看作是一个字符设备。这些串行端口所对应的设备名称是 /dev/ttySAC0;/dev/ttySAC1……

控制台终端(/dev/console)

在Linux系统中,计算机的输出设备通常被称为控制台终端(Console),这里特指printk信息输出到的设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty(物理终端)上,比如通过内核启动参数” console=ttySAC0”就把console映射到了串口0。

虚拟终端(/dev/tty*)

当用户登录时,使用的是虚拟终端。使用Ctcl+Alt+[F1—F6]组合键时,我们就可以切换到tty1、tty2、tty3等上面去。tty1–tty6等称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名。

console_init 分析

Linux 启动函数 start_kernel 会调用 console_init 函数。

linux4.14/kernel/printk/printk.c

linux4.14/drivers/tty/n_tty.c

我们可以看到,console_init 主要做了两件事情:

1、n_tty_init 主要调用 tty_register_ldisc(N_TTY, &n_tty_ops) 注册 tty 线路规程。

2、

call = __con_initcall_start;
 while (call < __con_initcall_end) {
  (*call)();
  call++;
 }

这里主要是调用 __con_initcall_start 到 __con_initcall_end 之间的函数。

__con_initcall_start 和 __con_initcall_end 定义在:

linux4.14/include/asm-generic/vmlinux.lds.h

中间包含了 .con_initcall.init 段:

linux4.14/include/linux/init.h

我们通过 console_init 声明的驱动模块,就会出现在这个段中,被调用。普通我们声明的驱动模块都是使用 module_init,如果我们写的是串口驱动,可以使用console_init 声明。

如果要看具体中间有什么函数,可以查看编译 Linux 内核的输出 System.map 文件,这个文件记载了从头到尾 Linux 干了什么,具体的地址存储了什么东西。

System.map 文件默认在编译后的 Linux 内核根目录下, 当然我们也可以修改到其他目录。

这里会有三列:地址,区,函数名字。

如果后面我们使用 console_init(serial_5685_xxxx)去声明我们的驱动,那么这个 serial_5685_xxxx 就会出现在 __con_initcall_start 和 __con_initcall_end 之间,就会被调用。

initcall机制

注意上述流程,我们来理解一下 initcall 机制:

普通我们写一个程序,想要它被调用,需要在主流程中调用这个函数,才算被调用。

那么这种方式如果放在 Linux 中,是难以想象的,我们自己写的代码要在多少个地方声明。

而你如果采用initcall机制,意思就是说,你使用一个字符串声明你的驱动初始化函数,那么所有的驱动初始化函数都存在内存中一个连续的段中,系统启动以后,会从这个段的第一个函数开始,一个一个遍历,进而一个一个调用,这就是 initcall 机制。这就是为什么我们写驱动只需要使用 module_init 声明,编译进去即可自动被调用的原因!!!

System.map

编译后的内核根目录 System.map 文件记载了所有的驱动加载顺序,如果你不确定驱动的加载顺序,在这里查看就可以,每次编译 Linux 内核就会产生一个新的 System.map。

tty 驱动

我们不要把 tty 驱动和 串口驱动 弄混了,tty 驱动架构如下:

其中 tty driver 等价于我们普通写的驱动,可以自己写。

也就是说,在 tty 驱动框架主要有三层:tty core、tty line discipline、tty driver,另外最上层是用户空间,最下层是硬件。

tty core 称之为 tty 核心,主要作用是向用户提供统一的接口。

tty line discipline 称之为 tty 线路规程,主要从上下两层接收数据,并按照一定协议进行转换,比如 ppp 或者蓝牙协议,这样你的 tty 终端就不止可以用普通的串口,还可以通过其他协议访问到我们的系统。比如手机链接 PCB 板子的 WiFi 接入系统控制终端,输入 ls、cd 等命令。这一层并不是必须的,你可以直接使用驱动和 tty core 进行通信,但一般这一层都会有。

tty driver 就是我们常说的串口驱动。

在 console_init 函数中,它做的两件事,就是注册 tty 线路规程,注册 tty 驱动,tty 核心是包含在内核当中的。tty 线路规程和 tty 驱动可以有很多个。

有的人会有疑问,为什么有了 tty 驱动了,还会有一个 tty 线路规程。得益于 Linux 模块化的思想,这里主要是为了分层与隔离。tty 驱动只和硬件相关,只解析基本的硬件信息,把硬件信息转换成字符。所有的对字符的进一步处理包括加入蓝牙协议传输,监控数据等都放在 tty 线路规程当中。这样 tty 驱动是可以完美复用和移植的。

分享一张彭大佬的图,本文我只讲了概念,彭大佬讲解过 tty 源码:

这里只需要注意一点,在右下角,tty driver 是没有 read 函数的,tty driver 层有 buffer,输入的数据会存储在 buffer 中,被读取。

原因很简单,对于 tty 来说,输入设备和输出设备不是同一个设备,输入设备是键盘,输出设备是屏幕,这和普通的 IIC、SPI 驱动同一个设备不一样。因此在设计上 tty driver 没有 read 函数。

·················· END ··················

本文分享自微信公众号 - 嵌入式Linux系统开发(Jason_Linux_),作者:Jasonangel

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-10-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 手把手教你分析 Linux 启动流程

    最新 Linux 内核是 5.15 版本。现在常用 Linux 内核源码为4.14、4.19、4.9 等版本,其中 4.14 版本源码压缩包大概 90+M,解压...

    用户8662056
  • 手把手教你分析 Linux 启动流程

    最新 Linux 内核是 5.15 版本。现在常用 Linux 内核源码为4.14、4.19、4.9 等版本,其中 4.14 版本源码压缩包大概 90+M,解压...

    Jasonangel
  • Docker Notes-storage

    摘要: Docker Notes系列为学习Docker笔记,本文是Docker存储介绍

    itliusir
  • 如何定制Linux外围文件系统?

    一般来说,我们所说的Linux系统指的是各种基于Linux Kernel和GNU Project的操作系统发行版。为了掌握Linux操作系统的使用,了解 Lin...

    人不沙雕枉少年
  • printf的归宿-数据打印到哪儿了

    近日在一次测试Linux内核路由查找算法的过程中,发现一个printf语句竟然能将性能降低2/3。当然,使用“竟然”一词并不意味着这个问题是第一次发现,我的想法...

    Linux阅码场
  • 第4阶段——制作根文件系统之分析init进程(2)

    本节目标: (1) 了解busybox(init进程和命令都放在busybox中) (2) 创建SI工程,分析busybox源码来知道init进程做了哪些事情 ...

    张诺谦
  • Kali Linux 采坑汇总(续篇)

    Kali Linux 是个坑特别多的操作系统,尤其是对于我这样的初学者来说,这也难怪会有那句名言:Kali 学得好,监狱进得早!这两天在定制自己的 Ka...

    悠风
  • 第3阶段——内核启动分析之start_kernel初始化函数(5)

    内核启动分析之start_kernel初始化函数(init/main.c) stext函数启动内核后,就开始进入start_kernel初始化各个函数, 下面只...

    张诺谦
  • linux_file_system

    在学校的时候泛泛读过一遍 apue,其中的部分知识只是有个大概印象,其实我个人对底层技术还是有热情和追求的 哈哈,打算把经典的书籍结合遇到的场景重读一遍,先拿 ...

    changan
  • 当我们在谈论高并发的时候究竟在谈什么?

    高并发是互联网分布式系统架构的性能指标之一,它通常是指单位时间内系统能够同时处理的请求数,简单点说,就是QPS(Queries per second)。

    Java团长
  • 当我们在谈论高并发的时候究竟在谈什么?

    这里先给出结论: 高并发的基本表现为单位时间内系统能够同时处理的请求数, 高并发的核心是对CPU资源的有效压榨。

    桶哥
  • Linux系统安全 | Linux中的Shell和Bash

    学安全的我们,经常会听到说获得某服务器的shell,就是指获得某个服务器的操作权限。我们学习linux时,经常会遇到bash,bash也是指的是某个服务器的权限...

    Gcow安全团队
  • Android init 启动

    Android是基于Linux系统的,所以Android启动将由Linux Kernel启动并创建init进程。该进程是所有用户空间的鼻祖。

    Rouse
  • 概述Linux TTY/PTS的区别

    当我们在键盘上敲下一个字母的时候,到底是怎么发送到相应的进程的呢?我们通过ps、who等命令看到的类似tty1、pts/0这样的输出,它们的作用和区别是什么呢?

    砸漏
  • Linux-485收发切换延迟的解决方法

    RS-485(亦称TIA-485, EIA-485)作为一种半双工总线,其收发过程不能同时进行。 RS-485通信的具体硬件原理可查阅其他资料,此处不详述。本...

    叶余
  • linux设备驱动第三篇:如何写一个简单的字符设备驱动

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动。本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符...

    程序员互动联盟
  • Linux0号进程,1号进程,2号进程

    本节我们将从linux启动的第一个进程说起,以及后面第一个进程是如何启动1号进程,然后启动2号进程。然后系统中所有的进程关系图做个简单的介绍

    DragonKingZhu
  • linux内核启动过程分析

    start_kernel是内核启动阶段的入口,通过单步调试,可以发现它是linux内核执行的第一个init,我们单步进入看看它做了哪些操作:

    De4dCr0w
  • 下拉式终端Tilda

    无意中发现一款非常好用的下拉式终端,很多人肯定会问,下拉式终端?什么叫下拉式终端?和linux自带的终端有什么区别呢?所以请带着答案看下面的内容

    kevinfaith

扫码关注云+社区

领取腾讯云代金券