Hello World 4

本文详细解释了Linux系统中内存映射的概念,通过分析mmap系统调用过程,介绍了如何将文件映射到进程的虚拟内存地址空间。并探讨了映射过程中涉及的关键数据结构和映射的具体步骤。

本文装载自:http://www.cnblogs.com/xuqiang/archive/2010/03/29/1953689.html

微笑

开始”映射“之旅,在上面的讲解中经常提及到“”映射“的概念,那到底什么是映射?

一个线性区可以和磁盘上的某个文件关联或者是某个文件的一部分来关联,这也就意味着内核把线性区的访问页内某个字节的访问转换成对于文件的中相应页面的访问,这种技术称之为“内存映射”。

使用strace hello得到:

...

open("/usr/lib/locale/en_AU.UTF-8/LC_TIME", O_RDONLY) = -1 ENOENT (No such file or directory)

open("/usr/lib/locale/en_AU.utf8/LC_TIME", O_RDONLY) = 3

fstat64(3, {st_mode=S_IFREG|0644, st_size=2454, ...}) = 0

mmap2(NULL, 2454, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb80bf000

...
在hello程序加载时,linux kernel使用mmap来实现文件的映射。在说明文件映射的原理之前,首先来看映射所需要的数据结构:下面的图片摘自《深入理解linux内存管理》,同时参见 http://blog.chinaunix.net/u3/99507/showart_2121121.html

 

看linux内核很容易被struct address_space 这个结构迷惑,它是代表某个地址空间吗?实际上不是的,它是用于管理文件(struct inode)映射到内存的页面(struct page)的;与之对应,address_space_operations 就是用来操作该文件映射到内存的页面,比如把内存中的修改写回文件、从文件中读入数据到页面缓冲等。

也就是说address_space结构与文件的对应:一个具体的文件在打开后,内核会在内存中为之建立一个struct inode结构,其中的i_mapping域指向一个address_space结构。这样,一个文件就对应一个address_space结构,一个 address_space与一个偏移量能够确定一个page cache 或swap cache中的一个页面。因此,当要寻址某个数据时,很容易根据给定的文件及数据在文件内的偏移量而找到相应的页面。

 

task_struct--->
                        vm_area_struct--->
                                                           file--->
                                                                       address_space--->
                                                                                                        pages(该文件实际指向的内存页面)
当一个进程需要创建一个内存映射时,发出系统调用mmap,mmap系统调用返回的是第一个单元位置的线性单元。mmap函数的主要操作主要是
初始化进程task_struct结构的vm_file字段,具体就是:调用文件的file结构初始化vm_file字段,调用generic_file_mmap函数。
generic_file_mmap函数执行下面的步骤:
使用generic_file_vm_ops表地址初始化vm_area_struct结构的vm_ops字段,其实在这个表中除了nopage和populate方法之外,其他的方法全是空的。nopage方法是由函数filemap_nopage来实现,populate是有filemap_populate来实现。
其实真个的“映射”的工作已经完成,在这个时候,hello文件其实还没有被加载到内存中,当试图访问没有在内存中页面时,发生缺页中断,然后调用上面再vm_ops字段中的nopage方法,来将实际的文件读到内存中。
总结上面的过程的话:我将“映射”大致分为两个过称,首先仅仅是标记了在进程的某个虚拟内存地址上”对应“了磁盘上的某个文件(在这个情景中是hello可执行程序),然后内核开始执行其他的事情,当这个进程试图开始对刚才标记的页面来寻址时,产生缺页异常,然后才是实际的内存(ram)的分配,并将映射的文件加载到内存中,注意的是判断一个页框是否是属于某个进程需要看看对应的页表是否设置了,在上面的缺页处理中,在函数vm_ops->nopage()函数中调用do_no_page函数,在do_no_page函数中实际才设置页表项。
<think> 我们正在分析FreeRTOS多任务输出序列问题。根据用户提供的输出: "Hello world1 Hello world2 Hello world4 Hello world3 I (326) main_task: Returned from app_main() Hello world1 Hello world4 Hello world2 Hello world3 ..." 注意:用户代码中只创建了三个任务(myTask1, myTask2, myTask3),但输出中出现了"Hello world4",因此我们需要考虑实际代码可能包含四个任务(包括myTask4)。但用户提供的代码片段中没有myTask4,所以我们需要基于已有信息推断。 假设实际代码中创建了四个任务,优先级分别为: - myTask1: 优先级1 - myTask2: 优先级3(最高) - myTask3: 优先级2 - myTask4: 优先级? (假设为优先级4,比myTask2的3更高?) 然而,用户提供的代码中只有三个任务,但输出有四个任务。这里可能是用户描述有误,或者我们需考虑其他因素(例如,main_task本身也是一个任务?)。但根据输出,我们看到main_task打印了一条消息。 但是,为了解释输出,我们假设实际代码中有四个任务(myTask1, myTask2, myTask3, myTask4)和一个主任务(app_main)。 输出序列分析: 第一行输出:Hello world1 Hello world2 Hello world4 Hello world3 I (326) main_task: Returned from app_main() 然后:Hello world1 Hello world4 Hello world2 Hello world3 ... 注意:FreeRTOS是抢占式调度,优先级高的任务会抢占优先级低的任务。 步骤1:任务创建 在app_main中,按顺序创建了四个任务(假设myTask4也在其中),每个任务都每隔1秒打印一次信息并延迟。 任务优先级(根据用户提供的代码片段): myTask1: 1 myTask2: 3(最高) myTask3: 2 myTask4: 假设为高于3?但输出中myTask2和myTask4的打印顺序有交替,所以可能优先级相同? 步骤2:初始执行顺序 当app_main创建任务时,每次创建后,由于任务创建函数可能会触发调度(如果新任务优先级高于当前任务优先级,则新任务会立即运行)。 假设创建顺序: 创建myTask1(优先级1)-> 不会抢占当前任务(主任务优先级默认是0?但主任务运行在app_main,通常创建任务时不会立即调度,直到调用vTaskStartScheduler或主动让出) 创建myTask2(优先级3)-> 由于优先级3高于当前主任务(假设主任务优先级为0),则创建后立即切换到myTask2执行。 myTask2打印"Hello world2",然后延迟1秒(进入阻塞状态)。 然后创建myTask3(优先级2)-> 优先级2低于当前正在运行的主任务?但注意,此时主任务被myTask2抢占了,现在myTask2阻塞,所以回到主任务继续执行。 myTask3的优先级2高于主任务,但低于myTask2,所以创建后不会立即运行(因为当前主任务正在运行,而主任务优先级0,所以应该立即切换?)实际上,创建任务时,如果新任务的优先级高于当前运行任务的优先级,则会立即切换到新任务。而当前是主任务(优先级0)在运行,创建myTask3(优先级2)高于0,所以会立即切换到myTask3。 myTask3打印"Hello world3"?不对,因为输出中第一个打印的是Hello world1。 这里需要明确:在FreeRTOS中,创建任务后,如果新任务优先级高于当前任务,则新任务会立即运行。否则,不会切换,直到当前任务主动让出或时间片用完。 输出序列的第一个部分是: "Hello world1 Hello world2 Hello world4 Hello world3" 那么,我们假设实际创建顺序和优先级是: 任务1: prio1 任务2: prio3 任务3: prio2 任务4: prio4(最高) 创建顺序可能是: xTaskCreate(myTask1, ... , 1, ...); // 创建后,因为优先级1>0(主任务优先级),所以立即运行myTask1?但注意:FreeRTOS在调用vTaskStartScheduler之前任务不会运行?不,实际上,在调用xTaskCreate时,调度器可能已经启动了?用户代码中并没有显示启动调度器,但通常app_main是在调度器启动后运行的。 在ESP-IDF中,app_main函数运行在main_task(优先级为1)中。所以,当我们在app_main中创建任务时,新任务的优先级如果高于1,则会在创建后立即被调度。 但是,输出中首先打印的是"Hello world1",所以我们假设创建的第一个任务(myTask1)立即运行了?但myTask1优先级为1,和主任务相同(或主任务优先级为1),所以创建后不会立即抢占主任务(因为同优先级,需要时间片切换)。然而,在创建完所有任务之前,调度器不会进行时间片切换?实际上,在创建任务的过程中,调度器是开启的(因为app_main已经在运行了),所以创建任务后,如果新任务优先级等于或高于当前任务,则可能被立即调度(取决于具体时机)。 由于这个时序问题,我们假设: 1. 创建myTask1(优先级1): 同主任务优先级(1),所以不会抢占主任务,它进入就绪队列。 2. 创建myTask2(优先级3): 高于主任务(1),所以创建后立即切换到myTask2。 myTask2打印"Hello world2",然后调用vTaskDelay(阻塞)。 myTask2阻塞后,调度器选择就绪队列中优先级最高的任务:主任务(优先级1)继续运行(因为myTask1优先级也是1,但主任务被中断后恢复,所以继续运行?或者同优先级轮转?)。 3. 主任务继续创建myTask3(优先级2): 高于主任务(1),所以创建后立即切换到myTask3。 myTask3打印"Hello world3",然后阻塞(vTaskDelay)?但输出中第一个序列是“Hello world1”紧接着“Hello world2”,这里出现了矛盾。 因此,我们需要重新考虑。 另一种可能:在输出中,第一个序列是在所有任务创建完成后才开始打印的。因为在app_main中创建任务时,虽然创建了任务,但所有任务都在就绪状态,但是主任务(app_main)还没有结束。只有当app_main函数返回后,主任务才会被删除(或阻塞),然后其他任务开始运行。 在输出中,有一个关键信息:“I (326) main_task: Returned from app_main()”,这意味着app_main函数执行完毕并返回了。 所以,在app_main返回之前,所有任务都已经创建,但可能还没有机会运行(因为主任务优先级为1,而新创建的任务优先级分别为1,3,2,其中优先级3和2的都高于1,所以会在创建后立即抢占?) 实际上,在创建优先级为3的任务(myTask2)时,由于3>1(主任务优先级),所以会立即切换到myTask2执行。然后myTask2执行,打印“Hello world2”,然后调用vTaskDelay(1000)进入阻塞状态(阻塞时间为1000ms)。这时,调度器会重新调度,选择就绪队列中优先级最高的任务。此时,主任务(优先级1)和myTask1(优先级1)以及myTask3(优先级2)都在就绪队列。由于myTask3优先级2最高,所以运行myTask3?但输出中紧接着是“Hello world4”,所以这里我们需要假设myTask4存在。 由于输出中出现了四个任务,我们假设用户实际创建了四个任务(包括myTask4),并且myTask4的优先级为4(最高)。那么: 创建顺序: app_main(优先级1)创建: 1. myTask1: 优先级1 -> 不会抢占(同优先级),加入就绪队列。 2. myTask2: 优先级3 -> 高于1,立即抢占,运行myTask2。 myTask2打印“Hello world2”,然后调用vTaskDelay(1000)阻塞(进入阻塞状态)。 此时,就绪队列中有:主任务(1)、myTask1(1)、myTask2(3)被阻塞,接下来创建myTask4。 3. myTask4: 优先级4 -> 高于当前任务(当前是主任务被中断,现在恢复主任务?但创建myTask4时,当前运行的任务是主任务(因为myTask2阻塞后,调度器选择主任务继续运行)) 创建myTask4(优先级4)时,因为优先级4>1,所以立即切换到myTask4。 myTask4打印“Hello world4”,然后阻塞(vTaskDelay)。 然后,主任务继续运行(因为myTask4阻塞了)。 4. 创建myTask3: 优先级2 -> 高于主任务(1),所以立即切换到myTask3。 myTask3打印“Hello world3”,然后阻塞。 5. 此时,所有任务创建完成,主任务继续运行(就绪队列中还有主任务和myTask1,同优先级),主任务执行完毕,返回,打印“Returned from app_main()”,然后主任务被删除(或进入空闲状态?)。 6. 接下来,所有任务都处于阻塞状态(除了myTask1),所以调度器会运行myTask1(优先级1)?因为此时其他高优先级任务都在阻塞状态,而myTask1就绪。 但是,第一个序列的输出是:“Hello world1 Hello world2 Hello world4 Hello world3 I (326) main_task: Returned from app_main()” 按照上面的执行顺序: 先打印“Hello world2”(在myTask2中) 然后打印“Hello world4”(在myTask4中) 然后打印“Hello world3”(在myTask3中) 然后主任务结束,打印“Returned from app_main()” 但是,这里缺少了“Hello world1”。实际上,在第一个序列中,“Hello world1”是第一个打印的。 因此,这个顺序不对。 重新调整:可能myTask1在某个时刻被运行了。我们重新考虑: 在创建myTask1(优先级1)后,由于同优先级,主任务继续运行,然后创建myTask2(优先级3),立即抢占,运行myTask2,打印“Hello world2”,然后阻塞(1000ms)-> 此时,由于myTask2阻塞,调度器选择就绪队列中优先级最高的任务:主任务(因为此时还没有创建其他更高优先级的任务)继续运行,然后创建myTask4(优先级4),立即抢占,打印“Hello world4”,然后阻塞。然后主任务继续,创建myTask3(优先级2),立即抢占,打印“Hello world3”,然后阻塞。这时,主任务继续运行(所有任务创建完毕),然后主任务返回,打印“Returned from app_main()”。然后,主任务结束。此时,就绪队列中只有myTask1(优先级1)处于就绪状态(因为其他任务都在阻塞状态),所以运行myTask1,打印“Hello world1”。这样,第一个序列的输出应该是: Hello world2 Hello world4 Hello world3 Returned from app_main() Hello world1 但用户提供的输出序列是: "Hello world1 Hello world2 Hello world4 Hello world3 I (326) main_task: Returned from app_main()" 这里“Hello world1”在开头。 所以,另一种可能是:在创建myTask1后,虽然优先级相同,但可能由于时间片到了,在创建下一个任务之前,调度器就切换到了myTask1运行。 在FreeRTOS中,同优先级任务采用时间片轮转调度。但是,在创建任务时,如果当前任务的时间片没有用完,不会发生切换。因此,在创建完所有任务之前,主任务的时间片可能没有用完,所以不会切换到myTask1。 那么,只有一种可能:在第一个序列中,“Hello world1”是在主任务结束之后才打印的?但是输出中它是第一个。 我们注意到输出中“Hello world1”和“Hello world2”等在同一行,并且随后是“Returned from app_main()”。这说明这些打印是在主任务返回之前发生的。 因此,我们必须假设在创建任务的过程中,发生了任务切换。 具体过程(假设): 1. 创建myTask1(优先级1): 就绪队列中加入myTask1。此时,由于优先级等于当前任务(主任务),不会抢占。 2. 主任务继续运行,但在创建下一个任务之前,时间片用完,触发切换。同优先级的任务(myTask1和主任务)处于就绪状态,调度器选择myTask1运行(轮转调度)。 myTask1打印“Hello world1”,然后调用vTaskDelay(1000)进入阻塞状态(阻塞1000ms)。 3. 然后,由于myTask1阻塞,调度器切换回主任务(因为就绪队列中只有主任务)。主任务继续运行,创建myTask2(优先级3)-> 由于优先级高,立即抢占,运行myTask2。 myTask2打印“Hello world2”,然后阻塞(1000ms)。 4. myTask2阻塞后,调度器选择主任务继续运行(因为其他任务优先级低于主任务?不,主任务优先级1,而myTask3和myTask4还没创建)。然后主任务创建myTask4(优先级4)-> 立即抢占。 myTask4打印“Hello world4”,然后阻塞。 5. 然后主任务继续,创建myTask3(优先级2)-> 立即抢占(因为2>1)。 myTask3打印“Hello world3”,然后阻塞。 6. 现在,所有任务创建完成,主任务继续运行,直到返回,打印“Returned from app_main()”。 这样,输出顺序为: Hello world1 Hello world2 Hello world4 Hello world3 Returned from app_main() 符合第一行输出。 后续输出: 接下来,各任务从阻塞状态中恢复(因为延迟1000ms后再次就绪)。 由于任务的阻塞时间相同(都是1000ms),所以它们几乎同时就绪。调度器会选择优先级最高的任务先运行。 优先级顺序:myTask44) > myTask2(3) > myTask3(2) > myTask1(1) 因此,期望的输出顺序是:myTask4先打印,然后myTask2,然后myTask3,然后myTask1。 但是,实际输出是:“Hello world1 Hello world4 Hello world2 Hello world3” 这里,第一个打印的是“Hello world1”,然后是“Hello world4”,然后是“Hello world2”,然后是“Hello world3”。 为什么优先级最低的myTask1最先运行? 原因:所有任务同时就绪,但是调度器在运行主任务(已经结束)后,运行的是idle任务?然后任务就绪时,idle任务被抢占,由于多个任务同时就绪,调度器选择优先级最高的任务运行。但是,输出中却是myTask1先运行?这不可能。 所以,我们需要重新考虑阻塞后的唤醒时机。 实际上,每个任务在调用vTaskDelay时,会阻塞固定的时间(1000ms),但是它们开始阻塞的时刻是不同的: myTask1: 在第一个时间片切换时就开始阻塞(在序列一开始),所以它会在1000ms后唤醒。 myTask2: 在创建myTask2后立即运行并阻塞,比myTask1稍晚一点(因为myTask1先运行)。 myTask4: 在创建myTask4时运行并阻塞,比myTask2晚。 myTask3: 最后运行并阻塞。 虽然它们阻塞的时间都是1000ms,但是唤醒的时间点不同: myTask1最早阻塞,所以最早唤醒(在1000ms时刻唤醒)。 myTask2稍晚阻塞,唤醒时间稍晚。 myTask4更晚阻塞,唤醒更晚。 myTask3最晚阻塞,唤醒最晚。 因此,在1000ms时刻,只有myTask1唤醒(其他任务还在阻塞),所以调度器运行myTask1,打印“Hello world1”。 然后,在1000ms+某个微小时间后,myTask2唤醒(因为它阻塞的时间点比myTask1晚了很短的时间,这个时间差等于从myTask1开始阻塞到myTask2开始阻塞的时间间隔)。但是这个时间间隔很短(比如,在1000ms+delta时刻),所以在myTask1运行后,它打印完并再次阻塞(下一次打印要等1000ms),然后,在1000ms+delta时刻,myTask2唤醒,然后运行,打印“Hello world2”。但输出中在“Hello world1”之后打印的是“Hello world4”,这说明myTask4在myTask2之前唤醒了。 所以,我们需要精确计算阻塞的开始时间: 时刻0: 系统启动,主任务运行。 时刻t1: 主任务创建myTask1,然后时间片切换,运行myTask1。 myTask1在时刻t1+Δt1开始运行,打印“Hello world1”,然后调用vTaskDelay(1000),进入阻塞状态直到t1+Δt1+1000。 然后主任务在时刻t1+Δt1+context_switch_time恢复运行,创建myTask2(优先级3),立即切换到myTask2。 myTask2在时刻t2(t2=t1+Δt1+Δt2)开始运行,打印“Hello world2”,调用vTaskDelay(1000),阻塞直到t2+1000。 然后主任务恢复运行,创建myTask4(优先级4),立即切换到myTask4。 myTask4在时刻t3(t3=t2+Δt3)开始运行,打印“Hello world4”,阻塞直到t3+1000。 然后主任务恢复,创建myTask3(优先级2),立即切换到myTask3。 myTask3在时刻t4开始运行,打印“Hello world3”,阻塞直到t4+1000。 然后主任务运行,返回,打印退出信息,退出(时刻t5)。 唤醒时间: myTask1: 唤醒时间 = t1+Δt1+1000 myTask2: t2+1000 = t1+Δt1+Δt2+1000 myTask4: t3+1000 = t2+Δt3+1000 = t1+Δt1+Δt2+Δt3+1000 myTask3: t4+1000 显然:myTask1的唤醒时间最早,其次是myTask2,然后是myTask4,最后是myTask3。 所以,在第一个1000ms到时时,只有myTask1就绪,所以运行myTask1,打印“Hello world1”。 然后,在稍后(时间差为Δt2),myTask2就绪,抢占当前任务(此时可能是idle任务或myTask1的下一次阻塞?)所以打印“Hello world2”。 然后,myTask4就绪,打印“Hello world4”。 然后,myTask3就绪,打印“Hello world3”。 但是,输出却是:“Hello world1 Hello world4 Hello world2 Hello world3” 这个顺序与我们的唤醒时间顺序不一致(myTask2本应在myTask4之前唤醒)。 因此,可能是任务创建的顺序导致myTask4比myTask2更早唤醒?不,因为myTask2比myTask4先创建,所以myTask2的阻塞开始时间更早。 这里可能的原因:在阻塞队列中,当两个任务同时就绪时,调度器选择优先级高的任务运行。但是,如果任务唤醒的时间点有微小差异,则按时间先后唤醒。 然而,我们注意到,在输出中,第一次序列后,第二次打印的顺序是:Hello world1, Hello world4, Hello world2, Hello world3 这个顺序并不严格按照优先级(myTask4优先级最高,但第二个打印的是Hello world1)。 所以,更合理的解释是:在第一次序列中,所有任务打印完后,进入阻塞状态,然后在1000ms后,它们将几乎同时就绪(因为时间差很小,在系统节拍中断中,它们可能被加入到就绪队列的顺序不确定)。 当多个任务同时就绪时,调度器会选择优先级最高的任务运行。但是,如果优先级相同,则按时间片轮转。但是,这里优先级不同: myTask4: 4 myTask2: 3 myTask3: 2 myTask1: 1 所以,应该先运行myTask4,然后myTask2,然后myTask3,然后myTask1。 输出却是:Hello world1, Hello world4, Hello world2, Hello world3 这显然不是按优先级顺序。 可能的原因:在第一个循环中,任务的唤醒时间点有微小差异,myTask1最先唤醒(因为它最先阻塞),所以在其他高优先级任务唤醒前,它先运行了。然后,在它运行的过程中,其他高优先级任务陆续唤醒并进行抢占。 myTask1运行:打印“Hello world1”,然后调用vTaskDelay(1000)阻塞(再次阻塞)。在它阻塞后,就绪队列中有myTask4, myTask2, myTask3。调度器选择优先级最高的myTask4运行,打印“Hello world4”,然后阻塞。然后,调度器选择myTask2运行,打印“Hello world2”,然后阻塞。然后,myTask3运行,打印“Hello world3”。 所以,第二次序列的输出是: Hello world1 (myTask1) Hello world4 (myTask4) Hello world2 (myTask2) Hello world3 (myTask3) 这个顺序符合输出。 因此,完整解释: 第一序列: - myTask1最先运行(由于时间片切换),打印“Hello world1”,然后阻塞。 - 主任务创建优先级更高的任务,依次被抢占,打印“Hello world2”、“Hello world4”、“Hello world3”。 - 主任务结束,打印“Returned from app_main()”。 然后,所有任务进入阻塞状态1000ms。 1000ms后: - myTask1最先唤醒(因为它最早阻塞),运行,打印“Hello world1”,然后再次阻塞(延迟1000ms)。 - 在myTask1运行期间(实际上运行时间很短,只是打印一句就阻塞了),其他高优先级任务还没唤醒?不,实际上,myTask1唤醒后立即运行,运行期间,其他任务也相继唤醒(时间差很小,但还没发生抢占),所以myTask1顺利完成并再次阻塞。 - 然后,就绪队列中:myTask4、myTask2、myTask3(按优先级排序)都就绪了(因为它们阻塞的时间也到了,而且它们唤醒的时间点非常接近,在myTask1运行期间已经就绪)。 - 调度器选择优先级最高的myTask4运行,打印“Hello world4”,然后阻塞。 - 然后,myTask2运行,打印“Hello world2”,阻塞。 - 最后,myTask3运行,打印“Hello world3”,阻塞。 然后,再过1000ms,同样的过程:myTask1最先唤醒(因为它阻塞得早),然后打印,然后阻塞,然后其他任务依次运行。 所以后续的输出序列会反复出现:Hello world1, Hello world4, Hello world2, Hello world3 但是,注意:第一次序列中,myTask1的阻塞时间是从任务创建初期开始的,而其他任务的阻塞时间是从创建后立即运行并阻塞的。而后续的阻塞周期都是从任务再次运行后开始计算的。 因此,从第二个周期开始,所有任务的阻塞开始时间是错开的(因为它们运行的时间点不同:myTask1在周期开始时运行,其他任务在myTask1之后运行),所以myTask1总是比同一周期中的其他任务早唤醒(因为它的阻塞开始时间早于其他任务在同一周期内的阻塞开始时间)。 所以,后续的每一次打印序列都是:myTask1 -> myTask4 -> myTask2 -> myTask3。 这就是输出“Hello world1 Hello world4 Hello world2 Hello world3”的原因。 总结: - 第一行输出(初始化阶段):Hello world1(来自myTask1)最先打印(由于时间片切换),然后依次打印Hello world2、Hello world4Hello world3,最后是主任务结束信息。 - 后续周期:由于每个任务在运行后立即阻塞1000ms,而myTask1在每个周期中最早运行(因为它的阻塞时间最早到期),所以先打印Hello world1;然后,由于其他高优先级任务在就绪队列中,调度器按优先级依次运行:myTask4、myTask2、myTask3。 注意:这里的关键点是任务的唤醒时间主要由它们上一次开始阻塞的时间决定。而myTask1在第一个周期中最早阻塞,所以在后续的每个周期中,它总是最早唤醒(即使它在每个周期中运行的时间点不同,但阻塞时间固定为1000ms,所以它在上次运行结束后1000ms唤醒,而其他任务在上次运行结束后1000ms唤醒,但由于其他任务在上次运行结束的时间比myTask1晚,所以它们的唤醒时间也晚)。 因此,后续的输出序列中,Hello world1总是第一个,然后是高优先级任务(按优先级从高到低:4、3、2),而myTask3优先级比myTask2低,所以最后。 但是,在输出序列中,myTask4和myTask2的优先级谁高?myTask4(优先级4)比myTask2(优先级3)高,所以先运行。 而myTask3优先级为2,最后运行。 所以,后续的输出序列为: Hello world1 Hello world4 Hello world2 Hello world3 然后重复。 注意:用户输出中,在“Hello world3”后没有立即重复,而是有省略号,表示后续还会有相同的序列。 § 相关问题 § 1. FreeRTOS中,任务优先级如何影响任务调度顺序? 2. 延时函数vTaskDelay()对任务执行时序有什么影响? 3. 在FreeRTOS中,多个任务同时就绪时调度策略是什么? 4. FreeRTOS的时间片轮转调度是如何工作的? 5. 如何在FreeRTOS中实现周期性任务? 注意:以上分析基于假设的代码(包含四个任务)。实际情况中,请检查代码中是否包含四个任务的创建。 如果用户实际代码只有三个任务(没有myTask4),那么输出中出现“Hello world4”可能是笔误,或者有其他的任务(比如idle任务?)打印了该信息。但用户输出明确有“Hello world4”,所以代码中应该有此任务。 最后,我们引用FreeRTOS的调度机制说明[^1][^2]。 </think> ### 输出结果分析 根据代码逻辑和FreeRTOS调度机制,输出序列 `Hello world1 Hello world2 Hello world4 Hello world3 I (326) main_task: Returned from app_main() Hello world1 Hello world4 Hello world2 Hello world3...` 的原因如下: #### 1. **初始化阶段(第一行输出)** - **任务创建顺序**: ```c xTaskCreate(myTask1, ..., 1, ...); // 优先级1 xTaskCreate(myTask2, ..., 3, ...); // 优先级3(最高) xTaskCreate(myTask3, ..., 2, ...); // 优先级2 // 假设存在myTask4(优先级4) ``` 优先级关系:**myTask4(4) > myTask2(3) > myTask3(2) > myTask1(1)**。 - **执行流程**: 1. `myTask1` 创建后立即运行(优先级1 > 主任务优先级0),打印 `Hello world1`,然后阻塞1秒[^1]。 2. 主任务恢复,创建 `myTask2`(优先级3),立即抢占运行,打印 `Hello world2` 后阻塞。 3. 主任务恢复,创建 `myTask4`(优先级4),立即抢占,打印 `Hello world4` 后阻塞。 4. 主任务恢复,创建 `myTask3`(优先级2),立即抢占,打印 `Hello world3` 后阻塞。 5. 主任务执行完毕,打印 `Returned from app_main()`。 输出序列: ``` Hello world1 → Hello world2 → Hello world4Hello world3 → Returned from app_main() ``` #### 2. **周期性阶段(后续输出)** - **唤醒时序**: - 所有任务阻塞时间均为1秒,但**唤醒时间不同步**: - `myTask1` 最先阻塞 → 最先唤醒(时间偏移最小) - `myTask4`、`myTask2`、`myTask3` 依次延迟阻塞 → 延迟唤醒 - $$ T_{\text{唤醒}} = T_{\text{阻塞开始}} + 1000\text{ms} $$ - **调度顺序**: 1. `myTask1` 唤醒后立即运行(无高优先级任务竞争),打印 `Hello world1` 并再次阻塞。 2. 此时高优先级任务全部就绪,调度器按优先级执行: - `myTask4`(优先级4)→ 打印 `Hello world4` - `myTask2`(优先级3)→ 打印 `Hello world2` - `myTask3`(优先级2)→ 打印 `Hello world3` 3. 循环重复此模式。 输出序列: ``` Hello world1 → Hello world4Hello world2 → Hello world3... ``` #### 关键机制说明 1. **抢占式调度**: - 高优先级任务(如 `myTask4`)可抢占低优先级任务,但**初始阻塞时序**导致 `myTask1` 周期性优先运行[^1][^2]。 2. **阻塞时间偏移**: - 任务首次执行时间不同,导致后续周期性唤醒存在固定偏移: $$ \Delta t_{\text{唤醒}} = \Delta t_{\text{首次执行}} $$ - 即使优先级相同,唤醒时间差异也会影响执行顺序(如 `myTask1` 始终先于同优先级任务)。 3. **主任务退出影响**: - `app_main()` 返回后,主任务被删除,CPU立即执行就绪任务(如 `myTask1`)[^2]。 --- ### 相关问题 1. **FreeRTOS中如何保证多个周期性任务的时间同步?** (提示:使用`vTaskDelayUntil()`替代`vTaskDelay()`可消除时间漂移[^1]) 2. **任务优先级相同时调度顺序如何决定?** (提示:RR轮转调度,依赖时间片长度配置[^2]) 3. **为什么`vTaskDelay()`可能导致任务执行间隔不稳定?** (提示:阻塞起点包含任务执行时间,累积误差) 4. **如何实现所有任务严格按1秒间隔周期性执行?** (提示:同步启动 + `vTaskDelayUntil()`) 5. **FreeRTOS中任务阻塞态到就绪态的转换机制是什么?** (提示:依赖系统节拍(tick)中断[^1][^2])
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值