ThreadX内核源码分析(SMP) - 线程执行核remap

Posted arm7star

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadX内核源码分析(SMP) - 线程执行核remap相关的知识,希望对你有一定的参考价值。

1、线程remap介绍(_tx_thread_smp_remap_solution_find)

        内核在挂起正在核上执行的线程时,会空出一个核,那么就可能在该核上执行新的线程,另外,因为是多核,所以线程存在绑核的情况,也就是新的线程可能绑定到其他核了,并不能在空出的核上执行,而其他核又有其他线程在执行,那么就需要考虑是否可以移动其他核的线程到空闲核上,然后让新的线程在让出的核上执行,这就涉及到一个移动线程的操作(remap);

        内核唤醒一个新线程时,如果有空闲的核并且该线程可以在该核上执行,那么就可以让新线程在该核上执行,如果有空闲核但是不能在该核执行,那么需要考虑是否可以移动其他核上的线程到空闲核上,让出核给新的线程执行,如果没有空闲核并且新线程可以抢占正在执行的线程,那么也要淘汰一个线程,空出一个核来执行,新线程并不一定能在淘汰线程所在核上执行,因此也需要通过移动线程找到一个合适的方案来执行新的线程;

        ThreadX内核通过_tx_thread_smp_remap_solution_find函数来查找一个能让线程在cpu上执行的方案。

2、_tx_thread_smp_remap_solution_find函数参数

        函数原型:static INLINE_DECLARE UINT  _tx_thread_smp_remap_solution_find(TX_THREAD *schedule_thread, ULONG available_cores, ULONG thread_possible_cores, ULONG test_possible_cores)

函数参数:

schedule_thread需要调度的线程,也就是要给schedule_thread线程找一个可执行的核
available_cores空闲的核
thread_possible_coresschedule_thread线程可能的核(schedule_thread绑定的核,并且这些核上有其他线程在执行,移动这些线程可能空出核让schedule_thread线程执行)
test_possible_cores正在执行的线程可以移动过去的核(不包括schedule_thread可能执行的核)

3、_tx_thread_smp_remap_solution_find实现(schedule_thread线程映射)

3.1、主要数据

UINT            core_queue[TX_THREAD_SMP_MAX_CORES-1];

core_queue是一个数组,里面存的是核的id,以schedule_thread为例,如果schedule_thread线程上次执行的核可用(有别的线程在执行,可能可以空出来),那么就把这个核保存到core_queue的最前面,然后从小到大将所有可能的执行的核加入到core_queue队列里面去,也就是按优先执行的核依次入队列;

UINT            queue_first, queue_last;

queue_first、queue_last用于指向队列的头和尾,也就core_queue的头尾索引;

TX_THREAD       *thread_remap_list[TX_THREAD_SMP_MAX_CORES];

thread_remap_list是一个线程指针数组,存储remap过程中的线程,thread_remap_list[i]也就是将该线程映射到核i上去执行。

3.2、schedule_thread线程可执行核入队列

        schedule_thread线程可执行核入队列主要代码如下:

1186     /* Setup the core queue indices.  */
1187     queue_first =  ((UINT) 0);
1188     queue_last =   ((UINT) 0);
1189 
1190     /* Build a list of possible cores for this thread to execute on, starting
1191        with the previously mapped core.  */
1192     core =  schedule_thread -> tx_thread_smp_core_mapped; // 线程上一次执行所在的核
1193     if ((thread_possible_cores & (((ULONG) 1) << core)) != ((ULONG) 0)) // 之前执行的核可用,之前执行的核放在core_queue前面,优先考虑之前执行的核
1194     
1195 
1196         /* Remember this potential mapping.  */
1197         thread_remap_list[core] =   schedule_thread; // 把schedule_thread线程映射到core上
1198         core_queue[queue_last] =    core; // core入队列
1199 
1200         /* Move to next slot.  */
1201         queue_last++; // 队列的末尾加1后移
1202 
1203         /* Clear this core.  */
1204         thread_possible_cores =  thread_possible_cores & ~(((ULONG) 1) << core); // core已经入队列了,清除thread_possible_cores对应的core
1205     

        获取线程上一次执行所在的核,让线程尽可能在上一次执行所在的核上执行:

