【RTOS学习】模拟实现任务切换 | 寄存器和栈的变化

🐱作者:一只大喵咪1201
🐱专栏:《RTOS学习》
🔥格言:你只管努力,剩下的交给时间!
图

目录

  • 🏀认识任务切换
    • 🏐切换的实质
    • 🏐栈中的内容
    • 🏐切换过程
  • 🏀实现任务切换
    • 🏐伪造现场
    • 🏐启动任务
    • 🏐切换任务
  • 🏀栈和寄存器变化
    • 🏐创建任务时
    • 🏐任务启动时
    • 🏐任务切换时
  • 🏀总结

🏀认识任务切换

🏐切换的实质

图
如上图所示代码,定义两个任务函数task_atask_b,在mymian函数中调用这两个函数,在调用的时候传入不同的参数。在任务函数中,打印出自己的函数名称后便开始死循环打印各自形参接收到的字符串。

tu
如上图所示,在调用task_a以后,该函数在它的栈中运行,局部变量保存在栈中,在其内部调用的putsputchar函数也会有自己的栈,这两个函数的栈紧挨着task_a的栈。

假设能执行到task_b函数,该函数也有一个栈,进行和上面相同的操作。

  • 每一个函数都对应着一个自己的栈。

而FreeRTOS执行的任务也是函数,它们也有自己的栈,每一个任务就对应着一个自己的栈

裸机程序中,函数在执行的过程中,使用的是函数自己的栈中的内容,各自的操作也是在自己的栈中完成,包括数据的保存,修改等等。

FreeRTOS中不同任务在执行的时候,也是使用任务函数自己栈中的内容,各自的操作也是在自己的栈中完成的。

  • 任务切换的本质,就是切换不同任务的栈让CPU来操作。
  • 任务切换其实就是在切换

🏐栈中的内容

我们知道,FreeRTOS中任务的切换是在SysTick_Handler中断中完成的,也就是说在该中断函数中完成了栈的切换。

首先要知道的就是,在切换栈的时候,栈中有什么!!!

tu
如上图所示,便是在SysTick_Handler中切换任务时,当前任务栈中的内容,包含R0~R3,R12,LR,返回地址,xPSR以及R4~R11这些寄存器中的内容。

在学习中断的保存现场时候本喵讲解过:

  • 在产生中断,调用中断服务函数之前,硬件保存R0~R3,R12,LR,返回地址,xPSR这些寄存器中的数据到栈中
  • 在中断服务函数中,软件保存R4~R11这些寄存器中的数据到栈中

硬件保存不用我们管,软件保存就需要代码来实现了,但是我们在写中断服务函数的时候,从来也没有写过保存R4~R11寄存器的代码,这是因为这部分代码由编译器替我们完成了。

  • 如果中断函数或者普通函数会使用到R4~R11寄存器,那么在一进入函数时需要先保存这些寄存器中的值到栈中,调用完毕时再将原本的值恢复到寄存器中。

🏐切换过程

tu
如上图所示,任务A执行一定时间后产生了Tick中断,在中断函数中切换成了任务B,随后开始执行任务B,执行一定时间后再次在Tick中断函数中切换成任务A,如此反复。

关键就在于SysTick_Handler中断服务函数中到底做了什么:

  • 暂停任务A:保存任务A的现场
    • R0~R3,R12,LR,返回地址,xPSR由硬件保存到任务A的栈中
    • R4~R11由软件保存到任务A的栈中
  • 运行任务B:恢复任务B的现场
    • 任务B栈中的R4~R11寄存器值由软件恢复到寄存器中
    • 任务B栈中的R0~R3,R12,LR,返回地址,xPSR寄存器值由硬件恢复到寄存器中。

🏀实现任务切换

🏐伪造现场

tu
继续看这张图,任务A和B的切换发生在Tick中断函数中,在切换任务执行A的时候,需要恢复任务A的现场,但是在任务A第一次运行的时候,它的现场哪里来的呢?

在第一次执行任务A时,它的现场并不存在,因为没有在执行任务A期间发生过Tick中断,所以任务A的栈中没有硬件保存的R0~R3,R12,LR,返回地址,xPSR寄存器值,也没有软件保存过R4~R11寄存器的值。

  • 所以,需要在创建任务的时候伪造一个该任务的现场,在第一次执行该任务的时候,有现场可恢复

