嵌入式八股文(二)linux基础篇


前言

  在继续开始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 特点及其应用场景

TCPUDP
特点面向连接、可靠、基于字节流无连接的、不可靠的、可向多主机发送
优势面向连接、拥塞控制、流量控制、可靠开销小、不需要连接
劣势耗时、开销占用大不可靠、无序
应用场景适用于需要高可靠性和完整性的数据传输,例如文件传输适用于对实时性要求较高的应用,如视频通话、音频流传输和实时游戏等。

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接口实现读写操作。这使得驱动开发者可以利用标准的文件操作接口与用户空间交互。
  • 12
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值