第3章第1节 两个固定任务之间的切…

云计算笔记—第一章 云计算的… 1.1计算模式的演变推进云计算的发展    主机计算模式:    在最开始,计算模式是由大型主机进行计算的。即由专门的人员管理大型主机。用户将自己的任务通过纸带等方式提交给主机管理员,管理员按照队列优先级进行运算,最后再将结果返回给用户。 C/S模式: 随着PC的出现,大家拥有了自己的电脑,计算模式转为C/S模式,即由客户端软件和服务器组成。这种模式充分发挥了PC的处理能力,一般将数据 阅读详情

源码请在https://github.com/ifreecoding/MbedRtos.git下载

第1节 两个固定任务之间的切换

程序的执行只与指令和数据相关,指令是不可修改的,编译后就确定了,能改变的只有数据,但指令需要对数据进行判断,走不同的指令分支,因此,如果我们需要控制程序的执行过程,不但需要编写出指令,还需要提供可方便使用的数据,操作系统任务切换的过程就是指令备份、恢复数据的过程。

通过前面的介绍,我们知道程序当前指令执行的结果只与R0~R15、CPSR这17个寄存器有关,只要我们能控制这17个寄存器,那么我们就可以控制程序的执行流程,这是实现任务切换的基础。

从C语言的角度来看,任务就是函数,只不过是在操作系统里,一个任务可以切换到其它任务,其实也就是一个函数可以切换到其它函数。当切换发生时,将正在执行的函数1的R0~R15、CPSR这17个寄存器临时保存起来,然后将希望执行的函数2的上次保存的数值恢复到R0~R15、CPSR这17个寄存器,这样芯片就从函数1切换到函数2运行了。当希望从函数2切换到函数1时,再将函数2的17个寄存器保存起来,恢复函数1的17个寄存器,芯片就又继续运行函数1了。这样便在函数1运行的中间插入了函数2,这就是任务切换,也就是所谓的“上下文切换”,函数1或函数2所在的最上层父函数调用的一系列函数就组成了任务,任务是从最上层父函数开始运行的。

图 12  任务切换过程

这种切换也可以在多个任务之间进行,至于什么时候切换,怎么控制切换,这就是操作系统要做的事情了。

下面我们将遵循着这一设计思路来编写一个最简单的切换过程——2个函数之间不停的互相切换,来验证任务切换过程中寄存器备份、恢复原理的正确性。

为了能看出任务切换的效果,我们设计2个函数TEST_TestTask1和TEST_TestTask2,这两个函数都是死循环,反复执行“打印消息—>延迟”的过程,我们可以通过打印信息来确认是哪个函数在执行,伪码如下:

TEST_TestTask1:

TEST_TestTask2:

while(1)

{

    打印“Task1 is running!”;

    延迟时间1秒;

}

while(1)

{

    打印“Task2 is running!”;

    延迟时间2秒;

}

如果没有函数切换功能,那么这样的函数只要一开始执行,它们就会一直死循环执行下去,不会给其它函数执行的机会,我们就只能看到只有一个函数在循环打印消息。如果能够按照上面是所讲述的切换原理发生函数切换,那么我们就应该能看到的是这2个函数是在循环交替打印。

现在我们需要一个函数,它具有备份、恢复这17个寄存器的功能,在TEST_TestTask1和TEST_TestTask2需要切换时就调用它,完成上下文切换。我们将这个函数命名为WLX_TaskSwitch,我们将WLX_TaskSwitch函数加入到TEST_TestTask1和TEST_TestTask2里:

TEST_TestTask1:

TEST_TestTask2:

while(1)

{

    打印“Task1 is running!”;

    延迟时间1秒;

    WLX_TaskSwitch();

}

while(1)

{

    打印“Task2 is running!”;

    延迟时间2秒;

    WLX_TaskSwitch();

}

我们将WLX_TaskSwitch函数设计为一个C函数,它仅对一些全局变量赋值,这些全局变量用来指明切换前函数的相关信息和切换后函数的相关信息。至于寄存器组备份、恢复的具体过程,由于是涉及到操作寄存器,因此只能使用汇编语言编写,将这个过程封装到由汇编语言编写的WLX_ContextSwitch函数里面来实现。