图
如上图所示代码,定义创建任务函数create_task,该函数的第一个参数就是要执行的任务函数,所以使用typedef void(*task_function)(void* param)重名名任务函数指针为task_function,方便后面使用。

在创建任务的函数create_task中伪造该任务现场时,需要的参数有:

  • 要执行的任务函数指针f
  • 传给任务函数的参数prama
  • 属于该任务的栈stack
  • 以及栈的大小stack_len
  • 这里本喵仅实现静态任务创建,由我们自己指定任务的栈。

在函数内部,由于形参stack指向的是栈的起始地址,也就是这段内存的最低地址,但是栈是从高地址向低地址向下生长的,所以需要得到栈顶的位置top,因为存放的16个32位的寄存器值,所以(stack + stack_len)得到的就是栈顶的地址。

下一步就是真正的伪造现场,用值填充这个栈,由于高地址存放的是高编号寄存器中的值这个规则,所以我们从栈底开始依次存放数据。

  • 先存放R4~R11
  • 再存放R0~R3,R12,LR,返回地址,xPSR

存放过程中,必须严格按照前面讲解的SysTick_Handler中断函数中栈中的内容顺序存放。

对于R4~R11,任务第一次启动时并不关心它里面的内容是什么,所以全部设置为0,R0是用来传递任务函数的参数param的,不能设置为0,R1~R3,R12也无所谓,全部设置为0。

对于LR,由于该任务是第一次运行,它必然不会是被其他函数调用的,所以无所谓返回地址,也设置为0。

对于返回地址,这才是Tick中断函数第一次启动任务后,退出Tick中断函数时真正的返回地址。将任务函数的地址放在这里,任务启动并退出中断函数后就会执行相应的任务函数,所以这里放入任务函数的指针f

对于状态寄存器xPSR,虽然任务是第一次执行,之前没有任何状态存在,但是它不能是0,必须将它的第24位置1:

TU
如上图所示,程序状态寄存器xPSR中的第24位T,该位为1表示使用Thumb指令集,为0表示使用ARM指令集,而本喵使用的Cortex-M3只能使用Thumb指令集,所以该位必须是1。

现场伪造完毕以后,需要将该任务的栈记录下来,方便下次切换时能够找到该任务的栈:

  • 创建全局数组task_stacks存放每个任务的栈顶
  • 使用task_count计数任务个数

tu

如上图所示,创建两个大小为1024字节的char类型静态全局数组stack_astack_b,这两个数组就是两个任务的栈。

  • 在常规创建数组语句的后面加上__attribute__ ((aligned (4)))表示让该数组在内存中要4字节对齐

如果不要求4字节对齐的话,在将该数组作为任务的栈时,由于CPU是32位处理器,所以在访问栈时可能由于不是4字节对齐而出现读取或者写入错误,导致程序出现问题。

  • 如此,在创建任务的时候就完成了现场伪造。

🏐启动任务

tu
如上图代码所示,在创建好两个任务之后,任务的启动也是在Tick中断函数中,该中断函数本喵定义成一个汇编函数SysTick_Handler_asm

Tick中断产生后,硬件会调用SysTick_Handler_asm中断服务函数,在一进入中断函数时,硬件已经完成了R0~R3,R12,LR,返回地址,xPSR寄存器的保存,将这些寄存器中的值保存到了栈中。

但是R4~R11寄存器中的值硬件并不管,需要软件完成,所以在一进入中断函数时,就使用STMDB SP!, {R4 - R11}汇编指令将R4~R11的值保存到栈中。

由于是中断函数,所以此时LR寄存器中的值并不是返回地址,而是那个特殊值,由于在后面会使用BL SysTick_Handler调用C函数来实现任务栈的切换,会改变LR中的值,所以这里要将此时LR中的特殊值也保存到栈中。

LR中的特殊值赋给R0进行传参,将真正栈(不包括栈中的LR)赋值给R1作为另一个参数进行传参。

  • 在任务没启动时,现场保护所操作的栈并不是我们指定的任务栈,而默认的栈。

