自定义博客皮肤VIP专享

*博客头图:

格式为PNG、JPG,宽度*高度大于1920*100像素,不超过2MB,主视觉建议放在右侧,请参照线上博客头图

请上传大于1920*100像素的图片!

博客底图:

图片格式为PNG、JPG,不超过1MB,可上下左右平铺至整个背景

栏目图:

图片格式为PNG、JPG,图片宽度*高度为300*38像素,不超过0.5MB

主标题颜色:

RGB颜色,例如:#AFAFAF

Hover:

RGB颜色,例如:#AFAFAF

副标题颜色:

RGB颜色,例如:#AFAFAF

自定义博客皮肤

-+
  • 博客(128)
  • 收藏
  • 关注

原创 C++类型转化

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。转换为子类对象的指针或引用(动态转换)。

2025-03-30 19:43:27 419

原创 C++智能指针

具体来说,RAll的核心是:对象构造时获取资源,对象析构的时候释放资源,又由于对象析构会在退出函数栈帧时自动释放,所以我们不需要显式的释放资源。在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。前两种智能指针实际上没有很好的解决智能指针之间的拷贝问题,于是C++11又设计了另一种可以支持拷贝且不会造成指针悬空的智能指针,即。auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份bit::auto_ptr来了解它的原理。

2025-03-30 13:20:04 648

原创 C++异常

实际使用中很多公司都会自定义自己的异常体系进行规范的异常管理,因为一个项目中如果大家随意抛异常,那么外层的调用者基本就没办法玩了,所以实际中都会定义一套继承的规范体系。这样大家抛出的都是继承的派生类对象,捕获一个基类就可以了。// 服务器开发中通常使用的异常继承体系public:, _id(id){}protected:int _id;public:{}

2025-03-30 11:54:39 308

原创 C++11新特性

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址。

2025-03-29 18:36:25 1240

原创 SPI通信协议

SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。SPI,是一种高速的,全双工同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备。

2025-03-28 22:45:33 1429

原创 STM32 IIC通信

IIC(Inter-Integrated Circuit)是 IIC Bus 简称,中文叫集成电路总线。它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。IIC使用两根信号线进行通信:一根时钟线SCL,一根数据线SDA。IIC将SCL处于高时SDA拉低的动作作为开始信号,SCL处于高时SDA拉高的动作作为结束信号;

2025-03-28 13:33:54 1598

原创 STM32 串口

模拟接收就是读取RX端的电平,需要注意在接收的时候需要一个外部中断,在起始位的下降沿触发,进入接收状态,并且对齐采样时钟,依次采样八次。可以选择无校验,奇校验和偶校验。注意,当TXE标志位置 1 时,数据其实还没有发送数据,只要数据从TDR转移到发送移位寄存器了,TXE就会置 1 ,我们就可以写入新的数据了,然后发送移位寄存器就会在下面的。起始位:串口的空闲状态必须是高电平,在传输的时候,必须先发送一个起始位,必须是低电平,打破空闲状态,产生一个下降沿,这个下降沿就是告诉接收设备,这一帧的数据要开始了。

2025-03-25 11:04:53 877

原创 STM32 DMA直接存储器存取

DMA配置(以ADC为例):把ADC_DR的地址塞进外设站点,定义的数组塞进存储器站点,如果ADC是连续转换则设置为循环模式,非连续则设置为单次模式,通道有几个就转运几次,并且每一次转运完成都硬件触发一次DMA请求进行DMA转运(对应外设要对应器通道),ADC_DR地址不自增,存储器地址自增,数据宽度都为半字,优先级配置示情况而定。STM32的CPU是32位的,有4G的寻址能力,但是芯片的存储器都是KB级别的,所以就有很多空间没有使用到,图中灰色部分[Reserved]都是没有使用到的地址。

2025-03-25 11:04:07 700

原创 读写锁与自旋锁

自旋锁是一种多线程同步机制,用于保护共享资源免受并发访问的影响。在多个线程尝试获取锁时,它们会持续自旋(即在一个循环中不断检查锁是否可用)而不是立即进入休眠状态等待锁的释放。这种机制减少了线程切换的开销,适用于短时间内锁的竞争情况。但是不合理的使用,可能会造成CPU的浪费。也就是说当一个线程持有锁,在临界区内处理数据花的随时间较长,此时就使用互斥锁,将其他线程挂起等待。但是如果在临界区内处理数据时间很短。比挂起线程然后再将线程唤醒画的时间还短,此时使用互斥锁就不合适,浪费时间。

2025-03-17 12:59:02 863

原创 Linux死锁

