嵌入式Linux入门知识点汇总-启动过程、设备树、设备框架、锁

目录

1.BootLoader启动过程?

引导加载程序(Bootloader)

补充u-boot的理解 通用的bootloader

2.系统调用过程?

3.设备驱动模型的三个重要成员?

4.驱动和设备注册是否存在先后顺序?

5.framebuffer机制?

6.字符设备和块设备的区别并分别举例?

1.字符设备

2.块设备

3.网络设备

7.MMU 及内存映射的流程

8.Linux线程跟进程的掌握

9.Linux内核有哪几种锁?

一、atomic原子变量/spinlock自旋锁 — —CPU

二、信号量/互斥锁 读写锁/抢占 — —临界区

10.设备树

11.linux嵌入式驱动框架

12.RAM、ROM、flash、eMMC(通俗易懂)

13.多线程 pthread

13.1 线程

13.2 线程同步

14 移植openSSH

14.1 移植 zlib 库

14.2 移植 openssl 库

14.3 移植 openssh 库

14.3.1 交叉编译openssh库

14.3.2 拷贝进开发板

1.BootLoader启动过程?

loader包含ATF UBOOT (设备树)FDT

对于复杂系统系统启动流程一般是:BootRom、Bootloader、Kernel、Filesystem(文件系统)、App

BootLoader的主要功能包括:关闭看门狗,初始化中断和异常向量表,进行时钟和外设的初始化,提供Can、Uart、Flash读写驱动等等。 大多数的Bootloader分为两个阶段,第一阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和可移植性。 SPL(Secondary Program Loader)。硬件设备初始化、初始化内存空间、初始化堆栈,随后将第二阶段的代码(uboot)复制到SRAM中,跳转到Uboot入口地址处Uboot第二阶段初始化本阶段要使用到的硬件设备,通常会初始化一个串口做命令行方便交互。随后可能检测系统内存映射(memory map),并将内核代码复制到DDR中,最后准备传递给内核的参数,并引导内核。

引导加载程序(Bootloader)

CPU上电后,处理器会执行一个位于Flash/ROM中的已知位置处的代码(这个地方是在硬件设计时设计好的,一上电它就开始执行这个位置的程序),Bootloader就是这第一段代码,Bootloader 从板子上的某个固态存储设备上将操作系统加载到RAM中运行(也可以在某些固态设备上直接运行,如NorFlash)。

 MIPS结构的CPU一般会从0xBFCO0000取第一条指令
 ARM结构的CPU一般会从地址0x0000000取第一条指令

Bootloader的启动过程可以分为两个阶段和三种启动方式,如下所述

1.Bootloader的两个阶段 (1)Bootloader第一阶段的功能 硬件设备初始化 为加载Bootloader 的第二阶段代码准备RAM空间 复制 Bootloader 的第二阶段代码到RAM空间中 设置好栈 跳转到第二阶段代码的C入口点 (2)Bootloader第二阶段的功能 初始化本阶段要使用到的硬件设备(如串口、Flash和网卡等) 检测系统内存映射( memory map ) 将内核映象和根文件系统映象从Flash 上读到RAM空间中 ·为内核设置启动参数 调用内核

2.Bootloader的启动方式(三种) (1)网络启动方式 网络启动方式的开发板不需要较大的存储介质,跟无盘工作站有点类似,但是使用这种启动方式之前,需要把Bootloader安装到板上的EPROM或者Flash中。Bootloader通过以太网接口远程下载Linux内核映像或者文件系统。Bootloader下载文件一般都使用TFTP网络协议,还可以通过DHCP的方式动态配置IP地址。

(2)硬盘启动方式 传统的Linux系统运行在台式机或者服务器上,这些计算机一般都使用BIOS引导,并使用磁盘作为存储介质。Linux传统上是LILO (Linux Loader) 引导,后来又出现了GUN的软件 (Grand Unified Bootloader) 。 这两种Bootloader广泛应用在X86的Linux系统上。

(3)Flash启动方式 大多数嵌入式系统上都使用Flash存储介质。Flash有很多类型,包括NOR Flash、NAND Flash和其它半导体盘。它们之间的不同在于: NOR Flash 支持芯片内执行(XIP, eXecute In Place),这样代码可以在Flash上直接执行而不必拷贝到RAM中去执行。而NAND Flash并不支持XIP,所以要想执行 NAND Flash 上的代码,必须先将其拷贝到 RAM中去,然后跳到 RAM 中去执行。NOR Flash 使用最为普遍。Bootloader一般放在Flash的底端或者顶端,这需要根据处理器的复位向量来进行设置。可以配置成MTD设备来访问Flash分区。