1192     core =  schedule_thread -> tx_thread_smp_core_mapped; // 线程上一次执行所在的核

        如果上一次执行所在的核可用(上面有线程在执行,可能可以空出来),那么将该核入队列并从可用核上面删除,将线程映射到该核(只是该线程可以在该核上面执行,但是该核上面的线程并不一定能移动到其他核上去,后面代码再检查该核上原来执行的线程是否可以移动到其他核上面去):

1193     if ((thread_possible_cores & (((ULONG) 1) << core)) != ((ULONG) 0)) // 之前执行的核可用,之前执行的核放在core_queue前面,优先考虑之前执行的核
1194     
1195 
1196         /* Remember this potential mapping.  */
1197         thread_remap_list[core] =   schedule_thread; // 把schedule_thread线程映射到core上
1198         core_queue[queue_last] =    core; // core入队列
1199 
1200         /* Move to next slot.  */
1201         queue_last++; // 队列的末尾加1后移
1202 
1203         /* Clear this core.  */
1204         thread_possible_cores =  thread_possible_cores & ~(((ULONG) 1) << core); // core已经入队列了,清除thread_possible_cores对应的core
1205     

3.3、其他可执行的核从小到大入队列

        线程可以执行的核依次按id从小到大入队列,也就是后面会从小到大检查是否存在可行方案让出核来给新的线程执行,优先让新的线程在小的核上面执行,主要代码如下:

    /* Loop to add additional possible cores.  */
    while (thread_possible_cores != ((ULONG) 0)) // schedule_thread可放的核依次加入core_queue,并将schedule_thread放到thread_remap_list里面
    

        /* Determine the first possible core.  */
        test_cores =  thread_possible_cores;
        TX_LOWEST_SET_BIT_CALCULATE(test_cores, core) // TX_LOWEST_SET_BIT_CALCULATE计算test_cores从最低位开始的第一个非0的二进制位(最低的为1的二进制位,也就是id最小的核),最小的可用核保存到core

        /* Clear this core.  */
        thread_possible_cores =  thread_possible_cores & ~(((ULONG) 1) << core); // 在可用核里面清除core

        /* Remember this potential mapping.  */
        thread_remap_list[core] =  schedule_thread; // schedule_thread线程映射到核core
        core_queue[queue_last] =   core; // 核core入队列

        /* Move to next slot.  */
        queue_last++; // 队列的末尾加1后移
    

        获取最小的可执行核,thread_possible_cores的每个二进制位代表一个核,0表示不可用,1表示可用,例如二进制的第3位为1,那么就表示核3可以用:

        /* Determine the first possible core.  */
        test_cores =  thread_possible_cores;
        TX_LOWEST_SET_BIT_CALCULATE(test_cores, core) // TX_LOWEST_SET_BIT_CALCULATE计算test_cores从最低位开始的第一个非0的二进制位(最低的为1的二进制位,也就是id最小的核),最小的可用核保存到core

4、_tx_thread_smp_remap_solution_find实现(移动到空闲核)

        因为新的线程不能直接在空闲核上面执行,那么就要移动其他正在执行的线程到空闲核上,空出一个核来给新的线程执行;上一节的schedule_thread线程映射过程中,schedule_thread线程映射到了所有可能的核上面,这些核上面有其他线程在执行,并没有检查该核上执行的线程是否可以移动到空闲核上面或者其他核上的线程移动之后是否可以空出一个核给被schedule_thread占用的核上的线程执行,那么_tx_thread_smp_remap_solution_find接下来就要检查schedule_thread线程映射的核上的线程是否可以移动到其他核上执行。

        移动核的循环实现理解不是很直观,简单理解就是,新的线程去占用所有可能的核,检查这些核上原来的线程是不是可以移动到空闲核上,如果找到了一个可以移动到空闲核的线程,那么就退出,新的线程占用该核即可(其他核保持不变),如果找不到可以移动到空闲核的线程,那么被新线程占用的线程就去占用其他可能的核,其他核上的线程可能存在可以移动的空闲核的线程,递归下去,直到有一个被占用的线程可以移动到空闲核上面去,或者找不到可占用的核了;如果找到了,那么通过移动到空闲核的线程就可以反过来找到线程的移动路径。