我们将使用C语言和汇编语言编写操作系统。C语言作为高级语言具有较好的可移植性,并且控制硬件方便,在嵌入式领域有极广泛的应用,但无法直接控制芯片的寄存器。汇编语言是与芯片内部硬件息息相关的,可控制寄存器,但编码困难,可移植性差。因此,本手册本着尽可能使用C语言的原则,在C语言无法实现或实现成本太大的情况下才使用汇编语言。

现在我们先来看看WLX_TaskSwitch函数,最左侧的5位数字是代码在源代码文件里的行号。Wanlix和Mindows的全部代码都可以从http://blog.sina.com.cn/ifreecoding网站免费下载,也可在网站内部的论坛上讨论。

00060  void WLX_TaskSwitch(void)

00061  {

00062      if(1 == guiCurTask)

00063      {

00064          

00065          gpuiCurTaskSpAddr = &guiTask1CurSp;

00066  

00067          

00068          guiNextTaskSp = guiTask2CurSp;

00069  

00070          

00071          guiCurTask = 2;

00072      }

00073      else //if(2 == guiCurTask)

00074      {

00075          gpuiCurTaskSpAddr = &guiTask2CurSp;

00076  

00077          guiNextTaskSp = guiTask1CurSp;

00078  

00079          guiCurTask = 1;

00080      }

00081  

00082      

00083      WLX_ContextSwitch();

00084  }

在这个函数里,我们用到了guiCurTask、guiTask1CurSp、guiTask2CurSp、gpuiCurTaskSpAddr、guiNextTaskSp这5个全局变量。guiCurTask用来指示当前运行的任务,在1和2之间不断变化。guiTask1CurSp保存的是TEST_TestTask1函数的寄存器组存储的内存地址,uiTask2CurSp保存的是TEST_TestTask2函数的寄存器组存储的内存地址,寄存器组备份、恢复时用的就是这两个全局变量指向的内存空间。gpuiCurTaskSpAddr用来存放guiTask1CurSp或guiTask2CurSp的地址,需要备份寄存器组的任务将指向它的寄存器组内存空间的变量的地址放入gpuiCurTaskSpAddr全局变量。guiNextTaskSp存放的是guiTask1CurSp或guiTask2CurSp,需要恢复寄存器组的任务将它的寄存器组内存空间地址放入guiNextTaskSp全局变量。这样,MDS_ContextSwitch函数在寄存器组备份、恢复时就可以通过gpuiCurTaskSpAddr和guiNextTaskSp分别找到备份和恢复寄存器组的内存空间了。

下面详细解释一下WLX_TaskSwitch函数:

00062行,对任务进行判断,如果当前运行的是任务1则进入此分支。

00065行,运行到此行,说明当前运行的是任务1,需要备份任务1的寄存器组数据,将任务1的全局变量guiTask1CurSp的地址存入全局变量gpuiCurTaskSpAddr中,准备供MDS_ContextSwitch函数使用。

00068行,需要还原任务2的寄存器组数据,将任务2的全局变量guiTask2CurSp保存到全局变量guiNextTaskSp中,准备供WLX_ContextSwitch函数使用。

00071行,准备从任务1切换到任务2,将保存当前运行任务ID的全局变量guiCurTask更新为将要运行的任务2。

00073~00080行与00062~00072行功能类似,不通的是从任务2切换到任务1的过程。

00083行,任务切换前的准备工作已经完成,调用WLX_ContextSwitch函数,开始寄存器组备份、恢复。

在介绍WLX_ContextSwitch函数之前,我们先设计一下保存寄存器组的内存结构,如图13所示:

图 13  寄存器组在内存中的结构

寄存器组中每个写着寄存器名字的位置用来保存对应的寄存器,可以保存R0~R14和CPSR寄存器,但没有为R15进行备份,这是因为Wanlix的切换过程是由函数主动调用WLX_ContextSwitch函数实现的,是写死在代码里的,在编译的时候编译器就会安排代码,在任务切换前将R15自动保存在R14中,这样我们只需要备份R14就足够了。

    这个寄存器组的位置存放在函数的栈中,当备份时,就从当前函数的SP栈指针指向的地址向栈增长的方向依次将寄存器存入,并更新SP栈指针,使之指向寄存器组中的“CPSR”,这也是一个压栈的过程,只不过是借用了函数的栈空间。当恢复时,从SP栈指针指向的寄存器组中依次恢复寄存器,并将SP栈指针恢复到函数切换前的所在位置,完成任务上下文切换。这时已经恢复的寄存器组所在的内存数据变为无效数据,当前函数运行时可能会压栈覆盖掉此空间的数据。

