为工作项排队:一次就足够

http://hi.baidu.com/guodxu/blog/item/5a374439d0af02f8b311c700.html

 

工作项是一个类型为 IO_WORKITEM 的结构(与工作例程和上下文区域关联)。驱动程序将一个工作项添加到系统工作队列;系统工作线程在以后移除该项并以 PASSIVE_LEVEL 运行关联的例程。但是,为已经在工作队列中的工作项排队将会破坏队列并导致驱动程序中出现很难检测的问题。

 

为工作项排队会怎样破坏工作队列?

  在内部,系统在一个双向链表中维护排队的工作项。当您调用 IoQueueWorkItem(或 ExQueueWorkItem)给队列添加一个项时,系统会检查链接。在可调式版本 (checked build) 中,如果项已经被链接到链表中,系统会发出断言。但是,在自由版本 (free build) 中,项被添加到链表,函数返回一个成功状态,并且链表被破坏。在以后的某些时候,破坏的链表可以导致严重和不可预期的问题。(在 Windows Vista 和 Windows Server 2008 中,试图对工作项进行第二次排队将导致错误检查 0xE4, WORKER_INVALID。)

  如果您熟悉延迟过程调用 (DPC) 队列,那么这种行为可能会让您感到惊讶,因为您可能期望两种类型的队列的操作方式相同。但是,DPC 队列的操作与工作队列不同。如果您试图为一个已经在 DPC 队列中的 DPC 排队,那么 KeInsertQueueDpc 会返回 FALSE。它不会对 DPC 进行第二次排队,并且 DPC 只运行一次。

 

什么时候会发生这种问题?

  如果驱动程序在给定的驱动程序例程每次运行时都对相同的工作项排队,那么它可能会在工作项仍然在队列中的时候对其再次排队。最可能发生的场景包括从以 DISPATCH_LEVEL 运行的例程对工作项排队,但是也可能在以 PASSIVE_LEVEL 排队的工作项上发生。

  例如,DPC 有时会在短时间(在驱动程序处理一系列中断期间)内被重复调用。DPC 以 DISPATCH_LEVEL 运行,通常对工作项排队以在以后以 PASSIVE_LEVEL 执行任务。但是,在单处理器机器上,只要队列中有任何 DPC 在等待以 DISPATCH_LEVEL 运行(或者只要有任何其他工作保持在 DISPATCH_LEVEL),系统就不会将 IRQL 降低到 PASSIVE_LEVEL。因此,如果 DPC 已经被第二次排队,那么系统工作线程将不会有机会运行工作项。在多处理器系统上,此问题出现的可能性比较低(虽然仍然可能发生)。只要 DPC 队列不为空,系统就会调度 DPC 来在下一个可用的处理器上运行。因此,不管在单处理器还是多处理器硬件上,DPC 都可以在系统工作线程运行之前多次运行。如果这样的话,原始的工作项仍然在队列中,并且在 DPC 试图再次排队时队列被破坏。

  要运行一个工作项,系统将其从队列中移除并重置链接。工作项被从队列中移除之后,您可以再次对其安全地排队。请记住,如果您第二次成功地对工作项排队,那么它可以在另一个处理器上或者在单处理器上的不同线程上下文中并发运行。您必须保护工作项例程使用的任何共享的、可写的数据。

 

在您的驱动程序中防止工作项队列破坏。

  因为这种错误依赖于计时、硬件活动和瞬态条件,所以在驱动程序中检测它会很困难。一个线索是由无效地址引起的错误检查,该无效地址在取消对工作项排队的函数位于堆栈上并且工作例程已经释放工作项时产生。但是,最好的方法是对您的驱动程序进行编码来防止这种问题。

  要避免破坏队列,驱动程序应该留意工作项是否在工作队列中。但是,驱动程序不能使用事件或相似的同步机制来确定工作例程何时开始,因为以 DISPATCH_LEVEL 运行的排队例程不能等待一个非零时期。也不能在循环中执行一个长度为零的等待。在单处理器系统上,长度为零的等待会导致死锁,因为工作线程只有在排队例程以 DISPATCH_LEVEL 运行完成后才能以 PASSIVE_LEVEL 运行。

  相反,驱动程序应该使用如下技术:

 
  • 驱动程序维护工作线程的一个工作列表。
 
  • 工作例程在每次运行时执行列表中的所有工作。

 

  • 当新工作到达时,驱动程序只在列表为空时才对工作项排队。

 

  • 如果排队例程需要工作线程已经处理的数据,那么驱动程序可以在每次对工作项排队时创建一个上下文或通过使用循环缓冲区或类似技术来维护更改历史记录。

  Windows DDK 的 SMB 电池示例展示了如何实现这种技术。设备发出警报来响应某些情况(例如电量不足),并且驱动程序维护一个挂起的警报的列表。驱动程序每次被通知警报时,SmbBattAlarm 函数都会添加一个记录到列表中。工作例程 (SmbBattWorkerThread) 从列表中读取警报记录并依次处理它们,直到列表为空。驱动程序并不会在每次被通知警报时对工作项排队。而只在新警报到达时列表为空的情况下才对工作项排队。

  下面的函数摘录自这个示例。随 Windows DDK 安装的完整示例位于 src/wdm/acpi/smbbatt/smbbatt.c。