图
如上图代码所示,这是Tick中断中调用的回调C函数SysTick_Handler,在函数内部,先调用is_task_running()判断任务有没有创建好,如果没有创建好就直接返回,此时这仅仅是一次普通的Tick中断。

如果任务创建好了,根据cur_task当前任务的值判断这是不是第一次启动任务,如果该值是-1,说明这是第一次启动任务,调用get_stack从记录任务栈的数组中得到第一个任务的栈,然后调用StartTask_asm函数将该任务的栈切换过来,开始执行。

  • 调用StartTask_asm传的参数中,stack是指定要切换的栈,lr_bak是特殊值,是为了软件恢复完毕后触发硬件恢复。

tu
如上图所示代码,StartTask_asm函数本喵同样定义成了一个汇编代码,在一进入该函数时,就将存放在要启动任务栈中的R4~R11值恢复到对应的寄存器中。

  • R0寄存器中的值是在调用StartTask_asm时传过来的,表示要启动任务的栈。

软件恢复完毕后,此时的R0指向栈中存放硬件要恢复的R0值所在的位置,所以使用MSR MSP, R0将该栈指针赋值给MSP,让真正的栈顶指针SP来管理这部分栈。

然后使用BX R1跳转,由于R1中的是一个特殊值,所以触发了硬件恢复,将栈中剩下的R0~R3,R12,LR,返回地址,xPSR值恢复到了对应的寄存器中。

  • 由于此时恢复的现场是我们伪造出来的,所以返回地址是该任务要执行的任务函数。

此时第一个任务就执行起来了。

补充用到的功能函数:

tu
如上图所示是用到的功能函数,这些函数都放在task.c文件中。启动任务只是将全局变量标志task_running置一,然后陷入死循环,此时在发生Tick中断时,通过该标志就可以知道任务是否创建成功,因为任务没有创建的时候,也有可能发生Tick中断。

🏐切换任务

tu
如上图所示,在切换任务时,仍然是在Tick中进行的,在调用SysTick_Handler回调C函数时,已经完成了现场保护,在C函数中执行的是红色框中的代码。

任务切换时,说明在前面已经有任务在运行了,得到前面的任务,再调用get_next_task()得到要切换的新任务,然后判断这是否只有一个任务,如果新任务和前一个任务相同的话,就说明只有一个任务,此时直接返回就可以,现场保存和现场恢复等操作都发生在这个任务上。

不止一个任务时,使用set_task_stack()将前一个任务的栈继续保存到记录任务栈的数组中,因为前一个任务在运行过程中栈会变化,然后获取新任务的栈,再更新当前任务的下标cur_task,最后调用StartTask_asm来切换任务。

StartTask_asm中的操作和启动第一个任务时一样,也是进行软件恢复和硬件恢复,然后返回到新任务函数处执行,此时就完成了任务的切换。

如此一来,两个任务就可以交替执行了。

tu
SysTick定时器的超时时间设置成1ms,此时每隔1ms就会发生一次任务切换,两个任务交替执行。

Tick中断发生时,硬件会保存当前任务的R0~R3,R12,LR,返回地址,xPSR寄存器的值到当前任务的栈中,其中返回地址就是发生中断时那条指令的下一条地址。

当任务再次被切换回来以后,会将返回地址赋值给PC,该任务就会接着被切换走的位置继续执行。

  • 只有第一次启动任务和第一次被切换上来的任务,返回地址是任务函数的地址,任务从函数的起始位置执行。

图
如上图所示,在创建两个任务a和b时,传给任务函数的参数分别是a和b,运行起来后,可以看到串口中字符a和b在交替打印。

tu
如上图所示,再创建一个task_c任务来进行一些计算,并且打印计算结果。

tu
如上图所示,此时串口中打印出来的结果有字符a和b,还有计算任务计算的结果,三个任务在同时运行,也完美的实现了任务之间的切换。

🏀栈和寄存器变化

现在本喵已经自己实现了多任务之间的切换,下面来看看每个过程中栈和寄存器的变化。

🏐创建任务时

创建任务的时候,在create_task中伪造了现场:

tu
如上图所示,严格按照寄存器在栈中的存放顺序伪造好了现场,这个栈是我们在创建任务时给该任务指定的栈,也就是那个全局数组。