图 14  寄存器组在栈中的位置

    在这里还需要说明一下系统栈和任务栈的概念。软件在刚启动时都是运行在没有操作系统的环境下,这时候所有函数都会使用同一个栈(ARM7中中断模式除外),这个栈就叫做系统栈。启动操作系统后,程序就会以任务为功能单元运行,在建立任务时需要为每个任务分配一个栈供任务运行时使用,这个栈就称之为任务栈。软件进入操作系统后就不使用系统栈了,完全使用任务栈。由于操作系统不会重新返回到非操作系统状态下,因此系统栈中保存的数据也没有用处了,用户可以将系统栈的内存空间拿来另作它用。


 

图 15  系统栈和任务栈

有了前面的铺垫,我们来看看操作系统内核最核心的函数WLX_ContextSwitch:

00012      .func WLX_ContextSwitch

00013  WLX_ContextSwitch:

00014  

00015      @保存当前任务的堆栈信息

00016      STMDB  R13, {R0-R14}

00017      SUB    R13, R13, #0x3C

00018      MRS    R0, CPSR

00019      STMDB  R13!, {R0}

00020  

00021      @保存当前任务的指针值

00022      LDR    R0, =gpuiCurTaskSpAddr

00023      LDR    R1, [R0]

00024      CMP    R1, #0

00025      BEQ    GETNEXTTASKSP

00026      STR    R13, [R1]

00027  

00028  GETNEXTTASKSP:

00029      @获取将要运行任务的堆栈信息并运行新任务

00030      LDR    R0, =guiNextTaskSp

00031      LDR    R13, [R0]

00032      LDMIA  R13!, {R0}

00033      MSR    CPSR, R0

00034      LDMIA  R13, {R0-R14}

00035      BX     R14

00036  

00037      .endfunc

00012行,定义WLX_ContextSwitch函数。

00013行,这是一条汇编伪指令,只是一个标号,代表WLX_ContextSwitch函数的起始地址,并不生成可执行代码。

00015行,在GNU环境的汇编语言里,“@”符号代表注释符,编译时其后的所有字符都被注释掉,不生成任何代码,其存在只为程序提供说明性帮助。

00016行,这是该函数的第一条可执行语句,它将当前任务的R0-R14寄存器保存到寄存器组中,寄存器组的地址由SP寄存器指定。

00017行,更新数据入栈后的SP栈指针。

00018行,将当前任务的CPSR寄存器保存到R0中。

00019行,将R0寄存器存入寄存器组,也就是把CPSR寄存器的内容存入寄存器组,并更新SP寄存器。00016~00019行所使用的汇编指令并不会改变CPSR寄存器的内容,因此00019行这条指令保存的R0就是当前任务进入WLX_ContextSwitch函数前的CPSR寄存器的数值。

00022行,获取全局变量gpuiCurTaskSpAddr的地址。

00023行,获取全局变量gpuiCurTaskSpAddr的内容,也就是存放当前任务的寄存器组的全局变量guiTaskXCurSp(当前任务为1则guiTaskXCurSp为guiTask1CurSp,当前任务为2则则guiTaskXCurSp为guiTask2CurSp)。

00024行,将gpuiCurTaskSpAddr全局变量的内容与0做比较。在非操作系统状态下全局变量gpuiCurTaskSpAddr为0,无需保存寄存器组数据。

00025行,如果gpuiCurTaskSpAddr全局变量为0,则跳转到GETNEXTTASKSP标志所在地址,准备进入操作系统状态,但还没有任务在运行,因此,不需要保存非操作系统状态下的SP栈指针。如果不为0则代表已经进入操作系统状态,不执行本行,执行00026行。

00026行,将当前任务的SP栈指针存入当前任务的guiTaskXCurSp全局变量中,下次运行时可借此找到任务的栈指针,并根据栈指针从寄存器组中恢复出寄存器的数值。

