Chinaunix
标题:
freebsd9.2-处理器间中断-发送
[打印本页]
作者:
71v5
时间:
2014-07-06 01:05
标题:
freebsd9.2-处理器间中断-发送
有了前面"内核如何实现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:
icr.jpg
(46.86 KB, 下载次数: 84)
下载附件
2014-07-06 01:03 上传
/***************************************************************************
* 参数描述:
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 }
复制代码
欢迎光临 Chinaunix (http://bbs.chinaunix.net/)
Powered by Discuz! X3.2