此时张三有A,李四有B,此时对方都想要得到对方的票。等待对方释放自己的资源。如果允许有外部干预,比如把其中某个人的资源强行释放给对方,这样就张三和李四就脱离了死锁状态。但是如果外部干预也没用,那就彻底没办法了,也就是死锁了。死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态。举个例子来描述,如果此时有一个线程A,按照先获得锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先获得锁b再获得锁a的顺序获得锁。

2025-03-17 12:58:27 329

原创 Linux实现线程池与单例模式

线程池是一种用于管理和复用一组工作线程的技术,目的就是提高程序性能和资源利用率,特别是在处理大量短时间任务时。简单来说,线程池就是预先创建一批线程,在有任务时就将其分配给某个线程执行,当线程执行完自己的任务之后并不会销毁而是等待下一个任务。这样就避免了频繁创建和销毁线程的大量开销。工作线程:在线程池中创建一批线程,这些线程等待并处理任务任务队列:待处理的任务队列。当有新任务时,任务就会被放入队列中等待执行线程池管理器:管理线程池中线程的生命周期,处理任务调度、线程创建和销毁等。

2025-03-16 16:33:47 1143

原创 Linux信号量

简单来说,信号量是一个资源计数器:当信号量的计数大于0时表示还有资源可以使用,当计数等于0时,表示资源不可以用,需要等待资源。但是使用信号量,可以在数组的基础上,可以让生产者和消费者同时访问,一个在前面生产,一个在后面消费,二者访问的不是数组的同一个元素。一开始,data资源为0,也就是为空情况,消费线程和生产线程都来了,消费线程是注定要被挂起。生产者p(space_sem)申请空间资源–,生产动作v(data_sem)数据资源++,生产者申请到空间了,但数据还是在那块空间,当然v的是数据的信号量。

2025-03-16 16:33:15 774

原创 Linux线程同步

大部分情况下都是相互制约的,当条件成立的时候,另一个线程唤醒此线程,如果另一个线程竞争到锁,其实这个线程大概率会陷入等待,等待此线程唤醒他,此时就释放锁没有线程再竞争,此时此线程就得到了锁。设想这样一个场景,当多个线程想去访问一个临界资源的时候,但是这个临界资源又需要另一个线程改变到某个值才行,在没有改变的时候多个线程会不断的去申请锁,检查,然后释放锁,此时能改变这个临界资源的线程竞争到锁的可能性就变小了,此时其他线程不断的竞争锁然后检查再释放,在没有改变前是做的无用功。,该类型用于表示条件变量。

2025-03-15 16:10:33 1151

原创 Linux线程互斥

加锁的时候,首先每个线程都会执行将al寄存器置为0,然后执行交换语句,当初始化了一个锁后,锁中是1,这个交换是原子性的,第一个执行这条语句的线程会将al寄存器的值和锁的值进行交换,此时这个线程就得到了锁,后面其他线程再执行这条语句就得到的都是0。当释放锁的时候,会将锁置1,然后唤醒等待的线程,等待的线程再去重新执行上后面的竞争锁的逻辑。如果一个线程申请成功了,执行临界区的代码了,在执行临界区代码期间,该线程可以被切换走执行其他线程,但是其他线程并不会进入临界区,该线程可以放心的执行完临界区的代码。

2025-03-15 16:09:56 950

原创 线程的终止、等待和分离

线程的创建已经谈论过,还有线程的终止,等待和分离。

2025-03-14 17:55:14 887

原创 LWP与TID

命令可以查看当前有哪些线程,其中有LWP表示轻量级进程的id,但是这个id和TID并不是相同的用法,LWP是给内核使用的,内核通过LWP标识一个执行流。当一个线程结束后,在内存上其实已经退出,内存中其实已经没有这个线程的执行流了,但是在地址空间上仍然保留了这个线程控制块。这个线程控制块包含了线程的一些信息和独立的栈结构,这个栈就是存储线程自己的数据,不被其他线程共享。因为线程是共享地址空间的,但是线程本身也要维护自己的数据,所以线程库的实现就类似于malloc的方式在。每个线程都有自己的线程控制块。

2025-03-14 17:54:29 898

原创 Linux线程概念

曾经学习的是进程 = 内核数据结构 + 进程的代码和数据但是当有了线程的概念就要改变一下这个说法进程是承担系统资源的基本实体。线程:在进程内部运行,是cpu调度的基本单位。即如果一个多核CPU,不同的核心可以同时执行不同进程的线程。初步理解:在下面,一个一个的task_struct就是一个一个的执行流,地址空间的正文代码也会被分为4部分,让每一个执行流去执行,这一个一个的执行流就是Linux中的线程,这是我们对线程的初步理解,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