补充u-boot的理解 通用的bootloader

u-boot=裸机程序 核心功能就是引导以及启动内核

对于嵌入式linux设备一般使用DDR或者SDRAM,这些内存需要初始化

a.读Flash把内核copy进内存,初始化内存,初始化时钟,flash等,把flash的内核copy到内存里

b.启动内核(u-boot的目标)

内核的目的:启动应用程序APP->1.读写flash:驱动程序 u盘 网络 屏幕 LCD 鼠标键盘等等

2.读写文件:文件系统 rtoofs

3.找到 启动APP

为何能SD卡启动 一开始CPU就去读SD卡 或者一开始就uart启动 emmc启动 是因为存在BootROM的原因

CPU可以直接访问BootROM BootROM把SD卡里面的uboot拷贝到RAM也就是内存里面去执行

分为XIP以及非XIP

XIP a.硬件初始化 b.Flash 内核拷贝到 RAM c.启动内核

非XIP (烧写到CPU不能访问到的设备上 也就是SD卡启动)

a.通过BootROM把uboot复制到内存RAM

b.执行uboot

c.硬件初始化 但是不再初始化内存

d.Flash 内核拷贝到 RAM

e.启动内核

2.系统调用过程?

系统调用允许应用程序请求获得操作系统内核的服务 触发中断。将系统调用号放入eax寄存器中,执行int 指令+中断号0x80触发中断 切换堆栈。切入到内核堆栈,将用户栈寄存器SSESP、CS等压栈,中断返回时弹栈。

中断处理程序。根据系统调用号执行对应的系统调用程序(system call(num)→sys fork/sys read....) 从中断程序返回

3.设备驱动模型的三个重要成员?

struct device 表示一个真实的或虚拟的设备

struct device driver 表示一个设备的驱动程序

struct bus type 表示一个总线类型

4.驱动和设备注册是否存在先后顺序?

在Linux的platform模型中,驱动程序和设备的注册顺序是独立的,没有固定的先后顺序。总线是负责匹配设备和设备驱动的。当有新的设备或者新的驱动加入到中线中时,总线将调用platform match函数对新增的设备或驱动进行配对,所以无论是否先注册哪一个,内核都将进行自动匹配匹配成功,设备和驱动之间会自动绑定,驱动的probe()函数会被调用。

5.framebuffer机制?

Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。用户可以将Framebuffer看成是显示内存的一个映像,通过mmap将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理内存、换贡机制等等具体细节,这些都是由Framebuffer设备驱动来完成的。通过mmap调用把显卡的物理内存空间映射到用户空间来进行操作

6.字符设备和块设备的区别并分别举例?

特点:字符设备以字节为单位进行输入和输出。每个字节都是独立的,设备无法寻址或读取特定的块;块设备以块为单位进行输入和输出。块是设备中数据的固定大小的块,可以寻址和读取访间方式:字符设备通常是顺序访问;块设备支持随机访问实例:键盘、鼠标、串口设备;SSD(固态硬盘)USB 存储设备

1.字符设备

字符设备指能够像字节流串行顺序依次进行访问的设备,对它的读写是以字节为单位。字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由字符设备驱动提供(一般字符设备都会实现相应的fops集),因此file_operations 也就成为了字符设备驱动的核心。

特点:

一个字节一个字节读写的设备

读取数据需要按照先后数据(顺序读取)

每个字符设备在/dev目录下对应一个设备文件,linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备。

常见的字符设备有鼠标、键盘、串口、控制台等

上层应用如何调用底层驱动:

1)应用层的程序open(“/dev/xxx”,mode,flags)打开设备文件,进入内核中,即虚拟文件系统中。

2)VFS层的设备文件有对应的struct inode,其中包含该设备对应的设备号,设备类型,返回的设备的结构体。

3)在驱动层中,根据设备类型和设备号就可以找到对应的设备驱动的结构体,用i_cdev保存。该结构体中有很重要的一个操作函数接口file_operations。

4)在打开设备文件时,会分配一个struct file,将操作函数接口的地址保存在该结构体中。

5)VFS层 向应用层返回一个fd,fd是和struct file相对应,这样,应用层可以通过fd调用操作函数,即通过驱动层调用硬件设备。