下面来看看,创建任务时,真正内存中的值和我们分析的是否一致:
图
如上图所示,打开该工程的反汇编文件,找到stack_a全局数组所在的地址是0x2000000c,本喵这里仅带大家看一个任务a的创建。

TU
如上图所示,首先要根据stack_a全局数组的地址计算出任务A栈的起始地址0x2000000C + 1024 = 0x2000040C,又因为伪造现场时栈中存放16个寄存器的值,所以0x2000040C + 16 * 4 = 0x200003CC得到就是伪造完现场后,任务A栈的SP所在位置。

SP所指位置在调试过程中查看该位置的内存,可以清楚的看到,从0x200003CC处开始向高地址增长的内存中,存放着我们要伪造的16个寄存器数据。

  • 伪造的R0处存放的值是0x00000061,这是任务A函数的形参,也就是字符a的ASCII码。
  • 伪造的返回地址处存放的值是0x0800054D,这是任务A要执行函数的入口地址。

tu
如上图所示,从反汇编文件中查找0x0800054C,发现这是task任务函数的入口地址。之所以在内存中存放的是0x0800054C + 1 = 0x0800054D,是因为最低位为1表示该函数使用的是Thumb指令集。

🏐任务启动时

tu

如上图所示,在SysTick_Handler_asm中断函数中打断点,让程序在启动任务时第一次进入Tick中断中完成软件保存后停下来。

从此时寄存器中的值可以看到SP = 0x2000FFBC,此时操作的栈就是这里,红色框中内存里的值是软件将对应寄存器中的值保存到栈中,蓝色框中是在产生Tick中断时,硬件将对应寄存器中的值保存到栈中。此时完成了现场保护

  • 此时的栈0x2000FFBC和我们指定的任务A的栈0x2000000C相差甚远,所以必然不是一个栈。

所以第一次启动任务过程中,Tick中断产生后,保存现场发生在程序启动时默认的栈中,不属于任何一个任务的栈。


图
如上图,在Tcik中断函数中完成现场保护以后会调用回调函数SysTick_Handler,在调回调函数中,获取到了任务A的栈stack = 0x200003CC,和我们前面计算出来的结果相符。

tu
如上图所示,回调函数的又调用StartTask_asm汇编函数来完成现场恢复,在调用汇编函数时,传入的参数stack = 0x200003CClr_bak = 特殊值

在汇编函数中,首先把任务A栈中伪造的R4~R11的值通过软件恢复到对应的寄存器中,此时R0寄存器指向任务A栈里伪造的R0处,硬件恢复就从这里开始。

  • 然后使用MSR MSP, R0将任务A的栈交给了SP寄存器管理。

此时我们伪造的任务A栈里的现场仅剩下R0~R3,R12,LR,返回地址,xPSR的值等待硬件恢复了。

tu

如上图所示,在StartTask_asm函数中完成软件恢复以后,用指令BX R1触发硬件恢复,此时R1中的值是由回调函数传递过来的特殊值。

硬件恢复完成以后,可以看到,程序从我们伪造的返回地址0x0800054D处开始执行,也就是任务A函数task,而且此时寄存器SP = 0x20000040C,该值是前面我们算出来的任务A栈的起始顶部位置。

  • 此时任务A在执行过程中,操作的就是属于该任务的栈了,完成了栈的切换。

🏐任务切换时

tu
如上图所示,在任务A启动以后执行的过程中,再次产生了Tick中断,打断点让程序停止在中断函数中。

在断点位置已经完成了现场保存,红色框中的部分是软件将LR,R4~R11寄存器中的值保存到栈中,蓝色框中的部分是硬件将R0~R3,R12,LR,返回地址,xPSR保存到栈中。

  • SP = 0x200003C4,该值位于任务A的栈0x2000000C~0x2000040C之间。

所以说现场保存发生在任务A的栈中,此时就保存了任务A的现场。

tu
如上图所示,中断函数再次调用了回调C函数SysTick_Handler,在该函数中,得到了任务B的栈stack = 0x200007CC,从内存窗口中可以看到任务B栈里存放的是创建任务B时伪造的现场。

  • 因为任务B是第一次被执行,所以恢复的是创建任务时伪造出来的现场。