00030行,获取全局变量guiNextTaskSp的地址。

00031行,获取全局变量guiNextTaskSp的内容,也就是存放将要运行任务的寄存器组的地址,将寄存器组的地址存入SP中。

00032行,根据SP从寄存器组中恢复将要运行任务的CPSR寄存器,恢复到R0中。

00033行,将R0保存到CPSR中,也就是恢复了将要运行任务的CPSR寄存器。

00034行,恢复R0-R14寄存器,其中包含了SP寄存器,因此不再需要额外更新SP寄存器。

00035行,跳转到将要运行的任务。至此,已经完成了2个任务的寄存器出入栈工作,芯片当前工作的寄存器已经从切换前运行的任务全部换成了切换后将要运行的任务,寄存器数据已经全部处理完了,只要能将PC指针正确跳到将要运行任务上次切出去那一时刻的位置就可以了。此时LR寄存器中保存的就是将要运行任务的上次切出去的地址,跳转到LR,完成2个任务切换的最后一步。

上面实现了任务的切换过程,但是还有一些事情需要解决,那就是测试函数TEST_TestTask1和 TEST_TestTask2第一次运行时,栈中的寄存器组是空的,无法进行寄存器组恢复,也就无法切换了。因此,我们需要一个初始化函数,来为第一次运行的任务初始化它的寄存器组栈空间。这个任务初始化函数是WLX_TaskInit,它只需要在本任务的栈内为寄存器组赋初值就可以了,至于每个寄存器应该赋什么初值,我们可以分析一下。

由于任务是第一次运行,所有一切都是空的,函数不会从R0~R12寄存器读数据使用,因此可以将这些寄存器全部置0。SP是栈指针,所以需要将任务的栈顶赋给SP。LR是返回地址,需要通过跳转到LR去第一次执行这个函数,而函数名就是函数的第一条指令所在的地址,函数名是指针,因此,将函数名存入存入LR中。

具体实现过程来看下面来看代码:

00020  void WLX_TaskInit(U8 ucTask, VFUNC vfFuncPointer, U32* puiTaskStack)

00021  {

00022      U32* puiSp;

00023  

00024      

00025      puiSp = puiTaskStack;               

00026  

00027      *(--puiSp) = (U32)vfFuncPointer;    

00028      *(--puiSp) = (U32)puiTaskStack;     

00029      *(--puiSp) = 0;                     

00030      *(--puiSp) = 0;                     

00031      *(--puiSp) = 0;                     

00032      *(--puiSp) = 0;                     

00033      *(--puiSp) = 0;                     

00034      *(--puiSp) = 0;                     

00035      *(--puiSp) = 0;                     

00036      *(--puiSp) = 0;                     

00037      *(--puiSp) = 0;                     

00038      *(--puiSp) = 0;                     

00039      *(--puiSp) = 0;                     

00040      *(--puiSp) = 0;                     

00041      *(--puiSp) = 0;                     

00042      *(--puiSp) = MODE_USR;              

00043  

00044      

00045      if(1 == ucTask)

00046      {

00047          guiTask1CurSp = (U32)puiSp;

00048      }

00049      else //if(2 == ucTask)

00050      {

00051          guiTask2CurSp = (U32)puiSp;

00052      }

00053  }

00020行,函数定义,ucTask入口参数确定需要初始化的函数;vfFuncPointer入口参数是需要初始化任务的函数;puiTaskStack入口参数是这个任务所使用的任务栈指针,需要是栈顶满栈指针。

00025行,将puiSp变量指向任务栈栈顶。

00027行,将函数指针存入寄存器组的LR位置。当该任务第一次运行,恢复寄存器时,该函数指针就会被恢复到LR寄存器中,当程序跳转到LR时也就开始运行该函数了。

00028行,将任务栈栈顶地址存入寄存器组的SP位置。当该任务第一次运行时,栈内初始化的数据全部取出后,任务栈已经空了,此时SP应该指向栈顶,这时候函数才开始运行。将任务栈栈顶地址存入SP中,当该任务第一次运行,恢复寄存器时,任务栈栈顶地址就会被恢复到SP寄存器中,该任务就会从SP所指的地址开始存放栈数据,也就到达了控制任务栈的目的。