ps:可以讲字符设备和块设备归为一类,它们都是可以顺序/随机地进行读取和存储的单元,二者驱动主要在于块设备需要具体的burst实现,对访问也有一定的边界要求。其他的没有什么不同。 网络设备是特殊设备的驱动,它负责接收和发送帧数据,可能是物理帧,也可能是ip数据包,这些特性都有网络驱动决定。它并不存在于/dev下面,所以与一般的设备不同。网络设备是一个net_device结构,并通过register_netdev注册到系统里,最后通过ifconfig -a的命令就能看到。 不论是什么设备,设备级的数据传输都是基本类似的,内核里的数据表示只是一部分,更重要的是总线的访问,例如串行spi,i2c,并行dma等。

2.块设备

块设备以数据块的形式存放数据,如NAND Flash以页为单位存储数据,并采用mount方式挂载块设备

块设备必须能够随机存取(random access),字符设备则没有这个要求。

块设备除了给内核提供和字符设备一样的接口外,还提供了专门面向块设备的接口块设备的接口必须支持挂装文件系统,通过此接口,块设备能够容纳文件系统,因此应用程序一般通过文件系统来访问块设备上的内容,而不是直接和设备打交道。

对于块设备而言,上层ext2,jiffs2,fat等文件系统会 实现针对VFS的file_opertations成员函数,所以设备驱动层将看不到file_opeations的存在。磁盘文件系统和设备驱动会将对磁盘上文件的访问转换成对磁盘上柱面和扇区的访问。

特点:

数据以固定长度进行传输,比如512K

从设备的任意位置(可跳)读取,但实际上,块设备会读一定长度的内容,而只返回用户要求访问的内容,所以随机访问实际上还是读了全部内容。

块设备包括硬盘、磁盘、U盘和SD卡等

每个块设备在/dev目录下对应一个设备文件,linux用户程序可以通过设备文件(或称设备节点)来使用驱动程序操作块设备。

块设备可以容纳文件系统,所以一般都通过文件系统来访问,而不是/dev设备节点。

正点原子IMX6u文档中有提到,可以去看看!

大家如果仔细观察的话就会发现有些硬盘或者 NAND Flash 就会标明擦除次数(flash 的特性,写之前要先擦除),比如擦除 100000 次等。因此,为了提高块 设备寿命引入了缓冲区,数据先写入到缓冲区中,等满足一定条件后再一次性写入到真正的物 理存储设备中,这样就减少了对块设备的擦除次数,提高了块设备寿命

都知道字符驱动设备的核心主要在于file_opeations而块设备驱动模型框架主要是block_device_operations

块设备也有操作集,但是file_opeations中是涉及到read、write这些操作的,涉及文件IO内容嘛,但是块设备不一样,众所周知我们都知道他是挂载类型的,那如何从物理块中读写数据?通过request_queue,request和bio

请求队列,请求,遍历请求之类的,点到为止,具体可以去看正点原子文档即可。

3.网络设备

虽然在Linux系统存在一句话叫一切皆文件,无论是各种文本文件还是具体的硬件设备(硬件由设备文件来实现相应)。但是网络设备在Linux内核中却是唯一不体现一切皆设备思想的驱动架构,因为网络设备使用套接字来实现网数据的接受和发送。

网络设备驱动不同于字符设备和块设备,不在/dev下以文件节点代表,而是通过单独的网络接口来代表。

特点:

网络接口没有像字符设备和块设备一样的设备号和/dev设备节点,只有接口名,如eth0,eth1

通过socket操作,而不是open read write

核心在于net_device_ops

7.MMU 及内存映射的流程

8.Linux线程跟进程的掌握

Linux之进程与线程详解(一文足矣)_linux下进程和线程-CSDN博客

Linux 多线程原理深剖_线程的布局-CSDN博客

文章后面也有区别的讲解,这里做一个大体了解,有个概念性的认识。

9.Linux内核有哪几种锁?

简析Linux内核中的各种锁:信号量/互斥锁/读写锁/原子锁/自旋锁/内存屏障等_linux 中主要有哪几种内核锁?-CSDN博客

Linux 中各种锁原理概述_linux 锁-CSDN博客

不同的锁,作用对象是不一样的,也就是作用域不一样

下面分别是作用于临界区CPU内存cache 的各种锁的归纳:

补充:cache是一种缓存,包含硬件缓存(CPU缓存)以及软件缓存(网页缓存,数据缓存)

补充:临界区用于描述一段只能被单个线程或进程在同一时间访问的代码区域。通常,这些代码区域涉及对共享资源的访问

临界区 -> semaphore信号量、Mutex互斥锁、rw-lock读写锁、preempt抢占

CPU -> atomic原子变量、spinlock自旋锁

内存 -> RCU 、Memory Barrier

cache -> Per-CPU

一、atomic原子变量/spinlock自旋锁 — —CPU

既然是锁CPU那就都是针对多核处理器或多CPU处理器。单核的话,只有发生中断会使任务被抢占,那么可以进入临界区之前先关中断,但是对多核CPU光关中断就不够了,因为对当前CPU关了中断只能使得当前CPU不会运行其它要进入临界区的程序,但其它CPU还是可能执行进入临界区的程序。

(1) atomic原子变量:

所谓原子操作, 就是该操作绝不会在执行完毕前被任何其他任务或事件打断, 也就说, 它是最小的执行单位, 不可能有比它更小的执行单位, 因此这里的原子实际是使用了物理学里的物质微粒的概念。原子操作需要硬件的支持, 因此是架构相关的, 其 API 和原子类型的定义都定义在内核源码树的 include/asm/atomic.h 文件中, 它们都使用汇编语言实现, 因为 C 语言并不能实现这样的操作。原子操作主要用于实现资源计数, 很多引用计数 (refcnt) 就是通过原子操作实现的。

(2) spinlock自旋锁:

当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待然后不断的判断锁是否能够被成功获取

#include <linux/spinlock.h>
// 定义自旋锁
spinlock_t my_lock;
void my_function(void)
{
    spin_lock(&my_lock);
    // 访问共享资源的操作
    spin_unlock(&my_lock);
}

ps:针对上述进行一些重要问题的阐述,如下所示

互斥锁中,要是当前线程没拿到锁,就会出让CPU;而自旋锁中,要是当前线程没有拿到锁,当前线程在CPU上忙等待直到锁可用,这是为了保证响应速度更快。但是这种线程多了,那意味着多个CPU核都在忙等待,使得系统性能下降。

因此一定不能自旋太久,所以用户态编程里用自旋锁保护临界区的话,这个临界区一定要尽可能小,锁的粒度得尽可能小。

为什么自旋锁的响应速度会比互斥锁更快?

我觉得主要还是作用域的问题 spinlock 主要针对CPU 互斥锁针对作用域 前者直接CPU操作 后者需要内核协助 在小林coding中说到,自旋锁是通过 CPU 提供的 CAS 函数(Compare And Swap),在「用户态」完成加锁和解锁操作,不会主动产生线程上下文切换,所以相比互斥锁来说,会快一些,开销也小一些。 而互斥锁则不是,前面说互斥锁加锁失败,线程会出让CPU,这个过程其实是由内核来完成线程切换的,因此加锁失败时,1)首先从用户态切换至内核态,内核会把线程的状态从「运行」状态设置为「睡眠」状态,然后把 CPU 切换给其他线程运行;2)当互斥锁可用时,之前「睡眠」状态的线程会变为「就绪」状态(要进入就绪队列了),之后内核会在合适的时间,把 CPU 切换给该线程运行。 然后返回用户态。这个过程中,不仅有用户态到内核态的切换开销,还有两次线程上下文切换的开销。 线程的上下文切换主要是线程栈、寄存器、线程局部变量等。 而自旋锁在当前线程获取锁失败时不会进行线程的切换,而是一直循环等待直到获取锁成功。因此,自旋锁不会切换至内核态,也没有线程切换开销。 所以如果这个锁被占有的时间很短,或者说各个线程对临界区是快进快出,那么用自旋锁是开销最小的! 自旋锁的缺点前面也说了,就是如果自旋久了或者自旋的线程数量多了,CPU的利用率就下降了,因为上面执行的每个线程都在忙等待— —占用了CPU但什么事都没做。

二、信号量/互斥锁 读写锁/抢占 — —临界区

(1) semaphore信号量

其实跟FreeRTOS的信号量很像很像!!但是RTOS得信号量是队列的变种,而linux下的信号量是利用spinlock的保护

信号量(信号灯)本质是一个计数器,是描述临界区中可用资源数目的计数器。