2025-03-13 15:51:33 895

原创 重新认识页表与地址空间

此时就可以知道,通过最高10位寻找页目录中的哪个,然后页目录中存的页表的32位地址,此时找到的是页表的起始地址。找到页表后,中间10位是这个页表的偏移量,找到对应的页表后里面存的就是页帧的地址,然后找到页帧。MMU接收到CPU发出的虚拟地址后,会根据当前CR3寄存器中存储的页目录表物理地址,以及虚拟地址的结构(如页目录索引、页表索引、页内偏移等),在页目录表和页表中查找对应的物理地址。需要知道的是,当子进程发生写时拷贝的时候,此时发生拷贝的就是这个变量所在的页帧,而不是单独的一个变量。

2025-03-13 15:50:25 700

原创 Linux信号

有了这个表,我们就能知道一个信号在该进程的响应方式,显然,对handler数组某一元素内容的修改就是修改某一信号的响应方式。当我们进入系统调用时,我们以操作系统的身份来执行时,此时就进入了内核态,操作系统把我们的底层工作做完,做完这些工作后返回到我们的调用处,继续执行下面的代码,但是操作系统,由内核态返回到用户态时,在返回的这个时候做信号的检测和处理。类似于共享空间,即使每一个进程空间都有内核空间,但所有进程的内核空间其实是共享的,这就说明了,内核级页表在任何进程中都是一样的,映射的都是同一个内核空间。

2025-03-08 19:10:09 1044

原创 进程间共享内存通信(结合代码模拟通信)

共享内存是进程通信最快的方法,因为没有额外的拷贝过程,直接对内存进行读取。此外,共享内存并不保证通信的同步,数据在读取之后不会自动清空释放,也就意味着无论何时只要想读就能读。所以很多时候,为了保证数据的同步性,即写入端进程写入数据后才让读端进程读取数据,会使用管道进行限制,因为管道的读写具有数据同步性,可以在写端写入内存后,写段唤醒管道的读,才能执行读后面的读取内存函数。即当有数据要写入的时候,就让管道发出一个提示,读端读到提示之后才从shm中读取数据。否则,读端就会堵塞等待。

2025-03-08 19:08:59 573

原创 STM32 ADC模数转换

ADC(Analog-Digital Converter)模拟-数字转换器ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁12位逐次逼近型ADC,1us转换时间。12位是分辨率,1us是转换频率,就是1MHz。输入电压范围:0 ~ 3.3V,转换结果范围:0~409518个输入通道,可测量16个外部和2个内部信号源。2个内部的是温度传感器和内部参考电压。温度传感器可以测量CPU温度。内部参考电压是一个1.2V左右的基准电压。

2025-03-04 21:10:39 1420

原创 命名管道(用命名管道模拟server和client之间的通信)

bash进程并不会给我们写的两个不同的程序创建通信的管道,即使这两个进程看起来好像都是bash的子进程,但是此时再用血缘关系那一套来看就不适用了。命名管道是一个特殊的文件。如果当前是以读的方式打开FIFO文件,而此时没有进程以写的方式打开该FIFO文件时,读端进程就会阻塞,直到有进程以写的方式打开该FIFO。如果当前是以写的方式打开FIFO文件,而此时没有进程以读的方式打开该FIFO文件时,写端进程就会堵塞,直到有进程以读的方式打开该FIFO。表示的是创建管道文件的路径可以是绝对路径也可以是相对路径。

2025-03-04 21:09:47 486

原创 TIM编码器测速

Encoder Interface 编码器接口编码器接口可接收增量(正交)编码器的信号,根据编码器旋转产生的正交信号脉冲,自动控制CNT自增或自减,从而指示编码器的位置、旋转方向和旋转速度每个高级定时器和通用定时器都拥有1个编码器接口两个输入引脚借用了输入捕获的通道1和通道2当编码器两个相的位置如图时为正向或者反相。实质:测频法测正交脉冲的频率CNT计次,然后每隔一段时间去一次计次,其实是测频法的思路。

2025-03-03 16:40:29 835

原创 利用管道创建进程池

一个进程创建子进程通常是为了让这个子进程去为它完成某个任务。bash中处理命令,就是得到命令创建进程,执行结束,进程销毁。但是频繁的创建进程,销毁进程这样作开销比较大。涉及到内存分配、上下文切换等操作。于是我们可以提前创建出一批进程,当有任务要做的时候就从这一批子进程中拿出一个空闲的去执行,一旦某个子进程把任务执行完之后也不立即销毁进程,而是等待下一个任务的来临。我们把这些预先创建的一批子进程就叫做进程池。我们把进程池中的进程也称为工作进程。父进程可以通过管道将任务分配给指定的一个进程。父进程也叫。