4.1、核占用及线程移动过程实例

        占用移动过程比较抽象,先以一个例子来演示一下整个过程,例如:线程8为新调度的线程,数组就是核,核下标从1开始,数组方框里面的数值是线程编号,第1个核里面执行的是线程1,当前的线程编号正好与核的编号相同(只是为了简单演示,实际情况线程是随机分布的),如下图所示:

 步骤1: 线程8占用所有可执行的核3~5,占用如下所示,小括号里面的是原来执行的线程,灰色为被占用的核

 步骤2: 从队列开始,检查占用核的线程是否可以移动的空闲线程,此时,队列里面第一个核是3,核3出队列,检查线程8占用的核3,看看上面执行的线程是不是可以移动的空闲核上,很显然,线程3不能移动到核8上面执行,那么线程3去占用其他可执行的核,很明显,线程3没有可以占用的核了

 步骤3: 从队列开始,检查占用核的线程是否可以移动的空闲线程,此时,队列里面第一个核是4,核4出队列,检查线程8占用的核4,看看上面执行的线程是不是可以移动的空闲核上,很显然,线程4不能移动到核8上面执行,那么线程4去占用其他可执行的核,很明显,线程4只能占用核6,核6入栈

  步骤4: 从队列开始,检查占用核的线程是否可以移动的空闲线程,此时,队列里面第一个核是5,核5出队列,检查线程8占用的核5,看看上面执行的线程是不是可以移动的空闲核上,很显然,线程5不能移动到核8上面执行,那么线程5去占用其他可执行的核,很明显,线程4只能占用核7,核7入栈

  步骤5: 从队列开始,检查占用核的线程是否可以移动的空闲线程,此时,队列里面第一个核是6,核6出队列,检查线程8占用的核6,看看上面执行的线程是不是可以移动的空闲核上,很显然,线程6可以移动到空闲核上面,那么查找结束,记录线程6为最后一个线程

步骤6: 从最后一个移动的线程反过来查找移动路径,如下图所示,线程6移动到核8,线程6原来在核6上面执行,那么就找哪个线程应该移动到核6,现在看到的是核6被线程4占用,那么线程4就应该移动到核6,接着找哪个线程应该移动到线程4原来所在的核4,核4线程是线程8占用,那么线程8就应该移动到核4上面执行,因为线程8就是新的线程,那么就移动结束了,没有移动的位置就不用移动了,例如核3被8占用,但是核3不需要移动,最终核3还是执行线程3,

 4.2、占用核的过程代码

占用核的整个过程就是找到移动到空闲核的那个线程,实现代码如下:

1226     /* Loop to evaluate the potential thread mappings, against what is already mapped.  */
1227     do
1228     
1229 
1230         /* Pickup the next entry.  */
1231         core = core_queue[queue_first]; // 取第一个占用的核
1232 
1233         /* Move to next slot.  */
1234         queue_first++; // 队列头指针后移(第一个核已经出队列了,指向队列里面的下一个元素)
1235 
1236         /* Retrieve the thread from the current mapping.  */
1237         thread_ptr =  _tx_thread_smp_schedule_list[core]; // 获取被占用的core核上正在执行的线程
1238 
1239         /* Determine if there is a thread currently mapped to this core.  */
1240         if (thread_ptr != TX_NULL) // core核上有线程正在执行,正在执行的线程为thread_ptr,那么新线程放到这个core上就需要将thread_ptr线程移动到其他核上
1241         
1242 
1243             /* Determine the cores available for this thread.  */
1244             thread_possible_cores =  thread_ptr -> tx_thread_smp_cores_allowed; // thread_ptr线程允许运行的核(thread_ptr线程绑定的核)
1245             thread_possible_cores =  test_possible_cores & thread_possible_cores; // 所有正在执行的线程可以移动的核test_possible_cores并上thread_ptr线程绑定的核就得到thread_ptr线程当前可以移动过去的核
1246 
1247             /* Are there any possible cores for this thread?  */
1248             if (thread_possible_cores != ((ULONG) 0)) // 如果有核可以移动过去,那么就移动该线程(移动到空闲核或者其他线程的核)
1249             
1250 
1251                 /* Determine if there are cores available for this thread.  */
1252                 if ((thread_possible_cores & available_cores) != ((ULONG) 0)) // thread_ptr线程可以移动到空闲核上面,那么就移动到空闲核上面
1253                 
1254 
1255                     /* Yes, remember the final thread and cores that are valid for this thread.  */
1256                     last_thread_cores =  thread_possible_cores & available_cores; // 记录thread_ptr线程可移动过去的空闲核(可能有多个空闲核,最终后面会取最小的核)
1257                     last_thread =        thread_ptr; // 记录最后一个移动的线程(通过最后一个线程来反向查找前面的线程)
1258 
1259                     /* We are done - get out of the loop!  */
1260                     break; // 找到合适的映射方案,退出循环
1261                 
1262                 else // 不能移动到空闲核上面,那么移动thread_ptr线程到其他可能的核上面
1263                 
1264 
1265                     /* Remove cores that will be added to the list.  */
1266                     test_possible_cores =  test_possible_cores & ~(thread_possible_cores); // thread_ptr线程会占用所有可能的核,那么test_possible_cores就要清除掉thread_ptr线程所占用的核,就剩下其他线程可占用的核;这里就是减掉占用的核
1267 
1268                     /* Loop to add this thread to the potential mapping list.  */
1269                     do // thread_ptr线程占用所有可能的核(可能移动过去的核)
1270                     
1271 
1272                         /* Calculate the core.  */
1273                         test_cores =  thread_possible_cores;
1274                         TX_LOWEST_SET_BIT_CALCULATE(test_cores, core) // 所有可能的核取最小的核,优先移动到最小的核上面去
1275 
1276                         /* Clear this core.  */
1277                         thread_possible_cores =  thread_possible_cores & ~(((ULONG) 1) << core); // 清除最小的核(最小的核已经取出来了)
1278 
1279                         /* Remember this thread for remapping.  */
1280                         thread_remap_list[core] =  thread_ptr; // thread_ptr线程占用核core
1281 
1282                         /* Remember this core.  */
1283                         core_queue[queue_last] =  core; // 占用的核core入队列,do ... while循环依次检查占用的核,检查完的核出队列,还没检查的核入队列
1284 
1285                         /* Move to next slot.  */
1286                         queue_last++; // 队列尾指针移动到下一个元素(下一个存储地址)
1287 
1288                      while (thread_possible_cores != ((ULONG) 0)); // 还有可能的核,那么继续去占用这些可能的核
1289                 
1290             
1291         
1292      while (queue_first != queue_last); // 所有占用的核检查完了,那么都没找到可移动到空闲核的,就不存在可行的移动方案

 4.3、反向查找移动路径

通过最后一个移动到空闲核的线程,反向查找前一个线程,直至找到新的调度线程,实现代码如下:

1294     /* Was a remapping solution found?  */
1295     if (last_thread != TX_NULL) // last_thread记录移动到空闲核的线程,如果不为空,那么就找到了,否则没有找到
1296     
1297 
1298         /* Pickup the core of the last thread to remap.  */
1299         core =  last_thread -> tx_thread_smp_core_mapped; // 获取last_thread线程所映射的核(移动之前所在的核)
1300 
1301         /* Pickup the thread from the remapping list.  */
1302         thread_ptr =  thread_remap_list[core]; // 获取占用last_thread线程执行核的线程(哪个线程需要移动到last_thread线程之前所在的核)
1303 
1304         /* Loop until we arrive at the thread we have been trying to map.  */
1305         while (thread_ptr != schedule_thread) // 不是新的schedule_thread线程,除了要将thread_ptr线程移动到last_thread线程之前所在核之外,还要知道哪一个线程移动到thread_ptr线程之前所在核,循环直到反向找到新的schedule_thread线程
1306         
1307 
1308             /* Move this thread in the schedule list.  */
1309             _tx_thread_smp_schedule_list[core] =  thread_ptr; // thread_ptr线程移动到核core上执行
1310 
1311             /* Remember the previous core.  */
1312             previous_core =  core; // 记录thread_ptr线程执行的核,后面再更新到thread_ptr线程
1313 
1314             /* Pickup the core of thread to remap.  */
1315             core =  thread_ptr -> tx_thread_smp_core_mapped; // 获取thread_ptr线程所映射的核(移动之前所在的核)
1316 
1317             /* Save the new core mapping for this thread.  */
1318             thread_ptr -> tx_thread_smp_core_mapped =  previous_core; // 更新thread_ptr线程所映射的核
1319 
1320             /* Move the next thread.  */
1321             thread_ptr =  thread_remap_list[core]; // 获取映射到core的线程(thread_ptr移动到该核执行)
1322         
1323 
1324         /* Save the remaining thread in the updated schedule list.  */
1325         _tx_thread_smp_schedule_list[core] =  thread_ptr; // thread_ptr == schedule_thread上面的循环结束,因此这里的thread_ptr就是schedule_thread,让schedule_thread线程在核core上面执行
1326 
1327         /* Update this thread's core mapping.  */
1328         thread_ptr -> tx_thread_smp_core_mapped =  core; // 记录schedule_thread线程映射的核
1329 
1330         /* Finally, setup the last thread in the remapping solution.  */
1331         test_cores =  last_thread_cores; // 最后一个移动线程可移动的核
1332         TX_LOWEST_SET_BIT_CALCULATE(test_cores, core) // 选最小的一个可以移动过去的核作为最后一个移动线程的执行核
1333 
1334         /* Setup the last thread.  */
1335         _tx_thread_smp_schedule_list[core] =     last_thread; // 将最后一个移动的线程放到核core上执行
1336 
1337         /* Remember the core mapping for this thread.  */
1338         last_thread -> tx_thread_smp_core_mapped =  core; // 更新last_thread线程所映射的核
1339     
1340     else // 没有找到合适的移动方案,返回TX_THREAD_SMP_MAX_CORES(无效的核,上一级函数就会尝试获取下一个就绪线程,看看是否可以移动到某个核上执行)
1341     
1342 
1343         /* Set core to the maximum value in order to signal a remapping solution was not found.  */
1344         core =  ((UINT) TX_THREAD_SMP_MAX_CORES);
1345     

5、总结

        整个过程就是先把能占的核先占了,如果被占的核上的线程能移动到空闲核上,那么就移动到空闲核上面去,如果不能,那就递归占能占的核。

        

以上是关于ThreadX内核源码分析(SMP) - 线程执行核remap的主要内容,如果未能解决你的问题,请参考以下文章

ThreadX内核源码分析(SMP) - 核间通信(arm)

ThreadX内核源码分析(SMP) - 核间通信(arm)

ThreadX内核源码分析(SMP) - 核间互斥(arm)

ThreadX内核源码分析(SMP) - 核间互斥(arm)

ThreadX内核源码分析 - 优先级及抢占阈值抢占(arm)

ThreadX内核源码分析 - 优先级及抢占阈值抢占(arm)