信号量进行多线程通信编程的时候,往往初始化信号量为0,然后用两个函数做线程间同步: sem_wait():等待信号量,如果信号量的值大于0,将信号量的值减1,立即返回。 如果信号量的值为0,则线程阻塞。 sem_post():释放资源,信号量+1 ,相当于unlock,这样执行了sem_wait()的线程就不阻塞了。

要注意:信号量本身也是个共享资源,它的++操作(释放资源)和--操作(获取资源)也需要保护。其实就是用的自旋锁保护的。如果有中断的话,会把中断保存到eflags寄存器,待操作完成,就去该寄存器上读取,然后执行中断。

(2) Mutex互斥锁

互斥锁中,要是当前线程没拿到锁,就会出让CPU;而自旋锁中,要是当前线程没有拿到锁,当前线程在CPU上忙等待直到锁可用

信号量的话表示可用资源的数量,是允许多个进程/线程在临界区的。但是互斥锁不是,它的目的就是只让一个线程进入临界区,其余线程没拿到锁,就只能阻塞等待。线程互斥的进入临界区,这就是互斥锁名字由来。

另外提一下std::timed_mutex睡眠锁,它和互斥锁的区别是: 互斥锁中,没拿到锁的线程就一直阻塞等待,而睡眠锁则是设置一定的睡眠时间比如2s,线程睡眠2s,如果过了之后还没拿到锁,那就放弃拿锁(可以输出获取锁失败),如果拿到了,那就继续做事。比如 用成员函数try_lock_for()

std::timed_mutex g_mutex;
//先睡2s再去抢锁
if(g_mutex.try_lock_for(std::chrono::seconds(2)))){
	// do something
}
else{
	// 没抢到
	std::cout<<"获取锁失败";
}

(3) rw-lock读写锁

用于读操作比写操作更频繁的场景,让读和写分开加锁,这样可以减小锁的粒度,提高程序的性能。 它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这可以提高并发性能,因为读操作通常比写操作频繁得多。读写锁这种就属于高阶锁了,它的实现就可以用自旋锁。

(4) preempt抢占

抢占必须涉及进程上下文的切换,而中断则是涉及中断上下文的切换。 内核从2.6开始就支持内核抢占,之前的内核不支持抢占,只要进程在占用CPU且时间片没用完,除非有中断,否则它就能一直占用CPU; 抢占的情况: 比如某个优先级高的任务(进程),因为需要等待资源,就主动让出CPU(又或者因为中断被打断了),然后低优先级的任务先占用CPU,当资源到了,内核就让该优先级高的任务抢占那个正在CPU上跑的任务。也就是说,当前的优先级低的进程跑着跑着,时间片没用完,也没发生中断,但是自己被踢掉了。 为了支持内核抢占,内核引入了preempt_count字段,该计数初始值为0,每当使用锁时+1,释放锁时-1。当preempt_count为0时,表示内核可以安全的抢占,大于0时,则禁止内核抢占

Per-CPU— —作用于cache per-cpu变量用于解决各个CPU里L2 cache和内存间的数据不一致性。

10.设备树

从设备树转换得来的 platform_device 会被注册进内核里,以后当我们每 注册一个 platform_driver 时,它们就会两两确定能否配对,如果能配对成功 就调用 platform_driver 的 probe 函数。

platform_match

/**
 * platform_match - bind platform device to platform driver.
 * @dev: device.
 * @drv: driver.
   *
 * Platform device IDs are assumed to be encoded like this:
 * "<name><instance>", where <name> is a short description of the type of
 * device, like "pci" or "floppy", and <instance> is the enumerated
 * instance of the device, like '0' or '42'.  Driver IDs are simply
 * "<name>".  So, extract the <name> from the platform_device structure,
 * and compare it against the name of the driver. Return whether they match
 * or not.
   */
   static int platform_match(struct device *dev, struct device_driver *drv)
   {
   struct platform_device *pdev = to_platform_device(dev);
   struct platform_driver *pdrv = to_platform_driver(drv);

   /* When driver_override is set, only bind to the matching driver */
   if (pdev->driver_override)
   	return !strcmp(pdev->driver_override, drv->name);

   /* Attempt an OF style match first */
   if (of_driver_match_device(dev, drv))
   	return 1;

   /* Then try ACPI style match */
   if (acpi_driver_match_device(dev, drv))
   	return 1;

   /* Then try to match against the id table */
   if (pdrv->id_table)
   	return platform_match_id(pdrv->id_table, pdev) != NULL;

   /* fall-back to driver name match */
   return (strcmp(pdev->name, drv->name) == 0);
   }