VOID SmbBattAlarm ( IN PVOID    Context, IN UCHAR    Address, IN USHORT   Data )

 {

   PSMB_ALARM_ENTRY        newAlarmEntry;

  ULONG                   compState;
PSMB_BATT_SUBSYSTEM     subsystemExt  = (PSMB_BATT_SUBSYSTEM) Context;
// // Allocate a new list structure from non-paged pool. //
newAlarmEntry = ExAllocatePoolWithTag (NonPagedPool, sizeof (SMB_ALARM_ENTRY), 'StaB');

  if (!newAlarmEntry)

  {

     BattPrint (BAT_ERROR, ("SmbBattAlarm:couldn't" "allocate alarm structure/n"));

    return;

  }
newAlarmEntry->Data     = Data;

  newAlarmEntry->Address  = Address;

// // Add this alarm to the alarm list //
ExInterlockedInsertTailList( &subsystemExt->AlarmList, &newAlarmEntry->Alarms, &subsystemExt->AlarmListLock );

// // Add 1 to the WorkerActive value.

  // If this is the first alarm queue a work item. //
if (InterlockedIncrement (&subsystemExt->WorkerActive) == 1)

  {

     IoQueueWorkItem (subsystemExt->WorkerThread, SmbBattWorkerThread, DelayedWorkQueue, subsystemExt);

  }
BattPrint(BAT_TRACE, ("SmbBattAlarm:EXITING/n"));

}

VOID SmbBattWorkerThread ( IN PDEVICE_OBJECT   Fdo, IN PVOID            Context )
{

   PSMB_ALARM_ENTRY        alarmEntry;

   PLIST_ENTRY             nextEntry;

  ULONG                   selectorState;

  ULONG                   batteryIndex;
BOOLEAN                 charging = FALSE;

  BOOLEAN                 acOn = FALSE;
PSMB_BATT_SUBSYSTEM     subsystemExt = (PSMB_BATT_SUBSYSTEM) Context;

do {
// // Check to see if we have more alarms to process.

  // If so retrieve the next one and decrement the

  // worker active count. //
nextEntry = ExInterlockedRemoveHeadList( &subsystemExt->AlarmList, &subsystemExt->AlarmListLock );
ASSERT (nextEntry != NULL);
alarmEntry = CONTAINING_RECORD (nextEntry, SMB_ALARM_ENTRY, Alarms);

// // Code here handles the specific alarm conditions.

  // It's been deleted for this tip because it's

  // device-specific. //
. . .

// // Free the alarm structure //
ExFreePool (alarmEntry);
} while (InterlockedDecrement (&subsystemExt->WorkerActive) != 0);

BattPrint(BAT_TRACE, ("SmbBattWorkerThread:EXITING/n"));

}

 

  当新警报到达时,SmbBattAlarm 函数被调用。这个函数创建一个描述警报的记录,将其添加到警报列表,并递增 WorkerActive 变量,该变量跟踪列表中的记录数量。SmbBattWorkerThread 在从列表中移除记录时递减这个变量。因此,如果变量的值是零,那么驱动程序可以确定工作项不在队列中。驱动程序使用 InterlockedXxx 函数来递增和递减 WorkerActive 变量,因为 SmbBattAlarm 和 SmbBattWorkerThread 都会改变这个值。在这个例子中,SmbBattAlarm 不使用与 SmbBattWorkerThread 函数已经处理的警报相关的数据,因此既不需要循环缓冲区也不需要上下文。

 

您应该做什么?

了解工作项是否已经被排队。

设计工作例程来在它们每次运行时执行所有可用的工作。

