前言
在继续开始Linux驱动学习之前,本期继续带来linux篇的八股文学习,以为后续的学习打个基础。本系列会不定期持续更新(随笔者的学习进度),大家可以收藏一下时不时看一下。
一、网络协议
这部分我们在之前的Linux应用篇简单介绍过了,这次主要是摘出来应付一下“八股取士”。
1. TCP和UDP
1.1 TCP的三次握手和四次挥手
TCP的三次握手应用于建立连接:
- 第一次握手:客户端向服务器发送一个SYN包(同步包),其中包含客户端的初始序列号。此时,客户端进入SYN_SEND状态,等待服务器的确认。
- 第二次握手:服务器收到SYN包后,会确认客户端的SYN(通过发送一个ACK包,其中ack=客户端的序列号+1),同时服务器也会发送一个自己的SYN包,其中包含服务器的初始序列号。这个包通常被称为SYN+ACK包。此时,服务器进入SYN_RECV状态。
- 第三次握手:客户端收到服务器的SYN+ACK包后,会再次发送一个ACK包以确认收到服务器的SYN包(ack=服务器的序列号+1)。发送完这个ACK包后,客户端和服务器都进入ESTABLISHED状态,表示连接已建立成功。
简记:同步包→同步包+应答包→应答包
TCP的四次挥手应用于释放连接,需要进行的四个步骤,具体步骤如下:
- 第一次挥手:当一方(假设为客户端)决定关闭连接时,它会发送一个FIN(Finish)报文段给对方。
- 第二次挥手:服务器收到FIN报文段后,会对这个FIN报文段进行确认,然后自己也发送一个ACK报文段。
- 第三次挥手:服务器在发送完ACK报文段后,会等待所有的数据传输完成,然后发送一个FIN报文。
- 第四次挥手:客户端收到服务器的FIN报文段后,会对其进行确认,并发送一个ACK报文段给服务器。
简记:Finish包→应答包→Finish包→应答包
1.2 特点及其应用场景
TCP | UDP | |
---|---|---|
特点 | 面向连接、可靠、基于字节流 | 无连接的、不可靠的、可向多主机发送 |
优势 | 面向连接、拥塞控制、流量控制、可靠 | 开销小、不需要连接 |
劣势 | 耗时、开销占用大 | 不可靠、无序 |
应用场景 | 适用于需要高可靠性和完整性的数据传输,例如文件传输 | 适用于对实时性要求较高的应用,如视频通话、音频流传输和实时游戏等。 |
2. 线程和进程
进程是资源分配和调度的基本单位,而线程是系统调度的基本单位,一个进程中可以同时运行多个线程,共享同一份系统资源。
2.1 线程同步
- 互斥锁 :一种简单的加锁机制,当一个线程获取了互斥锁时,其他试图获取该锁的线程将被阻塞,直到锁被释放。
- 信号量 :一个用于控制对多个共享资源的访问的计数器。
- 条件变量:条件变量通常与互斥锁一起使用,允许线程等待某个条件成立。
- 读写锁:允许多个线程同时读取共享资源,但只允许一个线程写入资源,当有线程正在写入资源时,其他试图读取或写入的线程将被阻塞。
2.2 进程间的通信方式
- 无名管道:半双工,数据单向流通且仅适用于亲缘进程间。
- 命名信号(FIFO):半双工,容许非亲缘进程间通讯。
- 共享内存:映射同一段能被其他进程访问的内存空间。
- 消息队列:保存在内核的消息的列表。
- 信号量:本质上是计数器。
- 套接字:不同设备间进程通讯。
2.3 进程的生命周期
- 运行:该进程正在执行
- 就绪:该进程可以执行但未获得许可,调度器可以在下次任务进行切换
- 睡眠:进程在等待外部事件,调度器无法在下次任务进行切换
2.4 多线程和多进程的实现方法
- 多进程:在主进程利用fork()等申请一个新的子进程,根据返回值判断是否创建成功,并利用exec()函数进行执行新的程序。
- 多线程:利用pthread_create()函数创建一个新的线程,根据返回值判断是否创建成功,并利用互斥锁和条件量等进入子线程操作。
2.5* 守护进程、孤儿进程和僵尸进程
守护进程通常用于执行后台任务,孤儿进程在父进程结束后由init进程接管,而僵尸进程则是因为父进程没有正确释放子进程的资源而产生的。
2.6* 协程
协程(Coroutine)是一种轻量级的用户态线程,操作系统对协程无感知。它实现的是协作式调度(非抢占式调度),即协程切换由当前协程控制,主动让出CPU(例如当前协程在等待异步网络IO时)。通常情况下,一个线程包含多个协程。
3. TCP/IP四层模型
TCP/IP四层可以说是OSI七层模型的简化版,主要有以下四层模型:
- 应用层(应用层+表示层+会话层)
- 传输层
- 网络层
- 网络接口层(物理层+数据链路层)
二、初识u-boot
这部分我们学习笔记还没有更新到,这里仅仅简单列一下理论,后续学到这部分在详细讲一下。
1. bootloader
Linux启动时需要的一段小程序,负责初始化时钟、中断和其他外设等,然后将内核从flash中加载到SDRAM中,并启动内核,相当于PC机的BIOS。U-Boot(Universal Bootloader)是一个开源的引导加载程序,是Bootloader的一个具象化的表现。
2. 为什么使用uboot启动内核
使用U-Boot启动内核的原因主要有以下几点:
- 初始化硬件:CPU不能直接从块设备(如SD卡、iNand、Nandflash等)中执行代码。在启动Linux内核之前,需要进行一系列的硬件初始化工作,如时钟、串口、DRAM等。U-Boot就是一个复杂的裸机程序,它负责这些初始化工作,为Linux内核的启动创造一个合适的环境。
- 加载内核:U-Boot会将Linux内核镜像从块设备中读取到内存中的特定位置(如链接地址处)。这个过程确保了内核代码在正确的内存地址处执行。
- 启动内核:在硬件初始化和内核加载完成后,U-Boot会执行一个特定的命令(如bootm命令)来启动Linux内核。这个命令告诉CPU从内存中的特定位置开始执行代码,从而启动Linux内核。
- 提供交互性:U-Boot还提供了一个交互式的命令行界面,允许用户进行配置、调试和维护操作。这使得在启动Linux内核之前或之后,用户可以对系统进行更精细的控制和调试。
- 支持多种平台和架构:U-Boot是一个开源的引导加载程序,支持多种处理器架构和嵌入式平台。这使得它成为嵌入式系统开发中广泛使用的工具之一。
3. u-boot的启动过程
U-Boot的启动过程可以分为两个阶段,每个阶段都有其特定的功能和任务。
- 第一阶段(汇编语言阶段):
- 设置异常向量表:当系统发生异常时,处理器会跳转到这些固定地址的向量表中进行处理。
- 硬件初始化:配置时钟相关参数,如内核时钟、总线时钟、IO接口时钟等。
- 准备RAM空间:使内存芯片可用,以便加载U-Boot第二阶段的代码。
- 复制U-Boot第二阶段的代码至RAM中。
- 定义入口
- 第二阶段:
- 初始化外设:包括串口、以太网接口、存储设备等。
- 检测内存映射:确定系统的内存布局和大小。
- 将内核从Flash或其他存储设备读取到RAM中。
- 为内核设置启动参数:这些参数包括内存大小、设备树地址等,用于内核启动时的初始化。
- 调用内核:将控制权交给内核,完成系统的启动过程。
3. u-boot和内核的数据传输
在完成相关设置(R1和R2)之后,uboot将机器ID通过R1发送给内核,并利用R2存放内存基地址。这里会产生标记的概念,他是一种数据结构是tag_header结构体和一个联合体组成。
三、部分驱动的实现流程
1. 字符驱动
- 利用alloc_chrdev_region申请设备号
- 创建类class_create
- 初始化cdev对象:最重要的是设置cdev的ops字段,它指向一个包含设备操作方法的file_operations结构体。
- 实现文件操作接口:在file_operations结构体中,需要实现设备的操作方法。这些方法定义了如何打开设备、关闭设备、读取数据、写入数据等。
- 注册字符设备驱动:通过调用cdev_add函数来完成,该函数将cdev对象添加到内核的字符设备列表中,并为其分配一个inode节点。
- 创建设备节点:device_create创建一个设备节点(也称为设备文件),以便应用程序能够访问该设备。这通常是通过在/dev目录下创建一个具有特定设备号和名称的文件来完成的。可以使用mknod命令或udev系统来创建设备节点。
- 清理和卸载:当不再需要设备驱动时,需要编写清理和卸载函数来释放资源并注销设备,包括注销cdev对象、删除设备节点、释放内存等操作。
2. GPIO子系统
- 采用设备树匹配的方式进行匹配
- 利用of_find_node_by_path( )获取设备节点
- 获取端口号of_get_named_gpio( )
- 申请gpio端口gpiod_get( )
- 设置端口方向gpiod_direction_output( )或gpiod_direction_input( )
- 设置端口值gpio_set_value( )
- 释放申请到的端口资源gpio_free
3. input子系统
- 采用设备树匹配的方式进行匹配
- 利用input_allocate_device申请input_dev结构体
- 初始化input_dev结构体,包括设置设备名称、设备ID、事件类型(如EV_KEY、EV_REL等)以及对应的事件码和值
- 利用input_register_device注册输入设备
- 当硬件产生输入事件时,驱动需要调用相应的函数(如input_event()或input_report_key()等)来上报这些事件
- 当不再需要使用输入设备时,应在remove中调用input_unregister_device()函数来注销该设备
- 注销后,还需要调用input_free_device()函数来释放之前分配的input_dev结构体和相关资源
4. iic子系统
- 采用设备树匹配的方式进行匹配
- 利用i2c_register_driver( )函数进行iic驱动的注册
- 之后用i2c_set_clientdata将数据和设备链接起来
- 进行设备初始化及数据传输,这里主要利用i2c_transfer函数
- 当设备不需要的时候,使用i2c_del_driver()等函数来注销和卸载驱动
5. spi子系统
- 需要在module_init使用spi_register_driver( )函数进行spi驱动的注册
- 利用of_get_named_gpio进行cs片选端口的获取
- 设置spi_device结构体的取值及使用spi_setup( )初始化spi
- 利用spi_message_init、spi_message_add_tail、spi_sync对spi_message结构体进行操作,将信息传递至用户层。
- 在module_exti中使用spi_unregister_driver( )函数进行spi驱动的注销
6. 中断子系统
- 首先,利用of_find_node_by_path函数获取按键设备树节点
- 再利用of_get_named_gpio获取按键使用的GPIO
- 用gpio_reques申请GPIO并通过gpio_direction_input设置引脚方向
- 使用irq_of_parse_and_map获取中断号并初始tasklet_init(软中断)
- 编写中断函数并在合适的位置使用tasklet_schedule进行软中断的调度(软中断)
- 编写tasklet_hander函数,进行中断下文的设计(软中断)
- 当结束操作之后利用free_irq进行资源释放
四、文件操作系统
Linux的文件系统(File System)是操作系统用于明确存储设备(如磁盘或基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构。简而言之,文件系统就是在存储设备上组织文件的方法。
1. 文件类型
描述了如何在存储设备上组织和管理数据。
- -:常规文件,即f:
- d:directory,目录文件
- b:block device,块设备文件,支持以块为单位进行随机访问
- c:character device,字符设备文件,支持以字符为单位进行访问
- major number:主设备号,用于表示设备型号,进而确定要加载的驱动程序
- minor number:次设备号,用于标识同类型中不同的设备
- l:symbolic link ,符号链接文件
- p:pipe,命名管道
- s:socket,套接字文件
2. 文件系统类型
描述存储在文件系统中的不同文件的性质和用途,文件系统类型决定了如何在存储设备上组织、存储和检索数据(对磁盘数据进行索引)。以下是一些常见的文件系统类型:
- EXT4(Fourth Extended File System):
广泛使用在Linux上的默认文件系统,支持大文件和大分区,具有高效的文件块分配和日志功能。 - XFS:
高性能文件系统,最初由SGI开发,适用于处理大量数据和高并发操作的环境,如数据库和大数据应用。 - Btrfs(B-tree File System):
新型文件系统,提供高级数据管理功能,支持快照、克隆、内置卷管理、数据校验和透明压缩。 - NTFS(New Technology File System):
Windows的默认文件系统,支持大文件和高级数据管理功能,Linux通过FUSE(Filesystem in Userspace)和ntfs-3g驱动程序支持NTFS。 - FAT32(File Allocation Table 32):
早期文件系统,广泛兼容于各种操作系统,适用于小型存储设备,但有文件和分区大小限制。
五、VFS(Virtual File System)
VFS是Linux内核中的虚拟文件系统层,它提供了一个抽象层,使得不同的文件系统可以通过统一的接口与内核交互。VFS使得用户空间程序不需要了解底层文件系统的具体实现细节,只需要通过标准的POSIX接口进行文件操作。
主要功能:
- 文件系统抽象:通过统一的接口屏蔽了不同文件系统的实现细节。 文件操作接口:提供open、read、write、close等文件操作接口。
- 挂载管理:管理不同文件系统的挂载点,并将它们组织成一个统一的目录树。
- 文件描述符管理:管理进程的文件描述符,支持多种文件类型(如普通文件、目录、设备文件等)。
关键数据结构:
- struct file:表示一个已打开的文件实例。
- struct inode:表示一个文件系统中的一个对象(文件、目录等)。
- struct super_block:表示文件系统的超级块,包含了文件系统的元数据。
- struct dentry:表示目录项,用于组织文件系统的层次结构。
六、sysfs
sysfs是Linux内核中专门为设备模型设计的虚拟文件系统,它以文件和目录的形式将内核对象和属性导出到用户空间,使得用户可以方便地查看和配置系统设备的属性。sysfs文件系统通常挂载在/sys目录下。
主要功能:
- 设备信息展示:将设备驱动模型中的信息以文件形式展示,便于用户查看和管理。
- 属性文件:提供一种机制,可以通过读写文件来获取或设置设备的属性。
- 层次结构:以层次化的目录结构展示内核对象,如设备、类、总线等,反映内核对象的关系。
关键数据结构: - struct kobject:内核对象,所有sysfs对象的基类。
- struct attribute:sysfs中的属性,每个属性对应一个文件。
- struct sysfs_ops:定义了属性文件的读写操作。
扩展:VFS与sysfs的关系
VFS和sysfs之间的关系可以从以下几个方面来理解:
- sysfs是VFS的一种实现:
sysfs本质上是通过VFS框架实现的一个特殊的文件系统。它利用VFS提供的抽象接口,将内核对象导出到用户空间。 - 文件系统的统一管理:
作为VFS的一部分,sysfs文件系统挂载在VFS的统一目录树中,通常是/sys目录。VFS管理sysfs的挂载点、目录结构和文件操作。 - 内核对象与文件系统的结合:
sysfs通过VFS接口,将内核中的对象(如设备、类、总线等)以文件和目录的形式呈现。这些文件和目录都是通过VFS的inode和dentry等数据结构来管理的。 - 设备驱动与用户空间的桥梁:
sysfs为设备驱动提供了一种标准机制,可以将驱动程序的配置和状态信息导出到用户空间,并通过VFS接口实现读写操作。这使得驱动开发者可以利用标准的文件操作接口与用户空间交互。