1.最先比较:是否强制选择某个 driver

if (pdev->driver_override)
   	return !strcmp(pdev->driver_override, drv->name);

2.然后比较:设备树信息

if (of_driver_match_device(dev, drv))
   	return 1;

3.接下来比较:platform_device_id

if (pdrv->id_table)
   	return platform_match_id(pdrv->id_table, pdev) != NULL;

4.最后比较

return (strcmp(pdev->name, drv->name) == 0);

没有转换为 platform_device 的节点,如何使用

任意驱动程序里,都可以直接访问设备树。

11.linux嵌入式驱动框架

主要针对于字符设备模型 核心还是fille_operations

核心在于应用层需要什么我驱动层提供层提供什么

注册其file_operations放入字符设备数组里面chrdevs[1]主设备号

谁来放呢?register_chrdev此函数来放,谁来调用?入口函数 module_init

谁来卸载?也就是出口函数来卸载罢了,整个流程就这么几个。。

那么问题来了?驱动层跟应用层如何传递数据?

利用copy_from_user copy_to_user

针对嵌入式设备中需要操作地址来使用外设,其中涉及到mmu 要对其映射出虚拟地址 iorrmap

12.RAM、ROM、flash、eMMC(通俗易懂)

RAM:Random-Access Memory的缩写,意思为“随机存取存储器”

ROM:Read-Only Memory的缩写,意思为“只读存储器”

RAM:

临时性:RAM存储的数据在断电时会丢失,因此称为易失性存储器

可读写:RAM可以被快速读取和写入,它的存储单元可以根据需要来读取和修改数据。

容量和速度:RAM的容量可以从几兆字节(MB)到数百GB不等,速度非常快,可以通过高速总线迅速访问数据。

ROM:

永久性:ROM中的数据在断电时不会丢失,因此被称为非易失性存储器。

只读性:ROM的内容只能被读取,无法直接修改。它通常在制造过程中被预先写入或烧录,供计算机系统在启动时使用。

常见的ROM:Flash

flash:全称flash memory,也就是平时说的“闪存”。现在用作存储电脑主板的 BIOS 、程序代码、应用数据的越来越多。绝大部分的 U 盘、SDCard、MMC卡、TF卡 等移动存储设备也都是使用 Flash 作为存储介质

Flash Memory 主要可以分为NOR Flash 和 NAND Flash 两类。区别:NAND型写入速度和擦除速度快、最大擦除次数多,大容量下NAND型比NOR型成本要低很多,体积也更小;NOR型支持片上执行,可以在上面直接运行代码,软件驱动比 NAND 简单,一般小容量的用NOR 型因为小容量NOR读取速度快。

常见的ROM:MMC、eMMC MMC是一种存储器接口协议,能符合这接口的内存器都可称作mmc储存体

eMMC (Embedded Multi Media Card),“嵌入式多媒体卡”、“嵌入式设备的存储器”。手机、平板的存储介质目前基本都是eMMC,功耗和成本都很低。类似硬盘,它将NAND Flash与控制器集成为一体,通过内在的控制器管理Flash,这样CPU可不再为Flash不断更新制程而烦恼兼容性问题。eMMC=NAND Flash+闪存控制芯片+标准接口封装。

13.多线程 pthread

13.1 线程

线程是轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源,可以这样理解,进程是资源分配的最小单位,线程是操作系统调度执行的最小单位。

进程有自己独立的地址空间, 多个线程共用同一个地址空间

线程更加节省系统资源, 效率不仅可以保持的, 而且能够更高 在一个地址空间中多个线程独享: 每个线程都有属于自己的栈区, 寄存器(内核中管理的) 在一个地址空间中多个线程共享: 代码段, 堆区, 全局数据区, 打开的文件(文件描述符表)都是线程共享的 线程是程序的最小执行单位, 进程是操作系统中最小的资源分配单位

每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片 一个地址空间中可以划分出多个线程, 在有效的资源基础上, 能够抢更多的CPU时间片

CPU的调度和切换: 线程的上下文切换比进程要快的多

上下文切换:进程/线程分时复用CPU时间片,在切换之前会将上一个任务的状态进行保存, 下次切换回这个任务的时候, 加载这个状态继续运行,任务从保存到再次加载这个过程就是一次上下文切换。