可以看到,任务B栈中的返回地址也是0x0800054D,这是因为任务A和任务B执行的是一个任务函数。

tu
如上图,回调函数再次调用了StartTask_asm函数来完成任务B的现场恢复,在该函数中,软件先恢复任务B栈中R0~R11的值到对应寄存器中,然后将此时的R0 = 0x200007EC赋值给SP,等待硬件从这里开始恢复剩下的R0~R3,R12,LR,返回地址,xPSR

图

如上图,在StartTask_asm中使用BX R1触发硬件恢复以后,蓝色框中的R0~R3,R12,LR,返回地址,xPSR由硬件恢复到了对应的寄存器中,而且此时SP = 0x2000080C,这是任务B栈的栈顶位置。

  • 此后任务B在执行过程中使用的就是它自己的栈。

此时就恢复了任务B的现场,当再次产生Tick中断时,就会保存任务B的现场,恢复任务A的现场,如此往复就实现了两个任务之间的切换。

🏀总结

实时操作系统中最重要的就是多任务之间的切换,在这里我们自己动手实现了一遍,对任务的切换有了一个更深的认识。

要时刻记住,任务切换的本质就是栈的切换,任务创建的本质就是伪造现场

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/219445.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于ssm的前后端分离鲜花销售系统论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本鲜花销售系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

java中的包