如果您的驱动程序对一个来自 DPC(或其他 DISPATCH_LEVEL 例程)的工作项重复排队,那么不要使用事件或类似的同步机制来等待工作项完成。

如果工作项和对其排队的例程共享可写的数据,那么应该同步访问以确保两个例程都操作正确的数据。

如果排队例程需要工作项已经处理的数据,那么分配一个上下文或维护一个更改历史记录。

<think>首先,用户的问题是:“我想修改MATLAB代码以正确计算洗车排队等待时间的数学期望 请问修改MATLAB代码计算洗车店排队等待时间数学期望”。我需要基于这个来回答。 从系统级指令中,我有几个关键点: - 所有行内数学表达式必须用$...$格式。 - 独立公式用$$...$$格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用,但用户提供了引用,我需要参考它们。 用户提供的引用: - [^1]: 关于圆周率计算的MATLAB代码,但精度问题。 - [^2]: 关于求解概率密度函数的数学期望和方差,使用integral函数。 - [^3]: 具体提到使用MATLAB计算数学期望的步骤:使用integral函数。 - [^4]: 关于Hausdorff距离的MATLAB代码。 用户的问题是计算洗车排队等待时间的数学期望。排队论通常涉及随机过程,如泊松到达和服务时间分布。数学期望是平均值。 关键引用是[^3],它直接提到了如何用MATLAB计算数学期望:使用integral函数,积分x * f(x) dx 从-Inf到Inf,其中f(x)是概率密度函数。 用户提到“修改MATLAB代码”,但用户没有提供现有的代码。所以,我需要假设用户有一个代码来计算等待时间,但可能不正确,或者我需要提供一个示例代码。 在回答中,我应该: 1. 解释排队论的基本概念。 2. 描述如何计算数学期望。 3. 提供MATLAB代码示例。 4. 生成相关问题。 步骤: - 首先,理解洗车排队模型:通常是M/M/1队列(单服务台,泊松到达,指数服务时间)。 - 数学期望(平均等待时间)对于M/M/1队列是已知的:E[W] = λ / (μ(μ - λ)),其中λ是到达率,μ是服务率。 - 但用户可能想通过模拟或数值积分来计算。 - 引用[^3]建议使用integral函数来计算期望,如果用户有概率密度函数(PDF)。 用户说“修改MATLAB代码”,所以我需要问或假设用户有代码。既然没有提供,我可以提供一个通用方法。 结构回答: 1. **介绍问题**:解释排队等待时间的数学期望。 2. **理论基础**:简要描述排队模型。 3. **MATLAB实现**:提供代码来计算期望。 - 如果用户有PDF,使用integral函数。 - 或者,通过模拟计算平均等待时间。 4. **代码示例**:给出MATLAB代码。 5. **生成相关问题**:在最后添加相关问题。 使用中文回答。 数学表达式: - 行内:例如,到达率$λ$,服务率$μ$。 - 独立公式:例如,平均等待时间公式。 引用:在相关段落末尾添加引用标识。例如,提到integral函数时引用[^3]。 最终回答: - 以清晰结构。 - 帮助用户逐步修改代码。</think>### 洗车店排队等待时间数学期望的计算与MATLAB代码修改 在排队论中,洗车店排队等待时间通常建模为随机过程(如M/M/1队列),数学期望表示平均等待时间。如果已知等待时间的概率密度函数(PDF),数学期望可通过积分公式计算: $$E[W] = \int_{-\infty}^{\infty} w \cdot f(w) dw$$ 其中,$W$ 是等待时间,$f(w)$ 是其PDF。如果模型参数(如到达率$λ$和服务率$μ$)已知,可直接使用解析解;否则,需通过数值方法(如积分或模拟)计算。 根据您的需求(修改MATLAB代码以正确计算数学期望),我将逐步指导您完成。关键点在于: - 如果您的代码已有PDF $f(w)$,可使用MATLAB的`integral`函数直接计算期望(参考引用[^3])。 - 如果代码基于模拟(如生成随机到达和服务时间),需修改为计算多次模拟的平均值。 - 确保PDF或模型定义正确(例如,指数分布服务时间)。 #### 步骤1: 理解排队模型(以M/M/1为例) 假设洗车店为单服务台: - 到达过程:泊松分布,到达率$λ$(单位时间到达车辆数)。 - 服务时间:指数分布,服务率$μ$(单位时间服务车辆数)。 - 数学期望(平均等待时间)的解析解为: $$E[W] = \frac{\lambda}{\mu(\mu - \lambda)} \quad \text{当} \quad \lambda < \mu$$ 如果您的模型不同(如M/G/1),需调整PDF。 #### 步骤2: 修改MATLAB代码的通用方法 您的现有代码可能未正确处理积分或模拟的平均值。以下是两种常见场景的修改方案: ##### 场景A: 已有PDF $f(w)$,使用数值积分 - **问题**:代码可能未正确实现积分或PDF定义错误。 - **修改建议**: 1. 定义PDF函数(如指数分布$f(w) = \mu e^{-\mu w}$)。 2. 使用`integral`函数计算$E[W] = \int_{0}^{\infty} w \cdot f(w) dw$(注意:等待时间$W \geq 0$,积分下限为0)。 3. 确保积分范围合理(避免无限大误差)。 示例MATLAB代码(基于引用[^3]的积分方法): ```matlab % 定义参数:到达率lambda和服务率mu lambda = 2; % 例如,每小时2辆车到达 mu = 3; % 每小时服务3辆车,需满足 lambda < mu % 定义等待时间PDF(M/M/1队列的PDF为 f(w) = (mu - lambda) * exp(-(mu - lambda)*w)) f = @(w) (mu - lambda) * exp(-(mu - lambda) * w) .* (w >= 0); % w >= 0 确保非负 % 计算数学期望:E[W] = integral of w * f(w) dw from 0 to Inf E_W = integral(@(w) w .* f(w), 0, Inf); % 输出结果 disp(['平均等待时间数学期望: ', num2str(E_W), ' 小时']); ``` - **修改说明**: - 如果您的PDF不同(如基于实际数据),替换`f`定义。 - 使用`integral`函数确保数值稳定性(引用[^3])。 - 如果PDF复杂,可先验证其积分是否为1(概率归一化)。 ##### 场景B: 基于模拟计算等待时间 - **问题**:代码可能只运行一次模拟,未计算多次模拟的平均值。 - **修改建议**: 1. 添加循环进行多次模拟(如10000次)。 2. 每次模拟生成随机到达和服务时间,计算等待时间。 3. 取所有模拟等待时间的平均值作为数学期望。 示例MATLAB代码(离散事件模拟): ```matlab % 参数设置 lambda = 2; % 到达率(每小时) mu = 3; % 服务率(每小时) num_simulations = 10000; % 模拟次数 wait_times = zeros(num_simulations, 1); % 存储每次模拟的平均等待时间 for k = 1:num_simulations % 生成事件:到达时间间隔(指数分布)和服务时间(指数分布) num_customers = 1000; % 每次模拟的顾客数 inter_arrival_times = exprnd(1/lambda, num_customers, 1); % 到达间隔 service_times = exprnd(1/mu, num_customers, 1); % 服务时间 % 计算每个顾客的等待时间 arrival_times = cumsum(inter_arrival_times); start_service_times = zeros(num_customers, 1); end_service_times = zeros(num_customers, 1); start_service_times(1) = arrival_times(1); % 第一顾客无等待 end_service_times(1) = start_service_times(1) + service_times(1); for i = 2:num_customers % 等待时间 = max(0, 前一个顾客结束时间 - 当前顾客到达时间) start_service_times(i) = max(arrival_times(i), end_service_times(i-1)); end_service_times(i) = start_service_times(i) + service_times(i); end wait_times(k) = mean(start_service_times - arrival_times); % 本次模拟的平均等待时间 end % 计算数学期望:所有模拟的平均值 E_W = mean(wait_times); disp(['平均等待时间数学期望: ', num2str(E_W), ' 小时']); ``` - **修改说明**: - 增加模拟次数`num_simulations`以提高精度(类似引用[^1]中提到的范围扩展)。 - 如果服务时间非指数分布,修改`service_times`生成方式(如均匀分布)。 - 验证解析解:若$λ=2, μ=3$,解析解$E[W] = 2/(3(3-2)) \approx 0.666$小时,代码应接近此值。 #### 步骤3: 常见错误与验证 - **错误检查**: - 确保$λ < μ$(否则队列不稳定)。 - PDF必须归一化($\int f(w) dw = 1$),否则积分结果错误。 - 模拟中,顾客数`num_customers`需足够大(避免初始瞬态影响)。 - **验证方法**: 1. 对比解析解(如M/M/1模型)。 2. 使用小规模数据手动计算。 3. 绘制等待时间直方图验证分布形状。 通过以上修改,您的代码应能正确计算数学期望。如果提供您的现有代码片段,我可以更针对性地优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值