线程更加廉价, 启动速度更快, 退出也快, 对系统资源的冲击小。

在处理多任务程序的时候使用多线程比使用多进程要更有优势,但是线程并不是越多越好,如何控制线程的个数呢?

文件IO操作:文件IO对CPU是使用率不高, 因此可以分时复用CPU时间片, 线程的个数 = 2 * CPU核心数 (效率最高)

处理复杂的算法(主要是CPU进行运算, 压力大),线程的个数 = CPU的核心数 (效率最高)

#include <pthread.h>

线程创建 pthread_create

线程退出 pthread_exit

线程回收 pthread_join

线程分离 pthread_detach

13.2 线程同步

假设有4个线程A、B、C、D,当前一个线程A对内存中的共享资源进行访问的时候,其他线程B, C, D都不可以对这块内存进行操作,直到线程A对这块内存访问完毕为止,B,C,D中的一个才能访问这块内存,剩余的两个需要继续阻塞等待,以此类推,直至所有的线程都对这块内存操作完毕。 线程对内存的这种访问方式就称之为线程同步,通过对概念的介绍,我们可以了解到所谓的同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次进行的。

其实也就是FreeRTOS类的东西 都是一样的 操作系统都是类似的

14 移植openSSH

主要侧重于移植openSSH的流程以及思路

14.1 移植 zlib 库

cd zlib-1.2.11/ //进去 zlib 源码 CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld AD=arm-linux-gnueabihfas ./configure --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/zlib //配置 make //编译 make install

1.指定目标平台编译移植的目标板的芯片型(例如ARM架构的Linux系统上编译)2.设置了交叉编译时使用的 C 编译器和静态库工具

14.2 移植 openssl 库

一样观察下述配置命令即可

./Configure linux-armv4 shared no-asm --prefix=/home/zuozhongkai/linux/IMX6ULL/tool/openssl CROSS_COMPILE=arm-linux-gnueabihf- make //编译 make install

主要是注意这个linux-armv4配置条款表示32位ARM凭条,make完之后make install安装,所以注意也是对应开发板本身的资源类型进行配置

14.3 移植 openssh 库
14.3.1 交叉编译openssh库

我们观察下述的命令,找到流程

cd openssh-8.2p1/ ./configure --host=arm-linux-gnueabihf --with-libs --with-zlib=/home/zuozhongkai/linux/ IMX6ULL/tool/zlib --with-ssl-dir=/home/zuozhongkai/linux/IMX6ULL/tool/openssl --disable-etcdefault-login CC=arm-linux-gnueabihf-gcc AR=arm-linux-gnueabihf-ar //配置 make //编译

上述内容可以知道确定 1.指定目标平台 编译移植的目标板的芯片型(例如ARM架构的Linux系统上编译),2.zlib库的位置 3.openssl库的位置 4.禁用默认的登录选项 5.设置了交叉编译时使用的 C 编译器和静态库工具

14.3.2 拷贝进开发板

创建目录,资源进行拷贝到开发板上,修改一些相关配置,建立软链接等等

如果不想远程连接都输入密码就生成密钥即可,本质跟vscode与linux远程一样,毕竟本质就是建立SSH连接

相关推荐

  1. Linux知识汇总

    2024-07-20 22:38:01       24 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-20 22:38:01       106 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-20 22:38:01       116 阅读
  3. 在Django里面运行非项目文件

    2024-07-20 22:38:01       95 阅读
  4. Python语言-面向对象

    2024-07-20 22:38:01       103 阅读

热门阅读

  1. NVM配置

    NVM配置

    2024-07-20 22:38:01      26 阅读
  2. 获取磁盘剩余容量-----c++

    2024-07-20 22:38:01       29 阅读
  3. springboot3.2 RedisCacheManager配置

    2024-07-20 22:38:01       28 阅读
  4. springSecurity学习之springSecurity简介

    2024-07-20 22:38:01       30 阅读
  5. 分布式锁-redisson锁重试和WatchDog机制

    2024-07-20 22:38:01       20 阅读
  6. Photoshop图层类型

    2024-07-20 22:38:01       25 阅读
  7. (一)js前端开发中设计模式前篇之对象

    2024-07-20 22:38:01       27 阅读
  8. 网络安全-网络安全及其防护措施6

    2024-07-20 22:38:01       25 阅读
  9. [C++ 入门基础 - 命名空间]

    2024-07-20 22:38:01       21 阅读