00029~00041行,任务刚创建时R0~R12寄存器中数据为无效值,因此此处全部填0。

00042行,将USR模式存入寄存器组的CPSR位置。这样当该任务第一次运行时,USR模式就会被恢复到CPSR寄存器中,任务就会从USR模式开始启动。MODE_USR是一个宏定义,其值为0x10,可以参考图5,代表USR模式。函数第一次运行时CPSR里面NZCV等各种状态均为0,因此此处CPSR寄存器中的状态位均被初始化为0。

00045~00052行,将每个任务的栈信息保存到它对应的全局变量中,供任务切换时使用。

创建任务所使用的函数都是通过WLX_TaskInit函数以这种隐式的方式开始运行的,并没有直接调用。在创建任务时就确定了任务所使用的根函数以及栈等其它信息,后续随着我们对操作系统的不断完善,还会有更多的信息被加入到任务中来,这也是任务不同于函数的地方,任务比函数拥有更多的信息,这样,操作系统才可以利用这些信息更方便的管理任务调度。

最后需要使用WLX_TaskStart函数从非操作系统状态开始进入到操作系统状态。WLX_TaskStart函数很简单,就是对上面介绍过的全局变量做一些初始化,然后调用任务切换函数WLX_TaskSwitch,WLX_TaskSwitch函数再调用WLX_ContextSwitch函数,将任务初始化函数WLX_TaskInit初始化的参数恢复到寄存器中开始运行,开始了第一个任务的运行,这样就进入到了操作系统状态。

WLX_TaskStart函数很简单,不再详细解释,代码如下:

00091  void WLX_TaskStart(void)

00092  {

00093      

00094      gpuiCurTaskSpAddr = (U32*)NULL;

00095  

00096      

00097      guiNextTaskSp = guiTask1CurSp;

00098  

00099      

00100      guiCurTask = 1;

00101  

00102      WLX_ContextSwitch();

00103  }

现在我们已经完成任务切换所需要的全部代码,在main函数里首先初始化硬件,然后调用WLX_TaskInit函数对2个任务进行初始化,最后调用WLX_TaskStart函数启动任务调度,这2个任务就开始交替执行了,交替向串口打印数据。 

00014  S32 main(void)

00015  {

00016      

00017      DEV_HardwareInit();

00018  

00019      

00020      WLX_TaskInit(1, TEST_TestTask1, TEST_GetTaskInitSp(1));

00021      WLX_TaskInit(2, TEST_TestTask2, TEST_GetTaskInitSp(2));

00022  

00023      

00024      WLX_TaskStart();

00025  

00026      return 0;

00027  }

测试函数TEST_TestTask1和 TEST_TestTask2运行时函数调用关系如下:

—>TEST_TestTask1

  —>WLX_TaskSwitch

    —>WLX_ContextSwitch

      —> TEST_TestTask2

        —>WLX_TaskSwitch

          —>WLX_ContextSwitch

            —>TEST_TestTask1

              —>……

我们来看看最终的效果:

图 16  两个任务交替执行

通过图16我们可以看到这两个任务交替的运行,在代码里我们并没有直接运行TEST_TestTask1和TEST_TestTask2函数,而是采用操作系统创建任务、切换任务的原理运行这两个函数的。从串口工具的输出来看,已经完全实现了我们的设计!

读者还可以观看串口输出的视频,请登陆ifreecoding_新浪博客网站下载,该视频动态的记录了两个任务在串口工具上输出的过程,可以看到TEST_TestTask1任务执行1秒后切换到TEST_TestTask2任务,TEST_TestTask2任务执行2秒后切换到TEST_TestTask1任务,如此循环。

还有一点需要说明,某些函数具有返回值,但我并没有完全判断这些返回值,当不影响软件功能时我一般是采用viod屏蔽了函数返回值,以突出本手册介绍的重点。但我建议,如果你是在做一个项目的话,最好能判断函数的返回值,以增强系统的健壮性。

Markdown排版进阶:精准缩进方案全解析 本文深入解析Markdown排版中的精准缩进方案,涵盖LaTeX空格、HTML实体、列表缩进和语义化标签等多种方法。通过实战案例和工具推荐,帮助开发者解决跨平台兼容性问题,提升技术文档的可读性和专业性,特别适合编写API文档、数学推导等需要精确排版的场景。 阅读详情