1.包的本质分析(原理) 包的本质 实际上就是创建不同的文件夹来保存类文件 2.一个文件中有两个类的i情况 package com.use;import com.xiaoqiang.Dog;public class Test {public static void main(String[] args) {Dog dog new Dog();System.out.println(dog); //com.xiaoqian…

最新版ES8的client API操作 Elasticsearch Java API client 8.0

作者:ChenZhen 本人不常看网站消息,有问题通过下面的方式联系: 邮箱:1583296383qq.comvx: ChenZhen_7 我的个人博客地址:https://www.chenzhen.space/🌐 版权:本文为博主的原创文章&#xff…

“京东API接口技术大揭秘:让你轻松驾驭电商开发!“

京东平台API接口技术贴 一、概述 京东平台提供了丰富的API接口,方便开发者进行应用开发。这些API接口涵盖了商品信息、订单管理、用户认证等多个方面,为开发者提供了强大的支持。本文将详细介绍京东平台API接口的技术细节和使用方法。 二、API接口概述…

回归预测 | MATLAB实现CHOA-BiLSTM黑猩猩优化算法优化双向长短期记忆网络回归预测 (多指标,多图)

回归预测 | MATLAB实现CHOA-BiLSTM黑猩猩优化算法优化双向长短期记忆网络回归预测 (多指标,多图) 目录 回归预测 | MATLAB实现CHOA-BiLSTM黑猩猩优化算法优化双向长短期记忆网络回归预测 (多指标,多图)效果…

CMake是什么

文章目录 一.什么是CMake二.CMake安装三.CMake一个HelloWord-的语法介绍3.1 PROJECT关键字3.2 SET关键字3.3 MESSAGE关键字3.4 ADD_EXECUTABLE关键字3.5 include_directories关键字3.6 aux_source_directory 四.语法的基本原则4.1 语法注意事项 五.内部构建和外部构建5.1 外部构…

dialog 在xml文件进行了自适应宽,但是失效了

如下图 讲述了为什么已经设置好了dialog的宽高 到了显示的时候就会失效的原因 解决方式 : 在自定的dialog中的onstart()方法中进行重新设置宽高 Window window getWindow();WindowManager.LayoutParams lp window.getAttributes();lp.height LinearLayout.La…

【操作系统的IO模型有哪些?】

操作系统的IO模型有哪些? 操作系统中的IO模型逐一拓展同步阻塞IO模型同步非阻塞IO模型IO复用模型信号驱动IO模型异步IO模型 操作系统中的IO模型 为了保护操作系统的安全,通过缓存加快系统读写,会将内存分为用户空间和内存空间两个部分。如果…

想学精MySQL,得先捋一捋高可用架构

📢📢📢📣📣📣 哈喽!大家好,我是【IT邦德】,江湖人称jeames007,10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】!😜&am…

ARM day8

1.题目&#xff1a;主机获取从机里面的温湿度数据&#xff0c;并打印出来 结果&#xff1a; 代码&#xff1a; main.c #include "iic.h"#include "si7006.h"void delay(int ms){int i,j;for(i0;i<ms;i){for(j0;j<2000;j);}}int main(){short tem;…

关于“Python”的核心知识点整理大全11

目录 ​编辑 6.2.4 修改字典中的值 6.2.5 删除键—值对 注意 删除的键—值对永远消失了。 6.2.6 由类似对象组成的字典 6.3 遍历字典 6.3.1 遍历所有的键—值对 6.3.2 遍历字典中的所有键 往期快速传送门&#x1f446;&#xff08;在文章最后&#xff09;&#xff1a; 6.…

【网络编程之初出茅庐】

前言&#xff1a;本章主要先讲解一些基本的网络知识&#xff0c;先把基本的知识用起来&#xff0c;后续会更深入的讲解底层原理。 网络编程的概念 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数…

网络基础(九):VLAN的概述及配置

目录 前言 一、分割广播域的方法 二、VLAN 1、VLAN的概述及优势 1.1VLAN的概述 1.2VLAN的优势 2、VLAN的种类 3、VLAN的三种端口类型 4、VLAN 的工作原理 4.1VLAN数据帧 4.2VLAN的范围 4.2VLAN的access类型工作原理 4.3VLAN的trunk类型工作原理 4.4VLAN的Hybird类…

CentOS7安装MySQL8.0

一、使用Yum安装 1. 使用wget下载MySQL的rpm包 wget https://repo.mysql.com//mysql80-community-release-el7-3.noarch.rpm2. 安装下载好的rpm包 yum localinstall mysql80-community-release-el7-3.noarch.rpm 3. 安装mysql&#xff08;该步可能出现问题&#xff09; yum…

JAVAEE-8-线程池

池 我们之前也接触过,比如说常量池,数据库连接池,线程池,进程池,内存池等等, 池的共性: 1.提前把要用的对象准备好 2.把用完的对象也不要立即释放,先留着以备下次使用 来提高效率!!! 最开始,进程能够解决并发编程的问题,因为频繁创建销毁进程的开销成本太大了,所以我们引…

新版Spring Security6.2案例 - Basic HTTP Authentication

前言&#xff1a; 书接上文&#xff0c;翻译官网Authentication的Username/Password这页&#xff0c;接下来继续翻译basic的这页&#xff0c;因为官网说的都是原理性的&#xff0c;这边一个小案例关于basic http authentication。 Basic Authentication 本节介绍 HTTP 基本身…

Unity 通过鼠标控制模拟人物移动和旋转视角

要通过鼠标控制并模拟人物移动和转换视角&#xff0c;将会使用射线检测、鼠标点击和鼠标水平移动&#xff0c;配合物体旋转和移动方法共同实现。 首先搭建个由一个Plane地板和若干cube组成的简单场景&#xff1a; 其次创建一个Capsule作为移动物体&#xff0c;并把摄像头拉到该…

腾讯云优惠券领取指南

腾讯云优惠券是降低云服务成本的有效手段&#xff0c;无论是新用户还是老用户&#xff0c;都可以通过下面方法领取到适合自己的优惠券。 1、腾讯云优惠券领取 领取入口&#xff1a;txy.ink 2、腾讯云优惠券介绍 腾讯云优惠券包括代金券和折扣券&#xff0c;代金券可以在结算…

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分

【CCF BDCI 2023】多模态多方对话场景下的发言人识别 Baseline 0.71 CNN 部分 概述CNN 简介数据预处理查看数据格式加载数据集 图像处理限定图像范围图像转换加载数据 CNN 模型Inception 网络ResNet 残差网络总结参数设置 训练 Train模型初始化数据加载训练超参数训练循环 验证…

Python 从入门到精通 学习笔记 Day04

Python 从入门到精通 第四天 今日目标 数据类型-又见str、数据类型-又见list 列表切片&排序&反转&循环、字典 数据类型 - 又见str 字符串定义 字符串是一个有序的字符的集合&#xff0c;用于在计算机里存储和表示文本信息 创建 a "Hello ,my name is Ha…