2025-03-03 16:39:21 481

原创 进程间通信

所以当父子进程需要通信的时候,假如父进程要进行读,子进程进行写,那么父进程就要关闭写文件的描述符,子进程就要关闭读文件的描述符。因为是进程间通信,此时就不需要再向磁盘中写数据了,所以根据上述的原理,在实现管道时,还要进行一些修改,并不和文件操作全部一样,所以就需要再设计一套接口,但是大致原理是相同的。因为对文件的操作的标志位不一样,所以要存在两个。当父进程没有sleep的时候,也不会一直读取,而是会阻塞,因为子进程sleep,在父进程读取后,管道中就没有了数据,但是子进程没有写入,父进程就处于阻塞状态。

2025-03-02 15:06:29 1033

原创 Linux动静态库

我们调用这个函数的时候,call 的就是在库中的起始地址的偏移量,通过这个偏移量和加载器维护的起始地址就能找到库中的函数了。最左侧的就是ELF的虚拟地址,其实,严格意义上应该叫做逻辑地址(起始地址+偏移量),但是我们认为起始地址是0,也就是说,其实虚拟地址在我们的程序还没有加载到内存的时候,就已经把可执行程序进行统一编址了。但是这还不够,只是给了路径,并没有给名称,不知道找哪个库,头文件可以自动寻找,因为我们在程序中给出了哪些头文件,但是库gcc不知道要找哪些库,指定的路径下会有很多的库。

2025-03-02 15:05:13 788

原创 Linux软硬链接

在Linux操作系统中,文件系统的核心概念之一是链接,包括软链接(符号链接)和硬链接。这些链接提供了访问文件系统中文件的灵活方式。软链接,被称为符号链接。类似于windows下的一个快捷方式。其本身也是一个文件,有着自己独立的inode。该文件的内容是链接对象文件的路径。这也是我们为什么可以通过快捷方式打开目标文件。比如桌面的快捷方式目标显示的就是这个文件的内容,这个文件是一个独立文件,有自己的inode,这个文件的内容是目标文件的路径。

2025-03-01 16:52:59 1126

原创 STM32输入捕获(附代码)