相关推荐

2026 AI工程核心:模型可靠性(Model Reliability)的落地实践

模型可靠性(Model Reliability)已成为高风险AI系统交付的刚性工程要求,它超越传统准确率指标,聚焦行为稳定性、环境鲁棒性与决策可追溯性三大可测量维度。其技术原理源于对数据漂移、推理扰动和部署异构性的根因建模,通过动态可信域、可靠性感知训练与RaaS(Reliability-as-a-Service)等机制实现闭环治理。该能力直接支撑金融风控、医疗影像、工业质检等强合规场景的SLA达标,是AI从实验室走向产线的核心门槛。本文基于7个真实生产项目,详解如何将Model Reliability转化

weixin_34378767的博客 564

谈谈rtos中多任务之间是如何进行转换(Cortex-M3)

实时操作系统,多任务之间的转换

m0_52316356的博客 720

GPU多任务调度

1、在cpu端使用多线程利用系统中的多gpu(如果系统中有n个gpu,可以指定任意个gpu参与计算)执行多个任务(可以是任意个),可以自由设定任务数量和任务队列大小。2、在同一个gpu上以不同的顺序执行多个任务(每个任务可能有多个kernel函数),并且统计不同的顺序序列,每个任务执行的时间和每个任务所有kernel执行时间;同时也可以每次执行一个任务序列。3、和2中的类似,主要实现单gpu上所有任务的kernel不同的执行序列对执行时间的影响。 以上实现的程序,主要用来考察不同的执行顺序(包括任务级和核函数级的各种不同顺序)对gpu功耗的影响,程序中部分参数怎么改、怎么添加任务,可以给我留言。

第3章第2节 任意任务间的切换

源代码请在https://github.com/ifreecoding/MbedRtos.git下载 第2节任意任务间的切换 上一节我们使用2个固定的任务验证了操作系统任务切换的功能,但这些代码并不具有通用性,如果要扩充其它任务,就必须修改操作系统函数,这显然是不可接受的。操作系统作为独立于用户

ifreecoding的博客 591

基因树构建全流程:从OrthoFinder到iTOL的保姆级教程(附兰科植物案例)

本文提供了一份从OrthoFinder到iTOL的基因树构建全流程保姆级教程。详细解析了利用OrthoFinder构建物种树、IQ-TREE构建无根基因树、Notung进行树调和生根,以及最终在iTOL中实现专业可视化的每一步操作与实战经验,并以兰科植物NB-ARC基因家族为例进行演示,帮助研究者系统掌握基因树分析方法。

tequila的博客 523

逆向攻防世界CTF系列40-ReverseMe-120

注意sub_401000main中只传了两个参数,但是跟进后发现四个(可能是隐式传递,放在寄存器里传递了),调试发现a4是输入长度,与最终的you_know_how_to_remove_junk_code比较的是v12(但是并没有被传入),开始怀疑是不是隐式传递了。_mm_xor_si128用于计算128位(16字节)的按位异或,然后通过v14控制循环结束的条件,可以看到v14增长的步长为16,而且通过上面得到的flag值解码得到的字符串为32个字节大小,正好是16的整数倍。

LH1013886337的博客 1410

今天在学习物化视图时,遇到了ORA-12034问题

今天在学习物化视图时,遇到了ORA-12034问题,场景如下: 物化视图日志站点:     1、对于T1表创建了物化视图日志:       create materialized view log on t1;      2、又创建了一个本地的物化视图:       create materialized view tmv1       refresh fast       star

haofeifei的专栏 6008

QT插件学习系列(一) 初识QtPlugin

QT插件学习系列(一) 初识QtPlugin 1、 概述 为什么我们要学习插件化,其和 windows 导出 dll 有什么区别呢? 导出的动态库如果缺失,程序不能运行。但插件可以。 同一套代码,即可分别在 windows 下和 linux 下生成插件。 QT 本身提供两种插件支持,一种称为高级 API,一种称为低级 API。 高级API的作用是扩展 QT 程序本身,需要子类化 QT 提供的插件基类,例如现有的 QTSqlDriver,因此你可也以编写自己的 QTStyle 扩展 QT。 低级

