- 论坛徽章:
- 0
|
有了前面"内核如何实现IA32中断处理"中的内容作为准备,依然将处理器间中断简称为IPI,下面结合ULE线程调度来看一下IPI的发送过程。
先来看一下需要用到的数据结构:
[数组cpu_ipi_pending]-用来处理IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个特殊的IPI:- /*****************************************************************************
- * Holds pending bitmap based IPIs per CPU 。
-
- 数组cpu_ipi_pending和下面的IPI中断向量号结合起来理解会更清楚:
- #define IPI_BITMAP_VECTOR 249
- 对于下面三个IPI的描述为"IPIs handled by IPI_BITMAPED_VECTOR":
- #define IPI_AST 0
- #define IPI_PREEMPT 1
- #define IPI_HARDCLOCK 2
- IPI_AST:忽略,内核中的描述"Nothing to do for AST"。
- IPI_PREEMPT:需要抢占时发送。
-
- IPI_HARDCLOCK:
- 在SMP系统中,只有BSP上的APIC TIMER启用,而其余AP上的APIC TIMER没有启用
- 时,当BSP上的APIC TIMER超时后,就会向其余AP发送一个IPI_HARDCLOCK
- 类型的IPI。
-
- 由此看见,当发送类型为IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK的IPI时,
- 其实是通过中断向量号为IPI_BITMAP_VECTOR对应的中断处理程序来处理的,
- 那么就需要通过一种方法,该方法能够设置一个标志,中断向量号为
- IPI_BITMAP_VECTOR对应的中断处理程序通过检查相应的标志来确定发送的
- 是IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个IPI的哪一个,这个这个方法
- 就是使用数组cpu_ipi_pending。
- 系统中每个cpu在数组cpu_ipi_pending中都有相应的元素,logical cpu id
- 作为数组cpu_ipi_pending的索引。
- 假设系统中某个cpu的logical cpu id为1,那么当该cpu接收到一个
- 中断向量号为IPI_BITMAP_VECTOR的中断时,相应的中断处理程序就会检查
- 数组元素cpu_ipi_pending[1]中IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK对应
- 的bit位是否被设置为1,如果是,就进行相应的处理。设置为1的任务由
- 发送IPI的cpu来完成。
- 通过上面的描述,可以看出,数组cpu_ipi_pending用来描述系统中某个cpu
- 是否有挂起的IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK的IPI。
- ********************************************/
- 198 static volatile u_int cpu_ipi_pending[MAXCPU];
复制代码 [和IPI计数器相关的数组]-对cpu上接收到的IPI进行计数:- /*******************************************************************************
- * 只有在编译内核时选择了COUNT_IPIS选项,下面的描述才有效。
- 161-169:
- 下面的数组对IPI进行计数,每个数组都对应一个IPI类型。
- 对于某一个特定数组,这里假设为数组数组ipi_invltlb_counts,系统中每个cpu
- 都在ipi_invltlb_counts数组中有相应的元素,数组的索引为logical cpu id。
- 对于cpu0,当cpu0接收到一个类型为IPI_INVLTLB的IPI时,相应的中断处理程序
- 就会递增*ipi_invltlb_counts[0].
- u_long intrcnt[INTRCNT_COUNT];
- char intrnames[INTRCNT_COUNT * (MAXCOMLEN + 1)];
-
- 初始化函数mp_ipi_intrcnt完成两个主要任务:
- 1:用intrcnt数组元素的地址初始化下面的指针数组。
- 2:使用类似"cpu%d:invltlb"这样的字符串初始化上面的intrname数组,这里的%d
- 为cpu的logical cpu id。
- *********************************************/
- 161 static u_long *ipi_preempt_counts[MAXCPU];
- 162 static u_long *ipi_ast_counts[MAXCPU];
- 163 u_long *ipi_invltlb_counts[MAXCPU];
- 164 u_long *ipi_invlrng_counts[MAXCPU];
- 165 u_long *ipi_invlpg_counts[MAXCPU];
- 166 u_long *ipi_invlcache_counts[MAXCPU];
- 167 u_long *ipi_rendezvous_counts[MAXCPU];
- 168 u_long *ipi_lazypmap_counts[MAXCPU];
- 169 static u_long *ipi_hardclock_counts[MAXCPU];
复制代码 [数组cpu_apic_ids和apic_cpuids]:- /******************************************************************************************
- * #define MAX_APIC_ID 0xfe
- #define MAXCPU 32
- 下面两个数组都是由函数assign_cpu_ids初始化的,该函数在初始化函数cpu_mp_start中被调用。
- 数组cpu_apic_ids的索引为logical cpu id,相应数组元素的值为cpu的local APIC ID。
- 数组apic_cpuids的索引为cpu的local APIC ID,相应数组元素的值为cpu的logical cpu id。
- ********************************************/
- 194 int cpu_apic_ids[MAXCPU];
- 195 int apic_cpuids[MAX_APIC_ID + 1];
复制代码 [struct tdq对象中的tdq_ipipending成员]:- /******************************************************************************
- * tdq - per processor runqs and statistics. All fields are protected by the
- * tdq_lock. The load and lowpri may be accessed without to avoid excess
- * locking in sched_pickcpu();
- 对于SMP系统:
- 每个cpu对应的一个struct tdq数据对象,数组tdq_cpu以cpu的logical id为索引。
- static struct tdq tdq_cpu[MAXCPU];
- 对于非SMP系统:
- 系统中只定义了一个struct tdq数据对象。
- static struct tdq tdq_cpu;
- tdq_ipipending:表示是否有挂起的IPI。
- *****************************************************/
- 225 struct tdq {
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 236 u_char tdq_ipipending; /* IPI pending. */
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 246 } __aligned(64);
复制代码 根据之前sched_add函数的描述,当把一个线程td添加到系统中某个cpu(假设为cpu5)的运行队列时,就会检查td是否能够抢占当前
正在cpu5上运行的线程,如果可以抢占,就要给cpu5发送一个IPI。
[摘取sched_add函数的部分代码]:- /**************************************************************************************
- * Select the target thread queue and add a thread to it. Request
- * preemption or IPI a remote processor if required.
- 参数描述:
- td:标识将要被添加到cpu运行队列中的线程。
- flags:传递给函数sched_add的标志,相关标志需要结合上下文分析,这里暂且略过。
- **************************************/
- 2342
- 2343 void
- 2344 sched_add(struct thread *td, int flags)
- 2345 {
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- /***************************************************************************************
- * 2365-2377:SMP系统
- *
- * Pick the destination cpu and if it isn't ours transfer to the
- * target cpu.
- 2370:函数sched_pickcpu为线程td挑选一个合适的cpu,局部变量smp_cpu保存了该
- cpu的logical cpu id,这里假设为5。
- 2371:函数sched_setcpu主要完成下面的工作:
- 将和线程td相关联的struct td_sched数据对象的ts_cpu成员设置为smp_cpu。
- 并返回logical cpu id为smp_cpu的运行队列,这里假设为&tdq_cpu[5]。
- ***********************************/
- 2365 #ifdef SMP
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 2370 smp_cpu = sched_pickcpu(td, flags);
- 2371 tdq = sched_setcpu(td, smp_cpu, flags);
- /************************************************************************
- * 2373-2376:
- 如果smp_cpu和当前cpu的logical cpu id不相等,那么就调用函数
- tdq_notify检查线程td是否能够抢占当前正在cpu logical id为smp_cpu
- 上运行的线程,如果可以,就发送一个IPI。
- *********************************************************/
- 2373 if (smp_cpu != PCPU_GET(cpuid)) {
- 2374 tdq_notify(tdq, td);
- 2375 return;
- 2376 }
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- 2389 }
复制代码 [摘取函数tdq_notify的部分代码]:- /*******************************************************************************
- * Notify a remote cpu of new work. Sends an IPI if criteria are met.
- 对于logical cpu id为5的cpu,此时参数描述及相应成员的取值如下:
- 参数描述:
- tdq:为&tdq_cpu[5]。
- td:已经被添加到运行队列tdq_cpu[5]的tdq_realtime队列,tdq_timeshare队列,
- tdq_idle队列三个队列之一中的线程。
- 成员取值描述:
- td->td_sched->ts_cpu的值为5。
- ***************************************************************************/
- 1011 static void
- 1012 tdq_notify(struct tdq *tdq, struct thread *td)
- 1013 {
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- /************************************************************
- * 1018-1019:
- 如果struct tdq对象的tdq_ipipending成员非零,表示此时正在
- 发送IPI,此时直接返回。
- *****************************************/
- 1018 if (tdq->tdq_ipipending)
- 1019 return;
- 。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
- /***************************************************************
- * 如果执行到这里,就表示线程td将抢占线程ctd。
- * 1033:将struct tdq对象的tdq_ipipending成员设置为1。
- 1034:发送类型为IPI_PREEMPT的IPI。
- 变量cpu的值为5。
- ********************************************************/
- 1033 tdq->tdq_ipipending = 1;
- 1034 ipi_cpu(cpu, IPI_PREEMPT)
- 1035 }
复制代码 从上面的描述中可以看出,调用函数ipi_cpu来发送一个IPI,函数ipi_cpu是内核提供的用来发送IPI的接口。
[函数ipi_cpu]:- /************************************************************************
- * send an IPI to a specific CPU.
- 参数描述:
- cpu:接收IPI的cpu的logical cpu id,这里假设为5。
- ipi:
- 一般情况下为中断向量号。
- 当发送IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个特殊的IPI时,对应
- 的不是中断向量号。
- 这里ipi的值为IPI_PREEMPT。
- 该函数实际上是函数ipi_send_cpu的一个简单封装。
- ********************************************/
- 1419 void
- 1420 ipi_cpu(int cpu, u_int ipi)
- 1421 {
- 1422
- /***********************************************************************
- * IPI_STOP_HARD maps to a NMI and the trap handler needs a bit
- * of help in order to understand what is the source.
- * Set the mask of receiving CPUs for this purpose.
- 1428-1429:
- #define IPI_STOP_HARD 252 Stop CPU with a NMI.
- 忽略之间的处理。
- 1432:ipi_send_cpu函数见下面的描述。
- *******************************/
- 1428 if (ipi == IPI_STOP_HARD)
- 1429 CPU_SET_ATOMIC(cpu, &ipi_nmi_pending);
- 1430
- 1431 CTR3(KTR_SMP, "%s: cpu: %d ipi: %x", __func__, cpu, ipi);
- 1432 ipi_send_cpu(cpu, ipi);
- 1433 }
复制代码 [函数ipi_send_cpu]:- /***************************************************************************************
- * Send an IPI to specified CPU handling the bitmap logic.
- * 参数描述:
- cpu:接收IPI的cpu的logical cpu id,这里假设为5。
- ipi:
- 一般情况下为中断向量号。
- 当发送IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个IPI时,对应
- 的不是中断向量号。
- 这里ipi的值为IPI_PREEMPT。
- 函数ipi_send_cpu的任务就是针对IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个特殊的IPI
- 做相应的处理。
- ***********************************
- 1190 static void
- 1191 ipi_send_cpu(int cpu, u_int ipi)
- 1192 {
- 1193 u_int bitmap, old_pending, new_pending;
- 1194
- 1195 KASSERT(cpu_apic_ids[cpu] != -1, ("IPI to non-existent CPU %d", cpu));
- /***********************************************************************************************************
- * 1197-1207:
- 如果发送的是IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个特殊的IPI就进行相应的处理。
-
- #define IPI_AST 0
- #define IPI_PREEMPT 1
- #define IPI_HARDCLOCK 2
- #define IPI_BITMAP_LAST IPI_HARDCLOCK 2
- #define IPI_IS_BITMAPED(x) ((x) <= IPI_BITMAP_LAST)
-
- 1197:
- 宏IPI_IS_BITMAPED检查ipi是否为IPI_AST或者IPI_PREEMPT或者IPI_HARDCLOCK,如果是,宏的值就为真。
- 这里为IPI_IS_BITMAPED(IPI_PREEMPT) IPI_PREEMPT <= IPI_BITMAP_LAST,很明显为真。
- 1198:变量bitmap为二进制10。
- 1199:此时重新设置变量ipi,将其设置为IPI_BITMAP_VECTOR,即中断向量号249。
- 由此可以看出,当发送IPI_AST,IPI_PREEMPT,IPI_HARDCLOCK三个特殊的IPI时,实际上发送的IPI的中断向量号
- 都为IPI_BITMAP_VECTOR。
- 1200-1203,do-while循环:
- 1201:old_pending保存的是当前数组元素cpu_ipi_pending[5]的值。
- 1202:new_pending为将要赋值给数组元素cpu_ipi_pending[5]的新值。
- do_while循环一直执行,直到old_pending和cpu_ipi_pending[cpu]的值相等时为止,此时函数atomic_cmpset_int
- 返回1,数组元素cpu_ipi_pending[cpu]的值被更新为new_pending。
-
- 因为APIC同时允许有两个挂起的相同中断,freebsd9.2貌似为了预防死锁,一次只允许有一个挂起的中断。
- 1205:如果old_pending非空,表示已经发送了一个中断向量号为IPI_BITMAP_VECTOR的IPI,但是cpu5还没有处理。
- 此时直接返回。
- *************************************************/
- 1197 if (IPI_IS_BITMAPED(ipi)) {
- 1198 bitmap = 1 << ipi;
- 1199 ipi = IPI_BITMAP_VECTOR;
- 1200 do {
- 1201 old_pending = cpu_ipi_pending[cpu];
- 1202 new_pending = old_pending | bitmap;
- 1203 } while (!atomic_cmpset_int(&cpu_ipi_pending[cpu],
- 1204 old_pending, new_pending));
- 1205 if (old_pending)
- 1206 return;
- 1207 }
- /**************************************************************
- * 1208:
- logical cpu id是内核用来标识cpu的,但是发送IPI时,必须使用
- 指定cpu的local APIC ID,这里就要用到数组cpu_apic_ids。
- 数组元素cpu_apic_ids[5]的值为cpu5的local APIC ID。
-
- 此时,变量ipi已经被正确设置,调用函数lapic_ipi_vectored
- 发送IPI。
- *************************************/
- 1208 lapic_ipi_vectored(ipi, cpu_apic_ids[cpu]);
- 1209 }
复制代码 [函数lapic_ipi_vectored]-该函数构造将要写入到ICR中的值,下面再看看ICR的格式,假设其为0,一般情况下,Reserved
字段的值先读出,然后保证写入原值即可,这里假设Reserved字段的值为0:
- /***************************************************************************
- * 参数描述:
- vector:中断向量号,这里为IPI_BITMAP_VECTOR,即中断向量号249。
- dest:cpu5的local APIC ID,这里假设为X,X为local APIC ID的二进制表示。
- *****************************
- 1441 void
- 1442 lapic_ipi_vectored(u_int vector, int dest)
- 1443 {
- /**********************************************************************
- * icrlo:将要写入ICR低32位的值。
- destfield:将要写入ICR高32位中Destination Field字段的值。
- 1449-1459:执行完后,icrlo的值为0x000000F9。
- *******************************/
- 1444 register_t icrlo, destfield;
- 1445
- 1446 KASSERT((vector & ~APIC_VECTOR_MASK) == 0,
- 1447 ("%s: invalid vector %d", __func__, vector));
- 1448
- 1449 icrlo = APIC_DESTMODE_PHY | APIC_TRIGMOD_EDGE;
- 1450
- 1451 /*
- 1452 * IPI_STOP_HARD is just a "fake" vector used to send a NMI.
- 1453 * Use special rules regard NMI if passed, otherwise specify
- 1454 * the vector.
- 1455 */
- 1456 if (vector == IPI_STOP_HARD)
- 1457 icrlo |= APIC_DELMODE_NMI | APIC_LEVEL_ASSERT;
- 1458 else
- 1459 icrlo |= vector | APIC_DELMODE_FIXED | APIC_LEVEL_DEASSERT;
- /******************************************************************************************
- * 因为这里是发送给指定cpu的IPI,所以dest为目的cpu的local APIC ID。
- 1462-1464:IPI的目的地为仅包括发送此IPI的cpu,此时icrlo的值为0x000400F9。
- 1465-1467:IPI的目的地为系统中全部cpu,包括发送此IPI的cpu,此时icrlo的值为0x000800F9。
- 1468-1470:IPI的目的地为系统中除发送此IPI的cpu以外的cpu,此时icrlo的值为0x000C00F9。
-
- 如果switch语句从上面任意三个case中的一个跳出,destfield的值不变,即为0。
- 针对这里讨论的情况,destfield的值为X,icrlo的值为0x000000F9。
- ************************************/
- 1460 destfield = 0;
- 1461 switch (dest) {
- 1462 case APIC_IPI_DEST_SELF:
- 1463 icrlo |= APIC_DEST_SELF;
- 1464 break;
- 1465 case APIC_IPI_DEST_ALL:
- 1466 icrlo |= APIC_DEST_ALLISELF;
- 1467 break;
- 1468 case APIC_IPI_DEST_OTHERS:
- 1469 icrlo |= APIC_DEST_ALLESELF;
- 1470 break;
- 1471 default:
- 1472 KASSERT((dest & ~(APIC_ID_MASK >> APIC_ID_SHIFT)) == 0,
- 1473 ("%s: invalid destination 0x%x", __func__, dest));
- 1474 destfield = dest;
- 1475 }
- 1476
- /*****************************************************************************************************
- * Wait for an earlier IPI to finish,检查是否有更早的IPI没有完成发送。
- #define BEFORE_SPIN 1000000
-
- ICR的字段Delivery Status:指示IPI的发送状态。0:IPI已经成功发送出去;1:还没有完成IPI的发送。
- 函数lapic_ipi_wait执行一个for循环,不停的测试Delivery Status字段的值,如果在指定的时间内,该
- 字段的值变为0,就返回1;否则返回0,表示出现了某种问题。
- 这个指定的时间为BEFORE_SPIN * (指令pause的时钟周期数)。
- *******************************************/
- 1478 if (!lapic_ipi_wait(BEFORE_SPIN)) {
- 1479 if (panicstr != NULL)
- 1480 return;
- 1481 else
- 1482 panic("APIC: Previous IPI is stuck");
- 1483 }
- /*****************************************************************************************************
- * 1485:
- 函数lapic_ipi_raw完成IPI的发送,见下面的描述。
- ***********************/
- 1485 lapic_ipi_raw(icrlo, destfield);
- /*************************************************************************
- * 执行到这里的话,IPI的发送已经完成。
- *
- * 1487-1512之间的代码只有在定义了DETECT_DEADLOCK才有意义,在有意义的
- 情况下,就是检查一下此次发送的IPI是否完成发送。
- ***************************************/
- 1487 #ifdef DETECT_DEADLOCK
- 1488 /* Wait for IPI to be delivered. */ #define AFTER_SPIN 1000
- 1489 if (!lapic_ipi_wait(AFTER_SPIN)) {
- 1490 #ifdef needsattention
- 1491 /*
- 1492 * XXX FIXME:
- 1493 *
- 1494 * The above function waits for the message to actually be
- 1495 * delivered. It breaks out after an arbitrary timeout
- 1496 * since the message should eventually be delivered (at
- 1497 * least in theory) and that if it wasn't we would catch
- 1498 * the failure with the check above when the next IPI is
- 1499 * sent.
- 1500 *
- 1501 * We could skip this wait entirely, EXCEPT it probably
- 1502 * protects us from other routines that assume that the
- 1503 * message was delivered and acted upon when this function
- 1504 * returns.
- 1505 */
- 1506 printf("APIC: IPI might be stuck\n");
- 1507 #else /* !needsattention */
- 1508 /* Wait until mesage is sent without a timeout. */
- 1509 while (lapic->icr_lo & APIC_DELSTAT_PEND)
- 1510 ia32_pause();
- 1511 #endif /* needsattention */
- 1512 }
- 1513 #endif /* DETECT_DEADLOCK */
- 1514 }
复制代码 [函数lapic_ipi_raw]:- /************************************************************************************
- * 参数描述:
-
- icrlo:将要写到ICR低32位的值,针对这里讨论的情况,值为0x000000F9。
-
- dest:目的cpu的local APIC ID或者为0,针对这里讨论的情况,值为X。
- *************************/
- 1407 void
- 1408 lapic_ipi_raw(register_t icrlo, u_int dest)
- 1409 {
- 1410 register_t value, saveintr;
- 1411
- 1412 /* XXX: Need more sanity checking of icrlo? */
- 1413 KASSERT(lapic != NULL, ("%s called too early", __func__));
- 1414 KASSERT((dest & ~(APIC_ID_MASK >> APIC_ID_SHIFT)) == 0,
- 1415 ("%s: invalid dest field", __func__));
- 1416 KASSERT((icrlo & APIC_ICRLO_RESV_MASK) == 0,
- 1417 ("%s: reserved bits set in ICR LO register", __func__));
- 1418
- /***********************************************************************************
- * Set destination in ICR HI register if it is being used.
-
- 1420:函数intr_disable将当前EFLAGS寄存器的值保存到变量中saveintr,之后清
- EFLAGS寄存器的IF标志,表示此时禁止中断。
-
- 针对这里讨论的情况,将执行1421-1426之间的语句。
- 变量lapic请参考"内核如何实现IA32中断处理"中的描述。
- 通过变量lapic访问cpu5的local APIC寄存器组:
- lapic->icr_hi:访问ICR高32位。
-
- 1422-1425:ICR高32位中的bit24-31被设置为X,bit0-23保持原来的值。
-
- 执行完后ICR中Destination Field字段的值为X,即这个IPI将发送给cpu5。
- ******************************************/
- 1420 saveintr = intr_disable();
- 1421 if ((icrlo & APIC_DEST_MASK) == APIC_DEST_DESTFLD) {
- 1422 value = lapic->icr_hi;
- 1423 value &= ~APIC_ID_MASK;
- 1424 value |= dest << APIC_ID_SHIFT;
- 1425 lapic->icr_hi = value;
- 1426 }
- /******************************************************************************************
- * 通过变量lapic访问cpu5的local APIC寄存器组:
- lapic->icr_lo:访问ICR低32位。
- 1429-1431:执行完后,value的值为0x000000F9。
-
- 1432:这个赋值操作就相当于对ICR低32位执行了一个写操作,ICR的Vector字段的值为0xF9。
-
- 此时,已经向cpu5发出了一个IPI,该IPI的中断向量号为IPI_BITMAP_VECTOR,当cpu5
- 接收到这个中断时,就按照"内核如何实现IA32中断处理"中最后描述的中断处理机制进行处理。
- 1432:用saveintr恢复EFLAGS寄存器。
- ****************************************/
- 1428 /* Program the contents of the IPI and dispatch it. */
- 1429 value = lapic->icr_lo;
- 1430 value &= APIC_ICRLO_RESV_MASK;
- 1431 value |= icrlo;
- 1432 lapic->icr_lo = value;
- 1433 intr_restore(saveintr);
- 1434 }
复制代码 |
|