从框图中可以看出,输入捕获和输出比较,一个通道共用一个寄存器,共用一个引脚,所以输入捕获和输出比较一个通道只能使用其中一个功能。IC(Input Capture)输入捕获输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前CNT的值将被锁存到CCR中,可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。(就是当输入引脚产生上升沿,输入捕获电路会捕捉到电平跳变,控制CNT锁存到CCR中。每个高级定时器和通用定时器都拥有4个输入捕获通道。可配置为PWMI。

2025-03-01 16:52:32 1170

原创 Linux文件系统

在我们没有打开文件的时候,我们知道文件是存储在磁盘上面的。所以要了解文件系统不可避免的要了解磁盘。因为大部分时候用的云服务器,而云使用的是机械硬盘,所以只从机械硬盘角度理解文件系统,不考虑固态硬盘。但是固态硬盘的和机械硬盘原理不同而已,理解了文件系统,二者的存储原理差别就无需纠结。

2025-02-28 20:03:38 1392

原创 重定向与缓冲区

但不同的是,dup2函数可以将一个已存在的文件描述符复制到另一个文件描述符上,并允许自定义新文件描述符的编号。我们知道stderr和stdout同样都是映射到显示器,之所以这样设计,是因为要打印错误信息,当向stderr流输入信息的时候,我们可以将这个重定向到我们自己的文件中,这样设计可以将错误信息输出到我们自己的文件中,方便我们debug。第一个代码的行为和重定向的行为一样,其实重定向差不多就是这样,重定向的本质就是改变文件描述符的内容,文件描述符的内容就是文件的地址,的作用是刷新缓冲区。

2025-02-28 20:02:50 975

原创 Linux文件操作

操作系统层面,管理文件的结构体(上面所说的struct file)中,就存在函数指针(上面图中画的也有,每个结构体中都有操作底层方法的指针),调用这些驱动层的函数,操作系统将数据拷贝到缓冲区中,驱动层的函数对缓冲区中的数据进行操作。类似键盘,显示器,鼠标,磁盘,网卡等,统称为IO设备,我们要使用这些设备,都要安装这些设备的驱动,所以在IO设备和操作系统中间还有一层叫做。表示的就是:以写的方式打开,如果之前文件不存在,则创建文件,并且打开文件后清空文件中的所有内容。函数的时候,没有文件是会创建此文件的。

2025-02-27 17:08:31 901

原创 STM32定时器输出比较(PWM代码)

OC(Output Compare)输出比较输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。每个高级定时器和通用定时器都拥有4个输出比较通道。基本定时器是没有的。高级定时器的前3个通道额外拥有死区生成和互补输出的功能。

2025-02-27 17:07:33 1043

原创 C语言实现简易shell

大部分命令都是shell交给子进程进行,运用进程替换来执行我们给的命令,少部分内建命令shell自己执行。

2025-02-26 11:45:46 189

原创 Linux进程程序替换

我们知道 “ 进程 = 内核数据结构 + 代码和数据 ”,上面函数的原理就是将内核数据结构基本不变,地址空间和页表不变。执行结果就是在执行第一个printf()后,执行了ls -la一样的命令,其实就是执行了这条命令,最后一条printf()命令并没有执行。系列函数的返回值不需要关心,因为一旦替换成功,就不会向后继续执行,一旦替换失败就会接着向后执行。(就是在path中寻找,如果是自己的程序还是要给路径),我们秩序给出参数即可。,而是原来的进程,使用了老进程的外壳,新进程的代码和数据。

2025-02-26 11:44:44 417

原创 Linux进程控制

所以在进程结束的时候,空间全部释放,但是pcb会保留一段时间,变为Z(僵尸)状态,写入退出码和退出信号。进程在退出的时候,如果父进程不管不顾,那么进程会处于Z状态。如果子进程没有退出,而父进程在进行执行waitpid进行等待,这就是阻塞等待。通过上述代码可以观察到,在子进程结束的时候子进程变为了Z状态,在父进程等待成功后子进程小时,被回收。这样,父进程可以在检测到子进程仍在运行时继续执行其他任务,并在稍后再次尝试检查子进程的状态,直到子进程结束。我们知道,我们自己写的所有的进程都是bash的子进程。

2025-02-25 11:20:56 611

原创 Python快速上手

变量不需要加类型语句不需要加分号不需要加头文件“ ” 表示次方print()函数不带 f ,直接输出变量的值,内部可以带双引号或者单引号都可Python中可以通过type来查看变量类型,类型根据初始化自己就确定了。Python中int可以根据数据大小自动扩容, 只要内存足够大, 理论上就可以表示无限大小的数据。所以没有long,short类型但是浮点型不能无限大,固定8个字节和 C++ / Java 等语言不同, Python 的小数只有 float 一种类型, 没有 double 类型

2025-02-25 11:20:02 600 2

原创 Linux进程地址空间

代码共享,所以看到前五次打印的g_val的地址都是一样的,这我们不意外,等到了第六次时,我们发现父进程g_val依然是0,子进程的g_val变成了100,因为我们将它改了,这也不意外,因为前面说了,父子进程之间代码共享,而数据是各自私有一份的(写时拷贝),但是令人奇怪的是地址竟然是一样的!由上面我们所了解的知识,一个进程有它的task_struct,有地址空间,有页表,页表当中有虚拟地址和物理内存的映射关系,有了页表的存在,虚拟地址到物理地址的一个转化,由操作系统来完成的,同时也可以帮系统进行合法性检测。

2025-02-24 14:34:05 960

原创 Linux2.6内核进程调度队列

通过此篇文章简单了解Linux是怎么调度进程的。上图是Linux2.6内核中进程队列的数据结构以及之间关系。

2025-02-24 14:33:40 974

原创 哈希表与unordered_map,unordered_set

即使多个元素通过哈希函数得到的地址是一样的,也不会显著影响哈希表的性能。此外,拉链法下的负载因子是可以超过1的,因此拉链法下的哈希表可以灵活的应对动态数据集的增长,不需要频繁的调整哈希表的大小。哈希表最显著的特点就是查找数据的效率非常的高,平均查找的时间复杂度为O(1)。造成哈希冲突的主要原因是,哈希表的大小是有限的,而输入的数据可能会非常多,因此不可能避免出现哈希冲突。需要直到的是,开散列的负载因子也是使用元素个数计算的,也就是说,数组中并没有占满,有的位置一直是空,那么也会面临扩容的情况。

2025-02-23 16:36:51 660

原创 C++模拟实现map和set

map和set的底层都是红黑树,而我们使用的红黑树之前模拟实现是使用的key - value模型。但是只有map是key - value模型,而set是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。但是从源码中可以看到,set虽然只存储key,但是他仍然是使用的key - value模型。底层实际存放的是由<value, value>构成的键值对。

2025-02-23 16:35:53 966

空空如也

TA创建的收藏夹 TA关注的收藏夹

TA关注的人

提示
确定要删除当前文章?
取消 删除