L_Andy的专栏 3113

Fatal signal 11 (SIGSEGV), code 1, fault addr 0xfffffff8 in tid 3851 (cameraserver)

--------- beginning of crash 01-02 00:09:09.871 F/libc ( 3851): Fatal signal 11 (SIGSEGV), code 1, fault addr 0xfffffff8 in tid 3851 (cameraserver) 01-02 00:09:09.872 W/ ( 432): debuggerd: ...

wangjicong_215的博客 1093

Win10家庭版如何添加本地用户和组

在众多Windows版本中,Windows 10以其丰富的功能和卓越的性能吸引了大量用户。然而,对于使用Win10家庭版的用户来说,他们可能会遇到一些功能上的限制,特别是在用户和组管理方面。虽然家庭版在某些高级功能上有所缩减,但我们仍然可以通过一些技巧来实现对本地用户和组的管理。本文将详细介绍如何在Win10家庭版中添加本地用户和组,以便用户能够更好地管理计算机资源。

nntxthml的博客 4556

rv1126 SDK编译【2020-0912版本SDK包】

rv1126 sdk 编译

学习记录 1万+

Java多线程  线程池

原文地址: 线程池">Java多线程  线程池作者:大关 本文由作者收集整理所得,作者不保证内容的正确行,转载请标明出处。 作者:关新全 Java多线程  线程池 2.1 ThreadPoolExecutor类          ThreadPoolExecutor类是ExecutorService的一个实现。一般使用线程池可以减少任务调用的开销,对执行大量异步任务时提供增强的性能,并能提供绑

jiqiujia的专栏 576

STM32CubeMX + HAL库避坑指南:为什么你的代码总在SystemClock_Config卡住?

本文深入剖析了STM32开发者在使用STM32CubeMX和HAL库时,代码在SystemClock_Config函数中卡死的根本原因。文章不仅指出硬件时序、Flash延迟配置等常见陷阱,更提供了从硬件设计、软件容错到深度调试的系统性解决方案,帮助开发者构建鲁棒的时钟初始化策略,彻底告别启动卡顿问题。

bb456的博客 928

POJ   1182  食物链

由于前几天一直没搞懂并查集,现在搞懂了才做出来,所以交得有点晚了 通过率:28%             难度:较难 食物链 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 17611   Accepted: 4934 Description 动物王国中有三类动物A,B,

算法之道 364

物联网与人工智能项目实战:构建智能农业监控系统

智能农业监控系统通过物联网技术实时监测农业环境中的各项数据(如土壤湿度、温度、光照强度等),并利用人工智能技术分析和预测农业生产的趋势,进而为农民提供决策支持。系统的核心目标是提高农作物的产量和品质,同时减少资源浪费,优化农田管理。环境监测:实时采集农田的温湿度、土壤湿度、光照等环境数据。作物生长监测:通过传感器和图像识别技术监测作物的生长状况。智能灌溉系统:根据土壤湿度和气候条件自动调整灌溉量。病虫害检测与预警:利用人工智能和图像识别技术自动检测作物病虫害,并及时给出预警。数据分析与决策支持。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 709

配置 SAP Web Dispatcher 的实战指南:打通 SAP Fiori、SAP Gateway 与前端系统的关键一跳

SAP Web Dispatcher 在 Fiori 架构中扮演关键角色,作为统一入口处理 HTTPS 请求、实现安全代理和流量分发。它将分散在不同系统的访问路径统一收口,解决浏览器同源策略限制,集中管理 HTTPS 终止和客户端信息传递。典型配置中,Web Dispatcher 监听 443 端口,根据路径将 OData 请求转发至 Gateway 系统,静态资源请求送至前端服务器。SSL 相关配置(如 PSE 文件)确保安全通信,而路由规则(wdisp/system_xx)决定请求转发目标。实施时需注意

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 103

2 时间管理和内存管理

时间管理 uC/OS-II的时间管理是通过定时中断来实现的,该定时中断一般为10毫秒或100毫秒发生一次(这个时间片段是OS的作者推荐的,大家可以参考邵贝贝翻译的《嵌入式实时操作系统ucos-II》这本书),时间频率取决于用户对硬件系统的定时器编程来实现。中断发生的时间间隔是固定不变的,该中断也成为一个时钟节拍。这里隐含的意思就是你选择的芯片如果想使用UCOS系统,前提条件一定要有一个Timer

不积跬步无以至千里,千里之行始于足下! 797

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

S3C6410的启动代码分析 一

本文开始第一篇,启动代码的编写,注意,仅仅是启动代码,并不是bootloader,因为只有boot,没有loader。        第一要明确:CPU上电之后,会从某个固定地址执行指令。ARM结构的CPU从地址0x00000000开始执行指令,MIPS结构的CPU将会从0XBFC00000取第一条指令并执行。(注意,这个地址是CPU的统一编址,需要把存储器件ROM或者flash映射到这个地址上

不积跬步无以至千里,千里之行始于足下! 1068

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Android 开发 UI 规则

Android的官方开发者博客发了一份幻灯片,介绍了一些Android UI设计的小贴士,在这里以看图说话的形式发出来。 Don’t:  1、不要照搬你在其它平台的UI设计,应该让用户感觉是在真正使用一个 Android 软件,在你的商标显示和平台整体观感之间做好平衡 2、不要过度使用模态对话框 3、不要使用固定的绝对定位的布局 4、不要使用px单位,使用dp或者为文本使用sp 5、不要使

133

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

操作系统与网络 2019-1-29

1.继续完成进程间通信 1.1 完成接收端的内存文件映射接受 1.在 Recv 端添加一个按钮 接收映射文件内存 ; 2.在 CRecvDlg::OnBnClickedButton3 函数中,打开内存文件映射: HANDLE h_mapping_file = ::OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, L"HH") ; 3.获取成功则获取内存空间: ...

weixin_42896619的博客 171

漫谈信号与系统系列 (转载)

原文地址:(转载)">漫谈信号与系统系列 (转载)作者:lazyboy入门第一课 什么是卷积 傅利叶变换 拉普拉斯变换 漫谈 信号与系统 入门第一课 什么是卷积 卷积有什么用 什么是傅利叶变换 什么是拉普拉斯变换 引子 ----------------------------------------[引用和转载请注明本文blog出处,谢谢] 很多朋友和我一样,工科电子类专业,学了一堆信号方面的课

windworst的专栏 674

【软考系统架构设计师】计算机网络章节习题集

【软考系统架构设计师】计算机网络章节习题集

千月星跡 1071

CC251x USB端点双缓冲与自动化配置实战指南

USB通信作为嵌入式系统与主机交互的核心接口,其性能优化关键在于对端点FIFO(先进先出队列)的高效管理。其原理在于通过合理的缓冲区策略,协调主机轮询与设备处理之间的速度差异,从而提升数据吞吐率与实时性。双缓冲技术通过提供两个独立的缓冲区,实现了数据准备与总线传输的并行化,是解决高速数据流场景下数据丢失问题的关键技术。AutoSet与AutoClear等自动化特性则进一步简化了固件流程,降低了CPU开销。这些技术在音频采集、实时传感器数据上报、HID设备等对稳定性和实时性要求高的应用场景中价值显著。本文以T

weixin_34082854的博客 507

【第七章-redux】

Redux是一个专为状态管理设计的JavaScript库,主要用于React等前端框架中集中管理共享状态。其核心概念包括Action(描述状态变化)、Reducer(纯函数处理状态变更)和Store(连接状态与操作的中央仓库)。Redux适用于多组件状态共享或通信场景,但建议谨慎使用。 工作流程:组件通过dispatch发送Action,Store调用Reducer生成新状态,触发订阅者更新。核心API包括createStore创建Store、combineReducers合并多个Reducer、apply

nunumaymax的博客 241

React详细笔记

初始化阶段:由 ReactDOM.render()触发—初次渲染render()componentDidMount()===>一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息更新阶段:由组件内部 this.setState()或父组件重新 render 触发render()===>必须要用卸载组件:由 ReactDOM.unmountComponentAtNode()触发。

2502_91080356的博客 205
上一篇: 第3章 Wanlix操作系统
下一篇: 第3章第2节 任意任务间的切换
ifreecoding
博客等级 码龄15年 148粉丝 33原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值